欢迎光临
我们一直在努力

场景实战:基于STM32F103C6的两位计数器与奇偶检测显示解决方案解析

文章目录

    • 一、方案整体设计与原理说明
      • 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口,接线如下:

数码管引脚功能STM32引脚限流电阻说明
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接线
LED颜色正极(串220Ω电阻)STM32引脚负极说明
绿色 PA0 PA0 GND 偶数时点亮
红色 PA1 PA1 GND 奇数时点亮
(3)STM32与按键接线

按键采用上拉输入模式,一端接STM32 GPIO(内部上拉),另一端接GND:

按键功能STM32引脚另一端说明
加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 环境准备

  • STM32CubeMX:下载V6.0及以上版本(官网:https://www.st.com/en/development-tools/stm32cubemx.html),用于图形化配置STM32外设,自动生成初始化代码。
  • Keil MDK-ARM:下载V5.36及以上版本,安装STM32F103C6T6对应的Device Pack(在Keil中通过“Pack Installer”搜索“STM32F1”安装),用于代码编写、编译和烧录。
  • ST-Link驱动:安装ST-Link V2驱动,确保电脑能识别下载器(驱动可从ST官网下载)。
  • 辅助工具:万用表(检查接线和电平)、串口调试助手(可选,用于调试输出)。
  • 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 代码编译与烧录

  • 代码补充:将上述所有代码按模块补充到Keil工程的main.c和main.h中,确保无遗漏;若Keil提示“g_seg_code未定义”,检查段码表是否在main.c中正确定义。
  • 编译代码:点击Keil工具栏的“Build”按钮,检查是否有错误(Error)和警告(Warning),确保0 Error、0 Warning。
    • 若出现“TIM2中断未定义”:检查STM32CubeMX是否启用TIM2中断,HAL_TIM_Base_Start_IT(&htim2)是否调用;
    • 若出现“GPIO引脚错误”:核对CubeMX配置的引脚与代码中宏定义的引脚是否一致。
  • 硬件连接:
    • 将ST-Link下载器连接到STM32最小系统板的SWD接口(SWDIO、SWCLK、GND),并连接电脑USB;
    • 给STM32供电(5V USB),检查面包板上的接线是否与2.2节一致,避免接反。
  • 代码烧录:点击Keil工具栏的“Download”按钮,等待烧录完成(底部提示“Download completed”)。
  • 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关闭所有位选

    六、功能扩展建议

  • 计数速度可调:添加第四个按键,切换定时器中断频率(1秒/0.5秒/2秒),实现不同计数速度;
  • 计数方向切换:添加按键切换“加计数”/“减计数”模式,定时器中断可自动减1;
  • 数码管小数点显示:利用dp段显示奇偶性(偶数亮小数点,奇数灭),替代LED指示;
  • 串口输出计数:配置USART1,将当前计数值和奇偶状态通过串口打印到电脑,便于调试;
  • 计数阈值报警:设置阈值(如50),计数值达到阈值时触发蜂鸣器报警;
  • 掉电保存计数:外接EEPROM(AT24C02),每次计数更新后保存到EEPROM,上电自动读取。
  • 总结

  • 本方案以STM32F103C6为核心,实现了00-99循环计数、奇偶检测、数码管显示和按键交互四大核心功能,硬件接线简单,代码模块化且注释详尽,零基础小白可按步骤完全落地。
  • 系统核心逻辑通过Mermaid流程图清晰呈现,关键模块(计数器、数码管驱动、按键消抖、奇偶检测)拆分为独立函数,便于理解和修改;核心参数(计数范围、扫描延时)可通过宏定义快速调整。
  • 调试时需优先验证电源和接线,再逐步测试定时器计数、按键交互、显示和奇偶检测功能,遇到问题可通过Keil的变量监控功能(Watch Window)查看g_counter、g_odd_flag等关键变量,快速定位故障点。
  • 赞(0)
    未经允许不得转载:171主机测评 » 场景实战:基于STM32F103C6的两位计数器与奇偶检测显示解决方案解析
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址