arduino扩展:Arduino Mega 控制 32 个舵机(参考表情机器人)

参考:表情机器人中使用22个舵机的案例

引言

在电子制作与自动化控制领域,Arduino 凭借其易用性和强大的扩展性备受青睐。Arduino Mega 作为其中功能较为强大的一款开发板,具备丰富的引脚资源,能够实现复杂的控制任务。舵机作为常见的执行机构,可精确控制角度,广泛应用于机器人、模型制作等场景。本文将深入探讨如何使用 Arduino Mega 控制 32 个舵机,详细分析硬件连接、代码实现,并对项目进行拓展思考。

硬件连接

材料准备

  • Arduino Mega 开发板:它拥有 54 个数字输入输出引脚,其中 15 个可提供 PWM 输出,这为同时控制多个舵机提供了硬件基础。
  • 32 个舵机:舵机通常有三根线,分别是电源线(VCC,一般为红色)、地线(GND,一般为黑色或棕色)和信号线(PWM,一般为橙色或黄色)。
  • 杜邦线:用于连接 Arduino Mega 和舵机,保证信号和电源的传输。
  • 电源模块:由于 32 个舵机同时工作时功耗较大,Arduino Mega 的板载电源可能无法满足需求,因此需要一个外部电源模块为舵机供电。

连接方式

  • 电源连接:将所有舵机的电源线(VCC)连接到外部电源的正极,所有舵机的地线(GND)连接到外部电源的负极和 Arduino Mega 的 GND 引脚,确保共地。这样可以避免电源干扰,保证系统的稳定性。
  • 信号连接:从 Arduino Mega 的 22 号引脚开始,依次将 32 个舵机的信号线(PWM)连接到 22 - 53 号引脚。在代码中,会将这些引脚与对应的舵机对象进行关联,从而实现对每个舵机的精确控制。
    在这里插入图片描述

硬件连接注意事项

  • 电源供应:确保外部电源能够提供足够的电流,以满足 32 个舵机同时工作的需求。否则,可能会出现舵机转动无力、抖动等问题。
  • 引脚使用:避免将其他设备连接到 22 - 53 号引脚,以免与舵机控制产生冲突。
  • 线路布局:合理规划杜邦线的布局,避免线路交叉和缠绕,减少信号干扰。

代码实现

代码整体功能概述

这段代码实现了使用 Arduino Mega 控制 32 个舵机,并让它们按照预设的四个不同序列进行运动,展示出多样化的动作效果。

代码详细分析

库的引入和变量定义
#include <Servo.h>
#define NUM_SERVOS 32
Servo myServo[NUM_SERVOS];
  • #include <Servo.h>:引入 Servo 库,该库提供了控制舵机的基本函数和方法。
  • #define NUM_SERVOS 32:定义常量 NUM_SERVOS 为 32,表示要控制的舵机数量。
  • Servo myServo[NUM_SERVOS];:创建一个包含 32 个 Servo 对象的数组 myServo,用于管理每个舵机。
setup() 函数
void setup() 
{for( int i=0; i<NUM_SERVOS; i++){myServo[i].attach( i + 22);     }
}
  • setup() 函数中,通过 for 循环将每个 Servo 对象与 Arduino Mega 开发板上从 22 到 53 的引脚依次连接起来。attach() 函数用于将舵机对象与特定的引脚关联,以便后续向该引脚发送 PWM 信号来控制舵机的角度。
loop() 函数

loop() 函数会不断循环执行,其中包含了四个不同的舵机运动序列。

序列一:随机角度运动
for( int a=0; a<15; a++)
{for( int i=0; i<NUM_SERVOS; i++){myServo[i].write( random( 0, 181));delay( 2);}delay( 150);
}
  • 该序列会循环 15 次。每次循环中,使用 random(0, 181) 函数为每个舵机生成一个 0 到 180 之间的随机角度,并通过 write() 函数将该角度发送给对应的舵机。delay(2) 用于在设置每个舵机角度后稍微延迟一下,让舵机有时间响应。delay(150) 用于在完成一次所有舵机角度设置后,暂停 150 毫秒,方便观察效果。
