STM32 基于寄存器&标准外设库的LED流水灯

STM32 基于寄存器&标准外设库的LED流水灯

引言:

STM32是一款广泛应用于嵌入式系统开发的微控制器系列,具有高性能、低功耗和丰富的外设功能。在STM32的开发过程中,我们可以选择使用基于寄存器或标准外设库的编程方式。LED流水灯是一种常见的嵌入式开发实践项目,通过控制多个LED灯的亮灭顺序,展示出流动的效果。本文将介绍如何使用STM32的基于寄存器或标准外设库的编程方式实现LED流水灯效果。无论您是初学者还是有一定经验的开发者,本文都将为您提供详细的步骤和示例代码,帮助您快速上手STM32的开发工作。无论您选择使用基于寄存器还是标准外设库的编程方式,都能够实现出色的LED流水灯效果,为您的嵌入式系统增添亮点。让我们开始这个有趣而有挑战性的STM32开发之旅吧!

~ ₍ᐢ…ᐢ₎♡ ~ ₍ᐢ…ᐢ₎♡ ~ ₍ᐢ…ᐢ₎♡

一、创建一个Stm32 的基本工程

创建一个 Stm32工程 为基本项目,本文不给予介绍

下面给出一个大佬链接
૮(˶ᵔ ᵕ ᵔ˶)ა 
希望大家自行参考:https://blog.csdn.net/weixin_44631044/article/details/114302471

二、使用基于寄存器实现 -> LED流水灯

1、寄存器讲解

STM32寄存器是指STM32微控制器中的特殊功能寄存器(Special Function Registers,简称SFR),用于控制和配置微控制器的各种功能和外设。寄存器是一种存储器元素,用于存储和操作数据。

  • STM32微控制器的寄存器可以分为通用寄存器和特殊功能寄存器两类。

    1. 通用寄存器:包括通用寄存器组和堆栈指针寄存器。通用寄存器组包括R0~R15,用于存储临时数据和计算结果。堆栈指针寄存器(SP)用于指示堆栈的当前位置。

    2. 特殊功能寄存器:包括控制寄存器、状态寄存器、数据寄存器等。这些寄存器用于配置和控制微控制器的各种外设和功能模块,如GPIO控制寄存器、定时器控制寄存器、串口控制寄存器等。

  • 在STM32寄存器编址方案中,每个寄存器都有一个唯一的地址,通过读写这些地址来操作寄存器。寄存器的位域(bit field)用于表示寄存器中的各个位的功能和含义,可以通过设置或清除位域来配置寄存器的不同功能。

2、GPIO讲解

GPIO是通用输入输出端口(General-purpose input/output)的英文简写,是所有的微控制器必不可少的外设之一,可以由STM32直接驱动从而实现与外部设备通信、控制以及采集和捕获的功能。STM32单片机的GPIO被分为很多组,每组有16个引脚,不同型号的MCU的GPIO个数是不同的,比如STM32F103C8T6只有PA、PB以及个别PC引脚而STM32F103ZET6拥有PA~PG的全部112个引脚。所有的GPIO都有基本的输入输出功能,同时GPIO还可以作为其它的外设功能引脚。

作为STM32最基本的外设,GPIO最基本的输出功能是由STM32控制 引脚输出高低电平,比如可以把GPIO接LED灯来控制其亮灭,也可以接继电器或者三极管,通过继电器或三极管来控制外部大功率电路的通断。

  • GPIO的硬件结构框图

在这里插入图片描述

3、GPIO工作模式

STM32的GPIO共有8种工作模式,分别是输入模式的模拟输入、上拉输入、下拉输入和浮空输入以及输出模式的推挽输出、开漏输出、推挽复用输出和开漏复用输出

在这里插入图片描述

为了便于理解,使用结构框图来详细讲解每一种模式:

  • 浮空输入模式

img

GPIO作为输入功能的浮空输入时,电信号使由外部流向内部的,从结构图的右侧往左侧看,信号流经顺序是①端口——②施密特触发器——③输入数据寄存器——④读取

  • 上拉输入模式

img

上拉输入和浮空输入的区别就是在第①和第②之间多了一个上拉电阻,这样GPIO在没有连接外部部件时的默认电平是高电平,其它流程和原来一样。

  • 下拉输入模式

img

下拉输入和浮空输入的区别就是在第①和第②之间多了一个下拉电阻,这样GPIO在没有连接外部部件时的默认电平是低电平,其它流程和原来一样。

  • 模拟输入模式

img

模拟输入模式和其它三种输入模式不同,它的外部电平信号没有流入输入数据寄存器,而是直接流入模拟输入部分。模拟输入一般是用来ADC读取和转换的。

  • 开漏输出模式

img

GPIO 的输出模式比输入模式复杂,首先看开漏输出模式,电平信号由STM32内部流出引脚,因此流向是①写(包括位设置/清除寄存器、输出数据寄存器)——②输出控制电路——③N-MOS管——④I/O端口

  • 开漏复用输出模式

img

开漏复用输出和开漏输出的区别在于信号来源,复用的来源不是内部直接通过输出数据寄存器写的,而是由复用功能的外设决定的。

  • 推挽输出模式

