STM32F411E-DISCO Uart 循环缓冲区中断
Posted
技术标签:
【中文标题】STM32F411E-DISCO Uart 循环缓冲区中断【英文标题】:STM32F411E-DISCO Uart circular buffer on interrupts 【发布时间】:2021-06-16 07:52:56 【问题描述】:我想接收和传输将被放入循环缓冲区的数据。我有插入字符和读取字符的功能。
我如何使用这些功能,我应该把它们放在哪里?目前我发送和接收我发送到终端的内容,它工作正常。
我只是学习,请给我一些建议
#define UART_RX_BUF_SIZE 7
#define UART_TX_BUF_SIZE 7
int8_t uart_put_char(char data);
int8_t uart_get_char(char *data);
volatile char uart_rxBuff[UART_RX_BUF_SIZE];
volatile char uart_txBuff[UART_TX_BUF_SIZE];
void uart_put_string(char *s);
typedef struct
volatile char *const buffer;
uint8_t head;
uint8_t tail;
circ_buffer_t;
volatile circ_buffer_t uart_rx_circBuff = uart_rxBuff, 0, 0;
volatile circ_buffer_t uart_tx_circBuff = uart_txBuff, 0, 0;
uint8_t received_char;
int8_t uart_put_char(char data)
uint8_t head_temp = uart_tx_circBuff.head + 1;
if (head_temp == UART_TX_BUF_SIZE)
head_temp = 0;
if (head_temp == uart_tx_circBuff.tail)
return 0;
uart_tx_circBuff.buffer[head_temp] = data;
uart_tx_circBuff.head = head_temp;
__HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE);
return 1;
int8_t uart_get_char(char *data)
if (uart_rx_circBuff.head == uart_rx_circBuff.tail)
return 0;
uart_rx_circBuff.tail++;
if (uart_rx_circBuff.tail == UART_RX_BUF_SIZE)
uart_rx_circBuff.tail = 0;
*data = uart_rx_circBuff.buffer[uart_rx_circBuff.tail];
return 1;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
if (huart->Instance == USART1)
// uart_put_char(&received_char);
HAL_UART_Transmit(&huart1, &received_char, 1, 100);
// uart_get_char(received_char);
HAL_UART_Receive_IT(&huart1, &received_char, 1);
【问题讨论】:
我不使用stm32,但在TI msp432上做类似的事情。通常,该卡应同时提供读取和发送缓冲区(尽管您当然可以提供自己的)。当从 UART 读取或写入时,当接收缓冲区接收到一个字符时将设置一个中断。您可以使用中断函数来处理发送和接收。您必须确保子系统时钟运行得足够快,可以同时发送和接收。我在 TI 卡上使用 115,200 波特的 3MHz 时钟在低功耗模式下遇到问题,但 12MHz 时钟似乎没有问题。 【参考方案1】:我想先放一个旁注,以免遗漏:
我建议在将字节放入缓冲区之后增加头部和尾部索引。因此,在初始化时,head 是0
。当您调用put_tx
时,该字节将存储在索引0
并且然后 递增到1
。当中断调用get_tx
时,尾部仍然是0
,所以你会得到第一个字符,然后它将增加到1
。没关系,但我认为如果您以这种方式思考,编写干净的代码会更容易。
此外,这完全是一个微优化,但请考虑将缓冲区大小设置为 2 的幂。这样,您可以使用 if (head==BUF_SIZE) head=0;
而不是 head &= BUF_SIZE-1;
,这样您将节省几个时钟周期,无需一个测试。 ;-)
设置起来肯定很痛苦,但如果你能绕过多个步骤,那就非常值得了。 HAL 可能会为您处理其中的大部分内容,但我对此知之甚少。
-
您需要一个用于 UART 事件的中断处理程序。
您需要告诉 UART 在发送字符时为
RXNE
(接收非空)和TXE
(发送空)引发中断。
您需要告诉 NVIC 启用 UART 中断。
我绝对建议对两个缓冲区都使用 push 和 pop(或 put 和 get)函数。中断将调用put_rx
和get_tx
,而用户代码将调用put_tx
和get_rx
。或者更好的是,编写一些包装函数,例如Uart_Send(char)
、Uart_SendBuffer(const char*, size_t)
、Uart_TryReceive(char*, size_t)
,它们将调用put_tx
和get_rx
。
如果 HAL 像我认为的那样聪明,您可能可以将步骤 1-3 合并为一个步骤,然后实现 HAL_UART_RxCpltCallback
(就像您所做的那样)和 HAL_UART_TxCpltCallback
。
我不知道 HAL 的工作原理如何足以为您提供精确的解决方案(我所有的 STM32 工作都建立在我意识到甚至存在 CMSIS 之前我自己制作的标题上 - 哎呀!)所以这就是我将如何做级代码。
void USART1_IRQHandler() __attribute__((interrupt))
// This would probably be handled in HAL_UART_RxCpltCallback
if (USART1->SR.RXNE) // We've received a byte!
uart_push_rx(USART1->DR); // Push the received byte onto the back
// of the RX buffer.
// This would probably be handled in HAL_UART_TxCpltCallback
if (USART1->SR.TXE) // Transmit buffer is empty - send next byte
char nextByte;
if (uart_pop_tx(&nextByte)) // Get the next byte in the buffer and
USART1->DR = nextByte; // shove it in the UART data register.
else // No more data in the circular buffer,
USART1->CR1.TXEIE = 0; // so disable the TXE interrupt or we'll
// end up stuck in a loop.
if (USART1->SR.TC) // There's also a flag for 'transmit complete'. This
// is different from TXE in that TXE means "Okay, I've started this
// one, tell me what'll come next," whereas TC says "Okay, I've
// finished sending everything now. Anything else, boss?"
// We won't use it, but be aware of the terminology. The HAL might
// try and confuse you with its words.
void InitialiseUart()
HAL_UART_Configure(&huart1, baud, stopBits, andStuff, probably, etc);
HAL_UART_Enable(&huart1);
// You probably don't need to worry about anything below here,
// if the HAL is smart. But I've included it for completeness,
// so you can understand more of what the MCU is doing.
// Enable RXNE (receive not empty) interrupt
USART1->CR1.RXNEIE = 1;
// Don't enable TXE (transmit empty) interrupt yet. Only when you send
// a character, or the interrupt will fire immediately.
// Enable UART interrupts at the system level
NVIC_EnableIRQ(USART1_IRQn);
如果您需要我,我会查看 HAL 代码并尝试为您提供一些更直接的指导,但我认为接受、理解并将其转化为您的实施是很有价值的。
【讨论】:
见下面我的回答。你有什么想法吗? 当我在 "uint8_t head_temp..." 和 if(USART_SR_TXE!=1) " if (uart_tx_circBuff.head == uart_tx_circBuff. tail) " 没有任何变化。收到 xxxxxxx 和很多奇怪的字符。【参考方案2】:我仔细查看了 HAL 源代码,看起来通过使用 HAL_xxxx_IT()
函数,所有的中断代码都已经为您处理好了。也就是说,在您的应用程序中使用裸机执行此操作有很多相似之处,因为您一次只发送和接收一个字符。
当您调用__HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE)
时,您是在告诉微控制器在数据寄存器为空时触发中断。但是 HAL 中断例程不知道要传输什么,所以它会发送垃圾直到它认为它完成了。
我认为另一个问题可能是在多个地方直接访问您的循环缓冲区,从而导致冲突。最好使用临时缓冲区(或指向单个 char
的指针)调用 HAL 函数,该缓冲区取自或存储到循环缓冲区中。
主要功能
// Entry point
int main(void)
// Initialise system and peripherals
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
MX_TIM10_Init();
// Enable interrupts
HAL_NVIC_EnableIRQ(USART1_IRQn);
// Prepare reception of the first character
HAL_UART_Receive_IT(&huart1, &received_char, 1);
while (1)
char downloaded;
if (UartReceiveChar(&downloaded) && downloaded == 'x')
UartSendChar(downloaded);
UART 封装器
// Function declarations
int8_t UartSendChar (char data);
void UartSendString (char* str);
int8_t UartReceiveChar (char* data);
// Variables
int8_t isTransmitting = 0;
uint8_t sendingChar;
uint8_t receivedChar;
// Function definitions
// Callback function for when a character has finished sending by the HAL
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
// The HAL has just sent the tail byte from the TX buffer
// If there is still data in the buffer, we want to send the
// next byte in the buffer.
if (buffer_pop_tx(&sendingChar))
HAL_UART_Transmit_IT(huart, &sendingChar, 1);
else
isTransmitting = 0;
// Callback function for when a character is received by the HAL
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
// The HAL has received a character into 'receivedChar'
// All we need to do is push it onto our circular buffer
buffer_push_rx(receviedChar);
// and prepare to receive the next character
HAL_UART_Receive_IT(huart, &receivedChar, 1);
// Send a character
int8_t UartSendChar(char data)
// Push the character onto the buffer
int8_t returnValue = buffer_push_tx(data);
// Start sending the buffer, if we're not already transmitting
if (!isTransmitting)
sendingChar = data;
isTransmitting = 1;
HAL_UART_Transmit_IT(&huart1, &sendingChar, 1);
return returnValue;
// Send a null-terminated string
int8_t UartSendString(char* str)
// Iterate through all the non-null characters
while (*str)
// Send the character; Wait if the buffer is full
while (!UartSendChar(*str)) ;
++str;
return 1;
// Receive a character
int8_t UartReceiveChar(char* data)
// Just a wrapper for buffer_pop_rx
return buffer_pop_rx(data);
缓冲区实现
// I've changed your circular buffer size for optimisation purposes
#define UART_RX_BUF_SIZE 8
#define UART_TX_BUF_SIZE 8
// Buffer type definition
typedef struct
volatile char *const buffer;
uint8_t head;
uint8_t tail;
uint8_t isFull : 1;
uint8_t isEmpty : 1;
uint8_t hasOverflowed : 1; // Overflow and underflow are only relevant if we choose to
uint8_t hasUnderflowed : 1; // allow pushing and popping with an empty/full buffer
circ_buffer_t;
// Function declarations
int8_t buffer_push_tx (char data);
int8_t buffer_push_rx (char data);
int8_t buffer_pop_tx (char* data);
int8_t buffer_pop_rx (char* data);
// Variables
volatile char uart_rxBuff[UART_RX_BUF_SIZE];
volatile char uart_txBuff[UART_TX_BUF_SIZE];
volatile circ_buffer_t uart_rx_circBuff = uart_rxBuff, 0, 0;
volatile circ_buffer_t uart_tx_circBuff = uart_txBuff, 0, 0;
// Function definitions
// Push a character onto the transmit buffer
int8_t buffer_push_tx(char data)
if (uart_tx_circBuff.isFull) // buffer is full
// uart_tx_circBuff.hasOverflowed = 1; // Nasty things can happen if we allow overflows. But in some special cases, it may be necessary.
return 0;
// Put the character at the head position and increment the head index
uart_tx_circBuff.buffer[uart_tx_circBuff.head++] = data;
uart_tx.circBuff.head &= (UART_TX_BUF_SIZE - 1); // don't use &= if the buffer size isn't a power of 2
// mark the buffer as full if the head and tail indices are the same
uart_tx_circBuff.isFull = (uart_tx_circBuff.head == uart_tx_circBuff.tail);
uart_tx_circBuff.isEmpty = 0;
// OK
return 1;
// Push a character onto the receive buffer
int8_t buffer_push_rx(char data)
if (uart_rx_circBuff.isFull) // buffer is full
// uart_rx_circBuff.hasOverflowed = 1;
return 0;
// Put the character at the head position and increment the head index
uart_rx_circBuff.buffer[uart_rx_circBuff.head++] = data;
uart_rx.circBuff.head &= (UART_RX_BUF_SIZE - 1); // don't use &= if the buffer size isn't a power of 2
// mark the buffer as full if the head and tail indices are the same
uart_rx_circBuff.isFull = (uart_rx_circBuff.head == uart_rx_circBuff.tail);
uart_rx_circBuff.isEmpty = 0;
// OK
return 1;
// Try to get a character from the receive buffer.
int8_t uart_pop_rx(char *data)
if (uart_rx_circBuff.isEmpty) // buffer is empty
// uart_rx_circBuff.hasUnderflowed = 1;
return 0;
// Put the character from the tail position of the buffer into 'data' and increment the tail index
*data = uart_rx_circBuff.buffer[uart_rx_circBuff.tail++];
uart_rx_circBuff.tail &= (UART_RX_BUF_SIZE - 1); // // don't use &= if the buffer size isn't a power of 2
// mark the buffer as full if the head and tail indices are the same
uart_rx_circBuff.isEmpty = (uart_rx_circBuff.head == uart_rx_circBuff.tail);
uart_rx_circBuff.isFull = 0;
// OK
return 1;
// Try to get a character from the transmit buffer.
int8_t uart_pop_rx(char *data)
if (uart_tx_circBuff.head == uart_tx_circBuff.tail) // buffer is empty
// uart_tx_circBuff.hasUnderflowed = 1;
return 0;
// Put the character from the tail position of the buffer into 'data' and increment the tail index
*data = uart_tx_circBuff.buffer[uart_tx_circBuff.tail++];
uart_tx_circBuff.tail &= (UART_TX_BUF_SIZE - 1); // don't use &= if the buffer size isn't a power of 2
// mark the buffer as full if the head and tail indices are the same
uart_tx_circBuff.isEmpty = (uart_tx_circBuff.head == uart_tx_circBuff.tail);
uart_tx_circBuff.isFull = 0;
// OK
return 1;
【讨论】:
我将代码上传到了开发板上,试图了解为什么在发送单个字符“x”后会变成“xx”。这最多可以工作 7 次,我发送 x 得到 xx,当我第八次发送 x 时,我得到无限字符串 'xxxxxxxxxxx ...',我必须通过重置来终止。但是当我一次发送 7 个字符时“xxxxxxx”。替换为 8 个字符 'xxxxxxxx'。一次发送 8 个“x”字符后,我必须通过重置来中断一个无限字符串。 您的原始代码中有多少还在使用?在您的原始帖子中,您的RxCplt
回调中有HAL_Uart_Transmit()
,因此它将发送回它收到的每个字符。在uart_put_char
中也有__HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE)
,因此只要您将某些内容放入TX 缓冲区,它就会开始发回未定义的垃圾。
我已将其删除。现在我只是在分析你的代码。以上是关于STM32F411E-DISCO Uart 循环缓冲区中断的主要内容,如果未能解决你的问题,请参考以下文章