在 AVR MCU 上使用中断通过 USART 进行传输
Using Interrupt to Transmit via USART on AVR MCU
我相信我了解如何使用中断在 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 有 2 个用于数据传输的独立硬件寄存器:UDRn
和 Transmit Shift Register
,我将其称为 TSR
从现在开始
当您将数据写入 UDRn
时,假设没有正在进行的 tx,它会立即移动到 TSR
并且 UDRE irq 触发告诉您 UDRn
寄存器是 "empty"。注意此时传输才刚刚开始,但重点是已经可以写入下一个字节到UDRn
.
当字节完全传输后,下一个字节从 UDRn
移动到 TSR
并且 UDRE
再次触发。因此,您可以将下一个字节写入 UDRn
等等。
您只能在 UDRn
为 "empty" 时将数据写入 UDRn
,否则您将覆盖它当前存储和等待传输的字节。
在实践中,您通常不会介意 TXC
irq,您想使用 UDRE
向 USART 模块提供更多数据。
TXC
irq,但是,如果你需要在传输实际完成时执行一些操作,则很有用。处理 RS485 时的一个常见示例是在发送完数据后禁用发射器,并可能重新启用本来可以禁用的接收器以避免回声。
关于您的代码
您的主要问题是您在 initUART()
中设置了 2 次 UCSR0B
并且第二次写入清除了您刚刚设置的位,因此它禁用了发射器。您想一次设置所有位,或者在第二个语句中使用 |=
。
我相信我了解如何使用中断在 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 有 2 个用于数据传输的独立硬件寄存器:UDRn
和 Transmit Shift Register
,我将其称为 TSR
从现在开始
当您将数据写入 UDRn
时,假设没有正在进行的 tx,它会立即移动到 TSR
并且 UDRE irq 触发告诉您 UDRn
寄存器是 "empty"。注意此时传输才刚刚开始,但重点是已经可以写入下一个字节到UDRn
.
当字节完全传输后,下一个字节从 UDRn
移动到 TSR
并且 UDRE
再次触发。因此,您可以将下一个字节写入 UDRn
等等。
您只能在 UDRn
为 "empty" 时将数据写入 UDRn
,否则您将覆盖它当前存储和等待传输的字节。
在实践中,您通常不会介意 TXC
irq,您想使用 UDRE
向 USART 模块提供更多数据。
TXC
irq,但是,如果你需要在传输实际完成时执行一些操作,则很有用。处理 RS485 时的一个常见示例是在发送完数据后禁用发射器,并可能重新启用本来可以禁用的接收器以避免回声。
关于您的代码
您的主要问题是您在 initUART()
中设置了 2 次 UCSR0B
并且第二次写入清除了您刚刚设置的位,因此它禁用了发射器。您想一次设置所有位,或者在第二个语句中使用 |=
。