img

推挽输出模式和开漏输出模式有一定的区别,其控制输出的寄存器是一样的,但是②部分的写1有效,即输出控制电路输出1的时候,P-MOS管导通,N-MOS管截止,这样I/O口电平就会被P-MOS管拉高,输出强高电平;相反,当输出控制电路输出0时,P-MOS管截止,N-MOS管导通,I/O端口电平被N-MOS管拉低,输出强低电平。同样,输出的电平信号可以被输入数据寄存器读取。

  • 推挽复用输出模式

img

推挽复用输出和推挽输出的区别在于信号来源,其信号来源是由复用功能相关的通信通道来控制。

5、快速入门STM32 GPIO的配置寄存器(CRL、CRH)

  1. 问题

    在使用STM32的时候配置GPIO是最常见的操作,可以使用比较简单明白的库函数配置,但很繁杂。使用寄存器的方式可以快速配置,对于同一个IO口的输入输出都需要使用到的时候,比如IIC通讯的SDA接口就是要输出和检测输入。

我们在很多工程都能看到比如下面的一些代码:

//IO方向设置
#define SDA_IN()  {GPIOA->CRL&=0XFFFFFFF0;GPIOA->CRL|=8;}
#define SDA_OUT() {GPIOA->CRL&=0XFFFFFFF0;GPIOA->CRL|=3;}

代码:GPIOA->CRL&=0XFFFFFFF0;GPIOA->CRL|=8;是什么意思呢?意思很简单就是配置IO的工作模式嘛!至于是怎么来写,我以前一直没弄懂,现在弄懂了记录下来。

  1. GPIO的配置寄存器CRL和CRH

STM32的一组GPIO有16个IO口,比如GPIOA这一组,有GPIOA0~GPIOA15一共16个IO口。每一个IO口需要寄存器的4位用来配置工作模式。

那么一组GPIO就需要16x4=64位的寄存器来存放这一组GPIO的工作模式的配置,但STM32的寄存器都是32位的,所以只能使用2个32位的寄存器来存放了。CRL用来存放低八位的IO口(GPIOx0—GPIOx7)的配置,CRH用来存放高八位的IO口(GPIOx8—GPIOx15)的配置。

这两个寄存器的全称是:端口配置低寄存器(GPIOx_CRL) (x=A…E) 和 端口配置高寄存器(GPIOx_CRH) (x=A…E)

也就是每一组GPIO都有两个32位的寄存器是用来配置IO口的工作模式的。

我们都清楚STM32的GPIO有八种工作模式,4个二进制数可以组合出16种情况,而我们只需要8种就行了。至于4位数怎么组合是什么工作模式,我们看STM32的手册。

3、工作模式的配置
我们直接看手册的说明:

在这里插入图片描述

在这里插入图片描述

可以看出,4位中又分为了CNFy和MODEy(y表示这组GPIO的第几个IO口),现在我们分析这两个的作用。
MODEy:

00:输入模式(复位后的状态) 
01:输出模式,最大速度10MHz 
10:输出模式,最大速度2MHz 
11:输出模式,最大速度50MHz

可以看出MODEy是用来配置是输出还是输入模式的。一般是使用00和11这两种情况。00是输入模式,11是输出模式。

CNFy:

在输入模式(MODE[1:0]=00):
00:模拟输入模式
01:浮空输入模式(复位后的状态) 
10:上拉/下拉输入模式
11:保留 
在输出模式(MODE[1:0]>00):
00:通用推挽输出模式
01:通用开漏输出模式
10:复用功能推挽输出模式
11:复用功能开漏输出模式

这些就是CNFy的配置,配置具体的工作模式。配合MODEy就可以配置出所有的工作模式了。
比如我需要配置上拉输入模式,那么4位寄存器的配置就是CNFy【10】MODEy【00】:1000换成十进制数就是8。

GPIOA->CRL&=0XFFFFFFF0;GPIOA->CRL|=8;

所以这段代码的意思就是将GPIOA0配置成上拉(下拉)输入模式。

根据上面的学习,我们可以写出一下的寄存器版代码

4、寄存器版代码–流水灯

#define RCC_AP2ENR	*((unsigned volatile int*)0x40021018)
	//----------------GPIOA配置寄存器 ------------------------
#define GPIOA_CRL	*((unsigned volatile int*)0x40010800)
#define	GPIOA_ORD	*((unsigned volatile int*)0x4001080C)
//----------------GPIOB配置寄存器 ------------------------
#define GPIOB_CRH	*((unsigned volatile int*)0x40010C04)
#define	GPIOB_ORD	*((unsigned volatile int*)0x40010C0C)
//----------------GPIOC配置寄存器 ------------------------
#define GPIOC_CRH	*((unsigned volatile int*)0x40011004)
#define	GPIOC_ORD	*((unsigned volatile int*)0x4001100C)
//-------------------简单的延时函数-----------------------

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

