Notifications
Clear all

ESP32 GPIO Interrupts - need some help.

17 Posts
4 Users
3 Likes
1,455 Views
(@rebeljd)
Member
Joined: 1 year ago
Posts: 16
Topic starter  

I want to interface a quadrature encoder to an ESP32 (development board).  My intent is to count pulses in both directions, that is increment and decrement a counter based on the direction of rotation of the encoder.  Quadrature encoders have two channels that generate symmetrical square waves that are 90 degrees out of phase from each other.  Thus, if you count the RISING and FALLING edge of both channels you get four counts per pulse of the encoder.  For example, if you have a 100 Pulses Per Rev encoder you can achieve a resolution of 400 Pulses Per Rev.  And, if during a RISING edge or FALLING edge you monitor the status of the other channel you can determine the direction of rotation.

My approach is to use GPIO Interrupts on both channels of the encoder.  I attached an interrupt to each channel for a both a RISING edge and a Falling Edge, for a total of four ISR’s.  Each ISR will look at the other channel to determine if it is a 1 or a O and then increment or decrement a counter value accordingly. This, in theory should increment or decrement the counter by 400 PPR in either direction.  (I’m testing with a 100 PPR Encoder).  But it doesn’t work.  However, if I comment out 3 of the 4 ISR’s the remaining ISR works as it should and counts up or down 100 counts per rev.  But when I run all 4 ISR’s it only counts to 200. 

I may be dealing interrupt priority issues, but I don’t have enough experience with the ESP32 to fully understand if this is what I’m dealing with.   I also realize there may be other ways of doing this, for example using the PCNT, but I get a little overwhelmed at the coding involved.  I'm a novice hobbyist, not a seasoned C++ programmer.  I’ve done quite a few Arduino projects over the years, but I’m new to the ESP32.  I should also point out I’m using the Arduino IDE.  I’ve tried this on both 1.8 and 2.0, with the same result.

Here is the code I’m using to test this.  I have looked at the signals with an Oscilloscope and they are nice sharp square waves, so I don’t think it’s a hardware issue.  Also, the encoder rotation will be relatively slow, so I don’t think the pulses will be coming in too fast.

Any help would be greatly appreciated.  

#define A_pin 34
#define B_pin 35
volatile long NSV = 0;  // Net Sum Value
int Apin = 0;
int Bpin = 0;


void IRAM_ATTR Arise (){
Bpin = digitalRead(B_pin);
if (Bpin==0) {NSV++;}
if (Bpin==1) {NSV--;}
}
  void IRAM_ATTR Brise (){  
Apin = digitalRead(A_pin);
if (Apin==1) {NSV++;}
if (Apin==0) {NSV--;}
}
void IRAM_ATTR Afall (){
Bpin = digitalRead(B_pin);
if (Bpin==1) {NSV++;}
if (Bpin==0) {NSV--;}
}
void IRAM_ATTR Bfall (){  
Apin = digitalRead(A_pin);
if (Apin==0) {NSV++;}
if (Apin==1) {NSV--;}
}
void setup(){
Serial.begin(115200);
pinMode (A_pin, INPUT);
pinMode (B_pin, INPUT);
attachInterrupt(A_pin,Arise,RISING);
//attachInterrupt(B_pin,Brise,RISING);  
//attachInterrupt(A_pin,Afall,FALLING);
//attachInterrupt(B_pin,Bfall,FALLING);
}
void loop(){
Serial.println(NSV);
}

 

 


   
Quote
Ron
 Ron
(@zander)
Father of a miniature Wookie
Joined: 3 years ago
Posts: 7030
 

@rebeljd How experienced are you at Arduino code, especially interrupts? I thought from your intro you were a noob, but then I see code that indicates some experience.

