Interrupts on both Rotary Encoder pins possible?

Haroen

Member
Hi, I tried to make my quadrature incremental encoder to respond immediatly when rotation occurs.
I used Interrupts for this.
But somehow it only react on half of the pulses!?

A 1 1 0 0
B 0 1 1 0

This is just some demo code to visualize the encoder with some variables of the PICAXE chip on the "PICAXE Editor 6" debug screen.
  • incA and incB are the interrupt pins,
  • EncoderA and EncoderB are variables to display pins state,
  • InterruptCounter should just increase on every next turn of the Rotary encoder.
  • This only result in 2 times increase and 2 times no increase!?

Code:
#PICAXE 20X2
SetFreq M8

Init:
	'PIN's.
	Symbol incA = pinB.0
	Symbol incB = pinB.1
	'VARIABLES.
	Symbol InterruptCounter=b1
	Symbol EncoderA=b2
	Symbol EncoderB=b3
	'INTERRUPT.
	Timer = $FFFF
	SetTimer t1s_8
	hintsetup %00000110
	Gosub Interrupt_Init

Main:
	Debug
	'Encoder pins debug visualisatie.
	If pinB.0=1 Then
		EncoderA=1
	Else
		EncoderA=0
	EndIf
	If pinB.1=1 Then
		EncoderB=1
	Else
		EncoderB=0
	EndIf
Goto Main

Interrupt:
	If hInt1Flag = 1 OR hInt2Flag = 1 Then
		hInt1Flag = 0: hInt2Flag = 0
	End If
	Inc InterruptCounter
	Debug
	Gosub Interrupt_Init
Return

Interrupt_Init:
	SetIntFlags Or %0000110, %00000110
Return
What's wrong here with the code?
 

geoff07

Senior Member
I haven't gone into the details of your problem. But putting a debug command in an isr is bound to mess up timings and cause unpredictability in a real-time system.
 

Haroen

Member
You're right, therefore I have to slowly turn the Rotary Encoder several times of the 4 different states(below) to confirm the problem of only reacting on half of the pulses.

A 1 1 0 0
B 0 1 1 0

It will also skip when rotating to fast but still the above problem remains.
 

hippy

Technical Support
Staff member
You're right, therefore I have to slowly turn the Rotary Encoder several times of the 4 different states(below) to confirm the problem of only reacting on half of the pulses.

A 1 1 0 0
B 0 1 1 0
Perhaps you could clarify how many pulses you think there are in the above.

The way you currently have it with a single HINTSETUP command; HINT1 and HINT2 will trigger an interrupt on positive going trasnsitions, of which there are two such events in the above, one on A the other on B.

If you want to interrupt on negative going transitions as well you will have to dynamically adjust the HINTSETUP command within the interrupt routine.
 

Haroen

Member
Every channel has 4 pulses, so 8 pulses total.
With one of the 4 states(S) I mean a combination of A+B.

S 1 2 3 4
----------
A 1 1 0 0
B 0 1 1 0

Dynamically adjusting the HINTSETUP command now works while rotating CW.
CCW is now logically the problem.
Any idea how to solve that one?
 

hippy

Technical Support
Staff member
Every channel has 4 pulses, so 8 pulses total.
With one of the 4 states(S) I mean a combination of A+B.

S 1 2 3 4
----------
A 1 1 0 0
B 0 1 1 0
That's not how a quadrature encoder would usually work. If you can post a link to the encoder you are using that might help clarify how things are or should be.

Dynamically adjusting the HINTSETUP command now works while rotating CW.
CCW is now logically the problem.
Any idea how to solve that one?
Perhaps post your modified code.
 

Haroen

Member
Encoder type: EC11J0924602

Code:
#PICAXE 20X2
SetFreq M8

Init:
	'PIN's.
	Symbol incA = pinB.0
	Symbol incB = pinB.1
	'VARIABLES.
	Symbol InterruptCounter=b1
	Symbol EncoderA=b2
	Symbol EncoderB=b3
	'INTERRUPT.
	Timer = $FFFF
	SetTimer t1s_8
	Gosub Interrupt_Init

Main:
	Debug
	'Encoder pins debug visualisation.
	If pinB.0=1 Then
		EncoderA=1
	Else
		EncoderA=0
	EndIf
	If pinB.1=1 Then
		EncoderB=1
	Else
		EncoderB=0
	EndIf
Goto Main

Interrupt:
	If hInt1Flag = 1 OR hInt2Flag = 1 Then
		hInt1Flag = 0: hInt2Flag = 0
	End If
	Inc InterruptCounter
	Debug
	Gosub Interrupt_Init
Return