序列二:同步角度运动
for( int i=0; i<NUM_SERVOS; i++)
{myServo[i].write( 0);            
}
delay( 1000);                      
for( int a=0; a<3; a++)
{for( int r=0; r<=180; r++)       {for( int i=0; i<NUM_SERVOS; i++){myServo[i].write( r);}delay( 6);}for( int r=180; r>=0; r--){for( int i=0; i<NUM_SERVOS; i++)  {myServo[i].write( r);}delay( 6);}
}
  • 首先,将所有舵机的角度设置为 0 度,即初始位置(舵机臂向左旋转),并延迟 1000 毫秒,让观察者适应这个状态。
  • 然后,循环 3 次,每次先将所有舵机从 0 度逐步旋转到 180 度(向右旋转),再从 180 度逐步旋转回 0 度(向左旋转)。delay(6) 用于控制舵机旋转的速度,让舵机有足够的时间转动到指定角度。
序列三:旋转波运动
for( int a=0; a<6; a++)
{for( int i=0; i<NUM_SERVOS; i++){for( int j=0; j<NUM_SERVOS; j++){int d = j - i;if( d < 0)d = -d;if( d > (NUM_SERVOS / 2))d = NUM_SERVOS - d;int angle = 90 - (10 * d);if( angle < 0)angle = 0;myServo[j].write( angle);}delay(40);}
}
  • 该序列会循环 6 次。对于每个舵机 i,会计算其他舵机 j 与它的距离 d,并根据距离计算出对应的角度 angle。距离越近,角度越接近 90 度;距离越远,角度越小。通过不断改变 i 的值,形成一个旋转的波状运动效果。delay(40) 用于控制波的传播速度。
序列四:“罗盘”运动
int pointer = NUM_SERVOS * 3 / 4;
showPointer( pointer);
delay( 1000);                       
for( int i=0; i<5; i++)
{showPointer( --pointer);delay( 150);
}
delay( 200);
// 后续还有多个类似的循环,省略部分代码
  • 首先,将指针 pointer 初始化为 NUM_SERVOS * 3 / 4,并调用 showPointer() 函数显示初始状态,延迟 1000 毫秒让观察者适应。
  • 然后,通过多次调用 showPointer() 函数,让指针按照一定的规律左右移动,模拟罗盘指针的转动效果。每次移动后延迟 150 毫秒,控制转动速度。
showPointer() 函数
void showPointer( int s)
{int pointerA = s % NUM_SERVOS;        int pointerB = (s + 1) % NUM_SERVOS;  int tailA = (s + 16) % NUM_SERVOS;int tailB = (s + 17) % NUM_SERVOS;myServo[pointerA].write(180-56);myServo[pointerB].write(56);myServo[tailA].write(95);myServo[tailB].write(85);int n = (NUM_SERVOS / 2) - 2;int start = pointerB + 1;for( int i=0; i<n; i++){int j = (start + i) % NUM_SERVOS;myServo[j].write( 2);}start = tailB + 1;for( int i=0; i<n; i++){int j = (start + i) % NUM_SERVOS;myServo[j].write( 178);}
}
  • 该函数用于创建“罗盘”效果。通过计算指针、尾部以及左右两侧舵机的索引,将它们设置为不同的角度,形成一个类似指针的形状。左右两侧的舵机分别设置为 2 度和 178 度,突出指针的效果。
GenerateDiagram() 函数
void GenerateDiagram()
{Serial.begin(115200);// 后续是生成 JSON 格式文本的代码,省略部分代码
}
  • 这个函数用于生成 Wokwi 模拟器所需的 diagram.json 文件。通过串口输出 JSON 格式的文本,包含了 Arduino Mega 和 32 个舵机的位置、连接等信息。

全部代码