However, off the cuff (I haven't used interrupts in a while now) I think I see a few problems. As a general rule every variable used inside an interrupt should be declared volatile. Glad to see you know about the IRAM_ATTR requirement. Inside the interrupt routine do as little as possible. The holy grail is to simply set a single boolean variable then in the main loop (BTW, why is yours empty?) test that variable (multiple in your case) and do the time-consuming operations there.

In your attachInterrupt statement, you neglected to use the digitalPinToInterrupt(pin) function.

 

First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, and 360, fairly knowledge in PC plus numerous MPU's and MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
Sure you can learn to be a programmer, it will take the same amount of time for me to learn to be a Doctor.


   
ReplyQuote
Ron
 Ron
(@zander)
Father of a miniature Wookie
Joined: 3 years ago
Posts: 7030
 

@rebeljd One minor point, it is irrevelant which IDE version you use, (1.8.x or 2.0.x) as they are simply shells that end up running the standard GCC command line and use exactly the same libraries.

First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, and 360, fairly knowledge in PC plus numerous MPU's and MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
Sure you can learn to be a programmer, it will take the same amount of time for me to learn to be a Doctor.


   
ReplyQuote
Will
 Will
(@will)
Member
Joined: 3 years ago
Posts: 2535
 

@rebeljd

Perhaps you could avoid getting bogged down on the encoder by finding a suitable library; then you can bypass the bottleneck and advance to the next steps in your project.

Once it's complete and any potential time constraints are past you'll have the time to examine how the library works and thus learn how to control the encoder yourself next time.

Anything seems possible when you don't know what you're talking about.


   
ReplyQuote
Ron
 Ron
(@zander)
Father of a miniature Wookie
Joined: 3 years ago
Posts: 7030
 

@rebeljd See the official arduino docs for what you are trying to do at HERE  and the details at HERE

EDIT These are documented as any architecture But I am not so sure it will work with esp32.

There are 2 esp32 specific libraries available in the library manager. See pic for details.

Screenshot 2023 02 19 at 15.06.27

First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, and 360, fairly knowledge in PC plus numerous MPU's and MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
Sure you can learn to be a programmer, it will take the same amount of time for me to learn to be a Doctor.


   
ReplyQuote
(@rebeljd)
Member
Joined: 1 year ago
Posts: 16
Topic starter  

Ron and Will, thanks for the quick replies.  I've done quite a few Arduino projects with interrupts, mostly on Nanos.  I decided to go with the ESP32 as it has more external interrupts available than an Arduino and its faster.  And I wanted to learn something new.  This code is just a portion of the larger project which is why there is not much in the loop.  I'm only using one variable (NSV)in this sample code and it is declared as volatile.  Based on what I'm trying to do I don't think I can reduce the code in the ISR's, but that may indeed be the problem.  I'll have to think about how to reduce it.  

I'm not familiar with digitalPinToInterrupt(pin) but I'll check it out.  Based on what I've learned searching the web to this point the attach interrupt I'm using should work.  And it does seem to work.  As I mentioned each ISR works fine standalone.  Its when I use all them together is when I'm having problems.  

I'll definitely check out the libraries, I'm always willing to benefit from someone else doing the heavy lifting.  

I may be going down a rabbit hole here, but you know how that is....

Jim

 


   
ReplyQuote
(@davee)
Member
Joined: 3 years ago
Posts: 1698
 

Hi @rebeljd,

   I haven't programmed interrupts on an ESP32, so all I am offering is the results of a brief Google, and some unsubstantiated speculation ... treat this reply with as much caution as you feel it deserves!

------------

Reviewing the discussion so far ... (NB sorry, this only covers the discussion up to the time I started writing this ... it does not include Ron's latest input above)

The fact that you report a count of exactly 200, rather than 400 you expected, suggests that the machine is behaving in a systematic way .. just not the same way as you expect. Whilst it doesn't rule out the possibility of a being overrun, it does seem to reduce the chance of that being a problem, supporting your analysis.

I had a very quick look for "digitalPinToInterrupt(pin) " that Ron @zander mentions. Clearly most, but not all 'Arduino' s require it, ( as per https://www.arduino.cc/reference/en/language/functions/external-interrupts/attachinterrupt/) but I didn't see it in the ESP32 documentation. Thus, it may be be just my search was too brief or it may not be relevant with ESP32 hardware.

It should be understood that interrupt hardware design, generally has a common ancestral design theme, but the fine detail is 'handcrafted' for each different processor family, so that the precise details of how it will behave, especially when more than interrupt source is enabled, can only be established by knowing the exact logical structure of that part of the processor. I didn't find such a description for the ESP32.

Also, whilst 'volatile' is often a requirement as Ron suggests, the symmetry and minimal size of your program suggests it is unlikely to be the problem, given that is able to count to 200.

And whilst I agree with Ron's general rule of minimising code in an interrupt function, your interrupt routines only  have two 'If's and and one increment per call, which should only generate a few short assembly language instructions, which I would suggest is much shorter than most interrupt routines will be.

-----

Speculation

In my very brief look I was looking for a 'description' of the interrupt hardware, but unfortunately,  I found more waffle than facts. Hence I am speculating that the hardware cannot support what you are asking it to do!

I am suggesting that each GPIO pin, that can generate interrupts, which I think I have read is all of the GPIOs for an ESP32,  has a certain interrupt detection logic path, probably including a comparator and an  inverter, each of which will be enabled or disabled to match the selected of rising/falling.

This path will be preset by the attachInterrupt function to detect to particular requirement of rising or falling.

However, your program has two different attachInterrupt function calls to the same pin. Hence, I am speculating you are asking the same simple logic gate to be doing two things at once.

This is like asking a one bit inverter, with one input and one output to behave like an inverter and a non-inverting buffer at the same time!

So I am guessing that only 2 of your 4 attachInterrupt statements are actually 'active' ... the other two being deactivated by the two active ones.

----------

Workaround

To round off the speculation, I'll suggest a workaround to try ... no promises it will work of course!

Change the wiring to use a total of 4 GPIO pins for sampling A and B, two GPIO pins each. Thus, A (and similarly B) will have two pins, one looking for the rise, the other looking for the fall. Obviously, you will also need to slightly modify your programme to achieve this.

----------

Best wishes, Dave


   
ReplyQuote
Ron
 Ron
(@zander)
Father of a miniature Wookie
Joined: 3 years ago
Posts: 7030
 

@rebeljd You forgot the digitalRead.

Next reply remember to use the Reply link and the handle name @zander for notifying others of your post.

This kind of interrupt is the 3rd level, 2nd is using the OS interrupts, first is using the hardware interrupts. With convenience comes overhead. The libraries will do the very low-level hardware bit banging allowing you to code the logic.

First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, and 360, fairly knowledge in PC plus numerous MPU's and MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
Sure you can learn to be a programmer, it will take the same amount of time for me to learn to be a Doctor.


   
ReplyQuote
Ron
 Ron
(@zander)
Father of a miniature Wookie
Joined: 3 years ago
Posts: 7030
 

@davee #$%^& I MISSED the 2 ISR's assigned to one pin. I am very sure that can't work.

BTW Dave, you omitted the digitalRead statement in the ISR. That might be the killer.

First computer 1959. Retired from my own computer company 2004.
Hardware - Expert in 1401, and 360, fairly knowledge in PC plus numerous MPU's and MCU's
Major Languages - Machine language, 360 Macro Assembler, Intel Assembler, PL/I and PL1, Pascal, Basic, C plus numerous job control and scripting languages.
Sure you can learn to be a programmer, it will take the same amount of time for me to learn to be a Doctor.


   
ReplyQuote
Will
 Will
(@will)
Member
Joined: 3 years ago
Posts: 2535
 

Posted by: @rebeljd

Based on what I'm trying to do I don't think I can reduce the code in the ISR's, but that may indeed be the problem.  I'll have to think about how to reduce it.  

You can shorten the code by replacing 

void IRAM_ATTR Arise() {
  Bpin = digitalRead(B_pin);
  if (Bpin == 0) { NSV++; }
  if (Bpin == 1) { NSV--; }
}

by 

void IRAM_ATTR Arise() {
  NSV = NSV + digitalRead(B_pin) ? -1 : 1;
}

and modify the other routines in a similar manner.

Posted by: @rebeljd

I may be going down a rabbit hole here, but you know how that is....

Say Hi to Alice 🙂

 

Anything seems possible when you don't know what you're talking about.


   
ReplyQuote
(@davee)
Member
Joined: 3 years ago
Posts: 1698
 

Hi @rebeljd,

  A post message afterthought ... 

  If my hypothesis as to why your code is only counting 200 is correct, then an alternative workaround, which maybe what the library functions that Ron @zander found do, (I haven't looked,) could be to actively change what each GPIO pin is looking for ... that is if it it just found a rise on a GPIO pin, then the only thing it would need to look for is a fall, and vice versa. Thus counting would now include continually calling an attachInterrupt function after each count.

This would increase the processor workload, and I imagine it would be necessary to put the continual attachInterrupt function calls in the main code, not the interrupt routine, so overall, it would reduce the maximum count rate, but allow you to only use two GPIO pins once again.

The choice might be yours!

Best wishes, Dave


   
ReplyQuote
(@davee)
Member
Joined: 3 years ago
Posts: 1698
 

Hi Ron @zander,

  You are of course correct ... I didn't spot the lack of digital pin reading.

  As I said .. my offering was just speculation of a concern for @rebeljd  to look into ... not a complete code audit!

  Hope you feeling better Ron.

Best wishes, Dave


   
ReplyQuote
(@davee)
Member
Joined: 3 years ago
Posts: 1698
 

Hi @will,

  Looking at your ternary function approach ... I agree, it looks a lot tidier, but I wonder how much difference will it make to the processor in terms of clock cycles. Not many less, I would guess, but happy to be informed if my 'gut feeling' is incorrect. It is a long time since I looked at compiler output that closely!

Best wishes, my friend. Dave


   
ReplyQuote
Will
 Will
(@will)
Member
Joined: 3 years ago
Posts: 2535
 

@davee

I don't have any ESP hardware defined, so I can't do any testing. However, it would be interesting to switch all of the code over to this form and see if the compiler eliminates the two int variables Apin and Bpin. if the compiler has already optimized them out of the sketch, then the size of the code and variables will be unchanged. If either (or even both) are reduced after the conversion, then the change was effective.

Frankly, I don't think it makes much difference. More sketch appears on screen at a time and we can all be grateful for the resulting reduction in recycled photons used.

Anything seems possible when you don't know what you're talking about.


   
DaveE reacted
ReplyQuote
(@davee)
Member
Joined: 3 years ago
Posts: 1698
 

Hi Ron @zander,

 re: BTW Dave, you omitted the digitalRead statement in the ISR. That might be the killer

I just reviewed the evidence .... if all 4 of the interrupt calls per full cycle of the quadature waveforms, had occurred, with the digitalRead statements missing, so that A_pin and B_pin were always eqial to zero, then there would have been two increments and two decrements, so that the net count would be zero.

However, @rebeljd reported a count of 200, not zero, suggesting just two increments and no decrements, per quadrature cycle.

So, this looks like the classic whodunit case, where the suspect shoots the victim multiple times in the head, but the suspect is not guilty of murder .....  because the victim was already dead!

Of course, when all four interrupt calls per cycle are executed, then the lack of digitalRead statements would indeed be fatal. 😀 

Best wishes, Dave


   
ReplyQuote
Page 1 / 2