3
\$\begingroup\$

I wrote some code for the ATmega328P, (Packaged in a module that clones the Arduino Nano).

Normally when I send UART data from my computer to these micros, it is received just fine. However I have started a new project and I am getting intermittent characters missing from the beginning of a message.

The code I wrote thus far reads the incoming data and prints it back to the computer. I am trying to send "<A2>". Something like 80% of the messages are received and printed back fine. The rest are missing characters. Oddly, they are at the beginning of the message, giving me "A2>" or "2>"

I am using a baud of 115200, though 9600 and even 600 appear to give the same ratio of bad messages. I tried swapping the micro and there was no change. I'm only sending messages once every 5 seconds or so.

The micro is being powered from the same USB cable going to the laptop that the data is sent over.

Below are oscilloscope/logic analyzer captures of two signals, one that worked fine and another that was missing characters. The probe is placed right at the "RX" pin and grounded right at "GND".

Signal received as "<A2>" (correct): Scope capture (good)

Signal received as only "2>" (incorrect): Scope capture (Not good?)

Those two signals look awfully close.

Update: As user duskwuff mentioned, the UART signals are actually not approaching 0V for LOW.

I tried using nothing but the bare module itself (no protoboard, nothing), but I'm getting the same results.

To further diagnose, I tried manually writing to the RX pin. That makes the signal swing properly down to 0V.

Test code:

void setup()
{
    delay(500);
    pinMode(0, OUTPUT);

    while (true)
    {
        digitalWrite(0, HIGH);
        delay(2000);
        digitalWrite(0, LOW); //This reaches 0V
        delay(2000);
    }
}

Watching the RX line during code flash, the LOW is also about 1.5V.

Now I'm extra confused. How can I get the same result with multiple bare-modules, even brand-name ones? That makes me lean toward blaming software.

Arduino Nano Clone
Image from wikimedia.org

The purpose of my this code is to create a sort of protocol for received messages that processes them one at a time, even if multiples are sent all at once ("<A1><A2><A3>...") or if they are sent in only partial pieces ("<A" and then "2>").

const int pinBuzzer = A5;      // the number of pin for the buzzer

unsigned long T = 0; // "T" value


//max input size expected for a single incoming msg
#define INPUT_SIZE 32
char serialCommandstr[INPUT_SIZE + 1];
char serialCommand_singleChar; //single char for itterating through incoming WX
byte serialCommandindex = 0; //index of RX buffer for itterating
bool serialCommand_msgPending = false; // if msg exists in buffer without termination ('>');
bool serialCommand_ready = false; // if terminated msg ready (a '>' is received)


void setup()
{
    delay(500);
    Serial.begin(115200);

} // END Setup()


void loop()
{
    Serial.println("Waiting for input in the form of \"<XY>\"");
    Serial.println("X=[A/B], Y=[1~8]");


    serialCommand_ready = false;
    while (serialCommand_ready == false)
    {
        serialPreParse();
    }
    if (serialCommandstr[1] == 'A')
    {
        T = (serialCommandstr[2] - 1) - char('0');
        Serial.print("A, ");
    }

    Serial.print("T=");
    Serial.print(T);
    Serial.print("us...");

    //Beep to indicate waveform start
    tone(pinBuzzer, 1319, 50);
    delay(51);

}

void serialPreParse()
{
    if (Serial.available() && serialCommand_msgPending == false)
    {
        // NEW incoming message (buffer previously empty)

        //reinitialize
        serialCommandstr[0] = '\0';
        serialCommand_singleChar = '\0';
        serialCommandindex = 0;
        serialCommand_msgPending = true; // if msg received without termination
        serialCommand_ready = false; // if terminated msg ready

    }

    while (Serial.available())
    {
        if (serialCommandindex == INPUT_SIZE)
        {
            //maximum size of C string reached
            break;
        }
        serialCommand_singleChar = Serial.read();
        //strcat(serialCommand_singleChar, serialCommandstr);
        serialCommandstr[serialCommandindex] = serialCommand_singleChar;
        serialCommandindex++;
        serialCommandstr[serialCommandindex] = '\0'; //null-termination

        if (serialCommand_singleChar == '>')
        {
            //End of single msg.
            serialCommand_ready = true;
            serialCommand_msgPending = false;
            break;
        }

    }


    if (serialCommand_ready == true)
    {
        if (serialCommandstr[0] != '<')
        {
            Serial.println("\nIncomplete command!");
        }

        Serial.println();
        Serial.print("RX:\"");
        Serial.write(serialCommandstr);
        Serial.println("\"");

    }


}
\$\endgroup\$
  • 1
    \$\begingroup\$ What is your 328Ps clock source, and at what frequency? \$\endgroup\$ – marcelm Jan 23 at 21:35
  • \$\begingroup\$ It's a 16MHz crystal. \$\endgroup\$ – Bort Jan 24 at 0:19
  • \$\begingroup\$ It sounds to me like the UART isn't synching up correctly. I'm not familiar with modern ones, but 40 years ago you could sometimes select the clock divider value -- 16 was normal but you could go to 4 if you knew your clock and the signal were in sync. The higher the divider (with the clock multiplied appropriately) the better, within reason. \$\endgroup\$ – Hot Licks Jan 24 at 3:59
  • \$\begingroup\$ Another kinda stupid question is whether your program is enabling interrupts, polling, or whatever is needed at the appropriate time. If your program is off doing something else, or if it simply doesn't know the data has arrived, it will miss the data. (Do you check the UART's "overrun" flag?) \$\endgroup\$ – Hot Licks Jan 24 at 4:04
  • \$\begingroup\$ @Marcelem That's not a crystal at all- it's a ceramic resonator (under the D5 marking on the PCB). Not nearly as accurate as a crystal, but should be okay enough for serial communications. \$\endgroup\$ – Spehro Pefhany Jan 24 at 15:04
