/* 8seg LED display driver firmware * Copyright (C) 2018 Sebastian Götte * * 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 . */ #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<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)< 48.0MHz */ RCC->CR |= RCC_CR_PLLON; while (!(RCC->CR&RCC_CR_PLLRDY)); RCC->CFGR |= (2<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<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<AFR[0] = (1<AFR[1] = (4<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<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 tr_al_fmt && alarms[i] < min_alr) { min_alr = alarms[i]; min_idx = i; } } if (min_idx == -1) { /* handle rollover */ for (int i=0; iCR &= ~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<= 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< 2) sel_field_idx = 0; } if (btn & (1< 2) sel_field_idx = 0; } if (btn & (1<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< input_hold_retrigger_backoff_ms) { input_hold[i]++; if (input_hold[i] == input_hold_retrigger_ms) { input_hold[i] = 0; input_triggered |= 1<= 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"); }