基于Arduino与超声波传感器的智能投票计数系统设计与实现 1. 项目概述与设计思路几年前我在一个社区活动里帮忙需要统计两个提案的现场投票。当时用的是最原始的方法两个纸箱投票者往里面扔乒乓球最后倒出来人工数。效率低不说还容易出错。当时我就想能不能做个能自动计数的“智能投票箱”这个想法一直搁置着直到最近指导几个学生做项目他们提出了类似的需求于是我们一起动手用最普及的Arduino平台和常见的超声波传感器把这个想法变成了现实。这个“基于Arduino的投票计数箱”项目本质上是一个双通道物体接近检测与计数器。它的核心逻辑很简单两个投票口对应两个超声波传感器当有选票我们用了小纸片投入时会短暂遮挡传感器系统检测到这个距离变化就为对应的选项累加一票并实时显示在LCD屏幕上。整个项目硬件成本极低代码逻辑清晰非常适合作为嵌入式系统入门、传感器应用或互动装置设计的练手项目。无论你是电子爱好者、创客教育者还是相关专业的学生都能通过复现这个项目深入理解超声波测距原理、Arduino编程、以及如何将硬件与软件结合解决一个具体的实际问题。2. 核心硬件选型与电路设计解析2.1 传感器选型为什么是HC-SR04市面上距离传感器种类很多比如红外、激光TOF、微波雷达等。我们选择最常见的HC-SR04超声波传感器主要基于以下几点考量非接触与介质适应性红外传感器容易受环境光、物体颜色影响而超声波基于声波反射对光线不敏感且我们使用的纸质选票表面粗糙能很好地反射声波可靠性高。成本与易用性HC-SR04模块价格通常在十元以内性价比极高。它接口简单仅需VCC、GND、Trig、Echo四线工作原理直观非常符合教学和原型开发的需求。测距范围与精度其有效测距范围在2cm到400cm之间精度可达3mm。对于投票箱这个场景传感器到投票通道的距离通常在10-20cm完全在其高精度测量区间内绰绰有余。抗干扰能力虽然多个超声波传感器同时工作可能因声波串扰导致误触发但通过分时触发和软件防抖我们可以有效规避这个问题。相比之下若使用红外对管需要精确对齐发射和接收端在手工制作的纸箱中安装调试更麻烦。注意HC-SR04的工作电压是5V与Arduino UNO的IO口电平完美匹配。直接连接即可无需电平转换电路。2.2 主控与显示单元Arduino UNO 1602 LCD主控选择Arduino UNO几乎是必然的。它拥有14个数字IO口和6个模拟输入口足以连接两个传感器和一个LCD屏。其内置的USB转串口芯片方便供电和程序上传社区资源丰富遇到问题容易找到解决方案。显示部分选用经典的1602字符型LCD屏16列2行。它能够清晰地显示“Opt 1”和“Opt 2”的标签及实时票数信息直观。之所以没有选用更炫酷的OLED屏一是考虑到在明亮环境下LCD的可视性更好二是其驱动库LiquidCrystal是Arduino标准库稳定且简单无需额外安装。2.3 电路连接详解与供电考量整个系统的电路连接可以分为三个部分传感器电路、LCD接口电路和电源电路。原始资料中的接线图描述比较简略这里我补充一些关键细节和原理。超声波传感器接线 每个HC-SR04的VCC和GND分别接到面包板的电源正极和负极总线。Trig触发引脚和Echo回响引脚分别连接到Arduino的数字引脚。在代码中我们定义了传感器1:Trig - D9,Echo - D8传感器2:Trig - D10,Echo - D11这里有一个重要细节Echo引脚输出的是5V脉冲信号而Arduino UNO的数字引脚可以安全接收5V输入其IO口耐压5V所以可以直接连接。1602 LCD接线4位模式 为了节省IO口我们采用4位数据模式而非8位模式。这意味着我们只使用DB4-DB7这4根数据线。控制线RS(寄存器选择) - D13,E(使能) - D12。数据线D4 - D5,D5 - D4,D6 - D3,D7 - D2。这里注意代码中的引脚顺序(d4, d5, d6, d7)对应的是Arduino引脚(5, 4, 3, 2)接线时需要一一对应。对比度调节LCD的V0引脚通过一个10K电位器连接到GND和VCC中间抽头接V0用于调节屏幕对比度。背光A(阳极)和K(阴极)之间我们串联了一个330欧姆的限流电阻后接到5V和GND。这个电阻至关重要没有它过大的电流可能会直接烧毁LCD的背光LED。供电方案 整个系统耗电不大。两个HC-SR04工作电流约15mA每个LCD背光约20-60mA取决于电阻Arduino自身约50mA。所以总电流在150mA以内。在开发调试阶段通过USB线连接电脑供电完全足够。如果需独立运行可以用一个9V电池接入Arduino的DC插座或者用一个5V/1A的手机充电宝直接给Arduino的5V和GND引脚供电后者更经济持久。实操心得在面包板上搭建电路时务必先断开电源。连接LCD屏的排线时最好使用杜邦线并确保插入方向正确。我曾因为一排线插反导致Arduino板发热幸好及时发现没有损坏元件。建议用不同颜色的导线区分电源红、地黑和信号线黄、绿等这样排查故障时一目了然。3. 机械结构设计与制作要点硬件电路是项目的“神经”而机械结构则是“骨骼”它决定了系统的稳定性和可靠性。原项目使用纸箱突出了低成本、易加工的特点但在实际制作中有几个细节需要特别注意。3.1 投票通道的设计与传感器安装原方案是在纸箱顶盖内侧水平方向开两个槽作为投票口传感器安装在箱内下方。这个设计可行但容易因投票物下落姿态不规则导致漏检或重复计数。我的改进方案设计一个倾斜的导流通道不要简单地在顶部开槽。可以用硬纸板或3D打印件制作一个“漏斗”形的导流槽引导选票小纸片以相对固定的轨迹下落。这能确保选票一定会经过超声波传感器的检测区域。传感器安装位置与角度将超声波传感器水平对射安装而不是朝上安装。在通道两侧相对位置各安装一个传感器一个作为发射一个作为接收需使用两个传感器配合。但为了简化我们沿用原项目的单传感器反射模式。此时传感器应略微向上倾斜使其声波锥角中心对准选票最可能经过的路径。HC-SR04的声波扩散角约为15度适当的倾斜可以扩大有效检测区域。固定与减震不要直接用热熔胶把传感器粘在纸箱上。纸箱易变形轻微形变可能导致传感器角度偏移。建议先用一小块亚克力板或硬塑料板作为传感器支架用螺丝或扎带固定传感器再将支架稳固地粘在箱体内壁。这能有效隔离箱体震动对传感器的影响。3.2 内部布局与电磁兼容性考虑“把一切塞进盒子”听起来简单但混乱的布局会带来问题。电源线与信号线分离尽量让面包板上的电源走线5V,GND和数字信号线Trig,Echo, LCD数据线分开走线避免平行长距离走线以减少噪声耦合。为USB线开孔在箱子侧面或背面开一个大小合适的圆孔或方孔让USB线可以穿出。孔洞边缘可以用胶带或热缩管包裹防止磨损线材。LCD屏幕的固定屏幕不要仅仅“塞”在孔里。可以在屏幕四周粘贴硬纸板边框从箱子内部卡住确保其稳固不晃动。显示面要与外表面平齐或略微内凹以防被撞到。3.3 选票投票物的设计使用1英寸x1英寸的硬纸板方块是个好主意。但需要统一规格材质使用有一定厚度和硬度的卡纸避免使用过软或易弯曲的纸张确保下落过程姿态稳定。表面保持表面平整不要有褶皱或反光涂层。超声波对粗糙表面的反射效果更好。测试正式使用前用至少20张选票进行多次投入测试观察计数是否准确。这可以验证通道设计和传感器阈值是否合理。4. 核心代码逻辑深度剖析与优化原项目提供的代码实现了基本功能但作为教学示例其中有些逻辑可以优化稳定性也能提升。我们来逐段解析并改进。4.1 超声波测距原理与代码实现HC-SR04的工作原理是主控给Trig引脚一个至少10微秒的高电平脉冲模块自动发射8个40kHz的超声波脉冲并检测回波。Echo引脚会输出一个高电平脉冲其宽度与距离成正比。距离 (高电平时间 * 声速) / 2。原代码中的测距函数是正确的digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); duration pulseIn(echoPin, HIGH); distance duration * 0.034 / 2; // 声速按340m/s计算单位cm这里0.034是声速340m/s换算成微秒/厘米的系数0.034 cm/μs。因为声音是往返所以除以2。4.2 投票判定逻辑的缺陷与改进原代码的判定逻辑是如果当前距离小于一个基线值x例如10cm就等待20ms然后再测一次距离。如果第二次距离大于10cm就认为有物体通过计数加一。if (distance1 x) { delay(20); // ... 再次触发并测量距离存入 distance3 if (distance3 10){ count1; } }这个逻辑存在一个风险如果一张选票非常缓慢地落下或者在传感器前停留时间超过20ms它可能被连续检测到多次distance1 x从而导致重复计数。优化方案引入状态机更稳健的方法是使用状态机State Machine来管理每个投票通道的状态。我们可以定义三个状态IDLE空闲、DETECTED检测到物体、COUNTED已计数。enum VoteState { IDLE, DETECTED, COUNTED }; VoteState state1 IDLE; VoteState state2 IDLE; int count1 0, count2 0; int detectionThreshold 10; // 检测阈值 (cm) int clearThreshold 15; // 物体离开阈值 (cm) void loop() { int dist1 measureDistance(trigPin1, echoPin1); int dist2 measureDistance(trigPin2, echoPin2); // 处理通道1 switch(state1) { case IDLE: if (dist1 detectionThreshold) { state1 DETECTED; // 检测到物体进入区域 } break; case DETECTED: if (dist1 clearThreshold) { // 物体离开检测区域 count1; state1 COUNTED; updateDisplay(); // 更新显示 } break; case COUNTED: // 等待物体完全离开防止同一物体二次触发 if (dist1 clearThreshold) { state1 IDLE; // 重置状态准备下一次检测 } break; } // 处理通道2的逻辑类似... }这种方法的优势在于一个物体从“进入”到“离开”的完整过程只会触发一次计数有效避免了重复计数。detectionThreshold和clearThreshold可以设置一个差值形成“迟滞”进一步增加抗干扰能力。4.3 双传感器防干扰策略两个超声波传感器同时工作可能会互相干扰。原代码是顺序触发先传感器1再传感器2这在一定程度上避免了同时发射。但为了更可靠可以加入一个短暂的随机延时或者确保在一个传感器的回波接收完成后再触发下一个。void loop() { long duration1 getPulseDuration(trigPin1, echoPin1); delayMicroseconds(500); // 增加一个固定间隔确保声波消散 long duration2 getPulseDuration(trigPin2, echoPin2); // ... 后续处理 }4.4 显示优化与数据持久化原代码直接将数字拼接成字符串显示。我们可以优化显示格式使其更美观并增加清零功能。#include LiquidCrystal.h LiquidCrystal lcd(13, 12, 5, 4, 3, 2); void updateDisplay() { lcd.clear(); lcd.setCursor(0,0); lcd.print(A:); lcd.print(count1); lcd.setCursor(8,0); lcd.print(B:); lcd.print(count2); lcd.setCursor(0,1); lcd.print(Total:); lcd.print(count1count2); }此外Arduino UNO断电后RAM数据会丢失。如果想保存票数可以写入EEPROM。但要注意EEPROM有擦写寿命约10万次不能每次计数都写入。可以每增加10票或按一个“保存按钮”时写入一次或者仅在断电前通过检测电压跌落保存。#include EEPROM.h void saveCounts() { EEPROM.put(0, count1); EEPROM.put(sizeof(int), count2); } void loadCounts() { EEPROM.get(0, count1); EEPROM.get(sizeof(int), count2); }5. 系统集成、调试与故障排查实录当硬件组装完毕代码上传后真正的挑战才刚刚开始让系统稳定可靠地工作。下面是我在调试这个项目过程中遇到的一些典型问题及解决方法。5.1 上电无显示或显示乱码问题现象LCD屏幕不亮或亮但显示黑色方块、乱码。排查步骤检查电源和背光用万用表测量LCD的VCC和GND引脚是否有5V电压。测量背光引脚A和K之间电压确认330Ω电阻已正确串联。调节对比度这是最常见的问题。缓慢旋转连接在V0引脚上的电位器直到字符清晰显示。有时对比度电压需要调到接近GND才能显示。检查接线重点检查RS、E和4根数据线D4-D7是否与代码定义、实际插线完全一致。一根线接错就会导致通信失败。检查代码初始化确认lcd.begin(16,2)已执行且放在setup()函数中。5.2 传感器读数不稳定或始终为0问题现象串口监视器显示的传感器距离值跳动很大或固定为0、一个极大值。排查步骤检查物理连接确认Trig和Echo没有接反。VCC是否接5VGND是否共地。检查测量环境传感器前方是否有强吸音材料如泡沫、厚绒布检测路径上是否有障碍物确保传感器前方至少20cm内开阔且被测物体表面平整。代码诊断在loop()开头添加Serial.println(“Start reading...”);并打印出duration脉冲时间的值。如果duration始终为0可能是pulseIn()函数超时默认1秒。这通常意味着没有收到回波检查传感器是否损坏或尝试增加pulseIn()的超时参数例如pulseIn(echoPin, HIGH, 30000)30毫秒超时。电源噪声如果使用电池供电电压不足可能导致传感器工作不正常。尝试改用USB供电测试。5.3 误计数或漏计数问题现象没投票时数字自己增加或投票了却没反应。排查步骤调整阈值变量x和y即detectionThreshold是关键。用串口监视器实时观察传感器到对面箱壁的静态距离。假设这个距离是25cm那么阈值应设置为比25cm小一些的数值比如20cm。这样只有当物体进入距离小于20cm时才可能触发。同时clearThreshold应略大于静态距离比如28cm。检查状态机逻辑如果采用了状态机打印出状态变化观察是否严格按照IDLE - DETECTED - COUNTED - IDLE流转。漏计数可能是DETECTED状态判断条件太苛刻误计数可能是IDLE状态判断条件太宽松。环境干扰是否有其他运动的物体如人走过进入传感器探测范围确保投票箱放置在相对静止的环境中。超声波可能被风扇、空调出风口的快速空气流动干扰。机械结构测试用选票反复测试观察其下落轨迹是否每次都经过传感器波束中心。调整传感器角度或导流槽形状。5.4 系统整体不稳定偶发性复位问题现象系统偶尔会重启LCD屏幕闪烁一下。排查步骤电源问题这是首要怀疑对象。用万用表监测5V电压在传感器触发瞬间观察电压是否有大幅跌落低于4.8V。HC-SR04在发射瞬间电流较大可能造成电源扰动。解决方法是在每个传感器的VCC和GND之间以及Arduino的5V和GND之间并联一个100μF的电解电容和一个0.1μF的瓷片电容用于滤波和储能。代码死循环或内存泄漏检查代码中是否有未合理处理的异常条件导致死循环。确保loop()函数每次执行时间不会过长。避免使用String类原代码中用了因为它容易导致内存碎片。可以使用字符数组char[]来替代字符串操作。6. 项目扩展与进阶玩法基础功能实现后这个项目还有很多可以挖掘和扩展的地方能让它从一个课堂作业升级为一个更实用的工具或更有趣的互动装置。6.1 增加无线数据传输与远程监控通过添加一个ESP8266或ESP32模块可以让投票箱接入Wi-Fi网络。票数可以实时上传到云端服务器如ThingsBoard、Blynk或自建的MQTT服务器这样管理员在手机或网页上就能远程查看实时票数统计甚至生成简单的图表。// 示例使用ESP8266连接Wi-Fi并发送数据 #include ESP8266WiFi.h WiFiClient client; void setup() { // ... 连接Wi-Fi } void loop() { // ... 计数逻辑 if (票数更新) { String url “/update?api_keyYOUR_KEYfield1” String(count1) “field2” String(count2); client.connect(“api.thingspeak.com”, 80); client.print(“GET ” url “ HTTP/1.1\r\n”); // ... 发送请求 } }6.2 添加声音与灯光反馈为了提升用户体验可以增加反馈机制蜂鸣器每次有效投票时发出一个短促的“嘀”声给予投票者确认。LED指示灯在每个投票口旁边安装一个不同颜色的LED。当该通道检测到投票时对应的LED快速闪烁一下。光带进度条使用WS2812B LED灯带根据两个选项的票数比例显示不同颜色的光带视觉上更直观。6.3 引入身份验证与防作弊机制基础的投票箱无法防止一人多投。可以结合其他模块增加简单验证RFID/NFC读卡器每个投票者分配一张唯一的RFID卡。投票前需要刷卡验证系统记录卡号确保“一人一票”。指纹识别模块实现更高级别的生物特征验证。物理防重复机制设计一个机械翻板投票后选票落入一个带锁的封闭票仓同时翻板复位防止从票仓内取出选票重复投入。6.4 优化供电与便携性设计为了脱离电脑和电源线使用使用电池供电采用一块大容量的18650锂电池3.7V配合升压模块升压至5V为整个系统供电。计算系统总电流和工作时间选择合适的电池容量如3000mAh。低功耗优化在代码中可以让Arduino在两次检测之间进入空闲Idle或睡眠Sleep模式仅靠定时器中断唤醒进行测量大幅降低待机功耗延长电池寿命。设计专用外壳使用激光切割亚克力板或3D打印制作一个结构坚固、外观专业的壳体替代纸箱。将传感器、屏幕、按钮等元件精准地嵌入其中。这个项目麻雀虽小五脏俱全。它串联了电子电路、嵌入式编程、传感器应用、机械结构甚至简单的工业设计。从最初的想法到遇到一个个具体的问题传感器误触发、显示乱码、电源干扰再到一步步分析、排查、解决和优化这个过程本身的价值远大于最终做出的那个“投票箱”。它教会你的是如何将一个模糊的需求落地为一个稳定可靠的物理系统。这才是嵌入式开发最核心的乐趣所在。