A better PIO program for PWM decode

One of the things that the BLDC controller needs to do is to decode a PWM signal from an AS5048A hall effect sensor. I had been using two RP2040 PIO programs to measure both the high time and the interval, but I was finding it a little awkward to interleave reads from two FIFOs (and it was using all the PIO resources with 4 motors).

After a bit of head scratching, I came up with this program that reads both the high time of the PWM and the interval and sends them in one 32-bit “struct” on the FIFO. The main reason that I wanted to use two programs before was to avoid the possibility of getting out of sync if I alternated high/interval on the same FIFO.

What if the CPU was reading the interval but thought it was the high time or vice versa? Packing them into one 32-bit value neatly solves that problem. The trick is to use the “IN” instruction, which is normally used to read from pins, but it supports reading from another register instead. By using IN to fetch 16 bits from the counter register (X), we can shift 16 bits of the counter into the ISR register, ready to be sent. By using the “autopush” feature on 32 bits the PIO automatically pushes the packed value after the second “IN” instruction. The only gotcha with this approach is that the counters are limited to 16 bits, which is enough to handle roughly 1.5ms intervals. The AS5048A uses a 1ms interval so that’s just about right for my application.

.wrap_target
  mov x, !NULL             ; x = 0xffffffff

loop_high:                 ; do {
  jmp x-- cont_loop_high   ;   x--
cont_loop_high:            ;
  nop                      ;   nop to match number of cycles below
  jmp pin loop_high        ; } while pin is high

  in x, 16                 ; Copy 16 bits of counter into ISR

loop_low:                  ; do {
  jmp x-- cont_loop_low   ;   x--
cont_loop_low:             ;
  jmp pin exit_loop_low    ;   if pin is high: break
  jmp loop_low             ; } while 1

exit_loop_low:
  in x, 16                 ; Copy 16 bits of counter into ISR
.wrap

The interval and count can then be decoded as follows:

uint32_t high = 0xffff - (raw_pio_output>>16);
uint32_t invl = 0xffff - (raw_pio_output & 0xffff);

The PIO assembler file is here on Github.

Leave a Reply

Your email address will not be published. Required fields are marked *