功能背景
我们的辅助功能中有串口 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 ;
}