Hi!请登陆

船新版本esp8266可动兽耳教程(?)

heiya 2024-10-27 794 10/27

ESP8266动耳控制板教程(?)

高情商:可动兽耳
低情商:ESP8266通过BMI160数据控制舵机(确信)

此教程为22年教程后续填坑√

基于ESP8266的电子兽耳3.0教程_哔哩哔哩_bilibili

船新版本esp8266可动兽耳教程(?)
鉴于有群友反应不会连线、不会找电源、不会烧程序等问题,搞出的新主板,基于前烂尾的slimevr工程改版(一堆烂尾工程),集成了2S充电、自动烧录、ESP12和BMI160。



接下来开始讲解
整个教程分为四部分,分别是材料选型,电路介绍,程序介绍,以及问题答疑。

首先是材料选型,淘宝上都有,全部购买的话一个板子30左右。

材料
1.ESP12模块,淘宝5元左右
2.BMI1606轴陀螺仪模块,淘宝5元左右
3.IP2325充电模块,淘宝1元左右
4.AMS1117-3.3,淘宝抽象价格4块钱50个
5.TYPE-C接口,淘宝几毛钱一个
6.CH340串口芯片,淘宝1.5元一片
7.电容、电阻、LED等常用原件,5块钱之内拿下

其次是电路介绍
电路的原理图以及PCB已经放在了立创开源广场点击前往
船新版本esp8266可动兽耳教程(?)
电路主要可以分为三个部分,第一个是充电电路,采用的是一颗英集芯的IP2325,它可以将5V的输入电压升压至8.4V对2S电池进行充电,下图为充电电路部分。
红色框内为一个充电电源灯,当插上充电器时会常亮。
绿色框内为IP2325的充电指示灯,指示灯常亮时处于充电状态,指示灯熄灭时代表充电完成,如果指示灯闪烁则充电异常停止充电。
黄色框内可以接一个NTC电阻,可以检测电池or主板温度,当温度异常时停止充电,如果不需要测温则焊接上51K电阻。
船新版本esp8266可动兽耳教程(?)


第二部分是烧录电路,采用的是CH340串口烧录,并且支持自动烧录(玄学?),原理图如下
在正常烧录时,需要将IO0口接地并复位,通过自动烧录电路可以通过CH340以及8050将IO0自动拉低复位(8050没了,换的9012会有问题?),程序烧录完成后会自动复位开始运行。
船新版本esp8266可动兽耳教程(?)


第三部分是外部接口,如下图
其中BMI160是BMI160陀螺仪的接口,通过IIC与主控通信。VIN为从电池来的输入电压满电为8.4V,对比5V将有更大的扭矩。servo引出了三个IO口,可以作为舵机的控制引脚或者按键(别的功能也可以)。
船新版本esp8266可动兽耳教程(?)


第三个内容为程序介绍
首先,你的电脑需要安装CH340驱动,点击可以直接下载CH340驱动下载
为了方便编写及二创,这次采用的的依旧是arduino IDE。鉴于上次点灯科技致死量的延迟,所以这次通信采用的是ESP NOW通信协议,控制设备也从手机更换为另一个同样的主板(doge)程序在此
程序部分分为两个部分,一个是舵机控制板,一个是遥控器(俩东西虽然可以用同一个板子,但是程序不一样)。
这次为了防止找不到库,所以使用的库都是在IDE内可以搜到的

先来看所需要的库
1.ESP8266wifi
2.espnow(这个好像是芯片包自带的)
3.DFRobot_BMI160
4.MobaTools

程序有三个,一个是用于获取esp8266地址的程序(可以不用,烧录程序时会显示),另一个是舵机控制板程序,最后一个是遥控器程序
接下来按顺序介绍


首先是获取ESP8266地址的程序,需要获取地址的板子是作为接收方的舵机控制板(以下程序都不要复制使用,由于编辑器特性,网页上所写程序会和正确程序有格式上的出入)

#include <ESP8266WiFi.h> 调用特定的库

void setup(){
Serial.begin(115200); 初始化串口用于调试
WiFi.mode(WIFI_AP_STA); 设置WIFI模式
}
void loop(){
Serial.println(WiFi.macAddress()); 输出当前芯片的地址
}

 


第二个程序是舵机控制板的程序,他会读取本身的BMI160或者通过ESP NOW读取遥控器上的BMI160来控制舵机

#include <ESP8266WiFi.h>
#include <espnow.h>
#include <DFRobot_BMI160.h>
#include <MobaTools.h>              //这些是调用库

MoToServo myServo1;                                          //创建舵机
MoToServo myServo2;

float filteredPos = 90;   // 初始滤波后的值
float alpha = 0.5;       // 平滑因子,0 &lt; alpha &lt; 1          //低通滤波器所需
DFRobot_BMI160 bmi160;                                       //定义BMI160传感器
const int8_t i2c_addr = 0x68;                                //设置地址
int mode =0;                                                 //创建模式变量
int16_t accelGyro[6] = {0};                                  //创建传感器返回值变量
int pos1,pos2;                                               //创建舵机角度变量
typedef struct struct_message {                              //创建发送数据结构体
   
  int message[3];
} struct_message;

struct_message myData;                                        

// 接收回调函数
void onDataRecv(uint8_t *mac, uint8_t *incomingData, uint8_t len) {               //ESP NOW接收回调函数
  memcpy(&amp;myData, incomingData, sizeof(myData));                                  //读取接收值
  Serial.print(myData.message[0]);
  Serial.print("\t");
  Serial.println(myData.message[1]);
  mode=myData.message[2];                                                         //赋值模式
}

void ICACHE_RAM_ATTR IntCallback();                                               //中断

void setup() {                                                                    //初始化
   
  Serial.begin(115200);                                                           // 初始化串口波特率112500用于调试
  attachInterrupt(digitalPinToInterrupt(0), IntCallback, RISING);                 //初始化按键中断
  pinMode(2, OUTPUT);                                                             //初始化LED
  WiFi.mode(WIFI_STA);                                                            //设置WIFI模式
  myServo1.attach(14);                                                            //初始化舵机引脚
  myServo2.attach(13);
  myServo1.setSpeed( 200 );                                                       //设置舵机运行速度
  myServo2.setSpeed( 200 ); 

  if (bmi160.softReset() != BMI160_OK){                                           //确认BMI160是否初始化
    Serial.println("reset false");
    while(1);
  }
  
  //set and init the bmi160 i2c address
  if (bmi160.I2cInit(i2c_addr) != BMI160_OK){                                     //确认IIC地址是否正确
    Serial.println("init false");
    while(1);
  }
  if (esp_now_init() != 0) {                                                      //确认ESP NOW是否初始化
    Serial.println("Error initializing ESP-NOW");
    return;
  }
  esp_now_set_self_role(ESP_NOW_ROLE_SLAVE);                                      //设置为接收设备
  esp_now_register_recv_cb(onDataRecv);                                           //创建回调函数
}

void IntCallback(){                                                               //按键中断函数(无遥控器时可用,有遥控器时失效)
  if(mode==0)
  {
    mode=1;
  }
  else if(mode==1)
  {
    mode =0;
  }
  
  if (mode==0) {
    // turn LED on:
    digitalWrite(2, HIGH);
  } else {
    // turn LED off:
    digitalWrite(2, LOW);
  }
}

float lowPassFilter(float input, float prevOutput, float alpha) {               //低通滤波器,效果一般
  return alpha * input + (1 - alpha) * prevOutput;
}

void loop() {
   if(mode==0)                                                                   //模式一,自身BMI160获取数据(模式数量和功能可自定义,不过要有一定基础)
   {
      int rslt = bmi160.getAccelGyroData(accelGyro);                             //获取BMI160数值
      
      if (rslt == 0) {                                                           //如果有数据开始处理
          float ax = accelGyro[3] * 1.0 / 16384.0; // Convert to g
          float ay = accelGyro[4] * 1.0 / 16384.0; // Convert to g
          float az = accelGyro[5] * 1.0 / 16384.0; // Convert to g

          float roll = atan2(ay, az) * 180.0 / PI; // Roll in degrees
          float pitch = atan2(-ax, sqrt(ay * ay + az * az)) * 180.0 / PI; // Pitch in degrees
          pos1=pitch*2+90;                                                       //将获取到的角度转换给舵机(随便写的,能用,要是有别的好方法可以自行替换)
          pos2=pitch*2+90;
          Serial.print("Roll: ");                                                 //串口输出调试
          Serial.print(roll);
          Serial.print(" °\tPitch: ");
          Serial.print(pitch);
          Serial.println(" °");
      } else {
          Serial.print("Error reading data: ");
          Serial.println(rslt);
      }
   }
   else if(mode==1)                                                                //模式二,遥控器获取数据
   {
    pos1=myData.message[1]*2+90;
    pos2=myData.message[1]*2+90;

   }
   else if(mode==2)                                                                //模式三,可以自己动
   {
    for(pos1;pos1&lt;179;pos1++) { myServo1.write(pos1); myServo2.write(180-pos1); delay(20); } for(pos1;pos1&gt;0;pos1--)
    {
      myServo1.write(pos1);
      myServo2.write(180-pos1);
      delay(20);
    }

   }
  myServo1.write(lowPassFilter(pos1,filteredPos,alpha));                         //把角度输出给舵机,函数里面的是低通滤波器
  myServo2.write(lowPassFilter(pos2,filteredPos,alpha)); 
  Serial.println(mode);
}

 

程序的大致思路是模式一通过读取自身的BMI160数据控制舵机,模式二通过遥控器上的BMI160控制舵机,模式三角度随时间改变,不受陀螺仪控制(有别的需求的也可以自行编写,需要一定基础)


第三个程序是遥控器的程序,他的功能是不断读取bmi160的数据并通过ESP NOW发送给舵机控制板(程序大部分相似只介绍不同的地方)

#include <ESP8266WiFi.h>
#include <espnow.h>
#include <DFRobot_BMI160.h>
// 广播地址,所有设备均可接收
uint8_t broadcastAddress[] = {                         //舵机控制板的地址,通过第一个程序获取
   0xec, 0xfa, 0xbc, 0xca, 0x8c, 0xb2};
DFRobot_BMI160 bmi160;
const int8_t i2c_addr = 0x68;
int gy[3],mode=0;;

float roll; 
float pitch;
// 发送数据结构
typedef struct struct_message {
   
  int message[3];
} struct_message;

struct_message myData;

void setup() {
  // 初始化串口监视器
  Serial.begin(115200);
  // 初始化Wi-Fi
  WiFi.mode(WIFI_STA);
  // 初始化ESP-NOW
  if (bmi160.softReset() != BMI160_OK){
    Serial.println("reset false");
    while(1);
  }
  //set and init the bmi160 i2c address
  if (bmi160.I2cInit(i2c_addr) != BMI160_OK){
    Serial.println("init false");
    while(1);
  }
  if (esp_now_init() != 0) {
   
    Serial.println("Error initializing ESP-NOW");
    return;
  }
  // 注册发送回调
  esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);                                      //设置发送函数
  esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_COMBO, 1, NULL, 0);                  //发送设置
  pinMode(0,INPUT);
}

void loop() {
  if(digitalRead(0)==0)                                                                //按键切换功能
  {
    while(digitalRead(0)==0)
    {}
    mode++;
  }
    int16_t accelGyro[6] = {0}; 
    int rslt = bmi160.getAccelGyroData(accelGyro);
    
    if (rslt == 0) {
        float ax = accelGyro[3] * 1.0 / 16384.0; // Convert to g
        float ay = accelGyro[4] * 1.0 / 16384.0; // Convert to g
        float az = accelGyro[5] * 1.0 / 16384.0; // Convert to g

        roll = atan2(ay, az) * 180.0 / PI; // Roll in degrees
        pitch = atan2(-ax, sqrt(ay * ay + az * az)) * 180.0 / PI; // Pitch in degrees

        // Serial.print("Roll: ");
        Serial.print(mode);
        Serial.print("\t ");
        Serial.print(roll);
        Serial.print("\t ");
        Serial.println(pitch);
        // Serial.println(" °");
       
    } else {
        Serial.print("Error reading data: ");
        Serial.println(rslt);
    }
   
  // 填充发送数据
  myData.message[0]=roll;
  myData.message[1]=pitch;
  myData.message[2]=mode;                                                                        //将数据赋值给数组

  // 发送数据
  esp_now_send(broadcastAddress,  (uint8_t *) &amp;myData, sizeof(myData));                          //发送

}

 

程序的大致思路是不断读取BMI160的值并进行解算,然后连同模式一起发送给舵机控制板。


最后一项就是答疑了,鉴于目前没有大量使用,所以如果有这里没有写的问题可以发送的我的邮箱(2225203467@qq.com)或者进群(916323814)反馈,解决后会更新在下方,欢迎提问。

IP2325指示灯闪烁(2024.10.28)
一般情况是因为NTC电阻那里检测异常
如果是使用的NTC电阻,检查阻值、连接或者温度有没有异常
如果使用的是电阻,请检查阻值以及焊接

这就是这块电路板的全部说明了,如果有问题欢迎大家指正。

- THE END -

heiya

5月08日15:03

最后修改:2025年5月8日
10

非特殊说明,本博所有文章均为博主原创。

共有 0 条评论