summaryrefslogtreecommitdiff
path: root/fw/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'fw/main.c')
-rw-r--r--fw/main.c502
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");
+}