☰
Arduino实战:电位器控制SG90舵机并用OLED显示ADC值
2026/9/30 7:29:09 网站建设 项目流程

做 Arduino 小项目时,我经常遇到一个现象:单个模块单独跑没有任何问题,一旦组合起来,就会出现“单独都会,合在一起乱”的尴尬局面。今天做一个很经典的联动实验:用电位器调节 SG90 舵机角度,同时用 OLED 屏幕实时显示 ADC 采样值和当前角度。

这个项目最大的价值不在于把三个模块接在一起点亮,而在于帮你打通“模拟量采集 → 数据处理 → PWM 输出 → OLED 显示”这条完整链路。以后你做机械臂、小车、云台、智能仪表这类项目时,大概率都要复用这套基本架构。

本文会从硬件接线讲到代码调试,再到常见问题排查,尽量把每一步的“为什么”也说清楚,方便你照着做也能快速排错。

1. 项目背景与功能拆解

1.1 这个项目解决什么问题

很多初学者一开始接触 Arduino UNO,会分别学到:

  • 用analogRead()读取电位器模拟量;
  • 用Servo库控制舵机旋转;
  • 用 I2C 总线给 OLED 屏写入文字。

但实际项目不会是“只会一个传感器”的简单形态。真实需求通常是:系统里同时有输入、执行和反馈。比如一台桌面云台,电位器是输入设备,舵机是执行机构,OLED 是状态反馈界面。三者协同工作,才是一个最小可用的嵌入式交互系统。

所以,这个联动验证项目的核心价值有四个:

  1. 验证 Arduino UNO 的 ADC 采样功能是否正常;
  2. 验证 PWM 舵机控制是否稳定;
  3. 验证 OLED 的 I2C 初始化和刷新逻辑是否可靠;
  4. 验证采样、映射、输出、显示这条数据链路是否顺畅。

1.2 功能拆解

我们可以把整个项目拆成几个独立单元:

模块作用输入/输出
电位器产生 0~5V 的模拟电压信号输入到 A0 引脚
Arduino UNOADC 采样、角度映射、I2C 通信、PWM 生成数据处理中枢
SG90 舵机根据 PWM 脉宽旋转到指定角度接收数字引脚 9 的输出
OLED显示加速度值与角度值,提供可视化反馈接收 I2C 数据

在后续代码中,我会尽量把读取、转换、执行、显示拆成不同函数,这样代码逻辑更清晰,也方便以后扩展。

2. 硬件准备与接线

2.1 元件清单

这里使用的都是常用模块,门槛比较低:

  • Arduino UNO R3 开发板一块;
  • 10K 电位器一个;
  • SG90 舵机一个;
  • 0.96 寸 I2C OLED 屏一块,分辨率 128×64,驱动芯片 SSD1306;
  • 杜邦线若干;
  • 面包板一个(可选,方便固定接线);
  • 5V 外接电源或 USB 供电。

SG90 是非常常见的 9g 舵机,工作电压一般在 4.8V 到 6V 之间。Arduino UNO 的 5V 引脚在空载时可以提供约 400mA 左右电流,单纯带一个 SG90 在大多数轻负载场景下是够用的。如果发现舵机抖动、复位或者乱转,就要考虑改用外部 5V 电源给舵机供电,并且把电源地线与 Arduino 的 GND 共地。

2.2 接线关系

先看电位器。电位器通常有三个引脚,左右两个固定端分别接 GND 和 5V,中间引脚是滑片,输出电压会随着旋钮位置变化。把中间脚接到 Arduino UNO 的 A0。

再看 OLED。市面上常见的 0.96 寸 OLED 模块分为 I2C 和 SPI 两种版本。本文使用的是四针 I2C 版本:VCC、GND、SCL、SDA。Arduino UNO 的 I2C 引脚固定为:

  • SCL 对应 A5;
  • SDA 对应 A4。

最后是 SG90。SG90 的接线颜色一般对应关系如下:

  • 棕色线:GND;
  • 红色线:VCC,接 5V;
  • 橙色线:信号线,接数字引脚。

为了避免和 I2C 引脚冲突,舵机信号线建议接在数字引脚 9。这个引脚由 Arduino Servo 库默认支持,输出 PWM 时也比较稳定。

下面是完整的接线表:

模块引脚Arduino UNO 引脚
电位器左固定端GND
电位器右固定端5V
电位器中间滑片A0
OLED VCC5V
OLED GNDGND
OLED SCLA5
OLED SDAA4
SG90 棕色线GND
SG90 红色线5V
SG90 橙色线数字引脚 9

2.3 接线注意点

接线时最容易出现的问题是“模块插反”和“共地遗漏”。

OLED 模块的 VCC 和 GND 一旦接反,模块可能直接烧毁。接电之前最好拿万用表确认一下模块引脚定义,不同商家的丝印排版不一定完全相同。OLED 模块虽然大多数支持 3.3V~5V 供电,但不同批次芯片的内部稳压方案不同,最好以模块背面标注为准。如果对电压不放心,可以接 3.3V。

舵机控制时必须保持 Arduino 和舵机电源共地。如果外接电源只给舵机供电却不和 Arduino GND 连在一起,舵机不会正常工作。SG90 在堵转或启动瞬间电流较大,如果直接从 UNO 的 5V 引脚供电,可能会造成电压跌落,进而让 Arduino 自动复位。遇到这种情况,我的建议是在舵机电源两端并联一个 220μF 到 1000μF 的电解电容,或者干脆用外部 5V 电源供电。

3. 原理拆解:ADC、PWM 与 I2C OLED

3.1 电位器与 ADC 采样

Arduino UNO 使用的 ATmega328P 芯片内置了一个 10 位 ADC。所谓 10 位,是指采样结果用 0~1023 的整数来表示,共 1024 个等级。

默认参考电压是 5V,所以每个 ADC 读数对应的电压大约为:

电压 = raw × 5.0 / 1023

电位器本质上是一个可变电阻分压器。当滑片移动到某一端时,A0 引脚读到的电压接近 0V,ADC 值接近 0;当滑片移动到另一端时,A0 引脚读到的电压接近 5V,ADC 值接近 1023。

analogRead()返回的整数精度已经能满足本项目需求,不需要额外放大或滤波。不过在实际使用中,电位器旋转时 ADC 数值总会有一点小范围跳动。对精度要求较高的场景,可以连续采样几次,然后取平均值,这部分我会在第 8 节的工程改进中给出示例代码。

3.2 SG90 舵机与 PWM 控制

需要先明确一个概念:SG90 舵机虽然是三线接口,和普通直流电机的控制方式却并不一样。

舵机内部有一个反馈电位器和一个控制电路。外部只需要提供一个周期约 20ms 的 PWM 信号,舵机内部会根据信号的高电平脉宽来确定输出轴角度。常见的脉宽范围是:

  • 0.5ms 对应 0°;
  • 1.5ms 对应 90°;
  • 2.5ms 对应 180°。

Arduino 提供的 Servo 库帮我们封装了这些细节。当你调用myServo.write(90),库会计算出合适的 PWM 脉宽并自动连续变化到目标角度。

因此,通常不需要直接使用analogWrite()去驱动舵机。analogWrite()只是控制占空比,并不精确控制脉宽,用来驱动电机转速可以,但直接驱动舵机容易造成角度不准确或抖动。

3.3 SSD1306 OLED 与 I2C 显示

OLED 模块的核心是 SSD1306 驱动芯片。本文使用的 128×64 屏幕,屏幕上有 128×64 个像素点。与传统 LCD 使用背光不同,OLED 是每个像素独立发光,所以显示黑色时像素是不点亮的。

I2C 是一种两线制总线,只需要 SDA 数据线和 SCL 时钟线。在 Arduino UNO 上,I2C 硬件接口固定对应 A4 和 A5。OLED 作为 I2C 从设备,默认地址常见为 0x3C,也有些模块是 0x3D。

让 OLED 显示内容,通常需要先把要显示的文字、图形画进一个内存缓冲区,然后再一次性刷新到屏幕上。这样能避免刷新过程中闪烁,也能减少 I2C 总线占用。

4. 环境准备与依赖库

4.1 Arduino IDE 环境

本文示例仍然使用 Arduino IDE,这是 Arduino 开发的通用工具,支持 Windows、macOS 和 Linux。

在开发前需要先确认 IDE 已经正确识别 Arduino UNO 开发板:

  1. 将 Arduino UNO 通过 USB 线连接到电脑;
  2. 打开 Arduino IDE;
  3. 在“工具 → 开发板”中选择“Arduino AVR Boards”下的“Arduino Uno”;
  4. 在“工具 → 端口”中选择对应的串口。

如果没有在开发板列表中看到 Arduino Uno,可能是 Arduino AVR Boards 平台还没有安装。可以在“开发板管理器”中搜索“Arduino AVR Boards”并安装。

4.2 安装 U8g2 库

代码里需要操作 OLED。本文使用 U8g2 库,这是一个功能丰富、兼容性强、更新活跃的 OLED 图形库,支持 SSD1306、SSD1309、SH1106 等大量屏幕驱动芯片。

打开 Arduino IDE 的“库管理器”:

  1. 点击左侧菜单中的“库管理器”图标;
  2. 在搜索框中输入U8g2;
  3. 找到作者为 oliver 的U8g2库,点击“安装”;
  4. 同时 Arduino Servo 库一般随 AVR 开发板平台自带,不需要额外安装。

需要注意,库管理器里可能还会出现U8glib,这是旧版本的前代库,建议直接使用U8g2。两者 API 有一定差别,网上搜索时要注意区分。

4.3 验证开发板与库

在菜单栏选择“文件 → 示例 → U8g2 → HelloWorld”,打开 U8g2 自带的示例程序,编译并烧录到 Arduino UNO。如果 OLED 屏幕上显示出 Hello World,说明 OLED 接线、开发板环境和库安装都正确。

如果这一步骤没有通过,就先不要继续后面的代码,优先排查接线、I2C 地址或屏幕驱动型号。

5. 完整代码实现

5.1 整体设计思路

在编写代码前,先明确主循环的数据流:

  1. 读取 A0 引脚的模拟电压值,得到 0~1023 的原始数据;
  2. 使用map()函数把原始值映射到 0~180 的角度区间;
  3. 通过Servo库把角度值发送给 SG90 舵机;
  4. 将 ADC 原始值和映射后的角度值显示到 OLED 上;
  5. 在串口监视器中同步输出数据,方便观察调试。

为了减少电位器的随机抖动,我会在代码中增加一个简单的均值滤波函数。每次生成角度前,连续读取 10 次 ADC 值,然后取平均值。这样虽然会增加几毫秒延迟,但对舵机控制稳定性有帮助。

5.2 完整代码

创建一个新的 Arduino 工程,命名为pot_servo_oled,粘贴以下代码。

/* * 文件名:pot_servo_oled.ino * 功能:Arduino UNO 读取电位器数值, * 控制 SG90 舵机角度,并用 OLED 实时显示。 */ #include <Servo.h> #include <U8g2lib.h> // 引脚定义 #define POT_PIN A0 // 电位器中间引脚,接 A0 #define SERVO_PIN 9 // 舵机信号线,接数字引脚 9 // OLED 对象,使用硬件 I2C,reset 引脚不用 U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE); // 舵机对象 Servo myServo; // 简单均值滤波函数:读取 n 次并返回平均值 int readAverage(int pin, int count) { long total = 0; for (int i = 0; i < count; i++) { total += analogRead(pin); delay(1); // 等待 ADC 采样稳定 } return total / count; } // 更新 OLED 显示 void updateOLED(int rawValue, int angleValue) { u8g2.clearBuffer(); // 清空内存缓冲区 u8g2.setFont(u8g2_font_6x13_tr); // 设置字体 // 第一行:显示 ADC 原始值 u8g2.setCursor(0, 14); u8g2.print("ADC:"); u8g2.print(rawValue); // 第二行:显示舵机角度 u8g2.setCursor(0, 30); u8g2.print("Angle:"); u8g2.print(angleValue); u8g2.print(" deg"); // 第三行:画一个简单的进度条 u8g2.drawFrame(0, 40, 128, 12); // 外框 int barWidth = map(angleValue, 0, 180, 0, 126); u8g2.drawBox(1, 41, barWidth, 10); // 填充条 u8g2.sendBuffer(); // 把缓冲区一次性发送到屏幕 } void setup() { Serial.begin(9600); // 初始化 OLED u8g2.begin(); // 将舵机绑定到引脚 9 myServo.attach(SERVO_PIN); // 等待舵机稳定 delay(100); } void loop() { // 1. 读取电位器 ADC 值,连续采样 10 次取平均 int rawValue = readAverage(POT_PIN, 10); // 2. 把 0~1023 映射到 0~180 度 int angleValue = map(rawValue, 0, 1023, 0, 180); // 3. 保护角度范围,避免越界 angleValue = constrain(angleValue, 0, 180); // 4. 控制舵机旋转 myServo.write(angleValue); // 5. 刷新 OLED 显示 updateOLED(rawValue, angleValue); // 6. 串口输出,便于调试 Serial.print("ADC="); Serial.print(rawValue); Serial.print(" Angle="); Serial.println(angleValue); delay(20); }

5.3 代码关键点说明

readAverage()函数连续读取了 10 次 ADC 值。每次读取之间加了 1ms 延时,目的是让 ADC 采样保持稳定。最终返回一个平均值,可以明显降低电位器旋转时的数值跳动。

map(rawValue, 0, 1023, 0, 180)是 Arduino 的整数映射函数。map()并不会自动限制输出范围,所以我在后面又加了constrain(angleValue, 0, 180),确保角度不会出现负数或超过 180 的情况。

