RT1060--串口空闲中断&EDMA接收不定长数据

RT1060--串口空闲中断&EDMA接收不定长数据

串口空闲中断 & EDMA 接收不定长数据

客户有接收不定长数据的需求,但是如果采用协议适配的方法会非常繁琐。使用UART的IDLE中断+DMA的方式,即可高效的解决这一问题!

另外,如果通过协议先接收单独的单字节,串口进中断接收在数据量大的时候效率会低很多

以RT1060EVK为例来进行这一操作!

代码部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
让我仔细分析一下这个main函数的执行流程和中断调用关系...

首先看到几个关键的中断和回调:
1. DEMO_LPUART_IRQHandler - UART的中断处理函数
2. LPUART_UserCallback - DMA传输完成的回调函数

中断触发的场景:
1. IDLE线检测 - 接收到一帧数据后的空闲状态
2. TC(传输完成)中断 - 发送完成后触发
3. DMA传输完成中断 - 通过callback通知

数据流转过程:
1. 初始状态是接收模式
2. 数据到来 -> DMA自动接收到buffer
3. 检测到空闲 -> IDLE中断
4. 处理数据 -> 准备发送
5. DMA发送 -> 发送完成触发TC中断
6. TC中断中切换回接收模式

这里面有很多细节需要梳理,让我详细分析每个阶段的处理...

让我详细解释main函数的逻辑和中断调用关系:

初始化配置:

1
2
3
4
5
6
7
8
9
10
// 配置UART中断
LPUART_EnableInterrupts(DEMO_LPUART,
kLPUART_IdleLineInterruptEnable| // 空闲线检测中断
kLPUART_RxOverrunInterruptEnable| // 接收溢出中断
kLPUART_NoiseErrorInterruptEnable| // 噪声错误中断
kLPUART_FramingErrorInterruptEnable| // 帧错误中断
kLPUART_ParityErrorInterruptEnable); // 奇偶校验错误中断

// 初始时禁用TC中断
LPUART_DisableInterrupts(DEMO_LPUART, kLPUART_TransmissionCompleteInterruptEnable);

中断和DMA的调用流程:

接收数据流程:

  • DMA持续接收数据到g_rxBuffer
  • 当检测到空闲线(一段时间没有新数据)时,触发IDLE中断;
  • 在IDLE中断处理函数中:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    void DEMO_LPUART_IRQHandler(void)
    {
    if ((kLPUART_IdleLineFlag)&stat)
    {
    // 1. 获取已接收的数据长度
    LPUART_TransferGetReceiveCountEDMA(...);

    // 2. 复制数据到发送缓冲区
    memcpy(g_txBuffer, g_rxBuffer, rx_count);

    // 3. 设置发送标志
    reciveFrame = true;

    // 4. 重新启动接收
    LPUART_TransferAbortReceiveEDMA(...);
    LPUART_ReceiveEDMA(...);
    }
    }

发送数据流程:

  • main循环检测到reciveFrame为true时启动发送
  • DMA开始发送数据
  • 发送完成后触发DMA完成中断,调用LPUART_UserCallback
  • 在callback中使能TC中断:
    1
    2
    3
    4
    5
    6
    7
    8
    void LPUART_UserCallback(...)
    {
    if (kStatus_LPUART_TxIdle == status)
    {
    // 使能TC中断
    LPUART_EnableInterrupts(DEMO_LPUART, kLPUART_TransmissionCompleteInterruptEnable);
    }
    }
  • TC中断触发后,在中断处理函数中:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    if((kLPUART_TransmissionCompleteFlag)&stat)
    {
    // 1. 禁用TC中断
    LPUART_DisableInterrupts(DEMO_LPUART, kLPUART_TransmissionCompleteInterruptEnable);

    // 2. 切换到接收模式
    Set_RS485_Receive();

    // 3. 重新启动接收
    LPUART_TransferAbortReceiveEDMA(...);
    LPUART_ReceiveEDMA(...);
    }

