STM32学习笔记(2)-点亮LED(分析GPIO口寄存器)

仅供个人学习,记录学习STM32中的要点,加深对知识的巩固

知识回顾

上一节创建了一个新的工程,那么我们在上一节的基础上通过对寄存器的控制来实现对LED的亮灭。

笔者所使用STM32开发板控制LED灯为高电平点亮,低电平熄灭。原理如下图所示:

alt

芯片上所对应的管脚(PA1-PA4):

alt

话不多说,这里直接上干货:

GPIO口寄存器

GPIO口寄存器是STM32微控制器中与GPIO口相关的寄存器,它们用于控制和配置GPIO口的输入和输出。不同的GPIO口寄存器有不同的功能,以下是一些常用GPIO口寄存器的功能介绍:

  1. GPIOx_CRH和GPIOx_CRL寄存器:这两个寄存器用于控制GPIO口的输入/输出模式、输出速率、上拉/下拉电阻和复用功能等。例如,可以使用GPIOx_CRH和GPIOx_CRL寄存器将GPIO口配置为推挽输出、开漏输出、浮空输入或带上拉/下拉电阻的输入。

  2. GPIOx_IDR寄存器:这个寄存器用于读取GPIO口的输入值。例如,可以使用GPIOx_IDR寄存器读取GPIO口的输入电平。

  3. GPIOx_ODR寄存器:这个寄存器用于设置GPIO口的输出值。例如,可以使用GPIOx_ODR寄存器将GPIO口的输出电平设置为高电平或低电平。

  4. GPIOx_BSRR寄存器:这个寄存器用于设置GPIO口的输出值,同时还可以通过它将GPIO口的输出值翻转。例如,可以使用GPIOx_BSRR寄存器将GPIO口的输出电平设置为高电平、低电平或翻转输出电平。

  5. GPIOx_BRR寄存器:这个寄存器用于将GPIO口的输出值设置为低电平。例如,可以使用GPIOx_BRR寄存器将GPIO口的输出电平设置为低电平。

  6. GPIOx_LCKR寄存器:这个寄存器用于锁定GPIO口的配置,防止意外更改。例如,可以使用GPIOx_LCKR寄存器锁定GPIO口的配置,以防止在运行时意外更改GPIO口的配置。

这里主要用的的是GPIOx_CRH和GPIOx_CRL寄存器。

  • GPIOx_CRH寄存器:用于控制GPIO口的高8位引脚的配置。每个引脚对应四位,共8个引脚,可以控制引脚的输入/输出模式、输出速率、上拉/下拉电阻和复用功能等。(PA8-PA15)

  • GPIOx_CRL寄存器:用于控制GPIO口的低8位引脚的配置。每个引脚对应四位,共8个引脚,可以控制引脚的输入/输出模式、输出速率、上拉/下拉电阻和复用功能等。(PA0-PA7)

GPIOx_CRH和GPIOx_CRL寄存器的各个位的功能如下:

  1. 输入/输出模式位(MODEy):用于设置GPIO口引脚的输入/输出模式,包括输入模式、输出模式、复用输入模式和复用输出模式。MODEy位共2位,分别控制引脚y和引脚y+1的输入/输出模式。

  2. 输出速率位(CNFy):用于设置GPIO口引脚的输出速率,包括通用推挽输出、开漏输出、复用推挽输出和复用开漏输出等。CNFy位共2位,分别控制引脚y和引脚y+1的输出速率。

  3. 上拉/下拉电阻位(CNFy):用于设置GPIO口引脚的上拉/下拉电阻,包括上拉电阻、下拉电阻和无上拉/下拉电阻等。CNFy位共2位,分别控制引脚y和引脚y+1的上拉/下拉电阻。

  4. 复用功能位(CNFy):用于设置GPIO口引脚的复用功能,例如选择复用为定时器输出或串口通信等。CNFy位共2位,分别控制引脚y和引脚y+1的复用功能。

