ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Arduino实战:电位器控制SG90舵机并用OLED显示ADC值

Arduino实战:电位器控制SG90舵机并用OLED显示ADC值 做 Arduino 小项目时我经常遇到一个现象单个模块单独跑没有任何问题一旦组合起来就会出现“单独都会合在一起乱”的尴尬局面。今天做一个很经典的联动实验用电位器调节 SG90 舵机角度同时用 OLED 屏幕实时显示 ADC 采样值和当前角度。这个项目最大的价值不在于把三个模块接在一起点亮而在于帮你打通“模拟量采集 → 数据处理 → PWM 输出 → OLED 显示”这条完整链路。以后你做机械臂、小车、云台、智能仪表这类项目时大概率都要复用这套基本架构。本文会从硬件接线讲到代码调试再到常见问题排查尽量把每一步的“为什么”也说清楚方便你照着做也能快速排错。1. 项目背景与功能拆解1.1 这个项目解决什么问题很多初学者一开始接触 Arduino UNO会分别学到用analogRead()读取电位器模拟量用Servo库控制舵机旋转用 I2C 总线给 OLED 屏写入文字。但实际项目不会是“只会一个传感器”的简单形态。真实需求通常是系统里同时有输入、执行和反馈。比如一台桌面云台电位器是输入设备舵机是执行机构OLED 是状态反馈界面。三者协同工作才是一个最小可用的嵌入式交互系统。所以这个联动验证项目的核心价值有四个验证 Arduino UNO 的 ADC 采样功能是否正常验证 PWM 舵机控制是否稳定验证 OLED 的 I2C 初始化和刷新逻辑是否可靠验证采样、映射、输出、显示这条数据链路是否顺畅。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 对应 A5SDA 对应 A4。最后是 SG90。SG90 的接线颜色一般对应关系如下棕色线GND红色线VCC接 5V橙色线信号线接数字引脚。为了避免和 I2C 引脚冲突舵机信号线建议接在数字引脚 9。这个引脚由 Arduino Servo 库默认支持输出 PWM 时也比较稳定。下面是完整的接线表模块引脚Arduino UNO 引脚电位器左固定端GND电位器右固定端5V电位器中间滑片A0OLED VCC5VOLED GNDGNDOLED SCLA5OLED SDAA4SG90 棕色线GNDSG90 红色线5VSG90 橙色线数字引脚 92.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 OLED3.1 电位器与 ADC 采样Arduino UNO 使用的 ATmega328P 芯片内置了一个 10 位 ADC。所谓 10 位是指采样结果用 0~1023 的整数来表示共 1024 个等级。默认参考电压是 5V所以每个 ADC 读数对应的电压大约为电压 raw × 5.0 / 1023电位器本质上是一个可变电阻分压器。当滑片移动到某一端时A0 引脚读到的电压接近 0VADC 值接近 0当滑片移动到另一端时A0 引脚读到的电压接近 5VADC 值接近 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 开发板将 Arduino UNO 通过 USB 线连接到电脑打开 Arduino IDE在“工具 → 开发板”中选择“Arduino AVR Boards”下的“Arduino Uno”在“工具 → 端口”中选择对应的串口。如果没有在开发板列表中看到 Arduino Uno可能是 Arduino AVR Boards 平台还没有安装。可以在“开发板管理器”中搜索“Arduino AVR Boards”并安装。4.2 安装 U8g2 库代码里需要操作 OLED。本文使用 U8g2 库这是一个功能丰富、兼容性强、更新活跃的 OLED 图形库支持 SSD1306、SSD1309、SH1106 等大量屏幕驱动芯片。打开 Arduino IDE 的“库管理器”点击左侧菜单中的“库管理器”图标在搜索框中输入U8g2找到作者为 oliver 的U8g2库点击“安装”同时 Arduino Servo 库一般随 AVR 开发板平台自带不需要额外安装。需要注意库管理器里可能还会出现U8glib这是旧版本的前代库建议直接使用U8g2。两者 API 有一定差别网上搜索时要注意区分。4.3 验证开发板与库在菜单栏选择“文件 → 示例 → U8g2 → HelloWorld”打开 U8g2 自带的示例程序编译并烧录到 Arduino UNO。如果 OLED 屏幕上显示出 Hello World说明 OLED 接线、开发板环境和库安装都正确。如果这一步骤没有通过就先不要继续后面的代码优先排查接线、I2C 地址或屏幕驱动型号。5. 完整代码实现5.1 整体设计思路在编写代码前先明确主循环的数据流读取 A0 引脚的模拟电压值得到 0~1023 的原始数据使用map()函数把原始值映射到 0~180 的角度区间通过Servo库把角度值发送给 SG90 舵机将 ADC 原始值和映射后的角度值显示到 OLED 上在串口监视器中同步输出数据方便观察调试。为了减少电位器的随机抖动我会在代码中增加一个简单的均值滤波函数。每次生成角度前连续读取 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 对象使用硬件 I2Creset 引脚不用 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 后正常现象是缓慢旋转电位器舵机会跟随旋转当电位器旋到最左端角度应接近 0°当电位器旋到最右端角度应接近 180°OLED 第一行显示变化的 ADC 值范围大约在 0 到 1023OLED 第二行显示当前角度显示数字会在 0 到 180 之间变化进度条会随着角度变化伸长或缩短。如果 OLED 有显示舵机也在转动基本说明接线以及代码逻辑都是正确的。6.2 串口输出示例打开 Arduino IDE 的“串口监视器”波特率设置为 9600可以看到类似以下输出ADC0 Angle0 ADC0 Angle0 ADC152 Angle26 ADC412 Angle72 ADC620 Angle109 ADC880 Angle154 ADC1023 Angle180观察这个输出时重点关注两个问题ADC 值是否随之单调变化角度值是否和电位器旋转方向一致。如果电位器向右转时角度反而变小可以把电位器左右两端接线对调一下或者在代码中用180 - angleValue做反转。6.3 用 OLED 调试点位OLED 在本项目里既有“用户界面”功能也有“调试仪表”功能。比如舵机运行中偶尔会抖动串口打印数值可能看不出问题但 OLED 能直观地展示 ADC 原始值和角度值的变化关系。如果 ADC 值没有明显变化但舵机角度在抖动说明问题多半出在电源或 PWM 信号上而不是电位器本身。如果 ADC 值在某个位置跳变特别大比如从 500 跳到 600OLED 上能看到角度跳跃。这时就可以针对性做数字滤波或者检查这个电位器是不是碳膜磨损严重。这种调试思路比单纯看串口数据更直观。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 的电源指示灯是否正常亮起。舵机接的是不是 5VOLED 的 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/A5STM32CubeMX 初始化 OLED 时也需要通过 I2C 外设配置和地址参数保持一致。核心逻辑仍然是“读取电压 → 计算角度 → 输出 PWM → 刷新显示”。如果想继续验证 PWM 的精确特性可以不用 Servo 库直接使用myServo.writeMicroseconds()输出特定脉冲宽度观察不同脉宽和实际角度的对应关系。你会发现不同批次的舵机在 0° 和 180° 对应的脉宽可能有小幅偏差这也是实际项目里经常需要“校准最小脉宽”的原因。如果你是用 Arduino UNO 做入门学习最后再补充一个我实际踩过的很值得注意的细节尽量把 OLED 的 SDA/SCL 杜邦线缩短插紧一些。I2C 在短距离下很稳定但如果线太长或者接触不良屏幕上会出现随机乱码、花屏甚至完全不显示。很多时候问题不是代码写得不对而是物理连接没做好。动手跑通一次这个小项目之后你会发现 ADC、PWM 和 I2C 这三种单片机里最常见的通信方式都有了具象认识再回去看其他驱动代码会轻松许多。
返回列表