寄存器与静态库

tip:寄存器与库函数具有同等重要的地位,在使用时没有优劣之分,笔者往往都是混合编程。


前言

读者在学习8位单片机时是否经历过记忆大量寄存器的经历呢?在STM32中具有更多的寄存器,所以出现了各种库,方便人们去使用。这次我们基于正点原子精英版跑马灯(STM32F103)例程讲解 寄存器,STD库之间不同与相同。

一、寄存器与静态库都是什么?

1.寄存器

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

2.静态库

静态库是指在我们的应用中,有一些公共代码是需要反复使用,就把这些代码编译为“库”文件;在链接步骤中,连接器将从库文件取得所需的代码,复制到生成的可执行文件中的这种库。STM32的静态库主要是将寄存器操作封装至C语言函数之中,方便人们去调用。

二、寄存器例程

0.准备阶段

(1)STM32参考手册.PDF
(2)寄存器模板例程

1.目标任务拆分

(1)初始化系统时钟
(2)LED灯初始化
(3)LED灯闪烁

2.目标实现

初始化时钟

我们翻取手册可得到这样的一张时钟图,我们最终要设置的是SYSCLK,PCLK1,PCLK2。于是我们可以通过这样的一条路径去设置
在这里插入图片描述
按照顺序为使能外部时钟、设置PLL、使能PLL、选择PLL为系统时钟。

我们先要设置时钟源为外部时钟(HSE)
查看手册找到RCC->CR寄存器在这里插入图片描述在这里插入图片描述

我们只需要将HSE设置为 1就行。
所以我们

RCC->CR|=0X00010000;

打开寄存器版本的例程,发现例程使用的是自己编写的函Stm32_Clock_Init(9);我们将其展开查看
发现相同。接下来只需要依照图上的线以此编写就可写出下列代码。

void Stm32_Clock_Init(u8 PLL)
{
	unsigned char temp=0;   
	MYRCC_DeInit();		  //复位并配置向量表
 	RCC->CR|=0x00010000;  //外部高速时钟使能HSEON
	while(!(RCC->CR>>17));//等待外部时钟就绪
	RCC->CFGR=0X00000400; //APB1=DIV2;APB2=DIV1;AHB=DIV1;
	PLL-=2;				  //抵消2个单位(因为是从2开始的,设置0就是2)
	RCC->CFGR|=PLL<<18;   //设置PLL值 2~16
	RCC->CFGR|=1<<16;	  //PLLSRC ON 
	FLASH->ACR|=0x32;	  //FLASH 2个延时周期(这块属于FLASH操作部分,此处不予讲解)
	RCC->CR|=0x01000000;  //PLLON
	while(!(RCC->CR>>25));//等待PLL锁定
	RCC->CFGR|=0x00000002;//PLL作为系统时钟	 
	while(temp!=0x02)     //等待PLL作为系统时钟设置成功
	{   
		temp=RCC->CFGR>>2;
		temp&=0x03;
	}    
}	   

最后控制一系列寄存器确定使用外部时钟输入后经过PLL倍频9倍后的72MHz作为系统时钟
AHB与APB1时钟为72MHz,APB2为36MHz

LED灯初始化

LED灯操作其实就是对GPIO的电平进行操作。
GPIO属于STM32的外设,在默认情况下STM32会关闭不需要的外设时钟来减少功耗,所以控制GPIO的第一步是打开GPIO的时钟,查询系统结构能看见GPIOB与GPIOE都挂载在APB2总线下;
在这里插入图片描述时钟控制为RCC寄存器组:查询手册可以看到APB2外设时钟使能寄存器。
在这里插入图片描述查看它的第3位与第6位。
在这里插入图片描述
所以写出

RCC->APB2ENR|=1<<3;
RCC->APB2ENR|=1<<6;