RCC寄存器

RCC 寄存器是 STM32 微控制器中用于配置和控制系统时钟的寄存器。这些寄存器包括:

  1. RCC_CR (RCC Control Register):用于控制内部时钟和外部时钟源的开关,以及设置系统时钟源。

  2. RCC_CFGR (RCC Configuration Register):用于配置系统时钟源的分频和倍频因子,以及外部时钟源的类型和分频因子。

  3. RCC_CIR (RCC Clock Interrupt Register):用于管理时钟中断,包括内部和外部时钟源的错误检测和中断。

  4. RCC_APB2RSTR (APB2 Peripheral Reset Register):用于重置 APB2 总线上的外设。

  5. RCC_APB1RSTR (APB1 Peripheral Reset Register):用于重置 APB1 总线上的外设。

  6. RCC_AHBENR (AHB Peripheral Clock Enable Register):用于控制 AHB 总线上的外设时钟开关。

  7. RCC_APB2ENR (APB2 Peripheral Clock Enable Register):用于控制 APB2 总线上的外设时钟开关。

  8. RCC_APB1ENR (APB1 Peripheral Clock Enable Register):用于控制 APB1 总线上的外设时钟开关。

  9. RCC_BDCR (Backup Domain Control Register):用于控制备份域的开关和配置。

  10. RCC_CSR (Control and Status Register):用于配置系统复位和低功耗模式等。

本小节主要用到的寄存器主要有以下三个:

第一个寄存器-GPIOA_CRL寄存器

由于我们控制的端口为PA1-PA4,所以我们主需要注意端口配置低寄存器,即GPIOA_CRL寄存器。如图中红色数字1-4所示。

alt

本质上就是控制GPIOA_CRL寄存器的第4到第19位。

针对PA1而言,即第四位到第七位,要输出的话,将MODEy[1:0]设置为输出模式。这里我们选择11,最大速度50MHz,在输出模式下,点亮LED可以选择通用推挽输出模式,所以为00。

alt

最后,PA1的设置为0011。PA2-PA4设置同理,也为0011。如下图。

alt

我们的地址由基地址和偏移地址构成。我们从之前的图可以看出,GPIOA_CRL寄存器的偏移地址为0x00,那么他的基地址可以再STM32的芯片手册memory map中去查看。

alt

STM32是32位的处理器,他的空间大小为2322^{32}.即最大值0xFFFF FFFF。 我们可以找到

alt

所以,当前GPIOA_CRL的寄存器地址为

0x40010800+0x000x40010800+0x00

那么我们之后编程就会往这个地址中去写第四到第十九位0011,就找到了LED地址。

第二个寄存器-GPIOA_ODR寄存器-

alt

往这个寄存器的1到4位写1输入高电平,写0输入低电平。

所以,当前GPIOA_ODR的寄存器地址为

0x40010800+0x0C0x40010800+0x0C

第三个寄存器-RCC寄存器

我们需要用到哪个外设,就将哪个外设的时钟打开。

我们通过CPU通过地址去找外设,而地址又挂载在总线上,CPU通过管理总线来管理外设。 在STM32微控制器中,APB1和APB2是两种不同的外设总线,用于连接不同类型的外设。具体来说:

  1. APB1 (Advanced Peripheral Bus 1) 是用于连接低速外设的总线,包括I2C、SPI、USART、USB等外设。APB1总线的时钟频率通常为APB2总线时钟频率的一半,最高可达42MHz。

  2. APB2 (Advanced Peripheral Bus 2) 是用于连接高速外设的总线,包括ADC、TIM、GPIO、EXTI等外设。APB2总线的时钟频率通常与系统时钟频率相同,最高可达72MHz。

