本次实验我们是模仿正点原子的寄存器Demo来深入学习DMA的工作原理并来纯手工敲写DMA实验。DMA是可以存储器与存储器,外设与存储器之间进行数据传输的。这里我们 以串口1作为外设,BUFF[xx]字符数组作为存储器的外设与存储器之间的数据传输实验。
那怎么看中文参考手册呢? 第一步:看手册的核心要点。 1、 简介是表示用最简单的语言来描述那种模块的大致内容,所以这个我们必须看。
2、 模块的特性我们也需要重点关注的,因为这里描述的东西在我们配置时极大可能需要了解的又可能疏忽的点。
3、 模块处理过程是很重要的,我们根据理解它的处理过程来配置程序的。
4、 看到这个配置过程字眼,我们要知道,这是重点中的重点,因为我们的是根据这个爹来配置程序的。
第二步:理解了上面的描述后,我们就要理解一下这个DMA的工作框图。 红色线:这表示我们在平常串口通信的时候我们是将usart_dr寄存器的值放入定义好的buff[xx]缓冲区里作为接收操作,而将字符串逐个的值赋予usart_dr寄存器作为输出操作。 从字面理解来说,这样我们就要将MCU腾出的时间出进行串口数据接收和数据发送了,这样就消耗了MCU的一部分资源了。 蓝色线:第一条线表示MCU与DMA的连线,是用来配置DMA用的。第二条线表示外设或存储器与DMA的数据交互的,可以理解成它就是一个小型的MCU,能直接处理存储器与寄存器之间的数据交互,不需要STM32这个MCU的参与,这大大减小了MCU资源占用。
第三步:配置的大致我们都知道后,我们要知道一个要点,但凡我们使用一个模块的东西去操作另一个模块的时候,这两个模块之间是没有线路直接相连的,而且还有这么多个通道那种,那么我们就要查看映射表了。 蓝色框就是我们要使用的外设了,它在DMA1模块里的通道4。
第四步:开始写寄存器操作的程序。(我们看着刚刚那个配置过程来写程序)
配置DMA的基地址,上门图可知,DMA这个模块里面每个通道都有自己对应的寄存器,那么我们配置寄存器也要相应的分开配置那个通道的,所以我们基地址需要分开来。 上面程序里为什么还要分DMA_BASE呢?这需要我们去看手册一个地方,如下图所示: DMA_ISR和DMA_IFCR这两个寄存器和下面的是有很明显的区别的,下面的没4个对应一个通道,里面除了起始地址不一样外,大小都一样,所以配置时可以共用一个结构体,而那个两个就只能用一个不同的结构体独立出来了,也就是为什么用DMA_BASE的原因了。 寄存器的基地址在哪里找呢?如下图可以查找到模块的起始地址。 而每个通道的基地址就在DMA寄存器印象和复位这个表中查找,模块起始地址加上表左边的偏移量就是基地址了。 这个结构体就按照DMA寄存器印象和复位表来写的。这里就步重复解释了。 - 指针 (每个通道结构体和那个特别一点的只有两个寄存器的结构体)
有以上的环境基础下,我们就可以开始配置DMA了。 我们需要寄存器那些位呢?配置哪个寄存器就进入哪个寄存器查询相关位的功能,列入下图2 图1 图2 好了下面开始配置 这个配置程序基本是根据手册中描述通道配置来编写的 (1打开时钟 2设置外设 3设置存储器 4传输的数量 5方向、是否循环模式、外设是否增量、存储器是否增量、外设数据宽、存储器数据宽、优先级、什么模式启动) 这里我就说一些重点,简单的自己看手册,只有自己学会看手册才学到东西。 sendlen=BUFFLEN; 这是用来保存你要传输的数据长度,用来触发你每次传输都是传输这个长度的数据。 循环模式:中文参考手册有详细解释。 为什么要把外设和存储器的位宽都设置8位:首先我们要理解一个过程DMA传输是将我们存储器的值全部发送出去为止,那么8位为一个字节,在这个传输的过程就是每次取存储器1个字节数据传输到外设,而外设也是一次取1个字节数据,那么实现数据一一对应了。 这个没什么好说的,就是关闭上一次已经传输完成的;重置它的传输量,因为每次传输数据它都会以传输一个数据递减1的形式递减至0,并且它不会自己重载回去(循环模式就可以递减至0又重载);开启新的DMA传输。 配置完后就将我们准备好的存储器写满内容,然后进行DMA传输,在串口中观看。 1、自己定义一个存储器,记得要大点,假设我们配置的是波特率为115200,那么就说明数据传输速率115200bit/s = 14400字节/s,而我们用34000个字节的数据进行传输的话,一个DMA传输过程大概花费的时间是34000/14400=2.36s左右。 2、我们使用串口作为外设 3、串口1在DMA通道1,外设为串口USART_DR,存储器SendBuff,存储器的数据长度。 4、弄个什么循环的把存储器SendBuff的数据灌满。例如下图 5、 开启数据传输,上面红框框里的是我们将本来使用复用的PA9口作为发送端换成了DMA->USART1_TX上(这个东西就类似与GPIO口复用成串口等这样) 上面虚线这行代码是用不了的,若想显示它传输的百分比,就需要用OLED屏或者使用串口2等方式显示(原因是因为DMA直接控制USART1_DR寄存器的,不需要MCU参与,导致USART1_DR这个一直被占用,就打印出传输过程的百分比量了)
完整程序:
- #include "stdio.h"
- #include "gpio.h"
- #include "USART1.h"
- #include "delay_ms.h"
- #include "clock.h"
- #include "USART1.h"
- #include "key.h"
- #include "DMA.h"
-
- const u8 Text_Buff[]="邓家文使用STM32F103 DMA串口实验";
- u16 str_len=sizeof(Text_Buff); //str_len将要发送字符的长度
- u8 SendBuff[34000]; //发送数据缓冲区 DMA( 寄存器->存储器SendBuff )
- u16 KeyKind=0; //key_kind按键类
-
- float pro=0; //进度条
- u16 i,mask,tt; //循环体使用的临时变量
-
-
- int main(void)
- {
- RCC_Config(9); //72MHz
- delay_init1(72); //打开延时
- GPIO_Init(); //初始化LED口
- USART_INIT(72,115200); //初始化串口
- key_init(); //初始化按键
- DMA_INIT(DMA_Channel4,(u32)&USART1->USART_DR,(u32)SendBuff,34000); //寄存器,存储器,存储器大小
-
- for(i=0;i<34000;i++)//填充数据到SendBuff
- {
- if(tt>=str_len)//加入换行符
- {
- if(mask)
- {
- SendBuff[i]=0x0a;
- tt=0;
- }else
- {
- SendBuff[i]=0x0d;
- mask++;
- }
- }else//复制TEXT_TO_SEND语句
- {
- mask=0;
- SendBuff[i]=Text_Buff[tt];
- tt++;
- }
- }
- while(1)
- {
- KeyKind=key_scan(0);
- if(KeyKind==2)
- {
- GPIOB->GPIO_ODR^=1<<5;
- USART1->USART_CR3=1<<7; //使能串口1的DMA发送
- DMA_OneSend(DMA_Channel4); //开启一次发送
-
- while(1)
- {
- if(DMA->DMA_ISR&(1<<13)) //等待通道1传输完成
- {
- DMA->DMA_IFCR|=1<<13; //清除通道1传输完成标志
- break;
- }
- pro=DMA_Channel4->DMA_CNDTR;//获取当前剩余数据量
- pro=1-pro/34000; //获取百分比
- pro*=100; //得到整数的百分比
- printf("residue:%f\r\n",pro);
- }
- }
- }
-
- }
复制代码
二、DMA.h
- #include "stdio.h"
- #include "gpio.h"
- #include "USART1.h"
- #include "delay_ms.h"
- #include "clock.h"
- #include "USART1.h"
- #include "key.h"
- #include "DMA.h"
-
- const u8 Text_Buff[]="邓家文使用STM32F103 DMA串口实验";
- u16 str_len=sizeof(Text_Buff); //str_len将要发送字符的长度
- u8 SendBuff[34000]; //发送数据缓冲区 DMA( 寄存器->存储器SendBuff )
- u16 KeyKind=0; //key_kind按键类
-
- float pro=0; //进度条
- u16 i,mask,tt; //循环体使用的临时变量
-
-
- int main(void)
- {
- RCC_Config(9); //72MHz
- delay_init1(72); //打开延时
- GPIO_Init(); //初始化LED口
- USART_INIT(72,115200); //初始化串口
- key_init(); //初始化按键
- DMA_INIT(DMA_Channel4,(u32)&USART1->USART_DR,(u32)SendBuff,34000); //寄存器,存储器,存储器大小
-
- for(i=0;i<34000;i++)//填充数据到SendBuff
- {
- if(tt>=str_len)//加入换行符
- {
- if(mask)
- {
- SendBuff[i]=0x0a;
- tt=0;
- }else
- {
- SendBuff[i]=0x0d;
- mask++;
- }
- }else//复制TEXT_TO_SEND语句
- {
- mask=0;
- SendBuff[i]=Text_Buff[tt];
- tt++;
- }
- }
- while(1)
- {
- KeyKind=key_scan(0);
- if(KeyKind==2)
- {
- GPIOB->GPIO_ODR^=1<<5;
- USART1->USART_CR3=1<<7; //使能串口1的DMA发送
- DMA_OneSend(DMA_Channel4); //开启一次发送
-
- while(1)
- {
- if(DMA->DMA_ISR&(1<<13)) //等待通道1传输完成
- {
- DMA->DMA_IFCR|=1<<13; //清除通道1传输完成标志
- break;
- }
- pro=DMA_Channel4->DMA_CNDTR;//获取当前剩余数据量
- pro=1-pro/34000; //获取百分比
- pro*=100; //得到整数的百分比
- printf("residue:%f\r\n",pro);
- }
- }
- }
-
- }
复制代码
三、DMA.c
- #include "DMA.h"
- #include "delay_ms.h"
-
- u16 sendlen; //保存DMA每次数据传送的长度,也就是最开始的传输量,100%
-
- //CPAR外设地址
- //CMAR存储器地址
- //BUFFLEN数据传输的长度或量
- void DMA_INIT(DMA_ChannelType*DMA_Chx,u32 CPAR,u32 CMAR,u16 BUFFLEN)
- {
- RCC->RCC_AHBENR=1<<0; //开启DMA1的时钟
- delay_ms1(5); //等待DMA时钟稳定
- DMA_Chx->DMA_CPAR=CPAR; //指向外设寄存器地址
- DMA_Chx->DMA_CMAR=CMAR; //指向存储器地址
- sendlen=BUFFLEN; //保存DMA传输数据量
- DMA_Chx->DMA_CNDTR=BUFFLEN; //传输数据量 每次传输都会自动递减,直到0后会重载配置的长度数值BUFFLEN
- DMA_Chx->DMA_CCR=0x00000000; //将配置寄存器的所有位清除(复位)
- DMA_Chx->DMA_CCR|=1<<4; //从存储器读出数据
- DMA_Chx->DMA_CCR|=0<<5; //普通模式,不循环
- DMA_Chx->DMA_CCR|=0<<6; //外设地址非增量模式, 这个是读数据,选择读出的位置大小是固定的,也就是不会随地址改变,所以非增量
- DMA_Chx->DMA_CCR|=1<<7; //存储器地址增量模式 这个是写数据,需要将读出来的数据依次的写入对应的地方,所以增量
- DMA_Chx->DMA_CCR|=0<<8; //外设数据宽度为8位 也就是一次读出1个字节
- DMA_Chx->DMA_CCR|=0<<10; //存储器数据宽度8位 也就是一次写入1个字节
- DMA_Chx->DMA_CCR|=1<<12; //中等优先级
- DMA_Chx->DMA_CCR|=0<<14; //启动非存储器到存储器模式
- }
-
- //启动一次DMA传输,上面定义的是一次传输1个字节
- void DMA_OneSend(DMA_ChannelType*DMA_Chx)
- {
- DMA_Chx->DMA_CCR&=~(1<<0); //先关闭上一次的DMA传输,因续上一次传输1个字节后并未关闭,若步关闭回影响下一次的传输,而且不能实现起停传输效果
- DMA_Chx->DMA_CNDTR=sendlen; //传输数据量
- DMA_Chx->DMA_CCR|=1<<0; //开启下一次的DMA传输
- }
-
复制代码
以上的的Word格式文档51黑下载地址:
带你模仿正点原子到寄存器编写--DMA.docx
(1.52 MB, 下载次数: 14)
|