STM32HAL库驱动数码管

本文档创建于2023年3月7日

本文记录了我学习数码管驱动的过程,实际是微机原理课上留的一个作业。

本文作者:RobotFreak

本文参考《NANO_STM32F103开发指南-HAL库版本_V2.0》,正点原子官网可下载。

数码管驱动原理

数码管,也称LED数码管,按发光二极管单元连接方式可分为“共阳极数码管”和“共阴极数码管”。我们使用的正点原子NANO STM32F103开发板板载的数码管为四位共阴极数码管,实物图于内部引脚图如下:

在这里插入图片描述

共阳极数码管是指将所有发光二极管的阳极接到一起,而共阴极数码管则是发光二极管的阴极连到一起,连接方式如下:

在这里插入图片描述

从上图可以看到,数码管为共阴时,当某一字段的发光二极管阳极为高电平时,响应字段就点亮,为共阳时,当某一字段的发光二极管阴极为低电平时,相应字段则点亮。所以通过点亮相对应的字段就可以显示我们需要的字符。 数码管要正常显示,就要用驱动电路来驱动数码管的各个端码,从而显示出我们要的字符,因此根据数码管的驱动方式不同,可以分为“静态式”和“动态式”两类。

静态显示驱动,也称为直流驱动。静态驱动是指每个数码管的每一个端码都有一个单片机的 I/O 端口进行驱动,或者使用如 BCD 码二-十进制译码进行驱动。静态驱动的优点是编程简单,显示亮度高,缺点是占用 I/O 端口多,如驱动 5 个数码管静态显示则需要 5×8=40 个 I/O 端口来驱动,要知道 STM32 单片机可用的 IO 端口是有限的,实际应用时必须增加译码器进行驱动,但会增加电路的复杂性。

动态显示驱动,是单片机应用中最为广泛的显示方式,动态驱动是将所有数码管的 8 个显示笔划“a,b,c,d,e,f,g,dp”的同名端连载一起,另外为每个数码管的公共级 COM 增加位选通控制电路,位选通由各自独立 IO 线控制,当单片机输出字形码时,所有数码管都接收到相同的字形码,但究竟是哪个数码管会显示出字形,取决于单片机对位选通 COM 端电路的控制,所有我们只要将需要显示的数码管的选通控制打开,改为就显示出字形,没有选通的数码管就不会亮。通过分时轮流控制各个数码管的 COM 端,就可以使各个数码管轮流显示。在轮流显示过程中,每位数码管的点亮时间为 1~2ms,由于人的视觉暂留现象及发光二极管的余辉效应,尽管实际上各位数码管并非同时点亮,只要扫描的速度够快,给人的印象就是一组稳定的显示数据,不会有闪烁感,动态显示的效果和静止显示是一样的,能够节省大量的 IO 端口,功耗降低。

扫描二维码关注公众号,回复: 15090533 查看本文章

这里我们使用动态驱动的方式驱动数码管,为了节省 IO,使用到驱动电路,段选数据使用 74HC595D 串行数据转并行数据输出芯片,位选数据使用 74HC138D三-八译码器芯片。 下面将介绍这两款芯片。

74HC595D

74HC595D 是恩智浦 NXP 公司推出的一款低噪声、低功耗、高速的 CMOS 移位寄存器, 能够驱动 15 个 LS-TTL 的负载。该器件包含一个 8 位串行输入、并行输出的移位寄存器及带有三态输出控制的 8 位 D 型存储器。74HC595D 的特点如下:

  1. 带存储功能的 8 位串行输入,并行输出的移位寄存器
  2. 工作电压范围宽:2V-6V
  3. 可级联使用

74HC595D 广泛应用于点阵屏和主控 IO 较少数据串行转并行的场合,74HC595D 的框图如下:

在这里插入图片描述

引脚说明

在这里插入图片描述

时序图

数据的串行输入需要按照规定的时间才能正常写入,根据上面讲解,我们要知道的是 SH_CP 上升沿高电平和 ST_CP 正脉冲保持的时间,如下图:

在这里插入图片描述

从上图可以看到时序的时间都是 ns 级别的,在写软件时我们只需要几 us 的时间就可以了。 通过 74HC595D 的使用,我们就可以以串行数据输入,并行数据输出控制数码管的端选段, 从而显示对应的字符。

74HC138D

74HC138D 是恩智浦 NXP 公司推出的一款高速 CMOS、低功耗的 3-8 译码器。通过三个地 址的输入设置,可选择对应八种的状态输出。74HC138D 的特点如下

  1. 低功耗

  2. 工作电压范围宽:3V-5V

74HC138D 广泛应用于消费类电子产品,74HC138D 的框图如下:

在这里插入图片描述

引脚说明

在这里插入图片描述

真值表

Y0~Y7 的输出是 A0~A2 的设置的,而它们是依照 3-8 译码形式设置,以下是它们工作的真值表:

在这里插入图片描述

从图可以看到,当/E1、/E2、E3 使能端使能后,设置 A0、A1、A2 的输入(高电平有效), /Y0~/Y7 都会有对应的状态输出(低电平),例如 A0、A1、A2 设置的是(A2=0,A1=1,A2=1), 根据 BCD-421 码的换算,代表是 3,则 Y3 输出有效电平 L,其他的都输出为无效电平 H。设 置(A2=1,A1=1,A0=1),代表是 7,则 Y7 输出有效电平 L,其他的都输出为无效电平 H。 通过 74HC138D 的使用,我们就可以控制对应数码管的位选端了。

硬件部分

我使用的是正点原子的NANO STM32F103实验板,板子上的原理图如下:

在这里插入图片描述

在这里插入图片描述

原理图中74HC595D芯片标的引脚标号名称可能与芯片手册标的有点不一样,以下是引脚对应标号关系:

在这里插入图片描述

从上方的原理图可以看到,74HC138D 的使能端引脚默认已上拉或下拉硬件使能, 74HC595D 的 RST 复位引脚与开发板的复位线相连接,当按下开发板的复位按键或首次上电即可对 74HC595D 的芯片的数据清零,以到达复位的作用。由于没有做级联的功能,SDO 引脚上做了悬空处理。而使能端引脚也同样做了默认硬件使能。

在数码管中,两个数码管的 A~DOT 段选段都与 74HC595D 芯片的 QA~QH 相连接,而数码管的 CH1~CH4 位选端则单独分别与74HC138D 芯片的 Y0~Y7 引脚相连接。通过控制 74HC595D 输出的段码和控制 74HC138D 输出的位选就可以实现选择点亮数码管。

段选即一位数码管显示的符号,决定一位数码管的哪几段点亮,显示相应字符。位选决定数码管哪一位点亮。NANO板上使用的数码管为四位数码管,一个数码管模块有4位,共有2个模块。

软件部分

代码部分参考了正点原子官方HAL库例程。重点在于明白数码管的驱动代码,明白数码管是如何驱动起来的

CubeMX配置

CubeMX中选择NANO板对应的型号STM32F103RCTx,时钟配置如下:

在这里插入图片描述

GPIO选择了数码管用到的几个进行了配置,初始化为不影响使用时的状态:

在这里插入图片描述

启用了TIM2时钟,分频系数设为72-1,则TIM2内计数器的频率为1MHz,也即1us计一个数,这个定时器用来延时,不产生中断:

在这里插入图片描述

Keil代码

数码管驱动代码如下:

smg.h

#ifndef __SMG_H__
#define __SMG_H__

#include "stm32f1xx_hal.h"

/**
 * @brief   设定数码管控制的位
 * @param   num 要控制的数码管编号0~7(共8个数码管)
 * @details 设定数码管要控制哪一位,这个数字与输出有效的74HC138
 *          Y0~7的编号一致,也就是说num就是a2a1a0拼起来之后对应的十进制形式
 *          那么我们将num的二进制形式的最低三位取出来,就是a2a1a0的值
 */
void setDigitalTubeBit(uint8_t num);

/**
 * @brief   向HC595中写入数据
 * @param   seg 段segment,写入的段码数据
 * @param   num 数码管位的编号,0~7(共8个数码管)
 * @details 将段码写入HC595的移位寄存器,还没有输出
 */
void writeData2ShiftRegister(uint8_t seg, uint8_t num);

/**
 * @brief   将写入HC595的数据输出到数码管
 * @param   None
 * @details LCLK产生上升沿,将移位寄存器数据放入数据寄存器,输出到数码管
 */
void writeData2DigitalTube(void);

#endif

smg.c

