一、 硬件组成

平衡车部分

1、STM32F103C8T6核心板:用于PWM控制平衡车电机、显示屏幕、蓝牙及无线接收、LED灯、计算轮数灯

2、底板:用于引出核心板信号,链接蓝牙模块、无线模块、按键模块、提供各模块供电灯

3、编码电机两个、电池、车轮

遥控器部分

1、基于STM32F103C8T6处理器的遥控器、显示屏幕、无线接收、摇杆等,具体见原理图。

二、原理图资料

底板原理图:

遥控器原理图

三、软件应用代码

(1)机载部分主函数部分

int main(void)
{
	/*初始化*/
	OLED_Init();//屏幕初始化并清零
	LED_Init();//LED灯初始化
	Key_Init();//按键初始化
	Motor_Init();//电机PWM初始化
	Encoder_Init();//编码器计数中断初始化
	MPU6050_Init();//MPU6050初始化
	BlueSerial_Init();//蓝牙初始化
	Serial_Init();//串口初始化
	NRF24L01_Init();//无线模块初始化
	
	Timer_Init();//定时器初始化
	
	/*显示初始界面*/
	OLED_Clear();//屏幕清零

	OLED_ShowString(4, 16, " 平衡车测试程序 ", OLED_8X16);//显示内容、坐标、文字像素宽度
	OLED_ShowString(0, 32, "      V1.0      ", OLED_8X16);//显示内容、坐标、文字像素宽度
	OLED_ShowString(0, 48, "             K4>", OLED_8X16);//显示内容、坐标、文字像素宽度
	OLED_Update();
	
	/*按K4键继续*/
	while (Key_Check(KEY_4, KEY_SINGLE) == 0);//KEY_SINGLE				0x08
	Key_Clear();
	
	/*初始化FLASH并检查FLASH中有没有存储参数*/
	if (Store_Init())
	{
		/*未存储参数,将程序中的默认参数保存到FLASH中*/
		SaveParam();
		
		/*提示已重置参数*/
		OLED_Clear();
		OLED_ShowString(0, 0,  "     [提示]     ", OLED_8X16);
		OLED_ShowString(0, 16, "   已重置参数   ", OLED_8X16);
		OLED_ShowString(0, 32, " 请注意执行校准 ", OLED_8X16);
		OLED_ShowString(0, 48, "             K4>", OLED_8X16);
		OLED_Update();
		
		/*按K4键继续*/
		while (Key_Check(KEY_4, KEY_SINGLE) == 0);
		Key_Clear();
	}
	
	/*上电后加载FLASH中保存的参数*/
	LoadParam();
	
	OLED_Clear();
	BlueSerial_ClearBuffer();//蓝牙清理缓存
	
	while (1)
	{
		/*指示灯*/
		if (RunFlag) {LED_ON();} else {LED_OFF();}
		
		/*按键*/
		if (Key_Check(KEY_1, KEY_SINGLE))		//K1按下,启动/停止
		{
			if (RunFlag == 0)
			{
				PID_Init(&AnglePID);
				PID_Init(&SpeedPID);
				PID_Init(&TurnPID);
				Angle = AngleAcc_Filter;
				RunFlag = 1;
			}
			else
			{
				RunFlag = 0;
			}
			BlueSerial_Printf("RunFlag=%d\r\n", RunFlag);
		}
		if (RunFlagUpdate)
		{
			RunFlagUpdate = 0;
			BlueSerial_Printf("RunFlag=%d\r\n", RunFlag);
		}
		if (Key_Check(KEY_2, KEY_SINGLE))		//K2按下,速度等级减1
		{
			if (SpeedLevel > 1) 
			{
				SpeedLevel --;
				SaveParam();
			}
			BlueSerial_Printf("SpeedLevel=%d\r\n", SpeedLevel);
		}
		if (Key_Check(KEY_3, KEY_SINGLE))		//K3按下,速度等级加1
		{
			if (SpeedLevel < 6)
			{
				SpeedLevel ++;
				SaveParam();
			}
			BlueSerial_Printf("SpeedLevel=%d\r\n", SpeedLevel);
		}
		if (Key_Check(KEY_4, KEY_SINGLE))		//K4按下,进入调试模式
		{
			DebugFlag = 1;
			RunFlag = 0;
			Motor_SetPWM(1, 0);
			Motor_SetPWM(2, 0);
			LED_OFF();
			Key_Clear();
			DebugMode();	//执行调试模式函数
		}
		
		/*蓝牙串口*/
		if (BlueSerial_ReceiveFlag())	//判断是否收到蓝牙数据包
		{
			BlueSerial_Receive();		//收到数据包,接收并解析数据包
			
			/*摇杆数据包,格式为:[joystick,LH,LV,RH,RV]*/
			if (strcmp(BlueSerial_StringArray[0], "joystick") == 0)
			{
				/*得到摇杆数据,转换为数值*/
				int8_t LH = atoi(BlueSerial_StringArray[1]);
				int8_t LV = atoi(BlueSerial_StringArray[2]);
				int8_t RH = atoi(BlueSerial_StringArray[3]);
				int8_t RV = atoi(BlueSerial_StringArray[4]);
				
				/*设定目标速度和目标转向幅度*/
				SpeedPID.Target = LV / 100.0 * SpeedLevel;
				TurnPID.Target = RH / 100.0 * SpeedLevel;
			}
			/*按键数据包,格式为:[key,按键名称,down/up]*/
			else if (strcmp(BlueSerial_StringArray[0], "key") == 0)
			{
				if (strcmp(BlueSerial_StringArray[1], "1") == 0 
				 && strcmp(BlueSerial_StringArray[2], "up") == 0)		//收到数据包[key,1,up]
				{
					Key_Flag[KEY_1] |= KEY_SINGLE;		//触发平衡车K1键按下
				}
				else if (strcmp(BlueSerial_StringArray[1], "2") == 0 
					  && strcmp(BlueSerial_StringArray[2], "up") == 0)	//收到数据包[key,2,up]
				{
					Key_Flag[KEY_2] |= KEY_SINGLE;		//触发平衡车K2键按下
				}
				else if (strcmp(BlueSerial_StringArray[1], "3") == 0 
					  && strcmp(BlueSerial_StringArray[2], "up") == 0)	//收到数据包[key,3,up]
				{
					Key_Flag[KEY_3] |= KEY_SINGLE;		//触发平衡车K3键按下
				}
			}
		}
		
		/*无线模块*/
		if (NRF24L01_Receive() == 1)	//判断是否收到无线模块数据包
		{
			if (NRF24L01_RxPacket[0] == 1)		//字节0为1,表示需要返回状态信息
			{
				/*写入相关的状态信息*/
				NRF24L01_TxPacket[0] = 0x00;					//0
				NRF24L01_TxPacket[1] = SpeedLevel;				//1
				NRF24L01_TxPacket[2] = PWML;					//2
				NRF24L01_TxPacket[3] = PWMR;					//3
				*(float *)&NRF24L01_TxPacket[4] = Angle;		//4 5 6 7
				*(float *)&NRF24L01_TxPacket[8] = SpeedLeft;	//8 9 10 11
				*(float *)&NRF24L01_TxPacket[12] = SpeedRight;	//12 13 14 15
				
				/*发送状态信息给遥控器,以便在遥控器上观察平衡车状态*/
				NRF24L01_Send();
			}
			
			/*得到遥控器数据*/
			int8_t LV = NRF24L01_RxPacket[2];
			int8_t RH = NRF24L01_RxPacket[3];
			uint8_t KEY0 = NRF24L01_RxPacket[5];
			
			/*设定目标速度和目标转向幅度*/
			SpeedPID.Target = LV / 100.0 * SpeedLevel;
			TurnPID.Target = RH / 100.0 * SpeedLevel;
			
			/*处理遥控器按键事件*/
			if (KEY0 & 0x01)					//遥控器K1键按下
			{
				Key_Flag[KEY_3] |= KEY_SINGLE;	//触发平衡车K3键按下
			}
			if (KEY0 & 0x02)					//遥控器K2键按下
			{
				Key_Flag[KEY_2] |= KEY_SINGLE;	//触发平衡车K2键按下
			}
			if (KEY0 & 0x04)					//遥控器K3键按下
			{
				Key_Flag[KEY_1] |= KEY_SINGLE;	//触发平衡车K1键按下
			}
			
		}
		
		/*OLED显示*/
		OLED_Printf(0, 0, OLED_6X8, "Angle  Speed  Turn");
		OLED_Printf(0, 10, OLED_6X8, "%+06.1f %+06.1f %+06.1f", AnglePID.Target, SpeedPID.Target, TurnPID.Target);
		OLED_Printf(0, 18, OLED_6X8, "%+06.1f %+06.1f %+06.1f", Angle, AveSpeed, DifSpeed);
		OLED_Printf(0, 26, OLED_6X8, "%+06.1f %+06.1f %+06.1f", AnglePID.Out, SpeedPID.Out, TurnPID.Out);
		
		OLED_DrawLine(0, 36, 127, 36);
		
		OLED_Printf(0, 40, OLED_6X8, "PWML:%+05d PWMR:%+05d", PWML, PWMR);
		OLED_Printf(0, 48, OLED_6X8, "SpdL:%+05.1f SpdR:%+05.1f", SpeedLeft, SpeedRight);
		OLED_Printf(0, 56, OLED_6X8, "SpdLevel:%1d", SpeedLevel);
		OLED_Update();
	}
}

(2)遥控器部分主函数代码

int main(void)
{
	/*初始化*/
	OLED_Init();
	Key_Init();
	AD_Init();
	NRF24L01_Init();
	Timer_Init();
	
	/*显示初始界面*/
	OLED_Clear();
	OLED_ShowString(0, 0,  "   [江协科技]   ", OLED_8X16);
	OLED_ShowString(4, 16, " 遥控器测试程序 ", OLED_8X16);
	OLED_ShowString(0, 32, "      V1.0      ", OLED_8X16);
	OLED_ShowString(0, 48, "            K10>", OLED_8X16);
	OLED_Update();
	
	/*按K10键继续*/
	while (Key_Check(KEY_10, KEY_SINGLE) == 0);
	Key_Clear();

	OLED_Clear();
	
	while (1)
	{
		KeyNum = 0;
		
		/*读取左右摇杆的AD值*/
		AD_LH = AD_GetValue(ADC_Channel_0);
		AD_LV = AD_GetValue(ADC_Channel_1);
		AD_RH = AD_GetValue(ADC_Channel_2);
		AD_RV = AD_GetValue(ADC_Channel_3);
		
		/*对AD值进行数据处理,得到摇杆数据*/
		LH = DataProcess(AD_LH);
		LV = DataProcess(AD_LV);
		RH = DataProcess(AD_RH);
		RV = DataProcess(AD_RV);
		
		/*读取按键*/
		if (Key_Check(KEY_1, KEY_SINGLE))
		{
			KEY0 |= 0x01;
			KeyNum = 1;
		}
		if (Key_Check(KEY_2, KEY_SINGLE))
		{
			KEY0 |= 0x02;
			KeyNum = 2;
		}
		if (Key_Check(KEY_3, KEY_SINGLE))
		{
			KEY0 |= 0x04;
			KeyNum = 3;
		}
		if (Key_Check(KEY_4, KEY_SINGLE))
		{
			KEY0 |= 0x08;
			KeyNum = 4;
		}
		if (Key_Check(KEY_5, KEY_SINGLE))
		{
			KEY0 |= 0x10;
			KeyNum = 5;
		}
		if (Key_Check(KEY_6, KEY_SINGLE))
		{
			KEY0 |= 0x20;
			KeyNum = 6;
		}
		if (Key_Check(KEY_7, KEY_SINGLE))
		{
			KEY0 |= 0x40;
			KeyNum = 7;
		}
		if (Key_Check(KEY_8, KEY_SINGLE))
		{
			KEY0 |= 0x80;
			KeyNum = 8;
		}
		if (Key_Check(KEY_9, KEY_SINGLE))
		{
			KEY1 |= 0x01;
			KeyNum = 9;
		}
		if (Key_Check(KEY_10, KEY_SINGLE))
		{
			KEY1 |= 0x02;
			KeyNum = 10;
		}
		if (Key_Check(KEY_11, KEY_SINGLE))
		{
			KEY1 |= 0x04;
			KeyNum = 11;
		}
		if (Key_Check(KEY_12, KEY_SINGLE))
		{
			KEY1 |= 0x08;
			KeyNum = 12;
		}
		
		/*定时器中,每隔100ms,置一次Flag*/
		/*即每隔100ms,发送一次数据*/
		if (Flag)
		{
			Flag = 0;
			
			/*填充遥控器数据*/
			NRF24L01_TxPacket[0] = Mode;
			NRF24L01_TxPacket[1] = LH;
			NRF24L01_TxPacket[2] = LV;
			NRF24L01_TxPacket[3] = RH;
			NRF24L01_TxPacket[4] = RV;
			NRF24L01_TxPacket[5] = KEY0;
			NRF24L01_TxPacket[6] = KEY1;
			
			/*发送遥控器数据*/
			SendFlag = NRF24L01_Send();
			
			/*发送成功后,清除按键,确保按键被成功发出*/
			if (SendFlag == 1)
			{
				KEY0 = 0;
				KEY1 = 0;
			}
			
			/*计算发送成功率,用于显示信号值*/
			SuccessRatio = CalculateSuccessRatio(SendFlag);
		}
		
		/*按键按下,在屏幕右上角闪烁此按键值*/
		if (KeyNum)
		{
			OLED_Printf(104, 0, OLED_8X16, "K%d", KeyNum);
			OLED_UpdateArea(104, 0, 24, 16);
			Delay_ms(100);
			OLED_ClearArea(104, 0, 24, 16);
			OLED_UpdateArea(104, 0, 24, 16);
		}
		
		/*K9键按下,切换显示模式*/
		if (KeyNum == 9)
		{
			Mode = !Mode;
			OLED_Clear();
		}
		
		/*模式0时,屏幕显示摇杆数据*/
		if (Mode == 0)
		{
			OLED_Printf(0, 16, OLED_8X16, "LH:%+04d", LH);
			OLED_Printf(0, 32, OLED_8X16, "LV:%+04d", LV);
			OLED_Printf(72, 16, OLED_8X16, "RH:%+04d", RH);
			OLED_Printf(72, 32, OLED_8X16, "RV:%+04d", RV);
		}
		/*模式1时,屏幕显示平衡车返回的状态信息*/
		else if (Mode == 1)
		{
			if (NRF24L01_Receive() == 1)
			{
				uint8_t SpeedLevel = NRF24L01_RxPacket[1];
				int8_t PWML = NRF24L01_RxPacket[2];
				int8_t PWMR = NRF24L01_RxPacket[3];
				float Angle = *(float *)&NRF24L01_RxPacket[4];
				float SpeedLeft = *(float *)&NRF24L01_RxPacket[8];
				float SpeedRight = *(float *)&NRF24L01_RxPacket[12];
				
				OLED_Clear();
				OLED_Printf(0, 21, OLED_6X8, "Angle:%+06.1f", Angle);
				OLED_DrawLine(0, 36, 127, 36);
				OLED_Printf(0, 40, OLED_6X8, "PWML:%+05d PWMR:%+05d", PWML, PWMR);
				OLED_Printf(0, 48, OLED_6X8, "SpdL:%+05.1f SpdR:%+05.1f", SpeedLeft, SpeedRight);
				OLED_Printf(0, 56, OLED_6X8, "SpdLevel:%1d", SpeedLevel);
				
			}
		}
		
		/*根据发送成功率,在屏幕左上角显示对应的信号值*/
		if (SuccessRatio >= 9)
		{
			OLED_ShowImage(0, 0, 16, 16, Signal_3);
		}
		else if (SuccessRatio >= 5)
		{
			OLED_ShowImage(0, 0, 16, 16, Signal_2);
		}
		else if (SuccessRatio >= 1)
		{
			OLED_ShowImage(0, 0, 16, 16, Signal_1);
		}
		else
		{
			OLED_ShowImage(0, 0, 16, 16, Signal_0);
		}
		
		OLED_Update();
	}
}

四、MATLAB建模

平衡车MATLAB建模是利用MATLAB强大的数学计算与仿真能力,对两轮自平衡电动车进行动态系统建模、控制器设计与仿真实现的完整过程。本文通过建立状态空间模型描述车体倾斜角、车轮转速等动态行为,结合牛顿-欧拉方程和实际物理参数,构建精确的系统模型。利用Simulink进行可视化建模与仿真,采用PID等控制策略实现稳定控制,并通过PID Tuner优化参数。最终可将模型转化为C/C++代码部署至嵌入式系统,实现从理论到实践的闭环开发。

五、理论研究

六、演示视频

平衡车视频演示

七、资料下载

通过网盘分享的文件:平衡车入门教程资料
链接: https://pan.baidu.com/s/1NbZryQY33YjNtP9MZxyCiA 提取码: 7nmc 
--来自百度网盘超级会员v2的分享

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