【发布时间】:2021-08-26 21:47:30
【问题描述】:
我正在 STM F446 上编写一个小应用程序:
- freertos(来自 git 的最新版本)
- lwip (pppos)(来自 git 的最新版本)
- LTE 调制解调器连接到 uart2(中断时 rx 和 tx,优先级为 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。我不知道这是否会严重影响性能,以至于会导致这个问题。我正在同时阅读更多内容。再次感谢您的指导。
【问题讨论】:
-
谁在使用这个信号量?
-
new 和 delete 应该是你在编写 uCs 时禁用的关键字。
-
为什么您的环形缓冲区需要动态内存?您在编译时已经有了大小。此外,为了使环形缓冲区更高效,容量应该是 2 的幂。
-
环形缓冲区成员不需要是
volatile。头和尾索引只能由环形缓冲区类修改。关键字volatile通常用于表示硬件改变的变量或程序控制之外改变的变量(可能是线程?)。 -
您的
Store错误。声明_buffer[head++]不考虑缓冲区的环形或循环性质。你可能想做:buffer[head] = value; head = (head + 1) % capacity;.
标签: c++ stm32 uart interrupt-handling