用寄存器&HAL库完成LED流水灯程序

一、了解stm32和寄存器

1、什么是寄存器

寄存器的功能是存储二进制代码,它是由具有存储功能的触发器组合起来构成的。一个触发器可以存储1位二进制代码,故存放n位二进制代码的寄存器,需用n个触发器来构成。
按照功能的不同,可将寄存器分为基本寄存器和移位寄存器两大类。基本寄存器只能并行送入数据,也只能并行输出。移位寄存器中的数据可以在移位脉冲作用下依次逐位右移或左移,数据既可以并行输入、并行输出,也可以串行输入、串行输出,还可以并行输入、串行输出,或串行输入、并行输出,十分灵活,用途也很广。
寄存器也就是CPU的一个储存器,当CPU转到寄存器这个地方的时候,就从寄存器里面拿出寄存的东西。

2、寄存器的映射

在存储器Block2 这块区域,设计的是片上外设,它们以四个字节为一个单元,共32bit,每一个单元对应不同的功能,当我们控制这些单元时就可以驱动外设工作。我们可以找到每个单元的起始地址,然后通过C 语言指针的操作方式来访问这些单元,如果每次都是通过这种地址的方式来访问,不仅不好记忆还容易出错,这时我们可以根据每个单元功能的不同,以功能为名给这个内存单元取一个别名,这个别名就是我们经常说的寄存器,这个给已经分配好地址的有特定功能的内存单元取别名的过程就叫寄存器映射。

3、stm的地址映射

片上外设区分为四条总线,根据外设速度的不同,不同总线挂载着不同的外设,APB1挂载低速外设,APB2和AHB挂载高速外设。相应总线的最低地址我们称为该总线的基地址,总线基地址也是挂载在该总线上的首个外设的地址。APB1总线的地址最低,因此片上外设就从这这个地址开始,也称外设基地址。

总线基地址
从存储器映射那张图的Block2可以看到,分为4大块,每块都有一个起始地址,这个起始地址就是基地址,然后到下一块起始地址的时候就会和前一块地址出现偏差,这个差值就是偏移量,即相对基地址的偏移量。如下图所示。
在这里插入图片描述
从上图可以看到APB1总线基地址是0x4000 0000,相对外设基地址的偏移量是0,所以此总线也是外设Block2的基地址。
外设基地址
每条总线上都会挂接着很多的外设,这些外设也会有自己的地址范围,XXX 外设的首个地址即最低地址就是XXX外设的基地址,也称作XXX边界地址。有关STM32F1xx 外设的具体边界地址可以参考《STM32F1xx 中文参考手册》P28页, 里面有详细的介绍。 这里我们就以GPIO外设来讲解外设基地址。其他的外设也是同样分析。GPIO外设基地址如下图所示。
在这里插入图片描述
从图中可以知道,外设GPIOx都是挂接在APB2总线上,属于高速的外设,而APB2总线的基地址是0x4001 0000,故GPIOA的相对APB2总线的地址偏移是800。
外设寄存器地址
XXX外设的寄存器就分布在其对应的外设地址范围内。这里我们以GPIO外设为例,GPIO是通用输入输出端口的简称,可以通过软件来控制其输入和输出。GPIO有很多个寄存器,每一个都有特定的功能。每个寄存器为32bit,占四个字节,这些寄存器都是按顺序依次排列在外设的基地址上。寄存器的位置都以相对该外设基地址的偏移地址来描述。这里我们以GPIOC端口为例,来说明GPIO都有哪些寄存器,如下图所示。
在这里插入图片描述

二、新建代码及编写

1、程序创建

在上方操作栏建立一个新的工程文件
在这里插入图片描述
选择芯片:
在这里插入图片描述
如下图打上勾在这里插入图片描述
右键单击Target1,点击mannage project item…
在这里插入图片描述
添加一个startup来启动文件,code来放用户代码:
在这里插入图片描述
在code文件创建一个.c和.h文件
在这里插入图片描述
在这里插入图片描述

