STM32寄存器C语言&HAL库方式的点亮LED流水灯

STM32寄存器&HAL库方式的LED程序

一、寄存器相关知识

1.1寄存器

寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。

简单来说,寄存器就是存放东西的东西。从名字来看,跟火车站寄存行李的地方好像是有关系的。只不过火车站行李寄存处,存放的行李;寄存器可能存放的是指令、数据或地址。

存放数据的寄存器是最好理解的,如果你需要读取一个数据,直接到这个寄存器所在的地方来问问他,数据是多少就行了。问寄存器这个动作,叫做访问寄存器。不同的数据会存放在不同的寄存器,例如引脚PA2与PB8的高低电平数据(1或0)肯定放在不同的寄存器里,那么怎么区分不同的寄存器呢?通过地址,不同的寄存器有不同的地址,就像一个人的行李寄存处在101号货柜,另一个人的行李寄存处在233号货柜。

指令、地址寄存器与数据寄存器类似,里边存放的都是0和1,毕竟单片机也只认识机器码,机器码都是0或1,只是特别的规定下,数据寄存器里面存放的0和1表示数据,指令寄存器里存放的表示指令。

1.2 通过数据手册找到某个寄存器的地址
手册中没有直接给出所有的寄存器的地址,需要我们稍加计算。STM32给不同的寄存器分配了不同的地址,有点像划分了片区。在《STM32中文参考手册_V10》的第28页,有不同寄存器的地址范围。
现在,假如我们想读取PB3引脚的电平,该怎么找到相关的寄存器?

1.2通过数据手册找到某个寄存器的地址

手册中没有直接给出所有的寄存器的地址,需要我们稍加计算。STM32给不同的寄存器分配了不同的地址,有点像划分了片区。在《STM32中文参考手册_V10》的第28页,有不同寄存器的地址范围。
现在,假如我们想读取PB3引脚的电平,该怎么找到相关的寄存器?

1.2.1找到GPIOB的基地址

所有GPIOB相关的寄存器,都住在0x4001 0C00到0x4001 0FFF范围内。
在这里插入图片描述

1.2.2找到端口输入寄存器的地址偏移

找到存储数据的单元,结论是0x4001 0C00+8 = 0x4001 0C08
在这里插入图片描述

1.2.3找到具体的屋子

PB3的数据位于从右往左数第4个。
在这里插入图片描述

经过这三步查找,我们可以做出以下结论:
PB3的输入数据位于0x4001 0C08这个地址上,这个地址上存放数据的右起第4个位就是PB3引脚对应的高低电平。

二、直接操作寄存器来点亮LED

本次实验采用GPIOA、B、C三个端口。该三个端口都属于APB2总线

2.1配置时钟

为什么配置时钟?为了省电,默认的时钟都是关闭的。配置STM32的任何资源前,都必须首先使能时钟。
  配置哪个时钟?
时钟的信息在参考手册里边,参考手册十分巨大,不用通读,就像一个字典,需要什么查什么。
  参考手册,搜索"时钟",在表1里可以看到。
时钟控制名字叫做RCC,属于AHB总线。GPIOA、B、C属于APB2。
在这里插入图片描述
下图系统结构可以看到时钟的从属关系,此图位于手册P25页,十分重要。可以看出AHB总线包含RCC时钟控制,GPIO是属于APB2的。
在这里插入图片描述
我们已经知道,GPIO端口B的地址从0x4001 0C00开始。接下来只寻找时钟使能寄存器的地址:
  复位和时钟控制RCC的地址从0x4002 1000开始;
  可以在6.3.7小节找到APB2外设时钟使能寄存器(RCC_APB2ENR),偏移地址是0x18,所以APB2的地址就是0x4002 1018。
  看手册RCC_APB2ENR,位2是IOPAEN,位3是IOPBEN,位4是IOPCEN,是IO端口A,B,C时钟使能,就是我们想要的。
在这里插入图片描述
使能对应端口时钟

//----------------APB2使能时钟寄存器 ---------------------
#define RCC_APB2ENR		*((unsigned volatile int*)0x40021018)

RCC_APB2ENR|=1<<2|1<<3|1<<4;			//APB2-GPIOA、GPIOB、GPIOC外设时钟使能	

2.2配置为通用输出

既然叫做IO,那么肯定就是可以输入,可以输出,到底是输入还是输出呢?
控制LED需要输出高电平或是低电平,所以需要配置为输出。
由于STM32的每个IO都需要4个位来配置,所以一个32位的寄存器最大只能配置8个IO(32位的单片机的寄存器就是32位的)。STM32中,用端口配置低寄存器(GPIOx_CRL)来配置引脚Px0-Px7, 用端口配置高寄存器(GPIOx_CRH)来配置引脚Px8-Px15。

