
简介智能羽毛球捡球机是一份基于AVR单片机的完整项目资料包面向嵌入式爱好者、电子竞赛参赛者及机器人开发者重点解决羽毛球自动识别、拾取与收集中的传感融合与机械控制问题。项目融合摄像头图像处理、超声波测距、红外避障配合吸风装置、倾斜传送带与转盘实现多球有序收集AVR单片机负责四路电机运动控制并通过PWM调节风机、传送带和转盘速度整体方案涉及传感器交互、蓝牙命令执行与外壳结构设计。包内共十九个文件整体约七百六十九KB以C语言源码和头文件为主辅以Hex烧录文件、工程配置、调试信息、仿真效果图及引脚说明等结构清晰便于直接查看程序、烧录验证和对照实物照片理解设计。当前已有一百一十人学习适合需要参考完整控制逻辑、电路连接与结构设计的初学者和竞赛团队使用。1. AVR 捡球机难点不在摄像头在让羽毛球从地面“站起来”进筒拆过这个项目的都知道真正耗时间的不是图像识别逻辑而是机械结构那一堆外壳和风道。羽毛球重心偏前、羽毛柔软传送带稍微不平整就会卡住连摄像头都还没开始工作球已经滚回去了。这个智能羽毛球捡球机把视觉感知交给摄像头、超声波和红外三路传感器把收集动作拆成吸风单向导入、倾斜传送带整姿、圆筒收纳三步整机由 AVR 单片机协调四路电机。其中风机吸力、传送带速度、左右驱动轮差速都用 PWM 调节蓝牙模块负责远程命令交互源码包里还带了 Proteus 仿真工程和实物效果图。适合做 AVR 课程设计、机电竞赛底盘验证或者单纯想研究多传感器融合和小功率直流电机控制的人。下面按传感器分工、源码编译链路、仿真实测的顺序展开最后说怎么避开仿真发散这类低级坑。2. 传感器分工摄像头、超声波和红外谁说了算2.1 三路感知数据怎么切先建一张分工表明确每个传感器该在什么时间尺度上工作否则主循环会乱成一团。摄像头输出的是 80×60 左右的重心坐标更新率大概 5 帧每秒它只回答“羽毛球在我视野的哪个方向”超声波回答“前方障碍距离我多少厘米”典型测距范围 3cm200cm轮询周期可以做到 50ms红外传感器走 ADC 采样回答“羽毛球是否已经到达收集通道入口”这是最后一步的到位判定。三路数据的优先级是红外到位 超声波防撞 摄像头找球因为距离越近数据越应该被信任。数据源输出粒度采样周期负责决策不适合做什么摄像头二值化重心像素坐标对200ms目标方向、转盘对准测距、识别球的新旧超声波 echo 脉宽微秒数50ms接近减速、避障区分球和障碍物红外反射电压05V ADC5ms球是否进收集口判断场地材质2.2 超声波触发与回波捕获AVR 的输入捕获是关键超声波模块常见做法是 TRIG 引脚给 10us 高电平触发ECHO 返回一段和距离成正比的高电平时间。很多新手直接while(ECHO)死等回波这在 Proteus 仿真里能跑换到实物上遇到无回波就会卡死整个主循环。更稳的做法是接 ATmega16 的 ICP1 引脚PD6用定时器 1 输入捕获中断测回波宽度。#define TRIG_PORT PORTB #define TRIG_PIN PB0 static volatile uint16_t echo_ticks; static volatile uint16_t echo_start; void sonic_trig(void) { DDRB | (1 TRIG_PIN); // PB0 推挽输出 PORTB | (1 TRIG_PIN); _delay_us(10); // 必须大于 10us短了模块不触发 PORTB ~(1 TRIG_PIN); } void setup_capture(void) { TCCR1A 0; TCCR1B (1 ICNC1) | (1 CS11); // 8 分频ICNC 滤波消抖 TIMSK | (1 TICIE1); } ISR(TIMER1_CAPT_vect) { if (TCCR1B (1 ICES1)) { // 本次是上升沿 TCCR1B ~(1 ICES1); // 下一跳变改为下降沿 echo_start ICR1; // 记录起始计数值 } else { TCCR1B | (1 ICES1); // 恢复上升沿捕获 echo_ticks ICR1 - echo_start; // 回波高电平宽度 } }回波脉宽换算距离要留意分频系数。ATmega16 工作在 8MHz、定时器 1 八分频后一个 tick 正好是 1us声波往返传播速度约 58us/cm所以距离 cm echo_ticks / 58。我一般会在中断里只更新变量不做除法把换算放到主循环外部需要时才计算因为uint16_t / 58在 8 位单片机上是几十个周期的运算中断里做不划算。2.3 摄像头在 AVR 上只能做“粗定位”ATmega16 只有 2KB SRAM装不下一帧 QVGA 图像。正常方案是摄像头模块输出灰度或二值化信号单片机只抽扫描线和少量行缓冲把分辨率降到 80×60再对每行做阈值比较累加得到羽毛球白色像素的重心坐标。计算量最大的部分是除法而且图像数据进来时 IO 口会被占满所以主循环里要避开摄像头像素窗口去读超声波。源码里1324.c对这部分处理是拆成两轮扫描第一轮算重心 x第二轮算重心 y两轮都只用一个uint8_t累加器RAM 占用很省。红外传感器的用法也值得说一句。TCRT5000 这类反射式红外对管输出模拟电压球靠近时反射增强、电压升高把它接到 PA0 用 ADC 采样阈值判据放在主循环里。注意不要让这个 ADC 采样和摄像头像素读取出现在同一个 10ms 窗口内否则总线竞争会让摄像头丢帧这是多传感器项目最常见的隐性 bug。3. 源码链路从 234325.prj 到 .hex 再到 PWM 电机代码3.1 工程里那一串文件分别是什么下载包里能看到234325.prj、234325.mak、234325.lst、234325.cof、234325.dbg、234325.SRC和234325.hex第一次打开工程的人很容易搞混。逐个说明文件类型作用你要不要管它1324.cC 源码主程序所有逻辑都在这里核心文件delay.h / usart.h / delay.c / usart.c头文件与实现延时函数和串口收发编译时自动引用234325.prjAVR Studio 4 工程文件保存芯片选型、编译选项双击打开工程用234325.makmakefile交给编译器执行构建一般不用改234325.hex烧录文件下载到 AVR 芯片的最终产物烧录用234325.lst汇编列表C 到汇编的映射排错时看调试辅助234325.cof / .dbg / .mp调试中间文件断点、变量监视不需要234325.SRC预处理后的 C宏展开后的中间源码很多时候被误认成源码有个小技巧.SRC这种大写后缀文件是编译器预处理中间产物宏替换后的内容真正改代码要改1324.c。如果在.SRC里改再重新编译会被覆盖白改。3.2 主循环状态机与蓝牙命令解析源码主体是个状态机空闲扫描、朝向羽毛球接近、风机吸取、传送带倒球四个状态。蓝牙通过usart.c的串口中断接收命令收到F提升风机 PWM 档位收到S停止全部电机收到T回传当前超声波距离。下面把串口初始化和命令分发写出来void uart_init(void) { UBRRH 0; UBRRL 51; // 8MHz 下 9600 波特率 UCSRA 0; UCSRB (1 RXEN) | (1 TXEN) | (1 RXCIE); UCSRC (1 URSEL) | (1 UCSZ1) | (1 UCSZ0); // 8N1 } ISR(USART_RXC_vect) { uint8_t cmd UDR; switch (cmd) { case F: if (fan_speed 250) fan_speed 20; // 风机 PWM 升档 break; case S: fan_speed 0; conveyor_speed 0; break; case T: uart_send_uint16(echo_ticks / 58); // 回传厘米距离 break; default: break; } }波特率寄存器的计算是这类代码最常见的翻车点。8MHz 主频下 9600 波特率的 UBRR 值是 51如果项目实际装在 16MHz 外部晶振上这个值要改成 103否则蓝牙收到的全是乱码。仿真工程里时钟用的是内部 8MHz烧实物时如果换了晶振F_CPU宏和 UBRR 要一起改。3.3 四路 PWM 的初始化与频率换算整机有风机、传送带、左右驱动轮四路需要调速度ATmega16 片上正好有四个硬件 PWM 输出OC0PB3、OC1APD5、OC1BPD4、OC2PD7。左右驱动轮共用定时器 1 的 OC1A/OC1B 是一个关键设计这样两路 PWM 来自同一个计数器差速转弯时相位严格同步直行不会因为两个定时器各自溢出产生累计偏差。void pwm_init(void) { // 定时器1左右驱动轮16位快速PWMICR17999 1kHz TCCR1A (1 WGM11) | (1 COM1A1) | (1 COM1B1); TCCR1B (1 WGM13) | (1 WGM12) | (1 CS10); ICR1 7999; // 顶端值决定频率 OCR1A 4000; // 左轮 50% 占空比 OCR1B 4000; // 右轮 50% 占空比 // 定时器0风机8位快速PWM20kHz以上避开可听噪声 TCCR0 (1 WGM00) | (1 COM01) | (1 CS00); OCR0 180; // 风机 70% 输出 // 定时器2传送带8位快速PWM TCCR2 (1 WGM20) | (1 COM21) | (1 CS20); OCR2 120; // 传送带中速 }PWM 频率由顶端值和分频系数共同决定8MHz 下常见组合如下表。风机用 31.25kHz 而不是低频 122Hz是因为低频 PWM 驱动直流电机会让电机线圈发出刺耳啸叫人耳正好落在 2kHz20kHz 敏感区31.25kHz 超出人耳上限整机工作噪声会好很多。PWM 输出定时器预分频顶端值频率用途OC00125531.25kHz风机OC22125531.25kHz传送带OC1A/OC1B1179991kHz左右驱动轮4. Proteus 仿真与引脚实测让 .hex 在虚拟环境先跑起来4.1 仿真工程挂载 hex 的步骤Proteus 仿真是这个项目调试的主战场。打开工程后按下面步骤把固件挂到 CPU 上双击原理图中的 ATmega16 芯片在 Program File 一栏选择234325.hexClock Frequency 保持 8MHzCKSEL 熔丝要和程序里的F_CPU一致。芯片引脚上可以挂 MOTOR-DC 电机模型、虚拟终端VIRTUAL TERMINAL和逻辑探头用来观察 PWM 波形。虚拟终端接 PD0/PD1 串口从电脑端发字符F和T能直接验证蓝牙命令解析逻辑。引脚分配按下面这张表对号入座仿真和实物共用一个版本外设引脚说明风机 PWMPB3 / OC0定时器 0 输出传送带 PWMPD7 / OC2定时器 2 输出左驱动轮 PWMPD5 / OC1A定时器 1 通道 A右驱动轮 PWMPD4 / OC1B定时器 1 通道 B超声波 TRIGPB010us 高电平触发超声波 ECHOPD6 / ICP1输入捕获测脉宽摄像头同步/阈值PD2 / INT0帧中断触发红外传感器PA0 / ADC0ADC 判定球到位蓝牙 RX / TXPD1 / PD0USART 9600 8N14.2 超声波仿真和实物的时间换算差异Proteus 里的超声波模型通常用 SRF04 元件TRIG 触发后 ECHO 返回脉宽它内部按脉宽 us 距离 cm * 58仿真。这个过程和实物模块一致但有一个坑仿真模型的回波是理想的方波实物回波会有毛刺和多次反射所以实物固件里一定要给 ECHO 脉宽加超时保护当echo_ticks超过 30000对应约 5 米时直接抛弃数据防止把杂波当成近距离障碍。uint16_t read_distance_cm(void) { sonic_trig(); _delay_ms(50); // 等待回波中断更新 echo_ticks if (echo_ticks 0 echo_ticks 30000) { return echo_ticks / 58; // 8分频下 1 tick 1us } return 0xFFFF; // 无效距离调用方要判断 }return 0xFFFF的做法比返回 0 更安全因为 0cm 会骗过后面的避障判断而 0xFFFF 至少能进防撞分支做兜底。主循环里拿到这个值后先判断是否等于 0xFFFF再做刹车或减速动作。4.3 avrdude 烧录与熔丝位注意实物烧录用 avrdude 加 USBasp 下载器命令行格式如下。下载前先用读熔丝命令备份原始值防止误熔丝锁死芯片avrdude -p m16 -c usbasp -P usb -U flash:w:234325.hex:i avrdude -p m16 -c usbasp -P usb -U lfuse:r:lfuse_backup.bin:r熔丝位只保守设置时钟源和源码保持一致。仿真工程如果是内部 8MHz就不要去开外部晶振选项如果实物装的是 16MHz 晶振那不仅要改熔丝位还要回去改UBRRL和 PWM 分频。很多人在实物上发现风机噪音尖锐、蓝牙乱码都是时钟源选错导致的全链路错乱。5. 避开仿真发散PWM 频率、超声波回显与查表调速Proteus 跑捡球机项目时最容易出现的仿真发散现象是逻辑探头上 PWM 波形完全正常但电机模型转速失控或者超声波 ECHO 上的值始终是 0xFFFF。排查顺序按照“时钟 → 分频 → 引脚方向”来。第一步看芯片的 clock frequency 是否 8MHz第二部看定时器寄存器里CS10/CS11/CS12的组合第三步查对应引脚的 DDR 是否设成了输入方向。仿真里 ECHO 接 IPD6 但没把 PD6 配成输入会导致捕获中断永远不触发。这里给一个更稳的调速方式查表梯形加减速。直接给 PWM 输出寄存器赋一个固定值电机会从 0 瞬间跳到满占空比启动电流冲击会干扰 ADC 采样的红外电压甚至让摄像头画面抖动。常见做法是维护一个 32 级加速度表static uint8_t ramp[32]; void build_ramp(void) { uint8_t i; for (i 0; i 32; i) { ramp[i] (uint8_t)((i * 255U) / 31); // 线性升速表 } } void set_motor_speed(uint8_t target) { // 每次调用只逼近一步让电机平滑加速 if (current_speed target) current_speed; else if (current_speed target) current_speed--; OCR1A ramp[current_speed / 8]; // 32级映射到 0~255 OCR1B ramp[current_speed / 8]; }每秒调用set_motor_speed()大约三十次四个执行器的转速变化就不会互相干扰。仿真发散时的经验是先用虚拟示波器确认每个 PWM 通道的占空比变化和current_speed一致再去调机械结构。超声波回显异常时把read_distance_cm()的返回值通过串口发到虚拟终端看数值是稳定递增还是跳变这比盲调阈值高效得多。如果感觉 Proteus 的时序和实物偏差太大可以把1324.c里纯逻辑部分抽出来放到 Wokwi 这类浏览器仿真平台上跑修改 PWM 引脚定义和寄存器映射后观察云端时序面板里的中断触发顺序。Wokwi 的优点是看得见中断执行节奏对排查多传感器采样窗口打架这类问题比 Proteus 直观。// 超声波超时保护写进主循环不在中断里处理 if (read_distance_cm() 0xFFFF) { OCR1A 0; OCR1B 0; // 前方数据异常先停轮子 PORTC | (1 PC0); // 点亮报警灯方便仿真观察 } else { PORTC ~(1 PC0); }这段超时处理放在主循环里还有个好处中断里不阻塞、不运算read_distance_cm()阻塞等待只在主循环执行摄像头像素采集窗口和蓝牙收包都不会被超声波中断卡住。整套项目的可复现阶段到这里就闭环了从仿真验证 PWM 波形到实物调通串口命令剩下就是外壳和风道的慢功夫了。本文还有配套的精品资源点击获取