【问题标题】:STM32 uart interrupt handler missing rx byteSTM32 UART中断处理程序缺少rx字节
【发布时间】: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


【解决方案1】:

发现问题。我启用了一些数字引脚用于调试目的,并在中断处理程序中切换它们,希望我能看到在中断期间有东西占用了 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 提供了相同的中断优先级,因此我正在停止接收中断处理程序,这最终导致我丢失字节。

也许这将为下一个学习微控制器的人提供一些有用的见解。

【讨论】:

    【解决方案2】:

    HAL_UART_Receive_IT(_handle, &amp;_receivedByte, 1); 可能是您的问题的原因。它在获得 1 个字节后禁用中断。当中断被禁用时,您可能会在再次调用HAL_UART_Receive_IT 之前丢失一些字节。改为在循环模式下使用 DMA。

    【讨论】:

    • 我从未见过具有循环模式的 DMA。我总是(一次)读取一个字节并插入到环形缓冲区中。
    • 并添加portYIELD_FROM_ISR(xHigherPriorityTaskWoken);在 xSemaphoreGiveFromISR 之后。
    • @ThomasMatthews ,我不了解其他 uC,但我使用过的所有 STM32 部件都有循环模式 DMA。其中一些还支持双缓冲模式,允许它们在不同内存位置的两个缓冲区之间交替。通常,使用中断一次接收 1 个字节不是问题。但是HAL_UART_Receive_IT() 的做法是错误的,这是众所周知的 STM32 HAL 的限制(或设计错误)。
    • @HS2 啊,我没听说过。也会调查的!谢谢
    • @bas,如果您能以某种方式绕过 HAL 并编写自己的 USART 中断处理程序,则可以使用中断来实现它,而无需 DMA。我曾经在一些缺少 DMA 的旧 PIC16 和 dsPIC30 uC 上这样做。关键是,USART 接收中断必须始终启用(这是 ST 的 HAL 失败的地方。)
    猜你喜欢
    • 2019-02-18
    • 2013-09-02
    • 1970-01-01
    • 2016-10-16
    • 2021-02-28
    • 2019-09-20
    • 1970-01-01
    • 2022-09-30
    • 2020-10-02
    相关资源
    最近更新 更多