首页 > 解决方案 > STM32 中断驱动的 UART 接收在几次完美接收后失败

问题描述

请注意帖子末尾的澄清和更新

TL;DR: STM32 有 3 个 UART 连接,1 个用于调试,2 个用于使用中断驱动的实际通信HAL_UART_Receive_IT。最初,中断驱动的 UART 接收工作正常,但随着时间的推移,其中一个 UART 的接收回调触发越来越少,直到 STM32 最终在那个 UART 上根本没有收到任何数据包(尽管我能够验证它们是发送)。我怀疑这个问题与时间有关。

情况:作为我论文的一部分,我开发了一个新的协议,现在必须实施和测试。它涉及两类参与者,服务器和设备。一个设备由一个 STM32、ESP32 和一个 UART 转以太网桥组成。STM32 通过 UART 连接到桥接器,并通过 UART 连接到 ESP32。网桥通过将 STM32 发送的串行数据转换为 TCP 数据包转发到服务器(反之亦然),将 STM32 连接到服务器。ESP32 接收来自 STM32 的成帧数据包,通过 BLE 广播它们,并将所有接收到的格式良好的 BLE 数据包转发到 STM32。即ESP32 只是一个BLE 桥。服务器和 ESP32 似乎工作正常。

简而言之,服务器试图找出哪些设备 D_j 可以听到来自设备 D_i 的 BLE 广告。服务器通过定期迭代所有设备 D_1、...、D_n 并发送它们以加密为 X_1、...、X_n 的随机数 Y_1、...、Y_n 来实现这一点。D_i 收到 X_i 后,将其解密以获取 Y_i,然后将其转发到 ESP32 以通过 BLE 广播。相反,每当STM32接收到来自ESP32的数据包(即通过BLE广播的数据包)时,它就会提取一些数据,对其进行加密并将其转发给服务器。

在服务器遍历所有设备之后,它会查看它在该轮中收到的所有消息。如果它例如接收到由D_j 发送的值为Y_i 的消息,它可以推断D_i 的广播以某种方式到达了D_j。

问题:按照我现在的设置方式,每个 STM32 似乎偶尔会“错过”ESP32 发送的消息。我在我的设置中拥有的此类设备越多,情况就越糟糕!只需两台设备,该协议就可以 100% 地工作。使用三个设备,它似乎也可以正常工作。然而,对于四个设备,STM32 的 ESP32 的 UART 接收回调最初工作正常,但经过几轮这样的循环后,它不会一直触发,直到最终完全不触发。

可视化: 下图显示了 n 个设备的示例拓扑。这里没有画出来,但是如果例如 D_1 要接收 Y_2,它会将其加密到 X_2' 并通过桥将其发送到服务器。 网络和时间线的可视化

注意:

  1. 加密和解密各取约。130毫秒
  2. 一个 ESP32 接收数据包、广播它和另一个 ESP32 接收的平均单向延迟是 ca。15ms
  3. 我知道 UART 本身并不是一种可靠的协议,应该在真实环境中使用帧。尽管如此,我被指示只是假设 UART 是完美的并且不会丢失任何东西。
  4. 由于项目范围较大,不能选择使用 RTOS

代码:

#define LEN_SERVER_FRAMED_PACKET 35
#define LEN_BLE_PACKET           24

volatile bool_t new_server_msg;
volatile bool_t new_ble_msg;

byte_t s_rx_framed_buf[LEN_SERVER_FRAMED_PACKET];   // Receive buffer to be used in all subsequent Server send operations
ble_packet_t ble_rx_struct;        // A struct. The whole struct is then interpreted as uint8_t ptr. when being sent to the ESP32 over UART

在里面:

< set up some stuff>
err = HAL_UART_Receive_IT(&SERVER_UART, s_rx_framed_buf, LEN_SERVER_FRAMED_PACKET);
if (!check_success_hal("Init, setting Server ISR", __LINE__, err)){
    print_string("Init after Signup: Was NOT able to set SERVER_UART ISR");
}else{
    print_string("Init after Signup: Was able to set SERVER_UART ISR");

}
err = HAL_UART_Receive_IT(&BLE_UART, &ble_rx_struct, LEN_BLE_PACKET);
if(!check_success_hal("Init, setting BLE ISR", __LINE__, err)){
    print_string("Init after Signup: Was NOT able to set BLE_UART ISR");
}else{
    print_string("Init after Signup: Was able to set BLE_UART ISR");

}

主循环:

while (1)
{

    // (2) Go over all 3 cases: New local alert, new BLE message and new Server message and handle them accordingly

    // (2.1) Check whether a new local alert has come in
    if (<something irrelevant happens>)
    {
        <do something irrelevant>
    }

    // (2.2) Check for new ble packet. Technically it checks for packets from the UART to the ESP32.
    if (new_ble_msg)
    {
        new_ble_msg = FALSE;
        int ble_rx_type_code = ble_parse_packet(&ble_rx_nonce, &ble_rx_struct);
        HAL_UART_Receive_IT(&BLE_UART, &ble_rx_struct, LEN_BLE_PACKET);                           // Listen for new BLE messages.
        <compute some stuff, rather quick> server_tx_encrypted(<stuff computed>, &c_write, "BLE", __LINE__); // Encrypts <stuff computed> and sends it to the server using a BLOCKING HAL_UART_Transmit(...).
                                                                                                             // Encryption takes ca. 130ms.
    }

    // (2.3) Check for new server packet
    if (new_server_msg)
    {
        new_server_msg = FALSE;                                             // Set flag to false
        memcpy(s_wx_framed_buf, s_rx_framed_buf, LEN_SERVER_FRAMED_PACKET); // Copy from framed receive buffer to framed working buffer.
                                                                            // This is done such that we can process the current message while also being able to receive new messages

        HAL_UART_Receive_IT(&SERVER_UART, s_rx_framed_buf, LEN_SERVER_FRAMED_PACKET); // Listen for new server messages.

        <decrypt it, takes ca.130 - 150ms. results in buffer ble_tx_struct>

            err = HAL_UART_Transmit(&BLE_UART, ble_tx_struct,
                                    LEN_BLE_PACKET, UART_TX_TIMEOUT);
        check_success_hal(err); // If unsuccessful, print that to debug UART
    }

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
}

