Files
Micro-Wheeled_leg-Robot/3.Software/wl_pro_robot/wl_pro_robot.ino
T
deepseek Bot 5d9e5f73b0 Add AP+STA WiFi manager and stabilize balancing
- Replace wifi.cpp/wifi.h with wifi_manager.cpp/wifi_manager.h: AP and STA
  run together, external WiFi credentials are stored in NVS and editable at
  /wifi, with optional static IP, 15s auto-reconnect, disconnect reason
  reporting, and build/version diagnostics
- Fix MPU6050 I2C port to I2C0 to match the board wiring
- Initialize distance_zeropoint at startup and on Robot Go! rising edge
- Clamp angle_zeropoint to +/-10 degrees to prevent balance-point drift
- Add DEBUG_DISABLE_YAW switch and periodic balance debug output
- Update WiFi setup instructions in README.md and README_CN.md
2026-10-25 19:15:17 +08:00

598 lines
18 KiB
Arduino
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// -----------------------------------------------------------------------------
// Copyright (c) 2024 Mu Shibo
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// -----------------------------------------------------------------------------
//机器人控制头文件
#include <MPU6050_tockn.h>
#include "Servo_STS3032.h"
#include <SimpleFOC.h>
#include <Arduino.h>
//wifi控制数据传输头文件
#include <WebSocketsServer.h>
#include <ArduinoJson.h>
#include <WebServer.h>
#include <WiFi.h>
#include <FS.h>
#include "basic_web.h"
#include "robot.h"
#include "wifi_manager.h"
#include "esp_adc_cal.h"
/************实例定义*************/
//电机实例
BLDCMotor motor1 = BLDCMotor(7);
BLDCMotor motor2 = BLDCMotor(7);
BLDCDriver3PWM driver1 = BLDCDriver3PWM(32,33,25,22);
BLDCDriver3PWM driver2 = BLDCDriver3PWM(26,27,14,12);
//编码器实例
TwoWire I2Cone = TwoWire(0);
TwoWire I2Ctwo = TwoWire(1);
MagneticSensorI2C sensor1 = MagneticSensorI2C(AS5600_I2C);
MagneticSensorI2C sensor2 = MagneticSensorI2C(AS5600_I2C);
//PID控制器实例
PIDController pid_angle {.P = 1, .I = 0, .D = 0, .ramp = 100000, .limit = 8};
PIDController pid_gyro {.P = 0.06, .I = 0, .D = 0, .ramp = 100000, .limit = 8};
PIDController pid_distance {.P = 0.5, .I = 0, .D = 0, .ramp = 100000, .limit = 8};
PIDController pid_speed {.P = 0.7, .I = 0, .D = 0, .ramp = 100000, .limit = 8};
PIDController pid_yaw_angle {.P = 1.0, .I = 0, .D = 0, .ramp = 100000, .limit = 8};
PIDController pid_yaw_gyro {.P = 0.04, .I = 0, .D = 0, .ramp = 100000, .limit = 8};
PIDController pid_lqr_u {.P = 1, .I = 15, .D = 0, .ramp = 100000, .limit = 8};
PIDController pid_zeropoint {.P = 0.002,.I = 0, .D = 0, .ramp = 100000, .limit = 4};
PIDController pid_roll_angle{.P = 8, .I = 0, .D = 0, .ramp = 100000, .limit = 450};
//低通滤波器实例
LowPassFilter lpf_joyy{.Tf = 0.2};
LowPassFilter lpf_zeropoint{.Tf = 0.1};
LowPassFilter lpf_roll{.Tf = 0.3};
// commander通信实例
Commander command = Commander(Serial);
void StabAngle(char* cmd) { command.pid(&pid_angle, cmd); }
void StabGyro(char* cmd) { command.pid(&pid_gyro, cmd); }
void StabDistance(char* cmd) { command.pid(&pid_distance, cmd); }
void StabSpeed(char* cmd) { command.pid(&pid_speed, cmd); }
void StabYawAngle(char* cmd) { command.pid(&pid_yaw_angle, cmd); }
void StabYawGyro(char* cmd) { command.pid(&pid_yaw_gyro, cmd); }
void lpfJoyy(char* cmd) { command.lpf(&lpf_joyy, cmd); }
void StabLqrU(char* cmd) { command.pid(&pid_lqr_u, cmd); }
void StabZeropoint(char* cmd) { command.pid(&pid_zeropoint, cmd); }
void lpfZeropoint(char* cmd) { command.lpf(&lpf_zeropoint, cmd); }
void StabRollAngle(char* cmd) { command.pid(&pid_roll_angle, cmd);}
void lpfRoll(char* cmd) { command.lpf(&lpf_roll, cmd); }
//void Stabtest_zeropoint(char* cmd) { command.pid(&test_zeropoint, cmd); }
//WebServer实例
WebServer webserver; // server服务器
WebSocketsServer websocket = WebSocketsServer(81); // 定义一个webSocket服务器来处理客户发送的消息
RobotProtocol rp(20);
int joystick_value[2];
//STS舵机实例
SMS_STS sms_sts;
//MPU6050实例
MPU6050 mpu6050(I2Cone);
/************参数定义*************/
#define pi 3.1415927
//LQR自平衡控制器参数
float LQR_angle = 0;
float LQR_gyro = 0;
float LQR_speed = 0;
float LQR_distance = 0;
float angle_control = 0;
float gyro_control = 0;
float speed_control = 0;
float distance_control = 0;
float LQR_u = 0;
float angle_zeropoint = -2.25;
float distance_zeropoint = -256.0; //轮部位移零点偏置(-256为一个不可能的位移值,将其作为未刷新的标志)
//YAW轴控制数据
float YAW_gyro = 0;
float YAW_angle = 0;
float YAW_angle_last = 0;
float YAW_angle_total = 0;
float YAW_angle_zero_point = -10;
float YAW_output = 0;
//腿部舵机控制数据
byte ID[2];
s16 Position[2];
u16 Speed[2];
byte ACC[2];
//逻辑处理标志位
float robot_speed = 0; //记录当前轮部转速
float robot_speed_last = 0; //记录上一时刻的轮部转速
int wrobot_move_stop_flag = 0; //记录摇杆停止的标识
int jump_flag = 0; //跳跃时段标识
float leg_position_add = 0; //roll轴平衡控制量
int uncontrolable = 0; //机身倾角过大导致失控
//【调试】测试平衡方向时置 true,屏蔽 YAW 差速,避免转向干扰轮子转向判断;测试完成改回 false
const bool DEBUG_DISABLE_YAW = false;
//电压检测
uint16_t bat_check_num = 0;
int BAT_PIN = 35; // select the input pin for the ADC
static esp_adc_cal_characteristics_t adc_chars;
static const adc1_channel_t channel = ADC1_CHANNEL_7;
static const adc_bits_width_t width = ADC_WIDTH_BIT_12;
static const adc_atten_t atten = ADC_ATTEN_DB_11;
static const adc_unit_t unit = ADC_UNIT_1;
//电量显示LED
#define LED_BAT 13
void setup() {
Serial.begin(115200);//通讯串口
Serial2.begin(1000000);//腿部sts舵机
//Wifi初始化:AP热点 + 外部WiFi(STA)
WiFi_Init();
WiFi_RegisterWebHandlers(webserver);
webserver.on("/", HTTP_GET, basicWebCallback);
webserver.begin();
websocket.begin();
websocket.onEvent(webSocketEventCallback);
//舵机初始化
//舵机有效行程450
//左侧舵机[2048+12+50,2048+12+450]
//左侧舵机[2048-12-50,2048-12-450]
sms_sts.pSerial = &Serial2;
ID[0] = 1;
ID[1] = 2;
ACC[0] = 30;
ACC[1] = 30;
Speed[0] = 300;
Speed[1] = 300;
Position[0] = 2148;
Position[1] = 1948;
//舵机(ID1/ID2)以最高速度V=2400步/秒,加速度A=50(50*100步/秒^2),运行至各自的Position位置
sms_sts.SyncWritePosEx(ID, 2, Position, Speed, ACC);
//电压检测
adc_calibration_init();
adc1_config_width(width);
adc1_config_channel_atten(channel, atten);
esp_adc_cal_characterize(unit, atten, width, 0, &adc_chars);
//电量显示LED
pinMode(LED_BAT,OUTPUT);
// 编码器设置
I2Cone.begin(19,18, 400000UL);
I2Ctwo.begin(23,5, 400000UL);
sensor1.init(&I2Cone);
sensor2.init(&I2Ctwo);
//mpu6050设置
mpu6050.begin();
mpu6050.calcGyroOffsets(true);
//连接motor对象与编码器对象
motor1.linkSensor(&sensor1);
motor2.linkSensor(&sensor2);
//速度环PID参数
motor1.PID_velocity.P = 0.05;
motor1.PID_velocity.I = 1;
motor1.PID_velocity.D = 0;
motor2.PID_velocity.P = 0.05;
motor2.PID_velocity.I = 1;
motor2.PID_velocity.D = 0;
// 驱动器设置
motor1.voltage_sensor_align = 6;
motor2.voltage_sensor_align = 6;
driver1.voltage_power_supply = 8;
driver2.voltage_power_supply = 8;
driver1.init();
driver2.init();
//连接motor对象与驱动器对象
motor1.linkDriver(&driver1);
motor2.linkDriver(&driver2);
motor1.torque_controller = TorqueControlType::voltage;
motor2.torque_controller = TorqueControlType::voltage;
motor1.controller = MotionControlType::torque;
motor2.controller = MotionControlType::torque;
// monitor相关设置
motor1.useMonitoring(Serial);
motor2.useMonitoring(Serial);
//电机初始化
motor1.init();
motor1.initFOC();
motor2.init();
motor2.initFOC();
//【调试】初始化位移零点,避免 distance_zeropoint=-256 造成的启动冲击
distance_zeropoint = -0.5f * (motor1.shaft_angle + motor2.shaft_angle);
// 映射电机到commander
command.add('A', StabAngle, "pid angle");
command.add('B', StabGyro, "pid gyro");
command.add('C', StabDistance, "pid distance");
command.add('D', StabSpeed, "pid speed");
command.add('E', StabYawAngle, "pid yaw angle");
command.add('F', StabYawGyro, "pid yaw gyro");
command.add('G', lpfJoyy, "lpf joyy");
command.add('H', StabLqrU, "pid lqr u");
command.add('I', StabZeropoint, "pid zeropoint");
command.add('J', lpfZeropoint, "lpf zeropoint");
command.add('K', StabRollAngle, "pid roll angle");
command.add('L', lpfRoll, "lpf roll");
//command.add('M', Stabtest_zeropoint, "test_zeropoint");
delay(500);
}
void loop() {
bat_check(); //电压检测
web_loop(); //Web数据更新
mpu6050.update(); //IMU数据更新
//勾选 Robot Go! 的上升沿:以当前姿态作为新的航向/位移零点
static bool go_last = false;
if (wrobot.go && !go_last) {
YAW_angle_total = 0;
YAW_angle_last = mpu6050.getAngleZ();
distance_zeropoint = -0.5f * (motor1.shaft_angle + motor2.shaft_angle);
}
go_last = wrobot.go;
lqr_balance_loop(); //lqr自平衡控制
//【调试】每100ms输出一次平衡状态
static uint32_t t_dbg = 0;
if (millis() - t_dbg > 100) {
t_dbg = millis();
Serial.printf("ang=%.2f gyro=%.2f zp=%.2f dist=%.2f u=%.2f spd=%.2f unc=%d\n",
LQR_angle, LQR_gyro, angle_zeropoint, distance_control, LQR_u, LQR_speed, uncontrolable);
}
yaw_loop(); //yaw轴转向控制
leg_loop(); //腿部动作控制
//将自平衡计算输出转矩赋给电机
const float yaw_out = DEBUG_DISABLE_YAW ? 0.0f : YAW_output;
motor1.target = (-0.5)*(LQR_u + yaw_out);
motor2.target = (-0.5)*(LQR_u - yaw_out);
//倒地失控后关闭输出
if( abs(LQR_angle) > 25.0f )
{
uncontrolable = 1;
}
if( uncontrolable != 0 )//扶起后延时恢复
{
if( abs(LQR_angle) < 10.0f )
{
uncontrolable++;
}
if( uncontrolable > 200 )//200次程序循环的延时时间
{
uncontrolable = 0;
}
}
//关停输出(遥控停止或角度过大失控)
if(wrobot.go==0 || uncontrolable!=0)
{
motor1.target = 0;
motor2.target = 0;
leg_position_add = 0;
}
//记录上一次的遥控数据数据
wrobot.dir_last = wrobot.dir;
wrobot.joyx_last = wrobot.joyx;
wrobot.joyy_last = wrobot.joyy;
//迭代计算FOC相电压
motor1.loopFOC();
motor2.loopFOC();
//设置轮部电机输出
motor1.move();
motor2.move();
command.run();
}
//lqr自平衡控制
void lqr_balance_loop(){
//LQR平衡算式,实际使用中为便于调参,讲算式分解为4个P控制,采用PIDController方法在commander中实时调试
//QR_u = LQR_k1*(LQR_angle - angle_zeropoint) + LQR_k2*LQR_gyro + LQR_k3*(LQR_distance - distance_zeropoint) + LQR_k4*LQR_speed;
//给负值是因为按照当前的电机接线,正转矩会向后转
LQR_distance = (-0.5) *(motor1.shaft_angle + motor2.shaft_angle);
LQR_speed = (-0.5) *(motor1.shaft_velocity + motor2.shaft_velocity);
LQR_angle = (float)mpu6050.getAngleY();
LQR_gyro = (float)mpu6050.getGyroY();
//Serial.println(LQR_distance);
//计算自平衡输出
angle_control = pid_angle(LQR_angle - angle_zeropoint);
gyro_control = pid_gyro(LQR_gyro);
//运动细节优化处理
if(wrobot.joyy != 0)//有前后方向运动指令时的处理
{
distance_zeropoint = LQR_distance;//位移零点重置
pid_lqr_u.error_prev = 0; //输出积分清零
}
if( (wrobot.joyx_last!=0 && wrobot.joyx==0) || (wrobot.joyy_last!=0 && wrobot.joyy==0) )//运动指令复零时的原地停车处理
{
wrobot_move_stop_flag = 1;
}
if( (wrobot_move_stop_flag==1) && (abs(LQR_speed)<0.5) )
{
distance_zeropoint = LQR_distance;//位移零点重置
wrobot_move_stop_flag = 0;
}
if( abs(LQR_speed)>15 )//被快速推动时的原地停车处理
{
distance_zeropoint = LQR_distance;//位移零点重置
}
//计算位移控制输出
distance_control = pid_distance(LQR_distance - distance_zeropoint);
speed_control = pid_speed(LQR_speed- 0.1*lpf_joyy(wrobot.joyy) );
//轮部离地检测
robot_speed_last = robot_speed; //记录连续两次的轮部转速
robot_speed = LQR_speed;
if( abs(robot_speed-robot_speed_last) > 10 || abs(robot_speed) > 50 || (jump_flag != 0)) //若轮部角速度、角加速度过大或处于跳跃后的恢复时期,认为出现轮部离地现象,需要特殊处理
{
distance_zeropoint = LQR_distance; //位移零点重置
LQR_u = angle_control + gyro_control; //轮部离地情况下,对轮部分量不输出;反之,正常状态下完整输出平衡转矩
pid_lqr_u.error_prev = 0; //输出积分清零
}
else
{
LQR_u = angle_control + gyro_control + distance_control + speed_control;
}
//触发条件:遥控器无信号输入、轮部位移控制正常介入、不处于跳跃后的恢复时期
if( abs(LQR_u)<5 && wrobot.joyy == 0 && abs(distance_control)<4 && (jump_flag == 0))
{
LQR_u = pid_lqr_u(LQR_u);//补偿小转矩非线性
//Serial.println(LQR_u);
angle_zeropoint -= pid_zeropoint(lpf_zeropoint(distance_control));//重心自适应
angle_zeropoint = constrain(angle_zeropoint, -10.0f, 10.0f); //限幅,防止平衡点跑飞
}
else
{
pid_lqr_u.error_prev = 0; //输出积分清零
}
//平衡控制参数自适应
if(wrobot.height < 50)
{
pid_speed.P = 0.7;
}
else if(wrobot.height < 64)
{
pid_speed.P = 0.6;
}
else
{
pid_speed.P = 0.5;
}
}
//腿部动作控制
void leg_loop(){
jump_loop();
if(jump_flag == 0)//不处于跳跃状态
{
//机身高度自适应控制
ACC[0] = 8;
ACC[1] = 8;
Speed[0] = 200;
Speed[1] = 200;
float roll_angle = (float)mpu6050.getAngleX() + 2.0;
//leg_position_add += pid_roll_angle(roll_angle);
leg_position_add = pid_roll_angle(lpf_roll(roll_angle));//test
Position[0] = 2048 + 12 + 8.4*(wrobot.height-32) - leg_position_add;
Position[1] = 2048 - 12 - 8.4*(wrobot.height-32) - leg_position_add;
if( Position[0]<2110 )
Position[0]=2110;
else if( Position[0]>2510 )
Position[0]=2510;
if( Position[1]<1586 )
Position[1]=1586;
else if( Position[1]>1986 )
Position[1]=1986;
sms_sts.SyncWritePosEx(ID, 2, Position, Speed, ACC);
}
}
//跳跃控制
void jump_loop(){
if( (wrobot.dir_last == 5) && (wrobot.dir == 4) && (jump_flag == 0) )
{
ACC[0] = 0;
ACC[1] = 0;
Speed[0] = 0;
Speed[1] = 0;
Position[0] = 2048 + 12 + 8.4*(80-32);
Position[1] = 2048 - 12 - 8.4*(80-32);
sms_sts.SyncWritePosEx(ID, 2, Position, Speed, ACC);
jump_flag = 1;
}
if( jump_flag > 0 )
{
jump_flag++;
if( (jump_flag > 30) && (jump_flag < 35) )
{
ACC[0] = 0;
ACC[1] = 0;
Speed[0] = 0;
Speed[1] = 0;
Position[0] = 2048 + 12 + 8.4*(40-32);
Position[1] = 2048 - 12 - 8.4*(40-32);
sms_sts.SyncWritePosEx(ID, 2, Position, Speed, ACC);
jump_flag = 40;
}
if(jump_flag > 200)
{
jump_flag = 0;//已准备好再次跳跃
}
}
}
//yaw轴转向控制
void yaw_loop(){
//YAW_output = 0.03*(YAW_Kp*YAW_angle_total + YAW_Kd*YAW_gyro);
yaw_angle_addup();
YAW_angle_total += wrobot.joyx*0.002;
float yaw_angle_control = pid_yaw_angle(YAW_angle_total);
float yaw_gyro_control = pid_yaw_gyro(YAW_gyro);
YAW_output = yaw_angle_control + yaw_gyro_control;
}
//Web数据更新
void web_loop(){
WiFi_Loop(); //WiFi状态维护/后台重连
webserver.handleClient();
websocket.loop();
rp.spinOnce();//更新web端回传的控制信息
}
//yaw轴角度累加函数
void yaw_angle_addup() {
YAW_angle = (float)mpu6050.getAngleZ();;
YAW_gyro = (float)mpu6050.getGyroZ();
if(YAW_angle_zero_point == (-10))
{
YAW_angle_zero_point = YAW_angle;
}
float yaw_angle_1,yaw_angle_2,yaw_addup_angle;
if(YAW_angle > YAW_angle_last)
{
yaw_angle_1 = YAW_angle - YAW_angle_last;
yaw_angle_2 = YAW_angle - YAW_angle_last - 2*PI;
}
else
{
yaw_angle_1 = YAW_angle - YAW_angle_last;
yaw_angle_2 = YAW_angle - YAW_angle_last + 2*PI;
}
if(abs(yaw_angle_1)>abs(yaw_angle_2))
{
yaw_addup_angle=yaw_angle_2;
}
else
{
yaw_addup_angle=yaw_angle_1;
}
YAW_angle_total = YAW_angle_total + yaw_addup_angle;
YAW_angle_last = YAW_angle;
}
void basicWebCallback(void)
{
webserver.send(300, "text/html", basic_web);
}
void webSocketEventCallback(uint8_t num, WStype_t type, uint8_t *payload, size_t length)
{
if(type == WStype_TEXT)
{
String payload_str = String((char*) payload);
StaticJsonDocument<300> doc;
DeserializationError error = deserializeJson(doc, payload_str);
String mode_str = doc["mode"];
if(mode_str == "basic")
{
rp.parseBasic(doc);
}
}
}
//电压检测初始化
void adc_calibration_init()
{
if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
printf("eFuse Two Point: Supported\n");
} else {
printf("eFuse Two Point: NOT supported\n");
}
//Check Vref is burned into eFuse
if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK) {
printf("eFuse Vref: Supported\n");
} else {
printf("eFuse Vref: NOT supported\n");
}
}
//电压检测
void bat_check()
{
if(bat_check_num > 1000)
{
//电压读取
uint32_t sum = 0;
sum= analogRead(BAT_PIN);
uint32_t voltage = esp_adc_cal_raw_to_voltage(sum, &adc_chars);
double battery=(voltage*3.97)/1000.0;
Serial.println(battery);
//电量显示
if(battery>7.8)
digitalWrite(LED_BAT,HIGH);
else
digitalWrite(LED_BAT,LOW);
bat_check_num = 0;
}
else
bat_check_num++;
}