需要注意的是,外设总线的时钟频率是由RCC寄存器中的时钟分频因子配置所决定的。具体地说,RCC_CFGR寄存器中的APB1和APB2分频因子可以分别配置APB1和APB2总线时钟频率的倍频因子,以便满足不同外设的时钟要求。例如,如果将APB1分频因子设置为2,而APB2分频因子设置为1,则APB1总线的时钟频率将为系统时钟频率的1/2,而APB2总线的时钟频率将等于系统时钟频率。

alt

上图可以看出我们的GPIOA挂载在ABP2总线上。

alt

可以看出ABP2默认值位0.所以找到其寄存器的第二位,将其置为一。

alt

alt 基地址 alt

所以它的地址为0x40021000 + 0x18 = 0x40021018,将其第二位置一. 就可以使能该GPIO的时钟。

写代码

alt

先看代码,逐一解释。

代码解释:

	*(unsigned long *)0x40021018 |= (1<<2);

解释:这行代码就可以使能该GPIO的时钟,将其第二位置一。

	(unsigned long *)0x40010800

解释:地址是没有方向的,所以使用unsigned long或者unsigned int。 0x40010800是GPIOA寄存器的基地址。使用(unsigned long *)0x40010800可以将GPIOA寄存器的基地址转换为一个指针,然后可以使用指针访问GPIOA寄存器的不同位。

	*(unsigned long *)0x40010800

括号内的* 是强制类型转换,括号外的 *是获取地址的值。

首先要将值清零。清零就是&操作(与操作).置1位(|)或操作

	*(unsigned long *)0x40010800 &= 0xFFF0000F;

解释:具体地说,(unsigned long * )0x40010800 是将GPIOA寄存器的基地址转换为一个指针,* 是用于指针解引用的运算符,使得该表达式访问了GPIOA寄存器。然后,&=是按位与并赋值运算符,将GPIOA寄存器的指定位与给定的掩码0xFFF0000F进行按位与运算,将结果赋值回GPIOA寄存器。通过这个操作,GPIOA寄存器的第4到19位被设置为0,而其他位不变。

	*(unsigned long *)0x40010800 &= 0xFFF0000F;//清除4-19位
    *(unsigned long *)0x40010800 |= 0x00033330;//将PA1-PA4配置为通用推挽输出,工作频率为50MHz[19:4] = 0000 0000 0000 0011 0011 0011 0011 0000

然后控制LED输出低电平。

//GPIO_ODR = 0x40010800 + 0x0c = 0x4001080C
*(unsigned long *)0x4001080C &= ~((1<<1) | (1<<2) | (1<<3) | (1<<4));

解释:用于设置 GPIOA 端口的输入/输出模式。具体来说,这个表达式的作用是将 GPIOA 端口的 1 到 4 位设置为输出模式,保留其他位的状态。

PA1-PA4循环输出高,低电平:

	while(1)
    {
  		  //点亮PA1-PA4.输出高电平
    	*(unsigned long *)0x4001080C |= (1<<1) | (1<<2) | (1<<3) | (1<<4);
        Delay(0xffff);
  		  //熄灭PA1-PA4.输出低电平
    	*(unsigned long *)0x4001080C &= ~((1<<1) | (1<<2) | (1<<3) | (1<<4));
        Delay(0xffff);
    }
    
    void Delay(unsigned long nCount)
    {
    	while(nCount--)
        {
        
        }
    }
    

改进

有时候看见这些数字,可读性较差。所以

需要宏定义

#define RCCAPB2ENR (*(volatile unsigned long *)0x40021018)

解释:具体来说,0x40021018 是 RCCAPB2ENR 寄存器的物理地址,volatile unsigned long * 将该地址强制转换为指向无符号长整型的指针,而 * 运算符用于解引用指针并访问该地址处的数据。因为该宏使用了 volatile 关键字,因此编译器不能将其优化或缓存,以确保每次访问都能够获得最新的寄存器值。

RCCAPB2ENR |= (1<<2);