2
\$\begingroup\$

There is a flaw in your handling of your serialCommand_msgPending flag.

Consider the following scenario:

  1. No data has arrived, you call serialPreParse()
  2. serialCommand_msgPending is false
  3. Serial.available() returns 0 in the first line of serialPreParse(), so that block is skipped (so serialCommand_msgPending is still false)
  4. A byte arrives on the serial port
  5. In the while loop, Serial.available() returns 1 (or more, but not a full message), so you read the byte and exit the while loop (no more bytes available) and return from serialPreParse()
  6. serialPreParse() is called again
  7. Serial.available() returns non-zero and serialCommand_msgPending is false, so first block is now executed, re-initialising your buffer and setting serialCommand_mgsPending to true.
  8. Subsequently, the while loop in serialPreParse() will receive the rest of the message, however you've lost the first byte(s) because they are overwritten by the subsequent bytes.

To fix it, you just need to move the check for serialCommand_msgPending and the re-initialisation code into the while loop:

void serialPreParse()
{
    while (Serial.available())
    {
         if (!serialCommand_msgPending)
         {
            // NEW incoming message (buffer previously empty)

            //reinitialize
            serialCommandstr[0] = '\0';
            serialCommand_singleChar = '\0';
            serialCommandindex = 0;
            serialCommand_msgPending = true; // if msg received without termination
            serialCommand_ready = false; // if terminated msg ready
        }

        if (serialCommandindex == INPUT_SIZE)
        {
            //maximum size of C string reached
            break;
        }
        serialCommand_singleChar = Serial.read();
        //strcat(serialCommand_singleChar, serialCommandstr);
        serialCommandstr[serialCommandindex] = serialCommand_singleChar;
        serialCommandindex++;
        serialCommandstr[serialCommandindex] = '\0'; //null-termination

        if (serialCommand_singleChar == '>')
        {
            //End of single msg.
            serialCommand_ready = true;
            serialCommand_msgPending = false;
            break;
        }
    }


    if (serialCommand_ready == true)
    {
        if (serialCommandstr[0] != '<')
        {
            Serial.println("\nIncomplete command!");
        }

        Serial.println();
        Serial.print("RX:\"");
        Serial.write(serialCommandstr);
        Serial.println("\"");

    }


}

\$\endgroup\$
  • \$\begingroup\$ Thanks for the step by step run-through, even if you saw I caught it before you posted. I'll conjure a fix tomorrow and report back. It was interesting to me at first that this race condition would happen so often, as it's two conditionals right after another, with the first being false/skipped. I thought "Hmm, there are so few executions for serial data to arrive in." but then I realized that it's because I have so little else going on in the loop. My much larger programs would be very unlikely to encounter this, and if they did, it would be ignored as a one-off error. \$\endgroup\$ – Bort Jan 24 at 23:29
  • \$\begingroup\$ When there's no data available you're running in quite a tight loop, so when you think about it, it's not too much of a surprise that a newly received byte might be detected first by either of the Serial.available() calls with a similar probability. \$\endgroup\$ – NMF Jan 24 at 23:38
  • \$\begingroup\$ I'm accepting this answer, as it has a step-by-step and your fix example code makes minimal changes to the original. You will receive your prize in the mail shortly. \$\endgroup\$ – Bort Jan 26 at 15:44
10
\$\begingroup\$

What jumps out to me here is the mark (low signal) levels in your UART signal. Marks should be close to zero, but the oscilloscope output you've included looks like it's only dropping to ~1.6V, especially on the first few characters. This is well above VIL for your microcontroller (0.2×VCC = 1V), so the microcontroller will not reliably interpret it as a logical low.

What hardware are you using to generate UART output from the computer? Make sure it shares a ground with the computer. If this doesn't help, it may be faulty and need to be replaced.

