STM32单片机开发板实验02:动态数码管实验

实验目标

控制动态数码管从左至右显示数字 0-7。

认识数码管

数码管是半导体发光器件,用于显示数字、字母、简单图形符号等内容。数码管基本单元是发光二极管,数码管内部由多个发光二级管构成,多个发光二极管在同一时间的不同状态,组合为不同的数字、字母或图形符号。
数码管型号多样,有单位数码管、双位数码管、四位数码管、八位数码管等等,单位数码管仅能显示一个数字、字母或图形符号,多位数码管可以显示多个数字、字母或图形符号。实际上,多位数码管都可以由单位数码管构成,例如4个单位数码管可以构成四位数码管。
不管将几位数码管连在一起,数码管的显示原理都是一样的,都是靠点亮内部的发光二极管来发光,下面我们来看看单位数码管是如何显示数字的。
单位数码管一般由7个发光二极管组成,有共阴,共阳两种,八个引脚,七个引脚接7个发光二极管,一个引脚是公共端,若共阴,该引脚接地,若共阳,该引脚接电源的正极。
单位数码管内部基础电路如上图所示,显示一个数字需要7个小段,每个小段需要一个发光二极管,因此数码管的引脚至少为8个,其中7个引脚分别接二极管的正极或负极,另外1个引脚为公共端,若数码管为共阴极,该引脚接地,若数码管为共阳及,该引脚接电源。
对于共阳极数码管来说,7个发光二极管的正极连接在一起形成公共端,该公共端接电源,负极分别接单片机的I/O端口,当我们给数码管任一个阴极低电平时,对应的这个发光二极管就点亮了。例如:若需要数码管显示数字0,g引脚送低电平,其它引脚送高电平,这样数码管就显示数字0了;若需要数码管显示数字8,将所有引脚(公共端除外)送入低电平,数码管就显示数字8了。因此,若需要数码管显示数字,只需要给发光二极管引脚送入低电平或高电平就可以了。

数码管动态显示原理

在静态显示模式下,多位数码管可通过两种方式工作:单独点亮某一位,或同时点亮多位显示相同内容。其核心原理在于:所有数码管的段选信号并联,而位选信号独立控制。当多个位选信号同时有效时,由于共享相同的段选信号,这些数码管必然显示相同数字。
动态显示则采用分时复用技术:通过减少段选线数量、独立控制位选线,并快速轮巡切换显示内容。例如,先给第一位送入数字0的段选信号并开启其位选,随后关闭第一位,立即给第二位送入数字1的段选信号并开启其位选。为形成视觉暂留效果,需在24ms(人类视觉分辨临界值)内完成刷新循环,这样观察者会看到多位数字同时显示的假象。若延长刷新间隔,则可明显观察到逐位点亮的扫描过程。

硬件设计

本实验使用到硬件资源如下:
(1)动态数码管模块
(2)74HC138
开发板上的动态数码管模块电路如下图所示:
该电路的主要功能是通过STM32核心板控制动态数码管的显示。STM32核心板通过GPIO引脚输出高低电平,控制数码管的段选和位选引脚,从而点亮相应的段和选择相应的数码管,实现数字和小数点的显示。
该电路采用2片四位一体共阴数码管(共8位),所有数码管的段选信号(a-dp)并联输出,每位独立引出公共端(位选端)。段选信号通过74HC245驱动芯片连接至STM32的PA0-PA7引脚,位选信号则由PB5/PB4/PB3通过74HC138译码器控制。这种设计实现了8位数码管的动态扫描驱动。

74HC138芯片

两个4位一体共阴数码管共需要8根位选线(每个数码管4位×2个)。虽然单片机可以直接通过IO口控制这些位选线,但这种方案会占用过多单片机的IO资源。在实际应用中,人们通常采用IO扩展芯片来实现更高效的资源利用。
74HC138译码器仅需3个单片机IO口即可控制8个输出,管脚布局如下图:
引脚说明:
A0, A1, A2:3位二进制地址输入,用于选择输出线。
S1, S2, S3:使能输入,用于控制芯片的工作状态。
Y0至Y7:8个输出引脚,根据输入地址信号,其中一个输出为低电平,其余为高电平。
74HC138是一个3线—8线译码器,它将3位二进制输入(A0, A1, A2)译码为8个输出(Y0至Y7)中的一个低电平信号。使能引脚(S1, S2, S3)用于控制芯片是否工作。当S1为低电平且S2和S3为高电平时,芯片处于工作状态,根据输入地址信号,相应的输出引脚变为低电平。

