2
votes

I am trying to get the USART working correctly on a Mega2560. What is happening is every once in a while I only receive the first two characters from a string sent from the terminal (Eclipse Serial Monitor) or one character is missing from the entire string. I have tried checking for Frame, Baud and other errors to no avail. I am using the cPlusPlus Library for std::string and std::vector, I did try using C strings (char array) but still had the issue so I don't think that this is causing any problems.

Eclipse is setup to add a newline ('\n') to the string when sending and I wait for that character before executing any code on the string, but still have the issue. I originally started with the Arduino Serial library but had the same problem, even worse sometimes, that is why I opted to use the AVR USART.

I am not sure if this is an issue but I do have 2 timers (4 and 5) running to control other aspects of my program, could these be contributing to the issue? I did try removing these and still got the same result maybe I have to add another command (sei, cli) or set something somewhere? I hope I am not missing something obvious, below is the relevant code.

Settings.h

const struct{
   uint16_t baud = 57600;
}settings;

USARTSerial

/**
 * Constructor
 */
USARTSerial::USARTSerial(){
    uint8_t baud = (F_CPU / (settings.baud * 16UL)) - 1;

    /*** Serial Setup ***/
    UBRR0H = (baud >> 8);
    UBRR0L = baud;

    //Transmit and receive enable
    UCSR0B |= (1 << TXEN0) | (1 << RXEN0);
    UCSR0C |= (1 << UCSZ00) | (1 << UCSZ01);
}

/**
 * Reads a single byte from the USART
 */
unsigned char USARTSerial::ReadChar(){

    loop_until_bit_is_set(UCSR0A, RXC0);

    //Get the received char
    return UDR0;
}

/**
 * Writes a single byte to the USART.
 */
void USARTSerial::WriteChar(char data){

    //Wait until byte is ready to be written    
    while((UCSR0A & (1<<UDRE0)) == 0){}

    // Transmit data
    UDR0 = data;
}

/**
 * Writes a std::string to the USART.
 */
void USARTSerial::Write(string data){

    //Loop unit we write all the data
    for (uint16_t i = 0; i < data.length(); i++){
        this->WriteChar(data[i]);
    }
}

Main

#include "Arduino.h"
#include "avr/interrupt.h"
#include "StandardCplusplus.h"
#include "string"
#include "Settings.h"
#include "USARTSerial.h"

std::string cmdStr;   /*** String to hold incoming data ***/
USARTSerial serial;   /*** Serial Interface ***/

void setup(){
    cli();

    //Setup Timer 5
    TCCR5A = 0;
    TCCR5B = 0;
    TCNT5  = 0;
    OCR5A = 16000;
    TCCR5B |= (1 << WGM52);
    TCCR5B |= (0 << CS52) | (0 << CS51) | (1 << CS50);
    TIMSK5 |= (1 << OCIE5A);

    //Setup Timer 4
    TCCR4A = 0;
    TCCR4B = 0;
    TCNT4 = 0;
    OCR4A = 40000;
    TCCR4B |= (1 << WGM12);
    TCCR4B |= (0 << CS12) | (1 << CS11) | (1 << CS10);
    TIMSK4 |= (1 << OCIE4A);

    serial = USARTSerial();

    //Enable the Interrupts
    sei();
}


/**
 * ISR(Timer5 Compare A)
**/  
ISR(TIMER5_COMPA_vect)
{
    //Do things...
}

/**
 * ISR(Timer4 Compare A)
**/
ISR(TIMER4_COMPA_vect) {

   //Do some really cool stuff....
}

void loop(){
    char inChar;

    if(bit_is_set(UCSR0A, RXC0)){

        inChar = serial.ReadChar();

        //Check if we have a newline
        if (inChar == '\n'){
            serial.Write(cmdStr);
            cmdStr = "";
        }
        else{
            cmdStr += toupper(inChar);
        }
    }
}

EDIT


