文章目录
-
- 一、方案整体设计与原理说明
-
- 1.1 核心原理
- 1.2 整体架构流程图
- 二、硬件选型与接线
-
- 2.1 核心硬件清单
- 2.2 详细接线说明
-
- (1)STM32与两位共阳数码管接线
- (2)STM32与奇偶状态LED接线
- (3)STM32与按键接线
- (4)电源接线
- 三、软件开发环境搭建
-
- 3.1 环境准备
- 3.2 STM32CubeMX初始化配置步骤
-
- 步骤1:新建工程
- 步骤2:配置RCC时钟
- 步骤3:配置GPIO引脚
- 步骤4:配置TIM2定时器(1秒中断)
- 步骤5:生成工程代码
- 四、核心代码编写
-
- 4.1 头文件与全局变量定义(main.h)
- 4.2 外设初始化代码(main.c)
- 4.3 计数器核心逻辑函数(main.c续)
- 4.4 数码管驱动函数(main.c续)
- 4.5 按键处理函数(main.c续)
- 4.6 主函数(main.c续)
- 五、代码烧录与调试
-
- 5.1 代码编译与烧录
- 5.2 硬件调试步骤
-
- 步骤1:基础功能验证
- 步骤2:按键功能验证
- 步骤3:数码管显示调试
- 步骤4:奇偶检测调试
- 5.3 常见问题与解决
- 六、功能扩展建议
- 总结
一、方案整体设计与原理说明
你需要实现的是基于STM32F103C6的两位计数器与奇偶检测显示系统,核心功能包含00-99循环计数、计数值奇偶性实时检测、两位数码管计数值显示和奇偶状态LED指示四大模块。本方案以STM32F103C6最小系统板为核心控制器,利用内置定时器实现1秒精准加1计数(支持手动加/减计数、复位),通过数值分解算法提取计数值的十位和个位,驱动两位数码管动态扫描显示;同时通过取余运算检测计数值奇偶性,用红绿LED分别指示偶数、奇数状态。整个方案模块化拆解,代码注释详尽,零基础小白可完全复刻落地。
1.1 核心原理
- 两位计数器原理:基于STM32 TIM2定时器实现1秒定时中断,中断回调函数中对计数值(00-99)进行加1操作,达到99后自动清零循环;同时支持本地按键实现手动加1、减1、复位计数,满足灵活操作需求。
- 奇偶检测原理:对当前计数值执行num % 2运算,结果为0则判定为偶数,点亮绿色LED;结果为1则判定为奇数,点亮红色LED,检测逻辑在每次计数更新后实时执行。
- 数码管显示原理:采用两位共阳数码管(新手易上手),通过GPIO口分别控制数码管的段选(a-g、dp)和位选(十位、个位),利用动态扫描技术(快速切换十位/个位显示)实现两位数值同时显示,视觉上无闪烁。
- 按键交互原理:按键采用上拉输入模式,通过消抖算法避免机械抖动导致的误触发,按下不同按键分别触发计数加1、减1、复位操作。
1.2 整体架构流程图
以下是系统完整工作流程的Mermaid流程图:
#mermaid-svg-RDfj0L7QF9l5iGAE{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RDfj0L7QF9l5iGAE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RDfj0L7QF9l5iGAE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RDfj0L7QF9l5iGAE .error-icon{fill:#552222;}#mermaid-svg-RDfj0L7QF9l5iGAE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RDfj0L7QF9l5iGAE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RDfj0L7QF9l5iGAE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RDfj0L7QF9l5iGAE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RDfj0L7QF9l5iGAE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RDfj0L7QF9l5iGAE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RDfj0L7QF9l5iGAE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RDfj0L7QF9l5iGAE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RDfj0L7QF9l5iGAE .marker.cross{stroke:#333333;}#mermaid-svg-RDfj0L7QF9l5iGAE svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RDfj0L7QF9l5iGAE p{margin:0;}#mermaid-svg-RDfj0L7QF9l5iGAE .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-RDfj0L7QF9l5iGAE .cluster-label text{fill:#333;}#mermaid-svg-RDfj0L7QF9l5iGAE .cluster-label span{color:#333;}#mermaid-svg-RDfj0L7QF9l5iGAE .cluster-label span p{background-color:transparent;}#mermaid-svg-RDfj0L7QF9l5iGAE .label text,#mermaid-svg-RDfj0L7QF9l5iGAE span{fill:#333;color:#333;}#mermaid-svg-RDfj0L7QF9l5iGAE .node rect,#mermaid-svg-RDfj0L7QF9l5iGAE .node circle,#mermaid-svg-RDfj0L7QF9l5iGAE .node ellipse,#mermaid-svg-RDfj0L7QF9l5iGAE .node polygon,#mermaid-svg-RDfj0L7QF9l5iGAE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-RDfj0L7QF9l5iGAE .rough-node .label text,#mermaid-svg-RDfj0L7QF9l5iGAE .node .label text,#mermaid-svg-RDfj0L7QF9l5iGAE .image-shape .label,#mermaid-svg-RDfj0L7QF9l5iGAE .icon-shape .label{text-anchor:middle;}#mermaid-svg-RDfj0L7QF9l5iGAE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-RDfj0L7QF9l5iGAE .rough-node .label,#mermaid-svg-RDfj0L7QF9l5iGAE .node .label,#mermaid-svg-RDfj0L7QF9l5iGAE .image-shape .label,#mermaid-svg-RDfj0L7QF9l5iGAE .icon-shape .label{text-align:center;}#mermaid-svg-RDfj0L7QF9l5iGAE .node.clickable{cursor:pointer;}#mermaid-svg-RDfj0L7QF9l5iGAE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-RDfj0L7QF9l5iGAE .arrowheadPath{fill:#333333;}#mermaid-svg-RDfj0L7QF9l5iGAE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-RDfj0L7QF9l5iGAE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-RDfj0L7QF9l5iGAE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RDfj0L7QF9l5iGAE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-RDfj0L7QF9l5iGAE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RDfj0L7QF9l5iGAE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-RDfj0L7QF9l5iGAE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-RDfj0L7QF9l5iGAE .cluster text{fill:#333;}#mermaid-svg-RDfj0L7QF9l5iGAE .cluster span{color:#333;}#mermaid-svg-RDfj0L7QF9l5iGAE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-RDfj0L7QF9l5iGAE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-RDfj0L7QF9l5iGAE rect.text{fill:none;stroke-width:0;}#mermaid-svg-RDfj0L7QF9l5iGAE .icon-shape,#mermaid-svg-RDfj0L7QF9l5iGAE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-RDfj0L7QF9l5iGAE .icon-shape p,#mermaid-svg-RDfj0L7QF9l5iGAE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-RDfj0L7QF9l5iGAE .icon-shape rect,#mermaid-svg-RDfj0L7QF9l5iGAE .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-RDfj0L7QF9l5iGAE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-RDfj0L7QF9l5iGAE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-RDfj0L7QF9l5iGAE :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}#mermaid-svg-RDfj0L7QF9l5iGAE .darkStyle>*{fill:#2c3e50!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-RDfj0L7QF9l5iGAE .darkStyle span{fill:#2c3e50!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-RDfj0L7QF9l5iGAE .darkStyle tspan{fill:#ffffff!important;}#mermaid-svg-RDfj0L7QF9l5iGAE .startEnd>*{fill:#e74c3c!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-RDfj0L7QF9l5iGAE .startEnd span{fill:#e74c3c!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-RDfj0L7QF9l5iGAE .startEnd tspan{fill:#ffffff!important;}#mermaid-svg-RDfj0L7QF9l5iGAE .decision>*{fill:#3498db!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-RDfj0L7QF9l5iGAE .decision span{fill:#3498db!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-RDfj0L7QF9l5iGAE .decision tspan{fill:#ffffff!important;}#mermaid-svg-RDfj0L7QF9l5iGAE .process>*{fill:#27ae60!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-RDfj0L7QF9l5iGAE .process span{fill:#27ae60!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-RDfj0L7QF9l5iGAE .process tspan{fill:#ffffff!important;}#mermaid-svg-RDfj0L7QF9l5iGAE .display>*{fill:#9b59b6!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-RDfj0L7QF9l5iGAE .display span{fill:#9b59b6!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-RDfj0L7QF9l5iGAE .display tspan{fill:#ffffff!important;}#mermaid-svg-RDfj0L7QF9l5iGAE .key>*{fill:#f39c12!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-RDfj0L7QF9l5iGAE .key span{fill:#f39c12!important;stroke:#ffffff!important;stroke-width:2px!important;color:#ffffff!important;fontSize:14px!important;fontFamily:Arial!important;}#mermaid-svg-RDfj0L7QF9l5iGAE .key tspan{fill:#ffffff!important;}
是
加1键
减1键
复位键
否
是
否
是(偶数)
否(奇数)
系统上电初始化
RCC时钟/GPIO/定时器配置
初始化计数值为00
初始化数码管(全灭)
初始化LED(全灭)
检测按键操作?
按键消抖处理
按键类型?
计数值+1,99则清零
计数值-1,00则置99
计数值重置为00
定时器1秒中断触发?
奇偶检测:计数值%2
结果=0?
点亮绿灯,熄灭红灯
点亮红灯,熄灭绿灯
分解计数值为十位+个位
数码管动态扫描显示十位+个位
二、硬件选型与接线
2.1 核心硬件清单
| STM32控制器 | STM32F103C6T6最小系统板 | 1 | 核心控制与计数逻辑 |
| 两位数码管 | 共阳型(红色/绿色) | 1 | 显示00-99计数值 |
| LED指示灯 | 绿色/红色(含220Ω限流电阻) | 各1 | 指示奇偶状态 |
| 轻触按键 | 6*6mm直插型 | 3 | 加1/减1/复位计数 |
| 限流电阻 | 220Ω(1/4W) | 10 | 数码管/LED限流 |
| 电源模块 | 5V USB供电模块 | 1 | 系统供电 |
| 杜邦线 | 公对公/公对母 | 若干 | 硬件接线 |
| ST-Link下载器 | V2版 | 1 | 代码烧录与调试 |
| 面包板 | 400孔 | 1 | 硬件搭建平台 |
2.2 详细接线说明
(1)STM32与两位共阳数码管接线
共阳数码管公共端接VCC(3.3V),段选/位选通过220Ω限流电阻接STM32 GPIO口,接线如下:
| a | 段a | PB0 | 220Ω | 串联电阻后接GPIO |
| b | 段b | PB1 | 220Ω | 同上 |
| c | 段c | PB2 | 220Ω | 同上 |
| d | 段d | PB3 | 220Ω | 同上 |
| e | 段e | PB4 | 220Ω | 同上 |
| f | 段f | PB5 | 220Ω | 同上 |
| g | 段g | PB6 | 220Ω | 同上 |
| dp | 小数点 | PB7 | 220Ω | 本方案暂不使用,可悬空 |
| COM1 | 十位位选 | PA8 | 220Ω | 低电平点亮十位 |
| COM2 | 个位位选 | PA9 | 220Ω | 低电平点亮个位 |
| 公共端 | 共阳 | 3.3V | – | 所有COM端接3.3V |
(2)STM32与奇偶状态LED接线
| 绿色 | PA0 | PA0 | GND | 偶数时点亮 |
| 红色 | PA1 | PA1 | GND | 奇数时点亮 |
(3)STM32与按键接线
按键采用上拉输入模式,一端接STM32 GPIO(内部上拉),另一端接GND:
| 加1键 | PA2 | GND | 按下计数值+1 |
| 减1键 | PA3 | GND | 按下计数值-1 |
| 复位键 | PA4 | GND | 按下计数值重置为00 |
(4)电源接线
- STM32最小系统板:5V USB供电,确保板载3.3V稳压输出正常;
- 数码管/LED/按键:均由STM32 3.3V引脚或USB 5V供电(共阳数码管公共端接3.3V即可)。
三、软件开发环境搭建
3.1 环境准备
3.2 STM32CubeMX初始化配置步骤
步骤1:新建工程
- 打开STM32CubeMX,点击“New Project”,在搜索框输入“STM32F103C6T6”,选择对应型号(STM32F103C6Tx)后点击“Start Project”。
- 弹出“Project Manager”提示框,点击“OK”跳过(默认配置即可)。
步骤2:配置RCC时钟
- 左侧菜单栏选择“RCC”:
- High Speed Clock (HSE):选择“Crystal/Ceramic Resonator”(外部8MHz晶振),启用HSE;
- Low Speed Clock (LSI):保持默认(内部低速晶振,无需修改);
- 点击顶部“Clock Configuration”,配置系统时钟为72MHz:
- HSE = 8MHz → PLLMUL = 9 → SYSCLK = 72MHz;
- AHB Prescaler = 1(HCLK=72MHz);
- APB1 Prescaler = 2(PCLK1=36MHz);
- APB2 Prescaler = 1(PCLK2=72MHz);
- 确认所有时钟路径无红色报错。
步骤3:配置GPIO引脚
- 左侧菜单栏选择“GPIO”,按以下配置各引脚:
STM32引脚模式命名其他参数 PB0-PB7 Output Push Pull SEG_A-SEG_G/Dp 速度High,无上拉/下拉 PA8-PA9 Output Push Pull BIT_TEN/BIT_ONE 速度High,无上拉/下拉 PA0-PA1 Output Push Pull LED_GREEN/LED_RED 速度High,无上拉/下拉 PA2-PA4 Input Pull-Up KEY_ADD/KEY_SUB/KEY_RESET 速度High
步骤4:配置TIM2定时器(1秒中断)
- 左侧菜单栏选择“TIM2”:
- 模式选择“Up Counter”(向上计数);
- 配置参数:
- Prescaler(预分频器):7199(72MHz/7200=10kHz);
- Auto-Reload Value(自动重装值):9999(10kHz/10000=1Hz,即1秒中断一次);
- 启用“Auto-reload preload”(自动重装);
- 启用“NVIC Settings”中的“TIM2 global interrupt”(TIM2中断),优先级设为1(抢占优先级1,子优先级0)。
步骤5:生成工程代码
- 点击顶部“Project Manager”:
- Project Name:设置为“TwoDigit_Counter”;
- Project Path:选择非中文路径(如D:\\STM32_Projects\\TwoDigit_Counter);
- Toolchain/IDE:选择“MDK-ARM”,版本“V5”;
- 点击“Code Generator”:
- 勾选“Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral”(按外设分文件生成代码);
- 勾选“Copy all used libraries into the project folder”(复制库文件到工程);
- 点击“Generate Code”,生成完成后点击“Open Project”直接打开Keil工程。
四、核心代码编写
4.1 头文件与全局变量定义(main.h)
#ifndef __MAIN_H
#define __MAIN_H
#include "stm32f1xx_hal.h"
/* 引脚宏定义 */
// 数码管段选(共阳,低电平点亮)
#define SEG_A_PIN GPIO_PIN_0
#define SEG_A_GPIO_PORT GPIOB
#define SEG_B_PIN GPIO_PIN_1
#define SEG_B_GPIO_PORT GPIOB
#define SEG_C_PIN GPIO_PIN_2
#define SEG_C_GPIO_PORT GPIOB
#define SEG_D_PIN GPIO_PIN_3
#define SEG_D_GPIO_PORT GPIOB
#define SEG_E_PIN GPIO_PIN_4
#define SEG_E_GPIO_PORT GPIOB
#define SEG_F_PIN GPIO_PIN_5
#define SEG_F_GPIO_PORT GPIOB
#define SEG_G_PIN GPIO_PIN_6
#define SEG_G_GPIO_PORT GPIOB
#define SEG_DP_PIN GPIO_PIN_7
#define SEG_DP_GPIO_PORT GPIOB
// 数码管位选(共阳,低电平点亮)
#define BIT_TEN_PIN GPIO_PIN_8
#define BIT_TEN_GPIO_PORT GPIOA
#define BIT_ONE_PIN GPIO_PIN_9
#define BIT_ONE_GPIO_PORT GPIOA
// LED奇偶指示
#define LED_GREEN_PIN GPIO_PIN_0
#define LED_GREEN_GPIO_PORT GPIOA
#define LED_RED_PIN GPIO_PIN_1
#define LED_RED_GPIO_PORT GPIOA
// 按键
#define KEY_ADD_PIN GPIO_PIN_2
#define KEY_ADD_GPIO_PORT GPIOA
#define KEY_SUB_PIN GPIO_PIN_3
#define KEY_SUB_GPIO_PORT GPIOA
#define KEY_RESET_PIN GPIO_PIN_4
#define KEY_RESET_GPIO_PORT GPIOA
/* 功能宏定义 */
#define COUNTER_MAX 99 // 计数器最大值
#define COUNTER_MIN 0 // 计数器最小值
#define SCAN_DELAY 1 // 数码管扫描延时(ms),越小闪烁越不明显
/* 全局变量 */
extern uint8_t g_counter; // 两位计数器值(0-99)
extern uint8_t g_digit_ten; // 十位数值(0-9)
extern uint8_t g_digit_one; // 个位数值(0-9)
extern uint8_t g_odd_flag; // 奇偶标志:0=偶数,1=奇数
/* 函数声明 */
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM2_Init(void);
// 计数器相关函数
void Counter_Init(void);
void Counter_Add(void);
void Counter_Sub(void);
void Counter_Reset(void);
void Counter_Check_OddEven(void);
// 数码管驱动函数
void DigitTube_Init(void);
void DigitTube_ShowNum(uint8_t num, uint8_t bit_flag); // bit_flag:0=十位,1=个位
void DigitTube_Scan(void);
void DigitTube_ShowCounter(void);
// 按键处理函数
void Key_Scan(void);
// 延时函数(毫秒级,替代HAL_Delay,避免阻塞)
void Delay_ms(uint32_t ms);
#endif /* __MAIN_H */
4.2 外设初始化代码(main.c)
#include "main.h"
/* 全局变量定义 */
uint8_t g_counter = 0; // 初始计数值0
uint8_t g_digit_ten = 0; // 初始十位0
uint8_t g_digit_one = 0; // 初始个位0
uint8_t g_odd_flag = 0; // 初始偶数
/* 外设句柄 */
TIM_HandleTypeDef htim2;
/* 共阳数码管段码表(0-9,低电平点亮)
段序:a b c d e f g dp
数值0:0x3F → 二进制00111111 → a,b,c,d,e,f亮,g灭
*/
const uint8_t g_seg_code[10] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
/* 系统时钟配置函数 */
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/* 配置HSE外部晶振 */
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/* 配置系统时钟总线 */
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* TIM2初始化函数(1秒中断) */
static void MX_TIM2_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 7199; // 预分频器7199,72MHz/7200=10kHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 9999; // 自动重装值9999,10kHz/10000=1Hz
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
// 启动TIM2中断
HAL_TIM_Base_Start_IT(&htim2);
}
/* GPIO初始化函数 */
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 使能GPIO时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/* 配置数码管段选(PB0-PB7):推挽输出 */
GPIO_InitStruct.Pin = SEG_A_PIN|SEG_B_PIN|SEG_C_PIN|SEG_D_PIN
|SEG_E_PIN|SEG_F_PIN|SEG_G_PIN|SEG_DP_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* 配置数码管位选(PA8-PA9):推挽输出 */
GPIO_InitStruct.Pin = BIT_TEN_PIN|BIT_ONE_PIN;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 配置LED(PA0-PA1):推挽输出 */
GPIO_InitStruct.Pin = LED_GREEN_PIN|LED_RED_PIN;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 配置按键(PA2-PA4):上拉输入 */
GPIO_InitStruct.Pin = KEY_ADD_PIN|KEY_SUB_PIN|KEY_RESET_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 初始状态:数码管全灭,LED全灭 */
HAL_GPIO_WritePin(GPIOB, SEG_A_PIN|SEG_B_PIN|SEG_C_PIN|SEG_D_PIN
|SEG_E_PIN|SEG_F_PIN|SEG_G_PIN|SEG_DP_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, BIT_TEN_PIN|BIT_ONE_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, LED_GREEN_PIN|LED_RED_PIN, GPIO_PIN_RESET);
}
/* 错误处理函数(死循环+红灯闪烁) */
void Error_Handler(void)
{
__disable_irq();
while (1)
{
HAL_GPIO_TogglePin(LED_RED_GPIO_PORT, LED_RED_PIN);
Delay_ms(200);
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
// 断言失败处理(可选添加串口打印)
}
#endif /* USE_FULL_ASSERT */
/* 自定义毫秒延时函数(避免HAL_Delay阻塞中断) */
void Delay_ms(uint32_t ms)
{
uint32_t tickstart = HAL_GetTick();
uint32_t wait = ms;
while((HAL_GetTick() – tickstart) < wait)
{
// 空循环延时
}
}
4.3 计数器核心逻辑函数(main.c续)
/* 计数器初始化函数 */
void Counter_Init(void)
{
g_counter = 0;
g_digit_ten = g_counter / 10; // 分解十位:0/10=0
g_digit_one = g_counter % 10; // 分解个位:0%10=0
Counter_Check_OddEven(); // 初始奇偶检测
}
/* 计数器加1函数 */
void Counter_Add(void)
{
if(g_counter >= COUNTER_MAX)
{
g_counter = COUNTER_MIN; // 99→00循环
}
else
{
g_counter++;
}
// 更新十位和个位
g_digit_ten = g_counter / 10;
g_digit_one = g_counter % 10;
// 检测奇偶性
Counter_Check_OddEven();
}
/* 计数器减1函数 */
void Counter_Sub(void)
{
if(g_counter <= COUNTER_MIN)
{
g_counter = COUNTER_MAX; // 00→99循环
}
else
{
g_counter—;
}
// 更新十位和个位
g_digit_ten = g_counter / 10;
g_digit_one = g_counter % 10;
// 检测奇偶性
Counter_Check_OddEven();
}
/* 计数器复位函数 */
void Counter_Reset(void)
{
g_counter = COUNTER_MIN; // 重置为00
// 更新十位和个位
g_digit_ten = g_counter / 10;
g_digit_one = g_counter % 10;
// 检测奇偶性
Counter_Check_OddEven();
}
/* 奇偶检测函数 */
void Counter_Check_OddEven(void)
{
if(g_counter % 2 == 0)
{
// 偶数:绿灯亮,红灯灭
g_odd_flag = 0;
HAL_GPIO_WritePin(LED_GREEN_GPIO_PORT, LED_GREEN_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(LED_RED_GPIO_PORT, LED_RED_PIN, GPIO_PIN_RESET);
}
else
{
// 奇数:红灯亮,绿灯灭
g_odd_flag = 1;
HAL_GPIO_WritePin(LED_GREEN_GPIO_PORT, LED_GREEN_PIN, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED_RED_GPIO_PORT, LED_RED_PIN, GPIO_PIN_SET);
}
}
/* TIM2中断回调函数(1秒触发一次加1) */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM2)
{
Counter_Add(); // 定时器中断触发加1
}
}
4.4 数码管驱动函数(main.c续)
/* 数码管初始化函数(全灭) */
void DigitTube_Init(void)
{
// 段选全置高(共阳,高电平灭)
HAL_GPIO_WritePin(GPIOB, SEG_A_PIN|SEG_B_PIN|SEG_C_PIN|SEG_D_PIN
|SEG_E_PIN|SEG_F_PIN|SEG_G_PIN|SEG_DP_PIN, GPIO_PIN_SET);
// 位选全置高(灭)
HAL_GPIO_WritePin(GPIOA, BIT_TEN_PIN|BIT_ONE_PIN, GPIO_PIN_SET);
}
/* 数码管显示单个数字
num:要显示的数字(0-9)
bit_flag:0=十位,1=个位
*/
void DigitTube_ShowNum(uint8_t num, uint8_t bit_flag)
{
uint8_t seg_data = g_seg_code[num]; // 获取段码
// 1. 先关闭所有位选,避免鬼影
HAL_GPIO_WritePin(GPIOA, BIT_TEN_PIN|BIT_ONE_PIN, GPIO_PIN_SET);
// 2. 输出段码(共阳,段码取反:0→亮,1→灭)
HAL_GPIO_WritePin(SEG_A_GPIO_PORT, SEG_A_PIN, (seg_data & 0x01) ? GPIO_PIN_RESET : GPIO_PIN_SET);
HAL_GPIO_WritePin(SEG_B_GPIO_PORT, SEG_B_PIN, (seg_data & 0x02) ? GPIO_PIN_RESET : GPIO_PIN_SET);
HAL_GPIO_WritePin(SEG_C_GPIO_PORT, SEG_C_PIN, (seg_data & 0x04) ? GPIO_PIN_RESET : GPIO_PIN_SET);
HAL_GPIO_WritePin(SEG_D_GPIO_PORT, SEG_D_PIN, (seg_data & 0x08) ? GPIO_PIN_RESET : GPIO_PIN_SET);
HAL_GPIO_WritePin(SEG_E_GPIO_PORT, SEG_E_PIN, (seg_data & 0x10) ? GPIO_PIN_RESET : GPIO_PIN_SET);
HAL_GPIO_WritePin(SEG_F_GPIO_PORT, SEG_F_PIN, (seg_data & 0x20) ? GPIO_PIN_RESET : GPIO_PIN_SET);
HAL_GPIO_WritePin(SEG_G_GPIO_PORT, SEG_G_PIN, (seg_data & 0x40) ? GPIO_PIN_RESET : GPIO_PIN_SET);
// 小数点暂不显示,置高
HAL_GPIO_WritePin(SEG_DP_GPIO_PORT, SEG_DP_PIN, GPIO_PIN_SET);
// 3. 点亮对应位选
if(bit_flag == 0)
{
HAL_GPIO_WritePin(BIT_TEN_GPIO_PORT, BIT_TEN_PIN, GPIO_PIN_RESET); // 十位亮
}
else
{
HAL_GPIO_WritePin(BIT_ONE_GPIO_PORT, BIT_ONE_PIN, GPIO_PIN_RESET); // 个位亮
}
// 4. 短暂延时,保证显示亮度
Delay_ms(SCAN_DELAY);
}
/* 数码管动态扫描显示当前计数值 */
void DigitTube_ShowCounter(void)
{
// 显示十位
DigitTube_ShowNum(g_digit_ten, 0);
// 显示个位
DigitTube_ShowNum(g_digit_one, 1);
}
4.5 按键处理函数(main.c续)
/* 按键扫描函数(消抖处理) */
void Key_Scan(void)
{
static uint8_t key_add_state = 1; // 按键状态:1=未按下,0=按下
static uint8_t key_sub_state = 1;
static uint8_t key_reset_state = 1;
static uint32_t key_add_time = 0; // 消抖计时
static uint32_t key_sub_time = 0;
static uint32_t key_reset_time = 0;
// 加1键扫描(PA2)
if(HAL_GPIO_ReadPin(KEY_ADD_GPIO_PORT, KEY_ADD_PIN) == GPIO_PIN_RESET)
{
Delay_ms(20); // 消抖延时20ms
if(HAL_GPIO_ReadPin(KEY_ADD_GPIO_PORT, KEY_ADD_PIN) == GPIO_PIN_RESET)
{
key_add_state = 0;
key_add_time = HAL_GetTick();
}
}
else if(key_add_state == 0 && (HAL_GetTick() – key_add_time) > 200)
{
// 按键释放且超过200ms,判定为有效按下
key_add_state = 1;
Counter_Add(); // 执行加1
}
// 减1键扫描(PA3)
if(HAL_GPIO_ReadPin(KEY_SUB_GPIO_PORT, KEY_SUB_PIN) == GPIO_PIN_RESET)
{
Delay_ms(20);
if(HAL_GPIO_ReadPin(KEY_SUB_GPIO_PORT, KEY_SUB_PIN) == GPIO_PIN_RESET)
{
key_sub_state = 0;
key_sub_time = HAL_GetTick();
}
}
else if(key_sub_state == 0 && (HAL_GetTick() – key_sub_time) > 200)
{
key_sub_state = 1;
Counter_Sub(); // 执行减1
}
// 复位键扫描(PA4)
if(HAL_GPIO_ReadPin(KEY_RESET_GPIO_PORT, KEY_RESET_PIN) == GPIO_PIN_RESET)
{
Delay_ms(20);
if(HAL_GPIO_ReadPin(KEY_RESET_GPIO_PORT, KEY_RESET_PIN) == GPIO_PIN_RESET)
{
key_reset_state = 0;
key_reset_time = HAL_GetTick();
}
}
else if(key_reset_state == 0 && (HAL_GetTick() – key_reset_time) > 200)
{
key_reset_state = 1;
Counter_Reset(); // 执行复位
}
}
4.6 主函数(main.c续)
/* 主函数 */
int main(void)
{
/* 初始化HAL库 */
HAL_Init();
/* 配置系统时钟 */
SystemClock_Config();
/* 初始化外设 */
MX_GPIO_Init();
MX_TIM2_Init();
/* 初始化功能模块 */
DigitTube_Init(); // 数码管初始化
Counter_Init(); // 计数器初始化
/* 主循环 */
while (1)
{
// 1. 按键扫描(检测手动操作)
Key_Scan();
// 2. 数码管动态扫描显示(必须持续执行,否则显示闪烁)
DigitTube_ShowCounter();
// 3. 低功耗延时,降低CPU占用
Delay_ms(1);
}
}
五、代码烧录与调试
5.1 代码编译与烧录
- 若出现“TIM2中断未定义”:检查STM32CubeMX是否启用TIM2中断,HAL_TIM_Base_Start_IT(&htim2)是否调用;
- 若出现“GPIO引脚错误”:核对CubeMX配置的引脚与代码中宏定义的引脚是否一致。
- 将ST-Link下载器连接到STM32最小系统板的SWD接口(SWDIO、SWCLK、GND),并连接电脑USB;
- 给STM32供电(5V USB),检查面包板上的接线是否与2.2节一致,避免接反。
5.2 硬件调试步骤
步骤1:基础功能验证
- 上电检查:STM32上电后,数码管显示“00”,绿色LED常亮(0是偶数),蜂鸣器无声音,按键无按下时无异常。
- 定时器计数验证:等待1秒,数码管自动从“00”变为“01”,绿色LED熄灭、红色LED点亮(1是奇数);每1秒加1,验证到“99”后自动回到“00”。
步骤2:按键功能验证
- 加1键:按下加1键,计数值立即+1(如01→02),LED随奇偶性切换;
- 减1键:按下减1键,计数值立即-1(如02→01),LED随奇偶性切换;
- 复位键:按下复位键,无论当前数值是多少,立即回到“00”,绿色LED点亮。
步骤3:数码管显示调试
- 若数码管显示模糊/闪烁:调整SCAN_DELAY宏定义(改为2ms),或检查限流电阻是否接对(必须220Ω,阻值过大会导致亮度低);
- 若数码管显示错误数字:核对g_seg_code段码表是否正确,共阳数码管段码表与共阴相反,若用共阴数码管需将段码表改为{0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}的取反值(0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90)。
步骤4:奇偶检测调试
- 依次验证计数值0(偶)、1(奇)、2(偶)…99(奇),观察LED是否准确切换;
- 若LED不亮:检查PA0/PA1引脚接线,确认LED正负极未接反,限流电阻正常。
5.3 常见问题与解决
| 数码管全灭 | 位选/段选引脚接线错误、共阳/共阴搞反 | 核对接线,确认数码管公共端接3.3V,段码表匹配类型 |
| 计数器不自动加1 | TIM2中断未开启、预分频/重装值错误 | 检查HAL_TIM_Base_Start_IT(&htim2)是否调用,核对预分频器7199、重装值9999 |
| 按键无响应 | 上拉输入未配置、按键接反 | 确认GPIO为Input Pull-Up,按键一端接GPIO、一端接GND |
| LED常亮不切换 | 奇偶检测函数未调用、引脚接线错误 | 检查Counter_Check_OddEven()是否在计数更新后执行,核对PA0/PA1接线 |
| 数码管显示鬼影 | 动态扫描时未先关闭位选 | 确认DigitTube_ShowNum中先执行HAL_GPIO_WritePin关闭所有位选 |






