10 ms·
A closer look at Raspberry Pi RP2040 Programmable IOs
- MatthiasWandel 6y agoI have been puzzling over how to program quadrature decoding with the PIO instructions. Not many chips have quadrature decoding built in.
- mmastrac 6y agoFunny enough, this popped up when I was googling RPI PIO just now: https://twitter.com/ZodiusInfuser/status/1357067388928335873 https://twitter.com/ZodiusInfuser/status/1357067388928335873
- deleted 6y ago[deleted]
- dragontamer 6y agoQuadrature decoding is just 4-state (aka: 2-bit) gray-code decoding. 0 -> 1 -> 2 -> 3 -> 0 -> 1 -> 2 -> 3... is counting up. Formally: newstate - oldstate == 1. 0 -> 3 -> 2 -> 1 -> 0 -> ... is counting down. Formally: newstate - oldstate == 3. 0 -> 2 is ambiguous. Carry on the "same direction as the last time". Formally: newstate - oldstate == 2. 0 -> 0 is standing still, no movement. newstate - oldstate == 0 ------- Except of course: 0, 1, 2, 3 are gray coded, not normal binary coded. * 0 == 00 * 1 == 01 * 2 == 11 * 3 == 10 Anyway, convert the 00 / 01 / 11 / 10 raw hardware bit-stream into the above numbers (0, 1, 2, 3). Then it becomes VERY easy.
- MatthiasWandel 6y agoThe question was not how quadrature encoding works, but how to implement it with the limited PIO instructions
- dragontamer 6y agoHmmm. Any I/O is basically compression. So lets think about what we're actually outputting. We're converting a raw bitstream from two GPIO pins paired (Ex: 00, 01, 01, 01, 11, 11, 10, 00, 01, 11), into a singular message: such as +6. There are two applications of a quadrature encoder that I can think of. 1. Virtual Pot -- Converting the messages into a kind of "-100 to +100 slider", such as a volume control knob. In this case, you want a regular update schedule at human-interface speeds (~1000 updates / second or slower). 2. Rotational Velocity sensor -- Converting the messages into a speed. You're willing to batch the bitstream up as slowly as possible, maybe waiting for a rollover event (+128 or -128). And you just interrupt on those overflows. ------- Since the input format is already set, we now think about the output format: how to represent +6 and or -12? Based on the PIO instruction set, it seems like +6 and -12 probably would be easiest as two separate registers in two different state-machines. Every time an "increment" is detected, the FIFO-associated with +1 gets a +1 bit added to it. Every time a "decrement" is detected, the 2nd FIFO associated with -1 gets a +1 bit added to it. ---- In pseudocode: IncrementSide(){ currentState = 00; // default while(1){ nextState = in(GPIO0) | in(GPIO1); if(currentState == 00 and nextState == 01){ push 1; } if(currentState == 01 and nextState == 11){ push 1; } if(currentState == 11 and nextState == 10){ push 1; } if(currentState == 10 and nextState == 00){ push 1; } currentState = nextState; } } "nextState" and "currentState" probably is just the X and Y registers of the PIO state machines. The above is "very pseudocode" as I don't really understand PIO yet. I'm saying "push 1", but "push 1 into the OSR". It looks like the OSR has some kind of auto-push mechanism, but if that doesn't work then a manual-push might be needed in the code proper. Hard to say from the docs alone. DecrementSide would be just the inverted if-statements: if(currentState == 00 and nextState == 11){ push 1; // Decrement side checking for a decrement } -------- This above methodology would only work for #2 ("velocity"), and fails for #1 ("positional"), because the increment-side and decrement-side would be updating at varying rates. I probably can make a positional-decoder instead of a velocity one using similar principles. Or maybe by somehow ensuring that the +1 and -1 messages go into the same FIFO to be picked up by the host CPU. The idea is route "0" messages to /dev/null, compressing the input stream. The host still sees the important +1 and -1 messages. The exact mechanism for that is up for debate, but the simple if-else state machine above seems to accomplish that to a limited extent. ------ EDIT: Hmmm, maybe a singular FIFO can be done if we have push1 and push0 for the two kinds of messages: 1 representing +1 and 0 representing -1. Anyway, my point is that there's lots of solutions here. It doesn't seem very difficult to me conceptually. Just a lot of experimentation needed to know exactly how those 9 instructions work and the exact mechanisms of the ISR / OSR / X reg / Y reg.
- tekromancr 6y agoDon't you need more states, or an invalid state state to account for bounces?
- dragontamer 6y agoDue to the grey-code methodology, a bounce will be interpreted as a "+1" followed by a "-1". (or alternatively: a -1 followed by a +1) in all situations. Ex: 00 -> 10 -> 00 is interpreted as 0 -> 3 -> 0, or -1 transition then a +1 transition. 01 -> 11 -> 01 is interpreted as 1 -> 2 -> 1, or +1 followed by a -1 transition. All possible combinations of bounces (00 -> 01 -> 00, 00 -> 10 -> 00, 01 -> 11 -> 01, 01 -> 00 -> 01, etc. etc.) have this +1 / -1 or -1/+1 property.
- yetihehe 6y agoIf you want to do it in pio, one of solutions would be some elaborate series of jump instructions to detect what changed and then in which direction. Every jump string would terminate with instruction sending direction to queue. Otherwise you could just output pins on every change into some array through dma and read that array on main processor and calculate current position in batched mode.
- MatthiasWandel 6y agoI guess the ting to do would be to somehow wait on a change on either line. Could be done with a wait on just one line if I only want half counts instead of quarter counts
- yetihehe 6y agoOr just continuously poll input pins and compare with scratch register. When anything changes, execute check logic. Probably best one is just waiting on pin changes and sending them to main processor for handling. I have multiple quadrature decoder in plans, but nothing started yet and this is approach I will be taking if no one makes anything better until then. When you check 16 pins for changes, you can easily handle 8 decoders with just one PIO state machine and some cunning decoding logic. I wouldn't be surprised, if someone implements quadrature decoding with hardware interpolator. It's one nice piece of hardware. You can do single cycle 2d texture mapping or rotozooming with that thing.
- IshKebab 6y agoQuadrature decoding is extremely common in microcontrollers, though the peripheral may not be called a quadrature decoder. Normally they just make the counter peripheral flexible enough to do it. Example for the ESP32: https://github.com/espressif/esp-idf/tree/73db142/examples/peripherals/pcnt/rotary_encoder https://github.com/espressif/esp-idf/tree/73db142/examples/p... I would not be surprised if you don't need PIO for this on this chip.
- MatthiasWandel 6y agoHad not thought of that. But that will give me one count per bar that goes past the encoder, yet there are four transitions that could potentially be counted. Of course, the pulses are not precisely symmetrical, so the four sub-counts are potentially not the same size.
- sfrigon 6y agoWhat stops major SoC designers to come up with a standard "programmable IOs" interface for all their IOs, a little bit like these PIOs, instead of shipping hundreds of flavors of the same CPU with different IO options? I guess it's more expensive to design & manufacture a truly general purpose IO, but doesn't the cost of warehouse and the risk of not having a market for that specific SoC outweigh the initial cost? It would also lower the number of pins on the SoC. e.g. You only want HDMI & SATA, here's the VHDL for it, you can even individually select the pins you want to use.
- dragontamer 6y ago> You only want HDMI & SATA SATA is a 6Gbps port, while HDMI is a 10Gbps port. The PIO ports discussed here are on the order of 100kHz, roughly 1-million times slower than HDMI, and 600,000 times slower than SATA.
- sfrigon 6y agoRight, but I didn't mean for this specific device. I meant for higher end SoCs, as in the RaspberryPi4/BeagleBone Black or even higher end SoCs. The RP2040 could be used as an inspiration to provide the same level of freedom to other SoCs.
- sfrigon 6y agoWell actually, it would be nice to replace any specific interface with a generic FPGA-like interface. But of course what you can implement with it would be limited to the speed of the CPU / peripheral.
- dragontamer 6y agoMy overall point is that GHz-speed decoding in a flexible manner seems... difficult... to say the least. Your discussion point of "here's a VHDL block" seems to understand the general issue. You need a non-trivial amount of FPGA-magic (LUTs) to implement logic and routing at those speeds. SATA has some kind of error-correction code if I remember correctly... so its not exactly easy to parse those messages.
- gchadwick 6y agoI've just been getting to grips with the PIO, they are a lot of fun to program. Whilst the instruction set is very limited with only enough memory for 32 of them you can do some pretty sophisticated things. A key part of the flexibility is the ability to execute incoming data as instructions. You can stream in a mixture of data and instructions via the DMA, the PIO separates the two and uses the instructions to modify execution so a simple loop becomes a lot more flexible. You can easily sync several of them up to. The scanvideo library: https://github.com/raspberrypi/pico-extras/blob/master/src/common/pico_scanvideo/README.adoc https://github.com/raspberrypi/pico-extras/blob/master/src/c... which is for producing video signals (like VGA) on the fly with scanlines supplied by the CPU is a good example of what's possible. You can have multiple scanline PIO running at once, each decoding a compressed RLE format including transparency and it composes them all together on the fly. If you've ever played one of the Zachtronics games (in particular Shenzhen IO) it feels like the programming in one of those games. Tight constraints but major possibilities when you think about it for a while.
- pkaye 6y agoThe Cypress FX3 is another chip with programmable IO which people have used to make low cost logic analyzers.