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_rxget_tx,而用户代码将调用put_txget_rx。或者更好的是,编写一些包装函数,例如Uart_Send(char)Uart_SendBuffer(const char*, size_t)Uart_TryReceive(char*, size_t),它们将调用put_txget_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 循环缓冲区中断的主要内容,如果未能解决你的问题,请参考以下文章

STM32F407-串口学习1(UART协议简介)

STM32:未对齐的循环 DMA UART 缓冲区

STM32F4 在中断时从UART接收数据的问题

STM32f103 HAL USB - UART桥接器

stm32 usart有啥功能

stm32f411ceu6怎么用Micropythor烧录固件