/* firmware for STM8S003-based dachboden badge * Copyright (C) 2019-2022 King Kévin * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include #include #include "stm8s.h" #include "main.h" // enable UART debug #define DEBUG 1 // pinout // on-board LED (sink on) #define LED_ONBOARD_PIN PB5 #define LED_ONBOARD_PORT GPIO_PB // pin to power IR demodulator (source on) #define IRM_ON_PIN PA3 #define IRM_ON_PORT GPIO_PA // IR demodulator output pin #define IRM_OUT_PIN PC7 // TIM1_CH2 #define IRM_OUT_PORT GPIO_PC // RGB LED pins (sink on) #define LED_RED_PIN PD3 // TIM2_CH2 #define LED_RED_PORT GPIO_PD #define LED_GREEN_PIN PD2 // TIM2_CH3 #define LED_GREEN_PORT GPIO_PD #define LED_BLUE_PIN PC5 // TIM2_CH1 #define LED_BLUE_PORT GPIO_PC // IR LED pin (source on) #define LED_IR_PIN PC4 // TIM1_CH4 #define LED_IR_PORT GPIO_PC // UV LED pin (source on) #define LED_UV_PIN PC3 // TIM1_CH3 #define LED_UV_PORT GPIO_PC // vibration sensor input (high on vibration) #define SHAKE_PIN PA2 #define SHAKE_PORT GPIO_PA #define SHAKE_IRQ IRQ_EXTI0 // port A // number of vibrations registered static volatile uint16_t shake_count = 0; // number of time counts (+1 @ 488 Hz) static volatile uint32_t time_count = 0; // blocking wait (in 10 us steps, up to UINT32_MAX / 10) static void wait_10us(uint32_t us10) { us10 = ((us10 / (1 << CLK->CKDIVR.fields.HSIDIV)) * 1000) / 206; // calibrated for 1 ms while (us10--); // burn energy } void putc(char c) { (void)c; IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog #if DEBUG while (!UART1->SR.fields.TXE); // wait until TX buffer is empty UART1->DR.reg = c; // put character in buffer to be transmitted // don't wait until the transmission is complete #endif } void puts(const char* s) { if (NULL == s) { return; } while (*s) { putc(*s++); } } void putn(uint8_t n) { n &= 0x0f; // ensure it's a nibble if (n < 0xa) { n += '0'; } else { n = 'a' + (n - 0x0a); } putc(n); } void puth(uint8_t h) { putn(h >> 4); putn(h & 0x0f); } void main(void) { sim(); // disable interrupts (while we reconfigure them) CLK->CKDIVR.fields.HSIDIV = CLK_CKDIVR_HSIDIV_DIV0; // don't divide internal 16 MHz clock CLK->CKDIVR.fields.CPUDIV = CLK_CKDIVR_CPUDIV_DIV0; // don't divide CPU frequency to 16 MHz while (!CLK->ICKR.fields.HSIRDY); // wait for internal oscillator to be ready // TODO only enable clock for peripherals used (PCG) // configure LED pin LED_ONBOARD_PORT->ODR.reg |= LED_ONBOARD_PIN; // switch LED off LED_ONBOARD_PORT->CR1.reg &= ~LED_ONBOARD_PIN; // use as open-drain LED_ONBOARD_PORT->DDR.reg |= LED_ONBOARD_PIN; // switch pin to output // configure option bytes // disable DATA (e.g. option byte) write protection if (0 == (FLASH_IAPSR & FLASH_IAPSR_DUL)) { FLASH_DUKR = FLASH_DUKR_KEY1; FLASH_DUKR = FLASH_DUKR_KEY2; } FLASH_CR2 |= FLASH_CR2_OPT; // set option bytes programming FLASH_NCR2 &= ~FLASH_NCR2_NOPT; // set option bytes programming OPT->OPT2.fields.AFR0 = 1; // remap TIM2_CH1 to PC5, TIM1_CH1 to C6, and TIM1_CH2 to C7 OPT->NOPT2.fields.NAFR0 = 0; // set complementary option byte OPT->OPT2.fields.AFR1 = 1; // remap TIM2_CH3 to PD2 OPT->NOPT2.fields.NAFR1 = 0; // set complementary option byte while (!(FLASH_IAPSR & FLASH_IAPSR_EOP)); // wait for write to complete FLASH_IAPSR &= ~FLASH_IAPSR_DUL; // re-enable write protection // configure UART for debug output UART1->CR1.fields.M = 0; // 8 data bits UART1->CR3.fields.STOP = 0; // 1 stop bit UART1->BRR2.reg = 0x0B; // set baud rate to 115200 (at 16 MHz) UART1->BRR1.reg = 0x08; // set baud rate to 115200 (at 16 MHz) UART1->CR2.fields.TEN = 1; // enable TX // configure IR demodulator pin IRM_ON_PORT->ODR.reg &= ~IRM_ON_PIN; // switch IR demodulator off IRM_ON_PORT->ODR.reg |= IRM_ON_PIN; // switch IR demodulator on IRM_ON_PORT->CR1.reg |= IRM_ON_PIN; // use as push-pull IRM_ON_PORT->DDR.reg |= IRM_ON_PIN; // switch pin to output // configure RGB LED LED_RED_PORT->ODR.reg |= LED_RED_PIN; // switch LED off LED_RED_PORT->CR1.reg &= ~LED_RED_PIN; // use as open-drain LED_RED_PORT->DDR.reg |= LED_RED_PIN; // use pin to output LED_GREEN_PORT->ODR.reg |= LED_GREEN_PIN; // switch LED off LED_GREEN_PORT->CR1.reg &= ~LED_GREEN_PIN; // use as open-drain LED_GREEN_PORT->DDR.reg |= LED_GREEN_PIN; // use pin to output LED_BLUE_PORT->ODR.reg |= LED_BLUE_PIN; // switch LED off LED_BLUE_PORT->CR1.reg &= ~LED_BLUE_PIN; // use as open-drain LED_BLUE_PORT->DDR.reg |= LED_BLUE_PIN; // use pin to output /* // configure IR LED LED_IR_PORT->ODR.reg &= ~LED_IR_PIN; // switch LED off LED_IR_PORT->CR1.reg |= LED_IR_PIN; // use as push-pull LED_IR_PORT->DDR.reg |= LED_IR_PIN; // use pin to output */ // configure UV LED LED_UV_PORT->ODR.reg &= ~LED_UV_PIN; // switch LED off LED_UV_PORT->CR1.reg |= LED_UV_PIN; // use as push-pull LED_UV_PORT->DDR.reg |= LED_UV_PIN; // use pin to output // configure vibration sensor input SHAKE_PORT->CR1.reg &= ~SHAKE_PIN; // leave floating (pulled down externally) SHAKE_PORT->DDR.reg &= ~SHAKE_PIN; // set as input SHAKE_PORT->CR2.reg |= SHAKE_PIN; // enable external input EXTI->CR1.fields.PAIS = EXTI_RISING_EDGE; // interrupt when vibration is detected shake_count = 0; // reset counter // use timer 4 (8-bit) as timeout counter TIM4->PSCR.fields.PSC = 7; // make it as slow as possible 16E6 / 2**7 = 125 kHz, / 256 = 488 Hz TIM4->CNTR.fields.CNT = 0; // reset counter TIM4->IER.fields.UIE = 1; // enable interrupt time_count = 0; // reset time counter TIM4->CR1.fields.URS = 1; // only update on overflow TIM4->CR1.fields.CEN = 1; // enable counter // configure timer to modulate IR LEDs at 38 kHz TIM1->PSCRH.reg = 0; // set prescaler to get most precise 38 kHz TIM1->PSCRL.reg = 0; // 16E6/(0+1)/65536 = down to 244 Hz #define TIM1_PERIOD 421U // 16E6/(0+1)/38000 TIM1->ARRH.reg = (TIM1_PERIOD >> 8); // set auto-reload register for 38 kHz period TIM1->ARRL.reg = (TIM1_PERIOD & 0xff); // 16E6/(0+1)/38000 TIM1->CCR4H.reg = (TIM1_PERIOD / 3) >> 8; // set duty cycle to 33% TIM1->CCR4L.reg = (TIM1_PERIOD / 3 ) & 0xff; // set duty cycle to 33% TIM1->CCMR4.output_fields.OC4M = 6; // set PWM1 mode TIM1->CCMR4.output_fields.CC4S = 0; // use channel as output TIM1->CCER2.fields.CC4E = 1; // enable channel output TIM1->BKR.fields.MOE = 1; // enable outputs TIM1->EGR.fields.UG = 1; // transfer all registers TIM1->CR1.fields.CEN = 1; // enable counter to start PWM /* don't use the AWU, else it will cause an active-halt instead of halt, using more power // configure auto-wakeup (AWU) to be able to refresh the watchdog // 128 kHz LSI used by default in option bytes CKAWUSEL // we skip measuring the LS clock frequency since there is no need to be precise AWU->TBR.fields.AWUTB = 10; // interval range: 128-256 ms AWU->APR.fields.APR = 0x3e; // set time to 256 ms AWU->CSR.fields.AWUEN = 1; // enable AWU (start only when entering wait or active halt mode) */ /* don't use IWDG since it wakes up from HALT mode and uses (a little) power use WWDG instead // configure independent watchdog (very loose, just it case the firmware hangs) IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog IWDG->KR.fields.KEY = IWDG_KR_KEY_ENABLE; // start watchdog IWDG->KR.fields.KEY = IWDG_KR_KEY_ACCESS; // allows changing the prescale IWDG->PR.fields.PR = IWDG_PR_DIV256; // set prescale to longest time (1.02s) IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog */ rim(); // re-enable interrupts LED_ONBOARD_PORT->ODR.reg &= ~LED_ONBOARD_PIN; // switch LED on to indicate we are ready bool action = false; // if an action has been performed puts("ready\r\n"); while (true) { putc('.'); IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog if (shake_count) { puts("vibrations: "); puth(shake_count); puts("\r\n"); shake_count = 0; // reset count } if (time_count > 488 * 10) { puts("10s\r\n"); time_count = 0; // reset counter halt(); } if (action) { // something has been performed, check if other flags have been set meanwhile action = false; // clear flag } else { // nothing down wfi(); // go to sleep (wait for any interrupt, including periodic AWU) } } } // auto wakeup void awu(void) __interrupt(IRQ_AWU) { volatile uint8_t awuf = AWU_CSR; // clear interrupt flag by reading it (reading is required, and volatile prevents compiler optimization) // let the main loop kick the dog } // vibration sensor input void shake_isr(void) __interrupt(SHAKE_IRQ) { shake_count++; // register vibration } // time counter void time_isr(void) __interrupt(IRQ_TIM4) { TIM4->SR.fields.UIF = 0; // clear flag time_count++; // remember overflow to count time }