diff options
Diffstat (limited to 'fw/main.c')
-rw-r--r-- | fw/main.c | 502 |
1 files changed, 502 insertions, 0 deletions
diff --git a/fw/main.c b/fw/main.c new file mode 100644 index 0000000..c60dff0 --- /dev/null +++ b/fw/main.c @@ -0,0 +1,502 @@ +/* 8seg LED display driver firmware + * Copyright (C) 2018 Sebastian Götte <code@jaseg.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + + +#include "global.h" +#include "serial.h" +#include "i2c.h" +#include "lcd1602.h" + +#include "mini-printf.h" + +/* Part number: STM32F030F4C6 */ + +volatile unsigned int comm_led_ctr, err_led_ctr; +volatile unsigned int sys_time_tick = 0; +volatile unsigned int sys_time_ms; +volatile unsigned int sys_time_s; +volatile unsigned int sys_flag_1Hz; +unsigned int frame_duration_us; +volatile uint8_t global_brightness; /* FIXME implement sending */ +volatile bool display_update_flag; +volatile bool adc_trigger_flag = 0; +volatile bool backchannel_trigger_flag = 0; + +void run_menu(void); +void update_display(void); +void calc_next_alarm(void); + +uint8_t random() { + static uint8_t x, a, b, c; + x++; //x is incremented every round and is not affected by any other variable + a = (a ^ c ^ x); //note the mix of addition and XOR + b = (b + a); //And the use of very few instructions + c = ((c + ((b >> 1) ^ a))); // the AES S-Box Operation ensures an even distributon of entropy + return c; +} + +volatile int input_hold_total[5] = {0}; +volatile int input_triggered = 0; +volatile int input_state = 0; + +void rtc_adjust(int field, int adj) { + RTC->ISR = RTC_ISR_INIT; + while (!(RTC->ISR & RTC_ISR_INITF)) + ; + + int offx = field*8; + int fmax = field == 2 ? 24 : 60; + + int tr = RTC->TR; + int fval = (tr >> offx) & 0xff; + + fval = (fval&0xf) + 10*(fval>>4); + + if (field == 0) { + /* seconds field special handling: set seconds to 0 when pressing down, ignore up */ + if (adj < 0) + fval = 0; + } else { + fval += adj; + } + + if (fval < 0) + fval = fmax-1; + if (fval >= fmax) + fval = 0; + + fval = ((fval/10)<<4) | (fval % 10); + + RTC->TR = (tr & ~(0xff<<offx)) | (fval << offx); + RTC->ISR = 0; + while (!(RTC->ISR & RTC_ISR_RSF)) + ; +} + +int main(void) { + /* Startup code */ + RCC->CR |= RCC_CR_HSEON; + while (!(RCC->CR&RCC_CR_HSERDY)); + RCC->CFGR &= ~RCC_CFGR_PLLMUL_Msk & ~RCC_CFGR_SW_Msk & ~RCC_CFGR_PPRE_Msk & ~RCC_CFGR_HPRE_Msk; + RCC->CFGR |= ((6-2)<<RCC_CFGR_PLLMUL_Pos) | RCC_CFGR_PLLSRC_HSE_PREDIV; /* PLL x6 -> 48.0MHz */ + RCC->CR |= RCC_CR_PLLON; + while (!(RCC->CR&RCC_CR_PLLRDY)); + RCC->CFGR |= (2<<RCC_CFGR_SW_Pos); + RCC->AHBENR |= RCC_AHBENR_DMAEN | RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN | RCC_AHBENR_FLITFEN; + RCC->APB1ENR |= RCC_APB1ENR_TIM3EN | RCC_APB1ENR_PWREN | RCC_APB1ENR_I2C1EN; + RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN | RCC_APB2ENR_ADCEN| RCC_APB2ENR_DBGMCUEN | RCC_APB2ENR_USART1EN | RCC_APB2ENR_SPI1EN | RCC_APB2ENR_TIM1EN; + RCC->CFGR3 |= RCC_CFGR3_I2C1SW; /* Route 48MHz system clock to I2C1 */ + + PWR->CR |= PWR_CR_DBP; + RCC->BDCR = RCC_BDCR_BDRST; + RCC->BDCR = 0; + RCC->BDCR = RCC_BDCR_RTCEN | (3<<RCC_BDCR_RTCSEL_Pos); + + RTC->WPR = 0xca; + RTC->WPR = 0x53; + RTC->ISR = RTC_ISR_INIT; + while (!(RTC->ISR & RTC_ISR_INITF)) + ; + RTC->PRER = (99 << RTC_PRER_PREDIV_A_Pos) | (2499 << RTC_PRER_PREDIV_S_Pos); + RTC->SSR = 0; + RTC->TR = 0; + RTC->DR = 0; + RTC->TSTR = 0; + RTC->TSDR = 0; + RTC->CR |= RTC_CR_ALRAIE; + EXTI->IMR |= 1<<17; + EXTI->RTSR |= 1<<17; + RTC->ISR = 0; + while (!(RTC->ISR & RTC_ISR_RSF)) + ; + + SystemCoreClockUpdate(); + SysTick_Config(SystemCoreClock/(1000/TICK_MS)); /* 10ms interval */ + NVIC_EnableIRQ(SysTick_IRQn); + NVIC_SetPriority(SysTick_IRQn, 3<<5); + + /* GPIO setup + * + * Note: since we have quite a bunch of pin constraints we can't actually use complementary outputs for the + * complementary MOSFET driver control signals (CTRL_A & CTRL_B). Instead, we use two totally separate output + * channels (1 & 4) and emulate the dead-time generator in software. + */ + GPIOA->MODER |= + (2<<GPIO_MODER_MODER6_Pos) /* PA6 - servo / TIM3_CH1 */ + | (2<<GPIO_MODER_MODER9_Pos) /* PA9 - SCL */ + | (2<<GPIO_MODER_MODER10_Pos);/* PA10 - SDA */ + + GPIOA->AFR[0] = + (1<<GPIO_AFRH_AFSEL9_Pos); /* PA6 */ + GPIOA->AFR[1] = + (4<<GPIO_AFRH_AFSEL9_Pos) /* PA9 */ + | (4<<GPIO_AFRH_AFSEL10_Pos);/* PA10 */ + + for (int i=0; i<5; i++) + GPIOA->PUPDR |= 1<<(2*i); + + /* I2C for LCD, temp sensor, current sensor */ + i2c_config_filters(I2C1, I2C_AF_ENABLE, 0); + i2c_config_timing(I2C1, 0x50330309); /* Magic value for 400kHz I2C @ 48MHz CLK. From the datasheet. The other option + is to have this fall out of STMCubeMX. I love downloading 120MB of software + to download another 100MB of software, only this time over unsecured HTTP, + to generate 3.5 bytes of configuration values using a Java(TM) GUI. */ + i2c_enable(I2C1); + lcd1602_init(); + + lcd_write_str(0, 0, "timer initialized \xbc" LCD_FILL); + lcd_write_str(0, 1, LCD_FILL); + + /* servo output */ + TIM1->CR1 = TIM_CR1_CEN; + TIM1->PSC = 48-1; /* 1us ticks */ + TIM1->ARR = 1000-1; /* 1ms interval */ + TIM1->EGR = TIM_EGR_UG; + TIM1->CCMR1 = (6<<TIM_CCMR1_OC1M_Pos); + TIM1->CCER = TIM_CCER_CC1E; + TIM1->DIER = TIM_DIER_UIE; + + TIM3->CR1 = TIM_CR1_CEN; + TIM3->PSC = 48-1; /* 1us ticks */ + TIM3->ARR = 20000-1; /* 20ms interval */ + TIM3->EGR = TIM_EGR_UG; + + NVIC_EnableIRQ(TIM1_BRK_UP_TRG_COM_IRQn); + NVIC_EnableIRQ(RTC_IRQn); + calc_next_alarm(); + + uint32_t last_update_ms = 0; + const int display_update_ivl_ms = 50; + while(23) { + int delta = sys_time_ms - last_update_ms; + if (delta < 0) + delta += 1000; + if (delta > display_update_ivl_ms) + update_display(); + } +} + +enum { + MENU_DEFAULT, + MENU_SET_TIME, + MENU_SET_ALARM, +} menu_state; + +int menu_adjustment_done = 0; +int sel_field_idx = 0; +int menu_alarm = 0; + +enum { + INPUT_LEFT, + INPUT_DOWN, + INPUT_UP, + INPUT_RIGHT, + INPUT_MENU +}; + +enum { + ALR_TON, + ALR_TOFF, + NUM_ALARMS +}; + +int alarms[NUM_ALARMS] = { + 9<<16 | 0<<8 | 0, + 21<<16 | 0<<8 | 0, +}; +const char *alarm_names[NUM_ALARMS] = {" ON", "OFF"}; +int next_alarm = -1; + +void RTC_IRQHandler() { + RTC->ISR &= ~RTC_ISR_ALRAF; + EXTI->PR = 1<<17; + calc_next_alarm(); + /* TODO FIXME handle alarm */ +} + +void adj_alarm(int alarm, int sel, int adj) { + int fields[3] = { alarms[alarm]>>16, (alarms[alarm]>>8) & 0xff, alarms[alarm] & 0xff }; + int max[3] = { 24, 60, 60 }; + sel = 2-sel; + + fields[sel] += adj; + if (fields[sel] < 0) + fields[sel] += max[sel]; + if (fields[sel] >= max[sel]) + fields[sel] -= max[sel]; + + alarms[alarm] = (fields[0]<<16) | (fields[1]<<8) | fields[2]; +} + +void calc_next_alarm() { + int tr = RTC->TR; + int h = ((tr>>RTC_TR_HT_Pos)&3)*10 + ((tr>>RTC_TR_HU_Pos)&0xf), + m = ((tr>>RTC_TR_MNT_Pos)&7)*10 + ((tr>>RTC_TR_MNU_Pos)&0xf), + s = ((tr>>RTC_TR_ST_Pos)&7)*10 + ((tr>>RTC_TR_SU_Pos)&0xf); + int tr_al_fmt = h<<16 | m<<8 | s; + + int min_alr = 0x7fffffff; + int min_idx = -1; + for (int i=0; i<NUM_ALARMS; i++) { + if (alarms[i] > tr_al_fmt && alarms[i] < min_alr) { + min_alr = alarms[i]; + min_idx = i; + } + } + + if (min_idx == -1) { /* handle rollover */ + for (int i=0; i<NUM_ALARMS; i++) { + if (alarms[i] < min_alr) { + min_alr = alarms[i]; + min_idx = i; + } + } + } + + next_alarm = min_idx; + RTC->CR &= ~RTC_CR_ALRAE; + while (!(RTC->ISR & RTC_ISR_ALRAWF)) + ; + h = min_alr>>16, m = (min_alr>>8)&0xff, s = min_alr & 0xff; + RTC->ALRMAR = RTC_ALRMAR_MSK4 + | (h/10 << RTC_ALRMAR_HT_Pos) | (h%10 << RTC_ALRMAR_HU_Pos) + | (m/10 << RTC_ALRMAR_MNT_Pos) | (m%10 << RTC_ALRMAR_MNU_Pos) + | (s/10 << RTC_ALRMAR_ST_Pos) | (s%10 << RTC_ALRMAR_SU_Pos); + RTC->ISR &= ~RTC_ISR_ALRAF; + RTC->CR |= RTC_CR_ALRAE; +} + +void update_display() { + char buf[17]; + bool blink_flag = sys_time_ms < 300; + int tr = RTC->TR; + + switch (menu_state) { + case MENU_DEFAULT: + mini_snprintf(buf, sizeof(buf), "%d%d:%d%d:%d%d" LCD_FILL, + (tr>>RTC_TR_HT_Pos)&3, (tr>>RTC_TR_HU_Pos)&0xf, + (tr>>RTC_TR_MNT_Pos)&7, (tr>>RTC_TR_MNU_Pos)&0xf, + (tr>>RTC_TR_ST_Pos)&7, (tr>>RTC_TR_SU_Pos)&0xf); + lcd_write_str(0, 0, buf); + if (next_alarm < 0) { + mini_snprintf(buf, sizeof(buf), LCD_FILL); + } else { + int next = alarms[next_alarm]; + int h = next>>16, m = (next>>8) & 0xff, s = next & 0xff; + mini_snprintf(buf, sizeof(buf), "%s AT %02d:%02d:%02d" LCD_FILL, alarm_names[next_alarm], h, m, s); + } + lcd_write_str(0, 1, buf); + break; + + case MENU_SET_TIME: + mini_snprintf(buf, sizeof(buf), "%d%d:%d%d:%d%d" LCD_FILL, + (tr>>RTC_TR_HT_Pos)&3, (tr>>RTC_TR_HU_Pos)&0xf, + (tr>>RTC_TR_MNT_Pos)&7, (tr>>RTC_TR_MNU_Pos)&0xf, + (tr>>RTC_TR_ST_Pos)&7, (tr>>RTC_TR_SU_Pos)&0xf); + if (sel_field_idx == 0 && blink_flag) + buf[6] = buf[7] = ' '; + if (sel_field_idx == 1 && blink_flag) + buf[3] = buf[4] = ' '; + if (sel_field_idx == 2 && blink_flag) + buf[0] = buf[1] = ' '; + lcd_write_str(0, 0, buf); + mini_snprintf(buf, sizeof(buf), "SET TIME" LCD_FILL); + lcd_write_str(0, 1, buf); + break; + + case MENU_SET_ALARM: + mini_snprintf(buf, sizeof(buf), "%02d:%02d:%02d" LCD_FILL, + alarms[menu_alarm]>>16, (alarms[menu_alarm]>>8) & 0xff, alarms[menu_alarm] & 0xff); + if (sel_field_idx == 0 && blink_flag) + buf[6] = buf[7] = ' '; + if (sel_field_idx == 1 && blink_flag) + buf[3] = buf[4] = ' '; + if (sel_field_idx == 2 && blink_flag) + buf[0] = buf[1] = ' '; + lcd_write_str(0, 0, buf); + mini_snprintf(buf, sizeof(buf), "SET %s" LCD_FILL, alarm_names[menu_alarm]); + lcd_write_str(0, 1, buf); + break; + } +} + +void run_menu() { + + const int enter_program_mode_ms = 1000; + + int btn = input_triggered; + input_triggered = 0; + + switch (menu_state) { + case MENU_DEFAULT: + if (input_state == (1<<INPUT_MENU) && input_hold_total[INPUT_MENU] >= enter_program_mode_ms) { + menu_state = MENU_SET_TIME; + sel_field_idx = 0; + menu_adjustment_done = 0; + } + break; + + case MENU_SET_TIME: + if (btn & (1<<INPUT_MENU)) { + if (menu_adjustment_done) { + menu_state = MENU_DEFAULT; + calc_next_alarm(); + } else { + menu_state = MENU_SET_ALARM; + sel_field_idx = 2; + menu_alarm = 0; + } + break; + } + + if (btn & (1<<INPUT_LEFT)) { + menu_adjustment_done = 1; + sel_field_idx++; + if (sel_field_idx > 2) + sel_field_idx = 0; + } + if (btn & (1<<INPUT_RIGHT)) { + menu_adjustment_done = 1; + sel_field_idx--; + if (sel_field_idx < 0) + sel_field_idx = 2; + } + + if (btn & (1<<INPUT_UP)) { + menu_adjustment_done = 1; + rtc_adjust(sel_field_idx, 1); + } + if (btn & (1<<INPUT_DOWN)) { + menu_adjustment_done = 1; + rtc_adjust(sel_field_idx, -1); + } + break; + + case MENU_SET_ALARM: + if (btn & (1<<INPUT_MENU)) { + if (menu_adjustment_done) { + menu_state = MENU_DEFAULT; + calc_next_alarm(); + } else { + sel_field_idx = 2; + menu_alarm++; + if (menu_alarm == NUM_ALARMS) + menu_state = MENU_DEFAULT; + } + break; + } + + if (btn & (1<<INPUT_LEFT)) { + menu_adjustment_done = 1; + sel_field_idx++; + if (sel_field_idx > 2) + sel_field_idx = 0; + } + if (btn & (1<<INPUT_RIGHT)) { + menu_adjustment_done = 1; + sel_field_idx--; + if (sel_field_idx < 0) + sel_field_idx = 2; + } + + if (btn & (1<<INPUT_UP)) { + menu_adjustment_done = 1; + adj_alarm(menu_alarm, sel_field_idx, 1); + } + if (btn & (1<<INPUT_DOWN)) { + menu_adjustment_done = 1; + adj_alarm(menu_alarm, sel_field_idx, -1); + } + break; + } +} + +void TIM1_BRK_UP_TRG_COM_IRQHandler() { /* every 1ms */ + TIM1->SR &= ~TIM_SR_UIF; + int inputs = (~GPIOA->IDR) & 0x1f; + input_state = inputs; + static int input_debounce[5] = {0}; + static int input_hold[5] = {0}; + + const int input_debounce_time_ms = 20; + const int input_hold_retrigger_backoff_ms = 1000; + const int input_hold_retrigger_ms = 120; + + for (int i=0; i<5; i++) { + int bit = inputs&1; + inputs >>= 1; + + if (bit) { + if (!input_debounce[i]) { + input_triggered |= 1<<i; + } + input_debounce[i] = input_debounce_time_ms; + + input_hold_total[i]++; + if (i == INPUT_UP || i == INPUT_DOWN) { + /* retrigger for UP and DOWN buttons */ + if (input_hold_total[i] > input_hold_retrigger_backoff_ms) { + input_hold[i]++; + if (input_hold[i] == input_hold_retrigger_ms) { + input_hold[i] = 0; + input_triggered |= 1<<i; + } + } + } + } else { + if (input_debounce[i]) + input_debounce[i]--; + input_hold[i] = 0; + input_hold_total[i] = 0; + } + } + + run_menu(); +} + +void NMI_Handler(void) { +} + +void HardFault_Handler(void) __attribute__((naked)); +void HardFault_Handler() { + asm volatile ("bkpt"); +} + +void SVC_Handler(void) { +} + + +void PendSV_Handler(void) { +} + +void SysTick_Handler(void) { + sys_time_tick++; + sys_time_ms += TICK_MS; + if (sys_time_ms++ >= 1000) { + sys_time_ms = 0; + sys_time_s++; + sys_flag_1Hz = 1; + } +} + +void _init(void) { +} + +void BusFault_Handler(void) __attribute__((naked)); +void BusFault_Handler() { + asm volatile ("bkpt"); +} |