回到首页 返回首页
回到顶部 回到顶部
返回上一页 返回上一页

【Arduino 动手做】B-Robot ESP32 版本 Unit 3 自平衡机器人 简单

头像 驴友花雕 2025.07.30 13 0

感觉我最近只做自平衡机器人,但我又做了一次。 。 。 (^_^;)
作为之前的改进,对车架进行了审查,增加了伺服系统,并将步进电机改为通用的 NEMA 14。
这款自平衡机器人基于 BROBOT EVO 打造,由 jjrobots 开源,在 B-Robot 论坛上架,但目前链接已死。

用 3D 打印机制作的框架
框架采用PLA灯丝,由于伺服安装和步进电机规格变化,与之前略有不同,但尺寸几乎相同。 这是开发图和装配的图像。

通用板使用 70mm × 50mm。
ESP32 和 DRV8825 母头的排针和步进电机和伺服的排针如图所示,短排针母头放在 ESP32 下方进行MPU6050,5V稳压器很热,所以安装了散热器,它的右侧用于电源和开关。它是一个块连接器。

MPU6050使用云台控制器自带的,商用MPU6050上的引脚数量不同,需要注意安装方向。
图片的正面是正向方向,在这个MPU6050的情况下,在这个方向上,在商业MPU6050的情况下,它看起来像下图。

从机架的开发图可以看出,底座用2mm的自攻螺钉安装在板上的板上,步进电机用8mm×3mm的螺钉固定。
切割板板和侧框的 3 毫米丝锥,并用尼龙螺钉固定(如果没有丝锥,3 毫米丝锥也可以)。
车轮安装有O型圈,切一个3mm的丝锥并用imo螺钉固定在轴上,伺服从背面用2mm的丝锥螺钉固定为SG90,舵机从正面插入MG90S并用丝锥螺钉固定,开关推入。
如图所示,电池用魔术贴固定在车架上,顺便说一句,连接器是从左侧开始的,按照伺服、左电机和右电机的顺序(从车身背面看)。
框架盖是一块0.5mm的PVC板,切成83mm×160mm,插入框架的凹槽中,将插图印刷粘贴在印章的印刷纸上。

B-Robot 论坛上发布的文章中有 Github 的链接,所以下载 zip 文件,问题的解决方法也在论坛中讨论,请大家读到最后。
我用Arduino IDE写作,所以是给用过的人准备的,网上有很多方法使用,这里就省略了。
您将需要 ESP32 库。
另外,Arduino IDE版本是Ver1.8.7,编译时出错,所以我使用了Ver1.9.0的测试版。
首先,解压从 Github 下载的 zip 文件,并将生成文件夹中的“BRobotEvo2ESP32”文件夹移动到要与 Arduino 一起使用的文件夹(通常是文档中的 Aruduino 文件夹),尽管该文件夹中有 14 个文件。删除“BRobotEvo2ESP32.ino”,因为它只包含消息,并将“ESP32SelfbalancingBot.CPP”文件重命名为“ESP32SelfbalancingBot.ino”。

另外,论坛上写的缺陷有修复,但我不记得我修复了哪个文件,所以我把这个单元3修改过的文件放在一起,做成一个Zip文件,所以我会上传它们。
B-Robot_ESP32_ZIP_file
在写草图之前,请拆下伺服连接器和电池,以防止不必要的作。

步进电机驱动器电流值调整
写入成功完成后,调整步进电机驱动器的电流值,首先在舵机和左右步进电机连接器断开的情况下测量电流值。就这个机器人而言,它的电流约为150mA
断电后,连接一个步进电机,接通电源,步进电机运行时的电流值通过驱动模块上的音量调节到250mA左右,另一个也以同样的方式调节。
这个现值对我来说刚刚好,但可能存在个体差异,所以还是实际作调整一下比较好,调整完成后,连接好所有连接器,躺着打开电源,过一会儿车轮会微微移动,当运动停止时,慢慢抬起车身,它就会站起来。