Interrupt_Init:
	If pinB.0=1 Then					'CW.
		hIntsetup %00000110			'Interrupt when going low(falling edge).
	Else
		hIntsetup %01100110			'Interrupt when going high(rising edge).
	EndIf
	SetIntFlags Or %0000110, %00000110
Return
 

hippy

Technical Support
Staff member
The encoder would seem to be a standard quadrature encoder; four phases and four transitions into and out of those phases ...

Code:
Phase            :0 1 2 3:  
            ___  : :_:_: :  ___
A signal  _|   |_:_| : |_:_|   |___
              ___: : :_:_:    ___
B signal  ___|   |_:_| : |___|   |_
                   : : : :
Transitions        | | | |
It would seem you should be able to modify your code to toggle the interrupt polarity for A and B signals separately which will catch all four transitions. This works in simulation ...

Code:
#Picaxe 20X2

Symbol hintMask = b0

hintMask = pinsB & %00000110 ^ %00000110 * 16 | %00000110
Gosub Interrupt_Init

Do
Loop

Interrupt:
  If hint1flag = 1 Then
    hintMask = hintMask ^ %00100000
    hint1flag = 0
  End If
  If hint2flag = 1 Then
    hintMask = hintMask ^ %01000000
    hint2flag = 0
  End If

Interrupt_Init:
  HIntSetup hintMask
  SetIntFlags Or %0000110, %00000110
  Return
 

Haroen

Member
Thanks for the simulation code, I will give it a try but...
These types of encoders however have several fixed lock positions.
When creating a program it will lock in 2 of the 4 states.
(1lock - 2 - 3lock - 4)
The other "In between states" are hard to get in those positions as I think of it.

What should I alter in your simulation code so it will only interrupt in those fixed lock positions?
 

inglewoodpete

Senior Member
Code:
#PICAXE 20X2
SetFreq M8

Init:
	'PINs.
	Symbol incA = pinB.0
	Symbol incB = pinB.1
	'VARIABLES.
	Symbol InterruptCounter=b1
	Symbol EncoderA=b2
	Symbol EncoderB=b3
	'INTERRUPT.
	Timer = $FFFF
	SetTimer t1s_8
	hintsetup %00000110
	Gosub Interrupt_Init

Main:
	Debug
	'Encoder pins debug visualisatie.
	If pinB.1=1 Then	If pinB.0=1 Then
		EncoderA=1
	Else
		EncoderA=0
	EndIf

		EncoderB=1
	Else
		EncoderB=0
	EndIf
Goto Main

Interrupt:
	If hInt1Flag = 1 OR hInt2Flag = 1 Then
		hInt1Flag = 0: hInt2Flag = 0
	End If
	Inc InterruptCounter
	Debug
	Gosub Interrupt_Init
Return

Interrupt_Init:
	SetIntFlags Or %0000110, %00000110
Return
What's wrong here with the code?
This is one of those times when I get on my soap box and preach the following: "Keep the interrupt routine execution time to an absolute minimum".

Remove the overhead of the "Gosub" from the Interrupt routine - put the SetIntFlags command in the routine. Don't test two bits when you can test one: Just test hIntFlag instead of hInt1Flag + hInt2Flag

