PID控制结构&角度环实现直立

平衡车PID控制结构

平均PWM:左右轮PWM的平均值,控制的是前进和后退的快慢。(简单来说就是平均PWM单独控制前后)

差速PWM:两个电机PWM的差值,很明显控制的是左右转弯的快慢。(简单来说差速PWM单独控制左右)

速度环与角度环的关系:

角度环(内环)

  • 任务:保持车身直立

  • 原理:车身前倾→轮子向前转;后倾→向后转

  • 输入:俯仰角

  • 输出:差分PWM(左右轮差速)

速度环(外环)

  • 任务:控制前进/后退/静止

  • 原理:检测实际速度,与目标速度比较,调整驱动力

  • 输入:编码器速度

  • 输出:平均PWM(左右轮同速)

两者如何配合

复制代码
左轮PWM = 平均PWM + 差分PWM / 2 
右轮PWM = 平均PWM - 差分PWM / 2

平均速度 = (左轮速度 + 右轮速度) / 2

差分速度 = 左轮速度 -- 右轮速度

  • 角度环负责站稳

  • 速度环负责跑多快

  • 两者叠加输出给电机,互不干扰

一句话总结

角度环让车不倒,速度环让车听话,两者叠加驱动电机,实现直立行走。

转向环:**控制小车左转和右转。**右边输入的是用户设定的目标转弯速度,如果小车实际走的是直线,测量得到左轮速度和右轮速度绝对一致(差分速度为0)。如果不是0,就说明小车走偏了,转向环会比对目标转弯速度和实际转弯速度。如果向左走偏了,就控制小车往右走。如果向右走偏了,就控制小车往左走。也就是通过控制差分PWM来控制小车左右转弯

举例(转向环):如果没有转向环的闭环控制,那么,你让这个平衡车走直线的时候,左右轮的PWM就可能会不一样(电机差异导致),这就导致了小车可能不能走直线。

加入PID后,互补滤波由原来的1ms调整一次变成了10ms调整一次,所以需要调整α的大小(由0.001变为0.01)。

虽然这样子之后小车的角度测量效果不错,但是后期却出现了小车无论如何直立不了的问题。问题出在了MPU6050上。

这个值是0x06,对应滤波器配置最大,输出数据最平滑,会导致Delay,导致小车无论怎么调都无法保持直立。

我们这里将0x06改成0x00,不使用自带的低通滤波器。

再看采样率分频,不使用自带的滤波器后,陀螺仪时钟的频率是8kHz,那么采样率就是8kHz / (1 + 9)= 800Hz,如果这个值改成7,那么采样率就是8kHz / (1 + 7)= 1kHz(这里的采样率就是MPU6050的刷新频率)。

代码逻辑:

cpp 复制代码
if (RunFlag)
			{
				AnglePID.Actual = Angle;
				PID_Update(&AnglePID);
				AvePWM = -AnglePID.Out;
				
				LeftPWM = AvePWM + DifPWM / 2;
				RightPWM = AvePWM - DifPWM / 2;
				
				if (LeftPWM > 100) {LeftPWM = 100;} else if (LeftPWM < -100) {LeftPWM = -100;}
				if (RightPWM > 100) {RightPWM = 100;} else if (RightPWM < -100) {RightPWM = -100;}
				
				Motor_SetPWM(1, LeftPWM);
				Motor_SetPWM(2, RightPWM);
			}
			else
			{
				Motor_SetPWM(1, 0);
				Motor_SetPWM(2, 0);
			}

之后我们开始调节P、I、D的参数:

cpp 复制代码
if (BlueSerial_RxFlag == 1)
		{
			char *Tag = strtok(BlueSerial_RxPacket, ",");
			if (strcmp(Tag, "key") == 0)							//收到按键数据包
			{
				char *Name = strtok(NULL, ",");
				char *Action = strtok(NULL, ",");
				
			}
			else if (strcmp(Tag, "slider") == 0)					//滑杆数据包
			{
				char *Name = strtok(NULL, ",");
				char *Value = strtok(NULL, ",");
				
				if (strcmp(Name, "AngleKp") == 0)
				{
					AnglePID.Kp = atof(Value);
				}
				else if (strcmp(Name, "AngleKi") == 0)
				{
					AnglePID.Ki = atof(Value);
				}
				else if (strcmp(Name, "AngleKd") == 0)
				{
					AnglePID.Kd = atof(Value);
				}
			}
			else if (strcmp(Tag, "joystick") == 0)					//摇杆数据包
			{
				int8_t LH = atoi(strtok(NULL, ","));
				int8_t LV = atoi(strtok(NULL, ","));
				int8_t RH = atoi(strtok(NULL, ","));
				int8_t RV = atoi(strtok(NULL, ","));
				
				AnglePID.Target = LV / 10;
				DifPWM = RH / 2;
			}
			
			BlueSerial_RxFlag = 0;
		}
		
		BlueSerial_Printf("[plot,%f,%f]", AnglePID.Target, Angle);

这里我们通过蓝牙模块进行调节:

相关推荐
不悔哥4 天前
开源OV-Watch:怎么做一个智能手表
单片机·开源·嵌入式
XiHongShi20164 天前
STM32F407 RTC定时器例程,建议保存
stm32·单片机·学习
优信电子4 天前
AIT1001 非接触式红外测温模块 STM32 驱动开发实战:从引脚到代码全解析
stm32·嵌入式·温度传感器·红外测温·非接触式测温·ait1001
深圳老胡4 天前
STM32F407 控制 L6470 步进电机驱动 —— 控制过程简介
笔记·stm32·单片机·嵌入式硬件·代码规范
wuyk5554 天前
《WiFi 嵌入式物联网开发全套实战》| 第 16 章 ESP32 AP+STA 双模共存原理与工程坑点
网络·stm32·物联网
AIgorithmGEEK4 天前
[Linux]线程三部曲(上):一个执行流的诞生——从操作系统一路拆到 pthread_create
linux·线程·pid
Brilliantwxx5 天前
【STM32】 SPI Flash 驱动开发实战:阻塞模式驱动 W25Q64(含工程代码)
开发语言·stm32·单片机·嵌入式硬件
youcans_5 天前
【嵌入式软件AI编程】16. Claude Code 与 VS Code 的协同开发
stm32·mcu·嵌入式·ai编程·claude code
Zw-awa5 天前
按键接到 STM32 后:GPIO 输入到底读到了什么
stm32
阿钱真强道5 天前
06 嵌入式操作系统 | vim 与软件管理:apt / 源码编译
ubuntu·vim·嵌入式·apt·源码编译