学习链接
ESP32入门
ESP32入门学习2
ESP32开发环境配置
ESP32学习视频
arduino入门
立创ESP32开源
遥控器开源
神书.四旋翼飞行器设计与实现 7.卡尔曼滤波在智能车中的应用 8.MPU6050与磁力计融合:攻克Z轴角度漂移的血泪史 9.逻辑分析仪的使用和安装 10.虚拟机的使用
基础编译学习 Arduino IDE 的三个主要函数的作用:
setup() 初始化一次,loop()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void setup () { pinMode (LED_BUILTIN, OUTPUT); int result = calculateSum (2 , 3 ); } void loop () { blinkLED (); delay (1000 ); } int calculateSum (int a, int b) { return a + b; } void blinkLED () { digitalWrite (LED_BUILTIN, HIGH); delay (500 ); digitalWrite (LED_BUILTIN, LOW); }
ESP32S2-学习 ESP32-S2原理图: S2-mini点灯代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <Arduino.h> int myFunction (int , int ) ;void setup () { pinMode (LED_BUILTIN,OUTPUT); } void loop () { digitalWrite (LED_BUILTIN,HIGH); delay (1000 ); digitalWrite (LED_BUILTIN,LOW); delay (1000 ); } int myFunction (int x, int y) { }
HW-724SSD1306 屏幕的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 #include <Wire.h> #include "SSD1306.h" SSD1306 display (0x3c , 5 , 4 ) ;void setup () { Serial.begin (115200 ); Serial.println ("OLED显示屏初始化中..." ); display.init (); display.flipScreenVertically (); display.clear (); display.setFont (ArialMT_Plain_24); display.setTextAlignment (TEXT_ALIGN_CENTER); display.drawString (64 , 10 , "CZX" ); display.setFont (ArialMT_Plain_16); display.drawString (64 , 40 , "Hello OLED!" ); display.setFont (ArialMT_Plain_10); display.setTextAlignment (TEXT_ALIGN_LEFT); display.drawString (0 , 55 , "ESP8266" ); display.setTextAlignment (TEXT_ALIGN_RIGHT); display.drawString (128 , 55 , "2024" ); display.display (); Serial.println ("文本显示完成!" ); } void loop () { delay (1000 ); }
ESP32的WIFI功能 四旋翼飞行器学习 卡尔曼滤波算法 一个笑话 一片绿油油的草地上有一条曲折的小径,通向一棵大树。一个要求被提出:从起点沿着小径走到树下。
“很简单。”A说,于是他丝毫不差地沿着小径走到了树下。
现在,难度被增加了:蒙上眼。
“也不难,我当过特种兵。”B说,于是他歪歪扭扭地走到了树旁。“唉,好久不练,生疏了。”(只凭自己的预测能力)
“看我的,我有DIY的GPS!”C说,于是他像个醉汉似地歪歪扭扭的走到了树旁。“唉,这个GPS没做好,漂移太大。”(只依靠外界有很大噪声的测量)
“我来试试。”旁边一也当过特种兵的拿过GPS,蒙上眼,居然沿着小径很顺滑的走到了树下。(自己能预测+测量结果的反馈)
“这么厉害!你是什么人?”
“卡尔曼!”
“卡尔曼?!你就是卡尔曼?”众人大吃一惊。
“我是说这个GPS卡而慢。”
卡尔曼滤波算法简介
引入协方差,协方误差,协方误差
方差 是特殊的协方差(一个变量与自身的协方差),均方误差 是评估预测效果的重要指标,而协方差 是理解多个变量间关系的关键。均方误差 (MSE - Mean Squared Error) 核心: 衡量 估计值 (或测量值) 与 真实值 之间的偏差程度。 特点: 评估的是“估计的准确性”或“测量的精确度”。误差越大,均方误差越大,说明估计或测量结果越不准确。方差 (Variance) 核心: 描述 单个随机变量 自身的离散程度或波动大小。 特点: 衡量的是变量值围绕其 期望值(均值) 的波动情况。方差越大,数据越分散。协方差 (Covariance) 核心: 描述 两个随机变量 之间的变化趋势及相关性。 特点: 正值 表示两变量变化趋势一致(你大我也大)。 负值 表示两变量变化趋势相反(你大我小)。 零 表示两变量没有线性相关关系
卡尔曼滤波的应用场景 1.当不能直接测量得到想要的数据时,应用卡尔曼滤波来间接测量 2.在有噪音情况下结合不同传感器来找到的最佳估计值
如在飞控上的应用
姿态角估计 通过建立含有姿态角,角速度,加速度等状态量的模型,通过IMU的数据进行卡尔曼滤波,重新估计出状态量,间接得到姿态角的数据 位置估计(组合导航): 加速度一次,二次积分可以得到速度,位置,GPS测量可以得到位置,通过卡尔曼滤波可以融合加速度计和GPS数据得到更精确的位置
智能车在卡尔曼滤波算法中的应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int kalman_filter (kalman_param *kfp, int input) { kfp->Now_P = kfp->LastP + kfp->Q; kfp->Kg = kfp->Now_P / (kfp->Now_P + kfp->R); kfp->out = kfp->out + kfp->Kg * (input-kfp->out); kfp->LastP = (1 -kfp->Kg) * kfp->Now_P; return kfp->out; }
四轴飞控发展历史 四轴飞控飞行原理 四旋翼飞行器通过改变四个旋翼的转速,依据力的合成与扭矩平衡原理实现灵活飞行;其相邻旋翼转向相反、对角转向相同,并分别配以正、反螺旋桨来共同产生向上的升力,且所有旋翼均需向下吹风以确保提供向上的拉力。
四轴飞控的控制算法 PID控制 飞控转向控制逻辑 1 2 3 4 5 6 7 8 graph TD A[传感器数据] --> B[姿态控制器] B --> C[生成控制量: roll, pitch, yaw] C --> D[油门权重计算] E[油门输入] --> D D --> F[电机混控器] F --> G[计算电机输出 R1-R4] G --> H[输出到电调]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 stateDiagram-v2 [*] --> 悬停状态 悬停状态 --> 横滚运动: out_roll ≠ 0 悬停状态 --> 俯仰运动: out_pitch ≠ 0 悬停状态 --> 偏航运动: out_yaw ≠ 0 横滚运动 --> 悬停状态: out_roll = 0 俯仰运动 --> 悬停状态: out_pitch = 0 偏航运动 --> 悬停状态: out_yaw = 0 横滚运动 --> 复合运动: 其他控制量 ≠ 0 俯仰运动 --> 复合运动: 其他控制量 ≠ 0 偏航运动 --> 复合运动: 其他控制量 ≠ 0 复合运动 --> 悬停状态: 所有控制量 = 0
运动类型
控制量
电机转速变化
横滚(Roll)
out_roll
左右电机差动
俯仰(Pitch)
out_pitch
前后电机差动
偏航(Yaw)
out_yaw
对角电机差动
油门权重
Thr_Weight
小油门时抑制控制量
高度控制和PID算法
串极PID控制逻辑
1 2 3 遥控器输入 → 速度环PID → 位置环PID → 电机控制 ↓ ↓ ↓ 期望速度 ← 期望位置 ← 传感器反馈
PID代码是示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 #include <math.h> typedef struct { float kp, ki, kd; float err, err_i, err_d, err_old; float output; } PID_Controller; typedef struct { PID_Controller pos_pid; PID_Controller speed_pid; float target_height; float target_speed; float base_throttle; } Height_Controller; float position_pid_update (PID_Controller* pid, float target, float current, float speed, float thr_weight, float dt) { pid->err = pid->kp * (target - current); pid->err_i += pid->ki * pid->err * dt; pid->err_i = constrain_float (pid->err_i, -thr_weight * 300 , thr_weight * 300 ); pid->err_d = pid->kd * (0.6f * (-speed * dt) + 0.4f * (pid->err - pid->err_old)); pid->err_old = pid->err; return pid->err + pid->err_i + pid->err_d; } float speed_pid_update (PID_Controller* pid, float pos_output, float target_speed, float current_speed, float acceleration, float base_throttle, float thr_weight, float dt) { pid->err = pid->kp * (target_speed - current_speed); pid->err_d = (0.002f / 10.0f ) * 10 * pid->kd * (-acceleration) * dt; pid->err_i += pid->ki * pid->kp * (pos_output - current_speed) * dt; pid->err_i = constrain_float (pid->err_i, -thr_weight * 300 , thr_weight * 300 ); float pid_sum = pid->kp * target_speed + pid->err + pid->err_d + pid->err_i; return base_throttle * 2 + thr_weight * constrain_float (pid_sum, -300 , 300 ); } float calculate_target_speed (float throttle) { float throttle_diff = throttle - 500 ; if (fabs (throttle_diff) <= 50 ) return 0 ; float deadzone_output = (throttle_diff > 0 ) ? (throttle_diff - 50 ) : (throttle_diff + 50 ); return 300.0f * deadzone_output / 200.0f ; } float height_control_update (Height_Controller* ctrl, float current_height, float current_speed, float acceleration, float throttle, float thr_weight, float dt) { ctrl->target_speed = calculate_target_speed (throttle); ctrl->target_height += ctrl->target_speed * dt; float pos_output = position_pid_update (&ctrl->pos_pid, ctrl->target_height, current_height, current_speed, thr_weight, dt); return speed_pid_update (&ctrl->speed_pid, pos_output, ctrl->target_speed, current_speed, acceleration, ctrl->base_throttle, thr_weight, dt); } float constrain_float (float value, float min_val, float max_val) { if (value < min_val) return min_val; if (value > max_val) return max_val; return value; }
代码逻辑图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 graph TD A[遥控器油门输入] --> B[calculate_target_speed<br/>计算期望速度] B --> C[target_speed<br/>期望垂直速度] C --> D[积分器<br/>target_height += target_speed × dt] D --> E[target_height<br/>期望高度] F[current_height<br/>当前高度] --> G[位置环PID] E --> G H[current_speed<br/>当前速度] --> G G --> I[pos_output<br/>位置环输出] I --> J[速度环PID] C --> J H --> J K[current_speed<br/>当前速度] --> J L[acceleration<br/>当前加速度] --> J M[base_throttle<br/>基础油门] --> J J --> N[final_throttle<br/>最终油门输出] O[thr_weight<br/>油门权重] --> G O --> J P[dt<br/>控制周期] --> G P --> J P --> D style G fill:#e1f5fe style J fill:#f3e5f5 style B fill:#e8f5e8 style D fill:#fff3e0 style N fill:#ffebee
串极PID控制 外环:负责最终的控制目标。它根据设定值 和主被控量 的偏差进行计算,其输出作为输入量 内环:负责快速响应副被控量 的变化和内部扰动,其输出直接作用于执行机构。
普通PID和串极PID的区别: 单PID:就像你盯着速度表,看到速度表底了,就踩油门,速度高了,就松油门 串极PID: 主控制器(外环:你的大脑):目标维持60km/h,它根据当前速度和60的偏差,决定一个“期望的油门深度”。 副控制器(内环:你的脚):目标是维持大脑给出的“期望油门深度”。如果遇到一个小坑导致车身抖动,你的脚会立刻感觉到油门的细微变化,并迅速做出调整来稳定它,而不需要等速度表发生明显变化
那么为什么条调参要先调整内环?
就如同单极PID,虽然不够稳定,但她依然可以调节系统的平衡,从而保证车速,单内环作为执行机构,也是可以保持系统的稳定的 而加上大脑就如同在已经快要稳定的系统中,加入大脑的微操,从而导致系统更加稳定
PID的调参秘诀:
四轴飞控硬件 四轴飞控硬件大致框架:
如何解决MPU6050零漂问题? 1.我的想法是MPU6050加一个磁力技基础上在加一个滤波算法读出6050的值
关于ESP32 ESP32是一款ESP32是一款高性能、低功耗的Wi-Fi/蓝牙双模物联网芯片 ,集成了丰富的外设和强大的计算能力。
使用ESP32进行四轴飞控的开发,作为四轴飞控核心,凭借双核处理、硬件PWM和Wi-Fi/蓝牙一体化,可实现低成本高响应的无人机控制;适合轻量级应用,但复杂环境需优化实时性
机械方面