Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.
|
Forum Index : Microcontroller and PC projects : PIO for quadrature encoders
Page 1 of 4 | |||||
Author | Message | ||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 857 |
I seem to remember that this wasn't possible with the PicoMite but I don't remember what the limitation was(?): # SPDX-FileCopyrightText: 2022 Jamon Terrell <github@jamonterrell.com> # SPDX-License-Identifier: MIT from rp2 import PIO, StateMachine, asm_pio from machine import Pin import utime @asm_pio(autopush=True, push_thresh=32) def encoder(): label("start") wait(0, pin, 0) # Wait for CLK to go low jmp(pin, "WAIT_HIGH") # if Data is low mov(x, invert(x)) # Increment X jmp(x_dec, "nop1") label("nop1") mov(x, invert(x)) label("WAIT_HIGH") # else jmp(x_dec, "nop2") # Decrement X label("nop2") wait(1, pin, 0) # Wait for CLK to go high jmp(pin, "WAIT_LOW") # if Data is low jmp(x_dec, "nop3") # Decrement X label("nop3") label("WAIT_LOW") # else mov(x, invert(x)) # Increment X jmp(x_dec, "nop4") label("nop4") mov(x, invert(x)) wrap() sm1 = StateMachine(1, encoder, freq=125_000_000, in_base=Pin(3), jmp_pin=Pin(2)) sm1.active(1) while(True): utime.sleep(1) sm1.exec("in_(x, 32)") x = sm1.get() print(x) So I would need to switch to Python for this? Github Link |
||||
Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 4225 |
Phenix, Yes, you can switch to python. This is RP2040 PIO/python code. That makes life easier. But.. This code is directly portable to MMBasic on the picomite. 1/ There is a built in PIO assembler. Instructions may not be exactly identical, but it works fine. 2/ There (also) is a "direct ecexute PIO instruction from MMBasic" 3/ And you can read the PIO FIFO to get results. But AFAIK, you are programming on CMM2, and CMM2 does not have a PIO. Regards, Volhout PicomiteVGA PETSCII ROBOTS |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 857 |
Actually no. Just the PicoMite and the ARMmite. I'd like this PIO routine to work with the PicoMite |
||||
JohnS Guru Joined: 18/11/2011 Location: United KingdomPosts: 3800 |
It will already work with Picomite. (Some syntax tweaks, sure.) John |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 857 |
Thanks John, exciting stuff Will try to lock-out the weekend to study and experiment. Any clue regarding maximum pulse frequency to expect? I have seen some impressive numbers from Harm in the past but these Octoquad guys are talking a measly 250KHz, albeit with eight encoders (16 pulse trains). They do state, however, that they haven't tested the limit. |
||||
JohnS Guru Joined: 18/11/2011 Location: United KingdomPosts: 3800 |
I've no idea about max freq, but does the code actually already do everything you want because if it doesn't you may be speeding it up or slowing it down. Beware of needing too many (PIO) program steps - each PIO is limited. John |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 857 |
Yeah, I only need to read one encoder but it would be nice to have a second one for an optional feature. In actual fact, 250KHz=1M quadrature cts/sec which is plenty fast. A linear axis can achieve a velocity of 1000mm/sec with a position resolution of 1µm. It was just that; I seem to remember Harm achieving much higher pulse rates. |
||||
Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 4225 |
Phenix, The PIO has 4 statemachines. When clocked at 133 Mhz, they easily decode a 10Mhz quadrature encoder.. Each. So you can run up to 8 on a single pico. Question is what you do with it. A pico may run a 1ms PID, but maybe not for 8 channels. Volhout PicomiteVGA PETSCII ROBOTS |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 857 |
I've been a proponent of Dual-Loop Feedback for several decades, now. Not always practical but if there is a need for true precision and stability, I highly recommend it. The industry has kinda gone backwards, over the years. Modern BLMs have an encoder fixed to the motor shaft and so this is utilized for position feedback. The problem is that; only the motor shaft-angle is controlled. It's assumed that the mechanical drive-train is backlash-free (never the case). |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 857 |
@Volhout 10MHz makes much more sense, considering sysclock and asm code. Very cool indeed |
||||
Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 4225 |
Phenix, When you are looking into the code, let me try to explain how I read their code, and think it works. A PIO can run a small program but has a limitted instruction set. A PIO has 2 register that can be used for counting: X and Y. Which ever one you choose does not matter. But it can ONLY DECREMENT, not increment. For increment they INVERT the register, decrement, then invert back. This is a 1's complement instruction. Meaning that this way of calculating has typically 2 zero values (0, and &hFFFFFFFF) . All registers are 32 bit. The ARM can talk to the PIO using FIFO's. MMBasic can write values in the FIFO's, and the PIO can take the values, and vice versa. Every PIO has 4 state machines, and each state machine has 2 FIFO's, 4 deep. Default one for READ and one fro WRITE (but you can chain them to create one that is 8 deep). The program in the state machine does following: start 1/check quadrature inputs 2/increment -or- decrement X register 3/copy X to output-register 4/push output-register in out-FIFO 5/read in-FIFO to input register (* see note) go to start The loop time determines the the maximum quadrature frequency. 1/ and 2/ are required to run the quadrature decoder 3/ and 4/ is needed because X cannot be copied to the FIFO directly This way MMBasic can at any time pull data from the FIFO and get the quadrature count. 5/ is needed to the ZERO-ing. MMBasic can instruct PIO to execute direct commands. But since MMBasic is "slow" and PIO is "fast" it is not possible to let PIO execute 2 successive commands. Only 1 command can be executed. The command is "copy input-register to X". In case you write 0 to the in-FIFO, the program will set the value 0 ready in the input-register. So when you execute the direct command, you will effectively zero the X register. But of coarse you can also write a different value to the FIFO (in example &h80000000) and use that as ZERO reference. Then quadrature decoding will work from there. Because the direct command is executed immediately, this can also happen in the increment instruction (INVERT-DECREMENT-INVERT) when X is inverted. Confusing the algorithm. That is why it is essential to only ZERO the system when the associated axis is halted. 1/ can be simple decoding a single edge, or dual edge, or dual edge on both I and Q signals. The latter gives better resolution, but you need to run the loop 4x more often, so the quadrature speed goes down. Once you have this program in PIO memory, each of the 4 state machines can use the exact same code. Each state machine has it's own IO pin definition, it's own FIFO's, and it's own clock speed. So you can run one decoder at 100MHz, and onother one ia 100kHz (to use for a rotary encoder on the user interface, not on the machine at all). Note: FIFO is 4 deep. So the most recent quadrature count is at position 4 in FIFO, You have to read all 4, and skip the first 3, only use the 4'th value as it is most recent. Regards, Volhout Edited 2024-04-25 17:23 by Volhout PicomiteVGA PETSCII ROBOTS |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 857 |
@Volhout Very much appreciated, thank you. Your timing was perfect because I was looking at This Issue Not sure that this is going to be robust enough. Don't want to be destroying mega-buck machinery. |
||||
Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 4225 |
Phenix, I have an idea what caused their problem. You can also use a direct command to get the X value. And when you apply that at the wrong moment you see exactly what they identify. And yes, using Y solves that issue. I am sure this code can be made robust. Essential is that you supply the PIO pins with good logic level signals. I have great confidence in PIO, once it runs, it simply works. I think of it more as "hardware". Like an FPGA. And it is pretty much that. Volhout Edited 2024-04-25 19:30 by Volhout PicomiteVGA PETSCII ROBOTS |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 857 |
Thanks for the reassurance Signal interfacing is via my long-time-standard MC3486 Differential Receiver because my encoders feature differential drivers. mc3486.pdf |
||||
Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 4225 |
Phenix, Just a suggestion: For the quadrature decoder, the code they show is decoding 1 phase out of 4. Decoding 2 phases out of 4 is also simple (you use 1 signal as a clock, the other as data). Decoding 4 phases is more tricky. So I would start the effort with 1/4 or 2/4 decoding. And once you have that running (including the PID on Pico), you can start thinking about 4/4 decoding. 4/4 is complexer PIO code, and with same mechanics, lays higher burden on the whole system. Regards, Volhout Edited 2024-04-25 20:48 by Volhout PicomiteVGA PETSCII ROBOTS |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 857 |
@Volhout Just throwing thoughts at you because I realise that you understand this stuff. After good results with the H7, I decided to try the Pico because I'm shooting for a design that can use the most readily available components and it doesn't get more ubiquitus than the Pico. A PID loop-rate of 1KHz is the generally accepted rate for 99.9% of applications because mechanical time-constants are comparatively low: Mechanical time constant is defined as the minimum time that an axis takes to reach 63% of target velocity. The loop rate that is recommended to be 5-10 times faster (way more than Nyquist). The sizes of machinery that I work with, these numbers are crazy. When coupled to a real load and with inertia, friction, stiction to contend with, a much lower sampling rate can still be a waste of processor time. The PicoMite can handle 1KHz but I have settled for 500HZ (the default for BeckHoff's TwinCAT and other big players). So, If I can eliminate my current LS7366 (quad counter) which I source from the US, all the better. Even using separate PicoMite processors, one-per-axis, coordinated motion over a serial-link can be extremely accurate. Only 8-bit addressing though so limited to 255 axes |
||||
JohnS Guru Joined: 18/11/2011 Location: United KingdomPosts: 3800 |
Wrong code is wrong code. John Edited 2024-04-25 21:04 by JohnS |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 857 |
Hmmm, some of the older machines that I retrofit, only have 10 pulses/mm but thanks to quadrature, I have X4 resolution. OTOH, the newer motors that I am looking at have 17-bits/rev so I would be looking at scaling, anyway. |
||||
Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 4225 |
This code basically is a 2/4 decoder (signal B is clock, A is data). .program quadrature start: wait 0 pin 0 ; wait for B == 0 jmp PIN, wait_high ; if A == 0 mov y, !x ; x++ { jmp y--, nop1 ; nop1: ; mov x, !y ; } jmp nop2 wait_high: jmp x--, nop2 ; x-- { nop2: ; } wait 1 PIN 0 ; wait for B == 1 jmp PIN, wait_low ; if A == 0 jmp x--, start ; x-- { ; } wait_low: ; else mov y, !x ; x++ { jmp y--, nop4 ; nop4: ; mov x, !y ; jmp start ; } It uses X to count, and Y in the invert-dec-invert. I have also found a 4/4 piece of code, on Github. This may work, although it uses the Y register and X register. So you will have to stop the axis for zero-ing. ; Must start at 0 so that the following jump table can be jumped into with a ; 'mov pc, isr' instruction. .origin 0 ; 16 element jump table based on 4-bit encoder last state and current state. jmp delta0 ; 00-00 jmp minus1 ; 00-01 jmp plus1 ; 00-10 jmp delta0 ; 00-11 jmp plus1 ; 01-00 jmp delta0 ; 01-01 jmp delta0 ; 01-10 jmp minus1 ; 01-11 jmp minus1 ; 10-00 jmp delta0 ; 10-01 jmp delta0 ; 10-10 jmp plus1 ; 10-11 jmp delta0 ; 11-00 jmp plus1 ; 11-01 jmp minus1 ; 11-10 jmp delta0 ; 11-11 ; Program actually starts here. .wrap_target delta0: public start: mov isr, null ; Make sure that the input shift register is cleared when table jumps to delta0. in y, 2 ; Upper 2-bits of address are formed from previous encoder pin readings mov y, pins ; Lower 2-bits of address are formed from current encoder pin readings. Save in Y as well. in y, 2 mov pc, isr ; Jump into jump table which will then jump to delta0, minus1, or plus1 labels. minus1: jmp x-- output ; Decrement x jmp output plus1: mov x, ~x ; Increment x by calculating x=~(~x - 1) jmp x-- next2 next2: mov x, ~x output: mov isr, x ; Push out updated counter. push noblock .wrap Actually quite smart use of the PIO capabilities. Uses most of the PIO memory though (28/32) becuase the large jump table. Success with programming... Volhout Edited 2024-04-25 21:34 by Volhout PicomiteVGA PETSCII ROBOTS |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 857 |
Oooo, I like the jump-table. I do the same thing, elsewhere. Very cool and very much appreciated. Gonna clear my desk and start playing. |
||||
Page 1 of 4 |
Print this page |