【问题标题】:Using Interrupt to Transmit via USART on AVR MCU在 AVR MCU 上使用中断通过 USART 进行传输
【发布时间】:2020-03-17 21:59:19
【问题描述】:

我相信我了解如何使用中断在 ATmega328p 的 UART 上接收串行数据,但我不了解如何传输数据的机制。

这是一个基本程序,我想用它来传输字符串“hello”,使用中断来驱动传输。我知道字符 'o' 可能会被传输两次,我可以接受。

#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 16000000UL
#define BAUD 19200
#define DOUBLE_SPEED 1

void initUART(unsigned int baud, unsigned int speed);

volatile uint8_t charIndex = 0;
volatile unsigned char command[5] = "hello";

int main(void)
{
    //initialize UART
    initUART(BAUD, DOUBLE_SPEED);

    sei();

    //What do I put here to initiate transmission of character string command?
    //Is this even correct?
    UDR0 = command[0];

    while(1)
    { 

    }   
}

ISR(USART_TX_vect) 
{
   // Transmit complete interrupt triggered
    if (charIndex >= 4) 
    {
        //Reach the end of command, end transmission
        return; 
    }
    //transmit the first char or byte
    UDR0 = command[charIndex];
    //Step to the next place of the command
    charIndex++; 
}

void initUART(unsigned int baud, unsigned int speed)
{
    unsigned int ubrr;

    if(speed)
    {
        //double rate mode
        ubrr = F_CPU/8/baud-1;
        //set double speed mode
        UCSR0A = (speed << U2X0);
    }
    else
    {
        //normal rate mode
        ubrr = F_CPU/16/baud-1;
    }

    //set the baud rate
    UBRR0H = (unsigned char)(ubrr >> 8);
    UBRR0L = (unsigned char)(ubrr);

    //enable Tx and Rx pins on MCU
    UCSR0B = (1 << RXEN0) | (1 << TXEN0);

    //enable transmit interrupt
    UCSR0B = (1 << TXCIE0);

    //set control bits, 8 bit char, 0 stop, no parity
    UCSR0C = (1 <<UCSZ00) | (1 <<UCSZ01);
}

我的理解是,如果我将第一个字符写入 UDR0(就像我在 main() 中所做的那样),这将触发传输完成中断,然后下一个字节将通过 ISR 传输。这似乎不起作用。

此处显示的代码使用 gcc 编译。有人可以解释一下吗?

【问题讨论】:

  • 当您完成传输后,我相信您必须禁用 USART 的 TX 中断,以免永远卡在其中。同样,您应该只在有数据要发送时才启用中断。但我不确定这是否能解决您当前的问题。你的问题具体是什么?您是否看到在 TX 上传输的任何字节或什么?

标签: c interrupt avr usart


【解决方案1】:

要理解的关键是,USART 有 2 个独立的硬件寄存器用于数据传输:UDRnTransmit Shift Register,从现在开始我将称之为 TSR

当您将数据写入UDRn 时,假设没有 tx 正在进行,它将立即移动到 TSR 并且 UDRE irq 触发告诉您 UDRn 寄存器为“空”。注意此时传输才刚刚开始,但重点是已经可以将下一个字节写入UDRn了。

当字节传输完毕后,下一个字节从UDRn 移动到TSR 并再次触发UDRE。因此,您可以将下一个字节写入UDRn 等等。

您必须仅在 UDRn 为“空”时向其写入数据,否则您将覆盖它当前存储和等待传输的字节。

实际上,您通常不介意TXC irq,您希望使用UDRE 向USART 模块提供更多数据。

TXC irq 但是,如果您需要在传输实际完成时执行某些操作,则它很有用。处理 RS485 时的一个常见示例是在您完成发送数据后禁用发送器,并可能重新启用您可以禁用以避免回声的接收器。

关于您的代码

您的主要问题是您在initUART() 中设置了UCSR0B 2 次,第二次写入清除了您刚刚设置的位,因此禁用了发射器。您想一次性设置所有位,或在第二个语句中使用|=

【讨论】:

  • 谢谢,没看到我设置了两次UCSR0B。当我回到家时,我会改变它。我也会花时间思考你的其他 cmets。谢谢。
  • Zmaster,我更正了 USART 初始化……这有很大的不同。谢谢你的收获。我还在 main() 中添加了代码,以便仅在知道 UDRE0 寄存器为空后才传输第一个字符。而(!(UCSR0A&(1
  • 好。为简化起见,我只需准备缓冲区和索引变量,然后启用 UDRE 中断并让 ISR 写入所有字节(包括第一个字节)。一旦启用它就会触发,因为 UDRn 寄存器最初是空的,然后当 UDRn 可以用下一个字节写入时再次触发。当缓冲区完全发送时,您需要禁用中断,否则它将继续触发。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-03-16
  • 2013-10-29
  • 2018-02-22
  • 2016-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多