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 : Rotary encoder interrupt issues
Page 1 of 2 | |||||
Author | Message | ||||
Andy-g0poy Regular Member Joined: 07/03/2023 Location: United KingdomPosts: 55 |
I have been trying out the PICO and MMBASIC as a simple development system and so far it is working nicely. I have solid code for the cheap ky-040 rotary encoder working fine I have been trying to get this higher quality encoder working, it's sold as for use with CNC machines. It is a really nice dial. It outputs the normal quad. phase signals, but it is slightly different to the KY-040 in that the phase of the outputs is always 90 degrees apart. The encoder is an optical type, so no contact bounce to worry about and it outputs 100 pulses per rev. The ode to use these encoders is veru very simply you simply interrupt of the rising edge of output A and then read output B. If hige is mover in one direction if low it's moved in the other. The simplest code I've tried is this: sub do_enc 'Rotary encoder interrupt routine 'Requires init_encoder to be set up and run at startup ' Returns: modifies rotflag +ive = CW -ive -CCW ' statea% = pin(clk%) stateb% = pin(dt%) if statea% = high% then if stateb% = low% then inc rotflag , counterclk% else inc rotflag , clockw% endif endif end sub It ALMOST works. It detects the clockwise direction OK but sometimes it gets the direction wrong. so the output jitters In the anticlockwise direction to situation is worse. Just to make sure tyat the encoder is OK, I wrote the same code in c on an little ardunio nano and it works perfectly. I'm wondering if I am hitting the limits of what the MMBASIC interrupt is capiable of, yopu can spin the encoders quite quickly, especially after takign out the detent spring, so 600Hz pulses would be easily generated. However the problem occurs at much lower speeds, and even when I turn the dial slowly the problem still occurs. I'm just wondering if it is just on the limit of what the system can cope with. The docs indicate that there can be a lot of system interrupts going on, so perhaps that's it. If anyone knows what the limits are any advice would be appriciated. I'm wondering if the PIO system could be used , but as yet I don't know anything about programming that. Andy |
||||
Mixtel90 Guru Joined: 05/10/2019 Location: United KingdomPosts: 6757 |
If you can use Count inputs those are hardware interrupts and are *very* fast. Mick Zilog Inside! nascom.info for Nascom & Gemini Preliminary MMBasic docs & my PCB designs |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 851 |
No need for interrupts....Credit to Volhout for making this possible 'Quadrature multichannel decoder for PicoMite using PIO 'this is a 4 channel quadrature decoder for MMBasic V5.08.00 or newer 'running on a PicoMite or PicoMiteVGA usinf PIO 1 'Original PIO source: https://github.com/p-o-l-e/quadrature-decoder '30-4-2024 Volhout 'system initialisation --------------------------------------------------------- 'program PIO 1 program_PIO 'program the code in PIO 1 'quadrature decoders generic settings PIO_f%=63e6 'frequency = 63MHz PIO_s%=0 'shift register (shift in left) PIO_e%=PIO(execctrl gp0,wrp_tgt,wrap) 'start and stop of the loop 'start quadrature decoders (uncomment the needed decoders) start_sm0 : sm0=1 'start decoder on GP0 and GP1 'start_sm1 : sm1=1 'start decoder on GP2 and GP3 'start_sm2 : sm2=1 'start decoder on GP4 and GP5 'start_sm3 : sm3=1 'start decoder on GP26 and GP27 'main MMBasic code ------------------------------------------------------------- 'this code is just a demo that shows the position of the quadrature decoders 'the selected decoder positions are printed on the console 'type "r" to reset the decoders to 0 'main loop do a$=inkey$ 'just for control 'get the data from the fifo and print if sm0 then print str$(read_fifo(0),12,0); 'get data from decoder if sm1 then print str$(read_fifo(1),12,0); 'get data from decoder if sm2 then print str$(read_fifo(2),12,0); 'get data from decoder if sm3 then print str$(read_fifo(3),12,0); 'get data from decoder 'just some pause pause 100 'any delay needed 'reset position (PIO X register) under control of keyboard if a$="r" then 'press r to zero position if sm0 then pio execute 1,0,&hA023 '= assembly "mov X, null" (zero X reg) if sm1 then pio execute 1,1,&hA023 '= assembly "mov X, null" (zero X reg) if sm2 then pio execute 1,2,&hA023 '= assembly "mov X, null" (zero X reg) if sm3 then pio execute 1,3,&hA023 '= assembly "mov X, null" (zero X reg) a$="" end if loop while a$="" 'exit when any key not r end ' subroutines ------------------------------------------------------------------- 'this function returns the actual count of decoder n function read_fifo(n) as integer local dat%(3) pio read 1,n,4,dat%() 'read whole fifo read_fifo = dat%(3) 'last data in fifo if read_fifo>2147483647 then inc read_fifo,-4294967296 '2'th complement end function 'this subroutine programs the quadrature decoder program for PIO1 into 'PIO program memory sub program_PIO 'this is the PIO program in machine code dim p%(7)=(&h001A00170015001A,&h0015001A001A0017,&h0017001A001A0015, &h001A00150017001A,&h4042A0404042A0C3,&hA029001A005AA0A6,&h8000A0C1A0290059,0) 'here the program is programmed in memory pio program 1,p%() wrap=27 'end of the program wrp_tgt=16 'start of the program loop end sub 'this subroutine starts state machine 0 quadrature decoder on gp0 and gp1 sub start_sm0 'uses GP0,GP1, assign pins in MMBasic setpin gp0,pio1 setpin gp1,pio1 'assign pins in PIO pin_sm0%=PIO(pinctrl 0,0,0,gp0) 'configure and start PIO pio init machine 1,0,PIO_f%,pin_sm0%,PIO_e%,PIO_s%,wrp_tgt pio start 1,0 end sub 'this subroutine starts state machine 1 quadrature decoder on gp2 and gp3 sub start_sm1 'uses GP2,GP3, assign pins in MMBasic setpin gp2,pio1 setpin gp3,pio1 'assign pins in PIO pin_sm1%=PIO(pinctrl 0,0,0,gp2) 'configure and start PIO pio init machine 1,1,PIO_f%,pin_sm1%,PIO_e%,PIO_s%,wrp_tgt pio start 1,1 end sub 'this subroutine starts state machine 2 quadrature decoder on gp4 and gp5 sub start_sm2 'uses GP4,GP5, assign pins in MMBasic setpin gp4,pio1 setpin gp5,pio1 'assign pins in PIO pin_sm2%=PIO(pinctrl 0,0,0,gp4) 'configure and start PIO pio init machine 1,2,PIO_f%,pin_sm2%,PIO_e%,PIO_s%,wrp_tgt pio start 1,2 end sub 'this subroutine starts state machine 3 quadrature decoder on gp26 and gp27 sub start_sm3 'uses GP26,GP27, assign pins in MMBasic setpin gp26,pio1 setpin gp27,pio1 'assign pins in PIO pin_sm3%=PIO(pinctrl 0,0,0,gp26) 'configure and start PIO pio init machine 1,3,PIO_f%,pin_sm3%,PIO_e%,PIO_s%,wrp_tgt pio start 1,3 end sub |
||||
Andy-g0poy Regular Member Joined: 07/03/2023 Location: United KingdomPosts: 55 |
Thank's both. I never considered that the count input might be useble, worth a think about. The PIO code looks very good, and probably just the thing to get started on exploring PIO cod. Andy |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 851 |
If that's a 100 line encoder, you will get 400 quadrature counts/revolution Here, the frequency is 63MHz. Divide that by 44 and the count rate ~1.43M counts/sec. Works beautifully. |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 851 |
Counting pulses is one thing but the phase offset between CHA & CHB indicates direction (up/down). Edited 2024-11-05 06:13 by PhenixRising |
||||
Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 4212 |
Hi Andy_g0poy, If you want to use this quadrature decoder as a way to learn PIO then please look at below code. This shows the assembly instructions for the PIO, rather than hex codes. There is also a thread on thebackshed forum where interactively parts of PIO are explained. It is quite a read, so reserve one or more evenings to go through it. PIO explained The Quadrature decoder PIO code: 'Q_decoder_pico_PIO 'based on Github code 'Generic defines f= 63e6 '63 MHz (50% of 126MHz clock of PicoMiteVGA) setpin gp0,pio1 'the I signal setpin gp1,pio1 'the Q signal ' PIO code ------------------------------------------------------------------- 'header and start of the PIO code pio assemble 1,".program test" pio assemble 1,".line 0" 'jump table that forms the state machine pio assemble 1,"JMP push_out" '00-00 pio assemble 1,"JMP minus1" '00-01 pio assemble 1,"JMP plus1" '00-10 pio assemble 1,"JMP push_out" '00-11 pio assemble 1,"JMP plus1" '01-00 pio assemble 1,"JMP push_out" '01-01 pio assemble 1,"JMP push_out" '01-10 pio assemble 1,"JMP minus1" '01-11 pio assemble 1,"JMP minus1" '10-00 pio assemble 1,"JMP push_out" '10-01 pio assemble 1,"JMP push_out" '10-10 pio assemble 1,"JMP plus1" '10-11 pio assemble 1,"JMP push_out" '11-00 pio assemble 1,"JMP plus1" '11-01 pio assemble 1,"JMP minus1" '11-10 pio assemble 1,"JMP push_out" '11-11 'the actual PIO code, uses Y to store previous IO state pio assemble 1,".wrap target" 'end of program returns here pio assemble 1,"delta0:" 'this is also the no-change entry pio assemble 1,"mov ISR,null" 'clear jump address pio assemble 1,"in Y, 2" 'shift 2 bits from Y into ISR pio assemble 1,"mov Y, pins " 'Copy 2 IO bits into Y (note "space" after pins) pio assemble 1,"in Y, 2" 'shift these 2 bits from Y into ISR pio assemble 1,"mov PC, ISR" 'jump to the address in ISR (jump table)) 'The plus and minus routines, X is the actual 32 bit position counter pio assemble 1,"minus1:" 'jump here to decrement X pio assemble 1,"JMP X--, push_out" 'decrement X pio assemble 1,"JMP push_out" 'regardless X value go to push_out pio assemble 1,"plus1:" 'label to increment X pio assemble 1,"mov X, !X" 'invert X pio assemble 1,"JMP X--, next1" 'decrement x pio assemble 1,"next1:" 'regardless X value come here pio assemble 1,"mov X, !X" 'invert back 'send the most recent position to FIFO pio assemble 1,"push_out:" 'label pio assemble 1,"mov ISR, X" 'move X to ISR pio assemble 1,"push" 'push ISR into FIFO, not blocked 'outer loop and end of program pio assemble 1,".wrap" 'end of code, jump to wrap target pio assemble 1,".end program"' list" 'configure PIO 1 statemachine 0 (free on PicoMiteVGA) p0=pio(pinctrl 0,0,0,gp0) 'gp0 is lowest IN pin (gp0,gp1)) e0=pio(execctrl gp0,pio(.wrap target),pio(.wrap)) 'wrap and wrap target s0=pio(shiftctrl 0,0,0,0,0,0) 'shift IN direction is left (the 5'th '0') 'initialize PIO 1 state machine 0 pio init machine 1,0,f,p0,e0,s0,0 'init machine with start=0 (=jmp delta0) 'start the quadrature decoder PIO 1 statemachine 0 pio start 1,0 'main MMBasic code ------------------------------------------------------------- dim dat%(3) 'array to store FIFO data 'main loop do a$=inkey$ 'just for control 'get the data from the fifo pio read 1,0,4,dat%() 'read whole fifo posi=dat%(3) 'last data in fifo if posi>2147483647 then inc posi,-4294967296 '2'th complement print posi 'show position 'just some pause pause 100 'any delay needed 'reset position (PIO X register) under control of keyboard if a$="r" then 'press r to zero position pio execute 1,0,&hA023 '= assembly "mov X, null" (zero the X register) a$="" end if loop while a$="" 'exit when any key not r end This is essentially the same code as PhenixRising, only used for 1 PIO state machine. It is remarkable that all 4 state machines can run the exact same code at the same time. But each state machine has it's own communication channel (FIFO's) and can be assigned it's own GPIO pins. Volhout Edited 2024-11-05 06:48 by Volhout PicomiteVGA PETSCII ROBOTS |
||||
Andy-g0poy Regular Member Joined: 07/03/2023 Location: United KingdomPosts: 55 |
The output is 100 pulses per rev. If you trigger on the rising edge of the A output, that will be slap in the middle of the pulse on the B output so you get the direction from the value of the B output. Really nice encoders - They use a slotted disk rather than etched lines. It's also possible to remove the detent spring, so you get rid of the "click" which makes it a very nice tuning knob. (I do have a 1000 pulse per rev shaft encoder to play with at some point) Andy |
||||
Andy-g0poy Regular Member Joined: 07/03/2023 Location: United KingdomPosts: 55 |
Hi Volhout, Many thanks, that makes it much more readable. I'll go through the posts as well. Andy |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 851 |
It might be that your 1000 pulse encoder has complementary outputs (A, -A, B, -B) for better noise immunity. I use the MC3486 line receiver to interface to the MCU. |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 851 |
@Volhout Hi Harm. I don't know if this is feasible but; apart from the A & B signals, there can also be a third "Index" signal (can be "C" or "I" or "Z"). This pulse occurs once/revolution and is used for accurate referencing (homing). Currently, I have this triggering an input-interrupt and just like most CNC machines, the homing routine has to turn the motor very slowly to register a consistent home position. The reason that other controllers have to do this slowly is because they only read the encoder during a PID update which is typically a 1KHz loop. So as per Nyquist, it's typical to use something like 250 quad-counts/sec homing speed (painful when 360° = 10,000 counts) My PicoMite uses an interrupt which responds fast enough that I don't need to be so slow BUT... It would be super-cool if this could be handled by the PIO where the main counter is set to zero (or a preset value) due to an index pulse transition. Enable_Index_Reset = True |
||||
Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 4212 |
Hi Phenix, I will look into it, but remember that when you enable this function in PIO, at every index pulse the counter will be zero'ed until you disable. And to do this you need MMBasic to enable and disable fast and controlled. The alternative, that you "trigger" this from MMBasic to happen only once, results in too much PIO code. Remember the 32 PIO program memory is almost full. I think it won't fit into PIO memory, and -if- I can make it fit it will be a function that is active for all 4 encoders at the same time. Maybe that is what you want, but maybe not. I think it is best to slow CNC movement down, and handle this from MMBasic. MMBasic should be able to do this within 1ms, so you can calculate the required maximum CNC speed from the encoder resolution. Volhout Edited 2024-11-05 18:06 by Volhout PicomiteVGA PETSCII ROBOTS |
||||
Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 4212 |
@Andy, I looked at your original code, and I think it could be optimized to work better. In MMBasic you can set an interrupt on a pin. If you set that for interrupt going high on clock, you do not need to check the clock being high in your interrupt routine. But... You do need to make sure the clock line goes low before you enable the interrupt again(1), -or- simplest. (2)Do not exit the interrupt routine before the clock line goes low. (1) re-program the interrupt for low transition. In the "low" interrupot routine program the interrupt again for "high". (2) just before "END SUB" insert DO:LOOP UNTIL PIN(clk%) = low% But this typically costs 50% CPU power wasted waiting. Your code however only uses the clock transition going high to count. The PIO code increments and decrements at every transition of either data or clock. So it gives better resolution. You can also improve your code. In example, this code would look at both clock edges (from 100 resolution to 200 resolution). sub do_enc 'Rotary encoder interrupt routine 'Requires init_encoder to be set up and run at startup ' Returns: modifies rotflag +ive = CW -ive -CCW ' statea% = pin(clk%) stateb% = pin(dt%) if statea% = high% then if stateb% = low% then inc rotflag , counterclk% else inc rotflag , clockw% endif else if stateb% = high% then inc rotflag , counterclk% else inc rotflag , clockw% endif endif end sub Then the interrupt should be set to both edges sensitive. SETPIN gpx,INTB,do_enc Regards, Volhout Edited 2024-11-05 18:24 by Volhout PicomiteVGA PETSCII ROBOTS |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 851 |
Yes. The idea is that, on power-up, the machine needs to be referenced: Enable_Index_Reset = True DO 'homing routine LOOP UNTIL homed = True Enable_Index_Reset = False Understand the memory limitation for PIO but was wondering if it could be made to work for only two encoders? Sorry for my PIO ignorance. I have decided to use an RP2350 for each motor and my "trade-mark" approach is "dual-loop" (two encoders/axis). No real need for four encoders. |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 851 |
I just came across an app note on my PC that might be of interest for non-PIO encoder interfacing. an-1011.pdf |
||||
Volhout Guru Joined: 05/03/2018 Location: NetherlandsPosts: 4212 |
Hi Phenix, I just wanted to indicate that the program will make the counter zero based on an external trigger (from MMBasic). That means that all running statemachines will zero their counter becuase the (common) program tells them to do this. Unless we control this through individual triggers (configurable per state machine) such as the TX-FIFO. These are individual. Or state machine configured GPIO pins. But then we have to sacrifice (max 4) pins solely for this. Volhout P.S. note that there are max 8 PIO instructions left. note that the "loop" will increase by the check for zero-ing, and thus the max encoder speed will go down. This may not be an issue, but it is worth to mention. Edited 2024-11-05 18:36 by Volhout PicomiteVGA PETSCII ROBOTS |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 851 |
Perfect! This would be a feature, not a fault Not a problem: An axis that is capable of 1000mm/sec with a resolution of 0.001mm (a ridiculous spec') would still only mean 1M quad-counts/sec (We have something really cool, here) |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 851 |
Just thinking about this though; I run a program, stop it, make a code change, re-run the program and the encoder-count is preserved from the previous version. Not in a position to play with this right now but it seems that we could have a PIO routine for homing and once homing is complete, load the standard encoder PIO code(?) |
||||
Marcel27 Regular Member Joined: 13/08/2024 Location: NetherlandsPosts: 53 |
Volhout, thanks for the info! Marcel If you use AI, you lose your mind. |
||||
PhenixRising Guru Joined: 07/11/2023 Location: United KingdomPosts: 851 |
@Volhout FYI: For best possible precision, the index is gated with A and B: (apologies for the poor quality) |
||||
Page 1 of 2 |
Print this page |