AVR USART 通信问题

Posted

技术标签:

【中文标题】AVR USART 通信问题【英文标题】:AVR USART Communication problems 【发布时间】:2017-03-16 09:25:30 【问题描述】:

我正在尝试让 USART 在 Mega2560 上正常工作。发生的事情是每隔一段时间我只收到从终端(Eclipse 串行监视器)发送的字符串中的前两个字符,或者整个字符串中缺少一个字符。我尝试检查帧、波特和其他错误无济于事。我正在为 std::string 和 std::vector 使用 cPlusPlus 库,我确实尝试使用 C 字符串(char 数组)但仍然有问题,所以我认为这不会导致任何问题。

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

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

设置.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);
        
    
 

【问题讨论】:

代码对于 UART polling 看起来没问题。如果您的程序在相当长的时间内(超过 2 个字节时间)执行其他操作,您可能希望基于中断读取 UART。 @tofro 我想知道使用 ISR 是否可以解决问题。我将更改我的代码,看看是否有帮助。完成后我会回来检查... 【参考方案1】:

我会发布这个作为答案,因为评论太长了:

我没有发现任何明显的问题,尤其是当您说您从计时器 ISR 中删除了代码以进行测试时。

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

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

    如果 RX 数据是通过轮询 RXC 标志来处理的(就像你的情况一样),你必须另外确保“主循环”中没有任何东西阻塞代码执行太久。

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

【讨论】:

正如我在上面的评论中提到的,我很好奇使用 ISR 是否会有所帮助,一旦我调整了我的代码,我会回复...谢谢!

以上是关于AVR USART 通信问题的主要内容,如果未能解决你的问题,请参考以下文章

AVR单片机教程——UART进阶

为啥要在 AVR 编程中校准振荡器

AVR 微控制器中的 USART 数字逻辑是啥?

Arduino/AVR:中断串行/I2C 通信是不是安全

USART 传输 - AVR (atmega169p) 接收 0 或 null 而不是 char 'a'

16 . USART 串口通信实验