#include "smg.h"
#include "tim.h"
#include "sys.h"
/**
 * 引脚对应
 * PB3  <-> HC595 DATA(SDI/DS)
 * PB4  <-> HC595 LCLK(STCP)
 * PB5  <-> HC595 SCLK(SHCP)
 * PC10 <-> HC138 A0
 * PC11 <-> HC138 A1
 * PC12 <-> HC138 A2
*/

/**
 * @brief   设定数码管控制的位
 * @param   num 要控制的数码管编号0~7(共8个数码管)
 * @details 设定数码管要控制哪一位,这个数字与输出有效的74HC138
 *          Y0~7的编号一致,也就是说num就是a2a1a0拼起来之后对应的十进制形式
 *          那么我们将num的二进制形式的最低三位取出来,就是a2a1a0的值
 */
void setDigitalTubeBit(uint8_t num)
{
    
    
	//一个二进制数与上0总是0,与上1还是它本身
	uint8_t bit0 = num & 0x01;      //取第一位
	uint8_t bit1 = (num&0x02) >> 1; //取第二位
	//int bit1 = (num >> 1) & 0x01; //与上一行操作等价
	uint8_t bit2 = (num&0x04) >> 2; //取第三位
	//int bit2 = (num >> 2) & 0x01; //与上一行操作等价
	//写入引脚
	HAL_GPIO_WritePin(GPIOC, GPIO_PIN_10, bit0);
	HAL_GPIO_WritePin(GPIOC, GPIO_PIN_11, bit1);
	//一开始上面的bit2是右移1位,当num第三位为1时,bit2是0x02,并不是我们想要的0或者1.现在改为了正确的右移两位
	HAL_GPIO_WritePin(GPIOC, GPIO_PIN_12, bit2); //使用库函数时,即使输入的不为1,根据非零为真的原则,也会输出高电平.但是日常使用时应该规范使用0/1表示假和真
	//PCout(12) = bit2;                          //直接用位带操作时,输入就是0/1,当bit2为0x02时这一位输出出来还是0
}

/**
 * @brief   向HC595中写入数据
 * @param   seg 段segment,写入的段码数据
 * @param   num 数码管位的编号,0~7(共8个数码管)
 * @details 将段码写入HC595的移位寄存器,还没有输出
 */
void writeData2ShiftRegister(uint8_t seg, uint8_t num)
{
    
    
	uint8_t i;
	for(i = 0; i < 8; i++) //先写段码,一共8位输出
	{
    
    
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, (seg>>i)&0x01); //将seg从最低位开始一位一位取出来,写入DS(串行数据输入),进入移位寄存器
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, 0); //SCLK写入0;初始化是1,此处产生一个下降沿
		delayNus(5); //延时5us
		HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, 1); //SCLK写入1.此处产生一个上升沿,数据输出移位
	}
	setDigitalTubeBit(num); //选中位
}

/**
 * @brief   将写入HC595的数据输出到数码管
 * @param   None
 * @details LCLK产生上升沿,将移位寄存器数据放入数据寄存器,输出到数码管
 */
void writeData2DigitalTube(void)
{
    
    
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, 1); //产生上升沿
	delayNus(5); //延时5us
	HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, 0); //恢复到低电平
}

main.c中main()函数的代码如下:

/* USER CODE BEGIN PV */
// 0,1,2,3,4,5,6,7,8,9
uint8_t smgNum[] = {
    
    0xfc, 0x60, 0xda, 0xf2, 0x66, 0xb6, 0xbe, 0xe0, 0xfe, 0xf6};

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
    
    
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM2_Init();
  /* USER CODE BEGIN 2 */
	uint8_t smgBit = 0; //位选
	uint8_t smgSeg = 0; //段选
	uint16_t t = 0;

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    
    
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		writeData2ShiftRegister(smgNum[smgSeg], smgBit);
		writeData2DigitalTube();
		smgBit++;
		if(smgBit == 8)
			smgBit = 0;
		t++;
		if(t == 1000)
		{
    
    
			t = 0;
			smgSeg++;
			if(smgSeg == 10)
				smgSeg = 0;
		}
    HAL_Delay(1); //延时1ms,不像2ms时会出现频闪.
  }
  /* USER CODE END 3 */
}

实现效果

实现的效果如下,八位数码管显示同一个值,每过1s更新一次数码管的值:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/svfsvadfv/article/details/129476601