Avoid using Debug when you need speed. Never use Debug in an interrupt routine. If you have to log data use SerTxd and keep the messages as small as you can.
Code:
SerTxd("A", #EncoderA, "B", #EncoderB)
Finally, you can replace the following
Code:
	If pinB.0=1 Then
		EncoderA=1
	Else
		EncoderA=0
	EndIf
...with...
Code:
EncoderA = pinB.0
 

hippy

Technical Support
Staff member
This is one of those times when I get on my soap box and preach the following: "Keep the interrupt routine execution time to an absolute minimum".

Remove the overhead of the "Gosub" from the Interrupt routine - put the SetIntFlags command in the routine. Don't test two bits when you can test one: Just test hIntFlag instead of hInt1Flag + hInt2Flag
Not saying you are wrong, and you are right that interrupt time is best kept to a minimum and especially in cases like encoders where the interrupts can occur frequently. But I thought it best explain my thinking behind the example I presented which could be more efficient but isn't.

First thing is that I test hint1flag and hint2flag rather than testing an actual I/O pin. This was because, in a fast changing system, the I/O pin may not be in the same state when tested as it was when it triggered the interrupt.

Second I test both hint1flag and hint2flag rather than one and assuming the interrupt must have come from one or the other. In a very fast system there may be more than one interrupt before the first is handled and this should better cope with that, particularly if the interrupt is also counting turn pulses.

In other respects it's right; keep it as short as possible, remove as much overhead as one can, and also run the PICAXE as fast as is possible.
 

hippy

Technical Support
Staff member
What should I alter in your simulation code so it will only interrupt in those fixed lock positions?
I don't think you can. As best I can see from the datasheet the detents occur at the transitions between one of the four states, when A is stable and on the rising and falling edges of the B signal.

You may need to think of it in a different way; count the transitions and determine which detent you are at.

I think you may need to better explain exactly what outcome or indication you wish to see as a result of turning the pot.
 

Haroen

Member
"Keep the interrupt routine execution time to an absolute minimum."
Yes, I agree.
In my research before posting this thread I did find something simular for a 18M2 using "setint" on the C-pins.
This was based previously on a 08M design by "Vertigo" that did react correct on only the fixed lock positions breedboard setup.:cool:
Thread: Rotary encoder usage, #118
One final try,
(which appears to work on my setup here),
before I go into Sleep mode.

Code:
;Forum Th 271011
#picaxe 18M2

symbol getBits = b0
symbol dir = b1
symbol counter = b3

setint %00000010,%00000010 ,C
main:
do
sertxd("Main loop - Counter=",#b3,13,10)
sertxd("Main loop - dir=",#b1,13,10) 'added to monitor random changes
pause 1000
loop

interrupt:
bit2 = pinC.2: bit1 = pinC.1 

getBits = getBits & %000000110
if getBits <> 0 then 
dir = bit2 * 2 'direction: if bit2=low then dir=0; if bit2=high then dir=2
counter = counter - 1 + dir 
do while getBits <> 0 

getBits = pinsC & %000000110

loop
endif
setint %00000010,%00000010 ,C
return
I use the 20X2 two Hint B-pins and have to change the above fancy code SetInt to HINTSETUP, but how?

By the way:
I used the Debug instead of Sertxd since my update of the PICAXE Editor 6 messed up my USB downloadcable communication.
That's another problem to solve later.
 
Last edited:

inglewoodpete

Senior Member
I use the 20X2 two Hint B-pins and have to change the above fancy code SetInt to HINTSETUP, but how?

I used the Debug instead of Sertxd since my update of the PICAXE Editor 6 messed up my USB download cable communication.
That's another problem to solve later.
This project that I posted a few years ago used both positive- and negative-edge hardware interrupts. That should help you understand the practicalities - I needed help from our resident guru (hippy) at the time!

If debug is working, then SerTxd also works (put a #Terminal 9600 at the start of your program.. Both use the SerOut pin and the download cable. I believe that Debug takes about 30mS to download a single packet of register data - that's why you should avoid it when time is precious.
 

Haroen

Member
This project that I posted a few years ago used both positive- and negative-edge hardware interrupts. That should help you understand the practicalities - I needed help from our resident guru (hippy) at the time!
Like I said, pretty fancy code to wrap my poor brains around to implement on a X2 chip!
If debug is working, then SerTxd also works (put a #Terminal 9600 at the start of your program.
That did the trick for Sertxd to work, Thanks.:D
 
Last edited:

Haroen

Member
OK, adjusted 20X2 code based on 18M2 from inglewoodpete.
Code:
#picaxe 20X2
SetFreq m8

#No_Table
#No_Data
#Terminal 9600

Symbol hintMask = b0
symbol getBits = b1
symbol dir = b2
symbol counter = b3

LET dirsB=%00

hintMask = pinsB & %00000110 ^ %00000110 * 16 | %00000110
Gosub Interrupt_Init

Main:
	do
		sertxd("Counter",#b3,", Dir=",#b1,13,10)
		pause 300
	loop
Goto Main


Interrupt:
	If hint1flag = 1 Then
 		hintMask = hintMask ^ %00100000
		hint1flag = 0
	End If
	If hint2flag = 1 Then
		hintMask = hintMask ^ %01000000
		hint2flag = 0
	End If
	bit2 = pinB.0: bit1 = pinB.1 
	getBits = getBits & %000000110
	if getBits <> 0 then 
		dir = bit2 * 2
		counter = counter - 1 + dir 
		do while getBits <> 0 
			getBits = pinsB & %000000110
		loop
	endif
	Gosub Interrupt_Init
Return

Interrupt_Init:
	HIntSetup hintMask
	SetIntFlags Or %0000110, %00000110
Return
Why don't I get changing Counter or Dir values in the Terminal window when rotating the encoder?
 

inglewoodpete

Senior Member
OK, adjusted 20X2 code based on 18M2 from inglewoodpete.?
I'm not sure where that 18M2 came from, as I have never used one. The M2-series PICAXEs do not support hardware interrupts.

Why don't I get changing Counter or Dir values in the Terminal window when rotating the encoder?
Some of your code is confusing to me. Bit1 and Bit2 are in b0, which you are calling hintMask. Also, having a do-loop within a time-critical interrupt routine can give confusing results - waiting for both inputs to go low will cause the loss of other encoder steps.

Perhaps you could add some comments to your code to explain the manipulations you are performing.
 

hippy

Technical Support
Staff member
Why don't I get changing Counter or Dir values in the Terminal window when rotating the encoder?
You have mashed together two disparate pieces of code which are in conflict with one another.

The added code used to create the counter appears to only use one signal and the level of the other to count a movement and determine direction. My original code handled all edges from both signals. You have added a polling while loop into an interrupt routine which prevents the original code working as intended. In addition the added code is corrupting the data used by the original code.

There are three ways to count pulses from a traditional rotary quadrature encoder. The lowest resolution, giving lowest count for a complete revolution, more turn required per count, is to count just one edge of one signal -

Rising A : If B low Then Clockwise Else Anticlockwise

That can be improved upon by counting rising and falling edges of that single signal, doubling the count per revolution, halving the turn required per count -

Rising A : If B low Then Clockwise Else Anticlockwise
Falling A : If B high Then Clockwise Else Anticlockwise

The highest resolution, most counts per revolution, least turn per count, can be achieved by counting all four rising and falling edges -

Rising A : If B low Then Clockwise Else Anticlockwise
Falling A : If B high Then Clockwise Else Anticlockwise
Rising B : If A high Then Clockwise Else Anticlockwise
Falling B : If B low Then Clockwise Else Anticlockwise

It is usually best to start with the lowest resolution mechanism and get that working, then increase resolution as one gets a better understanding of how rotary encoders work and the software mechanism for handling the encoders is understood.

Trying to throw code into the mix without understanding what one is trying to do and how that code works will usually be doomed to failure.

The software mechanism will be different depending on whether polling or SETINT or HINTSETUP interrupts are used. Higher resolution handling will require faster PICAXE speed and more careful design to avoid counts being lost.

Depending on application, particularly speed of encoder turning, it may be that higher resolution methods are less appropriate than lower resolution methods. It can be better to get a slower consistent count than a higher count with a jerky response and more counts missed.
 

Haroen

Member
I'm not sure where that 18M2 came from, as I have never used one. The M2-series PICAXEs do not support hardware interrupts.
Sorry, should have been code from "eclectic" second line: #picaxe 18M2 where he used setint.
I have to configure code now for 20X2 Hint.

Some of your code is confusing to me. Bit1 and Bit2 are in b0, which you are calling hintMask.
You're right, adjusted the code where getBits=b0 and hintMask=b4.

Also, having a do-loop within a time-critical interrupt routine can give confusing results - waiting for both inputs to go low will cause the loss of other encoder steps.
That was written in the original code from "eclectic". I thought that myself that the code will wait too long skipping steps?!

I will add comments to the code to explain the manipulations and try the three ways to count pulses from a traditional rotary quadrature encoder according to hippy.
It's still exotic code for me, still trying.

The software mechanism will be different depending on whether polling or SETINT or HINTSETUP interrupts are used.
Higher resolution handling will require faster PICAXE speed and more careful design to avoid counts being lost.
Depending on application, particularly speed of encoder turning, it may be that higher resolution methods are less appropriate than lower resolution methods.
It can be better to get a slower consistent count than a higher count with a jerky response and more counts missed.
I understand that it's a choice one needs to make for speed or accuracy and then carefull selection of the code to program.
 

Haroen

Member
Added comments to the mashed pieces of code to analyse what conflicts with one another....
Code:
#picaxe 20X2
SetFreq m8

#No_Table
#No_Data
#Terminal 9600

'           Used bits                                          xxxx   xxxx
symbol getBits   = b0 'b0 = bit7 : bit6 : bit5 : bit4 : bit3 : bit2 : bit1 : bit0
symbol dir = b1											'Direction indicator: 0 or 2.
symbol counter = b3										'Overall rotation counter.
Symbol hintMask = b4									'Hinterrupt Mask.

hintMask = pinsB & %00000110 ^ %00000110 * 16 | %00000110	'Set hintMask with PinsB AND %00000110 XOR %00000110 *16 OR %00000110. Interrupt 1 and 2 Enabled, falling edges.
Gosub Interrupt_Init

Main:
	do
		sertxd("Counter",#b3,", Dir=",#b1,13,10)
		pause 300
	loop
Goto Main


Interrupt:
	If hint1flag = 1 Then									'When interrupt on Hint1.
 		hintMask = hintMask ^ %00100000					'Set hintMask with previous hintMask AND to interrupt on background hardware serial receive.
		hint1flag = 0									'Reset hint1flag.
	End If
	If hint2flag = 1 Then									'When interrupt on Hint2.
		hintMask = hintMask ^ %01000000					'Set hintMask with previous hintMask AND to interrupt on hi2c write (slave mode).				
		hint2flag = 0									'Reset hint2flag.
	End If
	bit2 = pinB.0: bit1 = pinB.1							'Save rotary encoder pins status
	getBits = getBits & %000000110							'Isolate rotary encoder pins
	if getBits <> 0 then 									'If both pins are low, the direction is undetermined: discard
		dir = bit2 * 2									'Direction: if bit2=low then dir=0; if bit2=high then dir=2
		counter = counter - 1 + dir 						'Change counter variable accordingly
		do while getBits <> 0 							'Wait for the encoder to go to the next "detent" position
			getBits = pinsB & %000000110					'Rotary encoder pins AND 000000110.
		loop
	endif
	Gosub Interrupt_Init									'Restore interrupts.
Return

Interrupt_Init:
	HIntSetup hintMask
	SetIntFlags Or %0000110, %00000110					'Take the logical OR of the selected input pins, Interrupt on INT1 and 2.
Return
Not sure what the next lines do exactly or if correct comments:
1) hintMask = pinsB & %00000110 ^ %00000110 * 16 | %00000110
2) hintMask = hintMask ^ %00100000 'Set hintMask with previous hintMask AND to interrupt on background hardware serial receive.
3) hintMask = hintMask ^ %01000000 'Set hintMask with previous hintMask AND to interrupt on hi2c write (slave mode).
4) getBits = pinsB & %000000110 'Rotary encoder pins AND 000000110.
 

inglewoodpete

Senior Member
Not sure what the next lines do exactly or if correct comments:
1) hintMask = pinsB & %00000110 ^ %00000110 * 16 | %00000110
2) hintMask = hintMask ^ %00100000 'Set hintMask with previous hintMask AND to interrupt on background hardware serial receive.
3) hintMask = hintMask ^ %01000000 'Set hintMask with previous hintMask AND to interrupt on hi2c write (slave mode).
4) getBits = pinsB & %000000110 'Rotary encoder pins AND 000000110.
'^' is the bit-wise XOR function (refer to Variables - Mathematics - Manual 2 ~P25)
In short, "Zbit XOR 1" inverts Zbit.: either 1 ^ 1 = 0; 0 ^ 1 = 1. So, if a rising edge triggered a hardware interrupt on a pin, then prepare for the next interrupt to occur on that pin with a falling edge.

