[OTA]2.OTA串口设计

功能背景

我们的辅助功能中有串口 IAP 功能(串口 In-Application Programming),这项功能用于下载程序。当需要下载大量数据时,单靠 CPU 读取数据已经跟不上速度,因此我们采用 DMA + 空闲中断 的方案来高效搬运数据。

  • DMA(直接存储器访问) 自动地将数据从串口接收缓冲区搬运到内存中的一个数组,不需要 CPU 逐个字节处理。
  • 空闲中断 用于检测当前串口是否暂停了数据接收。当串口没有数据进来时,空闲中断会被触发,我们就在中断里停止 DMA 的搬运,这样就能准确地知道本次搬运的数据结束位置。

数据存储和指针管理

由于 DMA 数据搬运速度非常快,为了保证数据不会丢失,我们在内存中设置了一个一维数组来存放这些数据。为此,我们使用一组指针来管理这块内存区域:

  • SE 指针对(Start/End 指针对)
    • 包含两个指针:
      • Start 指针:指向数据接收的起始位置
      • End 指针:指向数据接收的结束位置
    • 每次 DMA 搬运数据时,都会用这对指针记录该次数据的开始和结束。

 

  • IN 指针
    • 用于记录数据写入的位置。
    • 在初始化时,IN 指针与 OUT 指针都指向 SE 指针对里的第 0 个成员的起始位置([0]S),告诉 DMA 数据一到就存入一维数组的起始位置。
    • 当 DMA 搬运结束并触发空闲中断时,会在中断中设置本次数据的结束位置([0]E),同时更新 IN 指针,让下次数据存到下一个有效区域,比如[1]S开始的位置。

 

  • OUT 指针
    • 用于提取数据。
    • 当 IN 指针和 OUT 指针不重合时,说明缓冲区里有数据可以处理。
    • 系统会通过判断这两个指针来决定是否需要读取数据。

 

  • END 指针
    • 表示 SE 指针对里的最后一个成员,相当于内存区域的边界。
    • 当 IN 或 OUT 指针移动时,必须检查是否达到了 END 指针位置,防止越界。

DMA 单次搬运量与缓冲区管理

为了防止数据搬运过快导致内存数组溢出,我们需要确定 DMA 每次能搬运的最大数据量。比如:

  • 假设一维数组的总空间为 2048 字节,但 DMA 每次最大搬运量设置为 256 字节。
  • 如果数组里剩余的可用空间不足 256 字节,那么就不能再让 DMA 搬运更多数据,而要将缓冲区的写入指针回滚到开始位置重新使用。

为此,我们会用一个变量记录当前已经存储的数据量,以便随时监控剩余空间是否足够。

 

话语多说无益只会越来越迷,我们直接看代码:

#define U0_RX_SIZE  2048 // 一维数组总大小
#define U0_RX_MAX   256  // 单次 DMA 最大搬运量
#define NUM         10   // 缓存区数据块数量

// 定义一个数据块指针结构体,用于表征一块数据的起始和结束位置
typedef struct
{
    uint8_t* start; // 数据开始位置
    uint8_t* end;   // 数据结束位置
} UCB_URxBuffptr;

// 定义一个控制块结构体,用于管理所有数据块及数据的读写
typedef struct
{
    uint16_t URxCounter;                   // 用于累计接收到的数据字节数
    UCB_URxBuffptr URxDataPtr[NUM];          // 存储 NUM 块数据的指针
    UCB_URxBuffptr* URxDataIN;             // 数据写入指针:当前写入位置
    UCB_URxBuffptr* URxDataOUT;            // 数据读取指针:当前读取位置
    UCB_URxBuffptr* URxDataEND;            // 缓存区的结束指针,防止越界
} UCB_CB;
//指针初始化
void PtrInit(void)
{ 
    U0CB.URxDataIN  = &U0CB.URxDataPtr[0];
    U0CB.URxDataOUT = &U0CB.URxDataPtr[0];
    U0CB.URxDataEND = &U0CB.URxDataPtr[NUM-1];
    U0CB.URxDataIN->start = &U0CB.URxDataPtr[0];
    U0CB.URxCounter = 0 ;
    
}