解释:在实际应用中,可以通过修改 RCCAPB2ENR 寄存器的位来控制特定外设的时钟开关。例如,将 RCCAPB2ENR 寄存器的第 2 位设置为 1,可以启用 GPIOA 端口的时钟。使用以上代码将 RCCAPB2ENR 寄存器的第 2 位设置为 1。

所以同样的,整体代码改进为:

main.c


#define RCCAPB2ENR 		(*(volatile unsigned long *)0x40021018)
#define GPIOACRL		(*(volatile unsigned long *)0x40010800)
#define GPIOAODR		(*(volatile unsigned long *)0x4001080C)

void Delay(unsigned long nCount);

int main(void)
{
	//RCC_APB2ENR= 0x40021000 + 0x18 = 0x40021018
	//RCC_APB2ENR bit[2] = 1 使能GPIOA外设时钟
	//*(unsigned long *)0x40021018 |= (1<<2); 
	RCCAPB2ENR |= (1<<2);
	
	//*(unsigned long *)0x40010800 &= 0xFFF0000F;//GPIOA_CRL = 0x40010800 + 0x00 ,清除[19:4]
	GPIOACRL &= 0xFFF0000F;
	//*(unsigned long *)0x40010800 |= 0x00033330;//[19:4] = 0011 0011 0011 011 将PA1~PA4配置为通用推完输出,工作频率50MHz
	GPIOACRL |= 0x00033330;
	
	//GPIO_ODR = 0x40010800 + 0x0C = 0x4001080C
	//*(unsigned long *)0x4001080C &= ~((1<<1)|(1<<2)|(1<<3)|(1<<4));  //PA1~PA4输出低电平,LED全灭
	GPIOAODR &= ~((1<<1)|(1<<2)|(1<<3)|(1<<4)); 
	
	while(1)
	{
		//点亮LED1~LED4  PA1~PA4输出高电平
		//*(unsigned long *)0x4001080C |= (1<<1)|(1<<2)|(1<<3)|(1<<4);
		GPIOAODR |= (1<<1)|(1<<2)|(1<<3)|(1<<4);
		Delay(0xffff);
		//熄灭LED1~LED4  PA1~PA4输出低电平
		//*(unsigned long *)0x4001080C &= ~((1<<1)|(1<<2)|(1<<3)|(1<<4));  //PA1~PA4输出低电平,LED全灭
		GPIOAODR &= ~((1<<1)|(1<<2)|(1<<3)|(1<<4));
		Delay(0xffff);
	}
}

void Delay(unsigned long nCount)
{
	while(nCount--)
	{
	}
}
}
}

需要注意的是,在进行寄存器操作时,需要事先了解寄存器的功能和用法,并仔细检查代码逻辑和正确性,以确保操作的正确性和有效性。同时,在使用寄存器进行操作时,推荐使用宏定义或函数封装等方式,以提高代码的可读性和可移植性。

全部评论
太棒了
点赞 回复 分享
发布于 2023-11-06 15:43 云南

相关推荐

给我发了笔试链接,想着等晚上回去做,结果还没做流程就终止了
伟大的小黄鸭在学习:我猜就是笔试几乎没用,就是用来给用人部门拖时间复筛简历的,可能用人部门筛到你简历觉得不合适就提前挂了
投递小鹏汽车等公司10个岗位
点赞 评论 收藏
分享
06-17 00:26
门头沟学院 Java
程序员小白条:建议换下项目,智能 AI 旅游推荐平台:https://github.com/luoye6/vue3_tourism_frontend 智能 AI 校园二手交易平台:https://github.com/luoye6/vue3_trade_frontend GPT 智能图书馆:https://github.com/luoye6/Vue_BookManageSystem 选项目要选自己能掌握的,然后最好能自己拓展的,分布式这种尽量别去写,不然你只能背八股文了,另外实习的话要多投,尤其是学历不利的情况下,多找几段实习,最好公司title大一点的
无实习如何秋招上岸
点赞 评论 收藏
分享
评论
3
5
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务