如何解决STM32 UART中断处理程序缺少rx字节
我正在 STM F446 上编写一个小应用程序:
- freertos(来自 git 的最新版本)
- lwip (pppos)(来自 git 的最新版本)
- LTE 调制解调器连接到 uart2(中断时 rx 和 tx,prio 为 5)
- PC 连接到 uart3(用于日志记录)(仅使用 tx,也在中断优先级 5 上)
接收的字节数各不相同。因此,每个接收到的字节都会在中断时存储在环形缓冲区中。一个专用的 lwip rx 任务正在以最高优先级从该任务读取数据,并使用环形缓冲区中的数据。
偶尔我会遇到 lwip 丢包的问题。当我开始比较接收到的字节和逻辑分析器时,我终于注意到了这个问题。在 lwip 丢弃数据包的情况下,我错过了 1 个字节(由于 fcs 不好,这很有意义)。
我对这个微控制器世界很陌生,所以我确信我做错了什么。我希望有人能给我一些指点。
- 我的中断处理程序是否过于臃肿?
- 必须我对每个外围设备使用不同的优先级吗?
当我将 uart3 设置为 prio 6 时问题没有出现(因此比连接到调制解调器的 uart 低一个优先级)。这就是我开始担心的地方。对两个 UART 使用相同的优先级真的是个坏主意吗?或者这是一个明确的信号,表明我的代码(特别是中断处理程序)中存在其他错误,我应该修复/改进?
中断处理程序:
extern "C" void HAL_UART_RxCpltCallback(UART_HandleTypeDef *uartHandle)
{
if (uartHandle == &uart2Handle)
{
uart2.RxHalInterruptCallback();
}
if (uartHandle == &uart3Handle)
{
uart3.RxHalInterruptCallback();
}
}
extern "C" void HAL_UART_TxCpltCallback(UART_HandleTypeDef *uartHandle)
{
if (uartHandle == &uart2Handle)
{
uart2.TxHalInterruptCallback();
}
if (uartHandle == &uart3Handle)
{
uart3.TxHalInterruptCallback();
}
}
以及在 uart 类中的实现:
void RxHalInterruptCallback()
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
_rxRingBuffer.Store(_receivedByte);
// Re-enable interrupt in HAL
HAL_UART_Receive_IT(_handle,&_receivedByte,1);
// Allow blocking read to continue,there is new data available
xSemaphoreGiveFromISR(_rxSemaphore,&xHigherPriorityTaskWoken);
}
void TxHalInterruptCallback()
{
uint16_t readBytes = 0;
_txRingBuffer.ReadAll(256,_txBuffer,&readBytes);
if (readBytes)
{
HAL_UART_Transmit_IT(_handle,(uint8_t*)_txBuffer,readBytes*sizeof(uint8_t));
}
}
最后是环形缓冲区的实现:
class RingBuffer
{
public:
RingBuffer(uint16_t size) : _size(size)
{
_head = 0;
_tail = 0;
_buffer = new uint8_t[size];
}
virtual ~RingBuffer()
{
delete [] _buffer;
}
virtual void Store(uint8_t byte)
{
// Store head and tail in atomic action to local variables
volatile uint16_t head = _head;
volatile uint16_t tail = _tail;
_buffer[head++] = byte;
head %= _size;
// If head is equal to tail after store,we no longer kNow where our data is
if (tail == head)
{
__disable_irq();
while (1)
{
GPIOB->ODR |= LED_RED;
}
}
// Restore head back to member
_head = head;
}
virtual void Store(uint8_t *data,uint16_t length)
{
volatile uint16_t head = _head;
volatile uint16_t tail = _tail;
for (volatile uint16_t i = 0; i < length; i++)
{
_buffer[head++] = data[i];
head %= _size;
// If head is equal to tail after store,we no longer kNow where our data is
if (tail == head)
{
__disable_irq();
while (1)
{
GPIOB->ODR |= LED_RED;
}
}
}
// Restore head back to member
_head = head;
}
virtual void ReadAll(size_t maxLength,uint8_t *data,uint16_t *actualReadBytes)
{
// Store head and tail in atomic local variable
volatile uint16_t tail = _tail;
volatile uint16_t head = _head;
// Keep grabbing bytes until we have all bytes or until we read the maximum amount of desired bytes
while (tail != head && (*actualReadBytes) < maxLength)
{
data[(*actualReadBytes)++] = _buffer[taiL++];
tail %= _size;
}
// Restore tail back to member
_tail = tail;
}
private:
volatile uint16_t _head;
volatile uint16_t _tail;
volatile uint16_t _size;
uint8_t *_buffer;
};
PS:正如有经验的程序员会注意到的那样,我仍在纠结何时使用 volatile
。我不知道这是否会使性能变得如此困难,以至于会导致这个问题。我正在同时阅读更多相关内容。再次感谢您的指导。
解决方法
HAL_UART_Receive_IT(_handle,&_receivedByte,1);
可能是您问题的原因。它在获得 1 个字节后禁用中断。当中断被禁用时,您可能会在再次调用 HAL_UART_Receive_IT
之前丢失一些字节。改为在循环模式下使用 DMA。
发现问题。我启用了一些用于调试的数字引脚,并在中断处理程序中切换它们,希望我能看到在中断期间有什么东西在占用 CPU 周期。
- rx 行是实际传入的消息
- rx int 显示我的“调试标记”中断对 rx 所用的时间
- tx int 显示我的“调试标记”中断对 tx 所用的时间
在上图中,它变得非常明显。接收消息时>300us 的差距是我丢失字节的根本原因。所以我使用这些“调试标记”来查找在传输中断期间消耗 CPU 的代码部分。有罪的部分是(当然......;-))我自己的环形缓冲区。
virtual void ReadAll(size_t maxLength,uint8_t *data,uint16_t *actualReadBytes)
{
// Store head and tail in atomic local variable
uint16_t tail = _tail;
uint16_t head = _head;
// For debugging only!! Port 3 is logging uart
if (_portId == 3)
{
GPIOF->ODR ^= GPIO_PIN_2;
}
// Keep grabbing bytes until we have all bytes or until we read the maximum amount of desired bytes
while (tail != head && (*actualReadBytes) < maxLength)
{
data[(*actualReadBytes)++] = _buffer[tail++];
tail %= _size;
}
// For debugging only!! Port 3 is logging uart
if (_portId == 3)
{
GPIOF->ODR ^= GPIO_PIN_2;
}
// Restore tail back to member
_tail = tail;
}
while 循环将字节从 ringbuffer 复制到实际发送缓冲区,耗时超过 300us。我没想到这会这么慢。我会将复制部分移出中断处理程序,并在非 ISR 线程中准备下一个发送缓冲区。
因为我给了 uart 2 和 uart 3 相同的中断优先级,所以我停止了接收中断处理程序,最终导致我丢失了字节。
也许这会给下一个正在学习微控制器的人提供一些有用的见解。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。