2025-01-31 07:17 AM - edited 2025-01-31 07:22 AM
I have set up a UART as receiver. I use circular buffer to copy the data in. The code pretty much works ok with one exception:
If the last byte of incoming data frame is to be saved in index 0 (UARTRx[0]), the data appears at this address AFTER the buffer counter indicates the data to be ready at that location. the delay could be few hundred microseconds.
Is it possible that the counter is updated, but the value is still not written yet?
/* Private includes ----------------------------------------------------------*/
#include "BMS.h"
#include "cmsis_os.h"
#include "string.h"
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
#define RX_BUF_SIZE 255 // must be power of 2
#define TX_BUF_SIZE 9
#define FR_BUF_SIZE 32
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
// Circular buffer to store received UART data
uint8_t frame[FR_BUF_SIZE]; // Buffer for individual received data frames
uint8_t UARTRx[RX_BUF_SIZE]; // UART Bus receive Circular DMA buffer
Receiving Task(infinite loop):
void BMS_Receive()
{
if(HAL_UART_Receive_DMA(&huart1, UARTRx, RX_BUF_SIZE) != HAL_OK) // Start UART reception with DMA
{
Error_Handler();
}
uint8_t head, checkedhead, tail, y;
int crc;
head = checkedhead = tail = 0;
while(1)
{
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_ORE) || __HAL_UART_GET_FLAG(&huart1, UART_FLAG_FE) || __HAL_UART_GET_FLAG(&huart1, UART_FLAG_NE)){
__HAL_UART_CLEAR_OREFLAG(&huart1); // The method of clearing all three flags is the same, so only one macro needs to be called
if(HAL_UART_Receive_DMA(&huart1, UARTRx, RX_BUF_SIZE) != HAL_OK) // Start UART reception with DMA
{
Error_Handler();
}
memset(UARTRx, 0, RX_BUF_SIZE);
osDelay(1);
Cprintf("Reset\n");
}
head = ((RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx)) -1 ) % RX_BUF_SIZE;
while (head != checkedhead)
{
if (UARTRx[tail] == BMS_UARTHEADER){
for (y = 0; y < FR_BUF_SIZE; y++) // Copy data from circular buffer
{
checkedhead = (tail + y) % RX_BUF_SIZE;
frame[y] = UARTRx[checkedhead];
if (checkedhead == head)break; // stop copy if head comes before full frame buffer filled
}
// do some parsing here
//so in this location i see that the UARTRx[0] value is wrong, by adding a small delay it is updated to correct value
tail = (tail+1) % RX_BUF_SIZE;
} else
{
checkedhead = tail;
tail = (tail+1) % RX_BUF_SIZE;
}
}
osDelay(1);
}
}
Solved! Go to Solution.
2025-02-04 01:05 AM
To answer my own question, buffer size was set up wrong. Correct is 256 and not 255!
2025-01-31 07:22 AM
> head = ((RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart1_rx)) -1 ) % RX_BUF_SIZE;
Seems off by one. Consider:
RX_BUF_SIZE = 8
NDTR = 8 (no transfers yet)
head = ((8 - 8) - 1 ) % 8 = -1
should be 0
(also note the overflow issue)
2025-01-31 07:40 AM - edited 2025-01-31 07:49 AM
since head is defined as uint8_t, the result would be 7 right?
Anyway once the receiver is running the NDTR is 0...7, and there is no overflow/underflow. My problem always occurs when receiver has been running for some time.
2025-01-31 07:44 AM
The reason I subtract 1 is to get the existing data location and not the next data location that the NDTR register refers to.
2025-01-31 08:00 AM
Let's be objective here: When no transfers have taken place yet, your code reads characters from UARTRx. That's a bug.
UARTRx should also be declared as volatile. Might not be an issue here, but it's possible.
> since head is defined as uint8_t, the result would be 7 right?
Yes
> Anyway once the receiver is running the NDTR is 0...7, and there is no overflow/underflow.
NDTR gets reloaded when it hits 0 in circular mode. Test it.
2025-02-04 01:05 AM
To answer my own question, buffer size was set up wrong. Correct is 256 and not 255!