1

i am newbie to this so i hope i can explain my problem clearly;

Arduino MEGA connected to PC via USB port.

I am using an encoder to trigger function to send data to pc, data is a simple string "A01" or "A02" when i turn encoder to opposite direction, when arduino sends that string to serial port PC answers with another string from serial and its something like "MCPHDGxxx" and arduino does if serial available gets the string and filter it to get and show that number on my 7-segment display. (xxx is a number between 000 and 359, and that number changes on PC screen when arduino send "A01" or "A02" string to PC)

When i don't send display data from PC to arduino, encoder function works as intended, changes numbers up and down rapidly on PC screen but when i try to send that "number" data to Arduino from serial port number on the PC screen changes so slowly like 1 point up/down per 2 sec even if i turn encoder continuously. Seems like receiving data from PC blocking my send data function until it's done.

Is it possible to make it work asynchronously?

Here is my sketch:

#include "DigitLedDisplay.h"
 #define outputA 24
 #define outputB 25

 int aState;
 int aLastState;  
 unsigned long lastButtonPress = 0;
 long finalMCPHDGLong;
 String ReceivedSerialString;

 DigitLedDisplay ld = DigitLedDisplay(21, 22, 23);

 void setup() { 
   pinMode (outputA,INPUT);
   pinMode (outputB,INPUT);
   
   Serial.begin (115200);
   // Reads the initial state of the outputA
   aLastState = digitalRead(outputA);

     /* Set the brightness min:1, max:15 */
  ld.setBright(10);

  /* Set the digit count */
  ld.setDigitLimit(8);

    
 } 

 void loop() { 
   aState = digitalRead(outputA); // Reads the "current" state of the outputA
   // If the previous and the current state of the outputA are different, that means a Pulse has occured
   if (aState != aLastState){     
     // If the outputB state is different to the outputA state, that means the encoder is rotating clockwise
     if (digitalRead(outputB) != aState) {
       if (millis() - lastButtonPress > 25) {
         Serial.println("A01");
        }

        // Remember last button press event
        lastButtonPress = millis(); 
       
     } else {
       if (millis() - lastButtonPress > 25) {
         Serial.println("A02");
        }

        // Remember last button press event
          lastButtonPress = millis();
       
     }
     
   } 
   aLastState = aState; 

    while (Serial.available() > 0){
      ReceivedSerialString = Serial.readString();
      if (ReceivedSerialString.substring(0,6) == "MCPHDG"){
        String value = ReceivedSerialString.substring(6,9);
          long val = value.toInt();
          finalMCPHDGLong = val;
          if((finalMCPHDGLong <= 99)){
              ld.printDigit(0,7);
            }if((finalMCPHDGLong <= 9)){
              ld.printDigit(0,6);
            }
          ld.printDigit(val, 5);
      }
    }
 }

2 Answers 2

2

Seems like receiving data from PC blocking my send data function until it's done.

You may be saying the same thing, but I'd put it that it's waiting for more data to arrive that isn't going to.

Is it possible to make it work asynchronously?

Depending on how you're defining "asynchronously" that may be unnecessary.

while (Serial.available() > 0){
  if (ReceivedSerialString.substring(0,6) == "MCPHDG"){
    String value = ReceivedSerialString.substring(6,9);

There's a lot that could be said about using String and conversion to int and choosing a simple message format and so forth, but I'll elide most of that. Finding a good approach to what you're doing would require understanding a lot more about what you're doing and why. But, taking your code and question as written, the main thing is that readString() here is terminating based on the value of Serial.setTimeout(), which by default is 1000 or 1 second. So you are spending a lot of time doing nothing following receipt of your command from the PC. So during that 1 second you are not tending to the "encoder".

You could play with setTimeout, but it would be better to avoid a timeout altogether by not attempting to read when there's no more command to be read. If this protocol you're using is line-oriented then you have a terminator you can use in the form of '\r' or '\n'. This can be done with Serial.readStringUntil(whatever_your_terminator_is); If you have both '\r' and '\n' you will need to read other part of the termination sequence and then throw it away. Either it will come in as part of the read string or it will not and you will need to do a second read to discard it. There are more sophisticated ways to go about this, but that maybe sufficient for what you're doing.

When it comes to not blocking (or significant blocking) of serial reads, you have to assure that the data is there to be read. Either because you know it's sent (and are willing to trust that it was received okay). Or you use Serial.setTimeout with a 0 or sufficiently small number, .read() indicates no data with a negative return value, or by checking .available(), and it all of these cases deciding what to do when you have not received everything, or everything... yet. Doing it well, manually, is a bit of a pain.

For writing/printing to serial there's a counterpart .availableForWrite() which you can use to determine ahead of time whether or not you're going to be stuck in waiting (in .print()/.println()/.write()) for the serial data to go out, in turn not tending to the encoder. Basically you can choose where and how to throttle your writing to serial so as not overly interfere checking the encoder rather than causing throttle to happen within Serial.write. With the way your code is currently written, with events coming in at the rate they apparently are (according to your debouncing code) this is probably not currently a problem for you. But it's worth keeping in mind if you begin handling events at a higher rate. Placing a character in the outbound serial buffer is significantly faster than waiting for space for a character to become available in the serial buffer.

1
  • Serial.setTimeout(25) solved the problem. Also other advices helped a lot, thanks.
    – Nocturnale
    Commented Feb 1, 2023 at 11:00
-1

Is not easy, but you can implement two modification.

The first one is handling encoder via interrupt, the second one, is change the while to an if, with the while you force the code to loop inside the while serial aviable loosing eventually encoders state change, to avoid this, you shuold take continusly the strings coming from PC and create a command queue and proces it cycle by cicle, unfortunatly arduino mega is single processor, so it can't handle different process at the same time.

You should olso to consider, and run a benckmark to test how much is the refresh rate of your display

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.