2026年寒假在家一起练 - 基于RP2350B核心板实现的LED与数码管多功能计时器
该项目使用了RP2350B核心板,实现了LED与数码管多功能计时器的设计,它的主要功能为:实现正计时、倒计时、计次随机数的LED与数码管显示。
标签
嵌入式系统
数字逻辑
显示
ADC
开发板
USB
Happy An
更新2026-03-24
上海交通大学
31


电子森林 2026 年寒假在家一起练

所选任务介绍

我选择了“平台2 - 兼容小脚丫 FPGA 板上功能及扩展生态的 RP2350B 核心板”作为本次“寒假在家一起练”学习平台,并选择完成“任务1 - 【RP2350B核心板】LED 与数码管多功能计时器”。

该任务要求完成以下四项功能:

  1. 使用 4 个按键实现计时开始、暂停、清零与模式切换,要求按键消抖与长按短按区分。
  2. 通过 4 个拨码开关选择三种工作模式:正计时、倒计时、计次随机数显示。
  3. 双位七段数码管(74HC595 驱动)显示当前数值,8 颗单色 LED 显示进度条,2 颗 WS2812 显示当前模式颜色。
  4. 通过 USB 虚拟串口输出模式切换与数值变化日志。

经过对 RP2350B 核心板片上资源、芯片手册和 PIO 汇编的学习,我成功实现了上述四项功能。

项目介绍

本项目是一个基于树莓派 RP2350B 的较完善计时系统。项目采用模块化设计思想,将显示、输入、逻辑控制分层处理。

系统上电后,首先读取硬件拨码开关的状态以决定初始工作模式;随后进入主循环,实时响应用户通过 ADC 按键和拨码开关输入的指令,若发生按键模式切换,串口会向上位机器发送模式状态文本。系统能够以毫秒级精度进行计时或运算,并通过高频刷新的方式,同时驱动以下外设:

  • 数码管:显示当前数值
  • LED 条:显示进度比例
  • RGB 灯:显示当前模式

所有使用到的硬件介绍

1. 移位寄存器 74HC595 芯片和其驱动的七段数码管

74HC595 是一款高速 CMOS 器件,其核心功能是将串行输入数据转换为并行输出(SIPO),仅需微控制器的 3 根引脚(数据、时钟、锁存)即可扩展出 8 路输出通道。它内部集成了 8 位移位寄存器和一个带有三态输出的存储寄存器(锁存器),支持多片级联以无限扩展 I/O 数量,是嵌入式开发中驱动 LED 阵列、数码管或控制继电器的经典低成本方案。

七段共阴极数码管是一种由 8 个发光二极管(7 个条形段和 1 个小数点)组成的显示器件,其内部所有 LED 的阴极(负极)共同连接到公共地端(GND)。在实际应用中,用户只需给对应的段引脚(a-g 及 dp)施加高电平即可点亮该段,通过对不同段位的逻辑控制来组合显示数字 0-9 以及部分英文字母,常用于电表、时钟等需要高亮度数字显示的工业与消费电子设备。

2. WS2812B RGB LED

WS2812B 是一种将控制电路与 RGB 芯片集成于一体的智能外控 LED 光源,通常采用 5050 封装。它最显著的特点是只需一根信号线即可实现级联控制(Single-wire control),支持真彩色 256 级亮度显示和超过 1600 万种色彩组合。由于其内置了数据整形电路和上电重置电路,在高频级联应用中能有效保持信号不失真,广泛应用于幻彩灯条、像素屏及各种嵌入式系统的视觉反馈。

3. 微处理器 RP2350 芯片

RP2350 是由树莓派官方设计的高性能微控制器芯片,其核心特征是采用了创新的双指令集架构,内部集成了两颗时钟频率高达 150 MHz 的核心,并支持在 Arm Cortex-M33 与开源 RISC-V(Hazard3)指令集之间自由切换。该芯片配备了 520 KB 的片上 SRAM,引入了基于 TrustZone 的安全架构(支持安全启动和抗篡改 OTP 存储),并搭载了升级版的 PIO(可编程 I/O)状态机以及全新的 HSTX(高速串行透传)外设,能够以极高的灵活性和效率处理复杂的实时任务、安全加密以及高速数字信号传输。

4. LED 进度条(8 颗独立 LED)

采用 Charlieplexing 技术连接。仅使用 4 个 GPIO 引脚(IO1 - IO4),利用 GPIO 的三态逻辑(高电平、低电平、高阻态 Input),理论上最多可驱动 N * (N - 1) = 12 颗 LED。本项目驱动了 8 颗,用于直观展示计数值在 0 - 99 量程中的比例。

5. 按键与拨码开关

