2

Currently i have a project that forced me to make a program that receives several data from Serial Input using Interrupt in Arduino. I use Arduino as the receiver and ESP32 as the sender through Hardware Serial. The program i have written for Arduino is as posted at Serial Input Basics with a few changes as written below :

const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing

// variables to hold the parsed data
char messageFromPC[numChars] = {0};
unsigned long sendDataPrevMillis = 0;
int controlCondition = 0;
int droneCondition = 0;
int gasValue = 0;
int pitchValue = 0;
int rollValue = 0;
int yawValue = 0;

boolean newData = false;

void setup() {
  attachInterrupt(digitalPinToInterrupt(1), handleInterrupt, RISING);
  Serial.begin(57600);
}

void loop() {
  
  Serial.print("Control Condition :");
  Serial.print(controlCondition);
  Serial.print(" Drone Condition :");
  Serial.print(droneCondition);
  Serial.print(" Roll:");
  if(rollValue < 1500)Serial.print("<<<");
  else if(rollValue > 1500)Serial.print(">>>");
  else Serial.print("-+-");
  Serial.print(rollValue);
  Serial.print("  Pitch:");
  if(pitchValue > 1500)Serial.print("^^^");
  else if(pitchValue < 1500)Serial.print("vvv");
  else Serial.print("-+-");
  Serial.print(pitchValue);
  
  Serial.print("  Gas:");
  if(gasValue < 1500)Serial.print("vvv");
  else if(gasValue > 1500)Serial.print("^^^");
  else Serial.print("-+-");
  Serial.print(gasValue);
  
  Serial.print("  Yaw:");
  if(yawValue < 1500)Serial.print("<<<");
  else if(yawValue > 1500)Serial.print(">>>");
  else Serial.print("-+-");
  Serial.println(yawValue);
}

void handleInterrupt(){
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '<';
    char endMarker = '>';
    char rc;

    while (Serial.available() && newData == false) {
        rc = Serial.read();

        if (recvInProgress == true) {
            if (rc != endMarker) {
                receivedChars[ndx] = rc;
                ndx++;
                if (ndx >= numChars) {
                    ndx = numChars - 1;
                }
            }
            else {
                receivedChars[ndx] = '\0'; // terminate the string
                recvInProgress = false;
                ndx = 0;
                newData = true;
            }
        }

        else if (rc == startMarker) {
            recvInProgress = true;
        }
    }
    if (newData == true) {

    strcpy(tempChars, receivedChars);
    char * strtokIndx; // this is used by strtok() as an index

    strtokIndx = strtok(tempChars, ",");
    controlCondition = atoi(strtokIndx);     

    strtokIndx = strtok(NULL, ",");
    droneCondition = atoi(strtokIndx);
    
    strtokIndx = strtok(NULL, ",");
    gasValue = atoi(strtokIndx);

    strtokIndx = strtok(NULL, ",");
    pitchValue = atoi(strtokIndx);     

    strtokIndx = strtok(NULL, ",");
    rollValue = atoi(strtokIndx);     

    strtokIndx = strtok(NULL, ",");
    yawValue = atoi(strtokIndx);     

    newData = false;
    }
}

The data will be sent from ESP32 to change the values of controlCondition, droneCondition, gasValue, pitchValue, rollValue, and yawValue.

I have tried it and the Interrupt cannot be triggered. The Interrupt should be triggered whenever the controlCondition, droneCondition, gasValue, pitchValue, rollValue, or yawValue change its value. Any idea what should I do? or is there something wrong with my code?

Note that the code that i showed in the void loop() is just an example code. The real code is more complex with so many calculations involved. The variables that I mentioned are the important variables for the calculations.

6
  • 2
    attachInterrupt(digitalPinToInterrupt(1), handleInterrupt, RISING) : which Arduino because pin 1 on say a Uno is not set up for edge triggered external interrupts. The usual method for handling serial is in the loop() using Serial.available() to check if there is any data to process. Why are you attempting to do this in an interrupt service routine? Are you planning blocking code in the loop() or what?
    – 6v6gt
    Commented Aug 14, 2023 at 2:50
  • 1
    stackoverflow.com/questions/53104440/…
    – jsotola
    Commented Aug 14, 2023 at 2:57
  • 1
    while (Serial.available()) ... if the arduino speed was scaled to human speed, then a serial character would arrive once a week
    – jsotola
    Commented Aug 14, 2023 at 3:01
  • @jsotola I'm not sure what you are getting at, to be honest.
    – Nick Gammon
    Commented Aug 14, 2023 at 7:24
  • @NickGammon I don't either ... I was probably thinking that the ISR is waiting for multiple bytes
    – jsotola
    Commented Aug 14, 2023 at 13:48