SG90 舵机的物理运动角度通常是 0° 到 180°,但不同批次的舵机可能存在差异。部分舵机往某个方向转动时,如果输入角度太大,会发出“嗡嗡”声并堵转。为了避免这个问题,实际项目中可以根据舵机测试结果,把映射输出范围缩小为 10° 到 170° 左右。

OLED 的刷新放在updateOLED()函数中。u8g2.clearBuffer()并不是直接清屏幕,而是清空内存缓冲区。u8g2.print()绘制的字符先存在缓冲区,最后通过u8g2.sendBuffer()一次性发送到屏幕。这种双缓冲机制能明显改善画面闪烁问题。

6. 运行效果与验证

6.1 预期现象

将代码编译烧录到 Arduino UNO 后,正常现象是:

  1. 缓慢旋转电位器,舵机会跟随旋转;
  2. 当电位器旋到最左端,角度应接近 0°;
  3. 当电位器旋到最右端,角度应接近 180°;
  4. OLED 第一行显示变化的 ADC 值,范围大约在 0 到 1023;
  5. OLED 第二行显示当前角度,显示数字会在 0 到 180 之间变化;
  6. 进度条会随着角度变化伸长或缩短。

如果 OLED 有显示,舵机也在转动,基本说明接线以及代码逻辑都是正确的。

6.2 串口输出示例

打开 Arduino IDE 的“串口监视器”,波特率设置为 9600,可以看到类似以下输出:

ADC=0 Angle=0 ADC=0 Angle=0 ADC=152 Angle=26 ADC=412 Angle=72 ADC=620 Angle=109 ADC=880 Angle=154 ADC=1023 Angle=180

观察这个输出时,重点关注两个问题:

  • ADC 值是否随之单调变化;
  • 角度值是否和电位器旋转方向一致。

如果电位器向右转时角度反而变小,可以把电位器左右两端接线对调一下,或者在代码中用180 - angleValue做反转。

6.3 用 OLED 调试点位

OLED 在本项目里既有“用户界面”功能,也有“调试仪表”功能。

比如舵机运行中偶尔会抖动,串口打印数值可能看不出问题,但 OLED 能直观地展示 ADC 原始值和角度值的变化关系。如果 ADC 值没有明显变化,但舵机角度在抖动,说明问题多半出在电源或 PWM 信号上,而不是电位器本身。

如果 ADC 值在某个位置跳变特别大,比如从 500 跳到 600,OLED 上能看到角度跳跃。这时就可以针对性做数字滤波,或者检查这个电位器是不是碳膜磨损严重。这种调试思路比单纯看串口数据更直观。

7. 常见问题与排查

7.1 问题速查表

问题现象常见原因解决思路
OLED 不亮或全黑接线错误、模块供电不足、I2C 地址不对确认 VCC/GND/SCL/SDA,重新插拔,扫描 I2C 地址
OLED 显示花屏SCL/SDA 接反、线太长、接触不良检查接线,缩短杜邦线,确保 SCL 到 A5、SDA 到 A4
舵机完全不动信号线接错、电源不足、Servo 库未正确 attach确认橙色线接数字引脚,检查舵机电源共地
舵机不断抖动或乱转供电电流不足、电位器采样跳动太大外部电源供电,加滤波,舵机电源端并联电容
电位器转到底但角度到不了 0 或 180电位器两端没有接 GND/5V,或映射范围不合理检查电位器接线,必要时调整 map 参数
上传程序后 Arduino 不断复位舵机启动瞬间电流过大导致 5V 电压跌落给舵机单独供电,不要长期堵转
编译提示找不到 U8g2lib.hU8g2 库未安装在库管理器安装 U8g2,确认拼写
程序能跑但 OLED 无任何文字缓冲区未发送或地址不对检查是否有 clearBuffer 和 sendBuffer,尝试 0x3D 地址

7.2 实操排查顺序

如果你做出来的现象和预期不一致,不要急着改代码,先从硬件开始排查。

第一步,检查供电。Arduino 的电源指示灯是否正常亮起。舵机接的是不是 5V,OLED 的 VCC 是否接反。

第二步,单独验证 OLED。注释掉舵机控制代码,烧录一个最简单的 OLED 显示程序。如果屏幕能显示,说明 OLED 和 I2C 没有问题;如果仍然不亮,先确认模块是不是 SSD1306,再看驱动型号是否匹配。

第三步,单独验证舵机。注释掉 OLED 刷新函数,通过串口发送一个固定角度值,比如 90°,然后观察舵机是否转动到中间位置。舵机能动,说明 PWM 引脚和 Servo 库正常。