UART接收回调函数:

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{

    if (huart == &SERVER_UART)
    { // One should technically compate huart -> Instance, but that works aswell...
        new_server_msg = TRUE;
        print_string("UART Callback: Server ISR happened!\r\n"); // Blocking write to debug UART. I know that this is typically considered bad form,
                                                                 // but as the callback function is only called once per receive and because that's the only way of letting me know that the callback has occurred,
                                                                 // I chose to keep the print in.
    }
    else if (huart == &BLE_UART)
    {
        new_ble_msg = TRUE;
        print_string("UART Callback: BLE ISR happened!\r\n");
    }
    else
    {
        print_string("UART Callback: ISR triggered by unknown UART_HandleTypeDef!\r\n");
    }
}

到目前为止我已经尝试过:

我在 Go 中编写了一个客户端实现并在我的计算机上运行它,客户端将直接相互发送 UDP 消息而不是 BLE。由于该版本即使使用许多“设备”也能完美运行,我相信问题出在 STM32 及其 STM32 <-> ESP32 UART 连接上。

为了让它在 3 个设备上工作,我简单地删除了 STM32 的大部分调试语句,并让服务器在发送 X_i 到 D_{i} 和 X_{i + 1} 到 D_{i + 1} 之间等待 250 毫秒。由于这似乎至少使问题变得如此罕见以至于我不再注意到它,我认为核心问题与时间有关。

通过绘制执行跟踪,我已经发现了我的方法的一个固有弱点:如果 STM32 调用HAL_UART_Receive_it(&BLE_UART, ble_rx_buf, LEN_BLE_PACKET)而 ESP32 当前正在向 STM 发送数据包并且已经发送了 k 字节,则 STM32 将只接收LEN_BLE_PACKET - k字节。这会导致BLE_UART.RxXferCountESP32 发送下一个数据包时出现错误。

在更理论的方面,我首先考虑使用 DMA 而不是中断驱动接收。然而,我避免了,因为 STM32 DMA 不像在更强大的系统中那样使用描述符环,而是实际上只是消除了必须接收LEN_BLE_PACKET(resp LEN_SERVER_FRAMED_PACKET) 中断的开销。

我当然也已经检查过stackoverflow,有几个人似乎遇到过类似的问题。例如,UART 接收中断在成功接收数小时后停止触发“Uart dma 接收中断在几分钟后停止接收数据”

问题:

  1. 鉴于我上面所描述的,STM32 的 BLE_UART 回调怎么可能在一段时间后简单地停止触发而没有任何明显的原因?

  2. 我在“到目前为止我已经尝试过什么”的最后一段中提出的问题实际上是问题的原因,这似乎是合理的吗?

  3. 我该如何解决这个问题?


澄清:

服务器向设备 D_i 发送请求后,服务器等待 250ms 后再向 D_{i + 1} 发送下一个请求。因此,D_i 有一个 250ms 的传输窗口,其中没有 D_j 可以传输任何东西。即,当轮到 D_i 广播其 nonce 时,其他设备必须简单地接收一个UART 消息。

由于服务器的接收通常相当快,解密需要 130 毫秒,并且波特率为 115200 的 UART 发送也很快,这个窗口应该足够长。

更新:

发布问题后,我更改了 ESP32,使 BLE 数据包不会立即通过 UART 转发到 STM32。相反,它们被排入队列,ESP32 中的一个专用任务将它们出列,数据包之间的延迟至少为 5ms。因此,STM32现在应该保证每个 BLE 数据包之间有 5ms。这样做是为了减少突发性(尽管由于澄清中提到的原因实际上没有任何突发......我只是绝望)。尽管如此,这似乎使 STM32 在 UART 接收器锁定之前“存活”了更长时间。

标签: cembeddedstm32microcontrolleruart

解决方案


您需要非常小心,尤其是在使用 STM32 HAL 库进行生产时,这些库在从服务器或其他任何地方接收快速且连续的数据时并不可靠。

我将根据我在实现类似应用程序时所做的工作来建议解决此问题的方法。这适用于我的 Firmware-Over-The-Air (FOTA) 项目,并有助于在使用 STM32 HAL 库时消除任何可能的 UART 故障。

步骤如下:

  • 确保通过调用重置 UARTMX_USARTx_UART_Init()
  • HAL_UART_Receive_IT()为或重新配置回调HAL_UART_Receive_DMA()

这两个设置将消除使用 STM32 HAL 接收中断的任何 UART 故障。


推荐阅读