到此为止GPIOB、GPIOE已经具备了运行的条件,下一步我们需要配置GPIO,让他实现输出功能。因为要实现闪烁功能这里使用开漏模式与推挽模式均可,这里以推挽模式为例。
我们可以在手册中找到端口配置寄存器在这里插入图片描述
低寄存器是配置GPIOX 0~7,高寄存器是配置GPIOX 8~16
这里我们使用的是GPIOB5与GPIOE5
在这里插入图片描述
依照上表能写出

	GPIOB->CRL&=0XFF0FFFFF; 
	GPIOB->CRL|=0X00300000;//PB.5 推挽输出  
	GPIOE->CRL&=0XFF0FFFFF; 
	GPIOE->CRL|=0X00300000;//PE.5 推挽输出  

这样GPIO就可以使用了,我们初始化的时候可以给这个GPIO设一个初始值。我们查询手册能找到端口输出数据寄存器在这里插入图片描述我们这里都设置为高电平

	GPIOE->ODR|=1<<5;      //PE.5输出高
	GPIOB->ODR|=1<<5;      //PB.5 输出高

到此为止GPIO就初始化完成了

LED灯闪烁

LED灯闪烁从根本上说就是GPIO的电平变换,我们可以每次改变GPIOX->ODR的值,但是代码量多了就会变得可读性极为差,所以我们引入Cortex M3架构支持的位带操作

#include "sys.h"
#define LED0 PBout(5)// PB5
#define LED1 PEout(5)// PE5

我们现在就可以用

LED0=1;//	LED0灯灭
LED1=0;//	LED1灯亮

LED1=1;//	LED1灯灭
LED0=0;//	LED0灯灭

控制灯的闪烁。
但是闪烁时间过快我们还需要在其中加入延时。我们可以像51那样在两个之间加入36M次的运算来实现半秒的延时,我们这里使用正点原子配套的delay程序来延时,delay程序具体实现方法为调用内部systick时钟进行定时,想了解的小伙伴可以自行学习,这里不过多讲述。
delay中包含个函数

函数原型函数功能
void delay_init(void);初始化delay模块
void delay_ms(u16 nms);延时nms毫秒
void delay_us(u32 nus)延时nus微秒

所以我们可以写出循环闪烁的代码

	delay_init();
	while(1)
	{
		LED0=1;//	LED0灯灭
		LED1=0;//	LED1灯亮
		delay_ms(500);
		LED1=1;//	LED1灯灭
		LED0=0;//	LED0灯灭
		delay_ms(500);
	}

这样我们的寄存器版本程序就写完了,可以发现我们全程都在查表、编写,同时我们的操作也是直接对底层寄存器进行操作。

三、库函数例程

我们先看一下库函数的例程构成那么库函数是怎么控制底层的呢?我们看一下手册可以发现在这里插入图片描述用户去调用FWLib组与Hardware中的函数从而间接的控制寄存器。
首先要提一下, 在固件库中, GPIO 端口操作对应的库函数函数以及相关定义在文件stm32f10x_gpio.h 和 stm32f10x_gpio.c 中。

0.准备阶段

(1)STM32F1开发指南-库函数版本.pdf
(2)库函数模板例程

1.目标任务拆分

(1)初始化系统时钟
(2)LED灯初始化
(3)LED灯闪烁

2.目标实现

初始化时钟

标准库例程打开并未发现有时钟初始化代码,那么初始化去哪里了呢?
仔细查看我们发现库函数有一个使用汇编写好的startup_stm32f10x_hd.s文件
打开查看发现这一行

Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  __main
                IMPORT  SystemInit
                LDR     R0, =SystemInit
                BLX     R0               
                LDR     R0, =__main
                BX      R0
                ENDP

单片机上电后会产生复位,依据此段代码可知,首先运行SystemInit的代码,再运行main主函数。
打SystemInit发现逻辑顺序与寄存器版本无差别,而且都是操作寄存器。

LED灯初始化

