2

I'm a little bit loss because I'm learning how to use the interruptions on Arduino. I'm creating a program to read an RFID code that arrives if an RFID transmitter is close to the antenna (a little antenna board RFID). I have two operating modes: one where I associate my board to an RFID, so I will act only where my RFID code is identified ( I save the RFID code on the board). and another mode where I read all the RFID codes and I save it.

I have one configuration step where if the user push a button, I want to wink a led to indicate that we are waiting for the presentation of the RFID code to associate to the board. I try to make a code and I have some problems to get out of the condition where I'm waiting for the RFID code (serial1).

Here is my code:

#include <Wire.h>
//const int reception_rfid = 15;
const int btnAssociation = 2; // bouton pour indiquer que l'on souhaite faire l'appareillage des RFID (interruption)
const int SwitchModeConfig = 4;
const int LedAssociationEnCours = A0;
int compteurLedAsso;
const int BUFFER_SIZE_RFID = 13;
char buf[BUFFER_SIZE_RFID];
void setup() {
 Wire.begin(8); // join i2c bus with address #8
 Wire.onRequest(requestEvent); // register event
 Serial.begin(9600);
 Serial1.begin(9600);
 pinMode(SwitchModeConfig, INPUT_PULLUP); 
 pinMode(btnAssociation, INPUT_PULLUP);
 pinMode(LedAssociationEnCours, OUTPUT);
 ConfigurationAssociation(); 
 digitalWrite(LedAssociationEnCours, HIGH);
}
void loop() {
 delay(100);
}
void ConfigurationAssociation(){
 int stateSwitch = digitalRead(SwitchModeConfig);
 if(stateSwitch == LOW){ 
 attachInterrupt(digitalPinToInterrupt(btnAssociation), InterruptAssociation, RISING);
 Serial.println("Mode association");
 }else{ 
 detachInterrupt(digitalPinToInterrupt(btnAssociation)); 
 Serial.println("Mode aleatoire");
 }
}
void InterruptAssociation(){
 compteurLedAsso = 0;
 Serial.println("Ouverture de l'Association d'un code RFID");
 while (Serial1.available() == 0 && compteurLedAsso <= 600) 
 {
 // clignoter la led rouge sur A0
 digitalWrite(LedAssociationEnCours, LOW);
 delay(5000);
 digitalWrite(LedAssociationEnCours, HIGH);
 delay(5000);
 compteurLedAsso++;
 Serial.println(compteurLedAsso);
 }
 if(Serial1.available() > 0){
 int codeRFID = Serial1.readBytes(buf, BUFFER_SIZE_RFID);
 Serial.println("CodeRFID: ");
 for(int i = 0; i < codeRFID; i++)
 Serial.print(buf[i]);
 }
 //Serial.println(compteurLedAsso);
 //compteurLedAsso++;
 Serial.println("Fin de l'attente code RFID");
}
void requestEvent() {
}
  1. First problem: I know that when we are inside a interruption we can't count the time pass on a while, I would like to wink the led 2 minutes: waiting the presentation of the RFID code to save and once the 2 minutes pass away I get out of the interruption. How make to wait 2 minutes for RFID code on serial1 port inside an interruption? my reception RFID board works fine and TX is connected to RX1 (pin 19) of Arduino mega (I verify on another code where reading RFID works)

  2. Second problem: when my board is inside the interruption that comes from a push button (connected on pin 2) I stay always on the while loop even when my RFID transmitter is reading an RFID code, I can't get out of and I don't know why! maybe you can help to understand and find a solution please?

Rohit Gupta
6122 gold badges5 silver badges18 bronze badges
asked Mar 8, 2024 at 8:45

2 Answers 2

3

The thing is: Inside an ISR (Interrupt Service Routine) not only does time measuring with millis() not work, delay() also won't work (it uses the same interrupt mechanism as millis() in the background). Neither does anything else, that depends on their own ISR to execute - like receiving serial data. The hardware Serial interface can receive single bytes without depending on interrupts, but to get these bytes into the buffer (and to actually use them via Serial) you need to let other ISRs execute.

You should NEVER write an ISR, which executes for a long time. Keep it in the microseconds, maximum in the very low millisecond range. How long you can get here until you see negative effects depends on other factors. You really should only do minimal work in an ISR and the rest in the main code.

