微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

STM32 UART中断处理程序缺少rx字节

如何解决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 周期。

enter image description here

  • 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 举报,一经查实,本站将立刻删除。