在智能手机上进行控制
jjrobots 发布了一个适用于 Android 和 iOS 的应用程序,我使用的是 Android 智能手机,所以我从 Google Play 安装了该应用程序。
当您打开 B-Robot 时,智能手机的 WiFi 设置列表中会显示“Bbot”,选择它,输入密码“12345678”,然后连接。
启动应用程序并选择“B-ROBOT EVO2”进行控制。
伺服系统可以通过右侧的按钮进行作,PID 可以通过智能手机的设置屏幕进行调整。

*笔记
如果在机器人站立时连接智能手机,作会停止片刻,机器人会摔倒,所以在机器人倒下时将智能手机连接到WiFi,然后让机器人站起来,然后在能够用智能手机控制它后让机器人站起来。

 

01.jpg
01-.jpg
01-0.jpg
01-1.jpg
02.jpg
03.jpg
04.jpg
05.jpg
06.jpg
07.jpg
08.jpg
09.jpg
09-.jpg

项目代码

 

代码
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiClient.h>
#include <ArduinoOTA.h>
#include <Wire.h>
#include "MPU6050.h"
#include <stdio.h>
#include "esp_types.h"
#include "soc/timer_group_struct.h"
#include "driver/periph_ctrl.h"
#include "driver/timer.h"
#include "driver/ledc.h"
#include "defines.h"
#include "globals.h"
#include "Motors.h"
#include "Control.h"
#include <WiFiUdp.h>
#include "OSC.h"
#include "esp32-hal-ledc.h"

void initTimers();

void initWifiAP() {
	//Serial.println("Setting up WiFi AP...");
	if (WiFi.softAP("bbot", "12345678")) {
		Serial.println("Wifi AP set up successfully");
	}
	WiFi.softAPConfig(IPAddress(192, 168, 4, 1), IPAddress(192, 168, 4, 1),
			IPAddress(255, 255, 255, 0));
}

void initMPU6050() {
	MPU6050_setup();
	delay(500);
	MPU6050_calibrate();
}

void setup() {
	pinMode(PIN_ENABLE_MOTORS, OUTPUT);
	digitalWrite(PIN_ENABLE_MOTORS, HIGH);

	pinMode(PIN_MOTOR1_DIR, OUTPUT);
	pinMode(PIN_MOTOR1_STEP, OUTPUT);
	pinMode(PIN_MOTOR2_DIR, OUTPUT);
	pinMode(PIN_MOTOR2_STEP, OUTPUT);

	pinMode(PIN_SERVO, OUTPUT);

	ledcSetup(6, 50, 16); // channel 6, 50 Hz, 16-bit width
	ledcAttachPin(PIN_SERVO, 6);   // GPIO 22 assigned to channel 1
	delay(50);
	ledcWrite(6, SERVO_AUX_NEUTRO);

	Serial.begin(115200);

	Wire.begin();

	initWifiAP();

	initMPU6050();
	initTimers();

	OSC_init();

	digitalWrite(PIN_ENABLE_MOTORS, LOW);
	for (uint8_t k = 0; k < 5; k++) {
		setMotorSpeedM1(5);
		setMotorSpeedM2(5);
		ledcWrite(6, SERVO_AUX_NEUTRO + 250);
		delay(200);
		setMotorSpeedM1(-5);
		setMotorSpeedM2(-5);
		ledcWrite(6, SERVO_AUX_NEUTRO - 250);
		delay(200);
	}
	ledcWrite(6, SERVO_AUX_NEUTRO);

	digitalWrite(PIN_ENABLE_MOTORS, HIGH);
}

void processOSCMsg() {
	if (OSCpage == 1) {
		if (modifing_control_parameters)  // We came from the settings screen
		{
			OSCfader[0] = 0.5; // default neutral values
			OSCfader[1] = 0.5;
			OSCtoggle[0] = 0;  // Normal mode
			mode = 0;
			modifing_control_parameters = false;
		}

		if (OSCmove_mode) {
			//Serial.print("M ");
			//Serial.print(OSCmove_speed);
			//Serial.print(" ");
			//Serial.print(OSCmove_steps1);
			//Serial.print(",");
			//Serial.println(OSCmove_steps2);
			positionControlMode = true;
			OSCmove_mode = false;
			target_steps1 = steps1 + OSCmove_steps1;
			target_steps2 = steps2 + OSCmove_steps2;
		} else {
			positionControlMode = false;
			throttle = (OSCfader[0] - 0.5) * max_throttle;
			// We add some exponential on steering to smooth the center band
			steering = OSCfader[1] - 0.5;
			if (steering > 0)
				steering = (steering * steering + 0.5 * steering)
						* max_steering;
			else
				steering = (-steering * steering + 0.5 * steering)
						* max_steering;
		}

		if ((mode == 0) && (OSCtoggle[0])) {
			// Change to PRO mode
			max_throttle = MAX_THROTTLE_PRO;
			max_steering = MAX_STEERING_PRO;
			max_target_angle = MAX_TARGET_ANGLE_PRO;
			mode = 1;
		}
		if ((mode == 1) && (OSCtoggle[0] == 0)) {
			// Change to NORMAL mode
			max_throttle = MAX_THROTTLE;
			max_steering = MAX_STEERING;
			max_target_angle = MAX_TARGET_ANGLE;
			mode = 0;
		}
	} else if (OSCpage == 2) { // OSC page 2
		if (!modifing_control_parameters) {
			for (uint8_t i = 0; i < 4; i++)
				OSCfader[i] = 0.5;
			OSCtoggle[0] = 0;

			modifing_control_parameters = true;
			OSC_MsgSend("$P2", 4);
		}
		// User could adjust KP, KD, KP_THROTTLE and KI_THROTTLE (fadder1,2,3,4)
		// Now we need to adjust all the parameters all the times because we dont know what parameter has been moved
		Kp_user = KP * 2 * OSCfader[0];
		Kd_user = KD * 2 * OSCfader[1];
		Kp_thr_user = KP_THROTTLE * 2 * OSCfader[2];
		Ki_thr_user = KI_THROTTLE * 2 * OSCfader[3];
		// Send a special telemetry message with the new parameters
		char auxS[50];
		sprintf(auxS, "$tP,%d,%d,%d,%d", int(Kp_user * 1000),
				int(Kd_user * 1000), int(Kp_thr_user * 1000),
				int(Ki_thr_user * 1000));
		OSC_MsgSend(auxS, 50);

#if DEBUG>0
		Serial.print("Par: ");
		Serial.print(Kp_user);
		Serial.print(" ");
		Serial.print(Kd_user);
		Serial.print(" ");
		Serial.print(Kp_thr_user);
		Serial.print(" ");
		Serial.println(Ki_thr_user);
#endif

		// Calibration mode??
		if (OSCpush[2] == 1) {
			Serial.print("Calibration MODE ");
			angle_offset = angle_adjusted_filtered;
			Serial.println(angle_offset);
		}

		// Kill robot => Sleep
		while (OSCtoggle[0] == 1) {
			//Reset external parameters
			PID_errorSum = 0;
			timer_old = millis();
			setMotorSpeedM1(0);
			setMotorSpeedM2(0);
			digitalWrite(PIN_ENABLE_MOTORS, HIGH);  // Disable motors
			OSC_MsgRead();
		}
	}
}

void loop() {
	OSC_MsgRead();

	if (OSCnewMessage) {
		OSCnewMessage = 0;
		processOSCMsg();
	}

	timer_value = micros();

	if (MPU6050_newData()) {
		MPU6050_read_3axis();

		loop_counter++;
		slow_loop_counter++;
		dt = (timer_value - timer_old) * 0.000001; // dt in seconds
		timer_old = timer_value;

		angle_adjusted_Old = angle_adjusted;
		// Get new orientation angle from IMU (MPU6050)
		float MPU_sensor_angle = MPU6050_getAngle(dt);
		angle_adjusted = MPU_sensor_angle + angle_offset;
		if ((MPU_sensor_angle > -15) && (MPU_sensor_angle < 15))
			angle_adjusted_filtered = angle_adjusted_filtered * 0.99
					+ MPU_sensor_angle * 0.01;

#if DEBUG==1
		Serial.print(dt);
		Serial.print(" ");
		Serial.print(angle_offset);
		Serial.print(" ");
		Serial.print(angle_adjusted);
		Serial.print(",");
		Serial.println(angle_adjusted_filtered);
#endif
		//Serial.print("\t");

		// We calculate the estimated robot speed:
		// Estimated_Speed = angular_velocity_of_stepper_motors(combined) - angular_velocity_of_robot(angle measured by IMU)
		actual_robot_speed = (speed_M1 + speed_M2) / 2; // Positive: forward

		int16_t angular_velocity = (angle_adjusted - angle_adjusted_Old) * 25.0; // 25 is an empirical extracted factor to adjust for real units
		int16_t estimated_speed = -actual_robot_speed + angular_velocity;
		estimated_speed_filtered = estimated_speed_filtered * 0.9
				+ (float) estimated_speed * 0.1; // low pass filter on estimated speed

#if DEBUG==2
				Serial.print(angle_adjusted);
				Serial.print(" ");
				Serial.println(estimated_speed_filtered);
#endif

		if (positionControlMode) {
			// POSITION CONTROL. INPUT: Target steps for each motor. Output: motors speed
			motor1_control = positionPDControl(steps1, target_steps1,
					Kp_position, Kd_position, speed_M1);
			motor2_control = positionPDControl(steps2, target_steps2,
					Kp_position, Kd_position, speed_M2);

			// Convert from motor position control to throttle / steering commands
			throttle = (motor1_control + motor2_control) / 2;
			throttle = constrain(throttle, -190, 190);
			steering = motor2_control - motor1_control;
			steering = constrain(steering, -50, 50);
		}

		// ROBOT SPEED CONTROL: This is a PI controller.
		//    input:user throttle(robot speed), variable: estimated robot speed, output: target robot angle to get the desired speed
		target_angle = speedPIControl(dt, estimated_speed_filtered, throttle,
				Kp_thr, Ki_thr);
		target_angle = constrain(target_angle, -max_target_angle,
				max_target_angle); // limited output

#if DEBUG==3
		Serial.print(angle_adjusted);
		Serial.print(" ");
		Serial.print(estimated_speed_filtered);
		Serial.print(" ");
		Serial.println(target_angle);
#endif

		// Stability control (100Hz loop): This is a PD controller.
		//    input: robot target angle(from SPEED CONTROL), variable: robot angle, output: Motor speed
		//    We integrate the output (sumatory), so the output is really the motor acceleration, not motor speed.
		control_output += stabilityPDControl(dt, angle_adjusted, target_angle,
				Kp, Kd);
		control_output = constrain(control_output, -MAX_CONTROL_OUTPUT,
				MAX_CONTROL_OUTPUT); // Limit max output from control

		// The steering part from the user is injected directly to the output
		motor1 = control_output + steering;
		motor2 = control_output - steering;

		// Limit max speed (control output)
		motor1 = constrain(motor1, -MAX_CONTROL_OUTPUT, MAX_CONTROL_OUTPUT);
		motor2 = constrain(motor2, -MAX_CONTROL_OUTPUT, MAX_CONTROL_OUTPUT);

		int angle_ready;
		if (OSCpush[0])     // If we press the SERVO button we start to move
			angle_ready = 82;
		else
			angle_ready = 74;  // Default angle
		if ((angle_adjusted < angle_ready) && (angle_adjusted > -angle_ready)) // Is robot ready (upright?)
				{
			// NORMAL MODE
			digitalWrite(PIN_ENABLE_MOTORS, LOW);  // Motors enable
			// NOW we send the commands to the motors
			setMotorSpeedM1(motor1);
			setMotorSpeedM2(motor2);
		} else   // Robot not ready (flat), angle > angle_ready => ROBOT OFF
		{
			digitalWrite(PIN_ENABLE_MOTORS, HIGH);  // Disable motors
			setMotorSpeedM1(0);
			setMotorSpeedM2(0);
			PID_errorSum = 0;  // Reset PID I term
			Kp = KP_RAISEUP;   // CONTROL GAINS FOR RAISE UP
			Kd = KD_RAISEUP;
			Kp_thr = KP_THROTTLE_RAISEUP;
			Ki_thr = KI_THROTTLE_RAISEUP;
			// RESET steps
			steps1 = 0;
			steps2 = 0;
			positionControlMode = false;
			OSCmove_mode = false;
			throttle = 0;
			steering = 0;
		}
		// Push1 Move servo arm
		if (OSCpush[0])  // Move arm
		{
			if (angle_adjusted > -40)
				ledcWrite(6, SERVO_MIN_PULSEWIDTH);
			else
				ledcWrite(6, SERVO_MAX_PULSEWIDTH);
		} else
			ledcWrite(6, SERVO_AUX_NEUTRO);

		// Servo2
		//ledcWrite(6, SERVO2_NEUTRO + (OSCfader[2] - 0.5) * SERVO2_RANGE);

		// Normal condition?
		if ((angle_adjusted < 56) && (angle_adjusted > -56)) {
			Kp = Kp_user;            // Default user control gains
			Kd = Kd_user;
			Kp_thr = Kp_thr_user;
			Ki_thr = Ki_thr_user;
		} else // We are in the raise up procedure => we use special control parameters
		{
			Kp = KP_RAISEUP;         // CONTROL GAINS FOR RAISE UP
			Kd = KD_RAISEUP;
			Kp_thr = KP_THROTTLE_RAISEUP;
			Ki_thr = KI_THROTTLE_RAISEUP;
		}

	} // End of new IMU data

	// Medium loop 7.5Hz
	if (loop_counter >= 15) {
		loop_counter = 0;
		// Telemetry here?
#if TELEMETRY_ANGLE==1
		char auxS[25];
		int ang_out = constrain(int(angle_adjusted * 10), -900, 900);
		sprintf(auxS, "$tA,%+04d", ang_out);
		OSC_MsgSend(auxS, 25);
#endif
#if TELEMETRY_DEBUG==1
		char auxS[50];
		sprintf(auxS, "$tD,%d,%d,%ld", int(angle_adjusted * 10), int(estimated_speed_filtered), steps1);
		OSC_MsgSend(auxS, 50);
#endif

	} // End of medium loop
	else if (slow_loop_counter >= 100) // 1Hz
			{
		slow_loop_counter = 0;
		// Read  status
#if TELEMETRY_BATTERY==1
		BatteryValue = (BatteryValue + BROBOT_readBattery(false)) / 2;
		sendBattery_counter++;
		if (sendBattery_counter >= 3) { //Every 3 seconds we send a message
			sendBattery_counter = 0;
			Serial.print("B");
			Serial.println(BatteryValue);
			char auxS[25];
			sprintf(auxS, "$tB,%04d", BatteryValue);
			OSC_MsgSend(auxS, 25);
		}
#endif
	}  // End of slow loop
}

【Arduino 动手做】B-Robot ESP32 版本 Unit 3 自平衡机器人
项目链接:
https://blogdaichan.hatenablog.com/entry/%3Fp%3D7129
https://blogdaichan.hatenablog.com/entry/%3Fp%3D7165
项目作者:大酱(id:blogdaichan)
项目视频:https://www.youtube.com/watch?v=42CqTkvmGMg
项目代码:https://github.com/ghmartin77/B-ROBOT_EVO2_ESP32
https://github.com/ghmartin77/B-ROBOT_EVO2_ESP32/blob/master/BRobotEvo2ESP32/ESP32SelfbalancingBot.cpp
https://drive.google.com/file/d/1mPW7OObvRVnkpS-6l-aGJaYy3k2D7bsu/view
3D打印文件:https://drive.google.com/file/d/1ec8WGoU-F8mJtrSDKjlTmDEM9zWmjJbQ/view

 

10.jpg
00192-.gif

评论

user-avatar
icon 他的勋章
    展开更多