74HC245驱动芯片

74HC245芯片可以在微控制器、计算机和其他数字电路之间实现数据总线的双向传输,它允许数据在两个方向上进行传输,具体方向由方向控制引脚(DIR)决定,并能提供较高的驱动电流。
74HC245芯片管脚布局如下图所示:
DIR(输入端):方向控制输入端,用于控制数据传输的方向。当DIR为高电平时,74HC245芯片处于输出模式;当DIR为低电平时,芯片处于输入模式。
OE(输入端):输出使能输入端,用于控制输出端口是否有效。当OE为高电平时,输出端口处于失能状态;当OE为低电平时,输出端口处于使能状态。
A1-A8、B1-B8(输入/输出端):数据传输接口,用于连接不同总线之间进行数据传输。
GND(电源):地线,接地使用。
VCC(电源):电源正极,通常为+5V或+3.3V。

软件编程

创建项目

打开 STM32CubeIDE,点击File -> New -> STM32 Project。在弹出的New STM32 Project窗口中,选择对应的 STM32 微控制器型号STM32F103C8T6。
输入项目名称:“PZSTM32F103C8T6Test02”
项目创建完成后,在STM32CubeIDE左侧的“Project Explorer”窗口会列出项目目录。

配置项目

打开PZSTM32F103C8T6Test02.ioc文件。
一、配置GPIO端口
分析开发板上的动态数码管模块电路图,动态数码管模块的段选信号通过74HC245驱动芯片连接至STM32的PA0-PA7引脚,位选信号则由PB5/PB4/PB3通过74HC138译码器控制。因此需要配置PA0-PA7引脚和PB3~PB5引脚为推送输出模式。

生成代码

图形化配置完成,保存配置文件PZSTM32F103C8T6Test02.ioc,STM32CubeIDE会自动创建基础程序代码,基础代码目录结构如下图所示:

代码编写

数码管初始化及显示函数