// ServoOverdone.ino
//
// Example for multiple Servo objects in a array.
//
// Version 1, 28 July 2021, by Koepel.
// Version 2, 15 August 2021, by Koepel.
//   changed timing, a little slower
//   diagram.json has servos in reverse order (I think it is visually better)
//   Added fourth sequence: "compass"
//
// Public Domain
//#include <Servo.h>#define NUM_SERVOS 32
Servo myServo[NUM_SERVOS];void setup() 
{// Attach pins from the Arduino Mega board to the Servo objects.// Starting from pin 22, there happen to be exactly 32 pins on the double row pins.for( int i=0; i<NUM_SERVOS; i++){myServo[i].attach( i + 22);      // pin 22 up to 53 is 32 pins}
}void loop() 
{// Sequence one.// All servo motor are set to a random angle.for( int a=0; a<15; a++){for( int i=0; i<NUM_SERVOS; i++){myServo[i].write( random( 0, 181));delay( 2);}delay( 150);}// Sequence two.// All servo motors move with the same angle.for( int i=0; i<NUM_SERVOS; i++){myServo[i].write( 0);            // set to begin position (horn is rotated left)}delay( 1000);                      // wait to let the viewer get used to itfor( int a=0; a<3; a++){for( int r=0; r<=180; r++)       // move horns to the right{for( int i=0; i<NUM_SERVOS; i++){myServo[i].write( r);}delay( 6);}for( int r=180; r>=0; r--){for( int i=0; i<NUM_SERVOS; i++)  // move horns to the left{myServo[i].write( r);}delay( 6);}}// Sequence three.// A rotating wave.for( int a=0; a<6; a++){for( int i=0; i<NUM_SERVOS; i++){for( int j=0; j<NUM_SERVOS; j++){// Calculate distance to active servoint d = j - i;if( d < 0)d = -d;if( d > (NUM_SERVOS / 2))d = NUM_SERVOS - d;int angle = 90 - (10 * d);if( angle < 0)angle = 0;myServo[j].write( angle);}delay(40);}}// Sequence four.// A "compass"// Start by pointing upwardsint pointer = NUM_SERVOS * 3 / 4;showPointer( pointer);delay( 1000);                       // let the viewer get used to new patternfor( int i=0; i<5; i++){showPointer( --pointer);delay( 150);}delay( 200);for( int i=0; i<9; i++){showPointer( ++pointer);delay( 150);}delay( 200);for( int i=0; i<5; i++){showPointer( --pointer);delay( 150);}delay( 200);for( int i=0; i<4; i++){showPointer( ++pointer);delay( 150);}delay( 160);for( int i=0; i<2; i++){showPointer( --pointer);delay( 150);}delay( 80);for( int i=0; i<1; i++){showPointer( ++pointer);delay( 150);}delay( 2000);
}// This function makes a "pointer" with the servos.
// It is used to create the "compass".
// The parameter 's' is the servo motor that has the pointer.
// It is allowed that 's' is below zero or larger than the numbers of servo motors.
void showPointer( int s)
{int pointerA = s % NUM_SERVOS;        // Using the '%' (remainder) for valid numberint pointerB = (s + 1) % NUM_SERVOS;  // pointer is made with the next servo motorint tailA = (s + 16) % NUM_SERVOS;int tailB = (s + 17) % NUM_SERVOS;// make pointer with servo motor s and s+1.myServo[pointerA].write(180-56);myServo[pointerB].write(56);// make tail with servo motor s+16 and s+17.myServo[tailA].write(95);myServo[tailB].write(85);// Set servos right of pointerint n = (NUM_SERVOS / 2) - 2;int start = pointerB + 1;for( int i=0; i<n; i++){int j = (start + i) % NUM_SERVOS;myServo[j].write( 2);}// Set servos left of pointerstart = tailB + 1;for( int i=0; i<n; i++){int j = (start + i) % NUM_SERVOS;myServo[j].write( 178);}
}// The function GenerateDiagram() can be used to generate
// the diagram.json file for Wokwi.
// To use it, call it from the setup() function, and the
// serial output can be copied into the diagram.json file.
void GenerateDiagram()
{Serial.begin(115200);Serial.print( "{\n");Serial.print( "  \"version\": 1,\n");Serial.print( "  \"author\": \"Generated\",\n");Serial.print( "  \"editor\": \"wokwi\",\n");Serial.print( "  \"parts\": [\n");Serial.print( "    {\n");Serial.print( "      \"type\": \"wokwi-arduino-mega\",\n");Serial.print( "      \"id\": \"mega\",\n");Serial.print( "      \"top\": 270,\n");Serial.print( "      \"left\": 185,\n");Serial.print( "      \"attrs\": {}\n");Serial.print( "    },\n");// Put the servo motor in reverse order in the diagram.json// I think that is visually better.// The horn now overlaps the next servo when the horn moves to the right.for( int i=NUM_SERVOS-1; i>=0; i--){float rotate = float( i) * (360.0 / float( NUM_SERVOS));float rad = rotate / 360.0 * 2.0 * M_PI;float top = (300.0 * sin( rad)) + 300.0;float left = (300.0 * cos( rad)) + 300.0;Serial.print( "    {\n");Serial.print( "      \"type\": \"wokwi-servo\",\n");Serial.print( "      \"id\": \"servo");Serial.print( i);Serial.print( "\",\n");Serial.print( "      \"top\": ");Serial.print( top);Serial.print( ",\n");Serial.print( "      \"left\": ");Serial.print( left);Serial.print( ",\n");Serial.print( "      \"rotate\": ");Serial.print( rotate);Serial.print( ",\n");Serial.print( "      \"attrs\": { \"hornColor\": \"Red\" }\n");Serial.print( "    }");if( i != 0)Serial.print( ",");Serial.print( "\n");} Serial.print( "  ],\n");Serial.print( "  \"connections\": [\n");for( int i=0; i<NUM_SERVOS; i++){int j = i + 1;if( j == NUM_SERVOS)j = 0;Serial.print( "    [ \"servo");Serial.print( i);Serial.print( ":V+\", \"servo");Serial.print( j);Serial.print( ":V+\", \"Red\", [] ],\n");Serial.print( "    [ \"servo");Serial.print( i);Serial.print( ":GND\", \"servo");Serial.print( j);Serial.print( ":GND\", \"Black\", [] ],\n");Serial.print( "    [ \"mega:");Serial.print( i + 22);Serial.print( "\", \"servo");Serial.print( i);Serial.print( ":PWM\", \"Green\", [ ] ],\n");}Serial.print( "    [ \"mega:GND.2\", \"servo9:GND\", \"Black\", [ ] ],\n");Serial.print( "    [ \"mega:5V\", \"servo9:V+\", \"Red\", [ ] ]\n");Serial.print( "  ]\n");Serial.print( "}\n");
}

