带你模仿正点原子编程风格到深入学习寄存器并手把手编写STM32F103寄存器程序(DMA篇)

一、前叙

本次实验我们是模仿正点原子的寄存器Demo来深入学习DMA的工作原理并来纯手工敲写DMA实验。DMA是可以存储器与存储器,外设与存储器之间进行数据传输的。这里我们
以串口1作为外设,BUFF[xx]字符数组作为存储器的外设与存储器之间的数据传输实验。

二、那怎么看中文参考手册呢?

1.引入库

第一步:看手册的核心要点。
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设置外设 3设置存储器 4传输的数量 5方向、是否循环模式、外设是否增量、存储器是否增量、外设数据宽、存储器数据宽、优先级、什么模式启动)
这里我就说一些重点,简单的自己看手册,只有自己学会看手册才学到东西。
sendlen=BUFFLEN; 这是用来保存你要传输的数据长度,用来触发你每次传输都是传输这个长度的数据。
循环模式:中文参考手册有详细解释。
为什么要把外设和存储器的位宽都设置8位:首先我们要理解一个过程DMA传输是将我们存储器的值全部发送出去为止,那么8位为一个字节,在这个传输的过程就是每次取存储器1个字节数据传输到外设,而外设也是一次取1个字节数据,那么实现数据一一对应了。
⑤ 编写每次触发DMA传输过程
在这里插入图片描述
这个没什么好说的,就是关闭上一次已经传输完成的;重置它的传输量,因为每次传输数据它都会以传输一个数据递减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这个一直被占用,就打印出传输过程的百分比量了)

希望能给大家学习上一些帮助。

1.完整的程序

test.c

#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

#ifndef __DMA_H
#define __DMA_H
#include "gpio.h"
#include "RCC.h"

/*DMA的基地址0x4002 0000 */
#define DMA_BASE 										(0x40020000)      //DMA1  
#define DMA_CHANNEL1_BASE						(0x40020008)			//通道1的
#define DMA_CHANNEL2_BASE						(0x4002001C)			//通道2的
#define DMA_CHANNEL3_BASE						(0x40020030)			//通道3的
#define DMA_CHANNEL4_BASE						(0x40020044)			//通道4的
#define DMA_CHANNEL5_BASE						(0x40020058)			//通道5的
#define DMA_CHANNEL6_BASE						(0x4002006C)			//通道6的
#define DMA_CHANNEL7_BASE						(0x40020080)			//通道7的

/*DMA寄存器结构体*/
typedef struct
{
	volatile unsigned int DMA_ISR;			//中断状态寄存器
	volatile unsigned int DMA_IFCR;			//中断标志清除寄存器
}DMA_Type;

typedef struct
{
	volatile unsigned int DMA_CCR;			//配置寄存器
	volatile unsigned int DMA_CNDTR;		//传输数量寄存器
	volatile unsigned int DMA_CPAR;		//外设地址寄存器
	volatile unsigned int DMA_CMAR;		//存储器地址寄存器
}DMA_ChannelType;


#define DMA 													((DMA_Type *) DMA_BASE)
#define DMA_Channel1									((DMA_ChannelType *) DMA_CHANNEL1_BASE)
#define DMA_Channel2									((DMA_ChannelType *) DMA_CHANNEL2_BASE)
#define DMA_Channel3									((DMA_ChannelType *) DMA_CHANNEL3_BASE)
#define DMA_Channel4									((DMA_ChannelType *) DMA_CHANNEL4_BASE)
#define DMA_Channel5									((DMA_ChannelType *) DMA_CHANNEL5_BASE)
#define DMA_Channel6									((DMA_ChannelType *) DMA_CHANNEL6_BASE)
#define DMA_Channel7									((DMA_ChannelType *) DMA_CHANNEL7_BASE)

void DMA_INIT(DMA_ChannelType*DMA_Chx,u32 CPAR,u32 CMAR,u16 BUFFLEN);
void DMA_OneSend(DMA_ChannelType*DMA_Chx);
#endif


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传输
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邓家文007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值