Arduino 是一种非常适合学习的开发平台,尤其对于初学者和有兴趣入门电子和嵌入式系统的人来说。以下是一些理由,说明为什么 Arduino 是一个很好的学习工具:
-
简单易用:
Arduino 提供了易于上手的编程环境(Arduino IDE),支持 C/C++ 编程语言。它有大量的现成库和示例代码,方便快速实现功能,而无需深入了解底层硬件。 -
广泛的资源和社区支持:
Arduino 拥有一个活跃的社区和丰富的在线资源,包括教程、项目示例、论坛等。这些资源可以帮助学习者解决问题并获取灵感。 -
硬件与软件的结合:
Arduino 通过简单的硬件接口和传感器与实际世界进行交互,帮助学习者理解电子原理,如数字输入输出、模拟输入输出、PWM 控制、串口通信等。 -
适合跨学科学习:
通过使用 Arduino,学习者可以接触到电子学、编程、自动化控制等多个学科,适合做为 STEM(科学、技术、工程和数学)教育的一部分。 -
成本低廉:
Arduino 板卡价格非常亲民,大部分初学者选择的模型(如 Arduino Uno)不超过几十美元。与此同时,还可以找到丰富的传感器和模块,方便制作各种有趣的项目。 -
项目驱动的学习:
通过动手做项目,学习者可以在实践中不断积累经验,解决问题,提升技能。无论是制作一个小型机器人、智能家居系统,还是自动化设备,Arduino 都能提供很好的支持。
总结来说,如果你对编程和电子学感兴趣,或者你想通过一个实践导向的学习方式掌握嵌入式系统,Arduino 是一个理想的学习平台。
下面我们开始学习Arduino:
硬件:Arduino Nano 控制板
相关的视频学习网课:第37期《Arduino入门》三叉戟 03:一闪一闪亮晶晶_哔哩哔哩_bilibili
安装IDE编辑器(Windows系统):Software | Arduino
傻瓜式安装即可
IDE设置:

简单的入门案例(点亮LED):
void setup() {
// pin: 针脚,引脚; mode:模式
pinMode(6,OUTPUT); // 把6引脚设置为输出模式
}
void loop() {
digitalWrite(6,HIGH);
delay(500);
digitalWrite(6,LOW);
delay(500);
}
上述代码可以显示一个LED灯的循环亮灭。这段代码控制连接到 6 号引脚的 LED 每隔 0.5 秒闪烁一次,亮起 0.5 秒,熄灭 0.5 秒,形成周期性的闪烁效果。