拓展与思考

功能拓展

  • 传感器结合:可以添加各类传感器,如超声波传感器、红外传感器等,根据传感器的数据动态调整舵机的运动。例如,当超声波传感器检测到前方有障碍物时,舵机可以控制机器人改变方向。
  • 远程控制:通过无线通信模块(如蓝牙、Wi-Fi)实现对舵机的远程控制。用户可以使用手机应用或电脑程序发送指令,让舵机执行特定的动作。
  • 增加舵机数量:虽然 Arduino Mega 有 54 个数字引脚,但受限于电源和处理能力,同时控制更多舵机可能会遇到挑战。可以考虑使用舵机驱动板(如 PCA9685)来扩展舵机控制数量。

代码优化

  • 减少延迟:代码中使用了大量的 delay() 函数,这会阻塞程序的执行,影响系统的实时性。可以使用定时器或状态机的方式来替代 delay() 函数,提高代码的效率。
  • 模块化设计:将不同的舵机运动序列封装成独立的函数,提高代码的可读性和可维护性。同时,方便添加新的运动序列。

总结

通过本文的介绍,我们详细了解了如何使用 Arduino Mega 控制 32 个舵机。从硬件连接的注意事项到代码实现的各个细节,都进行了深入分析。同时,对项目的拓展和代码优化也提出了一些思路。这个项目不仅展示了 Arduino Mega 的强大功能,也为我们在电子制作和自动化控制领域的进一步探索提供了基础。在实际应用中,我们可以根据具体需求对硬件和代码进行调整和扩展,创造出更加复杂和有趣的项目。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/70920.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PyQt学习记录03——批量设置水印