2 Answers 2

2

Why do you want to use an interrupt anyway? The serial hardware already generates an interrupt, and inside that ISR the incoming byte is placed into a buffer.

I presume you have a main loop where you are doing other stuff, and you want to react when some interesting serial data arrives.

I have an example of processing serial data without blocking on my page about serial processing which I reproduce below:

/*
Example of processing incoming serial data without blocking.

Author:   Nick Gammon
Date:     13 November 2011. 
Modified: 31 August 2013.

Released for public use.
*/

// how much serial data we expect before a newline
const unsigned int MAX_INPUT = 50;

void setup ()
  {
  Serial.begin (115200);
  } // end of setup

// here to process incoming serial data after a terminator received
void process_data (const char * data)
  {
  // for now just display it
  // (but you could compare it to some value, convert to an integer, etc.)
  Serial.println (data);
  }  // end of process_data
  
void processIncomingByte (const byte inByte)
  {
  static char input_line [MAX_INPUT];
  static unsigned int input_pos = 0;

  switch (inByte)
    {

    case '\n':   // end of text
      input_line [input_pos] = 0;  // terminating null byte
      
      // terminator reached! process input_line here ...
      process_data (input_line);
      
      // reset buffer for next time
      input_pos = 0;  
      break;

    case '\r':   // discard carriage return
      break;

    default:
      // keep adding if not full ... allow for terminating null byte
      if (input_pos < (MAX_INPUT - 1))
        input_line [input_pos++] = inByte;
      break;

    }  // end of switch
   
  } // end of processIncomingByte  

void loop()
  {
  // if serial data available, process it
  while (Serial.available () > 0)
    processIncomingByte (Serial.read ());
    
  // do other stuff here like testing digital input (button presses) ...

  }  // end of loop

It looks to me like you are looking for an "end marker" which is when you want to react to what you have received earlier. Like in my example above, you could just pick up individual bytes in the main loop, and when the end marker arrives the whole line can be processed. I used a newline for and end marker, but you could change that to '>'.


The problems with an interrupt like you are doing are:

  • The interrupt will fire on the first bit (out of 10) and so there won't be any serial data yet, and your program will uselessly look for serial data which won't be there.

  • You are calling Serial.read() which won't return anything because while you are in your ISR the main serial ISR won't be able to fire.

  • Your ISR is doing too much.

  • Serial line is normally HIGH, and transitions to LOW (the start bit) when the byte starts, so you should be looking for FALLING and not RISING.


I strongly suggest you rework to just check for serial available in the main loop, as I have done, and process it when the end delimiter arrives.

3
  • Actually my project is building a Drone that can be controlled with an Android App, so the variables that I mentioned are very important for calculating the PID for the Drone. And the calculations are very large, so it can take a long time for the loop to read the values of the variables. I have tried the way that you showed before and it did not work. You can see my code here. Any idea?
    – Zero
    Commented Aug 15, 2023 at 10:53
  • Well, saying it "did not work" is not very helpful. Your code has many many serial prints in it. What are you seeing on the terminal? You seem to be calling showParsedData in the main loop. Wouldn't it be better to call it directly after parsing the data?
    – Nick Gammon
    Commented Aug 16, 2023 at 6:35
  • You left in the test for \n as the end of the data, but I thought you had ">" as the data delimiter. Is there also a newline?
    – Nick Gammon
    Commented Aug 16, 2023 at 6:36
0

Don't do anything in your loop() function except calling other functions, each of which decides - for one part of your process - whether or not it is time to do that part. If so, do that part; if not, return immediately to the loop function.

Read my answer to a similar question that talks more about how to do this. This technique is called non-blocking programming. Your code should poll rapidly for whatever it might need to respond to, but should not wait for any of those things to occur. If you do that, you will likely not need the kind of response that would require interrupts.

Also, the print statements in your loop() will slow down (block) execution and may keep your PID from responding adequately. If you need them for debugging your calculations, you can wrap them in #if DEBUG ... #endif statements so one #define DEBUG can easily include them them when you need to (knowing that your device control will be affected) and remove them for operational tests. If you must keep some print statements live, make only as few as necessary live at one time, make sure to use the highest baud-rate possible, and keep them as brief as possible.

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.