Thanks to Rev1.0 and tofro I finally got my code working correctly. Indeed the Timers were causing some conflicts and moving the USART into a ISR worked beautifully. I was also able to get rid of one of the timers and simply move the operations into the main loop. One question I do have is about a small delay I have in the main loop, is this performing the same action as sleep() in std::stream? I know that you shouldn't have a delay in the main loop unless you specifically want the program to wait but during my testing adding the delay seemed to perfect the USART Rx. Below is the updated code....

USARTSerial

/**
 * Constructor
 */
USARTSerial::USARTSerial(){
    uint8_t baud = (F_CPU / (settings.baud * 16UL)) - 1;

    /*** Serial Setup ***/
    UBRR0H = (baud >> 8);
    UBRR0L = baud;

    //Transmit and receive enable
    UCSR0B |= (1 << TXEN0) | (1 << RXEN0) | (1 << RXCIE0); /** Added RXCIE0 for the USART ISR **/
    UCSR0C |= (1 << UCSZ00) | (1 << UCSZ01);
}

/**
 * Reads a single byte from the USART
 */
unsigned char USARTSerial::ReadChar(){

    loop_until_bit_is_set(UCSR0A, RXC0);

    //Get the received char
    return UDR0;
}

/**
 * Writes a single byte to the USART.
 */
void USARTSerial::WriteChar(char data){

    //Wait until byte is ready to be written    
    while((UCSR0A & (1<<UDRE0)) == 0){}

    // Transmit data
    UDR0 = data;
}

/**
 * Writes a std::string to the USART.
 */
void USARTSerial::Write(string data){

    //Loop unit we write all the data
    for (uint16_t i = 0; i < data.length(); i++){
        this->WriteChar(data[i]);
    }
}

/**
 * Available
 *
 * Returns if the USART is ready for reading
 */
bool USARTSerial::Available(){
    return bit_is_set(UCSR0A, RXC0);
}

Main

#include "Arduino.h"
#include "avr/interrupt.h"
#include "StandardCplusplus.h"
#include "string"
#include "Settings.h"
#include "USARTSerial.h"

std::string cmdStr;   /*** String to hold incoming data ***/
USARTSerial serial;   /*** Serial Interface ***/

void setup(){
    cli();

    //Setup Timer 5
    TCCR5A = 0;
    TCCR5B = 0;
    TCNT5  = 0;
    OCR5A = 16000;
    TCCR5B |= (1 << WGM52);
    TCCR5B |= (0 << CS52) | (0 << CS51) | (1 << CS50);
    TIMSK5 |= (1 << OCIE5A);

    serial = USARTSerial();

    //Enable the Interrupts
    sei();
}


/**
 * ISR(Timer5 Compare A)
**/  
ISR(TIMER5_COMPA_vect)
{
    //Do things...
}

/**
 * Main Loop
**/
void loop(){
     //Do some really cool stuff....
     delay (50);
} 

/**
 * USART Interrupt
 */
ISR(USART0_RX_vect){
    char inChar;

    if(serial.Available()){

        inChar = serial.ReadChar();

        //Check if we have a newline
        if (inChar == '\n'){
            /** Run code on the recieved data ***/
            cmdStr = "";
        }
        else{
            //Convert to Uppercase 
            cmdStr += toupper(inChar);
        }
    }
} 
1
Code looks alright for UART polling. In case you have your program doing something else for a considerable (longer than 2 byte times) time, you might want to base the UART reading on interrupt, though.tofro
@tofro I was wondering if using the ISR might fix the problem. I will change my code around and see if that helps. I will check back in once I get that done...Andy Braham

1 Answers

2
votes

I will post this as an answer since it was too long for a comment:

I don't see any obvious problem, especially when you say you removed the code from the timer ISR's for testing.

However, consider that the hardware UART uses a two byte FIFO. If the controller receives more two bytes without the UDR register being read out, data in the FIFO will be lost/overwritten. This may happen in your case. It typically happens in two scenarios:

  1. If RX data is handled using the RX ISR, you have to make sure that other ISRs are not blocking code execution. This is the general reason why ISR's should contain as little code as possible.

  2. If RX data is handled by polling the RXC flag (like in your case), you have to additionally make sure that nothing in the "main loop" is blocking the code execution too long.

Using the ISR instead of polling the RXC flag is the preferred variant.