Receive logic working

A project log for Learning IR codes

Building a reusable IR remote code learner and replayer on STM32

benedekkupperbenedekkupper 11/23/2021 at 22:390 Comments

Getting the receive logic to work was a lot tougher than the transmit one. For one, we don't know the carrier frequency of the incoming signal. Furthermore, receiving one IR code requires a lot more processing with PWM input mode, than the transmission, as each falling-rising edge pair needs to be processed individually.

The TIM's PWM input mode in general is rather well documented, but here's a quick summary: capture channels 1 and 2 work in tandem, one is the direct channel, which has the actual input pin connected, and this channel resets the TIM's counter on the capture event. The other is the indirect channel, used with opposite capture polarity. The direct channel's capture register will reflect the timer periods for the signal's period, the indirect channel's capture register will reflect the the timer periods for the signal's active phase.

In order to decouple the necessary signal processing from the PWM reception itself, DMA is used to load a sequence of captured PWM on-off timing pairs into memory. The DMA is run in circular mode, and half of the buffer is processed when the half transfer or transfer complete flags are raised. The used TIM peripheral has burst DMA support, which means that one DMA transfer request can result in multiple successive registers being transferred. I use this mode to transfer both CC1 and CC2 capture register values each time a capture event occurs. If the capture event's timing is used to transfer both registers, then the other channel's value will reflect the previous on/off duration. Therefore I had to make use of the CC DMA Selection (CCDS) bit, which in update mode will delay the DMA trigger until the timer is updated.

- It's nice to find that the TIM design is so flexible, but unfortunately I have to use a mix of HAL and LL drivers to get both the convenience of linking DMAs to peripherals, and the extensive API that lets me control any necessary bit, like this CCDS. -

So we can capture a lot of on-off pairs, but how do we know that an infrared code has completed? There's really only one way, by seeing no more on pulses until a timeout. Since we are using a timer already, can we use the same one to check for this timeout? Yes we can :) The timer is updated (reset) at each direct channel capture event, so we cannot directly rely on the update event for timeout detection. Fortunately there's an Update Request Source (URS) bit, when it's active, only the timer's counter overflow triggers the update interrupt. So we can just set a long reload value, the URS bit and enable the update interrupt request of the timer, and we automatically get interrupted when the infrared code has been completed, without having to monitor the timeout separately.

The signal identification itself consists of two parts:

1. When the first chunk of on-off pairs are received, the carrier frequency is estimated first. This is a balancing act between getting more accuracy by using larger buffers, or saving on RAM.

2. Once the carrier frequency is known, each on-off pair is counted as one modulated ON bit, and zero or more OFF bits.

When the timeout is reached, the timeout duration itself should - at least partially - be counted as terminating OFF bits.

The local loopback test has shown that the final implementation is correct, I only get 1 offset in the pronto code of the measured carrier frequency compared to the test signal (and of course different count for the last OFF symbol), which is a great result in my opinion. I'm looking forward to testing this code with some actual hardware, fingers crossed :)