主页 > 软件开发  > 

STM32的DMA解释

STM32的DMA解释
一句话解释:

DMA的特点就是无需CPU的参与就可以直接访问内存(可以直接读取内存的数据,也可以直接传数据给内存)

这个内存一般指的是片内SRAM、片内Flash

我举个例子:

有一个温度传感器,它以较高的频率(例如每秒1000次)采样温度数据,并通过SPI(Serial Peripheral Interface)接口将数据发送到STM32。你需要将这些数据存储到内存中,以便后续进行数据分析或处理。

如果用CPU的话,CPU需要频繁的从SPI接口读取数据并写入内存,会占用大量的CPU时间影响其它任务的执行。另外,CPU在数据搬运上的效率低。

那怎么办呢?

我们可以不通过CPU,让DMA当这个中间人,外设读到什么数据就直接写入到内存。让CPU做别的事情去,不会影响到CPU正常处理。

步入正题

STM32F103有 2 个 DMA 控制器,分别是DMA1和DMA2

DMA1 有 7 个通道、DMA2 有 5个通道

每个通道专门用来管理来自于一个或多个外设对存储器访问的请求

在同一个DMA模块上,多个请求间的优先权可以通过软件设置。

如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优先权。举个例子,通道2优先于通道4

代码设计 定义全局变量 #define SPI_RX_BUFFER_SIZE 1000 // 缓冲区大小,假设每秒采样1000次 uint8_t SPI_RxBuffer[SPI_RX_BUFFER_SIZE]; // 存储接收到的数据 volatile uint8_t DMA_TcFlag = 0; // DMA传输完成标志 初始化结构体 typedef struct { uint32_t DMA_PeripheralBaseAddr; // 外设地址 uint32_t DMA_MemoryBaseAddr; // 存储器地址 uint32_t DMA_DIR; // 传输方向 uint32_t DMA_BufferSize; // 传输数目 uint32_t DMA_PeripheralInc; // 外设地址增量模式 uint32_t DMA_MemoryInc; // 存储器地址增量模式 uint32_t DMA_PeripheralDataSize; // 外设数据宽度 uint32_t DMA_MemoryDataSize; // 存储器数据宽度 uint32_t DMA_Mode; // 模式选择 uint32_t DMA_Priority; // 通道优先级 uint32_t DMA_M2M; // 存储器到存储器模式 } DMA_InitTypeDef; DMA初始化函数 void My_DMA_Init(void) { // 1. 打开DMA1控制器时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE); // 2. 配置DMA2通道3作为SPI1接收 : 外设 -> 内存 DMA_InitTypeDef DMA_Config; DMA_Config.DMA_Channel = DMA_Channel_3; // SPI1_RX DMA_Config.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR; // SPI1数据寄存器地址 DMA_Config.DMA_MemoryBaseAddr = (uint32_t)SPI_RxBuffer; // 内存缓冲区地址 DMA_Config.DMA_DIR = DMA_DIR_PeripheralSRC; // 外设 -> 内存 DMA_Config.DMA_BufferSize = SPI_RX_BUFFER_SIZE; // 缓冲区大小 DMA_Config.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不递增 DMA_Config.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址递增 DMA_Config.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 数据大小为字节 DMA_Config.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_Config.DMA_Mode = DMA_Mode_Normal; // 普通模式 DMA_Config.DMA_Priority = DMA_Priority_High; // 高优先级 DMA_Config.DMA_FIFOMode = DMA_FIFOMode_Disable; // 禁用FIFO DMA_Config.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; DMA_Config.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_Config.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(DMA2_Stream3, &DMA_Config); // 3. 配置DMA2通道3支持DMA_IT_TC中断 DMA_ITConfig(DMA2_Stream3, DMA_IT_TC, ENABLE); // 4. 配置NVIC支持DMA2通道3中断 NVIC_InitTypeDef NVIC_Config; NVIC_Config.NVIC_IRQChannel = DMA2_Stream3_IRQn; // DMA2通道3中断 NVIC_Config.NVIC_IRQChannelPreemptionPriority = 0; NVIC_Config.NVIC_IRQChannelSubPriority = 0; NVIC_Config.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_Config); } 有一些参数可以更改:

SPI初始化函数 void My_SPI_Init(void) { // 1. 打开SPI1控制器时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); // 2. 配置SPI1 SPI_InitTypeDef SPI_Config; SPI_Config.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //表示SPI使用两条线(MOSI和MISO)进行全双工通信 //表示SPI1工作在主模式(Master Mode)。主模式下,SPI1可以主动发送数据并控制时钟线(SCK)。 SPI_Config.SPI_Mode = SPI_Mode_Master; SPI_Config.SPI_DataSize = SPI_DataSize_8b; SPI_Config.SPI_CPOL = SPI_CPOL_High; SPI_Config.SPI_CPHA = SPI_CPHA_2Edge; SPI_Config.SPI_NSS = SPI_NSS_Soft; SPI_Config.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; SPI_Config.SPI_FirstBit = SPI_FirstBit_MSB; SPI_Config.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_Config); // 3. 启用SPI1 SPI_Cmd(SPI1, ENABLE); // 4. 配置SPI1支持DMA接收。 //这使得SPI1可以在接收到数据时自动触发DMA传输,将数据存储到内存。 SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE); } DMA中断处理函数 void DMA2_Stream3_IRQHandler(void) { // 1. 判断是否为传输完成中断 if(DMA_GetITStatus(DMA2_Stream3, DMA_IT_TCIF3) != RESET) { // 2. 清除中断标志 DMA_ClearITPendingBit(DMA2_Stream3, DMA_IT_TCIF3); // 3. 关闭DMA通道 DMA_Cmd(DMA2_Stream3, DISABLE); // 4. 设置传输完成标志 DMA_TcFlag = 1; } } 测试函数 void SPI_DMA_Rx_Test(void) { // 1. 初始化缓冲区。清空接收缓冲区,确保没有残留数据。 memset(SPI_RxBuffer, 0, SPI_RX_BUFFER_SIZE); // 2. 关闭DMA通道 DMA_Cmd(DMA2_Stream3, DISABLE); // 3. 设置DMA传输长度 DMA_SetCurrDataCounter(DMA2_Stream3, SPI_RX_BUFFER_SIZE); // 4. 启动DMA接收 SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE); DMA_Cmd(DMA2_Stream3, ENABLE); // 5. 等待DMA传输完成 while(!DMA_TcFlag) { // 可以在这里执行其他任务 } // 6. 遍历缓冲区,打印接收到的数据 for(int i = 0; i < SPI_RX_BUFFER_SIZE; i++) { printf("Data[%d]: %d\n", i, SPI_RxBuffer[i]); } // 7. 清除标志 DMA_TcFlag = 0; }

标签:

STM32的DMA解释由讯客互联软件开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“STM32的DMA解释