Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 09:20 22 Nov 2024 Privacy Policy
Jump to

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 Kingdom
Posts: 56
Posted: 05:46pm 04 Nov 2024
Copy link to clipboard 
Print this post

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 Kingdom
Posts: 6761
Posted: 05:52pm 04 Nov 2024
Copy link to clipboard 
Print this post

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 Kingdom
Posts: 853
Posted: 05:54pm 04 Nov 2024
Copy link to clipboard 
Print this post

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
  print
 
  '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 Kingdom
Posts: 56
Posted: 06:43pm 04 Nov 2024
Copy link to clipboard 
Print this post

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 Kingdom
Posts: 853
Posted: 07:11pm 04 Nov 2024
Copy link to clipboard 
Print this post

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 Kingdom
Posts: 853
Posted: 08:12pm 04 Nov 2024
Copy link to clipboard 
Print this post

  Andy-g0poy said  Thank's both.

I never considered that the count input might be useble, worth a think about.

Andy


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: Netherlands
Posts: 4213
Posted: 08:30pm 04 Nov 2024
Copy link to clipboard 
Print this post

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 Kingdom
Posts: 56
Posted: 10:29pm 04 Nov 2024
Copy link to clipboard 
Print this post

  PhenixRising said  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.


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 Kingdom
Posts: 56
Posted: 10:32pm 04 Nov 2024
Copy link to clipboard 
Print this post

  Volhout said  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.

Volhout


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 Kingdom
Posts: 853
Posted: 06:04am 05 Nov 2024
Copy link to clipboard 
Print this post

  Andy-g0poy said  
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






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 Kingdom
Posts: 853
Posted: 07:40am 05 Nov 2024
Copy link to clipboard 
Print this post

@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: Netherlands
Posts: 4213
Posted: 08:05am 05 Nov 2024
Copy link to clipboard 
Print this post

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: Netherlands
Posts: 4213
Posted: 08:13am 05 Nov 2024
Copy link to clipboard 
Print this post

@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 Kingdom
Posts: 853
Posted: 08:21am 05 Nov 2024
Copy link to clipboard 
Print this post

  Volhout said  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


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 Kingdom
Posts: 853
Posted: 08:26am 05 Nov 2024
Copy link to clipboard 
Print this post

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: Netherlands
Posts: 4213
Posted: 08:31am 05 Nov 2024
Copy link to clipboard 
Print this post

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 Kingdom
Posts: 853
Posted: 08:55am 05 Nov 2024
Copy link to clipboard 
Print this post

  Volhout said  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.


Perfect! This would be a feature, not a fault  


  Volhout said  
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.


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 Kingdom
Posts: 853
Posted: 09:06am 05 Nov 2024
Copy link to clipboard 
Print this post

  Volhout said  
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.


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: Netherlands
Posts: 53
Posted: 09:30am 05 Nov 2024
Copy link to clipboard 
Print this post

  Volhout said  
...
PIO explained
...


Volhout, thanks for the info!

Marcel
If you use AI, you lose your mind.
 
PhenixRising
Guru

Joined: 07/11/2023
Location: United Kingdom
Posts: 853
Posted: 09:42am 05 Nov 2024
Copy link to clipboard 
Print this post

@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
© JAQ Software 2024