采用电阻分压网络设计。4 个按键(K1 - K4)和 4 位拨码开关全部连接到同一个 ADC 引脚。按下不同按键或拨动开关会改变分压电阻比例,从而产生不同的电压值,主控通过 ADC 读取电压值来识别输入信号。

方案框图和项目设计思路介绍

方案框图

设计思路介绍

系统采用“输入采集 - 状态判断 - 逻辑处理 - 显示刷新”四层设计:

  1. 通过 ADC 采集按键与拨码开关电压。
  2. 通过阈值判断识别当前输入状态。
  3. 根据当前模式执行正计时、倒计时或随机数显示逻辑。
  4. 通过数码管、LED 进度条和 WS2812 实时反馈系统状态。

调试软件及使用的编程语言说明、软件流程图及关键代码介绍

项目使用 Visual Studio Code IDE,结合树莓派官方的 Raspberry Pi Pico 插件对程序进行编写、编译、上传和调试。RP2350B 被配置为 ARM 处理器,采用 C 语言进行开发。

软件流程图

关键代码 1

关键代码 1 体现了项目中使用 PIO 驱动 74HC595 的方法。由于七段数码管需要持续输出段码与位选信号,如果完全依赖软件逐位翻转 GPIO,不仅效率较低,也会增加主循环负担。因此本项目利用 RP2350 的 PIO 外设完成串行移位输出,从而更稳定地驱动数码管显示。

.wrap_target
    out pins, 1   side 0 [1]
    nop           side 1 [1]
.wrap


static inline void hc595_program_init(PIO pio, uint sm, uint offset, uint pin_ser, uint pin_sck) {
    pio_sm_config c = hc595_program_get_default_config(offset);


    sm_config_set_out_pins(&c, pin_ser, 1);
    pio_gpio_init(pio, pin_ser);
    pio_sm_set_consecutive_pindirs(pio, sm, pin_ser, 1, true);


    sm_config_set_sideset_pins(&c, pin_sck);
    pio_gpio_init(pio, pin_sck);
    pio_sm_set_consecutive_pindirs(pio, sm, pin_sck, 1, true);


    sm_config_set_out_shift(&c, false, true, 16);
    sm_config_set_clkdiv(&c, 50.0);


    pio_sm_init(pio, sm, offset, &c);
    pio_sm_set_enabled(pio, sm, true);
}

说明:
- `out pins, 1` 指令负责将串行数据逐位送到 `SER` 引脚。
- `side 0` 和 `side 1` 用于同步控制移位时钟 `SCK`,在一个很短的周期内完成时钟翻转。
- `sm_config_set_out_shift(..., 16)` 表示每次向状态机发送 16 位数据,对应“段码 + 位选”组合。
- `PIO` 的引入减轻了主控软件逐位输出的负担,使数码管显示更加稳定,也体现了 RP2350 在底层时序控制方面的优势。

关键代码 2

关键代码 2 体现了单线 ADC 输入识别的方法。由于 4 个按键和 4 位拨码开关共用一个 ADC 通道,因此程序通过电压阈值分段来区分不同输入。

static KeyID adc_to_key(uint16_t val) {
    if (val < THRES_K1) return KEY_START;
    if (val < THRES_K2) return KEY_PAUSE;
    if (val < THRES_K3) return KEY_RESET;
    if (val < THRES_K4) return KEY_MODE;
    return KEY_NONE;
}


static SysMode adc_to_mode(uint16_t val) {
    if (val >= THRES_K4 && val < 3675) return MODE_RANDOM;
    if (val >= 3675 && val < 3725) return MODE_COUNT_DOWN;
    return MODE_COUNT_UP;
}


InputEvent input_get_event(void) {
    InputEvent evt = {KEY_NONE, EVENT_NONE};
    uint16_t raw = adc_read();
    KeyID curr_key = adc_to_key(raw);
    uint32_t now = to_ms_since_boot(get_absolute_time());


    if (curr_key == KEY_NONE) {
        detected_dip_mode = adc_to_mode(raw);
    }


    if (curr_key != KEY_NONE) {
        if (last_stable_key == KEY_NONE) {
            last_stable_key = curr_key;
            press_start_time = now;
        }
    } else {
        if (last_stable_key != KEY_NONE && (now - press_start_time > DEBOUNCE_MS)) {
            evt.key = last_stable_key;
            evt.type = EVENT_SHORT_PRESS;
        }
        last_stable_key = KEY_NONE;
    }
    return evt;
}

说明:
- `adc_to_key()` 用不同阈值判断当前是哪个按键被按下。
- `adc_to_mode()` 在没有按键按下时,读取拨码开关对应电压并解析成三种工作模式。
- `input_get_event()` 通过时间差实现按键消抖,并保留了长按扩展能力。
- 这种方法节省了 GPIO 资源,是本项目输入设计中的关键。

关键代码 3

