简体   繁体   中英

STM32F411E-DISCO Uart circular buffer on interrupts

I would like to receive and transmit data that will be put into a circular buffer. I have functions for inserting characters and reading characters.

How can I use these functions and where should I put them? Currently I transmit and receive what I send to the terminal and it works fine.

I am just learning, please give me some advice

#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);
  }
}

I want to put a side-note first, so it doesn't get missed: I would suggest incrementing your head and tail indices after you've put the byte in the buffer. So, upon initialisation, head is 0 . When you call put_tx , the byte will be stored at index 0 and then be incremented to 1 . When the interrupt calls get_tx , the tail is still 0 so you'll get that first character, then it'll be incremented to 1 . Doesn't really matter, but I think it's easier to write clean code if you think in this way. Also, it's totally a micro-optimisation, but think about setting your buffer size to a power of 2. That way, instead of going if (head==BUF_SIZE) head=0; you can go head &= BUF_SIZE-1; and you'll save a few clock cycles, no need for a test. ;-)


It definitely is a pain to set up, but very much worth it if you can get around the multiple steps. The HAL will probably handle much of this for you, but I don't know enough about that.

  1. You need an interrupt handler for UART events.
  2. You need to tell the UART to raise interrupts for RXNE (receive not empty), and TXE (transmit empty) when you've sent a character.
  3. You need to tell the NVIC to enable the UART interrupt.
  4. I definitely recommend having push and pop (or put and get) functions for both buffers. The interrupts will call put_rx and get_tx , while user code will call put_tx and get_rx . Or better yet, write some wrapper functions, like Uart_Send(char) , Uart_SendBuffer(const char*, size_t) , Uart_TryReceive(char*, size_t) that will call put_tx and get_rx .

If the HAL is as smart as I think it is, you can can probably combine steps 1-3 into a single step, and just implement HAL_UART_RxCpltCallback (as you've done) and HAL_UART_TxCpltCallback .

I don't know how the HAL works enough to give you an exact solution (all my STM32 work is built on headers I made myself before I realised even CMSIS existed - whoops.) so here's how I would do it in low-level code.

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);
}

If you need me to, I'll look into the HAL code and try and give you some more direct guidance, but I think it's valuable to take this, understand it, and translate it to your implementation.

I've had a closer look at the HAL source code, and it looks like by using the HAL_xxxx_IT() functions, all the interrupt code is already handled for you. That said, there are a lot of similarities to doing it in bare metal in your application, since you're only sending and receiving one character at a time.

When you call __HAL_UART_ENABLE_IT(&huart1, UART_IT_TXE) you're telling the microcontroller to fire an interrupt when the data register is empty. But the HAL interrupt routine doesn't know what to transmit, so it'll send out garbage until it thinks it's finished.

I think another problem might be caused by directly accessing your circular buffer in multiple places, causing clashes. You'd be better off calling your HAL functions with a temporary buffer (or pointer to a single char ) that either is taken from, or stores into, your circular buffer.


Main function

// 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 wrapper

// 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);
}

Buffer implementation

// 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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM