1. 整体设计思路与方案选型先说结论一台靠谱的乒乓球发球机核心不在于电机多贵、架子多 fancy而在于“双轮对旋摩擦驱动”这个物理方案以及一套能稳定控制轮速和出球节奏的逻辑。Arduino 在这里扮演的角色就是整个系统的大脑——它不负责发力但负责让发力变得可控。我在动手之前其实纠结过一段时间的方案选型。市面上现成的发球机从几百块到上万的都有拆开看原理大同小异两个摩擦轮高速对转乒乓球从中间被挤压甩出方向由轮子的角度和你持机的姿态决定。真正拉开差距的是出球频率是否均匀、转速是否稳定、调整是否方便。自己 DIY 的出发点不是为了省钱而是为了能完全掌控这套逻辑想调出球弧线就调轮速想改频率就改代码自由度是成品机比不了的。选型上最终定了 Arduino Uno R3 作为主控。为什么不选 ESP32原因很实在这个项目里没有复杂的网络通信需求也不需要跑音频、视觉之类的重负载任务Uno 的 16MHz 主频和 32KB Flash 完全够用而且它的 PWM 输出、中断引脚都很好用资料多到搜不完。ESP32 的优势在 Wi-Fi 和蓝牙如果之后想加手机 App 遥控再平滑过渡到 ESP32 也不迟。另外Uno 的 5V 逻辑电平对舵机和电机驱动模块非常友好不用做电平转换少一个坑。机械部分的核心结构是这样的储球漏斗负责存球漏斗底部开口通向一条倾斜的落球通道通道末端是一对相向旋转的摩擦轮。球落到轮间缝隙时被两个轮的摩擦力带飞出去。这个方案最大的好处是结构简单、出球速度快而且通过调整两个轮的转速差可以实现侧旋、上旋等不同旋转效果后面控制逻辑部分会细讲。还有一个重要决策供电方案。舵机、电机驱动模块、Arduino 三者对电压和电流的需求不一样绝对不能共用一个供电口。我用了两路电源一路 5V/3A 给 Arduino 和舵机一路 12V/5A 给电机驱动模块。分开供电一个是出于稳定性的考虑另一个是保护主控板电机启动的瞬间电流冲击很大如果和主控共地不做好很容易造成单片机复位重启表现就是发球发着发着突然“死机”一下。2. 核心细节解析与实操要点2.1 摩擦轮驱动的物理原理与关键参数先讲透摩擦轮驱动这个核心机制。两个轮子面对面安装间距略小于乒乓球直径标准乒乓球直径 40mm所以两轮间距大概要做到 36-38mm让球能陷进去一点但又不会被卡死。轮子以相反方向旋转球进入两轮之间后轮子外缘对球表面产生摩擦力摩擦力方向与轮子旋转方向相同于是球被加速并从另一侧甩出。出球速度取决于两个因素轮子的线速度和球与轮子的接触面积。轮子线速度不是简简单单等于电机转速乘以轮子周长因为球在两轮之间会存在滑动实际出球速度低于理论值这个损耗系数通常在 0.7-0.85 之间取决于轮子的材质和表面纹路。我之前用实心橡胶轮和最普通的 PVC 轮做过对比橡胶轮表面抓球更稳出球速度也更接近理论值但橡胶轮更贵而且用久了表面会老化发滑需要定期清洁或更换。轮径和电机转速的搭配也有讲究。我用的轮子是直径 50mm 的橡胶轮电机是 12V 的直流减速电机空载转速约 3000 RPM。不用减速箱的话轮子线速度就是 3.14 × 0.05 × 3000 / 60约等于 7.85 m/s这个速度打出去还不够快实际配上 3:1 减速后轮子转速约 1000 RPM线速度约 2.6 m/s加上滑动损耗实际出球速度大概在 5-8 m/s 之间刚好适合业余爱好者对练使用。如果需要更快的球速就把减速比调小或者换更高转速的电机。2.2 关键部件选型清单与注意事项舵机的选择是我踩过坑最多的地方。一开始我图便宜买了 SG90 这种 9g 微型舵机结果发现扭矩完全不够——落球通道的挡板机构和发射倾角的调整机构都需要一定的扭矩SG90 在负载稍大的时候会抖舵、发热甚至直接烧掉。后来换了 MG996R 金属齿轮舵机扭矩 9.4 kg·cm用起来就稳多了。MG996R 的工作电压是 4.8V-6.6V用 Arduino 的 5V 输出供电没问题但要注意的是它的瞬间启动电流可以达到 2A 以上如果直接从 Arduino 的 5V 引脚取电很容易把板子搞挂。正确的做法是舵机单独从电源模块取电只把信号线接到 Arduino 的数字引脚上同时共地。这一点新手特别容易忽略共地这个概念可以这样理解Arduino 说“我要你转到 90 度”这句指令是通过信号线上的电压变化来告诉舵机的但如果两个设备的地电平面不一样信号线的电压变化就“传不到”舵机那里于是舵机收不到指令表现就是舵机不动或乱动。电机驱动模块选择的 L298N虽然效率不高、发热不小但赢在皮实耐用、资料多适合入门。后级也可以换成 TB6612FNG 这种效率更高的模块但 L298N 的逻辑电压是 5V输入高电平阈值 2.3V所以 Arduino 的 5V 引脚直接怼上去就能驱动不需要额外的电平转换芯片省事。传感器部分落球通道里我加了一个对射式红外传感器用来检测是否有球落到发射口。这个传感器非常便宜几块钱一个但作用很大——它让系统知道自己“手里有没有球”没球的时候可以自动停轮避免空转磨损也可以据此统计发球数量。安装时要注意对射管的对准精度两边的发射管和接收管必须严格处在同一条直线上否则光路偏移会导致误判。2.3 机械结构设计细节结构设计上我的建议是先建模再动手哪怕不用专业的 CAD 软件用纸笔画出尺寸图也比凭感觉打孔强。发球机的整体结构可以分为三个部分底盘与支撑架、储球漏斗与落球通道、发射机构与角度调节。底盘我用了 2020 铝型材搭建加几块 3D 打印的 PLA 连接件。之所以选铝型材是因为它强度够、重量轻、后期改造方便——想加一个高度调节杆拧几颗螺丝就能装上。储球漏斗我最初用纸板糊了一个结果球的流动性很差堆在一起的球经常互相卡住后来老老实实用 3D 打印做了个漏斗内壁设计成 45 度以上的倾角球的流动性立刻好了很多。漏斗底部开口要控制在刚好让一个球通过的尺寸如果开口太大多个球会同时挤进落球通道造成卡球。单球通道的内径比乒乓球直径大 2-3mm 左右就行40.5-42mm这样球能顺畅下滑但不会并列。发射倾角的调节我做了两套方案。一套是手动调节——通过一个手拧螺丝锁紧机构松开就能调整发射头在竖直方向的倾角范围 15-50 度适合定点练习另一套是舵机自动调节——在发射头底部装一个 MG996R用代码控制倾角适合发球落点变化的训练。手动调节结构简单可靠性高舵机调节玩法多但会增加系统复杂度而且倾角改变会影响出球轨迹的稳定性需要更仔细的调参。3. 实操过程与核心环节实现3.1 控制系统电路接线这是完整的接线清单照着接就不会乱Arduino Uno R3负责主控逻辑。L298N 电机驱动模块接 Arduino 的 5、6 引脚PWM 控制两个摩擦轮转速12V 电源输入两个电机输出分别接左右摩擦轮电机。MG996R 舵机信号线接 Arduino 的 9 引脚电源单独从 5V/3A 电源模块取。对射式红外传感器信号线接 Arduino 的 2 引脚这里用到了外部中断功能球到达发射口时能第一时间触发逻辑。1602 LCD 显示屏I2C 接口接 Arduino 的 A4 (SDA) 和 A5 (SCL) 引脚用来实时显示轮速、发球计数、工作模式等参数。三个按键分别接 7、8、10 引脚功能是“模式切换”“速度加”“速度减”按键另一端接 GND。电源12V/5A 适配器给 L298N另外用一个 DC-DC 降压模块把 12V 降到 5V/3A给 Arduino、舵机和传感器供电。接线的一个关键操作是共地L298N 的 GND 和 DC-DC 降压模块的 GND 必须连在一起再一起接到 Arduino 的 GND 引脚。这样所有设备才有统一的电压参考点信号传输才可靠。如果不共地舵机和电机经常会出现随机抖动的怪异现象。3.2 Arduino 控制逻辑与代码实现控制逻辑是这台发球机的灵魂。我用三段式代码结构来组织分别是初始化、主循环、调参函数。核心逻辑包括轮速控制PWM、出球节奏控制定时器、倾角控制舵机、状态显示与按键响应。出球频率的控制逻辑是红外传感器检测到球落到位后系统启动一个可调的时间窗口比如每 500ms 发一个球窗口内控制两个摩擦轮各转一个预设的脉冲数把球“踢”出去。这个方案的优点是逻辑简单、实时性好缺点是球的初速度完全取决于轮速的稳定性所以 PWM 控制要做得平滑不能直接让轮子 0-100% 猛加速要加一个“软启动”渐变过程。下面是我写的一个简化的示例代码可以直接烧录测试这是核心逻辑完整版还需要加入按键消抖和 LCD 驱动代码结构我做了精简方便解释#include Servo.h // 引脚定义 const int PIN_MOTOR_LEFT 5; // 左摩擦轮电机 PWM const int PIN_MOTOR_RIGHT 6; // 右摩擦轮电机 PWM const int PIN_SERVO 9; // 舵机信号 const int PIN_IR_SENSOR 2; // 落球红外传感器中断引脚 Servo tiltServo; volatile bool ballDetected false; // 工作参数 int leftSpeed 180; // 左轮 PWM 值 0-255 int rightSpeed 180; // 右轮 PWM 值 0-255 int tiltAngle 30; // 发射倾角 15-50 int intervalMs 500; // 出球间隔 void setup() { pinMode(PIN_MOTOR_LEFT, OUTPUT); pinMode(PIN_MOTOR_RIGHT, OUTPUT); pinMode(PIN_IR_SENSOR, INPUT_PULLUP); tiltServo.attach(PIN_SERVO); tiltServo.write(tiltAngle); // 外部中断红外传感器下降沿触发 attachInterrupt(digitalPinToInterrupt(PIN_IR_SENSOR), onBallDetected, FALLING); Serial.begin(9600); softStart(100); // 电机软启动到 100 PWM } void loop() { if (ballDetected) { ballDetected false; launchBall(); delay(intervalMs); // 控制出球间隔 } // 这里可以加按键检测、串口指令处理等 // 例如通过串口发送 L200 R220 T35 来调整参数 handleSerialCommand(); } void onBallDetected() { ballDetected true; } void launchBall() { // 启动摩擦轮保持 currentSpeed 到目标转速 softStart(leftSpeed); softStart(rightSpeed); // 短暂延时等待球被甩出 delay(120); // 摩擦轮持续转动等待下一个球 } void softStart(int targetSpeed) { // 渐变加速防止瞬间冲击 for (int i 0; i targetSpeed; i 5) { analogWrite(PIN_MOTOR_LEFT, i); analogWrite(PIN_MOTOR_RIGHT, i); delay(5); } } void handleSerialCommand() { if (Serial.available() 0) { String cmd Serial.readStringUntil(\n); cmd.trim(); if (cmd.startsWith(L)) { leftSpeed cmd.substring(1).toInt(); } // 类似地解析 R 和 T 指令 } }这段代码体现了几个控制要点一是用外部中断响应落球信号避免主循环轮询的延迟二是软启动机制让电机转速渐变避免瞬间大电流冲击和球被甩飞三是提供串口调试口方便在电脑上实时调整转速和倾角参数。在实际测试中基本指哪打哪调整参数后立刻就能看到出球轨迹的变化。3.3 调试流程与参数整定组装完成后不要急着上满速调试按这个流程来第一步是“空转测试”不装球手动给电机通电观察两个摩擦轮是否相向旋转、转速是否均匀、有没有异响。注意检查轮子的夹紧程度——如果两轮间距太小电机会因为负载过大而发热甚至堵转间距太大球又会从中间漏下去根本没有摩擦力能把球甩出去。理想的间距是用手指轻轻捏住两个轮子能转动但能感受到明显的阻力。第二步是“送球测试”放一颗球进漏斗观察球能否顺利滑落到发射口红外传感器能否正确检测到球。我在这步遇到过球落下去但传感器没反应的情况排查发现是传感器安装位置不正发射管和接收管的光路被球架遮挡了一部分微调位置后就好了。第三步是“定点发射测试”固定倾角调整轮速发射 20 个球观察落点的分布。一个好的系统落点应该集中在一个半径 30cm 左右的圆圈内如果落点散得满桌子都是多半是轮子打滑或者两轮转速不一致。两轮转速不一致的表现很有趣球会往慢轮一侧旋转偏转转速差越大球的侧旋越明显。实际操作中我经常故意用这个特性来制造侧旋球训练接侧旋的应对能力。第四步是“调节参数标定”给系统加上按键或串口指令快速调整左右轮速差和倾角记录每档参数对应的出球轨迹特征形成一张自己的“参数-轨迹映射表”。这张表非常有用比如我可以存几组预设低速近台球练单项技术、高速远台球练步法、侧旋球练接发球判断一按键就能切换训练模式。4. 常见问题与排查技巧实录项目做到后面真正值钱的经验都在排查故障的过程里。整理了一份我踩过的坑直接照着快速对齐就行故障现象可能原因排查与解决方案舵机抖动或不转供电不足、信号线接触不良确认舵机单独供电、电流≥2A用万用表量信号线是否有 3.3V/5V 电平跳动电机不转或无力驱动模块使能端没拉高、电源功率不够、PWM 引脚接错检查 L298N 的 ENA/ENB 跳线帽是否插上确认 12V 电源实际输出电流核对代码里的引脚号球进入两轮间被卡住轮间距过小、轮子表面太光滑、球不是标准 40mm调整轮间距至 37mm 左右更换摩擦系数更大的橡胶轮换新球出球乱出、时快时慢轮速不稳定、球在漏斗中堆积受力不均检查供电波动给 Arduino 加稳压电容优化漏斗倾角检查两轮磨损程度是否一致红外传感器误触发环境光干扰、传感器安装松动、接线过长给传感器加遮光罩用热熔胶固定安装位置信号线用双绞线减少电磁干扰发球间隔不稳定delay() 阻塞主循环、定时器配置不当不要用 delay() 控制出球节奏改用 millis() 非阻塞定时或者用 Timer1 产生定时中断LCD 显示乱码或不亮I2C 地址不对、数据线接反先运行 I2C 扫描程序确认地址常见 0x27 或 0x3F检查 SDA/SCL 是否接反一个比较隐蔽的坑是 L298N 模块的 5V 输出问题。很多教程会让你直接用 L298N 板上的 5V 输出去给 Arduino 供电我一开始也这么干结果发现系统偶尔会重启。原因在于 L298N 的 5V 是逻辑供电电流能力有限而 Arduino 带动 LCD、传感器之后电流需求增大了两者互相抢电导致电压跌落触发 Arduino 的欠压复位。后面我改成独立的 DC-DC 降压模块供电问题立刻消失了。这个经验可以用一句话总结电源设计不是“能响就行”要算电流余量各功能模块的供电尽量隔离。还有一个经验是关于舵机安装位置的选择。发射头的倾角调整舵机如果安装在靠近摩擦轮的位置工作时的震动会很大长期下来舵机的齿轮会有明显磨损。我把舵机改到远离发射头、靠近底盘的转轴处用一根连接杆传递角度虽然结构复杂了一点但震动对舵机的伤害小了很多寿命明显延长。DIY 项目里结构上的每个选择都要考虑到“这个零件在什么环境下工作、会不会因为环境而提前损坏”这个维度。5. 功能扩展与进阶升级思路基础版本跑通之后这个项目的扩展空间非常可观而且难度是循序渐进的。首先是“无线控制”方向。把主控从 Arduino Uno 换成 ESP32就能通过手机浏览器直接调整所有参数——轮速、倾角、发球频率、预设模式。ESP32 自带 Wi-Fi 和蓝牙可以直接用 Arduino IDE 编程代码迁移成本极低。加上现在热门的 BLE 遥控方式手机靠近发球机就能连上实际体验和成品机差不多了。如果不想换主控也可以给 Uno 加一个 HC-06 蓝牙串口模块用手机 App 发送串口指令来控制接线和代码改造成本都很小是最快能玩出花的扩展方案。其次是“自动落点训练”方向。现在的版本靠倾角调节改变落点但只能手动切换。我们可以预设一套“训练模式”发球机自动按“左-中-右-中-左”的节奏改变倾角同时搭配轮速变化出不同旋转模拟实战中的多球训练场景。这个逻辑不复杂本质就是把倾角的舵机控制和定时器结合起来提前规划好一组角度序列即可。我自己做了这个功能之后练习效率明显提升因为不用频繁弯腰捡球、调整落点。再次是“球速测量”方向。两个摩擦轮之间的缝隙处加上一对光电测速传感器精确测量球飞出的实际速度。有了这位数据就能形成一套“轮速-实际球速”的标定曲线控制精度能再上一个台阶。这对进阶选手非常有价值尤其是练习接弧圈球的时候需要知道每个球大概的速度和旋转强度。最后是“Wokwi 仿真验证逻辑”的方向。如果你不想每次调试都动硬件可以在 Wokwi 这类在线仿真平台里把 Arduino 的接线、传感器和电机逻辑先跑一遍。我在做代码大改时都会先在仿真平台上验证逻辑正确性再烧录到真实设备上。这个习惯帮我省掉了大量反复写烧、烧了再改的时间。6. 写在最后的实操体会最后分享两个经验一个关于调试心态一个关于让项目更耐用的细节。调试心态方面这个项目最考验人的是“参数之间的耦合”。轮速会影响出球速度出球速度会影响球的轨迹轨迹又受到倾角的影响而倾角的调整又会反过来影响球进入摩擦轮的角度——所有变量都互相牵扯。我调试的时候习惯一次只动一个变量比如固定倾角不变只调轮速观察落点变化固定轮速不变只调倾角再观察。这样能建立一张精确的“参数→结果”映射表而不是靠感觉乱试。如果你发现怎么调都不对大概率不是调参的问题而是某个机械结构松了或传感器数据不对先排查硬件再回头调参。设备耐用性方面给所有螺丝连接处加上防松垫圈或螺纹胶这个细节非常关键。发球机工作时的震动比想象中大得多普通螺丝用几天就会松动最怕的是摩擦轮的固定螺丝松动——轻则影响出球稳定性重则轮子飞出来砸到人。我最初没注意这一点结果在一次高速测试中摩擦轮连接器松脱轮子带着螺丝直接飞了出去还好没伤到人。此后我所有关键连接都用螺纹胶加固每次使用前也养成了检查螺丝紧固状态的习惯。防潮防尘方面乒乓球馆环境相对干燥但粉尘不少舵机和电机这类精密部件需要做好保护。我给舵机输出轴加了防尘盖板电机接线端子用硅胶密封主板放在一个带透明视窗的亚克力盒里既方便观察 LED 状态灯又能挡灰。这个项目做下来带给我最大的收获不是“我会做发球机了”这个标签而是建立了一套从机械设计、电路搭建、嵌入式编程到现场调试的完整工程思维。如果你也想做一个我的建议是先把机械结构做简单做稳再上控制逻辑和花哨功能先把基础出球跑通再琢磨自动落点、无线遥控这些进阶玩法。一步一步来每一步都能看到反馈这种持续获得正反馈的感觉才是 DIY 最上头的部分。
阅读完成 · 觉得有帮助?