#include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ds1302.h" #include "graphics.h" #include "gui.h" #include "pins.h" #include "ss49e.h" #include "st7789.h" #include "units.h" void on_gpio_interrupt(uint pin, uint32_t eventMask); static void __not_in_flash_func(dma_irq_handler)(); int64_t button_debounce_callback(alarm_id_t alarmId, void *userData); int64_t button_hold_callback(alarm_id_t alarmId, void *userData); void on_button_short_press(uint button); void on_button_long_press(uint button); void on_wheel_hall_sensor_tick(); void init_button_states(); void save_to_rtc(); bool load_from_rtc(); void reset(); const uint8_t FRONT_BUTTON_PINS[4] = {PIN_BUTTON_0, PIN_BUTTON_1, PIN_BUTTON_2, PIN_BUTTON_3}; const uint16_t BRIGHTNESS_CONVERT_TABLE_X[] = {0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; const uint16_t BRIGHTNESS_CONVERT_TABLE_Y[] = {0, 7, 12, 21, 37, 64, 111, 194, 338, 588, 1024}; const size_t BRIGHTNESS_CONVERT_LABLE_LEN = 11; int16_t convert_brightness(int16_t input) { for (int i = 0; i < BRIGHTNESS_CONVERT_LABLE_LEN - 1; ++i) { if (input < BRIGHTNESS_CONVERT_TABLE_X[i + 1]) { int16_t x0 = BRIGHTNESS_CONVERT_TABLE_X[i]; int16_t x1 = BRIGHTNESS_CONVERT_TABLE_X[i + 1]; int16_t y0 = BRIGHTNESS_CONVERT_TABLE_Y[i]; int16_t y1 = BRIGHTNESS_CONVERT_TABLE_Y[i + 1]; return y0 + (y1 - y0) * (input - x0) / (x1 - x0); } } return -1; } struct PushButtonState { alarm_id_t debounceAlarm, holdAlarm; bool currentState, prevState; }; static struct PushButtonState buttonStates[4]; static uint heldButtons; // for debug (and for the future) absolute_time_t lastWheelTickTime = 0; // for hall sensors int adcDmaChannelA, adcDmaChannelB; uint16_t adcBufferA[1600]; uint16_t adcBufferB[1600]; SS49E_State wheelSensorState; // for speed measurement uint8_t intervalWheelTicks[SPEED_MEASUREMENT_WINDOW_SIZE]; uint8_t currentInterval = 0; uint16_t ticksPerWindow = 0; // odometer counters uint totalWheelTicks = 0; uint wheelTicksFromPowerOn = 0; DateTime currentDatetime; // unused currently // repeating_timer_t audioPlaybackTimer; int main() { #ifdef LOGGING stdio_usb_init(); #endif // display init const struct st7789_config displayConfig = { .spi = PICO_DEFAULT_SPI_INSTANCE(), .gpio_din = PIN_LCD_SDA, .gpio_clk = PIN_LCD_SCL, .gpio_cs = PIN_LCD_CS, .gpio_dc = PIN_LCD_DATA_COMMAND, .gpio_rst = PIN_LCD_RESET, .gpio_bl = PIN_LCD_BACKLIGHT, }; st7789_init(&displayConfig, LCD_WIDTH + LCD_OFFSET_X, LCD_HEIGHT + LCD_OFFSET_Y); // splash image (just for fun) const uint16_t *splashImage = (const uint16_t *)(XIP_NOCACHE_NOALLOC_NOTRANSLATE_BASE + 1581056); if (splashImage) { st7789_caset(LCD_OFFSET_X, LCD_OFFSET_X + 317); st7789_raset(LCD_OFFSET_Y, LCD_OFFSET_Y + 171); st7789_write(splashImage, 318 * 172 * sizeof(uint16_t)); } else { rom_reset_usb_boot(0, 0); } sleep_ms(1000); #ifdef LOGGING if (psram_is_available()) { printf("PSRAM available. detecting size... "); size_t psramSize = psram_detect_size(); printf("%u\n", psramSize); } #endif gpio_set_irq_callback(on_gpio_interrupt); // hall sensor (wheel only for now) adc_init(); adc_gpio_init(42); adc_select_input(2); adc_set_round_robin(1 << 2); adc_set_clkdiv(3000.0f / 1.0f); // 16kHz for 1 sensor adc_fifo_setup(true, true, 1, false, false); // wheel sensor (with N-S alternating magnet arrangement) ss49e_init(&wheelSensorState, MAGNET_POLTYPE_BIPOLAR, on_wheel_hall_sensor_tick); // DMA for hall sensor adcDmaChannelA = dma_claim_unused_channel(true); adcDmaChannelB = dma_claim_unused_channel(true); dma_channel_config dmaCfg = dma_channel_get_default_config(adcDmaChannelA); channel_config_set_transfer_data_size(&dmaCfg, DMA_SIZE_16); channel_config_set_read_increment(&dmaCfg, false); channel_config_set_write_increment(&dmaCfg, true); channel_config_set_dreq(&dmaCfg, DREQ_ADC); channel_config_set_chain_to(&dmaCfg, adcDmaChannelB); dma_channel_configure(adcDmaChannelA, &dmaCfg, adcBufferA, &adc_hw->fifo, 1600, false); dmaCfg = dma_channel_get_default_config(adcDmaChannelB); channel_config_set_transfer_data_size(&dmaCfg, DMA_SIZE_16); channel_config_set_read_increment(&dmaCfg, false); channel_config_set_write_increment(&dmaCfg, true); channel_config_set_dreq(&dmaCfg, DREQ_ADC); channel_config_set_chain_to(&dmaCfg, adcDmaChannelA); dma_channel_configure(adcDmaChannelB, &dmaCfg, adcBufferB, &adc_hw->fifo, 1600, false); dma_channel_set_irq0_enabled(adcDmaChannelA, true); dma_channel_set_irq0_enabled(adcDmaChannelB, true); irq_set_exclusive_handler(DMA_IRQ_0, dma_irq_handler); irq_set_enabled(DMA_IRQ_0, true); adc_run(true); dma_channel_start(adcDmaChannelA); // on-board LED gpio_init(25); gpio_set_dir(25, true); // front buttons init_button_states(); gpio_init_mask(0b1111 << 15); for (int pin = 15; pin <= 18; ++pin) { gpio_pull_up(pin); gpio_set_irq_enabled(pin, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true); } irq_set_enabled(IO_IRQ_BANK0, 1); ds1302_init(); // DateTime tmp = {26, 9, 9, 15, 10, 0, DOW_WED}; // ds1302_set_datetime(&tmp); load_from_rtc(); gpio_set_function(PIN_LCD_BACKLIGHT, GPIO_FUNC_PWM); pwm_set_wrap(BACKLIGHT_PWM_SLICE, 1024); pwm_set_chan_level(BACKLIGHT_PWM_SLICE, BACKLIGHT_PWM_CHANNEL, 1024); pwm_set_enabled(BACKLIGHT_PWM_SLICE, true); gpio_set_function(7, GPIO_FUNC_PWM); pwm_set_wrap(3, 256); pwm_set_chan_level(3, PWM_CHAN_B, 0); pwm_set_enabled(3, true); gui_init(); absolute_time_t nextLcdUpdateTime = delayed_by_ms(get_absolute_time(), 50); absolute_time_t nextClockUpdateTime = get_absolute_time(); absolute_time_t nextSaveToRtcTime = delayed_by_ms(get_absolute_time(), 10000); while (true) { absolute_time_t t = get_absolute_time(); if (t >= nextClockUpdateTime) { nextClockUpdateTime = delayed_by_ms(t, 1000); ds1302_get_datetime(¤tDatetime); printf("time is %i %i %i %i %i %i %i\n", currentDatetime.year, currentDatetime.month, currentDatetime.day, currentDatetime.dow, currentDatetime.hour, currentDatetime.minute, currentDatetime.second); } if (t >= nextLcdUpdateTime) { nextLcdUpdateTime = delayed_by_ms(t, 50); update_lcd(); if (gpio_get(PIN_BUTTON_0) == 0 && gpio_get(PIN_BUTTON_3) == 0) { rom_reset_usb_boot(0, 0); } } if (t >= nextSaveToRtcTime) { nextSaveToRtcTime = delayed_by_ms(t, 10000); save_to_rtc(); } sleep_until(nextLcdUpdateTime); } return 0; } void on_gpio_interrupt(uint pin, uint32_t eventMask) { if (pin == PIN_BUTTON_0 || pin == PIN_BUTTON_1 || pin == PIN_BUTTON_2 || pin == PIN_BUTTON_3) { uint buttonIndex; switch (pin) { case PIN_BUTTON_0: buttonIndex = 0; break; case PIN_BUTTON_1: buttonIndex = 1; break; case PIN_BUTTON_2: buttonIndex = 2; break; case PIN_BUTTON_3: buttonIndex = 3; } struct PushButtonState *buttonState = &buttonStates[buttonIndex]; if (buttonState->debounceAlarm != -1) cancel_alarm(buttonState->debounceAlarm); buttonState->debounceAlarm = add_alarm_in_us(BUTTON_DEBOUNCE_DELAY_us, button_debounce_callback, (void *)buttonIndex, false); gpio_acknowledge_irq(pin, eventMask); } } static void __not_in_flash_func(dma_irq_handler)() { if (dma_hw->ints0 & (1u << adcDmaChannelA)) { dma_hw->ints0 = 1u << adcDmaChannelA; for (uint i = 0; i < 1600; ++i) ss49e_process_sample(&wheelSensorState, adcBufferA[i]); float sum = 0.0f, max = 0.0f, min = 9999.0f; for (uint i = 0; i < 1600; ++i) { sum += adcBufferA[i]; if (adcBufferA[i] > max) max = adcBufferA[i]; if (adcBufferA[i] < min) min = adcBufferA[i]; } sum /= 1600; printf("%f ~ %f ~ %f\n", min, sum, max); dma_channel_set_write_addr(adcDmaChannelA, adcBufferA, false); dma_channel_set_trans_count(adcDmaChannelA, 1600, false); sum = 0; for (int i = 0; i < 16; ++i) { sum += intervalWheelTicks[i]; } currentInterval = (currentInterval + 1) % 16; intervalWheelTicks[currentInterval] = 0; ticksPerWindow = sum; } if (dma_hw->ints0 & (1u << adcDmaChannelB)) { dma_hw->ints0 = 1u << adcDmaChannelB; for (uint i = 0; i < 1600; ++i) ss49e_process_sample(&wheelSensorState, adcBufferB[i]); float sum = 0.0f, max = 0.0f, min = 9999.0f; for (uint i = 0; i < 1600; ++i) { sum += adcBufferB[i]; if (adcBufferB[i] > max) max = adcBufferB[i]; if (adcBufferB[i] < min) min = adcBufferB[i]; } sum /= 1600; printf("%f ~ %f ~ %f\n", min, sum, max); dma_channel_set_write_addr(adcDmaChannelB, adcBufferB, false); dma_channel_set_trans_count(adcDmaChannelB, 1600, false); sum = 0; for (int i = 0; i < 16; ++i) { sum += intervalWheelTicks[i]; } currentInterval = (currentInterval + 1) % 16; intervalWheelTicks[currentInterval] = 0; ticksPerWindow = sum; } } int64_t button_debounce_callback(alarm_id_t alarmId, void *userData) { (void)alarmId; uint buttonIndex = (uint)userData; struct PushButtonState *buttonState = &buttonStates[buttonIndex]; bool currentState = !gpio_get(FRONT_BUTTON_PINS[buttonIndex]); if (currentState != buttonState->prevState) { buttonState->prevState = currentState; if (buttonState->holdAlarm != -1) { cancel_alarm(buttonState->holdAlarm); } if (currentState == 1) { buttonState->holdAlarm = add_alarm_in_ms(BUTTON_HOLD_TIME_ms, button_hold_callback, (void *)buttonIndex, false); } else { buttonState->holdAlarm = -1; if (!(heldButtons & (1 << buttonIndex))) on_button_input(buttonIndex, BUTTON_SHORT_PRESS, heldButtons); else heldButtons &= ~(1 << buttonIndex); } } return 0; } int64_t button_hold_callback(alarm_id_t alarmId, void *userData) { (void)alarmId; uint buttonIndex = (uint)userData; struct PushButtonState *buttonState = &buttonStates[buttonIndex]; buttonState->holdAlarm = -1; heldButtons |= 1 << buttonIndex; on_button_input(buttonIndex, BUTTON_LONG_PRESS, heldButtons); return 0; } void on_wheel_hall_sensor_tick() { ++intervalWheelTicks[currentInterval]; ++totalWheelTicks; ++wheelTicksFromPowerOn; printf("\t\t\tTICK\7\n"); } void init_button_states() { for (int i = 0; i < 4; ++i) { buttonStates[i].debounceAlarm = -100; buttonStates[i].holdAlarm = -1; buttonStates[i].currentState = 0; buttonStates[i].prevState = 0; } heldButtons = 0; } void reset() { pwm_set_chan_level(BACKLIGHT_PWM_SLICE, BACKLIGHT_PWM_CHANNEL, 0); // turn off the screen (*((volatile uint32_t *)(PPB_BASE + 0x0ED0C))) = 0x5FA0004; // weird trick to actually reset the controller } void save_to_rtc() { printf("saving distance to RTC... "); static bool savedBefore = false; static uint savedDistance; uint8_t buffer[31]; memset(buffer, 0, 31); uint newDistance = totalWheelTicks * cmPerTick / 10; if (savedBefore && newDistance == savedDistance) { printf("skipped saving\n"); return; } *(uint *)(buffer + 0) = newDistance; uint16_t checksum = 0; for (int i = 0; i < 29; ++i) { checksum += buffer[i]; } *(uint16_t *)(buffer + 29) = checksum; uint8_t checkBuffer[31]; int limit = 10; do { printf("\nattempting to write...\n"); ds1302_set_write_protection(0); ds1302_write_ram_bulk(buffer, 31); for (int i = 0; i < 30; ++i) printf("%02x ", buffer[i]); printf("%02x\n", buffer[30]); ds1302_read_ram_bulk(checkBuffer, 31); printf("attempting to read back...\n"); for (int i = 0; i < 30; ++i) printf("%02x ", checkBuffer[i]); printf("%02x\n", checkBuffer[30]); if (limit-- == 0) break; } while (memcmp(buffer, checkBuffer, 31) != 0); #ifdef LOGGING for (int i = 0; i < 30; ++i) printf("%02x ", buffer[i]); printf("%02x\n", buffer[30]); #endif savedDistance = newDistance; savedBefore = true; #ifdef LOGGING printf("saved\n"); #endif } bool load_from_rtc() { uint8_t buffer[31]; ds1302_read_ram_bulk(buffer, 31); #ifdef LOGGING for (int i = 0; i < 30; ++i) printf("%02x ", buffer[i]); printf("%02x\n", buffer[30]); #endif uint16_t checksum = *(uint16_t *)(buffer + 29); uint16_t actualChecksum = 0; for (int i = 0; i < 29; ++i) { actualChecksum += buffer[i]; } if (actualChecksum != checksum) { #ifdef LOGGING printf("discarding invalid data in RTC memory\n"); #endif return false; } uint savedDistance = *(uint *)(buffer + 0); totalWheelTicks = savedDistance * 10 / cmPerTick; #ifdef LOGGING printf("successfully loaded data from RTC memory\n"); #endif return true; }