The hIntMask variable in your code is used with the hIntSetup (Ie hardware Interrupts) command - refer to the description in Manual 2. This has nothing to do with the SetIntFlags command, which can configure many internal (hardware) interrupts. As per the command description, hIntMask is divided into two 4-bit halves ('nibbles') each of which uses 3 bits to configure hInt0, 1 and 2. Multiplying or dividing hIntMask by 16 moves from the value of one nibble to the other and zero-filling the nibble just vacated. I hope that makes sense.
 

hippy

Technical Support
Staff member
Not sure what the next lines do exactly or if correct comments:
hintMask is used solely to set the HINTSETUP condition, which HINTx pins to use and which edge to interrupt upon. See the HINTSETUP commands to see what the meaning of the bit values within the hintMask variable will mean.


1) hintMask = pinsB & %00000110 ^ %00000110 * 16 | %00000110

This sets the initial HINT edge interrupt for the two signal lines. If a line is high the first interrupt needs to be falling edge, if the line is low the first interrupt needs to be rising edge.


2) hintMask = hintMask ^ %00100000 'Set hintMask with previous hintMask AND to interrupt on background hardware serial receive.

If the last HINT1 interrupt was on rising edge the next will be falling edge or vice versa. It doesn't affect background serial receive.

3) hintMask = hintMask ^ %01000000 'Set hintMask with previous hintMask AND to interrupt on hi2c write (slave mode).