我们需要使用A5、B9、C13三个引脚。其中A5属于端口配置低寄存器偏移地址为0x00,B9,C14属于端口配置高寄存器偏移地址为0x04。
在这里插入图片描述
在这里插入图片描述
先看标题GPIOx,表示不管是PA,PB还是PE,都能用。
A的偏移地址是0x00,意思是在基地址的基础上再加0x00,GPIOA就是0x40010800。同理,B,C的偏移地址是0x04,意思是在基地址的基础上再加0x04,所以,对于GPIOB和GPIOC来说就是0x40011004。

//----------------GPIOA配置寄存器 -----------------------
#define GPIOA_CRL		*((unsigned volatile int*)0x40010800)
//----------------GPIOB配置寄存器 -----------------------
#define GPIOB_CRL		*((unsigned volatile int*)0x40010C04)
//----------------GPIOC配置寄存器 -----------------------
#define GPIOC_CRH		*((unsigned volatile int*)0x40011004)

找到需要操作的寄存器后,把它配置为通用输出。
复位值是0x4444 4444,并不是0x0000 0000。所谓的复位值,就是指如果没有操作这个寄存器时,寄存器存放的默认值。复位值按位拆分0x4 = 0b0100,0x表示16进制,0b表示二进制,也就是默认CNF 01,MODE 00,是浮空输入。
我们需要的是输出高低电平,所以要设置为输出。输出模式又有好几种输出:
在这里插入图片描述

  • 推挽输出:可以输出高,低电平,连接数字器件;推挽结构一般是指两个三极管分别受两互补信号的控制,总是在一个三极管导通的时候另一个截止。
  • 开漏输出:输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行,适合于做电流型的驱动,其吸收电流的能力相对强(一般20ma以内)。

开漏是需要外接上拉电阻才可以输出高电平的,这里并不适合。所以需要设置为推挽输出。
功能是否是复用呢?复用的意思是有别的功能在这个脚上,比如USB,CAN,串口等,所以这些个脚就可能有多个功能。这里我们用不到。

GPIOA_CRL&=0xFF0FFFFF;		//设置位 清零	
GPIOA_CRL|=0x00200000;		//PA5推挽输出,把第23、22、21、20位变为0010

GPIOB_CRL&=0xFFFFFF0F;		//设置位 清零	
GPIOB_CRL|=0x00000020;		//PB9推挽输出,把第7、6、5、4变为0010

GPIOC_CRH&=0xFF0FFFFF;		//设置位 清零	
GPIOC_CRH|=0x00200000;		//PC13推挽输出,把第23、22、21、20变为0010

2.3点亮LED需要输出低电平

在单片机的编程中,要想做某件事,必须寻找相应的寄存器。在8.2.4小节,可以找到端口输出数据寄存器,就是我们需要的。我们需要输出0。但是中文手册有一个小小的BUG,0x0C写成了0Ch,可以参考英文原版。得知地址的偏移是0x0C,所以这个数据寄存器的地址就是0x4001 0C0C,把第8位写为0就行。默认就是0。
在这里插入图片描述
移位操作:

#define	GPIOA_ORD	*((unsigned volatile int*)0x4001080C)
GPIOA_ORD|=1<<5;			//设置初始灯为亮

三、C语言实现流水灯

本次采用三个灯实现,亮灯状态用1表示,灭灯状态用0表示。
初始态为0 0 0
状态一为1 0 0
状态二为0 1 0
状态三为0 0 1
状态三结束后继续进入状态一,一直循环达到流水灯效果。
代码如下:

#include "stm32f10x.h"
//----------------APB2使能时钟寄存器 ---------------------
#define RCC_APB2ENR		*((unsigned volatile int*)0x40021018)
//----------------GPIOA配置寄存器 -----------------------
#define GPIOA_CRL		*((unsigned volatile int*)0x40010800)
#define	GPIOA_ODR		*((unsigned volatile int*)0x4001080C)
//----------------GPIOB配置寄存器 -----------------------
#define GPIOB_CRL		*((unsigned volatile int*)0x40010C04)
#define	GPIOB_ODR		*((unsigned volatile int*)0x40010C0C)
//----------------GPIOC配置寄存器 -----------------------
#define GPIOC_CRH		*((unsigned volatile int*)0x40011004)
#define	GPIOC_ODR		*((unsigned volatile int*)0x4001100C)