2、代码书写

(1)、.c文件代码

#include "led.h"

//定义软件延时函数
void Delay()
{
    
    
	int i = 0;
	for(;i<6000000; i++);
}

int main()
{
    
    
	RCC_APB2ENR |= (1<<2|1<<3|1<<4);//使能GPIO端口时钟
	GPIOA_CRL |= (0X01<<20);//配置GPIOA_5引脚的模式
	GPIOB_CRH |= (0X01<<4);//配置GPIOB_9引脚的模式
	GPIOC_CRH |= (0X01<<20);//配置GPIOC_13引脚的模式
	GPIOA_ODR &= ~(0X01<<5);//设置几个引脚的初始状态
	GPIOB_ODR &= ~(0X01<<9);
	GPIOC_ODR &= ~(0X01<<13);
	while(1)
	{
    
    
		Delay();
		GPIOA_ODR |= 1<<5;
		GPIOB_ODR &= ~(0X01<<9);
		GPIOC_ODR &= ~(0X01<<13);
		Delay();
		GPIOA_ODR &= ~(0X01<<5);
		GPIOB_ODR |= (1<<9);
		GPIOC_ODR &= ~(0X01<<13);
		Delay();
		GPIOA_ODR &= ~(0X01<<5);
		GPIOB_ODR &= ~(0X01<<9);
		GPIOC_ODR |= (1<<13);
		Delay();
	}
	
}

(2)、.h文件代码

#ifndef __LED_H__
#define __LED_H__

//片上外设基地址  
#define PERIPH_BASE           ((unsigned int)0x40000000)
//APB1 总线基地址 	
#define APB1PERIPH_BASE       PERIPH_BASE 
//APB2 总线基地址 
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x10000)
//AHB总线基地址 
#define AHBPERIPH_BASE        (PERIPH_BASE + 0x20000)
//RCC外设基地址
#define RCC_BASE              (AHBPERIPH_BASE + 0x1000)
//GPIOA外设基地址
#define GPIOA_BASE			  (APB2PERIPH_BASE + 0x0800)
//GPIOB外设基地址
#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00)
//GPIOC外设基地址
#define GPIOC_BASE			  (APB2PERIPH_BASE + 0x1000)
//APB2使能时钟寄存器
#define RCC_APB2ENR			  *(unsigned int*)(RCC_BASE + 0x18)
//GPIOx的端口配置低寄存器
#define GPIOA_CRL			  *(unsigned int*)(GPIOA_BASE + 0x00)
#define GPIOB_CRL	   		  *(unsigned int*)(GPIOB_BASE + 0x00)
#define GPIOC_CRL			  *(unsigned int*)(GPIOC_BASE + 0x00)
//GPIOx的端口配置高寄存器
#define GPIOA_CRH		 	  *(unsigned int*)(GPIOA_BASE + 0x04)
#define GPIOB_CRH			  *(unsigned int*)(GPIOB_BASE + 0x04)
#define GPIOC_CRH			  *(unsigned int*)(GPIOC_BASE + 0x04)
//GPIOx的端口输出寄存器
#define GPIOA_ODR			  *(unsigned int*)(GPIOA_BASE + 0x0C)
#define GPIOB_ODR			  *(unsigned int*)(GPIOB_BASE + 0x0C)
#define GPIOC_ODR			  *(unsigned int*)(GPIOC_BASE + 0x0C)

#endif

三、软件仿真及分析

先对keil一些配置进行修改
在这里插入图片描述
输出波形:
在这里插入图片描述

四、参考资料

https://blog.csdn.net/chenhuanqiangnihao/article/details/113863846
https://blog.csdn.net/wer4567/article/details/127227743?spm=1001.2014.3001.5502

猜你喜欢

转载自blog.csdn.net/qq_54761976/article/details/127242136
今日推荐