1. 项目概述从机械到数字一个计数器的自我修养“计数器”这个概念听起来既古老又现代。从结绳记事到算盘珠子再到今天无处不在的电子显示屏人类对“计数”的需求从未改变。我最近动手做了一个“DIY数字点击计数器”起因很简单工作室里需要统计一些小零件的数量每次用纸笔或者手机备忘录总觉得不够直观也容易出错。市面上当然有成品的电子计数器但要么功能过剩价格不菲要么造型呆板缺乏趣味。于是一个念头冒了出来为什么不自己做一个呢它应该足够简单一个按钮、一个屏幕按一下数字就加一它也应该足够可靠用常见的元件就能搭建最重要的是这个过程本身就是一次从想法到实物的完整创造体验。这个项目非常适合电子爱好者入门或者任何想给枯燥工作增添一点科技乐趣的朋友。你不需要是电子工程专业出身只要对焊接有点兴趣愿意跟着步骤一步步来就能收获一个完全属于自己、功能定制的实用工具。无论是用来统计健身次数、记录阅读页数还是在小型工作坊中统计产量它都能派上用场。接下来我就把这个从零开始制作数字点击计数器的全过程包括电路设计、元件选型、程序编写到外壳制作的每一个细节毫无保留地分享给你。2. 核心思路与方案选型为什么是“单片机按钮显示屏”2.1 需求拆解与方案对比做一个计数器核心功能就三点感知动作输入、处理数据核心、显示结果输出。围绕这三点有几种实现路径纯硬件逻辑电路方案使用CD40110之类的十进制加减计数器芯片配合七段数码管。优点是响应极快无需编程缺点是功能固定比如复位、预置数需要更多硬件电路来实现灵活性差体积也相对较大。可编程逻辑器件方案比如使用CPLD或FPGA。性能强大可以集成非常复杂的逻辑但对于一个简单的计数器来说属于“高射炮打蚊子”成本高、开发复杂完全不必要。微控制器方案也就是我们常说的单片机方案。用一个集成了处理器、存储器和输入输出接口的小芯片通过编程来控制一切。这正是我选择的方案。为什么最终拍板单片机核心优势在于“灵活性”和“性价比”。通过编程我可以轻松实现点击计数、长按清零、双击归零到预设值、甚至通过多个按钮实现加减计数等复杂功能而硬件上只需增加一个按钮而已。同时像Arduino Nano、STM32F103C8T6蓝色药丸这类单片机开发板价格极其低廉资源却足够丰富。显示屏的选择也很多样从低成本的0.96寸OLED到更大的LCD1602液晶屏都可以即插即用。2.2 核心元件选型背后的逻辑确定了单片机方案具体选什么型号里面也有不少讲究主控芯片Arduino Nano vs. STM32Arduino Nano这是最无脑的选择。基于ATmega328P有丰富的库支持和社区资源编程环境Arduino IDE对新手极其友好。如果你完全是第一次接触单片机想快速看到成果Nano是首选。它的5V工作电压也完美匹配大多数显示屏和外围电路。STM32F103C8T6我这次选择的是它俗称“蓝色药丸”。原因有几个首先它基于ARM Cortex-M3内核性能比8位的AVR强很多虽然对于计数器来说性能溢出但“战未来”如果想后续增加蓝牙传输数据、电池电量检测等更复杂功能它有足够的潜力。其次价格比Arduino Nano兼容板更便宜。最后我想挑战一下稍微“底层”一点的开发环境使用PlatformIO或Keil更深入地理解寄存器和HAL库。对于有一定C语言基础想从Arduino过渡到更专业嵌入式开发的朋友STM32是个很好的跳板。显示屏OLED vs. LCD16020.96寸OLEDI2C接口我最终选择了它。优点是超高对比度、自发光的特性显示数字非常清晰锐利尤其在光线不佳的环境下。功耗极低适合电池供电。I2C接口只需要两根数据线SDA, SCL加电源线即可驱动节省单片机IO口。缺点是尺寸较小显示内容有限且长期静态显示可能有烧屏风险但对于计数器这种频繁变化的数字问题不大。LCD1602液晶屏经典款显示面积大可显示两行16个字符。需要至少6个IO口驱动如果使用I2C转接板则可以减少到2个。缺点是背光功耗相对较高显示效果没有OLED那么“酷炫”。输入装置按钮与防抖按钮是最简单的选择。但这里有一个关键陷阱按键抖动。机械按钮在按下和弹起的瞬间会产生数毫秒到数十毫秒的电平快速波动如果程序直接检测电平变化一次按压可能会被误判为多次。因此软件防抖是必须实现的环节。通常采用“延时检测法”或“状态机法”我会在编程部分详细说明。电源方案USB供电 vs. 电池供电为了方便调试和固定场所使用我保留了USB接口供电通过主控板的USB口。为了便携性我额外增加了一个3.7V锂电池如常见的10440电池和充电管理模块如TP4056。单片机工作电压是3.3V因此还需要一个升压稳压模块如MT3608将电池电压稳定升压到5V或3.3V给系统供电。这是一个完整的便携电源解决方案。3. 电路设计与硬件搭建详解3.1 原理图设计与解读整个系统的电路原理图非常简单遵循“最小系统外设”的原则。以STM32F103C8T6为例单片机最小系统核心是STM32芯片需要连接复位电路一个10kΩ上拉电阻到3.3V一个0.1uF电容到地、启动模式选择电路BOOT0和BOOT1引脚通过跳线或电阻设置通常BOOT0接地BOOT1随意表示从主Flash启动、以及电源滤波电容在3.3V和GND之间靠近芯片引脚处放置一个0.1uF和一个10uF的电容。OLED显示模块连接VCC - 3.3V注意有些OLED模块兼容5V和3.3V但STM32的IO口是3.3V电平为保险起见模块供电也接3.3VGND - GNDSCL - PB6STM32的I2C1时钟线SDA - PB7STM32的I2C1数据线按键输入电路按键一端接地GND另一端连接单片机的一个IO口如PA0同时在该IO口与3.3V之间连接一个10kΩ的上拉电阻。这样当按键未按下时IO口通过上拉电阻读到高电平3.3V当按键按下时IO口直接接地读到低电平0V。上拉电阻保证了空闲状态的稳定。电源电路电池供电支路电池正极接TP4056充电模块的BAT负极接BAT-。TP4056的OUT和OUT-输出接升压模块MT3608的输入。MT3608的输出调整为5V为整个系统供电。注意TP4056的CHRG充电指示和STDBY充满指示可以接两个LED灯到地用于显示充电状态。USB供电支路开发板的USB口或单独的USB转TTL模块的5V输出可以直接为系统供电。为了避免电池和USB同时供电产生冲突可以在两路电源之间加入二极管隔离电路两个肖特基二极管阳极分别接电池升压后的5V和USB的5V阴极并接在一起给系统供电这样电压高的一路会自动为系统供电。注意实际焊接时特别是使用面包板或万能板电源走线要尽量粗短并在关键芯片的电源引脚附近放置去耦电容通常是0.1uF这是保证系统稳定运行、防止莫名重启的关键很多新手会忽略这一点。3.2 硬件焊接与组装注意事项我选择用一块洞洞板万能板来搭建整个系统这样比面包板更牢固耐用。布局规划在焊接前先用元件在板子上大致摆放一下规划好位置。原则是电源模块TP4056, MT3608放在板子一侧远离模拟或高频部分虽然本项目没有单片机放在中心显示屏接口和按键放在板子边缘便于操作和观看的位置。良好的布局能简化布线减少干扰。焊接顺序建议先焊接电源部分焊接完成后用万用表测量输出电压是否正确、稳定确保电源没问题再继续。然后焊接单片机最小系统及其周边的电阻电容。接着焊接显示屏和按键的接口排针。最后连接所有的飞线。飞线技巧使用不同颜色的导线区分功能例如红色为VCC黑色为GND黄色为信号线。这样在调试时一目了然。走线尽量横平竖直在板子背面走线避免交叉必要时可以使用“跳线”电阻剪下来的引脚在元件面进行短距离跨接。外壳设计与制作为了让计数器更美观耐用一个外壳必不可少。我使用免费的3D建模软件如Fusion 360设计了一个简单的盒子分为底盖和面盖。面盖上开了两个孔一个方形孔用于露出OLED屏幕一个圆孔用于安装按钮。设计时一定要预留足够的内部空间并考虑好螺丝柱的位置。将模型导出为STL格式用3D打印机PLA材料打印出来。如果没有3D打印机也可以使用现成的塑料盒子或亚克力板手工切割制作。4. 软件编程与功能实现硬件是躯体软件是灵魂。下面以STM32为例使用HAL库在PlatformIO环境下进行编程讲解。4.1 开发环境搭建与工程配置安装PlatformIO它是一个跨平台的嵌入式开发工具可以作为VSCode的插件安装。比Keil/MDK更轻量对开源库的支持更好。创建新项目选择板子类型为“ST STM32” - “BluePill F103C8”。PlatformIO会自动配置好基本的编译工具链和框架选择HAL库。添加依赖库在platformio.ini文件中添加OLED显示所需的库例如非常流行的SSD1306驱动库adafruit/Adafruit SSD1306和GFX库adafruit/Adafruit-GFX-Library。PlatformIO会自动下载和管理这些库。4.2 核心代码解析#include Arduino.h // PlatformIO下使用Arduino风格的API #include Wire.h #include Adafruit_GFX.h #include Adafruit_SSD1306.h // 硬件引脚定义 #define BUTTON_PIN PA0 #define OLED_RESET -1 // 如果OLED没有RESET引脚则设为-1 // 全局变量 Adafruit_SSD1306 display(128, 64, Wire, OLED_RESET); volatile unsigned long count 0; // 计数值使用volatile防止编译器优化 unsigned long lastDebounceTime 0; // 上次防抖时间 const unsigned long debounceDelay 50; // 防抖延时毫秒 int buttonState HIGH; // 当前稳定按钮状态 int lastButtonState HIGH; // 上次读取的按钮状态 void setup() { // 初始化串口用于调试 Serial.begin(115200); // 初始化按钮引脚为上拉输入模式 pinMode(BUTTON_PIN, INPUT_PULLUP); // 初始化OLED显示屏 if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // I2C地址通常是0x3C或0x3D Serial.println(F(SSD1306 allocation failed)); for(;;); // 死循环初始化失败则停在这里 } display.clearDisplay(); display.setTextSize(4); // 大号字体 display.setTextColor(SSD1306_WHITE); display.setCursor(20, 20); display.println(0); display.display(); // 从EEPROM或模拟EEPROM读取上次保存的计数值 // STM32F103C8T6没有硬件EEPROM可以使用Flash模拟 // 这里为了简化先注释掉。实际应用应考虑掉电保存。 // EEPROM.get(0, count); // updateDisplay(); } void loop() { // 1. 读取按钮引脚当前电平 int reading digitalRead(BUTTON_PIN); // 2. 按键防抖处理 - 状态机法更可靠 // 如果读取状态与上次稳定状态不同则重置防抖计时器 if (reading ! lastButtonState) { lastDebounceTime millis(); } // 如果经过防抖延时后状态仍然保持不变且与当前稳定状态不同则认为是有效变化 if ((millis() - lastDebounceTime) debounceDelay) { if (reading ! buttonState) { buttonState reading; // 只有当按钮状态变为稳定低电平按下时才触发计数 if (buttonState LOW) { countIncrement(); } } } // 保存本次读取状态用于下次比较 lastButtonState reading; // 3. 长按清零检测示例可扩展 // 可以加入长按判断逻辑当检测到按钮持续按下超过2秒时将count清零。 // 这里留作扩展功能。 } void countIncrement() { count; Serial.print(Count: ); // 串口输出调试用 Serial.println(count); updateDisplay(); // 可选每计数一定次数保存到非易失存储器 // if (count % 10 0) { // EEPROM.put(0, count); // } } void updateDisplay() { display.clearDisplay(); display.setTextSize(4); display.setTextColor(SSD1306_WHITE); display.setCursor(20, 20); // 调整坐标使数字居中 // 将数字格式化为固定宽度看起来更整齐 char buffer[10]; sprintf(buffer, %6lu, count); // 右对齐占6位 display.println(buffer); display.display(); }代码关键点解读防抖算法这是按键处理的核心。代码中使用了状态机防抖。它不仅仅是在检测到变化后简单延时而是持续监测电平只有当电平变化后能稳定保持超过debounceDelay如50ms时间才确认是一次有效的动作。这种方法比简单的延时更可靠能有效滤除抖动。volatile关键字count变量被声明为volatile因为它会在主循环和可能的中断服务程序如果未来使用中断方式中被修改。这告诉编译器不要对这个变量进行激进的优化确保每次读取都从内存中获取最新值。显示优化使用sprintf将数字格式化为固定宽度这样数字变化时不会左右晃动观感更好。掉电保存代码中注释掉了EEPROM操作部分。STM32F103C8T6没有真正的EEPROM但可以将内部Flash的最后几页模拟成EEPROM来使用需使用特定的库如FlashStorage。需要注意的是Flash有擦写寿命约1万次频繁保存会缩短寿命。一个折中的方案是只在计数变化较大比如每10次或设备主动关机时保存一次。4.3 功能扩展思路基础计数功能实现后你可以轻松地扩展它多模式计数通过增加一个模式切换按钮实现加计数、减计数、循环计数0-1-2-...-N-0等模式。预设值与归零双击按钮将计数归零长按按钮进入预设值设置模式通过另一个按钮增减预设值再次长按确认并开始从该值计数。数据输出增加一个蓝牙模块如HC-05或Wi-Fi模块如ESP-01S将计数数据实时发送到手机App或电脑实现远程监控和统计。低功耗优化如果使用电池供电可以编程让单片机在无操作时进入睡眠模式Stop Mode仅通过按键外部中断唤醒这将极大延长续航。5. 调试、问题排查与优化实录即使按照步骤操作第一次制作也难免遇到问题。下面是我在制作和调试过程中遇到的一些典型情况及解决方法。5.1 常见问题速查表问题现象可能原因排查步骤与解决方法上电后无任何反应显示屏不亮1. 电源未接通或电压不对。2. 单片机未正确启动BOOT引脚配置错误。3. 核心芯片或电源模块损坏。1. 用万用表测量系统供电电压5V/3.3V是否正常。2. 检查STM32的BOOT0引脚是否通过10k电阻可靠接地对于从Flash启动。3. 触摸主控芯片看是否轻微发热严重发热可能短路或损坏。4. 尝试通过串口给单片机烧录一个最简单的LED闪烁程序测试芯片是否工作。显示屏白屏或乱码1. I2C通信失败地址错误、线接反、上拉电阻缺失。2. 供电不足。1. 用逻辑分析仪或示波器检查I2C总线的SCL和SDA是否有波形。没有的话检查接线。2. 确认程序中使用的I2C地址0x3C或0x3D与模块一致。有些模块背面有电阻选择地址。3. I2C总线SDA, SCL需要接上拉电阻通常4.7kΩ到10kΩ到3.3V。虽然模块或单片机内部可能有上拉但外部上拉更可靠。4. 检查给OLED模块的供电电压是否足够且稳定。按键计数不准确按一次跳多次按键抖动未处理好。1.首要检查软件防抖增大debounceDelay值如从50ms调到100ms测试。2.检查硬件在按钮两端并联一个0.1uF的电容到地可以吸收部分物理抖动。3. 确保上拉电阻10kΩ已正确连接按钮引脚模式设置为INPUT_PULLUP。计数偶尔自动增加或复位1. 按键引脚受到干扰。2. 电源不稳定导致单片机复位。3. 程序跑飞。1. 检查按键信号线是否过长是否与电源等大电流线路平行走线。可以尝试给按键引脚加一个软件滤波如连续读取多次才确认状态。2. 用示波器观察系统电源电压在按键按下时是否有大幅跌落。加强电源滤波电容在单片机电源引脚附近增加一个100uF的电解电容。3. 检查程序中是否有数组越界、死循环等bug。可以加入看门狗Watchdog功能在程序异常时自动复位。电池供电时工作时间很短1. 电池容量不足或老化。2. 系统静态功耗过大。3. 升压模块效率低。1. 测量系统在工作模式和待机模式下的总电流万用表串联在电池回路中。2.优化功耗将不用的单片机IO口设置为模拟输入模式在循环中增加空闲时进入睡眠模式的代码选择低功耗的LDO或DC-DC芯片如果不需要一直显示可以让OLED定时关闭背光。3. 检查升压模块的空载电流选择高效率的型号。5.2 我的实操心得与避坑指南“先电源后信号”这是电子制作的铁律。在连接任何功能模块前务必确保电源电压准确、稳定、无毛刺。一个不干净的电源是万恶之源会导致各种难以排查的随机故障。善用串口调试在代码的关键位置如按键检测、计数函数加入Serial.print()语句将变量值或状态信息打印到串口监视器。这是最直接、最有效的调试手段能让你清晰地知道程序运行到了哪一步变量的值是什么。模块化测试不要一次性把全部代码写完、全部硬件连好再测试。应该分模块验证先写个程序让OLED显示“Hello World”测试通信用再写个程序读按键并在串口打印测试输入通最后把两者结合起来。这样一旦出问题排查范围就小得多。为扩展留余地在设计洞洞板布局和编写程序时有意识地预留一些空间和接口。比如多引出几个GND和VCC的测试点在程序里用#define来定义引脚而不是直接写死数字把按键扫描、显示刷新写成独立的函数。这样未来想增加功能比如第二个按钮、蜂鸣器会非常轻松。外壳的适配性3D打印外壳时孔位和尺寸一定要留出公差。按钮孔最好比按钮直径大0.2-0.5mm屏幕窗口要比屏幕可视区大一圈并用台阶来卡住屏幕而不是靠胶水。可以在设计软件中用一个简单的“配合”测试来模拟安装过程。制作这个DIY数字点击计数器的整个过程就像完成一次微型的产品开发。从需求分析、方案选型、硬件设计、软件编程到调试封装每一步都充满了动手的乐趣和解决问题的成就感。它不仅仅是一个计数的工具更是一个可以随时把玩、修改和升级的电子玩具。当你按下自己制作的按钮看到屏幕上的数字稳稳地增加“1”时那种满足感是购买任何成品都无法替代的。希望这份详细的指南能帮你绕过我踩过的那些坑顺利创造出属于你自己的那个独一无二的计数器。