尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

九、DMA

九、DMA 1. DMA介绍1.1 什么是DMA令人头秃的描述DMADirect Memory Access直接存储器访问提供在外设与内存、存储器和存储器之间的高速数据传输使用。它允许不同速度的硬件装置来沟通而不需要依赖于CPU在这个时间中CPU对于内存的工作来说就无法使用。简单描述就是一个数据搬运工1.2 DMA的意义代替 CPU 搬运数据为 CPU 减负。1. 数据搬运的工作比较耗时间2. 数据搬运工作时效要求高有数据来就要搬走不及时搬走下一个数据来会覆盖上一个。3. 没啥技术含量CPU 节约出来的时间可以处理更重要的事。1.3 搬运什么数据存储器、外设这里的外设指的是spi、usart、iic、adc 等基于APB1 、APB2或AHB时钟的外设而这里的存储器包括自身的闪存(flash)或者内存(SRAM)以及外设的存储设备都可以作为访问地源或者目的。三种搬运方式存储器→存储器例如复制某特别大的数据buf存储器→外设 例如将某数据buf写入串口TDR寄存器外设→存储器 例如将串口RDR寄存器写入某数据buf存储器→存储器存储器→外设外设→存储器2. DMA框图如果CPU进行数据搬运流程是通过系统总线--总线矩阵--外设再原路返回cpu后搬到SRAM中。如果是DMA的话外设首先向DMA发送请求响应之后就开始数据的搬运。通过DMA总线--总线矩阵--外设--SRAM。不经过cpu。如果有多个外设去请求DMA就有仲裁器去判断各自的优先级。3. DMA控制器STM32F103 有 2 个 DMA 控制器DMA1 有 7 个通道DMA 2 有 5 个通道。一个通道每次只能搬运一个外设的数据 如果同时有多个外设的 DMA 请求则按照优先级进行响应。STM32F103C8T6 只有 DMA1 不是说每个通道都可以转运任意的外设。对应如下表、DMA1有7个通道DMA2 有 5 个通道4. DMA优先级管理优先级管理采用软件硬件软件 每个通道的优先级可以在DMA_CCRx寄存器中设置有4个等级最高级高级中级低级硬件 如果2个请求它们的软件优先级相同则较低编号的通道比较高编号的通道有较高的优先权。比如如果软件优先级相同通道2优先于通道4.有点像中断的自然优先级5. DMA传输方式DMA_Mode_Normal正常模式一次DMA数据传输完后停止DMA传送 也就是只传输一次DMA_Mode_Circular循环传输模式当传输结束时硬件自动会将传输数据量寄存器进行重装进行下一轮的数据传输。 也就是多次传输模式6. 指针递增模式外设和存储器源和目标的指针都可以在每次传输后可以自动向后递增或保持不变。当设置为增量模式时下一个要传输的地址将是前一个地址加上增量值。对于向外设传输数据的情况一般是不进行目标的指针的递增。源要递增。7. DMA数据对齐方式第一个图紫色为搬运的数据每个数据长度为8位一个字符目标大小一样直接覆盖。如果源为8位目标为16位一般是右对齐 前面补0。 如果目标32位也是一样。右对齐前面补0。第二个图中源16位目标8位结果就是低8位的留下高8位的截掉了。其余原理相同。8. DMA寄存器及库函数介绍代表DMA当前处于什么状态一般关注TCIFxtc代表transfer complete。代表传输完成置1。一般也关注CTCIFx这一位置1就清除中断状态寄存器的TCIFx的标志位。到底要转运多少数据就是在这里配置的。最多可以转运65536个字节2的16次方。外设和寄存器的地址的设置接下来看一下DMA的库函数。__HAL_RCC_DMA1_CLK_ENABLE(…) //打开时钟HAL_DMA_Init(…) //配置上述的大部分寄存器HAL_DMA_Start(…) //开始进行了数据的转运__HAL_LINKDMA(…) //进行DMA和外设的连接HAL_UART_Transmit_DMA(…) //串口DMA发送HAL_UART_Receive_DMA(…) //串口DMA接收__HAL_DMA_GET_FLAG(…) //获取标志位的状态__HAL_DMA_ENABLE(…) //使能__HAL_DMA_DISABLE(…) //失能小实验1DMA内存到内存数据搬运实验目的使用 DMA 将一个大数组的数据搬运到另一个位置。硬件清单上官二号ST-LinkUSB转TTL老规矩我们建立dam的文件夹打开工程.c.h文件添加到“品”和魔法棒。依旧首先#include dma.h后编译。在h中吧基本工作做好。接下来根据步骤开始.c文件的编写。首先肯定是void dma_init(void)初始化的函数需要调用上节课说过的HAL_DMA_Init(…) 。寻找我们的基地址也就是通道需要找对应的表格。我们之前看过的表他是针对外设的因为我们是内存到内存之间的转运所以任意通道都可以。DMA_HandleTypeDef dma_handle {0};//定义的全局变量的句柄 void dma_init(void) { __HAL_RCC_DMA1_CLK_ENABLE(); //打开时钟 dma_handle.Instance DMA1_Channel1; //基地址 dma_handle.Init.Direction DMA_MEMORY_TO_MEMORY; //怎么样的传输方向 //内存相关配置 dma_handle.Init.MemDataAlignment DMA_MDATAALIGN_BYTE; //数据对齐模式8位 dma_handle.Init.MemInc DMA_MINC_ENABLE; //数据增长方式递增 //外设相关配置 dma_handle.Init.PeriphDataAlignment DMA_PDATAALIGN_BYTE;//数据对齐模式8位 dma_handle.Init.PeriphInc DMA_PINC_ENABLE; //数据增长方式递增 dma_handle.Init.Priority DMA_PRIORITY_MEDIUM; //中等等级设置只用了一个通道随便设 dma_handle.Init.Mode DMA_NORMAL; //正常的传输模式内存到内存只支持正常模式 HAL_DMA_Init(dma_handle); //init句柄 }接下来再封装一个函数进行数据的转运HAL_DMA_Start(…) //开始进行了数据的转运。F12进来开一下 参数都分别是什么分别是句柄、源地、目标地址、长度。所以我们要定义源地址和目标地址也就是源数组和目标数组传数组就是传指针首地址。#include dma.h #include stdio.h #define BUF_SIZE 16 uint32_t src_buf[BUF_SIZE] { 0x00000000,0x11111111,0x22222222,0x33333333, 0x44444444,0x55555555,0x66666666,0x77777777, 0x88888888,0x99999999,0xAAAAAAAA,0xBBBBBBBB, 0xCCCCCCCC,0xDDDDDDDD,0xEEEEEEEE,0xFFFFFFFF }; uint32_t dst_buf[BUF_SIZE] {0};void dma_transmit(void) { // 长度不能仅仅是BUF_SIZE,数组的每个元素类型都是uint32—_tsizeof(uint32_t) * BUF_SIZE HAL_DMA_Start(dma_handle, (uint32_t)src_buf, (uint32_t)dst_buf, sizeof(uint32_t) * BUF_SIZE); while(__HAL_DMA_GET_FLAG(dma_handle, DMA_FLAG_TC1) RESET);//通道1 x写1. // TC位置0时while一直死循环最后有分号 注意读TC位置1进行下面的代码 int i 0; //循环因子。 for (i 0; i BUF_SIZE; i) //for循环打印 printf(buf[%d] %X\r\n, i, dst_buf[i]); //stdio.h }函数放到.h中进行声明在main中。#include sys.h #include delay.h #include led.h #include uart1.h #include dma.h int main(void) { HAL_Init(); /* 初始化HAL库 */ stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */ led_init(); /* 初始化LED灯 */ uart1_init(115200); dma_init(); printf(hello world!\r\n); dma_transmit();小实验2DMA内存到外设数据搬运实验目的使用 DMA 将一个大数组的数据通过串口 1 发送。硬件清单上官二号ST-LinkUSB转TTL前期准备工作和上面的一样这里我们就直接开始文件的编写。#include dma.h #include stdio.h /*声明一个外部变量告诉编译器“这个变量存在但定义在别处”*/ extern UART_HandleTypeDef uart1_handle; DMA_HandleTypeDef dma_handle {0}; void dma_init(void) { __HAL_RCC_DMA1_CLK_ENABLE(); //时钟1不变 dma_handle.Instance DMA1_Channel4; /*查表格看串口1对应的通道*/ dma_handle.Init.Direction DMA_MEMORY_TO_PERIPH; /*内存发送给外设*/ //内存相关配置 dma_handle.Init.MemDataAlignment DMA_MDATAALIGN_BYTE;/*数据对齐模式8位*/ dma_handle.Init.MemInc DMA_MINC_ENABLE;/*递增*/ //外设相关配置 dma_handle.Init.PeriphDataAlignment DMA_PDATAALIGN_BYTE;/*数据对齐模式8位*/ dma_handle.Init.PeriphInc DMA_PINC_DISABLE;/*外设接收地址不能递增*/ dma_handle.Init.Priority DMA_PRIORITY_MEDIUM; /*中等等级一个通道随便设*/ dma_handle.Init.Mode DMA_NORMAL; /*正常传输模式*/ HAL_DMA_Init(dma_handle); __HAL_LINKDMA(uart1_handle, hdmatx, dma_handle);/*与外设连接*/ } /*对应外设的句柄、UART 句柄中的 DMA 发送句柄成员、DMA的句柄*/ //数组名就是地址extern外部变量不要忘记声明。hdmatx是怎么来的 我们看F12点进来。本质上就是 将DMA句柄的地址赋值给uartl handle. hdmatx, .找到串口1句柄结构体的成员变量。hdmatx就是这样来的#include sys.h #include delay.h #include led.h #include uart1.h #include dma.h extern UART_HandleTypeDef uart1_handle; /*下面用到这个 外部变量了*/ uint8_t send_buf[1000] {0};/ *定义一个大数组*/ int main(void) { HAL_Init(); /* 初始化HAL库 */ stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */ led_init(); /* 初始化LED灯 */ uart1_init(115200); dma_init(); //printf(hello world!\r\n); int i 0; for(i 0; i 1000; i) send_buf[i] A; /*这个数组每个元素都是A*/ HAL_UART_Transmit_DMA(uart1_handle, send_buf, 1000); } /*串口句柄、发送数据的首地址数组名、长度*/ // 启动 DMA 发送将 send_buf 中的 1000 个字节通过 UART1 发送出去小实验3DMA外设到内存数据搬运实验目的使用 DMA 接收串口 1 数据。硬件清单上官二号ST-LinkUSB转TTL接下来是接收。#include dma.h #include stdio.h #include uart1.h /*UART1_RX_BUF_SIZE涉及了这个宏*/ extern UART_HandleTypeDef uart1_handle; extern uint8_t uart1_rx_buf[UART1_RX_BUF_SIZE]; DMA_HandleTypeDef dma_handle {0}; void dma_init(void) { __HAL_RCC_DMA1_CLK_ENABLE(); dma_handle.Instance DMA1_Channel5; /*查串口1的RX通道五*/ dma_handle.Init.Direction DMA_PERIPH_TO_MEMORY; /*方向外设到内存*/ //内存相关配置 dma_handle.Init.MemDataAlignment DMA_MDATAALIGN_BYTE; /*对齐8位*/ dma_handle.Init.MemInc DMA_MINC_ENABLE; /*递增*/ //外设相关配置 dma_handle.Init.PeriphDataAlignment DMA_PDATAALIGN_BYTE; /*对齐8位*/ dma_handle.Init.PeriphInc DMA_PINC_DISABLE; /*外设不递增*/ dma_handle.Init.Priority DMA_PRIORITY_MEDIUM; dma_handle.Init.Mode DMA_NORMAL; HAL_DMA_Init(dma_handle); __HAL_LINKDMA(uart1_handle, hdmarx, dma_handle);/*与外设连接*/ HAL_UART_Receive_DMA(uart1_handle, uart1_rx_buf, UART1_RX_BUF_SIZE); }/*串口1句柄、接受数据的缓冲区数组名、长度*/ //启动 DMA 接收将UART1接收到的数据自动存入uart1_rx_buf总共接收 UART1_RX_BUF_SIZE 个字节 //之后再一起给CPU就不会像之前一样一个字节一个字节给cpu每次都产生一个中断*/之后去UART.c中进行代码的编写之前空闲中断产生的话就直接打印出来现在是搬运出来DMA只有空闲中断。串口发出数据 → DMA自动搬运到缓冲区 → 外部设备停止发送总线空闲 → 触发空闲中断 → 执行处理代码 → 重新开启下一轮接收DMA接收到串口发出的数据一起存入到了缓冲区当中。if (__HAL_UART_GET_FLAG(uart1_handle, UART_FLAG_IDLE) ! RESET) //清除空闲中断 __HAL_UART_CLEAR_IDLEFLAG(uart1_handle); //停止DMA传输防止干扰 HAL_UART_DMAStop(uart1_handle); //获取接收到的数据长度 需要传输的数量---寄存器里的值剩下没传的 uart1_rx_len UART1_RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(dma_handle); //打印接收到的内容 /*读取指定DMA通道当前剩余的、尚未传输的数据量*/ printf(recv: %s, recv_len: %d\r\n, uart1_rx_buf, uart1_rx_len); //清空接收缓冲 uart1_rx_clear(); //重新开启串口DMA传输 HAL_UART_Receive_DMA(uart1_handle, uart1_rx_buf, UART1_RX_BUF_SIZE); }
返回列表