Arduino学习笔记【快速入门】

Arduino 是一种非常适合学习的开发平台,尤其对于初学者和有兴趣入门电子和嵌入式系统的人来说。以下是一些理由,说明为什么 Arduino 是一个很好的学习工具:

  1. 简单易用
    Arduino 提供了易于上手的编程环境(Arduino IDE),支持 C/C++ 编程语言。它有大量的现成库和示例代码,方便快速实现功能,而无需深入了解底层硬件。

  2. 广泛的资源和社区支持
    Arduino 拥有一个活跃的社区和丰富的在线资源,包括教程、项目示例、论坛等。这些资源可以帮助学习者解决问题并获取灵感。

  3. 硬件与软件的结合
    Arduino 通过简单的硬件接口和传感器与实际世界进行交互,帮助学习者理解电子原理,如数字输入输出、模拟输入输出、PWM 控制、串口通信等。

  4. 适合跨学科学习
    通过使用 Arduino,学习者可以接触到电子学、编程、自动化控制等多个学科,适合做为 STEM(科学、技术、工程和数学)教育的一部分。

  5. 成本低廉
    Arduino 板卡价格非常亲民,大部分初学者选择的模型(如 Arduino Uno)不超过几十美元。与此同时,还可以找到丰富的传感器和模块,方便制作各种有趣的项目。

  6. 项目驱动的学习
    通过动手做项目,学习者可以在实践中不断积累经验,解决问题,提升技能。无论是制作一个小型机器人、智能家居系统,还是自动化设备,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 秒,形成周期性的闪烁效果。

流程:

  1. 6 号引脚输出高电平(LED 亮起)。

  2. 程序暂停 500 毫秒。

  3. 6 号引脚输出低电平(LED 熄灭)。

  4. 程序暂停 500 毫秒。

  5. 重复上述过程。


交替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)

基本思路:

  • 加速度计 提供了 静态姿态信息(非常适合估算 RollPitch),但会对动态运动敏感,容易受到 加速度重力 的影响。

  • 陀螺仪 提供了 动态旋转信息,但是它会受到 漂移 的影响。

  • 卡尔曼滤波 能够有效地融合这两个数据源,利用加速度计的数据来校正陀螺仪的漂移,利用陀螺仪的数据来提供更精确的实时动态信息。

步骤:

  1. 安装卡尔曼滤波库
    你可以使用现有的卡尔曼滤波库,如 SimpleKalmanFilter 库来实现卡尔曼滤波。通过 Arduino IDE 的库管理器安装:

    • 工具 -> 库管理器 -> 搜索 SimpleKalmanFilter 安装它。

  2. 卡尔曼滤波算法

    • 使用卡尔曼滤波器来平滑 RollPitch 角度的计算。

    • 对于 Yaw,由于卡尔曼滤波通常需要一个参考信号(如磁力计),这里可以继续使用简单的 陀螺仪估算 或用更高级的算法来融合数据。

说明:

  • 卡尔曼滤波器:通过不断地融合加速度计和陀螺仪的数据,卡尔曼滤波器能有效地去除噪声并减少漂移,使得 RollPitch 角度更加准确和稳定。

  • Yaw 角度:由于没有磁力计或其他辅助传感器,Yaw 角度的估算仍然依赖于 陀螺仪 数据,这可能导致漂移,实际应用中可以考虑使用更复杂的算法(如 互补滤波卡尔曼滤波 来融合陀螺仪和磁力计的数据)来改进 Yaw 角度的计算。


优化后的代码:将读取的 RPY 数据实时显示在 OLED 屏幕上

在优化后的代码中,我们将 RollPitchYaw 数据可视化到 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);
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

随机惯性粒子群

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值