443 lines
13 KiB
C
443 lines
13 KiB
C
#include "config.h"
|
|
|
|
#include <boot/picobin.h>
|
|
#include <hardware/adc.h>
|
|
#include <hardware/dma.h>
|
|
#include <hardware/flash.h>
|
|
#include <hardware/gpio.h>
|
|
#include <hardware/irq.h>
|
|
#include <hardware/psram.h>
|
|
#include <hardware/pwm.h>
|
|
#include <hardware/regs/intctrl.h>
|
|
#include <pico/bootrom.h>
|
|
#include <pico/critical_section.h>
|
|
#include <pico/stdlib.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#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;
|
|
}
|