关键代码 3 展示了系统如何将运算结果同步输出到数码管和 LED 进度条。数码管负责显示当前数值,8LED 用于显示进度比例。

void display_show_number(int number) {
    if (number > 99) number = 99;
    if (number < 0) number = 0;


    display_buffer[0] = SEG_CODE[number / 10];
    display_buffer[1] = SEG_CODE[number % 10];
}


void leds_set_progress(int count) {
    if (count < 0) count = 0;
    if (count > 8) count = 8;
    target_progress = count;
}


void display_refresh(void) {
    if (current_digit_idx == 0) {
        send_data(0x02, display_buffer[0]);
    } else {
        send_data(0x01, display_buffer[1]);
    }
    current_digit_idx = !current_digit_idx;
}

说明:
- `display_show_number()` 先把当前数值拆分为十位和个位,再转换为七段数码管段码。
- `display_refresh()` 采用动态扫描方式交替刷新两位数码管,从而减少硬件引脚占用。
- `leds_set_progress()` 将当前值映射为 `0` 到 `8` 的 LED 亮灯数量,实现进度条效果。
- 这部分代码与主循环配合后,可以实现“数值显示 + 进度可视化”的同步反馈。

功能展示图及说明(实物展示、软件或工具调试)

功能展示图 1

说明 1

正计时模式初始状态和工作状态,WS2812B 显示绿色,数码管计数和8个LED亮灯数目比例匹配。

功能展示图 2

说明 2

计时模式初始状态和工作状态,WS2812B 显示蓝色,数码管计数和8个LED亮灯数目比例匹配。

功能展示图 3

说明 3

计次随机模式初始状态和工作状态,WS2812B 显示红色,数码管计数和8个LED亮灯数目比例匹配。

功能展示图 4

说明 4

上位机接收到的开发板发送的模式切换文本数据,0为正计时,1为倒计时,2为计次随机。

项目中遇到的难题及解决方法

项目中主要遇到了两个难题。

1. Charlieplexing LED 的显示亮度与闪烁问题

最初我对整个电路的工作方式感到困惑,后来在 AI 的帮助下才终于理解。Charlieplexing 需要轮询点亮 LED,如果主循环逻辑处理时间过长,LED 就会出现明显闪烁或亮度不均。

最终的解决方法如下:

  • 将显示刷新函数 leds_refresh()display_refresh() 放在主循环最顶端。
  • 保证极短延时(100 us)。
  • 将业务逻辑处理限制为每 10 ms 执行一次。

通过这种“高频刷新显示,低频处理逻辑”的时间分片调度方式,成功解决了 LED 的闪烁和亮度不均问题。

2. 单线 ADC 按键与拨码开关的信号冲突

原理图中,4 个按键和 4 个拨码开关共用一个 ADC 通道。当拨码开关处于不同组合时,基准电压会发生变化,导致按键按下的电压值漂移,难以区分“模式切换”和“按键动作”。

为了解决这个问题,我编写了专门的校准程序,打印所有状态下的 ADC 原始值。通过数据分析发现:

  • 按键按下时的电压值 < 3530
  • 拨码开关对应的电压值 > 3600

因此,我在代码中设立了 3580 作为分界阈值。同时,引入“手动接管标志位”逻辑,确保在使用 K4 按键后,程序不再响应拨码开关的电压波动,彻底解决了逻辑冲突。

心得体会

通过本次基于 RP2350 的项目开发,我收获颇丰。

首先,我对“软硬件协同设计”有了更深的理解。在处理单线 ADC 输入时,单纯依靠硬件电路无法完美解决冲突,必须配合软件上的“阈值分段”和“状态机逻辑”才能实现稳定交互。这让我明白,优秀的嵌入式工程师不仅要懂代码,更要懂电路特性。

其次,我对底层时序控制产生了敬畏之心。在调试 WS2812 的过程中,从“亮白光”到“无法变色”再到“完美控制”,我深刻体会到了高主频处理器带来的双刃剑效应。高主频虽然提升了运算速度,但也让传统的软件延时变得不再可靠,掌握汇编指令和中断控制对于底层驱动开发至关重要。

接着,AI在我学习过程中帮助很大,大模型的存在极大的减少了学习时间,提高了效率,现在完全不敢回忆没有AI辅助的学习开发生活了😀。

最后,模块化编程习惯的养成让我的开发效率倍增。在项目初期,我就将 DisplayInputLEDsWS2812 封装成独立的驱动文件。这不仅让 main.c 逻辑更加清晰,也使得排查问题时可以单独隔离测试。

综上所述,这个项目不仅锻炼了我的编程能力,更提升了我分析问题、拆解问题和解决问题的工程思维,为今后进行更复杂的嵌入式系统开发打下了坚实基础。


附件下载
Counter.zip
项目代码压缩包
团队介绍
单人团队
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号