A common code structure for this looks as follows:

  • In your main code you don't use any delay() calls. Everything timed should be done with millis() (like in the BlinkWithoutDelay example that comes with the Arduino IDE), so that it won't block execution. Also no long loops inside of loop(). Make sure, that your loop() function can iterate freely and fast.
  • In global scope you define a flag variable. A simple one byte variable, marked as volatile.
  • In your ISR you are doing nothing but setting this variable. Depending on your situation you might wanna set it depending on an digital input via digitalRead(), which is still fast, so OK to do.
  • In your main code you use an if statement to check for this flag variable to be set. On most loop() iterations this won't be executed. Only, if the flag variable got set inside the ISR, it will get executed. This is the place where you write the code handling the event. At the end of the block you then only need to reset the flag variable, so that you are ready for the next interrupt to happen.

This would look somewhat like this:

int button_pin = 2;
volatile bool button_flag = false;
void setup(){
 pinMode(button_pin, INPUT_PULLUP);
 attachInterrupt(digitalPinToInterrupt(button_pin), button_ISR, RISING);
}
void loop(){
 if(button_flag){
 // Handle interrupt here
 button_flag = false; // Reset flag variable to be ready for next interrupt
 }
}
void button_ISR(){
 button_flag = true;
}

Note, that the button_flag variable is defined as volatile, so that the compiler doesn't cache its value is some register and instead always reads the actual current value of it (not optimizing it).

In your code you should probably use another coding concept named Finite State Machine (FSM). This is the easiest method to implement a code with different states/stages while still keeping it non-blocking. You can read up about FSMs on the web (or for example in my answer to this question).

The gist is, that you use a variable, that represents the state, that your code is currently in. In loop() you then only ever execute the code for the current state. Changing to a different state is done by setting the state variable. The state, that your current code needs, are probably CHECK_FOR_SINGLE_RFID_TAG and ASSOCIATE_SINGE_RFID_TAG (though you can name these like you want). A simple FSM with this can look like the following:

#define CHECK_FOR_SINGLE_RFID_TAG 0
#define ASSOCIATE_SINGLE_RFID_TAG 1
// single byte variable so that we don't need to bother about atomic reads/writes
uint8_t state = CHECK_FOR_SINGLE_RFID_TAG;
void loop(){
 switch(state){
 case CHECK_FOR_SINGLE_RFID_TAG:
 if(Serial1.available()){
 // read Serial1 here, check the RFID tag ID against the saved one
 // and execute code accordingly
 }
 break;
 
 case ASSOCIATE_SINGLE_RFID_TAG:
 if(Serial1.available()){
 // read Serial1 here and save the new RFID tag ID in a variable
 // then reset the state variable to automatically go back to check mode
 // after the new RFID tag is enrolled
 state = CHECK_FOR_SINGLE_RFID_TAG;
 }
 // Use the BlinkWithoutDelay idiom to blink your LED non-blocking
 // something like
 // if(millis() - led_timestamp > led_interval){
 // digitalWrite(led, !digitalRead(led));
 // }
 break;
 }
}

You see, how we define out possible states, save the current one in a variable and act according to it inside of loop()? This concept is really powerful, so it is totally worth it to learn.


Note: I've used a variable of type uint8_t (a single byte) to keep this interrupt safe (you can read up about this by searching for something like arduino interrupt atomic read or arduino interrupt safe variable) for the next part and the defines to give each value a readable name. You could use an enum for that, though while writing this I got on a tangent about the size of an enum, which seems 16 bits on AVR (like the Arduino Mega), unless you use a specific compiler flag, according to this blog article. This explanation is not that important for you right now, but I also wanted to explain, why this doesn't use enums.


Now we can combine this with our interrupt code from above. In this case we don't need a seperate flag variable anymore. We can directly set the state variable inside the ISR:

#define CHECK_FOR_SINGLE_RFID_TAG 0
#define ASSOCIATE_SINGLE_RFID_TAG 1
volatile uint8_t state = CHECK_FOR_SINGLE_RFID_TAG;
int button_pin = 2;
void setup(){
 pinMode(button_pin, INPUT_PULLUP);
 attachInterrupt(digitalPinToInterrupt(button_pin), button_ISR, RISING);
 Serial.begin(115200);
 Serial1.begin(115200);
}
void button_ISR(){
 state = ASSOCIATE_SINGLE_RFID_TAG;
}
void loop(){
 switch(state){
 case CHECK_FOR_SINGLE_RFID_TAG:
 if(Serial1.available()){
 // read Serial1 here, check the RFID tag ID against the saved one
 // and execute code accordingly
 }
 break;
 
 case ASSOCIATE_SINGLE_RFID_TAG:
 if(Serial1.available()){
 // read Serial1 here and save the new RFID tag ID in a variable
 // then reset the state variable to automatically go back to check mode
 // after the new RFID tag is enrolled
 state = CHECK_FOR_SINGLE_RFID_TAG;
 }
 // Use the BlinkWithoutDelay idiom to blink your LED non-blocking
 // something like
 // if(millis() - led_timestamp > led_interval){
 // digitalWrite(led, !digitalRead(led));
 // }
 break;
 }
}

You can see, the button_ISR() is very small, thus very fast. And our loop() also runs unblocked, so the next iteration of it will come very fast and it will then execute the state, that we set in the ISR.

Note: When reading from Serial, make sure, that you don't use functions, that block for a long time. For example Serial.readString() will take 1s to execute (since that is the default timeout), which would block the non-blocking code (no good).

answered Mar 8, 2024 at 9:55
3
  • 1
    Hi! whao!! Thank you so much for all these super explanations very complete and clear! :) i'm gonna read all and see the exemples of FSM because i don't know anything about it for be honest. I will back with news or maybe answers about my problem if is ok with u. Again, thank u for ur help :) Commented Mar 8, 2024 at 12:52
  • *i will back with news or maybe QUESTIONS instead :) Commented Mar 11, 2024 at 12:44
  • I come back with news, thanks to your help @chrisl and EdgarBonet i understand how to make FSM to improved my arduino code. it's working! thank u so much again to the both! :) Commented Mar 25, 2024 at 8:00
3

chrisl's answer brings many excellent points, and I highly recommend you study it in detail. There are just a few points I would personally add or amend:

  • an ISR should probably not last more than a few tens of microseconds, and many bad things could happen during a 1 ms ISR (missed clock tics, lost serial input...)
  • a loop() iteration should not take more than a few milliseconds
  • there is no point in using an interrupt for reading a button: the user will keep the button pressed for many milliseconds, and you will have many opportunities for catching it in loop(); interrupts bring complexity that is only worth it when you have to deal with sub-millisecond timings
  • do not worry about the size of an enum unless you need to care about sub-microsecond timings

Now, my take at this problem. The first step is to define the state machine. I may call the states NORMAL and ASSOCIATION. The transitions would be:

  • NORMALASSOCIATION when a button press is detected
  • ASSOCIATIONNORMAL when either an RFID code is read or after a timeout

My tentative implementation:

// Attempt to read an RFID without blocking.
// Returns the found RFID code as a pointer to a NUL-terminated buffer,
// or nullptr if no RFID is available right now.
char *read_rfid() {
 // Implementation left as an exercise to the reader.
}
void loop() {
 static enum {NORMAL, ASSOCIATION} state;
 static uint32_t started_association; // association start time
 char *rfid = read_rfid();
 switch (state) {
 case NORMAL:
 if (rfid) {
 Serial.print("Read RFID code: ");
 Serial.println(rfid);
 }
 if (digitalRead(btnAssociation) == LOW) {
 Serial.println("Starting association");
 mode = ASSOCIATION;
 started_association = millis();
 }
 break;
 case ASSOCIATION:
 if (rfid) {
 assocate_rfid(rfid);
 Serial.println("Association successful, code: ");
 Serial.println(rfid);
 mode = NORMAL;
 }
 if (millis() - started_association >= assocation_timeout) {
 Serial.println("Association timeout");
 mode = NORMAL;
 }
 break;
 }
}

There are a few things I left out here, most notably:

  • The LED blinking, which should be done in the style of the Blink Without Delay example
  • The function read_rfid() that reads the serial port in a non-blocking fashion. For this, you will need to have a rule for deciding when a message is complete. This could be a message terminator (if the reader does send one) or a timeout.

Finally, I would recommend you read a couple of blog posts by Majenko:

answered Mar 8, 2024 at 11:00
1
  • Thank you so much for your help :) with your responses i can understand so many things and improve my code :) i keep going on my projet! thanks for ur help! :) Commented Mar 25, 2024 at 8:02

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.