STM32-01-认识单片机
STM32-02-基础知识
STM32-03-HAL库
STM32-04-时钟树
STM32-05-SYSTEM文件夹
STM32-06-GPIO
STM32-07-外部中断
STM32-08-串口
STM32-09-IWDG和WWDG
STM32-10-定时器
STM32-11-电容触摸按键
STM32-12-OLED模块
STM32-13-MPU
STM32-14-FSMC_LCD
文章目录
- STM32-15-DMA
- 1. DMA与中断的区别
- 1. DMA
- 2. 中断
- 2. DMA介绍
- 3. DMA结构框图
- 1. DMA框图
- 2. DMA处理过程
- 3. DMA通道
- 4. DMA相关寄存器
- 5. DMA相关HAL库驱动
- 6. 代码实现
STM32-15-DMA
1. DMA与中断的区别
**DMA
(Direct Memory Access
,直接内存访问)**和中断是两种不同的机制,用于管理计算机系统中外围设备与处理器之间的数据传输和处理。
1. DMA
工作原理:
- 独立传输:DMA允许外设直接与系统内存交换数据,而不需要通过处理器(CPU)。当需要大量数据传输时,DMA控制器接管传输任务,释放CPU去执行其他任务。
- 传输过程:DMA传输数据时,CPU启动DMA传输,然后DMA控制器接管整个传输过程。传输完成后,DMA控制器通过中断通知CPU传输完成。
作用:
- 提高效率:DMA减少了CPU在数据传输过程中的参与,使得CPU能够执行其他任务,从而提高系统的整体效率。
- 减少延迟:由于DMA可以独立进行传输,因此数据传输的延迟更低,特别是在处理大块数据时。
- 应用场景:DMA广泛应用于音频、视频数据流、网络数据包的传输、存储设备的数据读写等场景。
对程序的影响:
- 复杂度增加:引入DMA需要对DMA控制器进行配置,可能增加程序的复杂性。
- 同步问题:在DMA传输过程中,程序需要处理好数据同步问题,避免数据不一致性问题。
2. 中断
工作原理:
- 中断触发:中断是外设通过中断信号通知CPU某个事件发生,如输入设备有新数据可读取,定时器到期等。
- 中断处理:CPU响应中断后,暂停当前任务,跳转到相应的中断处理程序(ISR)执行。当中断处理程序执行完毕后,CPU恢复先前任务的执行。
作用:
- 实时响应:中断机制使CPU能够实时响应外设事件,保证系统对外部事件的快速反应。
- 事件驱动:中断使得程序可以基于事件驱动,而不是定期轮询外设状态,从而节省CPU资源。
- 应用场景:键盘输入、鼠标移动、网络数据包到达、定时器事件等。
对程序的影响:
- 中断处理程序设计:中断处理程序需要尽可能简短、快速,避免长时间占用CPU。
- 中断优先级管理:系统中可能有多个中断源,需要合理设计中断优先级,以确保关键中断能够及时响应。
- 上下文切换开销:中断会引起上下文切换,带来一定的性能开销。
总结
- DMA:适用于大量数据传输,降低CPU负载,提高系统效率。
- 中断:适用于实时事件响应,保证系统对外部事件的快速处理。
两者结合使用,可以构建高效、实时的嵌入式系统。例如,DMA用于大数据块的传输,而中断用于触发DMA传输和处理传输完成事件。
2. DMA介绍
- **DMA:**即直接存储器访问。DMA传输方式无需CPU直接控制传输,也没有中断处理方式那样保留现场和恢复现场的过程,通过硬件为RAM与I/O设备开辟一条直接传送数据的通路,能使CPU的效率大为提高。
- STM32F103内部有2个DMA控制器,
DMA1
有7
个通道,DMA2
有5
个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求,还有一个仲裁器来协调各个DMA请求的优先权。 - 特性:
- 每个通道都直接连接专用的硬件DMA请求,每个通道都支持软件触发。这些功能通过软件来配置。
- 在七个请求间的优先权可以通过软件编程设置,当软件相同时,由硬件决定。
- 独立的源和目标数据区的传输宽度,模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。
- 支持循环的缓冲器管理。
- 每个通道都有3个事件标志,这3个事件标志逻辑或成为一个单独的中断请求。
- 存储器和存储器间的传输。
- 外设和存储器,存储器和外设的传输。
- 闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标。
- 可编程的数据传输数目最大为65535。
3. DMA结构框图
1. DMA框图
-
①DMA请求
如果外设想要通过DMA来传输数据,必须先给DMA控制器发送DMA请求,DMA收到请求信号之后,控制器会给外设一个应答信号,当外设应答后且DMA控制器收到应答信号之后,就会启动DMA的传输,直到传输完毕。
-
②DMA通道
DMA具有12个独立可编程的通道,其中DMA1有7个通道,DMA2有5个通道,每个通道对应不同的外设的DMA请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。
-
③DMA优先级
当发生多个DMA通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器管理。仲裁器管理DMA通道请求分为两个阶段。第一阶段属于软件阶段,可以在
DMA_CCRx
寄存器中设置,有4个等级:非常高,高,中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的DMA通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道0高于通道1。在大容量产品和互联型产品中,DMA1控制器拥有高于DMA2控制器的优先级。
2. DMA处理过程
DMA(Direct Memory Access,直接内存访问)是一种允许外设直接与系统内存进行数据传输的机制,不需要CPU的直接干预。以下是DMA处理过程的详细描述:
DMA处理过程
- 配置DMA控制器:
- 源地址:设置数据传输的源地址(可以是外设寄存器地址或内存地址)。
- 目标地址:设置数据传输的目标地址(可以是外设寄存器地址或内存地址)。
- 传输方向:指定数据传输的方向,是从外设到内存,还是从内存到外设。
- 数据长度:设置需要传输的数据长度(字节数)。
- 传输模式:选择传输模式,可以是单次传输、块传输或连续传输模式。
- 启动DMA传输:
- 触发传输:在配置完成后,通过设置DMA控制器的启动位,开始数据传输。
- 数据搬运:DMA控制器接管数据传输任务,将数据从源地址搬运到目标地址。这个过程中,DMA控制器直接与内存控制器和外设总线进行交互,不需要CPU干预。
- 中断通知:
- 传输完成中断:数据传输完成后,DMA控制器产生中断信号,通知CPU传输已经完成。CPU执行相应的中断服务程序(ISR)处理后续任务。
- 错误处理:如果在传输过程中发生错误(如总线错误),DMA控制器也会产生中断,通知CPU进行错误处理。
3. DMA通道
DMA1通道与外设的对应关系
DMA2通道与外设的对应关系
4. DMA相关寄存器
寄存器 | 名称 | 作用 |
---|---|---|
DMA_CCRx | DMA通道x配置寄存器 | 用于配置DMA(核心控制寄存器) |
DMA_ISR | DMA中断状态寄存器 | 用于查询当前DMA传输状态 |
DMA_IFCR | DMA中断标志清除寄存器 | 用来清除DMA_ISR对应位 |
DMA_CNDTRx | DMA通道x传输数量寄存器 | 用于控制DMA通道x每次传输的数据量 |
DMA_CPARx | DMA通道x外设地址寄存器 | 用于存储STM32外设地址 |
DMA_CMARx | DMA通道x存储器地址寄存器 | 用于存放存储器的地址 |
USART_CR3 | USART控制寄存器3 | 用于使能串口DMA发送 |
-
DMA通道x配置寄存器(DMA_CCRx)
-
DMA中断状态寄存器(DMA_ISR)
-
DMA 中断标志清除寄存器(DMA_IFCR)
-
DMA通道x传输数量寄存器(DMA_CNDTRx)
该寄存器控制着DMA通道x的每次传输所要传输的数据量。其设置范围为
0~65535
。并且该寄存器的值随着传输的进行而减少,当该寄存器的值为0
的时候就代表此次数据传输己经全部发送完成。可以通过这个寄存器的值来获取当前DMA传输的进度。 -
DMA通道x外设地址寄存器(DMA_CPARx)
用来存储 STM32 外设的地址。
-
DMA通道x存储器地址寄存器(DMA_CMARx)
用来存放存储器的地址。
5. DMA相关HAL库驱动
-
使能DMA时钟
__HAL_RCC_DMA1_CLK_ENABLE();
-
初始化DMA
HAL_StatusTypeDef HAL_DMA_Init(DMA_HandleTypeDef *hdma); typedef struct { uint32_t Direction; /* 传输方向,例如存储器到外设 DMA_MEMORY_TO_PERIPH */ uint32_t PeriphInc; /* 外设(非)增量模式,非增量模式 DMA_PINC_DISABLE */ uint32_t MemInc; /* 存储器(非)增量模式,增量模式 DMA_MINC_ENABLE */ uint32_t PeriphDataAlignment; /* 外设数据大小:8/16/32 位 */ uint32_t MemDataAlignment; /* 存储器数据大小:8/16/32 位 */ uint32_t Mode; /* 模式:循环模式/普通模式 */ uint32_t Priority; /* DMA 优先级:低/中/高/非常高 */ }DMA_InitTypeDef; __HAL_LINKDMA(&g_uart1_handler, hdmatx, g_dma_handle);
-
使能串口的DMA发送,启动传输
HAL_UART_Transmit_DMA() HAL_StatusTypeDef HAL_UART_DMAStop(UART_HandleTypeDef *huart); /* 停止 */ HAL_StatusTypeDef HAL_UART_DMAPause(UART_HandleTypeDef *huart); /* 暂停 */ HAL_StatusTypeDef HAL_UART_DMAResume(UART_HandleTypeDef *huart); /* 恢复 */
-
查询DMA传输状态
//查询DMA传输通道的状态 __HAL_DMA_GET_FLAG(&g_dma_handle, DMA_FLAG_TC4); //获取当前传输剩余数据量 __HAL_DMA_GET_COUNTER(&g_dma_handle); //设置对应的DMA数据流传输的数据量大小 __HAL_DMA_SET_COUNTER (&g_dma_handle, 1000);
-
DMA中断使用
//通用中断处理函数 void HAL_DMA_IRQHandler(); //相关中断回调函数 //发送完成回调函数 void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart); //发送一半回调函数 void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef *huart); //接收完成回调函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart); //接收一半回调函数 void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart); //传输出错回调函数 void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);
6. 代码实现
-
功能
每按下按键KEY0,串口1就会以DMA方式发送数据,同时在LCD上面显示传送进度。打开串口调试助手,可以收到DMA发送的内容。LED0闪烁用于提示程序正在运行。
-
DMA初始化函数
void dma_init(DMA_Channel_TypeDef* DMAx_CHx) { if((uint32_t)DMAx_CHx > (uint32_t)DMA1_Channel7) { __HAL_RCC_DMA2_CLK_ENABLE(); } else { __HAL_RCC_DMA2_CLK_ENABLE(); } //将DMA与USART1连接起来 __HAL_LINKDMA(&g_uart1_handle, hdmatx, g_dma_handle); g_dma_handle.Instance = DMAx_CHx; //USART1_TX使用的DMA通道为DMA_Channel4 g_dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH; //模式选择为从存储器到外设 g_dma_handle.Init.PeriphInc = DMA_PINC_DISABLE; //外设非增量模式 g_dma_handle.Init.MemInc = DMA_MINC_ENABLE; //存储器增量模式 g_dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; //外设数据长度为8位 g_dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; //存储器数据长度为8位 g_dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM; //中等优先级 HAL_DMA_Init(&g_dma_handle); }
-
主函数
int main(void) { uint16_t i, k; uint16_t len; uint8_t mask = 0; float pro = 0; /* 进度 */ HAL_Init(); /* 初始化HAL库 */ sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */ delay_init(72); /* 延时初始化 */ usart_init(115200); /* 串口初始化为115200 */ led_init(); /* 初始化LED */ lcd_init(); /* 初始化LCD */ key_init(); /* 初始化按键 */ dma_init(DMA1_Channel4); /* 初始化串口1 TX DMA */ lcd_show_string(30, 50, 200, 16, 16, "STM32", RED); lcd_show_string(30, 70, 200, 16, 16, "DMA TEST", RED); lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED); lcd_show_string(30, 110, 200, 16, 16, "KEY0:Start", RED); len = sizeof(TEXT_TO_SEND); k = 0; for (i = 0; i < SEND_BUF_SIZE; i++) /* 填充ASCII字符集数据 */ { if (k >= len) /* 入换行符 */ { if (mask) { g_sendbuf[i] = 0x0a; k = 0; } else { g_sendbuf[i] = 0x0d; mask++; } } else /* 复制TEXT_TO_SEND语句 */ { mask = 0; g_sendbuf[i] = TEXT_TO_SEND[k]; k++; } } i = 0; while (1) { if (key_scan(0) == 1) /* KEY0按下 */ { printf("\r\nDMA DATA:\r\n"); lcd_show_string(30, 130, 200, 16, 16, "Start Transimit....", BLUE); lcd_show_string(30, 150, 200, 16, 16, " %", BLUE); /* 显示百分号 */ HAL_UART_Transmit_DMA(&g_uart1_handle, g_sendbuf, SEND_BUF_SIZE); /* 等待DMA传输完成,此时我们来做另外一些事情,比如点灯 * 实际应用中,传输数据期间,可以执行另外的任务 */ while (1) { if ( __HAL_DMA_GET_FLAG(&g_dma_handle, DMA_FLAG_TC4)) /* 等待 DMA1_Channel4 传输完成 */ { __HAL_DMA_CLEAR_FLAG(&g_dma_handle, DMA_FLAG_TC4); HAL_UART_DMAStop(&g_uart1_handle); /* 传输完成以后关闭串口DMA */ break; } pro = DMA1_Channel4->CNDTR; /* 得到当前还剩余多少个数据 */ len = SEND_BUF_SIZE; /* 总长度 */ pro = 1 - (pro / len); /* 得到百分比 */ pro *= 100; /* 扩大100倍 */ lcd_show_num(30, 150, pro, 3, 16, BLUE); } lcd_show_num(30, 150, 100, 3, 16, BLUE); /* 显示100% */ lcd_show_string(30, 130, 200, 16, 16, "Transimit Finished!", BLUE); /* 提示传送完成 */ } i++; delay_ms(10); if (i == 20) { LED0_TOGGLE(); /* LED0闪烁,提示系统正在运行 */ i = 0; } } }
-
实验结果