1. 项目缘起与整体设计思路
1.1 为什么选择写字机器人作为入门项目
很多人玩Arduino都是从点灯、蜂鸣器、超声波测距开始的,这些项目确实简单,但做完之后总觉得少了点“作品感”。写字机器人不一样,它能把代码变成看得见摸得着的物理动作,而且成品摆在桌上确实像那么回事。我第一次看到别人用两个舵机加一支笔在纸上写字的视频时,第一反应是“这玩意儿到底怎么控制笔画的”,后来自己动手做了一台才发现,核心逻辑比想象中简单得多,但细节坑是真不少。
这个项目的本质是双轴平面定位系统。两个舵机分别控制笔在X轴和Y轴方向的位置,通过PWM信号控制舵机转角,再通过连杆机构把角度变化转换成笔尖的平面位移。听起来像那么回事,实际上你完全可以把它理解成一个“用两根棍子推着笔在纸上画线”的装置。适合谁做?有基础Arduino编程经验、会插面包板、能看懂简单电路图的人。完全零基础也能做,但建议先把Arduino IDE装好、跑通一个舵机来回转的例程再开始。
1.2 机械结构选型:为什么是双舵机而不是步进电机
市面上写字机器人方案大致分三类:步进电机+同步带、双舵机+连杆、总线舵机机械臂。步进电机方案精度高、写字快,但成本直接翻倍,而且需要驱动模块和更复杂的电源设计。总线舵机机械臂方案灵活度高,但一套下来少说几百块,调试门槛也高。双舵机方案最大的优势是成本极低、结构简单、调试直观。两个SG90舵机不到二十块,加上一些杜邦线、亚克力板或者硬纸板,总成本能控制在五十块以内。
但双舵机方案有个天然缺陷:舵机的角度分辨率有限。SG90的PWM控制精度大约是每度对应11微秒左右的脉宽变化,而Arduino的analogWrite或者Servo库输出的脉宽分辨率大约是1微秒,理论上能分辨0.09度。但实际因为舵机内部电位器的死区和齿轮间隙,有效分辨率大概在0.5到1度之间。这意味着笔尖的定位精度大概在毫米级别,写大字没问题,写小字会糊。所以这个项目的定位很明确:做一台能写5厘米以上大字的写字机器人,用来演示原理和体验动手乐趣,而不是追求印刷级精度。
1.3 控制逻辑的核心:从坐标到角度的映射
写字机器人的核心算法其实就一句话:把你想写的字的笔画拆成一系列坐标点,然后通过逆运动学算出每个点对应的两个舵机角度。逆运动学这个词听着唬人,但在双舵机连杆结构里,它就是一个简单的几何关系。
假设两个舵机分别位于纸面的左下角和右下角,笔尖通过两根连杆分别连接到两个舵机上。已知笔尖坐标(x, y),两个舵机的固定位置分别是(0, 0)和(L, 0),那么笔尖到左舵机的距离d1 = sqrt(x² + y²),到右舵机的距离d2 = sqrt((L-x)² + y²)。再根据连杆长度和舵机臂长度,用余弦定理就能算出两个舵机需要转到的角度。这个计算在Arduino里用atan2和acos函数几行代码就能搞定。
但实际做的时候你会发现,纯几何计算出来的角度和实际需要的角度总有偏差,因为舵机臂和连杆的连接点不在舵机转轴上,而且连杆长度也不可能完全精确。所以实操中更常用的方法是标定法:先让舵机转到几个已知角度,记录笔尖的实际位置,然后拟合出一条角度到位置的映射曲线。这个方法虽然“不优雅”,但极其有效,而且能补偿掉大部分机械误差。
2. 核心细节解析与实操要点
2.1 舵机选型:SG90、MG90S还是MG996R
SG90是最常见的小舵机,塑料齿轮,扭矩约1.8kg·cm,重量9克。优点是便宜、轻、对电源要求低,直接接Arduino的5V引脚就能转。缺点是塑料齿轮容易扫齿,尤其是写字机器人这种需要频繁正反转的场景,用不了多久齿轮间隙就会变大,导致笔迹抖动。我第一台写字机器人用的就是SG90,写了大概两百多个字之后,X轴舵机就开始出现明显的回程差,笔尖回到同一个坐标时位置偏了差不多两毫米。
MG90S是SG90的金属齿轮版本,扭矩略大,价格贵几块钱,但耐用性提升明显。如果你打算长期玩这个项目,强烈建议直接上MG90S,省得中途换舵机重新标定。MG996R是更大一号的舵机,扭矩11kg·cm,金属齿轮,但重量55克,对支架强度要求高,而且堵转电流能到2.5A,Arduino的5V引脚根本带不动,必须外接电源。对于写字机器人这种轻负载场景,MG996R属于杀鸡用牛刀,没必要。
还有一个容易被忽略的参数是复位电流。舵机在每次上电时都会有一个复位动作,瞬间电流可能达到正常工作电流的三到五倍。如果你用Arduino的5V引脚直接给两个舵机供电,复位瞬间很可能导致Arduino重启或者程序跑飞。我踩过这个坑,后来老老实实给舵机单独接了一个5V 2A的电源适配器,问题就再也没出现过。
2.2 电源方案:为什么不能只用USB供电
Arduino Uno的5V引脚来自板载的AMS1117稳压芯片,最大输出电流大约800mA,但这是在输入电压7-12V且散热良好的情况下。如果你用USB供电,5V引脚的实际可用电流更小,大概只有400-500mA。两个SG90舵机在空载时每个电流约100mA,加起来200mA,看起来够用。但写字过程中舵机需要频繁启停和换向,峰值电流轻松超过500mA,再加上复位电流,USB供电几乎必然出问题。
我的建议是:舵机电源和Arduino电源完全分开。Arduino用USB供电或者9V电池供电都行,舵机用一个独立的5V 2A电源适配器,然后把两个电源的GND连在一起。这样舵机的电流波动不会影响Arduino,程序运行稳定得多。如果你非要用一个电源,那就选一个5V 3A以上的适配器,同时给Arduino和舵机供电,但要注意Arduino的5V引脚不能直接接外部5V电源,得从VIN引脚输入7V以上电压,或者从USB口输入5V。
2.3 机械结构搭建:从纸板到亚克力的升级路径
第一台原型机完全可以用硬纸板和热熔胶搞定。找一块A3大小的硬纸板做底板,两个舵机用热熔胶固定在底板的两个角上,舵机臂上打孔,用回形针或者细铁丝做连杆,笔用橡皮筋绑在连杆末端。这个结构粗糙但能用,主要目的是验证代码和运动逻辑。
但纸板结构有个致命问题:刚度不够。舵机一转,纸板跟着变形,笔尖位置就飘了。而且热熔胶在受力后会有微小的蠕变,用久了舵机位置会偏移。所以第二版建议用3mm亚克力板,用激光切割或者手工锯出底板和舵机支架,用M3螺丝固定舵机。亚克力的刚度足够,而且加工精度比纸板高一个数量级。如果你没有加工条件,也可以用现成的舵机支架套件,网上有卖双舵机云台支架的,十几块钱一套,省事很多。
连杆的选择也有讲究。回形针太软,写字时会有弹性形变;细铁丝好一些,但容易弯;最理想的是碳纤维杆或者铝管,直径2mm左右,刚度高重量轻。连接点用M2螺丝加尼龙锁紧螺母,既牢固又能微调松紧。笔的固定方式推荐用笔夹+扎带,比橡皮筋稳定,而且换笔方便。
3. 实操过程与核心环节实现
3.1 硬件连接与供电改造
先把两个舵机的信号线分别接到Arduino的9号和10号引脚,红线接5V电源正极,棕线接电源负极。注意这里舵机的5V不要接Arduino的5V引脚,而是接独立电源的正极。然后把独立电源的负极和Arduino的GND连起来,这一步千万不能忘,否则信号电平没有参考地,舵机会乱转。
如果你用的是MG90S,可以在舵机电源正负极之间并一个1000μF的电解电容,用来吸收复位电流和换向时的电流尖峰。这个电容相当于一个“小水库”,舵机瞬间要电的时候先从这个电容里取,不会把电源电压拉低。实测下来,加了电容之后舵机复位时的抖动明显减小。
代码方面,Arduino自带的Servo库就够用了。但要注意Servo库默认使用Timer1,会影响到9号和10号引脚的PWM输出。如果你同时要用其他PWM功能,可能会冲突。解决办法是用ServoTimer2库,或者换到其他引脚。不过对于写字机器人这个项目,只用两个舵机,直接用Servo库最省事。
3.2 坐标到角度的标定流程
前面说了纯几何计算会有偏差,所以实操中我推荐三点标定法。具体操作是:先让两个舵机都转到90度(中位),记录笔尖位置;然后左舵机转到45度,右舵机不动,记录笔尖位置;再左舵机转到135度,右舵机不动,记录笔尖位置。用这三个点拟合出左舵机角度和笔尖X坐标的关系。右舵机同理。
这个标定过程听起来麻烦,但做一次之后就能用很久。而且标定数据可以直接存在Arduino的EEPROM里,下次上电不用重新标。我自己的标定代码大概三十行,核心就是用map函数做线性插值。如果你想要更高精度,可以标五个点做二次拟合,但实际体验下来三点线性插值的精度已经够写大字了。
标定的时候有个小技巧:在笔尖下面垫一张坐标纸,这样你能直接读出笔尖的X、Y坐标,不用拿尺子量。坐标纸网上几块钱一大张,比自己做标记方便得多。
3.3 写字轨迹的生成与优化
写字机器人的轨迹生成有两种思路:一种是矢量法,把每个字的笔画拆成直线和圆弧,然后离散成点;另一种是位图法,把字渲染成黑白图片,然后逐行扫描提取笔迹坐标。矢量法写出来的字边缘光滑,但需要手动拆笔画,工作量大。位图法自动化程度高,但写出来的字有锯齿感,而且逐行扫描会导致笔尖频繁抬落,写字速度很慢。
我实际用的是混合法:先用矢量法把常用字拆好,存成一个坐标数组;对于不常见的字,用位图法临时生成。矢量法的坐标数组格式很简单,就是一个二维数组,每一行是一个笔画的起点和终点坐标。比如写“一”字就是{{10, 50}, {90, 50}},表示从(10,50)画到(90,50)。写“十”字就是两个笔画:{{50, 10}, {50, 90}}和{10, 50}, {90, 50}。
但直接画直线会有问题:舵机从起点转到终点的过程中,笔尖走的不是直线,而是一条弧线。这是因为两个舵机的角度变化不是线性的,而是三角函数关系。解决办法是把每条直线离散成20到50个小段,每段单独计算舵机角度,这样笔尖走的就近似直线了。离散点数越多,线条越直,但舵机动作越频繁,写字速度越慢。实测下来,每毫米两个点是比较好的平衡点。
3.4 完整代码框架与关键函数
代码整体分三块:初始化、坐标转换、轨迹执行。初始化部分设置舵机引脚、读取EEPROM里的标定数据、把笔抬到起始位置。坐标转换部分就是前面说的逆运动学计算,封装成一个函数xyToAngles(float x, float y, int &angle1, int &angle2)。轨迹执行部分遍历坐标数组,对每一段直线做离散化,然后逐个点设置舵机角度。
关键函数xyToAngles的实现逻辑是这样的:先计算笔尖到两个舵机转轴的距离,然后用余弦定理算出连杆和舵机臂之间的夹角,再加上舵机臂的初始角度偏移。这个偏移量就是标定要解决的问题。代码里用atan2算方向角,用acos算夹角,最后转换成舵机角度。注意acos的参数要限制在-1到1之间,否则浮点误差会导致返回NaN,舵机就会乱转。
还有一个容易忽略的点是舵机运动速度控制。如果直接让舵机从当前角度跳到目标角度,舵机会以最大速度冲过去,笔尖会剧烈抖动,写出来的线像蚯蚓爬。解决办法是在两个角度之间做插值,每次增加一小步,延时几毫秒,让舵机平滑过渡。这个插值步长和延时决定了写字速度,步长越小、延时越长,写字越慢但越稳。我一般用步长1度、延时15毫秒,写一个字大概需要十几秒。
4. 常见问题与排查技巧实录
4.1 舵机抖动、复位、发热的排查思路
舵机抖动是最常见的问题,原因通常有三个:电源功率不足、信号线干扰、机械负载过大。排查顺序建议从电源开始。先用万用表量一下舵机电源电压,在舵机转动时如果电压从5V掉到4.5V以下,那就是电源功率不够,换个大电流的适配器或者加电容。如果电压稳定但还抖,检查信号线是不是和电源线捆在一起了,分开走线试试。如果还抖,把舵机从机械结构上拆下来空载转,如果空载不抖负载抖,那就是机械结构卡涩,检查连杆有没有别劲。
舵机复位问题前面提过,主要是复位电流导致的电压跌落。除了加电容和独立供电,还可以在代码里加一个延时,上电后先等两秒再初始化舵机,给电源一个稳定时间。另外Servo库的attach函数会立即让舵机转到中位,如果你不希望上电时舵机突然动一下,可以先attach再慢慢转到目标角度。
舵机发热一般是堵转或者频繁启停导致的。写字机器人正常工作时舵机应该是温的,不至于烫手。如果烫手,先检查机械结构有没有卡死,再检查代码里有没有让舵机长时间保持在极限角度。SG90的极限角度是0到180度,但实际可用范围建议限制在10到170度之间,留一点余量,避免内部限位机构受力。
4.2 写字歪斜、笔画不直的机械原因
写字歪斜通常不是代码问题,而是机械问题。最常见的原因是两个舵机的转轴不在同一条水平线上。如果左舵机比右舵机高了两毫米,笔尖走的水平线就会变成斜线。解决办法是用一个直角尺校准两个舵机的安装位置,确保转轴等高。另一个原因是连杆长度不一致,左右连杆差一毫米,笔尖的轨迹就会偏。用游标卡尺量一下,尽量做到等长。
笔画不直还有可能是舵机齿轮间隙导致的。SG90的塑料齿轮用久了会有回程差,笔尖从A点到B点再回到A点,位置会偏。这个问题没法完全消除,只能换金属齿轮舵机或者接受这个误差。如果你发现某个方向的笔画特别不直,检查一下那个方向的舵机是不是齿轮磨损更严重。
4.3 上传项目出错与IDE配置问题
Arduino上传项目出错的原因很多,最常见的是串口被占用。如果你同时打开了串口监视器和其他串口软件,上传时会提示端口被占用。关掉串口监视器再上传就行。另一个常见原因是开发板型号选错,比如你用的是Arduino Uno但IDE里选的是Arduino Nano,上传会失败。在“工具”菜单里确认开发板和端口选对了。
还有一个坑是USB线的问题。有些USB线只能供电不能传数据,插上之后电脑能识别到端口但上传总是失败。换一根线试试,最好是带屏蔽的数据线。如果IDE里根本看不到端口,检查一下驱动装了没有,Arduino Uno用的是CH340或者ATmega16U2,不同版本驱动不一样。
对于ESP32开发板,上传时要注意按住BOOT键。ESP32在上电时会检测某个引脚的电平来决定运行模式,如果程序里把这个引脚拉低了,下次上传就会失败。解决办法是上传时按住BOOT键,等出现“Connecting...”再松开。这个坑我踩过好几次,后来养成了上传前先按BOOT的习惯。
4.4 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 舵机抖动 | 电源功率不足 | 万用表测舵机电源电压 | 换2A以上适配器,加1000μF电容 |
| 舵机复位 | 复位电流拉低电压 | 观察上电瞬间电压跌落 | 独立供电,上电延时2秒 |
| 舵机发热 | 机械卡涩或堵转 | 拆下舵机空载测试 | 调整连杆,限制角度范围 |
| 写字歪斜 | 舵机转轴不等高 | 直角尺校准 | 重新安装舵机支架 |
| 笔画不直 | 齿轮间隙或离散点太少 | 检查回程差,增加离散点 | 换MG90S,每毫米2个点 |
| 上传失败 | 串口占用或板型选错 | 检查端口和开发板设置 | 关闭串口监视器,重选板型 |
| ESP32上传失败 | BOOT引脚电平不对 | 观察上传时提示 | 按住BOOT键再上传 |
| 笔尖位置漂移 | 标定数据不准 | 重新三点标定 | 存EEPROM,定期校准 |
4.5 几个让我少走弯路的实操心得
第一个心得是先调机械再调代码。很多人一上来就埋头写代码,结果机械结构松松垮垮,代码调得再好也写不出好字。我的做法是先把机械结构装好,用手转动舵机臂,感受一下整个运动范围内有没有卡涩,笔尖能不能覆盖到纸面的每个角落。机械顺了,代码调试会快很多。
第二个心得是标定数据一定要存EEPROM。我第一版代码没存,每次上电都要重新标定,烦得要命。后来加了EEPROM读写,标定一次能用几个月。EEPROM的读写次数有限,大概十万次,但标定数据只在手动触发时才写,完全够用。
第三个心得是写字速度不要贪快。我一开始把延时设成5毫秒,写字确实快,但笔迹抖得厉害,而且舵机发热严重。后来改成15毫秒,写字慢了一半,但笔迹明显干净,舵机也只是温温的。写字机器人不是打印机,慢一点反而更有“手写”的感觉。
第四个心得是笔的选择比想象中重要。圆珠笔太滑,笔尖容易在纸上打滑;铅笔太软,写几个字就钝了;中性笔最合适,0.5mm的笔尖粗细刚好,出水流畅又不会洇墨。笔的固定也很关键,一定要夹紧,否则写字时笔会轻微晃动,笔迹就会发虚。
5. 功能扩展与进阶玩法
5.1 从写字到画图:轨迹数据的通用化
写字机器人的核心是坐标轨迹,只要把轨迹数据换掉,它就能画图。你可以用Inkscape把SVG矢量图导出成坐标点,然后转成Arduino能读的数组格式。我试过画一个简单的五角星和一只猫的轮廓,效果还不错。但要注意SVG里的曲线需要离散成足够密的点,否则画出来会有棱角。Inkscape有个“扁平化贝塞尔曲线”的功能,可以把曲线转成很多小直线段,导出后直接能用。
如果你想让机器人画得更精细,可以把坐标数据存到SD卡里,Arduino从SD卡读取。这样就不受Flash容量限制了,画复杂的图也没问题。SD卡模块几块钱一个,用SPI接口和Arduino连接,代码也不复杂。
5.2 加入ESP32实现无线控制
Arduino Uno没有无线功能,每次改字都要重新插USB线上传,很麻烦。换成ESP32之后,可以做一个简单的Web服务器,在手机浏览器里输入文字,ESP32收到后生成轨迹并控制舵机写字。ESP32的Arduino核心自带WiFi库,几十行代码就能搭一个Web服务器。我做过一个版本,手机连上ESP32的热点,打开网页输入“你好”,机器人就写“你好”,体验很流畅。
但ESP32的PWM频率和Arduino Uno不一样,Servo库在ESP32上需要改用ESP32Servo库。这个库的API和Servo库基本兼容,但引脚编号要按ESP32的GPIO来。另外ESP32的供电是3.3V,舵机信号线接上去也能用,但最好加一个电平转换模块,确保信号稳定。
5.3 用Wokwi仿真平台先验证代码
如果你还没有买硬件,或者担心代码有问题烧坏舵机,可以先用Wokwi仿真平台跑一遍。Wokwi支持Arduino Uno和ESP32,可以拖拽添加舵机、电阻、LED等元件,代码直接在浏览器里运行。虽然仿真里的舵机不会真的转,但你可以通过串口输出看到角度计算是否正确,逻辑有没有问题。我每次改完代码都会先在Wokwi里跑一遍,确认没有数组越界或者死循环,再下载到真机上。
Wokwi的舵机元件可以显示当前角度,你可以在仿真里直观地看到两个舵机的角度变化,对于调试逆运动学算法特别有用。而且Wokwi支持串口监视器,你可以把计算过程中的中间变量打印出来,方便定位问题。
5.4 从双舵机到总线舵机机械臂的升级路径
双舵机写字机器人玩透了之后,如果你想挑战更复杂的项目,可以考虑总线舵机机械臂。总线舵机通过串口通信,每个舵机有唯一的ID,可以串联在一起,只需要两根信号线就能控制十几个舵机。飞特舵机是市面上比较常见的选择,他们的总线舵机支持位置、速度、扭矩多种控制模式,精度也比PWM舵机高得多。
但总线舵机的门槛也高不少。首先是成本,一个总线舵机就要几十到上百块,一套六轴机械臂下来至少五六百。其次是通信协议,需要看手册配置波特率、ID、工作模式,调试起来比PWM舵机麻烦。不过如果你已经做过写字机器人,对舵机控制、坐标变换、轨迹规划有了基本概念,升级到总线舵机机械臂会顺利很多。我自己的路径就是先做写字机器人,再做总线舵机机械臂,后者虽然复杂,但核心逻辑是相通的。
5.5 用激光测距辅助标定的思路
写字机器人的标定一直是个麻烦事,每次换笔或者调整机械结构都要重新标。后来我想了个办法:在笔尖旁边装一个激光测距模块,实时测量笔尖到纸面的距离。虽然这个距离不能直接给出X、Y坐标,但可以用来检测笔尖是否接触纸面,以及纸面是否平整。如果纸面有起伏,激光测距的数据会变化,你可以据此调整笔的抬落高度,避免笔尖悬空或者压得太紧。
这个思路来自我玩STM32时做的一个项目,当时用RTOS配置运行舵机和激光测距,把数据解析后发送到电脑串口。那个项目让我意识到,激光测距模块(比如VL53L0X)体积小、精度高,完全可以集成到写字机器人上。不过这个功能属于锦上添花,不是必须的,建议先把基础功能做稳定再考虑。
6. 写在最后的一些个人体会
做写字机器人这个项目,最大的收获不是最后写出来的那几个字,而是过程中踩过的那些坑。电源问题让我学会了看数据手册算电流,机械抖动让我理解了刚度和间隙的重要性,标定偏差让我明白了理论计算和实际系统之间的差距。这些东西在书本上是学不到的,只有亲手做过、错过、改过,才能真正变成自己的经验。
如果你正在做这个项目,我的建议是:不要追求一次成功。第一版能动能写就行,哪怕字歪歪扭扭也没关系。然后在这个基础上一点点改进,换金属齿轮舵机、换亚克力底板、加电容、优化标定算法。每改一次,你都会对系统有更深的理解。我自己的写字机器人改了五版,从纸板到亚克力,从SG90到MG90S,从USB供电到独立电源,每一版都比上一版好一点,这个过程本身就很有意思。
最后分享一个小技巧:如果你觉得逆运动学计算太麻烦,可以先用查表法。把舵机角度从0到180度分成36份,每份5度,然后手动测量每个角度组合对应的笔尖位置,做成一个二维查找表。写字的时候根据目标坐标在表里找最近的点,直接用对应的角度。这个方法精度取决于表的密度,但胜在简单粗暴,不需要任何三角函数计算,特别适合数学基础薄弱的朋友。等查表法玩熟了,再回头理解逆运动学,会发现其实没那么难。