STM32的GPIO_蓝桥杯点灯实验_初识HAL库编程/HAL_Delay与SysTick延时
LED实验简介
LED实验原理
-
LED的连接
开发板上有8个LED灯,为共阳极连接,因此当对应连接的引脚输出高电平时LED熄灭,反之亮起
而为了节约引脚,开发板的LED阴极并未直接连接到单片机的引脚上,而是在期间增加了一个SN74HC573锁存器
-
锁存器的控制
当锁存器控制引脚LE(连接至PD2引脚)为高电平时,锁存器打通,锁存器的输入引脚的电平状态会被传递到输出引脚,低电平时锁定,输入引脚将无法影响输出引脚
LED的GPIO引脚配置
-
LED的GPIO引脚连接
单片机的PC8-15经过锁存器连接到LED1-8的阴极,而锁存器受PD2引脚控制
-
LED的GPIO初始化配置
综上,要点亮一个LED,需要控制锁存器导通传输低电平至对应LED的阴极,即PD2为高电平时,PC8-15为低电平
而上电初始化时,PC8和PD2引脚要配置为输出模式并输出高电平,即锁存器导通,LED熄灭
LED的使用
点亮LED使用的HAL库函数
-
配置引脚电平状态HAL_GPIO_WritePin
void HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState) //GPIOx:端口号,如GPIOA,GPIOB,GPIOC…… //GPIO_Pin:引脚号,如GPIO_PIN_0,GPIO_PIN_1,GPIO_PIN_2…… //PinState:要配置的状态,GPIO_PIN_SET或GPIO_PIN_RESET,对应高低电平 //使用例:将PC8引脚配置为高电平 HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8, GPIO_PIN_SET); -
反转引脚电平HAL_GPIO_TogglePin
void HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin) //使用例:将PC8引脚电平反转 HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_8); -
延时函数HAL_Delay
void HAL_Delay(uint32_t Delay) //以毫秒为单位进行延时 //使用例:延时500ms HAL_Delay(500);
代码实现
-
点亮LED1
//代码放在while(1)之内 /* USER CODE BEGIN 3 */ HAL_GPIO_WritePin(GPIOC,GPIO_PIN_8,GPIO_PIN_RESET); //将PC8置低电平,点亮LED1 } /* USER CODE END 3 */ -
让LED1以1Hz的频率闪烁
/* USER CODE BEGIN 3 */ HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_8);//反转PC8电平 HAL_Delay(1000);//延时1s } /* USER CODE END 3 */
LED功能拓展
位运算控制
-
位操作的思路
从上述流程可见在引入HAL库函数后,整个程序编写流程被大大简化了,虽然对只开一个灯的实验而言好像和直接操控寄存器并无什么不同,但对于更复杂的单元(比如各种通信协议,或者是PWM输出)来说,一行代码能够实现以前需要亲自配置好几个寄存器的各个控制位才能实现的功能
但我们依旧需要懂得库函数背后的相关原理,因为只是调用别人封装好的库会损失一些编程的灵活性(这也是本人STM8系列教程中使用寄存器配置各项功能的原因)
比如说,如果我们想在STM32上重现STM8的流水灯实验,再使用库函数就很繁琐了,这时可以使用当初配置寄存器时的思路:通过位运算来控制GPIO,从而实现流水灯效果
-
位操作选取GPIO引脚
在单片机对应头文件中,可以看到各个引脚标号的宏定义,例如:
#define GPIO_PIN_8 ((uint16_t)0x0100) //b0000 0001 0000 0000 //上文HAL库函数中的GPIO_PIN_8也就是通过这个宏定义,成为了表示8号引脚的参数可见当我们在HAL库函数对应参数中输入
GPIO_PIN_x时,实际就是使用了一个x位为1而其余位为0的数来选中端口组中第x位的引脚,我们可以利用这一点通过位运算更精细高效地选中并操控所需的GPIO引脚
代码实现
-
二进制控制LED
/* USER CODE BEGIN PV */ //首先定义一个8位变量,因为led只有8个,所以使用uint8_t uint8_t enled=0x11;//0001 0001,这使得LED1和LED5同时被操作 /* USER CODE END PV */ /* USER CODE BEGIN 3 */ HAL_GPIO_WritePin(GPIOC,enled<<8,GPIO_PIN_RESET); //通过位运算控制PC8-PC15,选中要操作的灯 } /* USER CODE BEGIN 3 */ -
封装LED控制函数
既然一个变量
enled就能实现对所有led的控制,我们可以将其封装成一个函数,进一步简化操作/* USER CODE BEGIN PFP */ void Led_Disp(uint8_t enled); /* USER CODE END PFP */ /* USER CODE BEGIN 4 */ void Led_Disp(uint8_t enled) { HAL_GPIO_WritePin(GPIOC,0xFF<<8,GPIO_PIN_SET);//熄灭所有led HAL_GPIO_WritePin(GPIOC,enled<<8,GPIO_PIN_RESET);//点亮指定led } /* USER CODE END 4 */ -
交替闪烁与流水灯花样函数
封装好LED控制函数后,制作LED花样就简单了很多
//每隔一个灯亮起,然后交替闪烁 uint8_t enled=0xAA;//b1010 1010 void Led_Disp_tick(uint8_t enled) { Led_Disp(enled); HAL_Delay(1000); Led_Disp(~enled);//取反,交替点亮 HAL_Delay(1000); }//流水灯同理 uint8_t enled=0x01; void Led_Disp_flow(uint8_t enled) { Led_Disp(enled); HAL_Delay(500); enled<<=1;//向左循环流水 if(enled==0x00) enled=0x01; }
关于锁存器
-
锁存器的作用
在介绍蓝桥杯开发板时提到为了节约引脚,开发板上的LCD和LED是共用PC8-PC15引脚的,为此使用了锁存器:在用到LCD时,要把PD2初始状态修改为低电平,使锁存器不导通,让PC引脚的信号不再控制LED
-
实用的LED控制函数
可以将对PD2的操作一并封装入前文我们所作的
Led_Disp函数中,在要修改LED状态前解锁锁存器,对LED修改完成后再进行锁存,另外加入控制灯亮灭的功能,得到蓝桥杯中实用的LED控制函数/* USER CODE BEGIN 4 */ void Led_Disp(uint8_t led, uint8_t mode)//enled决定所控制的灯,填写1-8对应LD1-8 { HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_SET);//锁存器导通 if(mode)//mode为1时点亮,为0时关灯 HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8 << (led - 1), GPIO_PIN_RESET); else HAL_GPIO_WritePin(GPIOC, GPIO_PIN_8 << (led - 1), GPIO_PIN_SET); HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, GPIO_PIN_RESET);//锁存器锁定 } /* USER CODE END 4 */
系统滴答定时器SysTick
不同的延时方式
-
阻塞延时
前文介绍的延时函数
HAL_Delay是一种阻塞式延时,也即通过空转CPU一段时间来延时,如果在循环体中调用该函数,会导致整个程序阻塞 -
中断延时
为了克服
HAL_Dealy的阻塞问题,可以使用SysTick进行中断延时:这是一个系统预先配置好的中断定时器,所谓中断是指其在延时过程中不妨碍其他程序的正常运行,只在达到1ms那一刻打断当前执行的代码,转而执行中断服务函数(在之后中断一节中详细介绍),我们可以使用相关的库函数调用这个计时器
SysTick的库函数
-
SysTick中断服务函数SysTick_Handler
CubeMX生成工程的初始化函数
HAL_Init()已将SysTick配置为每隔1ms中断一次(可以修改此值以实现不同时长的计时),也就是每隔1ms都会执行一次这个SysTick_Handler()因此,计时的一个简单而实用的方法是在
SysTick_Handler()中安排一个自增变量,记录的进入中断次数即经过的毫秒void SysTick_Handler(void) { HAL_IncTick(); /* USER CODE BEGIN SysTick_IRQn 1 */ time++;//将计时变量加在此处 /* USER CODE END SysTick_IRQn 1 */ } -
系统运行时间计时HAL_IncTick
SysTick用于记录系统启动后过去的时间,这通过中断函数中
HAL_IncTick()实现,在其中有一个全局变量uwTick用以记录系统工作时间,其原理和我们自己增加的自增变量是一样的__weak void HAL_IncTick(void)//此处的__weak是对函数属性的标识,在之后的中断一章中会讲解其含义 { uwTick += uwTickFreq;//uwTickFreq被设置为1,即1ms发生一次中断 } -
获取系统运行时间HAL_GetTick
uwTick记录的系统运行时间,可以通过HAL_GetTick获取;uwTick是一个uint32_t类型的变量,因此可以记录很久,但HAL库中也预备了超时清除的代码以防溢出错误__weak uint32_t HAL_GetTick(void) { return uwTick; }
代码实现
-
SysTick延时
由于每个周期结束后都需要对计时值清零以开始下一周期,因此我们不使用
HAL_GetTick,而是自己设置自增变量usled来控制LED闪烁频率,以免破坏uwTick所记录的系统工作时间//通过SysTick实现LED1每隔500ms反转一次 /*————————main.c文件————————*/ /* USER CODE BEGIN PV */ uint32_t usled;//用于计时的变量 //务必注意此处的变量类型必须和主函数中使用的一致 //否则计数异常,比如此处写为uint8_t会导致计时范围无法达到uint32_t的大小 uint8_t enled=0x01; /* USER CODE END PV */ /* USER CODE BEGIN 3 */ Led_Disp(enled); if(usled>=500)//500ms到 { usled=0;//重新计数 enled^=0x01;//^是异或符号,将enled的值与0x01异或,两数不同时得1 } /* USER CODE END 3 */ /*————————stm32g4xx_it.c文件————————*/ /* USER CODE BEGIN PV */ extern uint32_t usled;//使用extern关键字引入main.c的外部变量 /* USER CODE END PV */ void SysTick_Handler(void)//SysTick的中断复位函数 { /* USER CODE BEGIN SysTick_IRQn 0 */ /* USER CODE END SysTick_IRQn 0 */ HAL_IncTick(); /* USER CODE BEGIN SysTick_IRQn 1 */ usled++;//usled每隔1ms自增1,储存当前计时 /* USER CODE END SysTick_IRQn 1 */ } -
固定闪烁时长
实现中断计时后,就能实现这样的需求:上电后灯以0.1s为间隔闪烁,5s后熄灭,实现代码如下:
/*————————main.c文件————————*/ /* USER CODE BEGIN PV */ uint32_t usled;//有两个用于计时的变量,第一个控制闪烁 uint32_t usled2;//第二个则是控制led在5s后熄灭 uint8_t enled=0x01; /* USER CODE END PV */ /* USER CODE BEGIN 3 */ if(usled2<5000)//5s未到 { if(usled>=100) { usled=0;//控制闪烁的变量重新计数 enled^=0x01;//反转led1 } } else//5s已经到了 { enled=0x00;//关闭led } Led_Disp(enled); /* USER CODE END 3 */ /*————————stm32g4xx_it.c文件————————*/ /* USER CODE BEGIN PV */ extern uint32_t usled;//使用extern关键字引入main.c的外部变量 /* USER CODE END PV */ void SysTick_Handler(void)//SysTick的中断复位函数 { /* USER CODE BEGIN SysTick_IRQn 0 */ /* USER CODE END SysTick_IRQn 0 */ HAL_IncTick(); /* USER CODE BEGIN SysTick_IRQn 1 */ usled++;//usled每隔1ms自增1,储存当前计时 usled2++; /* USER CODE END SysTick_IRQn 1 */ }
本文来自博客园,作者:无术师,转载请注明原文链接:https://www.cnblogs.com/artlessist/p/18867009
本文使用知识共享4.0协议许可 CC BY-NC-SA 4.0
特别说明版权归属的文章以及不归属于本人的转载内容(如引用的文章与图片)除外

浙公网安备 33010602011771号