基于esp32的平衡车代码(三环控制)

2023-11-06

#include <Arduino.h>
#include "BluetoothSerial.h"
#include <MPU6050_tockn.h>
#include <Wire.h>
#include<Ticker.h>
Ticker timer_read_encoder; 
BluetoothSerial SerialBT;                  //实例化esp32的蓝牙串口对象
MPU6050 mpu6050(Wire);                     //实例化mpu6050对象
  /*【编码器宏定义】*/
#define ENCODER_L       16    
#define DIRECTION_L     17
#define ENCODER_R       18
#define DIRECTION_R     23
#define interrupt_time  20   //中断时间
  /*【引脚宏定义】*/
#define IN1       26         //0号通道
#define IN2       27         //1号通道
#define IN3       32         //2号通道
#define IN4       33         //3号通道
#define sda_pin   13         //SDA
#define scl_pin   15         //SCL
#define leftOffset     50
#define rightOffset    40
  /*【直立环变量】*/
float Balance_Angle_raw =-1.8;         //机械平衡角度   
float kp=5.0;
float kd=0.74;  
float bias; 
float AngleX;                          //X轴角度  
float GyroX;                           //X轴角速度    
  /*【转向环变量】*/
float turn_kp=-0.4;                    
float turn_spd;                        //转向速度目标值 
float GyroZ;                           //Z轴的角速度
  /*【速度环变量】*/
float V_kp=1.2;
float V_ki=0.009; 
float Go_V=0;                                    //小车速度
float error;                                     //速度偏差
float integrate;                                 //偏差的积分
int   error_last;                                //小车上一次速度的偏差
float error_filter;                              //过滤过的速度偏差
int32_t Velocity_L, Velocity_R;                  //左右轮编码器数据
int32_t Velocity_Left, Velocity_Right;           //左右轮速度
  /*【PWM变量】*/
int L_PWM;
int R_PWM;
int vertical_PWM;                  //直立PWM   
int v_PWM;                         //速度PWM
int turn_PWM;                      //转向PWM
int PWM;                           //总PWM 
void READ_ENCODER_L(void)                  {//读取左边编码器的数据
  if (digitalRead(ENCODER_L) == LOW) {                       //如果是下降沿触发的中断
    if (digitalRead(DIRECTION_L) == LOW)      Velocity_L--;  //根据另外一相电平判定方向
    else      Velocity_L++;
    }
  else {     //如果是上升沿触发的中断
    if (digitalRead(DIRECTION_L) == LOW)      Velocity_L++; //根据另外一相电平判定方向
    else     Velocity_L--; 
  }
}
void READ_ENCODER_R(void)                  {//读取右边编码器的数值
  if (digitalRead(ENCODER_R) == LOW) { //如果是下降沿触发的中断
    if (digitalRead(DIRECTION_R) == LOW)      Velocity_R--;//根据另外一相电平判定方向
    else      Velocity_R++;
  }
  else {   //如果是上升沿触发的中断
    if (digitalRead(DIRECTION_R) == LOW)      Velocity_R++; //根据另外一相电平判定方向
    else     Velocity_R--;
  }
}
void read(void)                            {//计算小车的速度
    Velocity_Left = Velocity_L;    Velocity_L = 0;  //读取左轮编码器数据,并清零,这就是通过M法测速(单位时间内的脉冲数)得到速度。
    Velocity_Right = Velocity_R;    Velocity_R = 0; //读取右轮编码器数据,并清零
}
void motor(int left_EN, int right_EN)      {//马达驱动函数

  left_EN = constrain(left_EN, -255, 255);
  right_EN = constrain(right_EN, -255, 255);  //限定PWM区间在-255~255
  if (left_EN >= 0)
  {
    ledcWrite(0, left_EN);
    ledcWrite(1, 0);
  }
  if (left_EN < 0)
  {
    ledcWrite(0, 0);
    ledcWrite(1, 0 - left_EN);
  }
  if (right_EN >= 0)
  {
    ledcWrite(2, right_EN);
    ledcWrite(3, 0);
  }
  if (right_EN < 0)
  {
    ledcWrite(2, 0);
    ledcWrite(3, 0 - right_EN);
  }
}
void serial_debug()                        {//蓝牙串口调试和控制函数,根据手机端发送来的串口数据调试或控制
  if (SerialBT.available()> 0){
    char DATA = SerialBT.read();
    delay(5);
    switch (DATA)
    {
      /*【调节平衡点】*/
      case 'u':Balance_Angle_raw += 0.1;  break;
      case 'd':Balance_Angle_raw -= 0.1;  break; 
      /*【调节直立环】*/
      case '0':kp -= 0.2;                 break;
      case '1':kp += 0.2;                 break;
      case '2':kd -= 0.01;                break;
      case '3':kd += 0.01;                break;
      /*【调节速度环】*/
      case '4':V_kp-= 0.02;                break;
      case '5':V_kp += 0.02;               break;   
      case '6':V_ki -= 0.0001;             break;
      case '7':V_ki += 0.0001;             break;  
      /*【调节转向环】*/
      case '8':turn_kp -= 0.1;            break;
      case '9':turn_kp += 0.1;            break;
      /*【小车指令控制】*/
      case 's':Go_V=0;integrate=0;            break;     //停止
      case 'a':Go_V=10;integrate=0;           break;     //前进
      case 'b':Go_V=-10;integrate=0;          break;     //后退
      case 'z':turn_spd = 0;integrate=0;      break;     //不转向
      case 'l':turn_spd = 300;                break;     //左转
      case 'r':turn_spd = -300;               break;     //右转
    }
    /*【蓝牙打印输出】*/
    SerialBT.print("Balance_Angle_raw: ");SerialBT.println(Balance_Angle_raw);
    SerialBT.println("直立环控制参数:");
    SerialBT.print("kp:");SerialBT.print(kp);SerialBT.print(" kd:");SerialBT.println(kd);
    SerialBT.println("速度环控制参数:");
    SerialBT.print("V_kp:");SerialBT.print(100*V_kp);SerialBT.print("V_ki:");SerialBT.println(100*V_ki);
    SerialBT.println("转向环控制参数:");
    SerialBT.print("  turn_kp:");SerialBT.println(turn_kp);   
    SerialBT.print(" PWM:");SerialBT.print(rightOffset);SerialBT.println("------"); 
  }
}
void vertical_pwm_calculation()            {//直立PMW计算,采用PD控制
  AngleX = mpu6050.getAngleX();            //获取X轴的角度
  GyroX = mpu6050.getGyroX();              //获取X轴的角速度              
  bias = AngleX - Balance_Angle_raw;       // 计算角度偏差
  vertical_PWM = kp*bias + kd * GyroX;     //计算实时的PWM脉冲。
}
void turn_pwm_calculation()                {//转向PMW计算
  GyroZ = mpu6050.getGyroZ();              //获取陀螺仪Z轴角速度
  turn_PWM = turn_kp * (turn_spd - GyroZ);
}
void v_pwm_calculation()                   {//速度PWM计算,采用PI控制
  error=(Velocity_Left+Velocity_Right)/2-Go_V;   //计算偏差
  error_filter=0.3*error+0.7*error_last;         //一阶滤波
  error_last=error;                              //将测得的偏差变成上一次的偏差,供下次利用
  integrate=integrate+error_filter;              //将过滤过的偏差积分
  integrate = constrain(integrate,-1500,1500);   //限定误差积分的最大和最小值
  v_PWM=V_kp*error+V_ki*integrate;               //计算速度的PWM
}
void motor_control()                       {//PWM补偿,转向PWM,并控制马达输出,让电机转动
  /*【死区补偿】*/
  if (PWM > 0){L_PWM = PWM + leftOffset;R_PWM = PWM + rightOffset;}
  if (PWM < 0){L_PWM = PWM - leftOffset;R_PWM = PWM - rightOffset;}          
  if (PWM == 0){L_PWM = 0; R_PWM = 0;} 
  /*【转向控制】*/
  L_PWM -= turn_PWM;
  R_PWM += turn_PWM; 
  /*【输出限幅】*/
  L_PWM = constrain(L_PWM, -255, 255); 
  R_PWM = constrain(R_PWM, -255, 255);
  /*【判断状态】*/
  if (AngleX > 45 || AngleX < -45){motor(0, 0);}
  /*【马达输出】*/
  motor(L_PWM, R_PWM);
}
void channel()                             {//PWM通道函数
  ledcSetup(0,1000,8);ledcAttachPin(IN1,0);//设置通道0
  ledcSetup(1,1000,8);ledcAttachPin(IN2,1);//设置通道1
  ledcSetup(2,1000,8);ledcAttachPin(IN3,2);//设置通道2
  ledcSetup(3,1000,8);ledcAttachPin(IN4,3);//设置通道3
}
void Encoder_Init()                        {//编码器初始化
  pinMode(ENCODER_L, INPUT);       //编码器引脚 输入模式
  pinMode(ENCODER_R, INPUT);       //编码器引脚 输入模式
  pinMode(DIRECTION_L, INPUT);     //编码器引脚 输入模式
  pinMode(DIRECTION_R, INPUT);     //编码器引脚 输入模式 
                                                         //编码器接口1 开启外部跳边沿中断 
  attachInterrupt(ENCODER_L, READ_ENCODER_L, CHANGE);    //中断函数READ_ENCODER_L
                                                         //编码器接口2 开启外部跳边沿中断 
  attachInterrupt(ENCODER_R, READ_ENCODER_R, CHANGE);    //中断函数READ_ENCODER_R
  interrupts();                                          //打开外部中断      
  timer_read_encoder.attach_ms(interrupt_time, read);    //设置20毫秒读取一次read函数
}                                  
void setup()                               {//初始化函数
  channel();                           //选择通道
  Serial.begin(9600);                  //串口初始化
  SerialBT.begin("ESP32_car");         //蓝牙初始化
  Wire.begin(sda_pin, scl_pin);        //I2C初始化
  Encoder_Init();                      //编码器初始化
  mpu6050.begin();                             //mpu6050初始化
  mpu6050.calcGyroOffsets(true);               //mpu6050数值补偿
  motor(0, 0);                                 //马达驱动初始化
  delay(10);                                   //延时
}
void loop()                                {//主函数
  serial_debug();                //串口调试+控制
  mpu6050.update();              //陀螺仪刷新
  v_pwm_calculation();           //速度环PWM计算
  vertical_pwm_calculation();    //直立环PWM计算
  turn_pwm_calculation();        //转向PWM计算
  PWM = vertical_PWM+kp*v_PWM;   //直立环和速度环的串级PID控制,将速度环的输出量输入到角度环,从而实现对速度的控制
  motor_control();               //最终由将得到的PWM给到马达,控制马达的输出
  Serial.print("integrate");Serial.print(integrate);
  Serial.print("PWM");Serial.print(PWM);
  Serial.print("v_PWM");Serial.println(kp*v_PWM);
}

 PID部分参数需自行修改

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于esp32的平衡车代码(三环控制) 的相关文章

随机推荐

  • 如何为模型不同层设置不同的学习率?

    在模型调参中常用的一种方法是针对不同层设置不同的学习率 以此避免因难易程度不一致引起的过拟合等问题 一 模型举例 class Model nn Module def init self input size hidden size outp
  • 【cfengDB】自己实现数据库第0节 ---整体介绍及事务管理层实现

    LearnProj 内容管理 MySQL系统结构 一条SQL执行流程 cfengDB整体结构 事务管理TM模块 TID文件规则定义 文件读写 NIO RandomAccessFile FileChannel ByteBuffer 接口实现
  • 【单调栈】找到左右两边的最近小于元素

    基本概念 从一个问题引出单调栈的这个概念 给定一个数组 对于数组中的每一个元素 分别找到它左边和右边最近的小于它的元素 无重复数组 默认该数组中的元素是无重复的 我们可以维护一个栈 从栈的下方到上方 元素的大小从小到大 对于数组中的每一个元
  • OSI参考模型与TCP/IP参考模型(计算机网络)

    一 1 OSI参考模型有7层 从上到下为 应用层 表示层 会话层 传输层 网络层 数据链路层 物理层 如下图1 2 TCP IP参考模型有4层 自上到下分别为 应用层 传输层 网际层 网络接口层 如下图2 3 常考的5层参考模型是这样的 自
  • 微信小程序生成分享带参数二维码图片 并添加文字功能

    笔者最近接到一个新的任务 不是很难的功能 就是之前没有接触过 后端生成带参数的小程序二维码图片 并在图片下面添加一些文字 想在将代码分享给大家 期望可以给大家提供帮助 一 首先生成小程序的分享二维码有三种方式 接口 A 适用于需要的码数量较
  • 编程报错和问题解决办法【总结篇】

    目录 1 VMware开启虚拟机失败 模块 Disk 启动失败 2 vim 输入时光标键会变成a b c d 3 vim中delete backspace 键不能向左删除 4 conda command not found解决办法 5 进入
  • Leaflet的Vue组件 — Vue2Leaflet

    原文地址 Leaflet的Vue组件 Vue2Leaflet 这两天折腾Vue 在GitHub上发现了一个开源项目Vue2Leaflet Vue2Leaflet是一个Vue框架的JavaScript库 封装了Leaflet 它使构建地图变得
  • element Tree树形控件使用记录

    需求为使用弹窗选择区域 弹窗左侧为待选区 右侧会展示当前已选中项 也是树形控件展示 如果打开弹窗时上次有选中数据 需要展示出来并勾选相应树形节点 1 html及配置项 数据源部分 由于需求中有需要主动设置选中项 所以需要设置node key
  • 【Linux】线程池

    目录 前言 线程池概念 线程池的实现 前言 这篇文章来实现一个线程池 线程池概念 线程池 一种线程使用模式 线程过多会带来调度开销 进而影响缓存局部性和整体性能 而线程池维护着多个线程 等待着监督管理者分配可并发执行的任务 这避免了在处理短
  • Linux_基础知识笔记4

    基础知识 一 Linux目录结构 二 cat 查看文件内容 三 more 查看文件内容 内容多 四 less 查看文件内容 五 head tail 查看文件内容 六 wc 统计文件内容 七 grep 检索和过滤文件内容 八 gzip bzi
  • 利用闭包,在不设置全局变量的情况下,完成再次点击退出功能

    做APP经常会用到的功能就是 第一次点击弹出退出提示 再次点击退出app 以前常规做法 就是立flag 代码如下 var ableToOut null api addEventListener name keyback function r
  • 关于Undefined symbols for architecture x86_64这个错的总结

    最近在Mac上做一个程序 需要调用动态链接库 出现两次Undefined symbols for architecture x86 64的错误 所以总结下 第一个是 Undefined symbols for architecture x8
  • C++ 复制(拷贝)构造函数

    复制构造函数的定义 复制构造函数是一种特殊的构造函数 其形参为本类的对象引用 作用是用一个已经存在的对象去初始化同类型的新对象 复制构造函数被调用的三种情况 1 定义了一个对象 以本类另一个对象作为初始值 发生复制构造 2 如果函数的形参是
  • matlab中input输入多个数_基于MATLAB的PID控制算法仿真

    一 初学者学习目的 1 利用Matlab Simulink实现PID控制算法 2 观察不同PID参数对控制性能的影响 3 掌握PID参数整定的方法 二 实践内容 1 以二阶系统 为被控对象 K 135 在阶跃输入信号的作用下 用simuli
  • keil添加了头文件仍然报找不到头文件的原因

    1 如果工程中有中文路径 keil是无法识别中文路径的 需要修改为英文路径 2 如果工程中的路径存在数字开头 则keil无法识别该路径 需要修改为以英文字符开头 3 如果修改了工程中的文件夹名 则需要重新将文件夹包含到工程中 4 点击魔术棒
  • Datax插件二次开发之HdfsWriter支持parquet

    Datax插件二次开发之HdfsWriter支持parquet Date December 24 2021 1 背景 目前 公司的OLAP和AD HOC组件主要使用impala 而当前我们的impala版本支持parquet textfil
  • 用c++创造编译器(Brainfuck)

    目录 初始化 主算法 思路 完整代码 补充 请先看此文章 大家了解之后 可能会想 这么简单的语言 能不能用代码写出一个Brainfuck呢 哈哈 当然可以 作为第一次发布文章的博主 就来帮帮你吧 初始化 include
  • RocketMq客户端日志参数设置

    使用的RocketMq版本为4 7 1 RocketMq的客户端日志打印 Logger的创建代码在org apache rocketmq client log ClientLogger中 部分代码如下 public static final
  • Unity Shader中使用GLSL创建材质

    目录 Unity Shader格式 Properties 怎么在脚本中使用类似于glUniform 的功能呢 SubShader Tags LOD pass pass内的tags说明 pass内的代码段 GLSL GLSL与CG语言的差异
  • 基于esp32的平衡车代码(三环控制)

    include