主循环逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
while(1)
{
if(reciveFrame) // 检查是否收到完整数据帧
{
// 1. 准备发送数据
sendXfer.data = g_txBuffer;
sendXfer.dataSize = send_size;

// 2. 切换到发送模式
Set_RS485_Transimit();

// 3. 启动DMA发送
LPUART_SendEDMA(DEMO_LPUART, &g_lpuartEdmaHandle, &sendXfer);

// 4. 清除标志
reciveFrame = false;
}
}

关键点说明

  1. DMA用于实际的数据传输,不占用CPU
  2. IDLE中断用于检测一帧数据的接收完成
  3. TC中断用于确保最后一个字节完全发送出去
  4. 使用回调和中断机制实现自动的收发切换
  5. 主循环只负责检测标志并启动新的发送

这种设计实现了高效的非阻塞式数据收发,同时正确处理了RS485半双工通信的方向切换时序。

备注:详细的回调/中断实现的自动收发切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
flowchart TD
A[初始状态: 接收模式] -->|DMA接收数据| B[接收数据]

B -->|检测到IDLE| C[IDLE中断处理]
C -->|1. 获取接收数据长度<br>2. 复制数据到TX buffer<br>3. 设置reciveFrame标志| D[主循环]

D -->|检测到reciveFrame| E[开始发送]
E -->|1. 切换RS485方向<br>2. 启动DMA发送| F[DMA发送数据]

F -->|DMA发送完成| G[DMA完成回调<br>LPUART_UserCallback]
G -->|使能TC中断| H[等待最后一个字节发送]

H -->|TC中断触发| I[TC中断处理]
I -->|1. 禁用TC中断<br>2. 切换RS485方向<br>3. 重启DMA接收| A

让我详细解释每个阶段的切换过程:

  1. 接收阶段:

    1
    2
    3
    // 初始配置为接收模式
    Set_RS485_Receive(); // GPIO设为低电平
    LPUART_ReceiveEDMA(...); // 启动DMA接收
  2. 接收完成检测:

    1
    2
    3
    4
    5
    6
    7
    8
    // IDLE中断处理函数中
    if ((kLPUART_IdleLineFlag)&stat)
    {
    // 获取接收到的数据
    LPUART_TransferGetReceiveCountEDMA(...);
    memcpy(g_txBuffer, g_rxBuffer, rx_count);
    reciveFrame = true; // 设置标志通知主循环
    }
  3. 切换到发送模式:

    1
    2
    3
    4
    5
    6
    7
    // 主循环中
    if(reciveFrame)
    {
    Set_RS485_Transimit(); // GPIO设为高电平
    LPUART_SendEDMA(...); // 启动DMA发送
    reciveFrame = false;
    }
  4. 发送完成处理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // DMA发送完成回调
    void LPUART_UserCallback(...)
    {
    if (kStatus_LPUART_TxIdle == status)
    {
    // 使能TC中断,等待最后一个字节发送完成
    LPUART_EnableInterrupts(DEMO_LPUART, kLPUART_TransmissionCompleteInterruptEnable);
    }
    }

    // TC中断处理
    if((kLPUART_TransmissionCompleteFlag)&stat)
    {
    LPUART_DisableInterrupts(DEMO_LPUART, kLPUART_TransmissionCompleteInterruptEnable);
    Set_RS485_Receive(); // 切换回接收模式
    // 重新启动接收
    LPUART_TransferAbortReceiveEDMA(...);
    LPUART_ReceiveEDMA(...);
    }

关键设计考虑:

  1. 为什么需要TC中断?

    • DMA完成不代表最后一个字节已经完全发送出去
    • TC中断确保最后一个字节完全发送后才切换方向
    • 避免方向切换过早导致数据截断
  2. 方向切换时序:

    • 发送前:先切换方向,再开始发送
    • 发送后:等待TC中断确认完全发送后,再切换回接收
  3. 自动化处理:

    • 使用中断和回调机制,无需主循环干预
    • 主循环只负责启动新的发送
    • 其他切换过程由中断自动完成

这种设计保证了RS485半双工通信的可靠性,同时通过DMA和中断机制提高了效率。

作者

Gavin

发布于

2024-12-04

更新于

2024-12-04

许可协议

CC BY-NC-SA 4.0

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×