流程:
-
6 号引脚输出高电平(LED 亮起)。
-
程序暂停 500 毫秒。
-
6 号引脚输出低电平(LED 熄灭)。
-
程序暂停 500 毫秒。
-
重复上述过程。
交替LED的闪烁diamond如下:
int on = 1;
int off = 0;
void setup() {
// pin: 针脚,引脚; mode:模式
pinMode(6, OUTPUT); // 把6引脚设置为输出模式
pinMode(7, OUTPUT);
}
void loop() {
digitalWrite(6, on);
digitalWrite(7, off);
delay(500);
digitalWrite(7, on);
digitalWrite(6, off);
delay(500);
}
这段代码可以实现2个LED灯的交替闪烁
控制LED的亮度代码如下:
// 跬步
int a = 30;
void setup() {
// pin: 针脚,引脚; mode:模式
pinMode(3, OUTPUT); // 把3引脚设置为输出模式
}
void loop() {
// analog 模拟 analogWrite(引脚,取值) 取值范围 0 ~ 255; 0:0V 255:5V
analogWrite(3,a);
}
呼吸灯的代码如下:
// 跬步
int led = 5;
int a1 = 1;
int a2 = 200;
int dd =500;
void setup() {
}
void loop() {
// analog 模拟 analogWrite(引脚,取值) 取值范围 0 ~ 255; 0:0V 255:5V
analogWrite(led,a1);
delay(dd);
analogWrite(led,a2);
delay(dd);
}
for 循环(呼吸灯):
// for 循环
int led = 5;
void setup() {
}
void loop() {
// analog 模拟 analogWrite(引脚,取值) 取值范围 0 ~ 255; 0:0V 255:5V
for(int a=0; a<=255; a++){
analogWrite(led,a);
delay(10);
}
for(int b=255; b>=0; b--){
analogWrite(led,b);
delay(10);
}
}
while循环(呼吸灯):
// while 循环
int led = 5;
void setup() {
}
void loop() {
// analog 模拟 analogWrite(引脚,取值) 取值范围 0 ~ 255; 0:0V 255:5V
int i = 0;
while (i<=255) {
analogWrite(led, i);
delay(10);
i++;
}
while (i>=0) {
analogWrite(led, i);
delay(10);
i--;
}
}
IO 控制IED灯亮:
// io
int i;
void setup() {
pinMode(3, INPUT); // 设置3号引脚为输入模式
Serial.begin(9600); // 以9600的波特率开启串口通讯
pinMode(3, OUTPUT);
}
void loop() {
i = digitalRead(3); // 读取3号引脚的值
Serial.println(i);
delay(100);
if(i == 1){
digitalWrite(8,HIGH);
}
else{
digitalWrite(8,LOW);
}
}
// io
int i;
boolean state = true;
void setup() {
pinMode(3, INPUT); // 设置3号引脚为输入模式
Serial.begin(9600); // 以9600的波特率开启串口通讯
pinMode(3, OUTPUT);
}
void loop() {
i = digitalRead(3); // 读取3号引脚的值
Serial.println(i);
// delay(100);
if(i == 1){
digitalWrite(8,state);
state = !state;
delay(500);
}
}
双开关检测:
// io
int a1, a2;
boolean state = true;
void setup() {
pinMode(3, INPUT); // 设置3号引脚为输入模式
pinMode(5, INPUT); // 设置5号引脚为输入模式
Serial.begin(9600); // 以9600的波特率开启串口通讯
pinMode(3, OUTPUT);
pinMode(5, OUTPUT);
}
void loop() {
a1 = digitalRead(3); // 读取3号引脚的值
a2 = digitalRead(5); // 读取3号引脚的值
Serial.print(a1);
Serial.println(a2);
if(a1 == 1 && a2 == 1){
digitalWrite(8,state);
state = !state;
delay(500);
}
}
升级版:单个开关控制:
// io
int a1;
boolean state1 = true;
boolean state2 = false;
void setup() {
pinMode(3, INPUT); // 设置3号引脚为输入模式
Serial.begin(9600); // 以9600的波特率开启串口通讯
pinMode(3, OUTPUT);
}
void loop() {
a1 = digitalRead(3); // 读取3号引脚的值
Serial.println(a1);
if(a1 == 1 && state2 == false){
digitalWrite(8,state1);
state1 = !state1;
state2 = true;
}else if(a1 == 0 && state2 == true){
state2 = false;
}
}
舵机控制:
// 舵机
# include <Servo.h>
Servo s;
void setup() {
s.attach(3);
}
void loop() {
for(int i = 0; i <= 180; i++){
s.write(i);
delay(10);
}
for(int i = 180; i >= 0; i--){
s.write(i);
delay(10);
}
}
压力传感器控制舵机:
// 舵机
# include <Servo.h>
Servo s;
int i,j;
void setup() {
s.attach(3);
Serial.begin(9600);
}
void loop() {
i = analogRead(A1);
j = map(i, 350, 1023, 255, 0);
Serial.println(j);
// delay(200);
if(j >= 150){
s.write(60);
delay(100);
}
else{
s.write(0);
}
}
超声波传感器控制舵机:
// 传感器
# include <Servo.h>
Servo s;
int x;
void setup() {
s.attach(3);
Serial.begin(9600);
pinMode(8, OUTPUT);
pinMode(7, INPUT);
}
void loop() {
digitalWrite(8, LOW);
delayMicroseconds(2);
digitalWrite(8, HIGH); // 发射10 微秒的脉冲信号
delayMicroseconds(10);
digitalWrite(8, LOW);
unsigned long t = pulseIn(7, HIGH); // 获取时间 微秒
float d = (t * 0.034)/2; // v = 0.034 cm/us
Serial.println(d);
// delay(500);
x = map(d, 4, 10, 180, 0);
if(d >= 4 && d <= 10){
s.write(x);
delay(20);
}
else{
s.write(0);
}
}
超声波传感器控制舵机(分段控制):
// 传感器
# include <Servo.h>
Servo s;
int x;
void setup() {
s.attach(3);
Serial.begin(9600);
pinMode(8, OUTPUT);
pinMode(7, INPUT);
}
void loop() {
digitalWrite(8, LOW);
delayMicroseconds(2);
digitalWrite(8, HIGH); // 发射10 微秒的脉冲信号
delayMicroseconds(10);
digitalWrite(8, LOW);
unsigned long t = pulseIn(7, HIGH); // 获取时间 微秒
float d = (t * 0.034)/2; // v = 0.034 cm/us
Serial.println(d);
// delay(500);
// x = map(d, 3, 10, 180, 0);
if(d >= 3 && d <= 6){
s.write(180);
delay(100);
}
else if(d >= 6 && d <= 9){
s.write(120);
delay(100);
}
else if(d >= 9 && d <= 12){
s.write(60);
delay(100);
}
else{
s.write(0);
delay(100);
}
}
I2C屏幕打印:
// I2C
# include <LiquidCrystal_I2C.h> // 调用LCD的I2C库
# include <Wire.h> // 调用I2C库
LiquidCrystal_I2C lcd(0x27,16,2); //创建lcd对象
// 参数1:I2C设备地址
// 参数2:I2C屏幕列数
// 参数3:I2C屏幕行数
void setup() {
lcd.init(); // 开启lcd通讯
lcd.backlight(); // 开启背景光源
}
void loop() {
lcd.setCursor(0,0); // 设置光标位置
lcd.print("Hello World");
}
用nano控制板点亮OLED屏幕:
参考:Arduino Uno 点亮OLED屏幕(SSD1306,4PIN,IIC) – 凌顺实验室
OLED显示屏不需要背光,因此在黑暗的环境中能获得非常好的对比度。另外,它的像素只有在开启的时候才会消耗能量,所以与其他显示器相比,OLED显示器的功耗更低。
没有背光源大大降低了运行OLED所需的功率。平均来说,显示屏的耗电量约为20mA,不过这取决于显示屏的点亮程度。
该模块的核心是一个功能强大的单芯片CMOS OLED驱动控制器——SSD1306。它可以通过多种方式与微控制器通信,包括I2C和SPI。
SPI一般比I2C快,但需要更多的I/O引脚。而I2C只需要两个引脚,并且可以与其他I2C外设共享。这是在引脚和速度之间的权衡。有的型号还多了一个RESET引脚。
在本示例中使用的型号只有四个引脚,与Arduino使用I2C通信协议进行通信。
SSD1306控制器的工作电压为1.65V至3.3V,而OLED面板需要7V至15V的电源电压。所有这些不同的电源需求都可以通过内部的充电泵电路来满足。这使得它可以轻松地连接到Arduino或任何5V逻辑微控制器,而无需使用任何逻辑电平转换器。
无论OLED模块的大小,SSD1306驱动都为屏幕内置了1KB图形显示数据RAM(GDDRAM),其中保存了要显示的位图。这个1K的内存区域被组织成8页(从0到7)。每页包含128个列/段(0到127块)。而每一列可以存储8位数据(从0到7)。
8页x 128段x 8位数据= 8192位= 1024字节= 1KB内存
- 显示技术 OLED(Organic LED)
- MCU通信 I2C / SPI
- 屏幕尺寸 跨度0.96英寸
- 解析度 128×64像素
- 工作电压 3.3V – 5V
- 工作电流 最大20mA
- 可视角度 160°
- 每行字符 21
- 字符行数 7
引脚说明:GND 为接地 VCC 为供电(需要供电3.3V-5V) SCL 为I2C时钟线 SDA 为I2C数据线
| Arduino 引脚 | <-> | OLED 引脚 |
|---|---|---|
| GND | <-> | GND |
| 3.3V | <-> | VCC |
| A5 | <-> | SCL |
| A4 | <-> | SDA |
要顺利运行OLED的程序,需要安装以下两个库。 安装方法很简单,在菜单栏依次打开: 菜单栏「工具」-> 「管理库」
在弹出的库管理窗口中的搜素对应的关键字,找到对应的库(注意要找对库名称和库作者),点击安装,等待安装完成即可。
安装库 Adafruit_GFX_Library;安装库 Adafruit_SSD1306
定义屏幕的分辨率:
SSD1306控制器的多功能性,使得该模块具有不同的尺寸和颜色:例如128x64、128×32,使用同样的库,进行开发。
像如下代码,声明了正在使用的OLED屏幕的分辨率是128x64,如果您使用了其他分辨率请进行对应的修改。
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &Wire);
定义屏幕的I2C地址:
一般默认的OLED屏幕的地址是0x3C,但我也遇到过不是这个地址的模块,如果有时候发现你的OLED屏幕,不亮,也许是I2C地址设置错了。
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
清空屏幕:
使用以下代码,作用是清空整个屏幕的显示,熄灭所有亮起来的像素。
display.clearDisplay();
屏幕更改生效:
最后,别忘了使用display()函数,使屏幕的更改生效
display.display();
还有其他函数,它们将帮助你处理OLED显示库,以编写文本或绘制简单的图形。
display.drawPixel(x,y, color) - 以x,y为坐标绘制一个像素。 display.setTextSize(n) - 设置字体大小,支持1-8号字体。 display.setCursor(x,y) - 设置开始显示文字的坐标。 display.print("message") - 打印x,y位置的字符。 display.display() - 调用此方法使更改生效。
程序代码:
#include <SPI.h> // 加载SPI库
#include <Wire.h> // 加载Wire库
#include <Adafruit_GFX.h> // 加载Adafruit_GFX库
#include <Adafruit_SSD1306.h> // 加载Adafruit_SSD1306库
// 定义 OLED屏幕的分辨率
Adafruit_SSD1306 display = Adafruit_SSD1306(128, 64, &Wire);
void setup() {
Serial.begin(9600); // 设置串口波特率
Serial.println("OLED FeatherWing test"); // 串口输出
display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // 设置OLED的I2C地址
display.clearDisplay(); // 清空屏幕
display.setTextSize(1); // 设置字体大小
display.setTextColor(SSD1306_WHITE); // 设置字体颜色
display.setCursor(0,0); // 设置开始显示文字的坐标
display.println("Hello World!"); // 输出的字符
display.println("HJX!");
display.display(); // 使更改的显示生效
}
void loop() {
}
上传程序后,即可看到屏幕显示所输出的的字符。
利用OLED实时显示压力传感器数值:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_ADDR 0x3C
#define SENSOR_PIN A0
#define UPDATE_INTERVAL 200
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);
void setup() {
Serial.begin(9600);
if(!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // 永久停止程序
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
// 显示初始界面
display.setCursor(0, 0);
display.println("Pressure Monitor");
display.println("----------------");
display.display();
pinMode(SENSOR_PIN, INPUT);
delay(1000); // 等待初始界面显示
}
void loop() {
static unsigned long lastUpdate = 0;
static int lastValue = -1; // 记录上次数值避免重复刷新
if(millis() - lastUpdate >= UPDATE_INTERVAL) {
int rawValue = analogRead(SENSOR_PIN);
int pressure = map(rawValue, 350, 1023, 255, 0);
// 仅在数值变化时更新显示
if(pressure != lastValue) {
display.fillRect(0, 16, 128, 48, SSD1306_BLACK); // 清除数值区域
display.setCursor(0, 16);
display.setTextSize(2);
display.print("Value: ");
display.println(pressure);
display.setTextSize(1);
display.setCursor(0, 40);
display.print("Raw: ");
display.print(rawValue);
display.print(" (");
display.print(rawValue * 100 / 1023);
display.println("%)");
display.display();
lastValue = pressure;
}
Serial.println(pressure);
lastUpdate = millis();
}
}
利用OLED实时显示压力传感器数值并控制舵机运动:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Servo.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_ADDR 0x3C
#define SENSOR_PIN A0
#define SERVO_PIN 3
#define DISPLAY_UPDATE_INTERVAL 200
#define SERVO_UPDATE_INTERVAL 100
Servo myServo;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire);
void setup() {
Serial.begin(9600);
// 初始化舵机
myServo.attach(SERVO_PIN);
// 初始化OLED显示
if(!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // 永久停止程序
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
// 显示初始界面
display.setCursor(0, 0);
display.println("Pressure Monitor");
display.println("----------------");
display.display();
pinMode(SENSOR_PIN, INPUT);
delay(1000); // 等待初始界面显示
}
void loop() {
static unsigned long lastDisplayUpdate = 0;
static unsigned long lastServoUpdate = 0;
static int lastPressureValue = -1; // 记录上次数值避免重复刷新
// 读取传感器值
int rawValue = analogRead(SENSOR_PIN);
int pressure = map(rawValue, 350, 1023, 255, 0);
// 更新OLED显示
if(millis() - lastDisplayUpdate >= DISPLAY_UPDATE_INTERVAL) {
// 仅在数值变化时更新显示
if(pressure != lastPressureValue) {
display.fillRect(0, 16, 128, 48, SSD1306_BLACK); // 清除数值区域
display.setCursor(0, 16);
display.setTextSize(2);
display.print("Value: ");
display.println(pressure);
display.setTextSize(1);
display.setCursor(0, 40);
display.print("Raw: ");
display.print(rawValue);
display.print(" (");
display.print(rawValue * 100 / 1023);
display.println("%)");
display.display();
lastPressureValue = pressure;
}
Serial.print("Pressure: ");
Serial.println(pressure);
lastDisplayUpdate = millis();
}
// 舵机控制 - 非阻塞方式
if(millis() - lastServoUpdate >= SERVO_UPDATE_INTERVAL) {
if(pressure >= 0) {
myServo.write(pressure);
} else {
myServo.write(0);
}
lastServoUpdate = millis();
}
}
利用压力传感器同时驱动3个舵机并显示OLED数值:
#include <Wire.h> // 引入 I2C 通信库,用于与 OLED 显示屏进行通信
#include <Adafruit_GFX.h> // 引入 Adafruit 图形库,支持绘制基本图形
#include <Adafruit_SSD1306.h> // 引入 Adafruit OLED 驱动库,用于驱动 SSD1306 显示屏
#include <Servo.h> // 引入 Servo 库,用于控制舵机
#define SCREEN_WIDTH 128 // OLED 显示屏宽度设置为 128 像素
#define SCREEN_HEIGHT 64 // OLED 显示屏高度设置为 64 像素
#define OLED_ADDR 0x3C // 设置 OLED 屏幕的 I2C 地址为 0x3C
#define SENSOR_PIN A0 // 设置压力传感器连接到模拟引脚 A0
#define SERVO_PIN_1 3 // 设置第一个舵机连接到数字引脚 D3
#define SERVO_PIN_2 5 // 设置第二个舵机连接到数字引脚 D5
#define SERVO_PIN_3 6 // 设置第三个舵机连接到数字引脚 D6
#define DISPLAY_UPDATE_INTERVAL 200 // 设置 OLED 显示的更新间隔为 200 毫秒
#define SERVO_UPDATE_INTERVAL 100 // 设置舵机控制的更新间隔为 100 毫秒
Servo myServo1; // 创建第一个 Servo 对象用于控制舵机1
Servo myServo2; // 创建第二个 Servo 对象用于控制舵机2
Servo myServo3; // 创建第三个 Servo 对象用于控制舵机3
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire); // 创建 OLED 显示对象,使用 I2C 通信
void setup() {
Serial.begin(9600); // 初始化串口通信,波特率为 9600,用于调试输出
myServo1.attach(SERVO_PIN_1); // 初始化第一个舵机对象,绑定到 D3 引脚
myServo2.attach(SERVO_PIN_2); // 初始化第二个舵机对象,绑定到 D5 引脚
myServo3.attach(SERVO_PIN_3); // 初始化第三个舵机对象,绑定到 D6 引脚
if(!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) { // 初始化 OLED 显示屏
Serial.println(F("SSD1306 allocation failed")); // 如果 OLED 初始化失败,输出错误信息
for(;;); // 如果 OLED 初始化失败,程序进入死循环,停止执行
}
display.clearDisplay(); // 清空 OLED 显示屏上的内容
display.setTextSize(1); // 设置显示字体大小为 1(较小)
display.setTextColor(SSD1306_WHITE); // 设置字体颜色为白色
display.setCursor(0, 0); // 设置光标位置在显示屏的左上角
display.println("Pressure Monitor");// 在 OLED 屏幕上打印标题 "Pressure Monitor"
display.println("----------------"); // 打印一条分隔线
display.display(); // 将内容显示到 OLED 屏幕上
pinMode(SENSOR_PIN, INPUT); // 设置压力传感器引脚为输入模式
delay(1000); // 延迟 1 秒,确保初始界面显示完全
}
void loop() {
static unsigned long lastDisplayUpdate = 0; // 记录上次显示更新的时间
static unsigned long lastServoUpdate = 0; // 记录上次舵机更新的时间
static int lastPressureValue = -1; // 记录上次的压力值,避免重复刷新显示
int rawValue = analogRead(SENSOR_PIN); // 读取压力传感器的原始模拟值(范围 0-1023)
int pressure = map(rawValue, 350, 1023, 255, 0); // 将原始值映射为压力值(范围 255~0)
if(millis() - lastDisplayUpdate >= DISPLAY_UPDATE_INTERVAL) { // 判断是否达到了显示更新的时间间隔
if(pressure != lastPressureValue) { // 如果当前压力值与上次值不同,则刷新显示
display.fillRect(0, 16, 128, 48, SSD1306_BLACK); // 清除之前显示的数值区域
display.setCursor(0, 16); // 设置光标位置
display.setTextSize(2); // 设置字体大小为 2(较大)
display.print("Value: "); // 显示文本 "Value: "
display.println(pressure); // 显示当前的压力值
display.setTextSize(1); // 字体恢复为 1
display.setCursor(0, 40); // 设置光标位置
display.print("Raw: "); // 显示原始传感器值
display.print(rawValue); // 显示传感器原始值
display.print(" (");
display.print(rawValue * 100 / 1023); // 显示百分比
display.println("%)");
display.display(); // 更新 OLED 显示屏
lastPressureValue = pressure; // 记录当前压力值,以便下一次对比
}
Serial.print("Pressure: "); // 输出当前压力值到串口
Serial.println(pressure);
lastDisplayUpdate = millis(); // 更新显示更新时间戳
}
if(millis() - lastServoUpdate >= SERVO_UPDATE_INTERVAL) { // 判断是否达到了舵机控制更新的时间间隔
if(pressure >= 0) { // 如果压力值有效
myServo1.write(pressure); // 根据压力值设置舵机1角度,范围 0~255 映射为舵机角度 0~180
myServo2.write(pressure); // 根据压力值设置舵机2角度
myServo3.write(pressure); // 根据压力值设置舵机3角度
} else {
myServo1.write(0); // 如果压力值无效,将舵机1角度设置为 0
myServo2.write(0); // 将舵机2角度设置为 0
myServo3.write(0); // 将舵机3角度设置为 0
}
lastServoUpdate = millis(); // 更新舵机控制时间戳
}
}
MPU6050型号IMU传感器数据的读取:
#include <Wire.h> // 引入 I2C 库,用于与 MPU6050 通信
#include <MPU6050.h> // 引入 MPU6050 库
#include <SimpleKalmanFilter.h> // 引入卡尔曼滤波库
MPU6050 mpu; // 创建 MPU6050 对象
// 创建两个卡尔曼滤波器对象
SimpleKalmanFilter kalmanRoll(1, 1, 0.01); // 用于 Roll 角的卡尔曼滤波器,参数可以调整
SimpleKalmanFilter kalmanPitch(1, 1, 0.01); // 用于 Pitch 角的卡尔曼滤波器,参数可以调整
void setup() {
Serial.begin(9600); // 初始化串口通信,波特率 9600,用于调试输出
Wire.begin(); // 启动 I2C 通信
mpu.initialize(); // 初始化 MPU6050 传感器
// 检查 MPU6050 是否连接成功
if (!mpu.testConnection()) {
Serial.println("MPU6050 connection failed!");
while (1); // 如果连接失败,停止程序
}
Serial.println("MPU6050 connected successfully!");
}
void loop() {
int16_t ax, ay, az; // 存储加速度数据
int16_t gx, gy, gz; // 存储陀螺仪数据
// 读取加速度计数据
mpu.getAcceleration(&ax, &ay, &az);
// 读取陀螺仪数据
mpu.getRotation(&gx, &gy, &gz);
// 将加速度和陀螺仪数据转换为实际的物理量(加速度单位:g,角速度单位:°/s)
float ax_g = ax / 16384.0; // 1g = 16384的原始值(对于±2g的量程)
float ay_g = ay / 16384.0;
float az_g = az / 16384.0;
float gx_dps = gx / 131.0; // 1°/s = 131的原始值(对于±250°/s的量程)
float gy_dps = gy / 131.0;
float gz_dps = gz / 131.0;
// 计算初步的欧拉角(Roll 和 Pitch)
float roll = atan2(ay_g, az_g) * 180.0 / PI; // 转换为度数
float pitch = atan2(-ax_g, sqrt(ay_g * ay_g + az_g * az_g)) * 180.0 / PI; // 转换为度数
// 使用卡尔曼滤波器优化 Roll 和 Pitch 角度
float filteredRoll = kalmanRoll.updateEstimate(roll);
float filteredPitch = kalmanPitch.updateEstimate(pitch);
// 输出加速度数据(单位:g),角速度数据(单位:°/s)和优化后的欧拉角(单位:°)
Serial.print("Accelerometer (g): ");
Serial.print("X = "); Serial.print(ax_g, 4); // 输出加速度(保留 4 位小数)
Serial.print(" Y = "); Serial.print(ay_g, 4);
Serial.print(" Z = "); Serial.println(az_g, 4);
Serial.print("Gyroscope (°/s): ");
Serial.print("X = "); Serial.print(gx_dps, 2); // 输出角速度(保留 2 位小数)
Serial.print(" Y = "); Serial.print(gy_dps, 2);
Serial.print(" Z = "); Serial.println(gz_dps, 2);
// 输出优化后的欧拉角(RPY)
Serial.print("Filtered Euler Angles (°): ");
Serial.print("Roll = "); Serial.print(filteredRoll, 2); // 输出 Roll 角度(保留 2 位小数)
Serial.print(" Pitch = "); Serial.print(filteredPitch, 2); // 输出 Pitch 角度(保留 2 位小数)
Serial.print(" Yaw = "); Serial.println(gz_dps, 2); // 使用陀螺仪 Z 轴角速度作为简单的 Yaw 估算
delay(500); // 每 500 毫秒读取一次数据
}
为了在 Arduino 中使用 卡尔曼滤波 来优化 欧拉角(RPY) 的计算,首先,我们需要了解 卡尔曼滤波 是如何工作的。卡尔曼滤波器是一种递归滤波器,能够通过加权平均的方式合并来自不同传感器的信号,以减小噪声,提高估计精度。
在你的应用中,你可以用 卡尔曼滤波 来融合来自 加速度计 和 陀螺仪 的数据,得到更稳定的 欧拉角(Roll、Pitch、Yaw)。
基本思路:
-
加速度计 提供了 静态姿态信息(非常适合估算 Roll 和 Pitch),但会对动态运动敏感,容易受到 加速度 和 重力 的影响。
-
陀螺仪 提供了 动态旋转信息,但是它会受到 漂移 的影响。
-
卡尔曼滤波 能够有效地融合这两个数据源,利用加速度计的数据来校正陀螺仪的漂移,利用陀螺仪的数据来提供更精确的实时动态信息。
步骤:
-
安装卡尔曼滤波库:
你可以使用现有的卡尔曼滤波库,如 SimpleKalmanFilter 库来实现卡尔曼滤波。通过 Arduino IDE 的库管理器安装:-
工具 -> 库管理器 -> 搜索
SimpleKalmanFilter安装它。
-
-
卡尔曼滤波算法:
-
使用卡尔曼滤波器来平滑 Roll 和 Pitch 角度的计算。
-
对于 Yaw,由于卡尔曼滤波通常需要一个参考信号(如磁力计),这里可以继续使用简单的 陀螺仪估算 或用更高级的算法来融合数据。
-
说明:
-
卡尔曼滤波器:通过不断地融合加速度计和陀螺仪的数据,卡尔曼滤波器能有效地去除噪声并减少漂移,使得 Roll 和 Pitch 角度更加准确和稳定。
-
Yaw 角度:由于没有磁力计或其他辅助传感器,Yaw 角度的估算仍然依赖于 陀螺仪 数据,这可能导致漂移,实际应用中可以考虑使用更复杂的算法(如 互补滤波 或 卡尔曼滤波 来融合陀螺仪和磁力计的数据)来改进 Yaw 角度的计算。
优化后的代码:将读取的 RPY 数据实时显示在 OLED 屏幕上
在优化后的代码中,我们将 Roll、Pitch 和 Yaw 数据可视化到 OLED 屏幕上。假设你已经成功读取了传感器数据并计算出欧拉角,接下来将这些数据展示在 OLED 屏幕上。
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <MPU6050.h> // 引入 MPU6050 库
#include <SimpleKalmanFilter.h> // 引入卡尔曼滤波库
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_ADDR 0x3C
#define UPDATE_INTERVAL 200
MPU6050 mpu; // 创建 MPU6050 对象
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire); // OLED 屏幕对象
// 创建卡尔曼滤波器对象
SimpleKalmanFilter kalmanRoll(1, 1, 0.01); // 用于 Roll 角的卡尔曼滤波器
SimpleKalmanFilter kalmanPitch(1, 1, 0.01); // 用于 Pitch 角的卡尔曼滤波器
void setup() {
Serial.begin(9600);
if(!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // 永久停止程序
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
// 显示初始界面
display.setCursor(0, 0);
display.println("MPU6050 Monitor");
display.display();
mpu.initialize(); // 初始化 MPU6050 传感器
// 检查 MPU6050 是否连接成功
if (!mpu.testConnection()) {
Serial.println("MPU6050 connection failed!");
while(1); // 如果连接失败,停止程序
}
delay(1000); // 等待初始界面显示
}
void loop() {
static unsigned long lastUpdate = 0;
if(millis() - lastUpdate >= UPDATE_INTERVAL) {
int16_t ax, ay, az; // 存储加速度数据
int16_t gx, gy, gz; // 存储陀螺仪数据
// 读取加速度计数据
mpu.getAcceleration(&ax, &ay, &az);
// 读取陀螺仪数据
mpu.getRotation(&gx, &gy, &gz);
// 将加速度和陀螺仪数据转换为实际的物理量(加速度单位:g,角速度单位:°/s)
float ax_g = ax / 16384.0; // 1g = 16384的原始值(对于±2g的量程)
float ay_g = ay / 16384.0;
float az_g = az / 16384.0;
float gx_dps = gx / 131.0; // 1°/s = 131的原始值(对于±250°/s的量程)
float gy_dps = gy / 131.0;
float gz_dps = gz / 131.0;
// 计算初步的欧拉角(Roll 和 Pitch)
float roll = atan2(ay_g, az_g) * 180.0 / PI; // 转换为度数
float pitch = atan2(-ax_g, sqrt(ay_g * ay_g + az_g * az_g)) * 180.0 / PI; // 转换为度数
// 使用卡尔曼滤波器优化 Roll 和 Pitch 角度
float filteredRoll = kalmanRoll.updateEstimate(roll);
float filteredPitch = kalmanPitch.updateEstimate(pitch);
// 输出到 OLED 屏幕
display.clearDisplay(); // 清除屏幕
display.setCursor(0, 0); // 设置显示的起始位置
display.setTextSize(1);
display.print("Roll: ");
display.println(filteredRoll, 2); // 显示 Roll 角度
display.setCursor(0, 16); // 设置下一行显示的位置
display.print("Pitch: ");
display.println(filteredPitch, 2); // 显示 Pitch 角度
display.setCursor(0, 32); // 设置下一行显示的位置
display.print("Yaw: ");
display.println(gz_dps, 2); // 显示 Yaw 估算(使用陀螺仪 Z 轴角速度)
display.display(); // 刷新显示内容
// 输出到串口
Serial.print("Roll: ");
Serial.print(filteredRoll, 2);
Serial.print(" Pitch: ");
Serial.print(filteredPitch, 2);
Serial.print(" Yaw: ");
Serial.println(gz_dps, 2);
lastUpdate = millis(); // 更新上次更新时间
}
}
利用IMU控制云台舵机的代码:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <MPU6050.h> // 引入 MPU6050 库
#include <SimpleKalmanFilter.h> // 引入卡尔曼滤波库
#include <Servo.h> // 引入 Servo 库
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_ADDR 0x3C
#define UPDATE_INTERVAL 200
// 舵机连接引脚
#define SERVO_PIN_1 3 // 设置第一个舵机连接到数字引脚 D3
#define SERVO_PIN_2 5 // 设置第二个舵机连接到数字引脚 D5
#define SERVO_PIN_3 6 // 设置第三个舵机连接到数字引脚 D6
MPU6050 mpu; // 创建 MPU6050 对象
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire); // OLED 屏幕对象
// 创建卡尔曼滤波器对象
SimpleKalmanFilter kalmanRoll(1, 1, 0.01); // 用于 Roll 角的卡尔曼滤波器
SimpleKalmanFilter kalmanPitch(1, 1, 0.01); // 用于 Pitch 角的卡尔曼滤波器
Servo servo1; // 创建第一个舵机对象
Servo servo2; // 创建第二个舵机对象
void setup() {
Serial.begin(9600);
// 提高 I2C 传输速度
Wire.setClock(400000); // 设置 I2C 传输速率为 400 kHz
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // 永久停止程序
}
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
// 显示初始界面
display.setCursor(0, 0);
display.println("MPU6050 Monitor");
display.display();
mpu.initialize(); // 初始化 MPU6050 传感器
// 检查 MPU6050 是否连接成功
if (!mpu.testConnection()) {
Serial.println("MPU6050 connection failed!");
while(1); // 如果连接失败,停止程序
}
delay(1000); // 等待初始界面显示
// 初始化舵机
servo1.attach(SERVO_PIN_1); // 将舵机1连接到引脚D3
servo2.attach(SERVO_PIN_2); // 将舵机2连接到引脚D5
}
void loop() {
static unsigned long lastUpdate = 0;
if (millis() - lastUpdate >= UPDATE_INTERVAL) {
int16_t ax, ay, az; // 存储加速度数据
int16_t gx, gy, gz; // 存储陀螺仪数据
// 读取加速度计数据
mpu.getAcceleration(&ax, &ay, &az);
// 读取陀螺仪数据
mpu.getRotation(&gx, &gy, &gz);
// 将加速度和陀螺仪数据转换为实际的物理量(加速度单位:g,角速度单位:°/s)
float ax_g = ax / 16384.0; // 1g = 16384的原始值(对于±2g的量程)
float ay_g = ay / 16384.0;
float az_g = az / 16384.0;
float gx_dps = gx / 131.0; // 1°/s = 131的原始值(对于±250°/s的量程)
float gy_dps = gy / 131.0;
float gz_dps = gz / 131.0;
// 计算初步的欧拉角(Roll 和 Pitch)
float roll = atan2(ay_g, az_g) * 180.0 / PI; // 转换为度数
float pitch = atan2(-ax_g, sqrt(ay_g * ay_g + az_g * az_g)) * 180.0 / PI; // 转换为度数
// 使用卡尔曼滤波器优化 Roll 和 Pitch 角度
float filteredRoll = kalmanRoll.updateEstimate(roll);
float filteredPitch = kalmanPitch.updateEstimate(pitch);
// 限制舵机角度范围(例如,Roll 限制在 0 到 180 度之间)
filteredRoll = constrain(filteredRoll, 0, 180); // 限制 Roll 角度范围
filteredPitch = constrain(filteredPitch, 0, 180); // 限制 Pitch 角度范围
// 输出到 OLED 屏幕(减少更新频率)
// static unsigned long lastDisplayUpdate = 0;
// if (millis() - lastDisplayUpdate >= 1000) { // 每秒刷新一次屏幕
// display.clearDisplay(); // 清除屏幕
// display.setCursor(0, 0); // 设置显示的起始位置
// display.setTextSize(1);
// display.print("Roll: ");
// display.println(filteredRoll, 2); // 显示 Roll 角度
// display.setCursor(0, 16); // 设置下一行显示的位置
// display.print("Pitch: ");
// display.println(filteredPitch, 2); // 显示 Pitch 角度
// display.display(); // 刷新显示内容
// lastDisplayUpdate = millis();
// }
// 输出到串口(减少输出频率)
// static unsigned long lastSerialUpdate = 0;
// if (millis() - lastSerialUpdate >= 1000) { // 每秒输出一次
// Serial.print("Roll: ");
// Serial.print(filteredRoll, 2);
// Serial.print(" Pitch: ");
// Serial.print(filteredPitch, 2);
// Serial.println();
// lastSerialUpdate = millis();
// }
// 控制舵机旋转:将 Roll 角度映射到舵机的角度范围(0-180度)
servo1.write(filteredRoll); // 控制第一个舵机
servo2.write(filteredPitch); // 控制第二个舵机
lastUpdate = millis(); // 更新上次更新时间
}
}
5个压力传感器控制5个舵机:
// 舵机
# include <Servo.h>
Servo s1,s2,s3,s4,s5;
int p1,p2,p3,p4,p5;
int k1,k2,k3,k4,k5;
int threshold_value = 10;
void setup() {
s1.attach(3);
s2.attach(4);
s3.attach(5);
s4.attach(6);
s5.attach(7);
Serial.begin(9600);
}
void loop() {
p1 = analogRead(A0);
p2 = analogRead(A1);
p3 = analogRead(A2);
p4 = analogRead(A3);
p5 = analogRead(A4);
k1 = map(p1, 350, 900, 300, 0);
k2 = map(p2, 350, 900, 300, 0);
k3 = map(p3, 350, 900, 300, 0);
k4 = map(p4, 350, 900, 300, 0);
k5 = map(p5, 350, 900, 300, 0);
Serial.println(k1);
Serial.println(k2);
Serial.println(k3);
Serial.println(k4);
Serial.println(k5);
// delay(200);
if(k1 >=threshold_value || k2 >=threshold_value || k3 >=threshold_value || k4 >=threshold_value || k5 >=threshold_value){
s1.write(k1);
s2.write(k2);
s3.write(k3);
s4.write(k4);
s5.write(k5);
delay(100);
}
else{
s1.write(0);
s2.write(0);
s3.write(0);
s4.write(0);
s5.write(0);
}
}
对上述代码进行优化,优化后的代码更加简洁、易于维护,并符合常见的代码规范:
// 舵机控制程序
#include <Servo.h>
// 创建5个舵机对象
Servo servo1, servo2, servo3, servo4, servo5;
// 定义输入端口和输出端口
const int sensorPins[] = {A0, A1, A2, A3, A4}; // 传感器引脚
const int servoPins[] = {3, 4, 5, 6, 7}; // 舵机引脚
// 舵机控制角度的变量
int sensorValues[5]; // 用于存储传感器读取值
int servoAngles[5]; // 用于存储舵机角度
// 阈值设置
const int thresholdValue = 10;
void setup() {
// 初始化舵机和传感器
for (int i = 0; i < 5; i++) {
// 将舵机对象连接到对应的引脚
if (i < 5) {
Servo &servo = getServoByIndex(i); // 获取对应舵机
servo.attach(servoPins[i]); // 附加舵机到引脚
}
}
Serial.begin(9600); // 启动串口通信
}
void loop() {
// 读取传感器输入值
for (int i = 0; i < 5; i++) {
sensorValues[i] = analogRead(sensorPins[i]); // 读取传感器值
}
// 将传感器值映射到舵机控制角度
for (int i = 0; i < 5; i++) {
servoAngles[i] = map(sensorValues[i], 350, 900, 300, 0);
}
// 输出调试信息到串口
printSensorValues();
// 根据传感器值调整舵机角度
if (isThresholdExceeded()) {
// 如果有传感器值超过阈值,则设置舵机角度
for (int i = 0; i < 5; i++) {
getServoByIndex(i).write(servoAngles[i]);
}
delay(100); // 延时
} else {
// 如果所有传感器值都低于阈值,则将舵机角度设置为0
resetServos();
}
}
// 根据索引返回舵机对象
Servo& getServoByIndex(int index) {
switch(index) {
case 0: return servo1;
case 1: return servo2;
case 2: return servo3;
case 3: return servo4;
case 4: return servo5;
default: return servo1; // 默认返回servo1
}
}
// 打印传感器值,用于调试
void printSensorValues() {
for (int i = 0; i < 5; i++) {
Serial.print("Sensor ");
Serial.print(i + 1);
Serial.print(": ");
Serial.println(servoAngles[i]); // 输出映射后的舵机角度值
}
}
// 检查是否有任何传感器值超过阈值
bool isThresholdExceeded() {
for (int i = 0; i < 5; i++) {
if (servoAngles[i] >= thresholdValue) {
return true; // 只要有一个传感器值超过阈值,返回true
}
}
return false; // 如果所有传感器值都低于阈值,返回false
}
// 将所有舵机角度设置为0
void resetServos() {
for (int i = 0; i < 5; i++) {
getServoByIndex(i).write(0);
}
}
1万+

被折叠的 条评论
为什么被折叠?