As above but for HINT2; if the last HINT2 interrupt was on rising edge the next will be falling edge or vice versa. It doesn't affect hi2c.
 

Haroen

Member
'^' is the bit-wise XOR function (refer to Variables - Mathematics - Manual 2 ~P25)
In short, "Zbit XOR 1" inverts Zbit.: either 1 ^ 1 = 0; 0 ^ 1 = 1. So, if a rising edge triggered a hardware interrupt on a pin, then prepare for the next interrupt to occur on that pin with a falling edge.
In my added comments to the mashed pieces of code I had seen the manual mathematics and had translated it like...
hintMask = pinsB & %00000110 ^ %00000110 * 16 | %00000110 'Set hintMask with PinsB AND %00000110 XOR %00000110 *16 OR %00000110. Interrupt 1 and 2 Enabled, falling edges.
So it should now be something like... Interrupt 1 and 2 Enabled, rising edge trigger then inverted to falling edges and so on.

2) hintMask = hintMask ^ %00100000 'Set hintMask with previous hintMask AND to interrupt on background hardware serial receive.
If the last HINT1 interrupt was on rising edge the next will be falling edge or vice versa. It doesn't affect background serial receive.
3) hintMask = hintMask ^ %01000000 'Set hintMask with previous hintMask AND to interrupt on hi2c write (slave mode).
As above but for HINT2; if the last HINT2 interrupt was on rising edge the next will be falling edge or vice versa. It doesn't affect hi2c.
A lot becomes clear to me what I read wrong with the collection of the used PICAXE manual BASIC commands for my overview:

HINTSETUP mask
- mask is a variable/constant which defines which interrupt pins to activate.
Bit 7 - reserved
Bit 6 - Interrupt 2 Trigger (1 = rising edge, 0 = falling edge)
Bit 5 - Interrupt 1 Trigger (1 = rising edge, 0 = falling edge)
Bit 4 - Interrupt 0 Trigger (1 = rising edge, 0 = falling edge)
Bit 3 - reserved
Bit 2 - Interrupt 2 Enable
Bit 1 - Interrupt 1 Enable
Bit 0 - Interrupt 0 Enable (not available on 20X2)

I read the bits listing Up->Down is bits from left to right:
Where bits are like bit7 : bit6 : bit5 : bit4 : bit3 : bit2 : bit1 : bit0 CORRECT?


SETINTFLAGS OR flags,mask
- flags is a variable/constant (0-255) which specifies flags byte condition.
- mask is variable/constant (0-255) which specifies the mask
Name Special function Command
flag0 hint0flag X2 parts - interrupt on INT0 hintsetup
flag1 hint1flag X2 parts - interrupt on INT1 hintsetup
flag2 hint2flag X2 parts - interrupt on INT2 hintsetup
flag3 hintflag X2 parts - interrupt on any pin 0,1,2 hintsetup
flag4 compflag X2 parts - comparator flag compsetup
flag5 hserflag hserial background receive has occurred hsersetup
flag6 hi2cflag hi2c write has occurred (slave mode) hi2csetup
flag7 toflag timer overflow flag settimer

I read the flags listing Up->Down is flags from left to right:
Where flags are like flag0 : flag1: flag2: flag3: flag4: flag5: flag6: flag7 is INCORRECT I see?!

So flags are also read as increasing bits always from right to left?
In that case "background serial receive" and "hi2c" have nothing to do with my code. Code now makes more sense!
 

inglewoodpete

Senior Member
Yes, you're doing well :). There is lot to learn with microcontrollers and you never stop learning.

Number structure, whether binary, decimal or hexadecimal are all the same - the higher order values are to the left and smaller to the right.

So we read 5,261 (decimal) as thousands, hundreds, tens, units Ie (5 x 1,000) + (2 x 100) + (6 x 10) + (1 x 1)

...and %11010100 as bit7 : bit6 : bit5 : bit4 : bit3 : bit2 : bit1 : bit0
Ie %11010100 = (1 x 128) + (1 x 64) + (0 x 32) + (1 x 16) + (0 x 8) + (1 x 4) + (0 x 2) + (0 x 1)
 

Haroen

Member
Thanks, but I'm not there yet to master the code.
So, Bits, Flags and Masks are Always like %76543210.

The hIntMask variable in your code is used with the hIntSetup (Ie hardware Interrupts) command - refer to the description in Manual 2. This has nothing to do with the SetIntFlags command, which can configure many internal (hardware) interrupts.
This means SetIntFlags is not needed here? But then no values are shown in Terminal.

I adjusted the comments of the code:
Code:
#picaxe 20X2
SetFreq m8

#No_Table
#No_Data
#Terminal 9600

'           Used bits                                          xxxx   xxxx
symbol getBits   = b0 'b0 = bit7 : bit6 : bit5 : bit4 : bit3 : bit2 : bit1 : bit0
symbol dir = b1									'Direction indicator: 0 or 2.
symbol counter = b3								'Overall rotation counter.
Symbol hintMask = b4							'Hinterrupt Mask.
Symbol MEMcounter = b5							'Counter previous value.
Symbol MEMdir = b6								'Direction previous value.

'Set hintMask PinsB AND %00000110 XOR %00000110 *16 OR %00000110. 
hintMask = pinsB & %00000110 ^ %00000110 * 16 | %00000110	'If a line is high the first interrupt needs to be falling edge, if the line is low the first interrupt needs to be rising edge.