\$\endgroup\$
  • 2
    \$\begingroup\$ I think you may be onto something. The mark levels appear to drop lower over the course of he 4-character message, making the last one more likely to be recognized than the 1st. \$\endgroup\$ – brhans Jan 23 at 21:39
  • \$\begingroup\$ Wow, I erroneously assumed that the signal was reaching near-zero because the logic decoder was right below it! Great catch, but I'm not sure how this can happen. I mentioned that the board is powered through the same USB cable that carries the data because I imagined ground issues would thus be ruled out. I even tried a different USB cable to no avail. What should I do next to isolate the cause? \$\endgroup\$ – Bort Jan 24 at 0:20
  • \$\begingroup\$ You probably have something else shorted to or driving the receive data pin \$\endgroup\$ – Chris Stratton Jan 24 at 1:49
  • \$\begingroup\$ @ChrisStratton - See my Q update. I believe I have isolated this to be a SW issue, as bare modules from different manufactures all give the same result. But how would SW cause this!? \$\endgroup\$ – Bort Jan 24 at 14:53
2
\$\begingroup\$

Lets assume that electrically everything is good and maybe your scope's ground was poorly connected. Can you try this as a test and see if it gives you the result you want ( I don't have an Arduino to test). Basically, it looks like there maybe a race condition in your code between where you are resetting the state of your preparser and where you are actually reading data. You can get into a state where you check if serial data is available in the reset code and its not, then you move on to the reading where there may now be data available. In this condition the reset code did not execute so your index and other variables are not reset correctly.

This code simplifies the retrieving of the command just for a test so see if the software is the issue. It will call serialGetCommand and block until a '>' char is received. Once it is it will return with what was sent before the '>' in the serialCommandstr buffer which can then be checked. It seems like this is what you were trying to accomplish but I may be wrong.

const int pinBuzzer = A5;      // the number of pin for the buzzer

unsigned long T = 0; // "T" value

//max input size expected for a single incoming msg
#define INPUT_SIZE 32
char serialCommandstr[INPUT_SIZE + 1];
byte serialCommandindex = 0; //index of RX buffer for itterating

void setup()
{
    delay(500);
    Serial.begin(115200);

} // END Setup()


void loop()
{
    Serial.println("Waiting for input in the form of \"<XY>\"");
    Serial.println("X=[A/B], Y=[1~8]");

    serialGetCommand();

    if (serialCommandstr[1] == 'A')
    {
        T = (serialCommandstr[2] - 1) - char('0');
        Serial.print("A, ");
    }

    Serial.print("T=");
    Serial.print(T);
    Serial.print("us...");

    //Beep to indicate waveform start
    tone(pinBuzzer, 1319, 50);
    delay(51);

}

void serialGetCommand()
{
    char serialCommand_singleChar = '\0';
    serialCommandindex = 0;

    while(serialCommand_singleChar != '>')
    {
        if(Serial.available())
        {
        serialCommand_singleChar = Serial.read();
        serialCommandstr[serialCommandindex++] = serialCommand_singleChar;
        }
    }

    serialCommandstr[serialCommandindex] = '\0';
}

\$\endgroup\$
  • \$\begingroup\$ Can you give a quick run-down on the conceptual change of the code you provided, and the output you expect from it? \$\endgroup\$ – Bort Jan 24 at 17:58
  • \$\begingroup\$ Your fix appears to be missing-character-free! However, it's stuck in a loop, not allowing other things to happen. Maybe I can alter that, I'll have to take another look later. \$\endgroup\$ – Bort Jan 24 at 21:39
  • 1
    \$\begingroup\$ As for the race-condition, is it the time between if (Serial.available() && serialCommand_msgPending == false) {...} and while (Serial.available()) {...} ? If so, I think I understand your explanation to say that the if() could be false because there is no serial available, and then it becomes available and the while() becomes true. Perhaps I can fix that by combining the if() and the while() so that Serial.available() is only tested once per time-slice/loop. \$\endgroup\$ – Bort Jan 24 at 21:41
  • \$\begingroup\$ @Bort - that's it! My answer (that kind of explained what ace8080 was saying but in a different way) is just a few mintues too late. \$\endgroup\$ – NMF Jan 24 at 22:00
  • \$\begingroup\$ Ace, this is a great first post. You should register here at Stackexchange. Just wondering: How did you come across this question? \$\endgroup\$ – Bort Jan 24 at 23:31
1
\$\begingroup\$

Do you have external pull resistors on the RX line (including pulls that might be enabled in the USB to UART chip)? If so make sure you don't have the internal pull resistors enabled in ATMega (Write 0 to the appropriate bit in the appropriate PORT register).

\$\endgroup\$
  • \$\begingroup\$ I tried bare modules and I get the same result. \$\endgroup\$ – Bort Jan 24 at 17:59

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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