GPIO 相关的函数和定义分布在固件库文件 stm32f10x_gpio.c 和头文件 stm32f10x_gpio.h 文件中。在固件库开发中, 操作寄存器 CRH 和 CRL 来配置 IO 口的模式和速度是通过 GPIO 初始化
函数完成:

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)

这个函数有两个参数, 第一个参数是用来指定 GPIO,取值范围为 GPIOA~GPIOG。第二个参数为初始化参数结构体指针,结构体类型为 GPIO_InitTypeDef。下面我们看看这个结构体的定义。

typedef struct
{
 uint16_t GPIO_Pin;
 GPIOSpeed_TypeDef GPIO_Speed;
 GPIOMode_TypeDef GPIO_Mode;
}GPIO_InitTypeDef;

具体操作在上次推送中有详细解释,这里不做赘述。
最终我们可以编写出一下代码


 GPIO_InitTypeDef  GPIO_InitStructure;
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOE, ENABLE);	 //使能PB,PE端口时钟
	
 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;				 //LED0-->PB.5 端口配置
 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; 		 //推挽输出
 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		 //IO口速度为50MHz
 GPIO_Init(GPIOB, &GPIO_InitStructure);					 //根据设定参数初始化GPIOB.5
 GPIO_SetBits(GPIOB,GPIO_Pin_5);						 //PB.5 输出高

 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;	    		 //LED1-->PE.5 端口配置, 推挽输出
 GPIO_Init(GPIOE, &GPIO_InitStructure);	  				 //推挽输出 ,IO口速度为50MHz
 GPIO_SetBits(GPIOE,GPIO_Pin_5); 						 //PE.5 输出高

LED灯闪烁

在固件库中设置 ODR 寄存器的值来控制 IO 口的输出状态是通过函数 GPIO_Write 来实现
的:

函数名函数举例函数功能
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);GPIO_SetBits(GPIOB,GPIO_Pin_5);将GPIOx的GPIO_Pin口设置为高电平
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);GPIO_ResetBits(GPIOB,GPIO_Pin_5);将GPIOx的GPIO_Pin口设置为低电平

再加上正点原子提供的delay_ms函数可写出闪烁代码

	delay_init();		  //初始化延时函数
	while(1)
	{
			GPIO_ResetBits(GPIOB,GPIO_Pin_5);  //LED0对应引脚GPIOB.5拉低,亮  等同LED0=0;
			GPIO_SetBits(GPIOE,GPIO_Pin_5);   //LED1对应引脚GPIOE.5拉高,灭 等同LED1=1;
			delay_ms(300);  		   //延时300ms
			GPIO_SetBits(GPIOB,GPIO_Pin_5);	   //LED0对应引脚GPIOB.5拉高,灭  等同LED0=1;
			GPIO_ResetBits(GPIOE,GPIO_Pin_5); //LED1对应引脚GPIOE.5拉低,亮 等同LED1=0;
			delay_ms(300);                     //延时300ms
	}

当然也可以和寄存器一样使用位带操作控制LED灯,和寄存器版本相同先定义后控制

	delay_init();	    //延时函数初始化	  
	while(1)
	{
		LED0=!LED0;
		LED1=!LED1;
		delay_ms(300);	 //延时300ms
	}

这样我们程序算是写
完啦,可以发现我们的查表量减少了很多,而且从函数名称我们也能直观的了解这个函数的功能,不需要了解抽象的寄存器。极大的精简了编程环节。

两者比较

名称移植性阅读性代码空间执行效率
标准库++++++++
寄存器+++++

库函数相比于寄存器在编写、阅读与移植方面较高的优势,而寄存器在代码所占空间执行效率方面也有绝对性的优势,我们不能仅仅通过某方面评判某种编译方式的优劣,而要取长补短,在实时性强的地方使用寄存器,在对阅读性与移植有要求的地方尽可能使用库函数。这样我们的单片机才能充分的发挥他的功能。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值