From eed88beebe29075837d514638fd0de809a770c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?King=20K=C3=A9vin?= Date: Mon, 26 Sep 2022 18:05:34 +0200 Subject: [PATCH] main: config pin, IR out, shake wakeup --- main.c | 203 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 199 insertions(+), 4 deletions(-) diff --git a/main.c b/main.c index ef00e44..737919e 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,5 @@ -/* firmware template for STM8S microcontroller - * Copyright (C) 2019-2020 King Kévin +/* firmware for STM8S003-based dachboden badge + * Copyright (C) 2019-2022 King Kévin * SPDX-License-Identifier: GPL-3.0-or-later */ #include @@ -9,7 +9,41 @@ #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) @@ -18,6 +52,44 @@ static void wait_10us(uint32_t us10) 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) @@ -26,24 +98,133 @@ void main(void) 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 |= AWU_CSR_AWUEN; // enable AWU (start only when entering wait or active halt mode) + 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 @@ -52,8 +233,22 @@ void main(void) } } -void awu(void) __interrupt(IRQ_AWU) // auto wakeup +// 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 +}