stm8s/main.c

658 lines
22 KiB
C

/* firmware for STM8S003-based dachboden badge
* Copyright (C) 2019-2022 King Kévin <kingkevin@cuvoodoo.info>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include "stm8s.h"
#include "main.h"
// enable UART debug
#define DEBUG 1
// EEPROM start address
#define EEPROM_ADDR 0x4000
// pinout
// 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 controlled by nMOS)
#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;
// last time the badge was shook
static volatile uint32_t time_shake = 0;
// time after last shake to go to sleep, in seconds
#define REST_TIME (5 * 60U)
// period to share our color code, in seconds
#define SHARE_TIME (1U)
// period to enforce our color code, in seconds
#define MASTER_TIME (1U)
// time counts per us (1/(16E6/(3+1)) * 1000*1000 = 0.25 us)
#define NEC_TICKS_PER_US 4UL
// burst error margin in %
#define NEC_ERROR 15
// AGC burst length (9 ms)
#define NEC_AGC_BURST (9000 * NEC_TICKS_PER_US)
// AGC slot length (9 + 4.5 ms)
#define NEC_AGC_SLOT (NEC_AGC_BURST + (4500 * NEC_TICKS_PER_US))
// bit burst length (560 us)
#define NEC_BIT_BURST (560 * NEC_TICKS_PER_US)
// logical 0 slot length
#define NEC_0_SLOT (1125 * NEC_TICKS_PER_US)
// logical 1 slot length
#define NEC_1_SLOT (2250 * NEC_TICKS_PER_US)
#define NEC_AGC_SLOT_MIN (NEC_AGC_SLOT * (100 - NEC_ERROR) / 100U)
#define NEC_AGC_SLOT_MAX (NEC_AGC_SLOT * (100 + NEC_ERROR) / 100U)
#define NEC_0_SLOT_MIN (NEC_0_SLOT * (100 - NEC_ERROR) / 100U)
#define NEC_0_SLOT_MAX (NEC_0_SLOT * (100 + NEC_ERROR) / 100U)
#define NEC_1_SLOT_MIN (NEC_1_SLOT * (100 - NEC_ERROR) / 100U)
#define NEC_1_SLOT_MAX (NEC_1_SLOT * (100 + NEC_ERROR) / 100U)
// bit position in the NEC message (-2 = invalid, -1 = AGC)
static volatile int8_t nec_bit = -2;
// complete NEC message
static volatile uint8_t nec_msg[4] = {0};
// flag set if NEC message has been received
static volatile bool nec_flag = false;
// set when data is received over UART
static volatile char uart_c = 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);
}
// ASCII to nibble (0xff if invalid)
static uint8_t a2n(char c)
{
if (c >= '0' && c <= '9') {
return c - '0';
} else if (c >= 'a' && c <= 'f') {
return c - 'a' + 0xa;
} else if (c >= 'A' && c <= 'F') {
return c - 'A' + 0xa;
} else {
return 0xff;
}
}
// set duty cycle of red LED
void led_red(uint16_t bightness)
{
TIM2->CCR2H.reg = (bightness >> 8); // set duty cycle
TIM2->CCR2L.reg = (bightness >> 0); // set duty cycle
}
// set duty cycle of green LED
void led_green(uint16_t bightness)
{
TIM2->CCR3H.reg = (bightness >> 8); // set duty cycle
TIM2->CCR3L.reg = (bightness >> 0); // set duty cycle
}
// set duty cycle of blue LED
void led_blue(uint16_t bightness)
{
TIM2->CCR1H.reg = (bightness >> 8); // set duty cycle
TIM2->CCR1L.reg = (bightness >> 0); // set duty cycle
}
void led_rgb(uint8_t* rgb)
{
if (NULL == rgb) {
return;
}
led_red(rgb[0] << 8);
led_red(rgb[0] << 8);
led_green(rgb[1] << 8);
led_green(rgb[1] << 8);
led_blue(rgb[2] << 8);
// no idea why, but if I don't do it a second time the blue is a bit on when switched off
// it is not about the register order or preload
led_blue(rgb[2] << 8);
}
// configure timer to capture IR NEC codes
static void timer_ir_in(void)
{
TIM1->CR1.reg = 0; // disable counter before reconfiguring it
TIM1->IER.reg = 0; // reset interrupts
TIM1->BKR.reg = 0; // reset register
TIM1->CCER1.reg = 0; // reset register
TIM1->CCER2.reg = 0; // reset register
TIM1->PSCRH.reg = 0; // set prescaler to get most precise 9+4.5 ms
TIM1->PSCRL.reg = 3; // 16E6/(3+1)/65536 = up to 16 ms
TIM1->ARRH.reg = 0xff; // let it count to the end
TIM1->ARRL.reg = 0xff; // an overflow means the signal is corrupted
TIM1->CCMR1.input_fields.CC1S = 1; // configure channel as input and map CH1 to TI1FP1
TIM1->CCER1.fields.CC1P = 1; // trigger on a low level or falling edge of TI1F
TIM1->CCMR2.input_fields.CC2S = 2; // configure channel as input and map CH2 to TI1FP2
TIM1->CCER1.fields.CC2P = 0; // trigger on a high level or rising edge of TI1F
TIM1->SMCR.fields.TS = 5; // set trigger to filtered timer input 1 (TI1FP1)
// don't filter the external trigger
TIM1->SMCR.fields.SMS = 4; // reset on trigger
TIM1->CCER1.fields.CC1E = 1; // enable channel 1 for input capture
TIM1->CCER1.fields.CC2E = 1; // enable channel 2 for input capture
TIM1->IER.fields.CC1IE = 1; // enable interrupt for channel
TIM1->IER.fields.CC2IE = 1; // enable interrupt for channel
TIM1->IER.fields.UIE = 1; // enable update interrupt
TIM1->CR1.fields.URS = 1; // only update on overflow
TIM1->SR1.reg = 0; // clear all flags
TIM1->CNTRL.reg = 0; // reset counter
TIM1->CNTRH.reg = 0; // reset counter
TIM1->EGR.fields.UG = 1; // transfer all registers
TIM1->CR1.fields.CEN = 1; // enable counter to start capture
nec_bit = -2; // invalidate current packet
IRM_ON_PORT->ODR.reg |= IRM_ON_PIN; // switch IR demodulator on
}
#define TIM1_PERIOD 421U // 16E6/(0+1)/38000
// configure timer to transmit IR burst at 38 kHz
static void timer_ir_out(void)
{
IRM_ON_PORT->ODR.reg &= ~IRM_ON_PIN; // switch IR demodulator off
TIM1->CR1.reg = 0; // disable counter before reconfiguring it
TIM1->CCER1.reg = 0; // reset register
TIM1->CCER2.reg = 0; // reset register
TIM1->IER.reg = 0; // reset interrupts
TIM1->PSCRH.reg = 0; // set prescaler to get most precise 38 kHz
TIM1->PSCRL.reg = 0; // 16E6/(0+1)/65536 = down to 244 Hz
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->SR1.reg = 0; // clear all flags
TIM1->CNTRL.reg = 0; // reset counter
TIM1->CNTRH.reg = 0; // reset counter
TIM1->CR1.fields.OPM = 1; // send one pulse at a time
TIM1->EGR.fields.UG = 1; // transfer all registers
// don't enable timer yet
}
// transmit IR pulses
static void nec_pulse(uint16_t pulses, bool mark)
{
if (mark) {
TIM1->CCR4H.reg = (TIM1_PERIOD / 3) >> 8; // set duty cycle to 33%
TIM1->CCR4L.reg = (TIM1_PERIOD / 3 ) & 0xff; // set duty cycle to 33%
} else {
TIM1->CCR4H.reg = 0; // set duty cycle to 0%
TIM1->CCR4L.reg = 0; // set duty cycle to 0%
}
while (pulses--) {
TIM1->CR1.fields.CEN = 1; // enable counter to start PWM for 1 pulse
while (TIM1->CR1.fields.CEN); // wait until pulse completes
}
// ensure we are off at the end
TIM1->CCR4H.reg = 0; // set duty cycle to 0%
TIM1->CCR4L.reg = 0; // set duty cycle to 0%
}
// transmit 4 byte NEC code over IR (LSb first)
static void nec_transmit(uint32_t code)
{
if (NULL == code) {
return;
}
timer_ir_out(); // configure to transmit pulses
sim(); // disable interrupts to keep timings tight
// all time are hand tuned
nec_pulse(333, true); // send AGC burst, 9 ms
nec_pulse(166, false); // AGC space, 4.5 ms
// transmit bits
for (uint8_t i = 0; i < 32; i++) {
nec_pulse(21, true); // bit burst, 560 us
if (code & 0x1) {
nec_pulse(61, false); // bit space, 2.25 ms - 560 us
} else {
nec_pulse(21, false); // bit space, 1.12 ms - 560 us
}
code >>= 1; // go to next bit
}
nec_pulse(22, true); // end pulse, 560 us
rim(); // re-enable interrupts
}
void main(void)
{
bool master = false; // if we are not a slave badge, but master controller
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
// only power used peripherals
CLK_PCKENR1 = CLK_PCKENR1_UART1234 | CLK_PCKENR1_TIM1 | CLK_PCKENR1_TIM25 | CLK_PCKENR1_TIM46;
CLK_PCKENR2 = 0;
// 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
UART1->CR2.fields.REN = 1; // enable RX
UART1->CR2.fields.RIEN = 1; // enable RX interrupt
char uart_cmd[10]; // buffer for received data
uint8_t uart_used = 0; // how much of the buffer is used
// configure IR demodulator pin
IRM_ON_PORT->ODR.reg &= ~IRM_ON_PIN; // switch IR demodulator off
IRM_ON_PORT->CR1.reg |= IRM_ON_PIN; // use as push-pull
IRM_ON_PORT->DDR.reg |= IRM_ON_PIN; // switch pin to output
/* use PWM instead of GPIO for controlling RGB LED
LED_RED_PORT->ODR.reg &= ~LED_RED_PIN; // switch LED off
LED_RED_PORT->CR1.reg |= LED_RED_PIN; // use as push-pull
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 push-pull
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 push-pull
LED_BLUE_PORT->DDR.reg |= LED_BLUE_PIN; // use pin to output
*/
// configure timer 2 for PWM-controlling RGB LED
TIM2->PSCR.fields.PSC = 0; // set prescaler to to 244 Hz, 16E6/(2**0)/65536 = 244 Hz
TIM2->ARRH.reg = 0xff; // set period to max for most precisions
TIM2->ARRL.reg = 0xff; // set period to max for most precisions
TIM2->CCMR1.output_fields.OC1M = 6; // set PWM1 mode
TIM2->CCMR1.output_fields.CC1S = 0; // use channel as output
TIM2->CCER1.fields.CC1E = 1; // enable channel output
led_blue(0); // switch off blue LED
TIM2->CCMR2.output_fields.OC2M = 6; // set PWM1 mode
TIM2->CCMR2.output_fields.CC2S = 0; // use channel as output
TIM2->CCER1.fields.CC2E = 1; // enable channel output
led_red(0); // switch off red LED
TIM2->CCMR3.output_fields.OC3M = 6; // set PWM1 mode
TIM2->CCMR3.output_fields.CC3S = 0; // use channel as output
TIM2->CCER2.fields.CC3E = 1; // enable channel output
led_green(0); // switch off green LED
TIM2->EGR.fields.UG = 1; // transfer all registers
TIM2->CR1.fields.CEN = 1; // enable counter to start PWM
// load color
uint8_t rgb[3]; // eyes color
rgb[0] = *(uint8_t*)(EEPROM_ADDR + 0); // load red color
rgb[1] = *(uint8_t*)(EEPROM_ADDR + 1); // load green color
rgb[2] = *(uint8_t*)(EEPROM_ADDR + 2); // load blue color
led_rgb(rgb); // set color
// 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 update 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 receive IR message
timer_ir_in();
/* 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
bool action = false; // if an action has been performed
puts("\r\nready\r\n");
while (true) {
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
if (shake_count) {
puts("vibrations: ");
puth(shake_count);
puts("\r\n");
time_shake = time_count; // remember time to stay awake
shake_count = 0; // reset count
}
if (nec_flag) {
puts("\r\nr");
puth(nec_msg[0]);
puth(nec_msg[1]);
puth(nec_msg[2]);
puth(nec_msg[3]);
puts("\r\n");
if (0x80 == nec_msg[0] && 0x7f == nec_msg[1]) { // radio remote
if (0x04 == nec_msg[2] && 0xfb == nec_msg[3]) { // 1
rgb[0] = 0x80;
rgb[1] = 0;
rgb[2] = 0;
} else if (0x05 == nec_msg[2] && 0xfa == nec_msg[3]) { // 2
rgb[0] = 0;
rgb[1] = 0x80;
rgb[2] = 0;
} else if (0x06 == nec_msg[2] && 0xf9 == nec_msg[3]) { // 3
rgb[0] = 0;
rgb[1] = 0;
rgb[2] = 0x80;
} else if (0x01 == nec_msg[2] && 0xfe == nec_msg[3]) { // mute
LED_UV_PORT->ODR.reg |= LED_UV_PIN; // switch UV LED on
} else if (0x12 == nec_msg[2] && 0xed == nec_msg[3]) { // power
LED_UV_PORT->ODR.reg &= ~LED_UV_PIN; // switch UV LED off
rgb[0] = 0;
rgb[1] = 0;
rgb[2] = 0;
}
led_rgb(rgb); // ensure [new] color is set
} else if (0x01 == nec_msg[0]) { // badge tries to influence us
for (uint8_t i = 0; i < 3; i++) {
if (nec_msg[1 + i] > rgb[i]) {
rgb[i]++;
} else if (nec_msg[1 + i] < rgb[i]) {
rgb[i]--;
}
}
led_rgb(rgb); // set new color
} else if (0x02 == nec_msg[0]) { // master sets our color
rgb[0] = nec_msg[1];
rgb[1] = nec_msg[2];
rgb[2] = nec_msg[3];
led_rgb(rgb); // set new color
}
nec_flag = false; // clear flag
action = true; // redo loop
}
if (!master && 0 == time_count % (488UL * SHARE_TIME)) {
uint32_t code = 0x01; // code to send
code |= ((uint32_t)rgb[0] << 8);
code |= ((uint32_t)rgb[1] << 16);
code |= ((uint32_t)rgb[2] << 24);
nec_transmit(code);
timer_ir_in(); // go back to IR capture
putc('t');
action = true; // redo main loop
}
if (master && 0 == time_count % (488UL * MASTER_TIME)) {
uint32_t code = 0x02; // code to send
code |= ((uint32_t)rgb[0] << 8);
code |= ((uint32_t)rgb[1] << 16);
code |= ((uint32_t)rgb[2] << 24);
nec_transmit(code);
putc('m');
action = true; // redo main loop
}
if (time_count > time_shake + 488UL * REST_TIME && !master) {
LED_UV_PORT->ODR.reg &= ~LED_UV_PIN; // switch UV LED off
IRM_ON_PORT->ODR.reg &= ~IRM_ON_PIN; // switch IR demodulator off
led_red(0); // ensure LED is off
led_green(0); // ensure LED is off
led_blue(0); // ensure LED is off
// save color
if (rgb[0] != *(uint8_t*)(EEPROM_ADDR + 0) || rgb[1] != *(uint8_t*)(EEPROM_ADDR + 1) || rgb[2] != *(uint8_t*)(EEPROM_ADDR + 2)) {
// disable DATA (e.g. EEPROM) write protection
if (0 == (FLASH_IAPSR & FLASH_IAPSR_DUL)) {
FLASH_DUKR = FLASH_DUKR_KEY1;
FLASH_DUKR = FLASH_DUKR_KEY2;
}
for (uint8_t i = 0; i < 3; i++) {
*(uint8_t*)(EEPROM_ADDR + i) = rgb[i];
while (!(FLASH_IAPSR & FLASH_IAPSR_EOP)); // wait until programming is complete
}
FLASH_IAPSR &= ~FLASH_IAPSR_DUL; // re-enable write protection
}
puts("rest\r\n\n");
halt();
IRM_ON_PORT->ODR.reg |= IRM_ON_PIN; // switch IR demodulator on
led_rgb(rgb); // set color
time_shake = 0; // reset stay awake time
time_count = 0; // reset counter
}
if (uart_c) { // data received over UART
putc(uart_c); // echo back
if ('\r' == uart_c || '\n' == uart_c) { // end of line received
if (uart_used >= 9 && 'T' == uart_cmd[0]) { // transmit command
bool code_valid = true; // verify it it's really a hex string
uint32_t code = 0; // parsed code
for (uint8_t i = 0; i < 8; i++) {
uint32_t n = a2n(uart_cmd[1 + i]);
if (n > 0xf) {
code_valid = false;
break;
} else {
code |= (n << (i * 4));
}
}
if (code_valid) {
nec_transmit(code); // transmit code
timer_ir_in(); // go back to IR capture
puts("\r\ncode transmitted\r\n");
}
} else if (uart_used >= 7 && 'M' == uart_cmd[0]) { // switch to master
bool code_valid = true; // verify it it's really a hex string
uint32_t code = 0x02000000; // parsed master code
for (uint8_t i = 0; i < 6; i++) {
uint32_t n = a2n(uart_cmd[1 + i]);
if (n > 0xf) {
code_valid = false;
break;
} else {
code |= (n << ((i + 1) * 4));
}
}
if (code_valid) {
rgb[0] = code >> 8; // save color
rgb[1] = code >> 16; // save color
rgb[2] = code >> 24; // save color
led_rgb(rgb); // set color
nec_transmit(code); // transmit code
if (!master) {
master = true; // switch to master mode
IRM_ON_PORT->ODR.reg &= ~IRM_ON_PIN; // switch IR demodulator off
puts("\r\nmaster set\r\n");
}
}
}
uart_used = 0; // reset buffer
} else if (uart_used < ARRAY_LENGTH(uart_cmd)) {
uart_cmd[uart_used++] = uart_c;
}
uart_c = 0; // clear flag
action = true; // redo main loop
}
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
}
// system time counter
void time_isr(void) __interrupt(IRQ_TIM4)
{
TIM4->SR.fields.UIF = 0; // clear flag
time_count++; // remember overflow to count time
}
// IR capture timer counter
void nec_uo_isr(void) __interrupt(IRQ_TIM1_UO)
{
if (TIM1->SR1.fields.UIF) { // timer overflow
nec_bit = -2; // set to invalid message since it took longer than NEC slot
TIM1->SR1.fields.UIF = 0; // clear flag
}
}
// IR capture timer counter
void nec_cc_isr(void) __interrupt(IRQ_TIM1_CC)
{
static uint32_t nec_bits = 0; // temporary buffer to construct message
if (TIM1->SR1.fields.CC1IF) { // start of burst
const uint16_t slot = (TIM1->CCR1H.reg << 8) + TIM1->CCR1L.reg;
if (-1 == nec_bit) { // AGC received
nec_bits = 0; // reset message
if (slot < NEC_AGC_SLOT_MIN || slot > NEC_AGC_SLOT_MAX) {
nec_bit = -2; // slot invalid
}
} else if (nec_bit >= 0 && nec_bit < 32) {
if (slot >= NEC_0_SLOT_MIN && slot <= NEC_0_SLOT_MAX) {
// bit should already be 0
} else if (slot >= NEC_1_SLOT_MIN && slot <= NEC_1_SLOT_MAX) {
nec_bits |= (1UL << nec_bit);
} else {
nec_bit = -2; // slot invalid
}
}
nec_bit++; // start the bit
TIM1->SR1.fields.CC1IF = 0; // clear flag
}
if (TIM1->SR1.fields.CC2IF) { // start of pause
if (32 == nec_bit) {
nec_msg[0] = (nec_bits >> 0); // save message
nec_msg[1] = (nec_bits >> 8); // save message
nec_msg[2] = (nec_bits >> 16); // save message
nec_msg[3] = (nec_bits >> 24); // save message
nec_flag = true; // notify we received a message
nec_bit = -2; // restart message
} else if (nec_bit >= 0) {
// here we could verify the burst length
}
TIM1->SR1.fields.CC2IF = 0; // clear flag
}
}
// UART RX interrupt
void rx_isr(void) __interrupt(IRQ_UART1_RX)
{
if (UART1->SR.fields.RXNE) { // there is data
uart_c = UART1->DR.reg; // read received data, also clears flag
}
}