0. 目录 PyQt学习记录01——加法计算器 PyQt学习记录02——串口助手 1. 前言 本次主要是为了学习Qt中的 QFileDialog 函数&#xff0c; QFileDialog.getExistingDirectory&#xff1a;用于选择文件夹&#xff0c;返回的是一个文件夹路径。 QFileDialog.getOpenFileName&…

Visual Studio 使用 “Ctrl + /”键设置注释和取消注释

问题&#xff1a;在默认的Visual Studio中&#xff0c;选择单行代码后&#xff0c;按下Ctrl /键会将代码注释掉&#xff0c;但再次按下Ctrl /键时&#xff0c;会进行双重注释&#xff0c;这不是我们想要的。 实现效果&#xff1a;当按下Ctrl /键会将代码注释掉&#xff0c;…

社区版IDEA中配置TomCat(详细版)

文章目录 1、下载Smart TomCat2、配置TomCat3、运行代码 1、下载Smart TomCat 由于小编的是社区版&#xff0c;没有自带的tomcat server&#xff0c;所以在设置的插件里面搜索&#xff0c;安装第一个&#xff08;注意&#xff1a;安装时一定要关闭外网&#xff0c;小编因为这个…

Flink-DataStream API

一、什么样的数据可以用于流式传输 Flink的DataStream API 允许流式传输他们可以序列化的任何内容。Flink自己的序列化程序用于 基本类型&#xff1a;即字符串、长、整数、布尔值、数组复合类型&#xff1a;元组、POJO和Scala样例类 基本类型我们已经很熟悉了&#xff0c;下…

渗透利器:Burp Suite 联动 XRAY 图形化工具.(主动扫描+被动扫描)

Burp Suite 联动 XRAY 图形化工具.&#xff08;主动扫描被动扫描&#xff09; Burp Suite 和 Xray 联合使用&#xff0c;能够将 Burp 的强大流量拦截与修改功能&#xff0c;与 Xray 的高效漏洞检测能力相结合&#xff0c;实现更全面、高效的网络安全测试&#xff0c;同时提升漏…

Java 大视界 -- 深入剖析 Java 在大数据内存管理中的优化策略(49)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

JVM ②-双亲委派模型 || 垃圾回收GC

这里是Themberfue 在上节课对内存区域划分以及类加载的过程有了简单的了解后&#xff0c;我们再了解其他两个较为重要的机制&#xff0c;这些都是面试中常考的知识点&#xff0c;有必要的话建议背出来&#xff0c;当然不是死记硬背&#xff0c;而是要有理解的背~~~如果对 JVM …

html文件怎么转换成pdf文件,2025最新教程

将HTML文件转换成PDF文件&#xff0c;可以采取以下几种方法&#xff1a; 一、使用浏览器内置功能 打开HTML文件&#xff1a;在Chrome、Firefox、IE等浏览器中打开需要转换的HTML文件。打印对话框&#xff1a;按下CtrlP&#xff08;Windows&#xff09;或CommandP&#xff08;M…

2025 西湖论剑wp

web Rank-l 打开题目环境&#xff1a; 发现一个输入框&#xff0c;看一下他是用上面语言写的 发现是python&#xff0c;很容易想到ssti 密码随便输&#xff0c;发现没有回显 但是输入其他字符会报错 确定为ssti注入 开始构造payload&#xff0c; {{(lipsum|attr(‘global…

Web前端开发--HTML

HTML快速入门 1.新建文本文件&#xff0c;后缀名改为.html 2.编写 HTML结构标签 3.在<body>中填写内容 HTML结构标签 特点 1.HTML标签中不区分大小写 2.HTML标签属性值中可以使用单引号也可使用双引号 3.HTML语法结构比较松散&#xff08;但在编写时要严格一点&…

网络工程师 (30)以太网技术