本实验采用的是共阴极数码管,数码管数字0-&的码表为:
// 数码管段选码表(共阴数码管)
const uint8_t SEG_CODE[] = {
    0x3F, // 0
    0x06, // 1
    0x5b, // 2
    0x4f, // 3
    0x66, // 4
    0x6D, // 5
    0x7D, // 6
    0x07, // 7
};
本实验通过位选信号来控制数码管的某一位显示数字,enable_digit函数用于产生位选信号。
void enable_digit(uint8_t digit)
{
    GPIO_PinState pin3_state = GPIO_PIN_RESET;
    GPIO_PinState pin4_state = GPIO_PIN_RESET;
    GPIO_PinState pin5_state = GPIO_PIN_RESET;


    switch(digit) {
        case 0: // A2A1A0=000 → Y0
            pin3_state = GPIO_PIN_SET;
            pin4_state = GPIO_PIN_SET;
            pin5_state = GPIO_PIN_SET;
            break;
        case 1: // A2A1A0=001 → Y1
            pin3_state = GPIO_PIN_RESET;
            pin4_state = GPIO_PIN_RESET;
            pin5_state = GPIO_PIN_SET;
            break;
        case 2: // A2A1A0=010 → Y2
            pin3_state = GPIO_PIN_RESET;
            pin4_state = GPIO_PIN_SET;
            pin5_state = GPIO_PIN_RESET;
            break;
        case 3: // A2A1A0=011 → Y3
            pin3_state = GPIO_PIN_RESET;
            pin4_state = GPIO_PIN_SET;
            pin5_state = GPIO_PIN_SET;
            break;
        case 4: // A2A1A0=100 → Y4
            pin3_state = GPIO_PIN_SET;
            pin4_state = GPIO_PIN_RESET;
            pin5_state = GPIO_PIN_RESET;
            break;
        case 5: // A2A1A0=101 → Y5
            pin3_state = GPIO_PIN_SET;
            pin4_state = GPIO_PIN_RESET;
            pin5_state = GPIO_PIN_SET;
            break;
        case 6: // A2A1A0=110 → Y6
            pin3_state = GPIO_PIN_SET;
            pin4_state = GPIO_PIN_SET;
            pin5_state = GPIO_PIN_RESET;
            break;
        case 7: // A2A1A0=111 → Y7
            pin3_state = GPIO_PIN_SET;
            pin4_state = GPIO_PIN_SET;
            pin5_state = GPIO_PIN_SET;
            break;
        default:
            break;
    }
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, pin3_state);
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_4, pin4_state);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, pin5_state);
}
函数用于控制一个74HC138译码器,通过设置GPIOB的PIN3、PIN4和PIN5三个引脚的电平状态,来选择8个不同的数码管位(Y0-Y7)。
void display() {
   uint32_t last_scan_time = 0;
   const uint32_t SCAN_INTERVAL = 2; // 扫描间隔(ms)
   uint8_t current_digit = 0;
   // 数码管动态扫描
    if(HAL_GetTick() - last_scan_time >= SCAN_INTERVAL) {
          last_scan_time = HAL_GetTick();
          // 消隐处理
          HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);
         // 选择数码管
         enable_digit(current_digit);
         // 输出段选数据
         uint8_t seg_data = SEG_CODE[current_digit];
         for(int j = 0; j < 8; j++) {
             HAL_GPIO_WritePin(GPIOA, GPIO_PIN_0 << j, (seg_data & (1 << j)) ? GPIO_PIN_SET : GPIO_PIN_RESET);
         }
         // 更新当前显示位
         current_digit = (current_digit + 1) % 8;
    }
}
函数实现了数码管动态扫描显示功能,通过周期性切换数码管的位选(选中当前显示位)和段选(输出对应字符的段码),实现多个数码管的动态显示。
变量last_scan_time记录上次扫描时刻,用于计算扫描间隔;变量SCAN_INTERVAL定义两次扫描之间的时间间隔为 2ms,合理的扫描间隔需小于人眼视觉暂留时间(约 50ms),否则会出现闪烁;变量current_digit表示当前正在驱动的数码管位(共 8 位,索引 0~7),每次扫描后递增并取模,实现循环扫描。
扫描间隔判断
if(HAL_GetTick() - last_scan_time >= SCAN_INTERVAL) {
    last_scan_time = HAL_GetTick(); // 更新上次扫描时间
    // 执行扫描操作
}
确保数码管扫描频率稳定在 1/SCAN_INTERVAL = 500Hz(即每 2ms 扫描一次)。
消隐处理
在切换数码管位选前,关闭所有段选引脚,避免前一位的段码残留在下一位上,产生重影。

初始化GPIO函数

初始化GPIO函数由STM32CubeIDE自动生成,我们需要在函数内任意位置添加下面两条语句:
  __HAL_RCC_AFIO_CLK_ENABLE();
  __HAL_AFIO_REMAP_SWJ_DISABLE ();
当需要将JTAG/SWD占用的引脚用作普通GPIO时(如驱动数码管、按键等),需先执行这两条语句。因为JTAG默认使用PB3和PB4引脚。

主函数

int main(void)
{


  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  while (1)
  {
  display();
  }
}
主函数内容比较简单,首先初始化相关外设和配置时钟,然后进入
while 循环,在循环内调用display函数。

编译和运行

配置输出HEX文件
STM32CubeIDE窗口,选择菜单栏Project -> Properties,弹出项目属性对话框。
项目属性对话框左侧列表项,选择C/C++Build->Settings。在项目属性对话框右侧Tool Settings选项卡内,选择“MCU/MPU Post build outputs”列表项,在其右侧的检查项中选中“Convert to Intel Hex fiile”。
构建工程
STM32CubeIDE窗口,选择菜单栏Project -> Build All,STM32CubeIDE会在Console窗口输出构建信息,若构建过程出现问题,根据错误信息进行修改。
下载程序到板卡
购买的开发板配套资料一般都提供了烧录程序,如普中开发板的烧录程序在在配套资料的开发工具/程序烧入目录内,可以看到里面有两个烧写软件:一个是“STC-ISP 下载”软件;另一个是普中公司研发的自动下载软件。STC-ISP 下载软件需要冷启动,即先点击下载然后开启单片机电源,普中公司研发的自动下载软件,只需一键即可下载,操作非常简单。