//------------------------主函数--------------------------
int main()
{
	RCC_AP2ENR|=1<<2;			//APB2-GPIOA外设时钟开启
	RCC_AP2ENR|=1<<3;			//APB2-GPIOB外设时钟开启
	RCC_AP2ENR|=1<<4;			//APB2-GPIOC外设时钟开启

	// A灯
	GPIOA_CRL&=0xFF0FFFFF;		//设置位 清零	
	GPIOA_CRL|=0x00200000;		//PA12推挽输出
	GPIOA_ORD|=0<<5;			//设置初始灯为灭
	// B灯
	GPIOB_CRH&=0xFFFFF0FF;		//设置位 清零	
	GPIOB_CRH|=0x00000200;		//PB1推挽输出
	GPIOB_ORD|=0<<10;			//设置初始灯为灭
	// C灯
	GPIOC_CRH&=0xFF0FFFFF;		//设置位 清零
	GPIOC_CRH|=0x00200000;   	//PC14推挽输出
	GPIOC_ORD|=0<<13;			//设置初始灯为灭
		
	while(1)
	{	
		GPIOA_ORD=0x1<<5;		//PA5高电平	
		Delay_ms(1000);
		GPIOA_ORD=0x0<<5;		//PA5低电平
		Delay_ms(1000);
		
		GPIOB_ORD=0x1<<10;		//PB10高电平	
		Delay_ms(1000);
		GPIOB_ORD=0x0<<10;		//PB10低电平
		Delay_ms(1000);
		
		GPIOC_ORD=0x1<<13;		//PC13高电平	
		Delay_ms(1000);
		GPIOC_ORD=0x0<<13;		//PC13低电平
		Delay_ms(1000);
		
	}
}

5、结果展示

VID_20231014_103543

三、使用基于寄存器实现 -> LED流水灯

代码: main.c

#include "stm32f10x.h" // Device header
#include "Delay.h"

#include "LED.h"


int main(void)
{
    LED_Init();
	


    while (1)
    {
		
		
		GPIO_WriteBit(GPIOA,GPIO_Pin_5,1);
		Delay_ms(1000);
		GPIO_WriteBit(GPIOA,GPIO_Pin_5,0);
		Delay_ms(1000);

		GPIO_WriteBit(GPIOB,GPIO_Pin_10,1);
		Delay_ms(1000);
		GPIO_WriteBit(GPIOB,GPIO_Pin_10,0);
		Delay_ms(1000);

		GPIO_WriteBit(GPIOC,GPIO_Pin_13,1);
		Delay_ms(1000);
		GPIO_WriteBit(GPIOC,GPIO_Pin_13,0);
		Delay_ms(1000);		
		
    }
}

代码: LED.c

void LED_Init()
{
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);    // 打开外设,使能GPIOC端口时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);    // 打开外设,使能GPIOC端口时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);    // 打开外设,使能GPIOC端口时钟
	
	// 配置  端口数据
	GPIO_InitTypeDef GPIO_InitStructure;
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;		 // 配置端口
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;// 配置IO口的速度
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;		 // 配置端口
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;// 配置IO口的速度
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;		 // 配置端口
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;// 配置IO口的速度
	GPIO_Init(GPIOC,&GPIO_InitStructure);
	

	GPIO_ResetBits(GPIOA,GPIO_Pin_5); // 设置初始熄灭
	GPIO_ResetBits(GPIOB,GPIO_Pin_10); // 设置初始熄灭
	GPIO_ResetBits(GPIOC,GPIO_Pin_13); // 设置初始熄灭
	
}

结果展示:

VID_20231014_104233

KEIL的软件仿真分析仪观察GPIO的波形

QQ录屏20231014105702

四、两种方式对比

基于寄存器

  • 直接操作GPIO相关寄存器实现LED控制,如GPIOx_MODER、GPIOx_ODR等
  • 代码更低级,接近硬件寄存器操作
  • 控制更细致,可以实现更复杂的LED动画效果
  • 需要了解STM32单片机GPIO相关所有寄存器
  • 代码难度较大,调试和维护也较复杂

基于固件库

  • 使用STM32官方固件库GPIO相关函数实现LED控制,如HAL_GPIO_WritePin()等
  • 代码更高级,抽象掉了寄存器操作细节
  • 控制相对简单,主要实现简单的LED亮灭
  • 不需要了解STM32底层GPIO寄存器
  • 代码难度小,开发和调试更简单
  • 但动画效果实现难度较大,需要更多固件库支持

五、总结

总的来说,基于寄存器的方法更加底层、灵活和精细,适合对硬件有更深入了解和更高级的控制需求。而基于固件库的方法更加高级、简洁和易于理解,适合快速开发和简单应用。选择哪种方法取决于个人的编程经验、项目需求和时间限制。通过这个实验,我对STM32的寄存器操作和HAL库的使用有了更深入的了解,并且能够根据项目需求选择合适的开发方法。

最后感谢大佬友情链接:

  • https://blog.csdn.net/qq_39530692/article/details/130835922
  • https://blog.csdn.net/qq_44016222/article/details/123206403
  • https://blog.csdn.net/qq_43279579/article/details/110320013
  • https://blog.csdn.net/weixin_45915259/article/details/123878323
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

LX很爱吃葱

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

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

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

打赏作者

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

抵扣说明:

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

余额充值