一、起源与发展 以太网技术起源于20世纪70年代&#xff0c;最初由Xerox公司的帕洛阿尔托研究中心&#xff08;PARC&#xff09;开发。最初的以太网采用同轴电缆作为传输介质&#xff0c;数据传输速率为2.94Mbps&#xff08;后发展为10Mbps&#xff09;&#xff0c;主要用于解决…

ONES 功能上新|ONES Copilot、ONES TestCase、ONES Wiki 新功能一览

ONES Copilot 支持基于当前查看的工作项相关信息&#xff0c;利用 AI 模型&#xff0c;在系统中进行相似工作项的查找&#xff0c;包括基于已关联工作项的相似数据查找。 应用场景&#xff1a; 在查看工作项时&#xff0c;可利用 AI 模型&#xff0c;基于语义相似度&#xff0c…

从 X86 到 ARM :工控机迁移中的核心问题剖析

在工业控制领域&#xff0c;技术的不断演进促使着工控机从 X86 架构向 ARM 架构迁移。然而&#xff0c;这一过程并非一帆风顺&#xff0c;面临着诸多关键挑战。 首先&#xff0c;软件兼容性是一个重要问题。许多基于 X86 架构开发的工业控制软件可能无法直接在 ARM 架构上运行…

《qt open3d网格平滑》

qt open3d网格平滑 效果展示二、流程三、代码效果展示 二、流程 创建动作,链接到槽函数,并把动作放置菜单栏 参照前文 三、代码 1、槽函数实现 void on_actionFilterSmoothSimple_triggered();void MainWindow::on_actionF

Redis 的缓存雪崩、缓存穿透和缓存击穿详解,并提供多种解决方案

本文是对 Redis 知识的补充&#xff0c;在了解了如何搭建多种类型的 Redis 集群&#xff0c;并清楚了 Redis 集群搭建的过程的原理和注意事项之后&#xff0c;就要开始了解在使用 Redis 时可能出现的突发问题和对应的解决方案。 引言&#xff1a;虽然 Redis 是单线程的&#xf…

路由过滤方法与常用工具

引言 在前面我们已经学习了路由引入&#xff0c;接下来我们就更进一步来学习路由过滤 前一篇文章&#xff1a;重发布&#xff1a;路由引入&#xff08;点击即可&#xff09; 路由过滤 定义&#xff1a;路由器在发布或者接收消息时&#xff0c;可能需要对路由信息进行过滤。 作用…

元宵佳节,我的创作纪念日:技术之路的回顾与展望

今天是元宵节&#xff0c;一个象征着团圆与美好的节日。巧合的是&#xff0c;今天也是我作为技术博客博主的创作纪念日。在这个特别的日子里&#xff0c;我想和大家分享我的创作故事&#xff0c;回顾初心、总结收获、展望未来&#xff0c;同时也希望能为正在技术道路上探索的你…

【STM32】H743的以太网MAC控制器的一个特殊功能

调试743的MAC&#xff0c;翻阅手册的时候&#xff0c;发现了一个有意思的功能 混杂模式 H743的MAC控制器&#xff0c;可以设置为混杂模式&#xff0c;这就意味着它可以做一些网络监控的应用&#xff0c;譬如连接具备端口镜像功能的交换机&#xff0c;然后直接代替PC实现网络数据…

一个AI应用的开发、上线流程解析

目录 1. 模型文件格式 1.1 CheckPoint (ckpt) 文件格式 1.2 .pth 文件格式 1.3 .mindir 文件格式 1.4 .onnx 文件格式 2. 推理&#xff08;Inference&#xff09; 2.1.pth (PyTorch模型格式) 2.2 .mindir (MindSpore模型格式) 2.3.onnx (开放神经网络交换格式) 2.4实…

使用grafana v11 建立k线(蜡烛图)仪表板

先看实现的结果 沪铜主力合约 2025-02-12 的1分钟k线图 功能介绍: 左上角支持切换主力合约,日期,实现动态加载数据. 项目背景: 我想通过前端展示期货指定品种某1天的1分钟k线,类似tqsdk 的web_gui 生成图形化界面— TianQin Python SDK 3.7.8 文档 项目架构: 后端: fastap…