Arduino玩了大半年,回头翻翻自己的笔记和代码,发现已经攒了好几个能跑的小项目。这篇文章算是我入门阶段的真实记录,不是那种"三步教你学会某某"的速成教程,更像是一个过来人把弯路和惊喜都摊开给你看。内容主要围绕几个问题展开:怎么选板子、怎么写第一段能动手的代码,以及我做过的几个小东西——一个能显示温湿度、超阈值会响的报警小站,一个天黑自动合上的窗帘控制器,还有一个阳台自动浇花的小装置。如果你刚拿到一块Arduino板子不知道从哪下手,这篇应该能帮你省掉不少弯路;如果你已经做过几个项目,可以直接翻到最后的问题排查部分,说不定能对号入座。
我尽量把"为什么这么做"讲清楚,再给出可以直接抄的接线和代码。文章比较长,按入门顺序排开:先认识Arduino本身,然后是环境与编程基础,接着逐个项目拆解,最后是常见问题与排查技巧。代码和接线表都贴出来了,照着做就能跑起来,但要真正变成自己的东西,还是建议每个模块都亲手改一改、调一调。
1. 入门第一件事:想清楚Arduino是什么,以及怎么选板子
1.1 为什么入门选Arduino而不是其他板子
先说说我自己的理解。Arduino本质是一块单片机开发板,加上一整套好用的开发工具和库。它最大的价值不是某颗神乎其神的芯片,而是把"读传感器、控制电机、处理逻辑"这件事的门槛压得很低。比如你要让一个灯每隔一秒闪烁,用裸单片机得看数据手册、翻寄存器、配时钟,用Arduino只需要在setup和loop里写几行代码。
我更愿意把它理解成"硬件世界里的乐高":引脚就相当于插孔,传感器模块是现成的积木块,代码则是连接它们的卡扣。不需要懂复杂的电路原理,也能在半小时内让一个LED按你的心意亮灭。等玩明白了再往深处钻,那时再看寄存器、看原理图,反而容易多了。
不少人会把Arduino和树莓派放在一起比较。树莓派是一台完整的迷你电脑,能跑操作系统、能跑各种大程序,但它的强项不在实时控制上:开机要几十秒,系统更新的问题也多,一个引脚坏了整个板子都受影响。Arduino上电瞬间就开始跑程序,某个引脚坏了可以换一个脚重新定义,这对硬件折腾来说非常友好。对入门者来说,Arduino还有个隐形优势——生态资料极其丰富,你踩到的坑基本都有人踩过,搜一搜就能找到填坑记录。
1.2 板子怎么选:Uno、Nano,还是ESP32
我的第一块板子是Uno R3,后来才慢慢接触Nano和ESP8266。给新手的建议是:先买Uno。不是因为性能多好,而是因为它的引脚全部排开,和面包板配合很方便,而且绝大多数教程都默认用Uno,遇到问题好查。等做出来的东西想缩小体积,或者想装进小盒子里,可以换Nano,代码几乎不用改。
如果确定要联网,直接上ESP8266或ESP32。它们自带WiFi,性能也更强,但要留意绝大多数型号只有3.3V逻辑电平,而很多传感器模块是5V供电的,不能直接把3.3V引脚接到5V传感器上,得做电平匹配。另外ESP系列的库和传统Arduino有些差异,模拟输入脚的定义、串口数量等都不一样,代码移植时容易踩坑。
| 板卡 | 逻辑电平 | 典型用途 | 入门友好度 |
|---|---|---|---|
| Uno R3 | 5V | 通用学习、原型验证 | 最高 |
| Nano | 5V | 缩小体积、小项目 | 高 |
| ESP8266 | 3.3V | 联网数据采集 | 中 |
| ESP32 | 3.3V | 联网+复杂控制 | 中 |
如果是纯入门,不用纠结,直接Uno R3,等有具体需求了再换。
1.3 入门必备的配件清单
我踩过的一个弯路,是买了一整套"豪华配件包",里面塞了大量用不到的模块,真正有用的反而被挤在角落里。其实入门阶段的核心配件不算多:Arduino板子、面包板、杜邦线(公对公为主、母对母备用)、几颗按键、电阻包(尤其10k和220Ω)、LED、蜂鸣器、光敏电阻、DHT11温湿度模块、SG90舵机、0.96寸OLED屏、土壤湿度模块、继电器模块、小水泵。如果后面想做小车,再买电机驱动板,完全不用一开始就囤货。
杜邦线的质量非常影响体验。那种一拔一插就松垮的线,真的会让人崩溃到想砸板子。排查到最后,经常发现只是线接触不良。强烈建议买触点上带小弹片、插拔有阻尼感的线,虽然贵一点点,但能省下大量排查时间。
注意:面包板用久了弹片会氧化变松,同一个孔位插几次就开始接触不良。遇到诡异故障,先用另一根线换一个孔位试试,往往问题立消。
2. 环境搭建与编程基础:这些东西先弄明白再动手
2.1 IDE、驱动和板卡管理器
Arduino IDE直接到官网下载,新版2.x的体验已经不错了。装完IDE之后,第一项工作是装驱动:很多高仿Uno板用的是CH340芯片,需要手动装CH340驱动,否则电脑识别不到COM口。如果插上板子后设备管理器里出现黄色感叹号,不要急着重装IDE,先去把对应驱动补上。识别不到串口时,也先别怀疑板子坏了,USB线本身也可能只供电不传数据——我手头就有一根只能充电不能通信的线,坑过我好几次。
装非官方板卡支持(比如ESP)时,需要在首选项里添加开发板管理器网址,再从管理器里搜索安装。这一步看起来简单,但很多人失败在网址填错,或者下载到一半中断。解决办法是换一个源,或者找离线包,这属于环境问题,不用太纠结。
2.2 代码结构:setup和loop是怎么配合的
Arduino程序没有传统的main函数,入口就是setup()和loop()。setup只运行一次,用来做初始化:设定引脚模式、开启串口、启动传感器库。loop则是无限循环,持续执行主逻辑。第一次写代码,不建议直接抄整个项目,先写一个最简单的LED闪烁,把pinMode、digitalWrite、delay这三个函数吃透。
模拟输入输出是真正常用到的概念。analogRead读到的范围一般是0到1023,对应0到5V电压;analogWrite输出的是PWM波,范围0到255,可以让LED亮度渐变,也能让舵机转到特定角度。这里容易有个误解:所谓"模拟输出"并不是真正的连续电压,而是快速切换高低电平,靠惯性让人眼看起来像模拟量。理解这一点,后面遇到LED亮度不均匀、舵机在某角度抖动等问题时,就能想到是不是PWM频率或者占空比精度的问题。
延时方面,非敏感场景用delay是最方便的,但一旦涉及多任务——比如同时读传感器和驱动蜂鸣器——delay会把整个程序卡住,传感器刷新不了,按键也响应不了。替代方案是用millis()记录时间差,非阻塞地执行任务。入门阶段可以先靠delay把功能跑通,但我建议尽早习惯millis(),否则项目稍微复杂一点,改起来会非常痛苦。
2.3 按键输入:为什么非要上拉电阻
按键是入门第一个容易翻车的地方。数字输入引脚如果悬空,会接收到各种电磁干扰,读数忽高忽低,按键按下与否完全不可靠。解决办法是给引脚接一个上拉电阻到电源,这样没按键时引脚读到高电平,按下按键时读到低电平。Arduino内部已经内置了上拉电阻,在pinMode里使能INPUT_PULLUP就行,比外接电阻省事很多。
用一个简单类比:引脚就像一只怕吵的耳朵,上拉电阻相当于给它塞了耳塞,没有信号时它听到的是"安静"。如果不上拉,引脚听到的全是环境噪音,输出自然不稳定。
2.4 动手前先做引脚规划:这个习惯能省一半时间
接线前画一张引脚分配表,几乎是我最想安利给新手的习惯。看清哪些引脚带PWM(比如Uno上是3、5、6、9、10、11),哪些是I2C引脚(A4/A5),哪些是串口引脚(0/1),避免传感器抢脚。我就犯过一次把OLED插在I2C引脚上、结果程序里却按普通数字脚初始化的错误,屏幕死活不出字,排查了半天才发现线序插错位了。
画表的时候顺便把电平也标清楚:哪些模块要5V供电,哪些是3.3V逻辑,电源脚和地脚有没有跟别人共用。多花五分钟做这件事,后面能少折腾几个小时。
3. 我的几个小项目:从功能拆解到完整实现
3.1 小项目一:温湿度报警小站
这个项目的需求很简单:房间里的温湿度要能实时显示,超过设定阈值就报警。硬件只有三样:DHT11温湿度模块、0.96寸I2C OLED屏、有源蜂鸣器。
接线表:
| DHT11引脚 | Arduino引脚 |
|---|---|
| VCC | 5V |
| GND | GND |
| DATA | D2 |
| OLED引脚 | Arduino引脚 |
|---|---|
| VCC | 5V |
| GND | GND |
| SDA | A4 |
| SCL | A5 |
| 蜂鸣器引脚 | Arduino引脚 |
|---|---|
| 正极 | D8 |
| 负极 | GND |
代码:
#include <DHT.h> #include <U8g2lib.h> #define DHTPIN 2 #define DHTTYPE DHT11 #define BUZZER_PIN 8 DHT dht(DHTPIN, DHTTYPE); U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0); float tempLimit = 30.0; float humiLimit = 70.0; void setup() { Serial.begin(9600); dht.begin(); pinMode(BUZZER_PIN, OUTPUT); u8g2.begin(); } void loop() { float temp = dht.readTemperature(); float humi = dht.readHumidity(); if (isnan(temp) || isnan(humi)) { Serial.println("DHT11 read error"); return; } u8g2.clearBuffer(); u8g2.setFont(u8g2_font_6x10_tr); u8g2.setCursor(0, 12); u8g2.print("Temp: "); u8g2.print(temp); u8g2.print(" C"); u8g2.setCursor(0, 24); u8g2.print("Humi: "); u8g2.print(humi); u8g2.print(" %"); u8g2.sendBuffer(); if (temp > tempLimit || humi > humiLimit) { digitalWrite(BUZZER_PIN, HIGH); } else { digitalWrite(BUZZER_PIN, LOW); } delay(2000); }这是可以直接运行的完整代码,用库管理器搜索安装DHT sensor library和U8g2两个库就行。DHT11的读取间隔必须大于1秒,所以delay用了2000毫秒,读太快会返回NAN。
项目本身简单,但有个很典型的坑:DHT11插上电立刻读,返回的往往是NAN。解决方法是上电后等1秒以上,或者先读一次直接丢弃。另外,DHT11本来就是低精度传感器,温湿度波动在1℃和5%左右都算正常,别把它当精密仪器用。真需要精确测量,后面可以换DHT22或者SHT30,代码结构基本不用大改。
这个项目还能扩展:阈值改成按键可调、数据通过串口发给上位机、加一个继电器控制加湿器。我当时把报警逻辑改成晚上才响、白天不烦人,其实就是在loop里加一个时间段判断,非常简单,但做完特别有成就感。
3.2 小项目二:光控窗帘
这个项目的需求是:白天自动把窗帘拉开,天黑自动合上。核心就是光敏电阻检测环境亮度,舵机旋钮带动帘绳。硬件用了光敏电阻加一个10k电阻组成分压电路。光敏电阻的阻值随光照变化,光照越强阻值越小,模拟输入读到电压越高。我把光敏电阻一端接5V,另一端接A0,同时从这个点串联10k电阻到GND,这就把电阻变化转换成了电压变化。
接线表:
| 元件 | Arduino引脚 |
|---|---|
| 光敏电阻一端 | 5V |
| 光敏电阻另一端 | A0,同时经10k电阻到GND |
| SG90舵机信号线 | D9(PWM) |
| SG90电源线 | 5V(建议外部供电) |
| SG90地线 | GND(必须共地) |
代码:
#include <Servo.h> #define LIGHT_PIN A0 #define SERVO_PIN 9 Servo myServo; int openAngle = 0; int closeAngle = 150; int lightValue = 0; bool isOpen = true; void setup() { Serial.begin(9600); myServo.attach(SERVO_PIN); myServo.write(openAngle); } void loop() { lightValue = analogRead(LIGHT_PIN); Serial.print("light: "); Serial.println(lightValue); if (lightValue < 300 && isOpen) { myServo.write(closeAngle); isOpen = false; } else if (lightValue > 500 && !isOpen) { myServo.write(openAngle); isOpen = true; } delay(500); }调试的时候有几个点要认真对待。舵机第一次上电往往会抖一下,这是它在自动校准初始角度,不是程序问题,但如果在极限角度上电,舵机可能会使劲顶着结构,造成堵转发热。每次上电前先用手抓住舵盘转一转,确认它没有顶着东西,再固定安装。
光敏电阻的阈值不是一成不变的,阴天和晴天的读数差别很大,房间灯光也会有影响。我先把Serial输出打开,观察一整天的实际读数变化,再确定阈值。代码里特意留了滞回区间:低于300才关窗帘,高于500才开窗帘,中间不动。没有滞回区间的话,窗帘会在临界光照下反复抽搐,这是我实际调试时亲眼见到的场面,非常滑稽。
舵机供电要特别注意。SG90标称电流不大,但快速动作或堵转瞬间电流很高,细杜邦线、劣质面包板都会导致电压跌落,舵机表现成没力、抖动。更稳妥的方案是外部5V电源单独给舵机供电,Arduino板子和舵机之间只共用GND,信号线照常接。共地的原因很简单:两个电源必须有公共参考点,信号才能被正确识别——不共地,信号回不来。
3.3 小项目三:阳台自动浇花装置
这个项目解决的是出门几天、阳台花没人浇的问题。用土壤湿度模块检测土壤含水量,太干就启动水泵浇水,浇够了自动停。硬件列表:土壤湿度模块、继电器模块、小水泵。土壤湿度模块有两根探针插进土里,电阻变化转为电压,我用A1读取模拟值。模块上通常还有一个小电位器,用来调灵敏度。
接线表:
| 元件 | Arduino引脚 |
|---|---|
| 土壤湿度模块VCC | 5V |
| 土壤湿度模块GND | GND |
| 土壤湿度模块A0 | A1 |
| 继电器模块IN | D6 |
| 继电器模块供电 | 5V和GND(看模块类型) |
| 水泵 | 继电器常开端接电源正极,公共端接电源负极 |
这里有一个非常关键的坑:很多继电器模块其实是低电平触发,IN引脚为低电平时继电器才吸合。如果代码里写反,就会变成"土壤很湿的时候反而一直在浇水"。买模块时先看清楚丝印说明,或者最开始用一个LED接在继电器输出端做测试,确认触发逻辑再连水泵。
水泵的控制逻辑不复杂,但一定要加一个最大浇水时长保护。防止土壤湿度探头松动、读数一直很干,水泵无限工作把花淹了。我在代码里限制每次浇水最多10秒,之后至少等30分钟才能再次浇水。
#define SOIL_PIN A1 #define RELAY_PIN 6 #define MAX_WATER_SEC 10 #define CHECK_INTERVAL_MIN 30 unsigned long lastCheckTime = 0; unsigned long waterStartTime = 0; bool isWatering = false; void setup() { Serial.begin(9600); pinMode(RELAY_PIN, OUTPUT); digitalWrite(RELAY_PIN, HIGH); // 取决于继电器是低电平触发还是高电平触发 } void loop() { unsigned long now = millis(); if (now - lastCheckTime < CHECK_INTERVAL_MIN * 60000UL) { return; } lastCheckTime = now; int soilValue = analogRead(SOIL_PIN); Serial.print("soil: "); Serial.println(soilValue); if (isWatering) { if (now - waterStartTime >= MAX_WATER_SEC * 1000UL) { digitalWrite(RELAY_PIN, HIGH); isWatering = false; Serial.println("stop watering"); } return; } if (soilValue > 600) { digitalWrite(RELAY_PIN, LOW); waterStartTime = now; isWatering = true; Serial.println("start watering"); } }注意:不同模块的数值含义可能相反,有的模块读数越大表示越干,有的反之。接线后务必先打印裸读数,用手摸一下探头——湿手和干手分别看读数变化——确定判断方向,再写逻辑。
继电器模块也分两种,一种自带光耦隔离,一种是普通继电器。做水泵这种带感性负载的控制,我更推荐带光耦的模块,可以避免水泵启动或停止瞬间的反电动势干扰单片机。水泵本身是直流电机,堵转会发热,水管如果被折住了,很容易烧水泵。这个经验我一定要记在这里:小水泵最怕的不是电路问题,而是管路问题。装机前把水管理顺,确保水位高于进水口,比调什么代码都重要。
3.4 几个项目的共性和扩展思路
这三个项目放在一起,能组出不少新东西。温湿度报警加上联网模块,就变成远程温湿度监测;光控窗帘把光敏电阻换成环境光传感器,可以联动室内灯光亮度;自动浇花把定时逻辑改成根据天气接口调整,就更聪明了。
如果想实现数据传到手机,可以先不用一上来就学复杂的ESP32开发。更平滑的路径是:用一块ESP8266模块通过串口和Arduino通信,Arduino负责采集传感器数据,ESP8266负责WiFi收发,串口协议自己定。这个方案的优点是把"采集"和"通信"分离,调试起来可以一件事一件事地处理,出问题容易定位。
4. 常见问题与排查技巧实录
4.1 编译报错:锁定第一行,别被海量报错吓到
编译期最常见的问题是库冲突和重复定义。一个项目里同时装了两个功能重叠的库,容易出现xx was not declared或redefinition的错误。解决办法:优先用官方库管理器安装库,少用手动下载Zip;同一个项目不要引入两个功能类似的库。遇到报错,看第一行错误信息,它往往会直接指向文件和函数名,后面几百行往往只是连锁反应。
另一类是选错开发板类型。写了AVR板的代码跑到ESP板,模拟引脚号定义不同,自然报错。出现这类问题,先检查Tools菜单里的Board选择是否正确,再看端口配置。
4.2 上传失败:按顺序排查,不信邪
上传失败的原因很多:COM口选错、板子没有进入bootloader、驱动没装好。正确顺序是:先看设备管理器有没有对应COM口,IDE里是否选对端口;再换一根USB线试试,有些线只能供电不能通信;最后在上传前按一下板子上的复位键,时机要卡在IDE提示"上传中"出现之前。如果还是不行,把手边能换的都换一遍——USB口、电脑、甚至换一块板子——逐个变量排除。
程序一旦启动就死机,或者串口监视器里不断打印垃圾信息,大概率是波特率不对。Serial.begin(9600)和串口监视器右下角的波特率必须一致,否则出来就是乱码。
4.3 传感器读数不稳定:先查电源、再查共地、最后怀疑传感器
传感器读数乱跳,我的排查顺序是固定的:先用万用表测模块VCC端的实际电压,很多5V模块在高负载下实际只有4.6V,稳压不足,读数自然飘;再检查共地,仔细看所有模块的GND是否都连到了同一个公共点;然后检查杜邦线是否松动。都没问题,才考虑传感器本身坏了。
DHT11这类传感器容易踩的坑更特殊:读取太快、上电立刻读、数据引脚离电源线太近被干扰。以前我以为是模块坏了,后来发现是数据线太长、旁边又有电机在工作,信号被干扰了。把线缩短、远离电机线,问题就消失了。
| 现象 | 排查顺序 | 常见原因 |
|---|---|---|
| 读数乱跳 | 电压→共地→接线→传感器 | 供电不足、线松、干扰 |
| 读数恒为某值 | 换引脚→换模块 | 引脚烧坏、模块损坏 |
| 读数总是NAN | 读取间隔→上电时间→接线顺序 | DHT11读太快、上电即读 |
4.4 一个排查实例:蜂鸣器一直响
我在做温湿度报警时遇到过一个问题:蜂鸣器一上电就响,程序逻辑明明没有触发报警条件。排查过程供参考:
先用一段最简代码强制digitalWrite(D8, LOW),把蜂鸣器关闭。如果关闭不了,说明不是代码逻辑问题,而是硬件问题。然后用万用表量蜂鸣器两端电压,发现D8引脚输出正常,但蜂鸣器仍然响。最后拆下蜂鸣器一看,正负极接反了——有源蜂鸣器正极本来应该接D8,负极接GND,我把它插反了,结果变成了正极接GND、负极接D8,工业逻辑下一样能响,但控制信号就反了。
这个案例想说明的排查思路是:用最简单的代码隔离软件问题,用万用表验证硬件问题,最后才怀疑板子本身。从软件到硬件逐层排除,大部分故障都能定位,不要一开始就认为是板子坏了。
5. 写在最后,回到Arduino入门这件事
入门Arduino,最难的不是背语法,而是建立一种"硬件回路思维":每个模块都有电源脚、地脚、信号脚,信号要么是高电平、低电平,要么是模拟量,程序要做的事情就是把这些信号读进来、算一算、再输出控制。一旦想通了这一点,换什么传感器心里都有底,因为底层逻辑都是相通的。
我的建议是,别做那种"复制教程代码进去、灯亮了就完事"的项目,一定要给自己加需求。我做完温湿度报警之后,给自己加了一个"晚上自动静音"的规则,就逼着自己去学怎么判断时间、怎么处理用户按键。项目不一定要大,但要有变化、要有坑。坑踩多了,自然就会了。
后面我打算把几个小项目整合成一个"阳台管家":光照控制遮阳帘,土壤湿度控制浇水,温湿度控制通风。控制器从Uno换成ESP32,数据想办法推到手机里。硬件这条路,入门之后才算真正开始。