AVR USART通信问题

时间:2016-11-02 22:01:38

标签: arduino avr atmega usart

我正在努力让USART在Mega2560上正常工作。发生的事情是偶尔我只收到终端发送的字符串中的前两个字符(Eclipse Serial Monitor)或者整个字符串中缺少一个字符。我试过检查Frame,Baud和其他错误无济于事。我正在使用cPlusPlus库用于std :: string和std :: vector,我确实尝试使用C字符串(char数组),但仍然遇到了问题所以我认为这不会导致任何问题。

Eclipse设置为在发送时向字符串添加换行符('\ n')并在执行字符串上的任何代码之前等待该字符,但仍然存在问题。我最初是从Arduino Serial库开始的,但遇到了同样的问题,有时甚至更糟,这就是为什么我选择使用AVR USART。

我不确定这是否是一个问题,但我确实有两个计时器(4和5)运行来控制我的程序的其他方面,这些是否会导致问题?我确实尝试删除这些并仍然得到相同的结果可能我必须添加另一个命令(sei,cli)或在某处设置某些东西?我希望我没有遗漏一些明显的东西,下面是相关代码。

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]);
    }
}

主要

#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);
        }
    }
}

修改

感谢Rev1.0和tofro,我终于让我的代码正常工作了。事实上,计时器引发了一些冲突,并且将USART转移到ISR中工作得很漂亮。我还能够摆脱其中一个计时器,只需将操作移动到主循环中。我有一个问题是我在主循环中有一个小延迟,这是否与std :: stream中的sleep()执行相同的操作?我知道你不应该在主循环中有延迟,除非你特别希望程序等待,但在我的测试中添加延迟似乎完善了USART Rx。以下是更新的代码......

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);
}

主要

#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 个答案:

答案 0 :(得分:2)

我会将此作为答案发布,因为评论时间过长:

我没有看到任何明显的问题,特别是当您说您从计时器ISR中删除代码以进行测试时。

但是,请考虑硬件UART使用双字节FIFO。如果控制器在没有读出UDR寄存器的情况下接收到更多两个字节,则FIFO中的数据将被丢失/覆盖。这可能发生在您的情况下。它通常发生在两种情况下:

  1. 如果使用RX ISR处理RX数据,则必须确保其他ISR不会阻止代码执行。这就是为什么ISR应该包含尽可能少的代码的一般原因。

  2. 如果通过轮询RXC标志来处理RX数据(就像你的情况一样),你还必须确保“主循环”中的任何内容都不会阻止代码执行太长时间。

  3. 使用ISR而不是轮询RXC标志是首选变体。