每日更新教程,评论区答疑解惑,小白也能变大神!"
目录
一.直接内存访问(DMA)
1.1 概述
- CW32x030 支持直接内存访问(DMA),无需 CPU 干预,即可实现外设和存储器之间、外设和外设之间、存储器 和存储器之间的高速数据传输。DMA 控制器内部的优先级仲裁器,可实现 DMA 和 CPU 对外设总线控制权的仲裁, 以及多 DMA 通道之间的调度执行。
1.2 主要特性
- 5 条独立 DMA 通道
- 3 种数据传输宽度:8bit、16bit、32bit
- 4 种传输模式:软件 BLOCK、软件 BULK、硬件 BLOCK、硬件 BULK
- 源地址及目的地址类型:外设、存储器
- 地址增量方式:固定、自增 ● 待传输数据块数量:1 ~ 65535
- 寻址范围:0x0000 0000 ~ 0xFFFF FFFF
1.3 功能框图
- DMA 控制器的 AHB 总线通过总线矩阵和外设的 AHB 总线相连,实现 DMA 与外设间的数据互联,包括 FLASH、 SRAM、CRC、GPIO 以及各 APB 设备等。DMA 控制器的功能框图如下图所示:
- DMA 控制器
- 5 条独立 DMA 通道,通道优先级和通道号绑定,通道号越小优先级越高,通道号越大优先级越低。
- Bus Matrix 总线矩阵
- CPU 系统总线和 DMA 总线接口均连接到总线矩阵上,通过总线矩阵切换和外设的连接。当 CPU 与 DMA 访 问不同的 AHB 总线设备或 AHB 到 APB 桥时,数据传输可以同时进行;当 CPU 和 DMA 同时访问同一个 AHB 设备(AHB 到 APB 桥接器下不同设备也被认为是同一 AHB 设备)时,CPU 的优先级高于 DMA。
- AHB to APB 桥接器
- DMA 可以通过总线矩阵及 AHB 到 APB 桥接器实现对 APB 设备的访问。同一个 AHB 到 APB 桥接器下的所有 APB 设备共享同一 AHB 总线,因此被看作是同一个 AHB 设备。当 CPU 和 DMA 同时访问同一 AHB 到 APB 桥接器下的 APB 设备(无论是访问同一个 APB 设备还是不同的 APB 设备)时,CPU 的优先级高于 DMA。
1.4 DMA 传输模式
- 支持 4 种传输模式:软件触发 BLOCK 传输模式,软件触发 BULK 传输模式,硬件触发 BLOCK 传输模式, 硬件触发 BULK 传输模式。
- 软件和硬件触发 部分外设支持硬件 DMA,当它们被配置为 DMA 通道的触发源时,可以产生 DMA 请求(DMA request),硬 件触发启动 DMA 传输,无需 CPU 参与。不支持硬件 DMA 的外设,只能配置为软件触发启动 DMA 传输。 触发方式由 DMA 触发源控制寄存器 DMA_TRIGy(y 是 DMA 通道号,y=1~5,下同)的 TYPE 位域来控制, TYPE 为 0 为软件触发方式,TYPE 为 1 则为硬件触发方式。
- BLOCK 和 BULK 传输模式 BLOCK 和 BULK 传输模式的区别是,在传输过程中是否允许被更高优先级的传输请求(高优先级 DMA 或 CPU)打断。DMA 控制及状态寄存器 DMA_CSRy 的 TRANS 位域为 1 为 BLOCK 传输模式,为 0 则为 BULK 传输模式。 BULK 传输模式时,会一次性完成所有数据的传输。 数据传输过程中不会插入传输间隙,传输过程也不会被 CPU 或者其它高优先级 DMA 通道传输请求打断。 BLOCK 传输模式时,每传输完成 1 个数据块后会插入一个传输间隙,在该传输间隙内,由仲裁器进行传输 优先级仲裁。如果有 CPU 或更高优先级的 DMA 通道要访问当前 DMA 通道所占用的外设,仲裁器会释放当 前 DMA 通道对该设备的控制权,让 CPU 或者更高优先级的 DMA 通道优先访问,等完成后再继续之前被打 断的 DMA 通道传输。
- DMA 传输模式,如下表所示:
1.5 DMA 传输过程
- 一次完整的 DMA 传输过程,用户需做以下设置:
- 步骤 1:设置传输模式,DMA_CSRy.TRANS 设置 1 为 BLOCK 传输模式,设置 0 则为 BULK 传输模式;
- 步骤 2:设置触发方式,DMA_TRIGy.TYPE 设置 0 为软件触发,设置 1 则为硬件触发方式;
- 步骤 3:配置 DMA 通道数据传输位宽,DMA_CSRy.SIZE,选择 8、16、32bit;
- 步骤 4:配置 DMA 通道传输数据块数量,DMA_CNTy.CNT,有效范围 1 ~ 65535;
- 步骤 5:配置 DMA 通道传输数据块大小,DMA_CNTy.REPEAT,请写入 1;
- 步骤 6:配置 DMA 传输源地址,DMA_SRCADDRy;
- 步骤 7:配置 DMA 传输目的地址,DMA_DSTADDRy;
- 步骤 8:配置 DMA 源地址增量方式,DMA_CSRy.SRCINC,固定或自动增加;
- 步骤 9:配置 DMA 目的地址增量方式,DMA_CSRy.DSTINC,固定或自动增加;
- 步骤 10:设置 DMA_CSRy.EN 为 1,使能对应的 DMA 通道;
- 步骤 11:对于软件触发 DMA 传输,设置 DMA_TRIGy.SOFTSRC 启动 DMA 传输,对于硬件触发 DMA 传输不需设置, 而只需正确设置用于启动 DMA 的外设。
1.6 软件触发 BLOCK 传输模式
- 设置 DMA_TRIGy.SOFTSRC 为 1,将立即触发 DMA 通道进行数据传输。
- DMA 通道每传输完成 1 个数据块,DMA 插入一个传输间隙,仲裁器在该传输间隙内进行传输优先级仲裁。如果 在该传输间隙内,有 CPU 或更高优先级的 DMA 请求要访问当前 DMA 通道所占用的 AHB 设备,仲裁器会释放当 前 DMA 通道对该设备的控制权,让 CPU 或者更高优先级的 DMA 通道优先访问;当 CPU 或更高优先级的 DMA 通道释放设备访问权后,本 DMA 通道未完成的数据传输将继续进行。
- 传输完成后 DMA_TRIGy.SOFTSRC 自动清零。
- 软件触发 BLOCK 传输模式,传输 3 个数据位宽为 16bit 的数据(DMA_CNTy.REPEAT = 1,DMA_CNTy.CNT = 3), 其中,SA 代表源地址,DA 代表目标地址,源地址和目的地址均为地址自增模式,传输过程如下图所示:
1.7软件触发 BULK 传输模式
- 设置 DMA_TRIGy.SOFTSRC 为 1,将立即触发 DMA 进行数据传输,DMA 通道传输完 DMA_CNTy.CNT 个数据块 后停止传输并释放外设占用权,传输完成后 DMA_TRIGy.SOFTSRC 自动清零。
- 软件触发 BULK 传输模式没有传输间隙,在 DMA_CNTy.CNT 个数据块传输完成之前,该 DMA 通道将一直占用外 设,CPU 或更高优先级的 DMA 通道只能等待该 DMA 传输完成后才能访问该外设。 在用户应用中,建议每次不要传输太多的数据,否则将可能造成 CPU 在一段时间内完全不能访问被该 DMA 通道 占用的外设。
- 软件触发 BULK 传输模式,传输 3 个数据位宽为 16bit 的数据(DMA_CNTy.REPEAT = 1,DMA_CNTy.CNT = 3), 其中,SA 代表源地址,DA 代表目标地址,源地址和目的地址均为地址自增模式,传输过程如下图所示:
1.7硬件触发 BLOCK 传输模式
- DMA 通道对应的外设产生硬件触发请求信号时,DMA 会传输 1 个数据块,然后等待下一次硬件触发请求信号, 直到 DMA 控制器中配置的数据量全部传输完毕。DMA 通道支持的硬件触发请求信号,请参考触发源控制寄存器 DMA_TRIGy 的 HARDSRC 位域。
二.寄存器列表
三.案例-通过软件触发DMA方式实现内存到内存的DMA传输
预处理器定义与全局变量
#define BUFFER_SIZE 32U // 定义缓冲区大小为32个32位无符号整数
uint32_t aSRC_Const_Buffer[BUFFER_SIZE] = // 源数据缓冲区(常量数据)
{
0x01020304, 0x05060708, 0x090A0B0C, 0x0D0E0F10,
0x11121314, 0x15161718, 0x191A1B1C, 0x1D1E1F20,
0x21222324, 0x25262728, 0x292A2B2C, 0x2D2E2F30,
0x31323334, 0x35363738, 0x393A3B3C, 0x3D3E3F40,
0x41424344, 0x45464748, 0x494A4B4C, 0x4D4E4F50,
0x51525354, 0x55565758, 0x595A5B5C, 0x5D5E5F60,
0x61626364, 0x65666768, 0x696A6B6C, 0x6D6E6F70,
0x71727374, 0x75767778, 0x797A7B7C, 0x7D7E7F80
};
uint32_t aDST_Buffer[BUFFER_SIZE]; // 目标数据缓冲区(用于DMA传输)
uint8_t TransOverFlag; // DMA传输完成标志位
内存操作函数
/**
* @brief 比较两段内存内容
* @param mem1 内存区域1指针
* @param mem2 内存区域2指针
* @param bytesize 比较的字节数
* @return 0表示相同,-1表示不同或参数错误
*/
int32_t myMemcmp(uint8_t *mem1, uint8_t *mem2, uint32_t bytesize)
{
int i = 0;
uint8_t *p = mem1;
uint8_t *q = mem2;
if (p == NULL || q == NULL) // 空指针检查
{
return -1;
}
for (i = 0; i < bytesize; i++, p++, q++) // 逐字节比较
{
if (*p != *q)
{
return -1;
}
}
return 0;
}
/**
* @brief 清零指定内存区域
* @param buf 待清零内存指针
* @param size 清零的字节数
*/
void ZeroMemory(uint8_t *buf, uint32_t size)
{
uint32_t i = 0;
for (i = 0; i < size; i ++)
{
*buf = 0x0;
}
}
错误处理与DMA中断
/**
* @brief 错误处理函数(死循环)
*/
void Error_Handle()
{
while (1); // 进入死循环表示错误状态
}
/**
* @brief DMA通道1中断服务函数
* 处理传输完成和传输错误中断
*/
void DMA_CHANNEL1_IRQ_FUNCTION(void)
{
if (DMA_GetITStatus(DMA_IT_TC1)) // 传输完成中断
{
DMA_ClearITPendingBit(DMA_IT_TC1); // 清除中断标志
TransOverFlag = 1; // 设置传输完成标志
}
if (DMA_GetITStatus(DMA_IT_TE1)) // 传输错误中断
{
DMA_ClearITPendingBit(DMA_IT_TE1); // 清除中断标志
Error_Handle(); // 进入错误处理
}
}
NVIC配置
/**
* @brief NVIC嵌套向量中断控制器配置
* 配置DMA通道1的中断
*/
void NVIC_Configuration(void)
{
__disable_irq(); // 全局禁用中断
NVIC_ClearPendingIRQ(DMACH1_IRQn); // 清除DMA通道1中断挂起位
NVIC_EnableIRQ(DMACH1_IRQn); // 使能DMA通道1中断
__enable_irq(); // 全局启用中断
}
主函数
/**
* @brief 主函数
* 初始化硬件并启动DMA传输
* @return 程序返回值(未使用)
*/
int32_t main(void)
{
DMA_InitTypeDef DMA_InitStruct;
// 启用DMA和GPIOB的AHB总线时钟
RCC_AHBPeriphClk_Enable(RCC_AHB_PERIPH_DMA | RCC_AHB_PERIPH_GPIOB, ENABLE);
// 配置GPIOB的8/9引脚为推挽输出
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pins = GPIO_PIN_8 | GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_Init(CW_GPIOB, &GPIO_InitStruct);
PB08_SETLOW();
PB09_SETLOW();
TransOverFlag = 0; // 初始化传输完成标志
DMA_StructInit(&DMA_InitStruct); // DMA初始化结构体默认值
ZeroMemory((uint8_t *)&aDST_Buffer[0], sizeof(aDST_Buffer)); // 清空目标缓冲区
// 配置DMA参数
DMA_InitStruct.DMA_Mode = DMA_MODE_BLOCK; // 块传输模式
DMA_InitStruct.DMA_TransferWidth = DMA_TRANSFER_WIDTH_32BIT; // 32位传输宽度
DMA_InitStruct.DMA_SrcInc = DMA_SrcAddress_Increase; // 源地址递增
DMA_InitStruct.DMA_DstInc = DMA_DstAddress_Increase; // 目标地址递增
DMA_InitStruct.DMA_TransferCnt = BUFFER_SIZE; // 传输数据量
DMA_InitStruct.DMA_SrcAddress = (uint32_t)&aSRC_Const_Buffer[0]; // 源地址
DMA_InitStruct.DMA_DstAddress = (uint32_t)&aDST_Buffer[0]; // 目标地址
DMA_InitStruct.TrigMode = DMA_SWTrig; // 软件触发模式
DMA_Init(CW_DMACHANNEL1, &DMA_InitStruct); // 初始化DMA通道1
DMA_ClearITPendingBit(DMA_IT_ALL); // 清除所有DMA中断标志
DMA_ITConfig(CW_DMACHANNEL1, DMA_IT_TC | DMA_IT_TE, ENABLE); // 使能传输完成和错误中断
NVIC_Configuration(); // 配置NVIC中断
DMA_Cmd(CW_DMACHANNEL1, ENABLE); // 使能DMA通道1
DMA_SWTrigCmd(CW_DMACHANNEL1); // 软件触发DMA传输
// 主循环
while (1)
{
if (TransOverFlag == 1) // 传输完成
{
TransOverFlag = 0;
// 比较源和目标缓冲区内容
if (myMemcmp((uint8_t *)&aDST_Buffer[0], (uint8_t *)&aSRC_Const_Buffer[0], BUFFER_SIZE) == -1)
{
Error_Handle(); // 内容不一致则进入错误处理
}
else // 传输验证成功
{
while (1) // 进入指示成功的闪烁循环
{
PB08_TOG(); // 切换PB8状态
PB09_TOG(); // 切换PB9状态
FirmwareDelay(1000000); // 延时
}
}
}
else // 传输未完成时的等待状态
{
PB08_TOG(); // 切换PB8状态
PB09_TOG(); // 切换PB9状态
FirmwareDelay(100000); // 较短延时
}
}
}