第四步,再恢复完整代码。通常经历过前三步后,问题基本已经被定位到某一个具体模块上,接下来只需要仔细检查那一处接线或配置即可。

OLED 还有一个看似异常但正常的现象:SCL、SDA 接反后,屏幕不会显示内容,但模块背景不会像 LCD 那样明显变亮。OLED 本身没有独立背光,通电后即使屏幕没有内容,也可能是一块纯黑色。因此不要认为屏幕全黑就一定没有供电,优先确认是“没显示内容”还是“根本没上电”。判断方法很简单:如果模块通电后用手轻触屏幕表面,能感觉到轻微发热,说明供电通常已经正常。

8. 最佳实践与扩展建议

8.1 工程化改进

项目能跑起来之后,可以继续做一些工程化的优化,让代码更接近真实产品的状态。

第一,增加采样滤波。当前代码里我只做了固定 10 次采样取平均,这种方式简单有效,但如果采样次数太多,会让系统响应变慢。更合理的方式是设置一个“最小变化阈值”,只有角度变化超过一定数值才更新舵机和 OLED,这样可以减少细微抖动对舵机的频繁刺激。

下面的代码片段演示了“变化量过滤”的思路:

int lastAngle = -1; const int MIN_CHANGE = 2; void loop() { int rawValue = readAverage(POT_PIN, 10); int angleValue = map(rawValue, 0, 1023, 10, 170); angleValue = constrain(angleValue, 0, 180); // 只有角度变化超过阈值时才执行操作 if (abs(angleValue - lastAngle) >= MIN_CHANGE) { myServo.write(angleValue); updateOLED(rawValue, angleValue); lastAngle = angleValue; } delay(10); }

第二,在舵机负载较大的场景下,不建议使用 Arduino UNO 的 5V 引脚直接驱动舵机。最好使用外部稳压电源。外部电源的负极需要和 Arduino 的 GND 连接,确保参考电位一致。

第三,合理划分函数。显示、采样、控制逻辑最好分开,不要让loop()内堆积过多重复代码。这样即使以后更换屏幕或传感器,改动范围也能压缩到最小。

第四,接入看门狗或超时保护。比如舵机角度超过机械可动范围时,代码应自动限制输出范围。一般 SG90 的最大可控范围在 180° 附近,但实际安装机械结构时并不一定允许转到极限位置,限制角度可以有效避免舵机堵转发热。

8.2 进一步扩展方向

这个联动验证项目本身并不复杂,但它是一套很好的技术模板,可以从下面几个方向继续深入。

  • 增加按键校准功能。外接两个按键,分别记录当前电位器位置对应的最小角度和最大角度,这样更换电位器或舵机时无需改代码,使用体验更友好。
  • 显示更多参数。在 OLED 上增加一栏电压值显示,把 ADC 值换算成实际电压,比如rawValue * 5.0 / 1023,方便做电压采集类项目。
  • 增加多个预设角度记忆。按一下按键记录当前角度,下次上电时自动恢复到记录位置。
  • 把电位器换成其他模拟传感器,比如光敏电阻、摇杆模块或超声波距离传感器,复用这套“采集 → 映射 → 执行 → 显示”流程。
  • 把主控从 Arduino UNO 换到 ESP32 或 STM32 时,需要重点检查引脚差异。例如 ESP32 的 ADC 往往是 12 位,OLED 硬件 I2C 引脚不一定是 A4/A5;STM32CubeMX 初始化 OLED 时也需要通过 I2C 外设配置和地址参数保持一致。核心逻辑仍然是“读取电压 → 计算角度 → 输出 PWM → 刷新显示”。

如果想继续验证 PWM 的精确特性,可以不用 Servo 库,直接使用myServo.writeMicroseconds()输出特定脉冲宽度,观察不同脉宽和实际角度的对应关系。你会发现不同批次的舵机在 0° 和 180° 对应的脉宽可能有小幅偏差,这也是实际项目里经常需要“校准最小脉宽”的原因。

如果你是用 Arduino UNO 做入门学习,最后再补充一个我实际踩过的很值得注意的细节:尽量把 OLED 的 SDA/SCL 杜邦线缩短,插紧一些。I2C 在短距离下很稳定,但如果线太长或者接触不良,屏幕上会出现随机乱码、花屏甚至完全不显示。很多时候问题不是代码写得不对,而是物理连接没做好。

动手跑通一次这个小项目之后,你会发现 ADC、PWM 和 I2C 这三种单片机里最常见的通信方式都有了具象认识,再回去看其他驱动代码,会轻松许多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询