Gosub Interrupt_Init

Main:
	If counter<>MEMcounter OR dir<>MEMdir Then
		sertxd("Counter=",#b3,", Dir=",#b1,13,10)		 'Show: increments of Counter and the Direction as 0 or 2.
		MEMcounter=counter: MEMdir=dir
		pause 300
	EndIf
Goto Main


Interrupt:
	If hint1flag = 1 Then							'When interrupt on Hint1.
 		hintMask = hintMask ^ %00100000			'If the last HINT1 interrupt was on rising edge the next will be falling edge or vice versa.
		hint1flag = 0							'Reset hint1flag.
	End If
	If hint2flag = 1 Then							'When interrupt on Hint2.
		hintMask = hintMask ^ %01000000			'If the last HINT2 interrupt was on rising edge the next will be falling edge or vice versa.
		hint2flag = 0							'Reset hint2flag.
	End If
	bit2 = pinB.0: bit1 = pinB.1					'Save rotary encoder pins status
	getBits = getBits & %000000110					'Isolate rotary encoder pins
	if getBits <> 0 then 							'If both pins are low, the direction is undetermined: discard
		dir = bit2 * 2							'Direction: if bit2=low then dir=0; if bit2=high then dir=2
		counter = counter - 1 + dir 				'Change counter variable accordingly
		do while getBits <> 0 					'Wait for the encoder to go to the next "detent" position
			getBits = pinsB & %000000110			'Rotary encoder pins AND 000000110.
		loop
	endif
	sertxd("                   getBits=",#b0,", hintMask=",#b4,13,10)
	Gosub Interrupt_Init							'Restore interrupts.
Return

Interrupt_Init:
	HIntSetup hintMask
	SetIntFlags Or %0000110, %00000110			'Take the logical OR of the selected input pins, Interrupt on INT1 and 2.
Return

Code:
do while getBits <> 0 				'Wait for the encoder to go to the next "detent" position
	getBits = pinsB & %000000110		'Rotary encoder pins AND 000000110.
loop
Without the While loop the counts don't increase and decrease correct?
 

inglewoodpete

Senior Member
Thanks, but I'm not there yet to master the code.
So, Bits, Flags and Masks are Always like %76543210.

This means SetIntFlags is not needed here? But then no values are shown in Terminal.

Hardware interrupts are one of the more challenging areas of the PICAXE to understand.

The hIntSetup command configures the actual pin/s to record events occurring on the pins. So a fleeting high (or low, depending on the trigger bit setting in the hIntSetup command) will be latched (recorded) internally in the chip, but only if the hIntSetup mask has enabled that pin/bit. Your code will only jump into the Interrupt service routine if the appropriate flag bit/s have been configured to cause an interrupt using the SetIntFlags command. So the whole thing is a two-stage process.

I have to admit that I've never developed a program for a rotary encoder. However, I think I have a grasp of how they work.

I think your program needs further development.

When the PICAXE starts up, it has no idea what position the encoder shaft is in - it has a 1-in-4 chance of being at 0,0. So, within the initialisation code, just before entering the main loop, your program needs to read the current position of the rotor for reference when the first interrupt occurs. Also, you cannot ignore a condition of 0,0 as your code does at the moment.

Detecting the rotation of the two-bit encoder shaft involves 8 different conditions: the current value of the two inputs/bits after the interrupt and the previous condition of the two bits before the interrupt - that's why you need a record of the inputs' condition before entering the main loop.
 

hippy

Technical Support
Staff member
Unfortunately there is still a fundamental conflict in the processing; the interrupts being done on all four edges of the A and B signals, while the direction and counting determination is intended to work by polling and using a single signal change of state and looking at the other signal to determine the direction. It needs to be one or the other to work.
 

Haroen

Member
Yes that makes sense what I see in the terminal view.

This is with the While loop, counts correct per two like 0, 2, 4, 6, 8, 10?
Skips a whole lot of steps though, so interrupts are not set right like you mentioned.
Forum1.jpg

Without the While loop the counts don't increase correct?
Skips half as much steps as above picture.
Forum2.jpg

So I have to...
  • Finetune the two-stage process to detect the rotation of the 8 different conditions of the two-bit encoder shaft: hIntSetup and SetIntFlags!
  • That it doesn't ignore a condition of 0,0 that the code does at the moment.
  • Read the current position of the rotor for reference within the initialisation code before entering the main loop like...
    bit2 = pinB.0: bit1 = pinB.1 'Save rotary encoder pins status
    That should also be just before the "Gosub Interrupt_Init?
 

hippy

Technical Support
Staff member
The below appears to work when simulated.

There was a bug in the initialisation code earlier due to a misalignment of bits; should be B.0 => HINT1 and B.1 => HINT2 on the 20X2, corrected in the below.

The count reporting code outputs a character at a time to maximise interrupt response rate. The count output will jump more than one if encoder changes while count is being output -

Code:
#Picaxe 20X2
#Terminal 19200

Symbol hintMask = b0
Symbol hint1bit = bit5
Symbol hint2bit = bit6

Symbol dirflag  = b1
Symbol counter  = w1   ; b3:b2

Symbol tcount   = w2   ; b5:b4
Symbol t1       = b6
Symbol t2       = b7
Symbol t3       = b8
Symbol t4       = b9
Symbol t5       = b10
Symbol tindex   = b11
Symbol tchar    = b12

SetFreq M16

hintMask = pinsB & %00000011 ^ %00000011 * 32 | %00000110
Gosub Interrupt_Init

Do
  tcount = counter
  BinToAscii tcount, t1,t2,t3,t4,t5
  For tindex = 0 To 12
    ;                012345  6  7  8  9  10 11 12
    LookUp tindex,( "Count=",t1,t2,t3,t4,t5,CR,LF ),tchar
    SerTxd( tchar )
  Next
  Do : Loop Until counter <> tcount
Loop

Interrupt:
  If hint1flag = 1 Then
    hintMask = hintMask ^ %00100000
    hint1flag = 0
    dirflag = hint1bit ^ hint2bit
    counter = counter - 1 + dirflag + dirflag
  End If
  If hint2flag = 1 Then
    hintMask = hintMask ^ %01000000
    hint2flag = 0
    dirflag = hint1bit ^ hint2bit
    counter = counter + 1 - dirflag - dirflag
  End If

Interrupt_Init:
  HIntSetup hintMask
  SetIntFlags Or %0000110, %00000110
  Return
 

Haroen

Member
Thanks for the code.
The below appears to work when simulated.... The count output will jump more than one if encoder changes while count is being output.
In simulation I have seen it working also tested it to work and skipped when spinning to fast, but that's ok for my application.
Thanks also to @inglewoodpete who also helped me understanding the wonderful world of PICAXE:cool:
 

Flitch

New Member
Hi guys, sorry for intruding the thread but i have similar problem, got no idea how to write interupt masks for my circuit. I am using 3 buttons and rotary encoder. Priority is on encoder ofc, but it would be nice to have butons in interupt section too. This is how my encoder spins clockwise and counter clockwise. Any ideas? (using PA20X2)kombo rot.png

Edit: pins are normaly pulled high, rotating the encoderdrags them low
 

Haroen

Member
Hi Flitch,
The encoder type that I use, has also a push button for selection after scrolling.
On a 20X2 Hint1 & Hint2 only exist.
The 28X2 and 40X2 have also a Hint0 that for just one (push)button could be implemented in the Hint code for B-pins.
Lines: init hintMask, add interrupt hint0flag and edit SetIntFlags.

Your encoder skips some step where it does nothing, what type encoder do you have?

The encoder circuit that I use is pulled high too.
 

Flitch

New Member
hey, it's just some generic ebay encoder, that iddle time is there probably becouse i was turning it very slowly to get more precise timing on logic analyzer (if you are refering to pauses between turns on timing diagram), maybe OR-ing all 3 buttons to same interupt and somehow figuring out which one was used? Or some other trick?
Bigest problem is i have to drive nokia 5110 LCD all the time and read 2 I2C devices in main loop, so all input info should be somehow gathered thorugh interupts, just dont have any idea how to pack it all in there

http://www.ebay.com/itm/12mm-Rotary-Encoder-Switch-Flat-Top-Knob-NEW-/330769509111

https://www.dropbox.com/s/1xzahdfjy4bqnn5/20160310_150310.jpg?dl=0
https://www.dropbox.com/s/0o2g7gmc0v4pyad/20160310_143249.jpg?dl=0
 

hippy

Technical Support
Staff member
That is described as a two-bit gray code encoder but doesn't seem to be looking at the logic analyser traces with the two coinciding edges on both signals. It should still be usable but may require a state machine to determine direction being turned.

Merging the three button signals to give a single interrupt may be a good solution especially if the pot is not likely to be turned when the buttons are pushed.
 

Flitch

New Member
I have come to solution which removes 2 buttons from interupt section since they wont be used offten (main loop will check button state after redrawing lcd, which means for button to be used they have to be pressed for longer time, something like pressing them for 1 second would trigger the effect), which leaves 1 button and 2 RE-pins for main interupt settup. I just have no idea how to implement that state machine for detecting rotation direction on this type of rotary encoder, that is where i need help here. Thanks for being helpfull guys :)
 
Top