main: add IR NEC decoding

This commit is contained in:
King Kévin 2022-10-12 12:16:17 +02:00
parent 0e7914ad9d
commit d54b5f1d26
1 changed files with 131 additions and 1 deletions

132
main.c
View File

@ -45,6 +45,35 @@ static volatile uint32_t time_count = 0;
// time after last shake to go to sleep, in seconds
#define REST_TIME (5 * 60U)
// 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;
// blocking wait (in 10 us steps, up to UINT32_MAX / 10)
static void wait_10us(uint32_t us10)
{
@ -205,11 +234,12 @@ void main(void)
// 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
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 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
@ -224,6 +254,32 @@ void main(void)
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
*/
// configure timer to receive IR message
TIM1->CR1.fields.CEN = 0; // disable counter before reconfiguring it
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->CCER2.fields.CC3E = 0; // ensure other channel is not used
TIM1->CCER2.fields.CC4E = 0; // ensure other channel is not used
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->EGR.fields.UG = 1; // transfer all registers
TIM1->CR1.fields.CEN = 1; // enable counter to start capture
/* 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
@ -257,6 +313,36 @@ void main(void)
puts("\r\n");
shake_count = 0; // reset count
}
if (nec_flag) {
puth(nec_msg[0]);
puth(nec_msg[1]);
puth(nec_msg[2]);
puth(nec_msg[3]);
puts("\r\n");
if (0x00 == nec_msg[0] && 0xff == nec_msg[1]) { // radio remote
if (0x08 == nec_msg[2] && 0xf6 == nec_msg[3]) { // 1
led_red(0x8000);
led_green(0);
led_blue(0);
} else if (0x0a == nec_msg[2] && 0xf4 == nec_msg[3]) { // 2
led_red(0);
led_green(0x8000);
led_blue(0);
} else if (0x0c == nec_msg[2] && 0xf2 == nec_msg[3]) { // 3
led_red(0);
led_green(0);
led_blue(0x8000);
} else if (0x02 == nec_msg[2] && 0xfc == nec_msg[3]) { // mute
LED_UV_PORT->ODR.reg |= LED_UV_PIN; // switch UV LED on
} else if (0x24 == nec_msg[2] && 0xda == nec_msg[3]) { // power
LED_UV_PORT->ODR.reg &= ~LED_UV_PIN; // switch UV LED off
led_red(0);
led_green(0);
led_blue(0);
}
}
nec_flag = false; // clear flag
}
if (time_count > 488UL * REST_TIME) {
puts("rest\r\n");
time_count = 0; // reset counter
@ -293,3 +379,47 @@ 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) { // slot longer than NEC slot
nec_bit = -2; // set to invalid message
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;
nec_bit++; // start the bit
if (0 == 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 (32 == nec_bit) { // trailing pulse received
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) {
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
}
}
TIM1->SR1.fields.CC1IF = 0; // clear flag
}
if (TIM1->SR1.fields.CC2IF) {
TIM1->SR1.fields.CC2IF = 0; // clear flag
}
}