//延时函数
 void Delay()
 {
   u32 i=0;
   for(;i<5000000;i++);
 }
 int main(void)
 {	
	RCC_APB2ENR|=1<<2|1<<3|1<<4;			//APB2-GPIOA、GPIOB、GPIOC外设时钟使能	
	

	GPIOA_CRL&=0xFF0FFFFF;		//设置位 清零	
	GPIOA_CRL|=0x00200000;		//PA5推挽输出
	GPIOA_ODR&=~(1<<5);			//设置初始灯为灭
	
	GPIOB_CRL&=0xFFFFFF0F;		//设置位 清零	
	GPIOB_CRL|=0x00000020;		//PB9推挽输出
	GPIOB_ODR&=~(1<<9);			//设置初始灯为灭
	 
	GPIOC_CRH&=0xFF0FFFFF;		//设置位 清零	
	GPIOC_CRH|=0x00200000;		//PC13推挽输出
	GPIOC_ODR&=~(1<<13);			//设置初始灯为灭		


	while(1){
		//A灯
		GPIOA_ODR&=~(1<<5);		//PA5低电平,因为是置0,所以用按位与
	 	Delay();
		GPIOA_ODR|=1<<5;		//PA5高电平

		
		//B灯
		GPIOB_ODR&=~(1<<9);		//PB9低电平,因为是置0,所以用按位与
	 	Delay();
		GPIOB_ODR|=1<<9;		//PB9高电平


		
		
		//C灯
		GPIOC_ODR&=~(1<<13);		//PC13低电平,因为是置0,所以用按位与
	 	Delay();
		GPIOC_ODR|=1<<13;		//PC13高电平		

		}

}

在这里插入图片描述

二、STM32CubeMX生成代码使用HAL库点亮流水灯

2.1安装STM32CubeMX

官网下载
在这里插入图片描述
(网站加载较慢 可能不能第一时间显示)
在这里插入图片描述
一路next下去
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最后done即可

2.2安装HAL库

STM32 HAL固件库是Hardware Abstraction Layer的缩写,中文名称是:硬件抽象层。HAL库是ST公司为STM32的MCU最新推出的抽象层嵌入式软件,为更方便的实现跨STM32产品的最大可移植性。HAL库的推出,可以说ST也慢慢的抛弃了原来的标准固件库,这也使得很多老用户不满。但是HAL库推出的同时,也加入了很多第三方的中间件,有RTOS,USB,TCP / IP和图形等等。
和标准库对比起来,STM32的HAL库更加的抽象,ST最终的目的是要实现在STM32系列MCU之间无缝移植,甚至在其他MCU也能实现快速移植。
并且从16年开始,ST公司就逐渐停止了对标准固件库的更新,转而倾向于HAL固件库和 Low-layer底层库的更新,停止标准库更新,也就表示了以后使用STM32CubeMX配置HAL/LL库是主流配置环境。
在这里插入图片描述
点击HELP->Manage embedded software packages
在这里插入图片描述
勾选上你要安装的HAL库, 点击“Install Now” 直到安装成功。
在这里插入图片描述
在这里插入图片描述

2.3新建项目

在这里插入图片描述
选择自己所需要的芯片(博主使用型号为STM32F103ZET6)

在这里插入图片描述
在这里插入图片描述
然后配置时钟,进入上面的rcc,有两个时钟,一个是hse和lse,我们要用是GPIO接口,而这些接口都在APB2里
在这里插入图片描述
在这里插入图片描述
然后选择自己需要的引脚(博主个人使用了PA5,PB9,PC13)
在这里插入图片描述
点击project manager,配置好自己的路径和项目名,然后IDE那项改为MDK-ARM
在这里插入图片描述
进入 code generate界面,选择生成初始化.c/.h文件,后面点击generate code,选择open project,然后就到KEIL5了
在这里插入图片描述
在这里插入图片描述

2.4keil仿真调试

点击OpenProject,打开main.c文件,找到主函数,将以下代码替换到while循环中
在这里插入图片描述

HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_RESET);//PA5亮灯
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_SET);//PB9熄灯
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);//PC13熄灯
HAL_Delay(1000);//延时1s
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_SET);//PA5熄灯
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_RESET);//PB9亮灯
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);//PC13熄灯
HAL_Delay(1000);//延时1s		
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_5,GPIO_PIN_SET);//PA5熄灯
HAL_GPIO_WritePin(GPIOB,GPIO_PIN_9,GPIO_PIN_SET);//PB9熄灯
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);//PC13亮灯
HAL_Delay(1000);//延时1s

在这里插入图片描述
build成功
找到生成的hex文件进行烧录(编译前需要点击魔法棒按钮在output中选择创建hex文件,具体操作可看我上一篇博客,在此不再赘述)
在这里插入图片描述
点亮成功!
在这里插入图片描述

2.5观察GPIO端口的输出波形

Target界面中,选择跟正确的晶振大小,我使用的是8MHz的外部晶振。这个选项在软件仿真中起到很重要的作用,如果选择错误,那么波形一定是错误的,因为时间不准确。
在这里插入图片描述

Debug页的设置:
在这里插入图片描述
然后进行调试
在这里插入图片描述
选择逻辑分析仪
在这里插入图片描述
选择要观察的引脚:

点击Setup Logic Analyzer,按红叉旁边的按钮,添加,注意Display Type选择bit,在ShiftRight里输入引脚。
在这里插入图片描述
在这里插入图片描述
832ddf.png)
在这里插入图片描述
运行程序查看波形图
在这里插入图片描述
引脚为低电平的灯亮,高电平的灯不亮,高低电平转换周期(LED闪烁周期)为1s左右。

至此结束

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值