资讯动态

Python控制Arduino与树莓派:串口、Firmata、GPIO实战指南

发布时间:2026/10/13 21:20:18 来源:尧图企业网站定制
这两年问我硬件入门的人越来越多十句话里八句都绕不开同一个问题用Python到底怎么控制Arduino或者树莓派这个问题看着简单真上手之后却藏着一堆隐性知识比如两边的数据怎么对齐、串口为什么一打开板子就重启、树莓派上明明写了代码却报权限错误。这篇文章不打算写成说明书而是把两套方案从原理到落地完整过一遍——先搞清楚Arduino和树莓派的定位区别再分别给出串口控制、Firmata协议、GPIO直连的完整代码和调试技巧。无论你是做课程设计、攒个人作品集还是单纯想验证一个想法都能在这篇里找到可以直接抄作业的方案。1. 动手之前先把Arduino和树莓派这两条路线理清楚很多人把Arduino和树莓派放在一起对比其实它们根本不是同类东西。理解这一点比记住任何一条API都重要。1.1 Arduino和树莓派的本质区别专职前台和综合办公室Arduino本质上是一块单片机开发板核心是一颗AVR芯片比如常见的那颗16MHz的芯片跑的是裸机程序。它没有操作系统代码就是不断循环的loop你让它20微秒切换一次引脚电平它就能老老实实做到。这种确定性极高、定时精确的能力业内叫硬实时。树莓派则是一台完整的微型电脑跑着Linux系统背后有几百个进程在抢CPU时间。你可以同时开网页、跑Python、挂下载任务但它毕竟是分时调度的一个Python脚本里sleep(0.01)实际睡多久可能要受系统负载影响误差能到几十毫秒。这种能力叫软实时说白了就是“大多数时候够用但关键场合不保证”。打个比方Arduino像酒店前台只有一个人一次只能专心办一件事但反应极快、从不走神树莓派像一个大办公室能同时处理很多业务但谁先谁后全看排班系统遇到大任务还会全员卡顿。这个区别直接决定了你后面所有的选型和代码思路。比如你要精确控制舵机角度、要高频采集传感器数据、要做闭环控制主力一定是Arduino你要做图像识别、跑数据库、做网页控制界面那只能上树莓派。1.2 选型逻辑什么样的项目该用哪块板子我自己接过的项目里选错平台导致返工的情况太多了。这里给出一张我长期用的选型对照表可以帮你快速判断。项目需求推荐平台核心原因传感器采集加简单控制LED、舵机、电机Arduino实时性好断电重连就恢复代码简单需要界面、数据库、网络服务树莓派原生跑完整Python生态不用考虑单片机资源限制图像识别、语音、小型模型推理树莓派算力和系统支持是硬门槛高频PWM、电机调速、多路舵机联动Arduino硬件PWM稳定树莓派软件PWM会抖长时间无人值守的数据采集两者结合Arduino负责底层采集树莓派负责存储和上报最后一行是我比较推荐的一种架构让Arduino做它擅长的实时采集和控制再通过串口把处理好的数据交给树莓派由树莓派完成日志记录、图表展示、网络上传。很多工业级的小型物联网网关就是这么干的两头各干各的不会互相拖累。1.3 一套够用的物料清单如果你是零基础起步不需要一步到位买一堆传感器。以Arduino为例基础套装通常包含一块Arduino开发板或兼容板、一根能传数据的USB线这点后面会重点说、面包板、LED若干、220欧电阻、轻触按键、一个舵机、一个DHT11温湿度模块、若干杜邦线。树莓派这边需要板子、5V/2.5A以上的电源、TF卡和读卡器、散热片、网线或无线网络。软件方面只需要装好Python3以及pyserial、pyfirmata、gpiozero这几个库。提示先不要急着买一堆传感器模块。我见过太多人装备吃灰几年还没拆封。真正需要的时候再按项目需求单独买性价比高得多。2. 环境搭建与通信方案选型串口、Firmata、GPIO怎么选2.1 用Python控制Arduino的两种方案自定义串口协议还是FirmataArduino自己不能直接跑Python所以“用Python控制Arduino”本质上都是通过通信接口间接实现的。目前最主流的两条路一条是给Arduino烧录自定义固件让你自己的代码通过串口接收Python发来的指令另一条是烧录Firmata协议固件之后Python端直接通过pyFirmata库操作引脚像操作本地硬件一样。第一条路的典型流程是Arduino上跑一个循环程序不停检查串口缓冲区有没有数据收到指令就执行对应动作。好处是效率高、逻辑完全受控适合正式项目缺点是每改一次控制逻辑就要重新烧录一次固件。第二条路更省事。你只需要给Arduino烧一次StandardFirmata示例固件之后在Python里写board.digital[13].write(1)就能点亮引脚13的LED不需要再碰Arduino IDE。对快速原型验证来说非常合适。代价是Firmata协议本身有解析开销做高频操作比如逐毫秒刷新PWM性能会打折扣这种场景还是要回到自定义串口协议。2.2 用Python控制树莓派GPIO直连、本地运行和远程调试树莓派完全不存在上面这个绕路问题因为它本身就是台电脑Python可以直接跑在系统里。控制硬件靠的是GPIO引脚库比较常见的有RPi.GPIO和gpiozero。RPi.GPIO是经典库文档多、老项目里到处都是但有个明显的缺点需要root权限运行而且在新一点的树莓派型号上支持不够好。gpiozero则是更现代的选择它对LED、按键、舵机、传感器都做了高层抽象代码看起来特别像自然语言比如控制一个LED就是led LED(17)然后led.on()底层细节全部封装好了新手用起来非常舒服。另外树莓派支持远程调试。你可以在电脑上通过SSH登录树莓派直接在终端里跑Python脚本也可以在电脑上的编辑器里写好代码同步到树莓派执行。我常用的一套组合是在电脑上写代码、用Git同步、在树莓派上跑这样既能享受电脑的编辑体验又能直接在硬件上测试。2.3 开发环境安装与连通性验证环境安装本身不难坑点在于装完之后的验证。Arduino这边先装好Arduino IDE然后在示例库里找到Firmata下的StandardFirmata选择板型和串口直接烧录。Python端执行pip install pyserial pyfirmata树莓派这边官方系统通常自带Python3和gpiozero只需要确认一下版本pip install gpiozero装完之后别急着跑代码先做连通性验证这一步能帮你避免一半的“我的代码怎么没反应”问题。用下面这段代码列出所有串口设备import serial.tools.list_ports ports serial.tools.list_ports.comports() for p in ports: print(p.device, p.description)如果运行后找不到任何设备第一步检查USB线是不是只能充电不能传数据这一点比你想的更常见。树莓派上则可以用gpioinfo命令查看GPIO状态确认系统已经正确识别引脚。3. 四个能直接抄作业的实操案例3.1 案例一Arduino串口控制LED灯亮灭先看最简单的点灯。接线方式LED长脚正极串一个220欧电阻接到Arduino的13号引脚短脚负极接GND。电阻的作用是限流Arduino引脚输出5V而LED正常工作电压大概2V不串电阻直接接LED会因为电流过大很快烧坏。Arduino端烧录这样一段固件String cmd; void setup() { Serial.begin(9600); pinMode(13, OUTPUT); } void loop() { while (Serial.available()) { char c Serial.read(); if (c \n) { if (cmd ON) { digitalWrite(13, HIGH); } else if (cmd OFF) { digitalWrite(13, LOW); } cmd ; } else { cmd c; } } }这里使用的是按行解析的方式Python每次发一条以换行符结尾的指令Arduino读完一整行再执行。用字符串而不是单个数字的好处是代码可读性高以后要增加命令比如“TOGGLE”翻转非常方便。Python端控制代码import serial import time ser serial.Serial(/dev/ttyACM0, 9600, timeout1) time.sleep(2) ser.write(bON\n) time.sleep(1) ser.write(bOFF\n) ser.close()注意两个细节第一write传的是字节对象必须是bON\n而不是字符串ON\n否则会报类型错误第二time.sleep(2)必不可少Arduino在串口连接建立瞬间会触发一次复位如果不等板子复位完成就发命令前几条指令基本会丢。3.2 案例二用pyFirmata控制舵机不用重刷固件假设你想让舵机转到某个角度用自定义固件的话得重新烧录一次。用pyFirmata则简单得多。先确保Arduino烧录的是StandardFirmata这个固件自带舵机支持然后直接写Pythonimport time from pyfirmata import Arduino board Arduino(/dev/ttyACM0) pin board.get_pin(d:9:s) # d表示数字引脚9是引脚号s表示舵机 pin.write(90) time.sleep(1) pin.write(180) time.sleep(1) board.exit()get_pin(d:9:s)这行是关键它向Firmata固件申请把9号数字引脚配置为舵机模式。之后pin.write(角度)就能直接控制舵机转向整个过程不用动Arduino那边的源码。有个非常容易犯的错误舵机电压不够。大舵机启动瞬间电流能到几百毫安甚至更高如果直接从Arduino板载5V引脚取电轻则舵机抖动无力重则把板子拉复位。我的建议是独立给舵机接一个5V电源并且把电源负极和Arduino的GND连在一起也就是俗称的共地。不共地的话控制信号没有统一的参考点舵机一样会乱动。如果后续要读模拟量比如接一个电位器pyFirmata还要求启动一个迭代器from pyfirmata import util it util.Iterator(board) it.start() board.analog[0].enable_reporting() value board.analog[0].read()不启动迭代器的话模拟引脚的数值永远不会自动更新这是pyFirmata新手最常见的困惑之一。3.3 案例三树莓派GPIO实现按钮控制灯这个是树莓派入门必做的交互案例按一下按钮LED状态翻转。接线同样简单按钮一端接GPIO17另一端接GNDGPIO内部启用上拉电阻LED经220欧电阻接GPIO18和GND。gpiozero的代码像读出需求描述一样直白from gpiozero import LED, Button led LED(18) btn Button(17, pull_upTrue) def on_press(): led.toggle() btn.when_pressed on_press input(按回车退出...\n)pull_upTrue的意思是启用GPIO内置上拉电阻这样按钮没按下时引脚读到高电平按下后接地读到低电平不需要额外接电阻。这种“按键直接接GND、启用内部上拉”的接法比传统的外接电阻方案少一根线也更稳定。when_pressed是事件回调方式gpiozero会在后台线程里监听按钮状态主程序可以继续做别的。这是实际项目里更推荐的方式比轮询省资源。当然如果你只是想要一个简单的轮询版本也可以写死循环from gpiozero import LED, Button led LED(18) btn Button(17, pull_upTrue) while True: if btn.is_pressed: led.on() else: led.off()轮询的问题在于CPU会被占满而且按钮抖动可能导致逻辑反复跳变。gpiozero的Button默认自带软件防抖如果想调灵敏度可以设置bounce_time参数。写硬件交互代码时防抖这件事永远不要忽略。3.4 案例四树莓派读取温湿度传感器并记录到文件DHT11是最常见的入门温湿度传感器三根线分别是VCC、DATA、GND。接线方式VCC接树莓派的3.3VDATA接GPIO4GND接GND。如果买的是模块版本通常板上已经带了上拉电阻直接连就行如果是裸传感器需要在DATA和VCC之间接一个10k欧上拉电阻。树莓派上推荐用CircuitPython生态的DHT库安装命令pip install adafruit-circuitpython-dht sudo apt install libgpiod2读取代码import board import adafruit_dht import time dht adafruit_dht.DHT11(board.D4) while True: try: temp dht.temperature hum dht.humidity print(f温度: {temp:.1f}°C, 湿度: {hum:.1f}%) except RuntimeError as e: print(读取失败稍后重试:, e) time.sleep(2)DHT11这个传感器的通信协议比较特殊对时序要求极高稍有干扰就会返回校验错误所以代码里必须加try-except重试机制这不是可有可无的容错而是这个传感器正常工作的必要部分。读取间隔也不要低于1秒这个传感器自身设计就比较慢。把数据写进文件也很简单import csv with open(temp_log.csv, a, newline) as f: writer csv.writer(f) writer.writerow([time.strftime(%Y-%m-%d %H:%M:%S), temp, hum])每次读取后追加一行就形成了一个最基础的数据记录系统。后面想画图表或者上报云端只需要另写一个脚本定期解析这个CSV文件就行。注意如果你发现树莓派读DHT11经常失败而Arduino读同样的传感器却一切正常不用怀疑传感器坏了这是平台差异。DHT11的时序协议在裸机环境里跑更合适树莓派上有操作系统干扰稳定性天然差一截。这种情况下把传感器接到Arduino、由Arduino通过串口把数据转发给树莓派反而是更省心的架构。4. 常见问题与排查技巧实录4.1 串口打不开、权限不足怎么解决这是Arduino玩家第一个高频卡点。Windows上最常见的表现是Python报找不到COM口先设备管理器里看有没有识别出设备没有的话大概率是USB线的问题——很多廉价线只走电源不走数据换一根带数据传输的线立刻解决。端口号大于9时pyserial里要用\\\\.\\COM10这样的写法不然也会打不开。Linux上则是经典的权限错误serial.serialutil.SerialException: could not open port /dev/ttyACM0 [Errno 13] Permission denied: /dev/ttyACM0这是因为当前用户不在dialout用户组里。执行下面命令后注销重新登录即可sudo usermod -a -G dialout $USER另外还有一个隐蔽问题Arduino IDE的串口监视器如果开着会独占串口Python这边自然打不开。所以排查顺序应该是先确认设备被识别再确认权限最后确认串口没被其他程序占用。4.2 通信乱码、数据错位一般是哪几个原因乱码的第一嫌疑永远是波特率不匹配。Arduino端Serial.begin(9600)Python端就必须写serial.Serial(port, 9600)两边都是9600才能对上。如果改成115200记得两边的代码都要改。第二个常见问题是Python发送的数据编码不对。ser.write(ON\n)会直接报类型错误正确的方式是带上前缀b转成字节ser.write(bON\n)。对于字符串变量要用ser.write(cmd.encode())。第三个问题是Arduino每次打开串口都会自动复位导致模块重新初始化恰好赶上Python发来的第一批数据被丢得干干净净。解决办法就是前面说的连接后sleep两秒再发数据。如果你是老手想彻底关闭这个行为可以在串口对象创建后手动设置ser.setDTR(False)。还有一个很多人没意识到的问题Arduino端写代码时把调试用的Serial.println(debug)和控制指令混在同一个串口里发。Python端如果全部按指令解析就会看到一堆莫名其妙的字符串。我的做法是正式项目里把调试信息通过单独的引脚或文件输出串口只走协议数据。4.3 树莓派GPIO报错和编号混乱怎么处理树莓派上跑GPIO代码最常见的报错是RuntimeError: No access to /dev/mem. Try running as root!解决方式是把当前用户加入gpio组或者用sudo运行。但从治本的角度我建议用gpiozero而不是老式的RPi.GPIO前者在权限处理上更友好代码也更不容易踩到历史兼容问题。引脚编号混乱也是新手重灾区。树莓派GPIO有两种编号方式BOARD编号是物理引脚位置BCM编号是芯片内部编号。比如物理第11号引脚对应BCM编号17如果接线时按物理位置接代码里却按BCM编号写灯自然不亮。gpiozero默认用BCM编号接线前先查清楚你手上的引脚编号表这比盲试快得多。另一个诡异问题是“Another program is using GPIO”之类的资源占用提示。这通常是上一次脚本没有正常退出或者同时开了多个终端跑同一个引脚。重启树莓派或者杀掉残留进程就能解决。4.4 实时性不够用怎么靠架构补救树莓派跑Python控制硬件最尴尬的时刻就是需要精确定时的时候。Linux系统下有太多不可控因素后台任务、WiFi中断、Python垃圾回收都可能让定时任务出现几十毫秒的延迟。如果你在做的是呼吸灯、舵机联动这类对时序敏感的项目就会明显感到抖动。我的经验是不要硬扛。如果只是PWM需求优先选择支持硬件PWM的引脚或者干脆用I2C外接一个舵机驱动板让专用芯片去生成PWM波形。如果是高频率采样比如要采集音频或振动信号正确做法是让Arduino这类单片机做采样和预处理把平均值、峰值之类的结果通过串口丢给树莓派而不是让树莓派直接高频读引脚。整体设计思路就一句话需要“实时”的工作交给单片机需要“智能”的工作交给树莓派。方向对了后面少踩很多坑。5. 踩过的坑汇总与进阶方向5.1 新手最容易踩的六个坑第一个坑是USB线只充电不传数据。这个问题隐蔽性极强能浪费你一下午的排查时间。建议准备两三条质量有保障的数据线专门给开发板用别和充电线混在一起。第二个坑是忘记共地。当你要让Arduino和别的板子、传感器模块通信时所有设备的GND必须连在一起不然信号参考点不一致数据必然乱。检查顺序里“地线接了吗”永远排第一位。第三个坑是按钮没做防抖。直接读引脚电平做按键计数你会发现按下一次数字跳了好几个。要么用库里现成的防抖参数要么在代码里加延时过滤别指望手按得足够稳。第四个坑是树莓派直接拔电关机。你以为在跑数据采集实际在加速SD卡报废。正确做法永远是sudo poweroff等电源指示灯彻底灭了再拔。第五个坑是舵机或电机直接取电板载5V。大电流设备一启动整个板子电压被拉垮轻则重启重则损坏器件。外接独立电源并在GND处共地是标准的供电姿势。第六个坑是带电接GPIO线。热插拔GPIO引脚轻则干扰电平重则把引脚甚至整个芯片烧掉。每一次接线前先断电。这六个坑我在不同项目里反复踩过后来干脆把这张清单打印出来贴在桌面上每次接硬件之前扫一眼有效避免了无谓的返工。5.2 进阶从本机控制到局域网和物联网当你能用Python熟练控制Arduino和树莓派之后下一步很自然是让控制不再局限于电脑面前。一个简单实用的做法是在树莓派上跑一个极简的Web接口通过浏览器或手机控制LEDfrom flask import Flask from gpiozero import LED app Flask(__name__) led LED(18) app.route(/on) def turn_on(): led.on() return OK app.route(/off) def turn_off(): led.off() return OK app.run(host0.0.0.0, port5000)在树莓派上启动这个脚本后局域网内任何设备只要访问http://树莓派IP:5000/on就能开灯。如果把Arduino串口也接到树莓派上你甚至可以把这个Web服务当成一个桥网页控制树莓派树莓派再通过串口转发指令给Arduino从而实现对Arduino外设的远程控制。再往物联网方向走可以引入MQTT协议把传感器数据发布到消息服务器用现成的仪表盘工具实时展示。数据采集端往往还是Arduino加传感器树莓派做网关负责转发和本地存储。这套架构下你要写的代码量不大但整个系统的扩展性比单板方案强很多。最后分享一个我自己养成的习惯每次接线之前先在纸上画出引脚对应关系再写代码。硬件项目里最让人崩溃的从来不是代码逻辑而是线明明接错了你还在纠结为什么Python代码没反应。把这些基本习惯抓好Python控制开发板这件事真的没有想象中那么难。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