03 LED驱动编写

上一篇:02 CubeMX配置引脚
下一篇:04 按键驱动编写

作者:桂信科黄鹏老师。note部分是我添加的内容

主要内容

  1. 搞懂74HC573的作用,搞懂点亮LED的原理。(接触过模数电更易懂)
  2. 在CubeMX配置好LED相关引脚,用HAL库驱动LED。
  3. 在搞懂以上问题的情况下,进阶使用共用体和结构体位域控制LED。

一、共阳LED原理图分析

1. 如何入门原理图分析?

如下图,需要你从原理图中分析出以下问题:
1) 74HC573是什么?它有何作用?
573是信号锁存器,它不但增强了电流驱动能力,还实现了单片机的引脚共用。
2) LED的驱动引脚是什么?
STM32上的PC8~PC15。
3) LED的驱动电平是什么?
低电平,因为LED共用正极电源。
4) 驱动信号如何从单片机引脚传递到LED?
这需要搞懂573的锁存步骤。
Pasted image 20240125101614.png

2. 74HC573是什么?

接下来带你看懂74HC573数据手册中的重点部分。正如手册开头的简介所说:“74HC573是8路透明传输的数据锁存器;3态输出。”啥是锁存器?啥是透明传输?啥是3态?
Pasted image 20240126205528.png

Note

锁存器(Latch):在数字电子学中,锁存器是一种能够捕获并保存输入数据的设备,当锁存器被使能时,它会保持输入状态,直到被再次使能。

透明传输 (Transparent Latch) :指的是在使能信号激活(通常是高电平或低电平)时,锁存器的输入会直接反映到输出端,这就像数据能够“透明地”通过锁存器一样。在这种模式下,锁存器像是不存在一样,输入到输出之间没有延迟。一旦使能信号关闭(变为不使能状态),锁存器就会“锁定”当前的输入状态,即便输入信号之后改变,输出也会保持在最后一次使能期间的状态。

3态 (3-state) 输出:指的是输出端可以存在于三种不同的状态:高电平、低电平和高阻态(也称为高阻抗状态或Z状态)。在高阻态下,输出端会断开与电路的连接,好像它根本就不存在一样。这允许多个器件能够连接到同一条数据总线而不会互相干扰,因为只有被选中(或者说被使能)的器件会将数据放在总线上,其他的器件则处于高阻态,对总线的状态没有任何影响。

总结一下,74HC573是一个8位的锁存器,具有透明传输特性,当使能端激活时输入直接影响输出也就是控制LED;并且其输出是3态的,能够被设置为高电平、低电平或者高阻态。这使得该器件非常适合在数据总线和其他多器件共享资源的应用场合中使用。

下图是74HC573的功能框图。其中D0~D7是信号输入,Q0~Q7是信号输出,LE是锁存信号输入,OE是输出使能信号输入(头上的横杠代表低电平有效)
Pasted image 20240126210726.png

下图是74HC573的功能表。
Pasted image 20240126211404.png

该功能表跟原理图对比可知:
1) 由于OE引脚接地,所有输出一直使能。
2) 输入到输出的链路取决于LE信号,高电平信号透明传输,低电平信号锁存。

3) 573信号锁存(控制LED)的步骤

第一步:单片机往LED控制端口送数据。
第二步:单片机拉高573的LE端口信号,573得以将数据送到锁存内部。
第三步:单片机拉低573的LE端口信号,573得以将数据锁存。

//在此代码之前添加LED控制,这是573的LE引脚电平控制代码。
//拉高送数据
HAL_GPIO_WritePin(LE_GPIO_Port, LE_Pin, GPIO_PIN_SET);
//拉低数据锁存
HAL_GPIO_WritePin(LE_GPIO_Port, LE_Pin, GPIO_PIN_RESET);

二、引脚配置

参照02 CubeMX配置引脚#一、共阳LED引脚完成该步骤。

三、编程思想

1. 基本思想

1)往对应的LED控制IO口的输出寄存器或置位复位寄存器送数据。
2)进行数据锁存操作。

代码如下:

//LED1开灯
HAL_GPIO_WritePin(LCD_D8_LED1_GPIO_Port,LCD_D8_LED1_Pin,GPIO_PIN_RESET);
//数据锁存
LEDData_LE(); 
//LED1关灯
HAL_GPIO_WritePin(LCD_D8_LED1_GPIO_Port,LCD_D8_LED1_Pin,GPIO_PIN_SET);
//数据锁存
LEDData_LE();

此编程方法存在的问题:
LED的控制端口与LCD的数据端口是共用PC8~PC15的。假如只开LED1时PC8=0而PC9~PC15=1,然后操作LCD时将PC9置为0了,我再想要关闭LED1时PC8=1,然后锁存数据。此时我把PC9=0锁存了,导致LED2误操作!

如何解决这个问题?
IO口共用时必须要有控制数据备份。修改备份的控制数据,然后对IO口进行整体赋值。

2. 共用引脚时数据备份的思想

1)声明一个备份变量。
2)根据要控制的LED编号,对备份变量进行移位操作。
3)将备份变量赋值给LED的控制端口。

代码如下:

//声明并定义备份变量
uint8_t byLED_Data = 0xff;  
//LED 控制示例
if(_on_off == LED_ON) //开灯
{
	byLED_Data &= ~(1<<0);  //将第0位置0
}
else // 关灯
{
	   byLED_Data |= (1<<0);  //将第0位位置1
}
//向数据端口发送数据。操作寄存器的方式GPIOC->ODR,还用了与或赋值
GPIOC->ODR = (GPIOC->ODR & 0x00FF) | (byLED_Data << 8);  
//拉高送数据
HAL_GPIO_WritePin(LE_GPIO_Port, LE_Pin, GPIO_PIN_SET);  
//拉低数据锁存
HAL_GPIO_WritePin(LE_GPIO_Port, LE_Pin, GPIO_PIN_RESET); 

核心算法:
将第n位置0: byLED_Data &= ~(1<<n); //将第n位置0
将第n位置1: byLED_Data |= (1<<n); //将第n位置1

思考:
1)将变量中的第n位置0置1计算有点复杂,能否有跟简单直观的方法?
有,请看下一节。
2)C语言中运算符 ! 与 ~ 的区别?
!是逻辑非运算符,用于布尔表达式的取反。
~是按位取反运算符,对整数的每一位进行取反操作。

3. 共用体和结构体位域控制LED

1)将一个8位的变量和一个结构体通过共用体定义在一起。
2)结构体使用位字段定义,位域是C语言的一个特性。
3)声明一个该共用体的变量。
4)对结构体的位字段单独操作即可。

代码如下:

typedef union{
	uint8_t byLED_Data; //8位数据,表示LED灯的数据
	struct
	{                                          
	  uint8_t bLED1:1; //1位数据,存储LED的亮灭   
	  uint8_t bLED2:1;
	  uint8_t bLED3:1;
	  uint8_t bLED4:1;
	  uint8_t bLED5:1;
	  uint8_t bLED6:1;
	  uint8_t bLED7:1;
	  uint8_t bLED8:1;
	}stLED;
}_unLEDType;
//使用_unLEDType数据类型定义变量
static _unLEDType s_unLED_data;
//避免魔鬼数字用LED_OFF代表1,实现单独LED的关灯
s_unLED_data.stLED.bLED1 = LED_OFF; 
//向数据端口发送数据
GPIOC->ODR = (GPIOC->ODR & 0x00FF) | (s_unLED_data.byLED_Data << 8);
//拉高送数据
HAL_GPIO_WritePin(LE_GPIO_Port, LE_Pin, GPIO_PIN_SET);   
//拉低数据锁存
HAL_GPIO_WritePin(LE_GPIO_Port, LE_Pin, GPIO_PIN_RESET); 
Note

是不是简洁明了了?篇幅所限以上是代码片段,需要理解后去使用。一点都不明白的话去问ChatGPT,还没搞懂请去群里提问。

上一篇:02 CubeMX配置引脚
下一篇:04 按键驱动编写