一、题目
题目链接:自动行驶小车(H题)
我们截取一些重要信息
1. 小车行驶场地示意图
2.要求
二、赛题分析
技术挑战与准备
-
MCU熟悉度:尽管TI MSPM0系列MCU在使用上类似于STM32CUBEIDE+Keil,但其开发环境也需要熟悉。因此,首要任务是熟悉TI MSPM0的编程环境,以及其基本的寄存器配置和编程接口。// 我这里使用的嘉立创的地猛星MSPM0G3507
-
硬件接口与驱动:根据小车配置,需要编写电机驱动、传感器(寻迹,MPU6050,OLED等)的接口代码。这要求能移植即可,且嘉立创也提供了移植教程。
-
实时性与稳定性:由于小车行驶过程中需要快速响应环境变化,因此代码的实时性和稳定性至关重要。
开发流程建议
-
环境搭建:安装并配置TI MSPM0的开发环境,确保能够编译、下载和调试代码。
-
硬件测试:单独测试各个硬件组件(电机、传感器等),确保其工作正常。
-
基础功能实现:先实现小车的基本移动功能,如前进、后退、左转、右转等。
-
路径跟踪与避障:在基础功能基础上,逐步加入路径和寻迹逻辑。
-
集成测试与调优:将各功能模块集成,进行整体测试,并根据测试结果进行调优。
MSPM0G3507开发教程: 立创·地猛星MSPM0G3507开发板
熟悉基础控制
1. LED点灯
- 目标:学会如何通过编程控制MSPM0G3507的GPIO(通用输入输出)端口来点亮和熄灭LED灯。
- 学习要点:了解GPIO端口的配置(如设置为输出模式)、编写控制LED亮灭的代码、理解电平高低与LED状态的关系。
2. 按键控制
- 目标:掌握如何通过编程读取MSPM0G3507的GPIO端口状态来检测按键是否被按下,并根据按键状态执行相应的操作。
- 学习要点:了解GPIO端口的输入模式配置、按键的去抖动处理(避免误触发)、编写按键扫描和状态处理的代码。
3. 串口通信
- 目标:学会配置MSPM0G3507的USART(通用同步/异步接收/发送器)模块,实现与PC或其他设备之间的串口通信。
- 学习要点:理解串口通信的基本原理(如波特率、数据位、停止位等参数)、USART模块的初始化配置、编写数据发送和接收的代码、使用串口调试助手等工具进行测试。
4. PWM输出
- 目标:掌握MSPM0G3507的PWM(脉冲宽度调制)模块的配置和使用,实现模拟信号的输出控制(如电机速度调节)。
- 学习要点:了解PWM的基本原理、PWM模块的初始化配置(如时钟源选择、占空比设置等)、编写控制PWM输出的代码、通过实验观察PWM波形并调整参数以达到预期效果。
通过系统地学习和实践上述基础功能,我们可以为后续的自动行驶小车项目打下坚实的基础。这些技能不仅有助于我们更好地理解MCU的工作原理和编程方法,还能在项目中发挥关键作用,如通过PWM控制电机转速、通过串口通信实现与上位机的数据交换等。
三、硬件设计
// 无效内容,只需要注意我们这里采用的是双MCU即可
在比赛进程中,我们遭遇了一个挑战:在尝试将控制程序加入MPU6050传感器后,发现MCU(微控制器单元)意外地失去了响应。面对时间紧迫的竞赛环境,我们迅速评估了当前状况,并决定采取一个高效且直接的解决方案来应对这一突发问题。
为了在最短时间内恢复系统功能并确保任务能够顺利进行,我们决定采用双MCU(两块地猛星)的并行架构作为最优解。这一策略旨在通过分散负载和提供冗余度来绕过原系统中的潜在障碍,同时保留关键传感器(MPU6050)的数据采集能力。
通过部署双MCU系统,我们可以将控制逻辑分配到两个独立的处理器上,其中一个专注于与MPU6050的通信和数据处理,而另一个则负责执行其他关键任务,如电机控制、通信接口管理等。这样的设计不仅能够有效避免单一故障点导致的系统瘫痪,还能提升整体系统的稳定性和响应速度。在实施这一方案时,我们将紧密关注两个MCU之间的同步与协调,确保数据传输的准确性和实时性
四、基础功能编写
1. MPU6050
* MPU6050姿态角获取
MPU6050的移植方式:MPU6050六轴传感器
值得注意的是,关于stdio.c和stdio.h文件的移植步骤中,直接获取这些文件的方式可能未予详细说明,但关键信息已包含在文章的末尾部分——即文章最后的移植成功案例的代码下载链接中。
移植成功案例代码下载链接:
链接:MPU6050移植成功案例
提取码:yaah
注意:我们在直接按照移植步骤完成后并未成功实现MPU6050的姿态获取(串口无反应)
我们做了如下更改:将void delay_us(unsigned long __us) ;更改如下,同时也发生了二中的情况。
void delay_us(unsigned long __us)
{
int delay__1us = 32000;
int dd = 0;
for(int i = 0; i< delay__1us;i++){
dd++;
}
}
* MPU6050姿态角发送与接收
发送:使用UART1将信息发送出去(此处为了简便和配合不熟悉MCU,我们只发送一个字节)
为了通过UART1发送信息(在此场景中简化为仅发送一个字节),同时考虑到我们处理的角度数据范围在-180度到180度之间,并且单个字节(0-255)的传输限制,我们设计一个简单的编码方案。
int yaw_int = yaw;
yaw_int = yaw_int + 180;
yaw_int = yaw_int/2;
sendInt(yaw_int);
delay_ms(20);
将 int 类型的数据装换后发出
void sendInt(int value)
{
uint8_t sendBuff[sizeof(int)];
// 将 int 转换为字节数组
for (int i = 0; i < sizeof(int); i++)
{
sendBuff[i] = (value >> (i * 8)) & 0xFF; // 提取每个字节
}
// 逐字节发送
for (int i = 0; i < sizeof(int); i++)
{
DL_UART_Main_transmitData(UART_1_INST, sendBuff[i]);
}
}
接收:主控板通过UART1接受数据并解码
我们MPU6050的MCU会一直发送姿态角,所以我们只需要在使用时接受一次就可以了。
//获取姿态角
int yaw_out(){
int usrt_dd = 0;
usrt_dd = DL_UART_Main_receiveData(UART_1_INST);
usrt_dd =usrt_dd*2 ;
return usrt_dd;
}
2. 主控环境配置
BEEP
Track
TB6612
KEY
PWM
UART: 串口1不需要中断
3. BEEP+LED
#include "BEEP.h"
#include "ti_msp_dl_config.h"
//#include "board.h"
void beep_delay_ms(unsigned int ms)
{
unsigned int i, j;
// 下面的嵌套循环的次数是根据主控频率和编译器生成的指令周期大致计算出来的,
// 需要通过实际测试调整来达到所需的延时。
for (i = 0; i < ms; i++)
{
for (j = 0; j < 8000; j++)
{
// 仅执行一个足够简单以致于可以预测其执行时间的操作
__asm__("nop"); // "nop" 代表“无操作”,在大多数架构中,这会消耗一个或几个时钟周期
}
}
}
void BEEP(int delay, int en){
static int delay_cnt=0;
if(delay_cnt >= delay*32000){
delay_cnt = 0;
DL_GPIO_togglePins(BEEP_GPIO, BEEP_GPIO_PIN);// 翻转电平
DL_GPIO_togglePins(BEEP_GPIO, BEEP_LED_PIN);// 翻转电平
}
if(en == 0) {
DL_GPIO_clearPins(BEEP_GPIO, BEEP_LED_PIN);//输出低电平
DL_GPIO_setPins(BEEP_GPIO, BEEP_GPIO_PIN);
//输出高电平
}
}
void BEEP_delay(int delay, int en){
for(int i =0;i<en*2;i++){
beep_delay_ms(delay);
DL_GPIO_togglePins(BEEP_GPIO, BEEP_GPIO_PIN);// 翻转电平
DL_GPIO_togglePins(BEEP_GPIO, BEEP_LED_PIN);// 翻转电平
}
BEEP_init();
}
void BEEP_init()
{
DL_GPIO_setPins(BEEP_GPIO, BEEP_GPIO_PIN);
DL_GPIO_clearPins(BEEP_GPIO, BEEP_LED_PIN);//输出低电平
}
4. 寻迹模块
#include "track.h"
#include "ti_msp_dl_config.h"
void track_delay_ms(unsigned int ms)
{
unsigned int i, j;
// 下面的嵌套循环的次数是根据主控频率和编译器生成的指令周期大致计算出来的,
// 需要通过实际测试调整来达到所需的延时。
for (i = 0; i < ms; i++)
{
for (j = 0; j < 8000; j++)
{
// 仅执行一个足够简单以致于可以预测其执行时间的操作
__asm__("nop"); // "nop" 代表“无操作”,在大多数架构中,这会消耗一个或几个时钟周期
}
}
}
// track为寻迹数组, size为寻迹模块大小
int tracking_7(int *track){
int i = 0;
int white = 0;
//如果读取到的引脚值大于0,说明引脚为高电平
i++;
if( DL_GPIO_readPins(Track_GPIO, Track_PIN_1) > 0 )
{
track[i] = H; //保留为高电平
white++;
}
else//如果引脚为低电平
{
track[i] = L;//保留为低电平
}
i++;
if( DL_GPIO_readPins(Track_GPIO, Track_PIN_2) > 0 )
{
track[i] = H; //保留为高电平
white++;
}
else//如果引脚为低电平
{
track[i] = L;//保留为低电平
}
i++;
if( DL_GPIO_readPins(Track_GPIO, Track_PIN_3) > 0 )
{
track[i] = H; //保留为高电平
white++;
}
else//如果引脚为低电平
{
track[i] = L;//保留为低电平
}
i++;
if( DL_GPIO_readPins(Track_GPIO, Track_PIN_4) > 0 )
{
track[i] = H; //保留为高电平
white++;
}
else//如果引脚为低电平
{
track[i] = L;//保留为低电平
}
i++;
if( DL_GPIO_readPins(Track_GPIO, Track_PIN_5) > 0 )
{
track[i] = H; //保留为高电平
white++;
}
else//如果引脚为低电平
{
track[i] = L;//保留为低电平
}
i++;
if( DL_GPIO_readPins(Track_GPIO, Track_PIN_6) > 0 )
{
track[i] = H; //保留为高电平
white++;
}
else//如果引脚为低电平
{
track[i] = L;//保留为低电平
}
i++;
if( DL_GPIO_readPins(Track_GPIO, Track_PIN_7) > 0 )
{
track[i] = H; //保留为高电平
white++;
}
else//如果引脚为低电平
{
track[i] = L;//保留为低电平
}
return white;
}
5. TB6612驱动
car.h
#ifndef CAR_H
#define CAR_H
// PA3 为左 || PA2为右
#define PWM_MAX 999 // PWM最大数值
#define PWM_name PWM_car_INST // 小车PWM组名字
#define PWM_L GPIO_PWM_car_C0_IDX // 小车左PWM
#define PWM_R GPIO_PWM_car_C1_IDX // 小车右PWM
void PWM_init(); // 初始化PWM
void PWM_left(int speed); // 左侧PWM速度
void PWM_right(int speed); // 右侧PWM速度
// TB6612
#define TB6612 TB6612_PORT
#define L0 TB6612_PIN_L0_PIN // PA18
#define L1 TB6612_PIN_L1_PIN // PA17
#define R0 TB6612_PIN_R0_PIN // PA16
#define R1 TB6612_PIN_R1_PIN // PA15
#define L0_L DL_GPIO_clearPins(TB6612,L0);//输出低电平
#define L0_H DL_GPIO_setPins(TB6612,L0); //输出高电平
#define L1_L DL_GPIO_clearPins(TB6612,L1);//输出低电平
#define L1_H DL_GPIO_setPins(TB6612,L1); //输出高电平
#define R0_L DL_GPIO_clearPins(TB6612,R0);//输出低电平
#define R0_H DL_GPIO_setPins(TB6612,R0); //输出高电平
#define R1_L DL_GPIO_clearPins(TB6612,R1);//输出低电平
#define R1_H DL_GPIO_setPins(TB6612,R1); //输出高电平
void car_init();
void car_forward(int LL, int RR);
void car_retreat(int LL, int RR);
void car_left(int LL, int RR);
void car_right(int LL, int RR);
void car_stop();
void car_turn_Left(int i);
void car_turnn_right(int i);
void car_main(int LL , int RR);
#endif // CAR_H
car.c
#include "car.h"
#include "ti_msp_dl_config.h"
// PB6 为左 || PA2为右
// #define PWM_MAX 999 // PWM最大数值
void PWM_init() // 初始化PWM
{
DL_TimerG_setCaptureCompareValue(PWM_name,0,PWM_L);
DL_TimerG_setCaptureCompareValue(PWM_name,0,PWM_R);
}
void PWM_left(int speed) // 左侧PWM速度
{
speed = 1000 - speed;
if(speed >= 1000) speed = 999;
if(speed < 0) speed = 0;
DL_TimerG_setCaptureCompareValue(PWM_name,speed,PWM_L);
}
void PWM_right(int speed) // 右侧PWM速度
{
speed = 1000 - speed;
if(speed >= 1000) speed = 999;
if(speed < 0) speed = 0;
DL_TimerG_setCaptureCompareValue(PWM_name,speed,PWM_R);
}
// TB6612
#define TB6612 TB6612_PORT
#define L0 TB6612_PIN_L0_PIN // PA18
#define L1 TB6612_PIN_L1_PIN // PA17
#define R0 TB6612_PIN_R0_PIN // PA16
#define R1 TB6612_PIN_R1_PIN // PA15
void car_init(){
PWM_init();
L0_L;
L1_L;
R0_L;
R1_L;
}
void car_forward(int LL, int RR)
{
PWM_left(LL);
PWM_right(RR);
L0_H;
L1_L;
R0_H;
R1_L;
}
void car_retreat(int LL, int RR)
{
PWM_left(LL);
PWM_right(RR);
L0_L;
L1_H;
R0_L;
R1_H;
}
void car_left(int LL, int RR)
{
PWM_left(LL);
PWM_right(RR);
L0_H;
L1_L;
R0_H;
R1_L;
}
void car_right(int LL, int RR)
{
PWM_left(LL);
PWM_right(RR);
L0_H;
L1_L;
R0_H;
R1_L;
}
void car_stop()
{
L0_L;
L1_L;
R0_L;
R1_L;
}
void car_turn_Left(int i)
{
PWM_left(i);
PWM_right(i);
L0_L;
L1_H;
R0_H;
R1_L;
}
void car_turnn_right(int i)
{
PWM_left(i);
PWM_right(i);
L0_H;
L1_L;
R0_L;
R1_H;
}
void car_main(int LL , int RR)
{
if(RR >= 0){
R0_H;
R1_L;
}else if(RR < 0){
R0_L;
R1_H;
RR = -RR;
}
if(LL >= 0)
{
L0_H;
L1_L;
}else if(LL < 0)
{
L0_L;
L1_H;
LL = -LL;
}
//范围限制
if(RR >= 1000) RR = 999;
if(RR < 0) RR = 0;
if(LL>=1000) LL = 999;
if(LL< 0) LL = 0;
PWM_left(LL);
PWM_right(RR);
}
7. KEY
#include "key.h"
#include "ti_msp_dl_config.h"
#include "board.h"
//自定义延时(不精确)
void key_delay_ms(unsigned int ms)
{
unsigned int i, j;
// 下面的嵌套循环的次数是根据主控频率和编译器生成的指令周期大致计算出来的,
// 需要通过实际测试调整来达到所需的延时。
for (i = 0; i < ms; i++)
{
for (j = 0; j < 8000; j++)
{
// 仅执行一个足够简单以致于可以预测其执行时间的操作
__asm__("nop"); // "nop" 代表“无操作”,在大多数架构中,这会消耗一个或几个时钟周期
}
}
}
void KEY_number(int *key)
{
int i = 0;
int key_delay = 50;
//如果读取到的引脚值大于0,说明引脚为高电平
i++;
if( DL_GPIO_readPins(KEY_GPIO, KEY1) > 0 )
{
key[i] = 1; //保留为高电平
}
else//如果引脚为低电平
{
key_delay_ms(key_delay);
if( DL_GPIO_readPins(KEY_GPIO, KEY1) > 0 )
{
key[i] = 1; //保留为高电平
}
else key[i] = 0;//保留为低电平
}
i++;
if( DL_GPIO_readPins(KEY_GPIO, KEY2) > 0 )
{
key[i] = 1; //保留为高电平
}
else//如果引脚为低电平
{
key_delay_ms(key_delay);
if( DL_GPIO_readPins(KEY_GPIO, KEY2) > 0 )
{
key[i] = 1; //保留为高电平
}
else key[i] = 0;//保留为低电平
}
i++;
if( DL_GPIO_readPins(KEY_GPIO, KEY3) > 0 )
{
key[i] = 1; //保留为高电平
}
else//如果引脚为低电平
{
key_delay_ms(key_delay);
if( DL_GPIO_readPins(KEY_GPIO, KEY3) > 0 )
{
key[i] = 1; //保留为高电平
}
else key[i] = 0;//保留为低电平
}
i++;
if( DL_GPIO_readPins(KEY_GPIO, KEY4) > 0 )
{
key[i] = 1; //保留为高电平
}
else//如果引脚为低电平
{
key_delay_ms(key_delay);
if( DL_GPIO_readPins(KEY_GPIO, KEY4) > 0 )
{
key[i] = 1; //保留为高电平
}
else key[i] = 0;//保留为低电平
}
i++;
if( DL_GPIO_readPins(KEY_GPIO, KEY5) > 0 )
{
key[i] = 1; //保留为高电平
}
else//如果引脚为低电平
{
key_delay_ms(key_delay);
if( DL_GPIO_readPins(KEY_GPIO, KEY5) > 0 )
{
key[i] = 1; //保留为高电平
}
else key[i] = 0;//保留为低电平
}
i++;
if( DL_GPIO_readPins(KEY_GPIO, KEY6) > 0 )
{
key[i] = 1; //保留为高电平
}
else//如果引脚为低电平
{
key_delay_ms(key_delay);
if( DL_GPIO_readPins(KEY_GPIO, KEY6) > 0 )
{
key[i] = 1; //保留为高电平
}
else key[i] = 0;//保留为低电平
}
}
五、任务编写
经过对题目的深入剖析,我们可以将其核心内容精炼地划分为两大核心任务:一是实现车辆的直线稳定行驶,确保行进过程中方向的精确控制与稳定;二是执行寻迹功能,即车辆需具备根据预设路径或轨迹进行智能追踪与导航的能力。这两个部分相辅相成,共同构成了题目要求的关键解决方案。
1. 直线调整方案
// 直线调整方案
void yaw_adjust(float target, float now_yaw, int speed, int B) {
// 确保目标和当前偏航在 0-359 范围内
if (target >= 360) target -= 360;
if (now_yaw >= 360) now_yaw -= 360;
// 计算调整量
int adjust = target - now_yaw;
// 归一化 adjust 到 -180 到 180 的范围
if (adjust > 180) {
adjust -= 360;
} else if (adjust < -180) {
adjust += 360;
}
// 调整电机速度
car_main(speed - adjust * B, speed + adjust * B);
}
函数概述
yaw_adjust
函数接收四个参数:target
(目标偏航角)、now_yaw
(当前偏航角)、speed
(基础速度)和B
(调整系数),用于调整车辆或机器人的左右电机速度,以修正其行进方向。
参数解析
- target:这是车辆或机器人应当达到的偏航角目标值,用于指明最终的前进方向。通过比较当前偏航角与目标偏航角,可以计算出所需的调整量。
- now_yaw:当前车辆或机器人的偏航角,反映了其当前的行进方向。这一参数的实时获取对于方向调整至关重要。
- speed:基础速度,即在不进行方向调整时,车辆或机器人应维持的速度。
- B:调整系数,用于将偏航角的调整量转换为电机速度的调整量。这个系数决定了调整的灵敏度和速度。
核心逻辑
-
角度标准化:首先,函数通过模运算(隐式地,通过加减360的倍数)确保
target
和now_yaw
都在0到359度的范围内,这是处理周期性角度数据的常用方法。 -
计算调整量:接着,计算目标偏航角与当前偏航角之间的差值
adjust
,这代表了需要进行的调整方向和大小。 -
归一化调整量:由于偏航角的差值可能是正数或负数,且可能超出-180到180度的范围,函数通过加减360度的方式将
adjust
归一化到这个范围内。这一步骤确保了调整指令的直观性和一致性。 -
调整电机速度:最后,根据
adjust
的值和调整系数B
,计算左右电机的速度调整量,并调用car_main
函数(这里假设是控制车辆电机速度的函数)来应用这些调整。左电机速度减小、右电机速度增加意味着向左转,反之则向右转。
2. 任务一
任务一就比较简单了,保持直线行驶,识别到寻迹线即可停止。
// 任务1
void test1(float yaw_init){
//delay_ms(200);
float pitch=0,roll=0,yaw=0; //欧拉角
float now_yaw = yaw;
int track[8];
int speed = 400; // 直线行驶速度
int BS = 3; // 偏角倍数
int time_cnt = 0; // 行驶时间计数
int time_cnt_max = 32*1000*100; // 32MHZ约等于 1s
int target_time = 0; // 当有疑似目标时,记录时间
int write_min = 5; // 当出现最小白色数时为疑似目标
int white = 0;
int en = 1;
while(en == 1)
{
white = tracking_7(track);
//获取欧拉角
now_yaw = yaw_out();
yaw_adjust(yaw_init, now_yaw, 300, BS);
if(track[1] == 1 ) {
car_main(100,-100);
//delay_ms(2000);
return;
}
if(track[7] == 1 ) {
car_main(-100,100);
//delay_ms(2000);
return;
}
//delay_ms(5);
if(white >= 1 )
{
en = 0;
return;
}
}
}
变量定义
-
欧拉角 (
pitch
,roll
,yaw
): 这里定义了三个欧拉角,但pitch
和roll
被初始化为0且未在后续代码中使用,可能是预留的变量或用于其他目的。yaw
用于存储从某个函数(假设是yaw_out()
)获取的当前偏航角,但实际上被now_yaw
变量覆盖。 -
now_yaw
: 存储当前的偏航角,用于与初始偏航角yaw_init
进行比较和调整。 -
track
数组: 用于存储追踪到的轨迹信息,这里假设tracking_7(track)
函数会填充这个数组,其中track[1]
和track[7]
可能代表特定位置(如左右边界)的追踪结果。 -
速度和调整系数:
speed
定义了直线行驶的速度,而BS
(偏角倍数)用于计算偏航角调整时电机速度的调整量。 -
时间相关变量:
time_cnt
和time_cnt_max
用于时间计数,但在这个函数体中并未使用。 -
目标时间和疑似目标检测:
target_time
和write_min
变量似乎是为了检测疑似目标而设计的,但在函数体内并未完全实现这一逻辑。 -
white
和en
:white
用于存储追踪到的白色(或特定颜色)的数量,而en
用作循环的控制变量。
逻辑流程
-
初始化变量: 设置初始速度、偏角倍数等。
-
主循环:
- 调用
tracking_7(track)
函数追踪轨迹,并获取白色(或特定颜色)的数量。 - 调用
yaw_out()
获取当前偏航角,并使用yaw_adjust
函数进行偏航角调整。 - 检查
track[1]
和track[7]
的值,如果检测到特定条件(可能是左右边界),则执行相应的转向操作并退出函数。 - 如果
white
的数量大于等于1(这里逻辑上可能有误,因为通常寻迹会要求连续多个白色点或达到一定阈值),则停止循环并退出函数。但这里直接返回可能会忽略一些重要的逻辑处理。
- 调用
潜在问题和改进建议
-
变量
yaw
的未使用: 如果yaw
不用于其他目的,可以考虑移除它以减少混淆。 -
white
的逻辑可能不够严谨: 通常寻迹会要求连续检测到一定数量的白色点或满足其他更复杂的条件,而不是简单地检查是否大于等于1。 -
时间计数器的未使用: 如果不需要时间控制,可以移除
time_cnt
和time_cnt_max
变量。如果需要,应实现相应的逻辑。 -
代码的可读性和可维护性: 可以通过添加注释、重构代码(如将特定逻辑封装为函数)来提高代码的可读性和可维护性。
-
错误处理和异常检测: 在实际应用中,应考虑添加错误处理和异常检测机制,以应对可能的传感器故障、通信错误等问题。
-
返回值的处理: 函数直接通过
return
语句退出,可能需要在调用该函数时考虑如何处理其返回值或确保它不会导致意外的行为。
3. 寻迹阶段
在寻迹过程中,如果纯靠识别全白的话容易出现误判,我们加入时间变量来缓解这种错误。
// 寻迹
void track_adjust(int speed)
{
int track[8];
int track_old[8]; //记录先前一次的寻迹表
int Mobile_records =0;
int BW = 0;
int BW_N = 55;
int cnt = 0;
int cnt_max = 32*1000/2 + 5000 + 500 ;
while(1)
{
int write = 0;
write = tracking_7(track);
if(track[1] == 1) Mobile_records = -1; // 需要左转+
else if(track[7] == 1) Mobile_records = 1; // 需要右转
else Mobile_records = 0;
if(write <= 0 && Mobile_records == 0) // 全为 0 且不是由寻迹不当导致
{
cnt++;
}
else cnt = 0;
if(cnt >= cnt_max) return;
if(track[1] == 1) BW = -5;
else if(track[7] == 1) BW = 3;
else if(track[2] == 1) BW = -3;
else if(track[6] == 1) BW = 3;
else if(track[3] == 1) BW = -1;
else if(track[5] == 1) BW = 1;
else if(track[4] == 1) BW = 0;
car_main(speed + BW*BW_N, speed - BW*BW_N);
}
}
功能
- 寻迹调整:根据寻迹传感器返回的8个位置(
track[8]
数组)的检测结果,调整车辆的左右轮速度,以保持在预定的轨迹上行驶。 - 错误处理:如果连续检测到没有有效轨迹(即
write <= 0
且Mobile_records == 0
),则增加计数器cnt
。如果计数器超过某个阈值(cnt_max
),则函数返回,可能是为了避免无限循环或处理无法找到轨迹的情况。也同时是判断完成寻迹的标志。
变量说明
- track[8]:存储寻迹传感器返回的8个位置的状态(通常是二进制值,表示是否检测到轨迹)。
- track_old[8]:虽然声明了,但在函数体内未使用,可能是为了未来扩展,比如记录上一次的状态以进行比较。
- Mobile_records:用于记录车辆需要进行的转向操作(左转、右转或直行)。
- BW:基础转向值,根据寻迹传感器的不同位置设置不同的值,用于调整左右轮的速度差,实现转向。
- BW_N:转向调整的倍数,用于放大
BW
的影响,使转向更加明显。 - cnt:计数器,用于记录连续未检测到有效轨迹的次数。
- cnt_max:计数器阈值,当
cnt
达到此值时,函数返回。
逻辑流程
- 调用
tracking_7
函数获取寻迹传感器的状态,存储在track
数组中。 - 根据
track
数组的值,设置Mobile_records
变量,以决定是否需要左转、右转或直行。 - 检查是否连续未检测到有效轨迹(
write <= 0 && Mobile_records == 0
),如果是,则增加cnt
计数器。 - 如果
cnt
计数器达到阈值cnt_max
,则函数返回。 - 根据
track
数组的值,设置BW
变量,用于调整左右轮的速度差。 - 调用
car_main
函数,根据speed
和BW
的值调整左右轮的速度,实现转向或直行。
注意事项
- 无限循环:由于函数使用了
while(1)
,它将在没有外部干预的情况下无限循环。这通常是期望的行为,但需要有适当的退出条件(如cnt >= cnt_max
)。 - 性能优化:如果
tracking_7
函数或car_main
函数的执行时间较长,可能会影响系统的响应速度。可能需要考虑优化这些函数的性能。 - 代码扩展:
track_old
数组当前未使用,但为未来可能的扩展(如比较当前和上一次的状态)提供了空间。 - 错误处理:除了连续未检测到轨迹的情况外,还可以考虑添加其他错误处理逻辑,如传感器故障检测等。
4. 任务二
任务二即为前面的调用,注意中间进行了小量的微调即可。
//任务2
void test2(float yaw_init,int speed)
{
test1(yaw_init);
car_stop();
BEEP_delay(100, 2);
track_adjust(speed);
car_stop();
BEEP_delay(100, 2);
yaw_init = yaw_init + 180;
if(yaw_init >= 360){
yaw_init = yaw_init - 360;
}
int now = yaw_out();
while(1){
int ccccc = 10;
if(yaw_init - now >= 0) car_main(-ccccc,ccccc);
else car_main(ccccc,-ccccc);
now = yaw_out();
if(yaw_init - now + 1>=yaw_init - now && yaw_init - now -1 < yaw_init - now) break;
}
test1(yaw_init);
car_stop();
BEEP_delay(100, 2);
track_adjust(speed);
car_stop();
BEEP_delay(100, 2);
return;
}
5. 任务三 认为四
任务三对角度进行计算即可(当然也会出现误差)
// 任务3
void test3(float yaw_init, int speed){
int yaw_init_180 = yaw_init;
if(yaw_init_180 >= 360) yaw_init_180 = yaw_init_180 - 360;
yaw_init = yaw_init - 33;
if(yaw_init < 0) yaw_init = 360-yaw_init;;
turn_yaw(yaw_init,yaw_init);
test1(yaw_init);
car_stop();
car_main(-100,100);
delay_ms(1000);
BEEP_delay(100, 2);
track_adjust(speed);
car_stop();
BEEP_delay(100, 2);
/*
yaw_init = yaw_init - 90;
if(yaw_init < 0){
yaw_init = 360 + yaw_init;
}*/
/*
int now = yaw_out();
while(1){
int ccccc = 10;
if(yaw_init_180 - now >= 0) car_main(-ccccc,ccccc);
else car_main(ccccc,-ccccc);
now = yaw_out();
if(yaw_init_180 - now + 1>=yaw_init_180 - now && yaw_init_180 - now -1 < yaw_init_180 - now) break;
}*/
test1( yaw_init_180-180+ 42);
car_stop();
car_main(100,-100);
delay_ms(1000);
BEEP_delay(100, 2);
track_adjust(speed);
car_stop();
BEEP_delay(100, 2);
//test2(dd,speed);
return;
}
// 任务4
void test4(float yaw_init, int speed){
for(int i = 0; i<4;i++){
test3(yaw_init,speed);
car_stop();
BEEP_delay(100, 2);
}
}
6. main.c
#include "ti_msp_dl_config.h"
#include "BEEP.h"
#include "track.h"
#include "car.h"
#include <stdio.h>
#include <stdio.h>
//#include "oled.h"
#include "board.h"
#include "key.h"
//#define DELAY (32000000)
// 整体实现函数
// 直线调整方案
void yaw_adjust(float target, float now_yaw, int speed, int B);
// 寻迹
void track_adjust(int speed);
// 任务1
void test1(float yaw_init);
//任务2
void test2(float yaw_init,int speed);
// 任务3
void test3(float yaw_init, int speed);
void test4(float yaw_init, int speed);
// 通用变量
#define KEY_MAX 6
//int track[8];
int key[KEY_MAX + 1];
void receiveInt(int *receivedValue)
{
uint8_t recvBuff[sizeof(int)] = {0}; // 接收缓冲区
// 逐字节接收
for (int i = 0; i < sizeof(int); i++)
{
// 接收一个字节
recvBuff[i] = DL_UART_Main_receiveData(UART_1_INST);
}
// 将接收到的字节组合成一个 int
*receivedValue = 0; // 初始化
for (int i = 0; i < sizeof(int); i++)
{
*receivedValue |= (recvBuff[i] << (i * 8)); // 组合字节
}
}
//获取姿态角
int yaw_out(){
int usrt_dd = 0;
usrt_dd = DL_UART_Main_receiveData(UART_1_INST);
usrt_dd =usrt_dd*2 ;
return usrt_dd;
}
int main(void)
{
SYSCFG_DL_init(); //芯片资源初始化,由SysConfig配置软件自动生成
car_init();
BEEP_init(); //初始化声光
while(1)
{
//car_main(300,300);
KEY_number(key);
//receiveInt(&usrt_dd);
if(key[1] == 0){
test1(yaw_out());
car_stop();
BEEP_delay(100, 2);
}
if(key[2] == 0){
int yew_test2 = yaw_out();
test2(yew_test2, 300);
//track_adjust(300);
// car_stop();
// BEEP_delay(100, 2);
}
if(key[3] == 0){
int yew_test2 = yaw_out();
test3(yew_test2, 350);
//track_adjust(300);
// car_stop();
// BEEP_delay(100, 2);
}
if(key[4] == 0){
int yew_test2 = yaw_out();
test4(yew_test2, 350);
//track_adjust(300);
// car_stop();
// BEEP_delay(100, 2);
}
}
}
// 直线调整方案
void yaw_adjust(float target, float now_yaw, int speed, int B) {
// 确保目标和当前偏航在 0-359 范围内
if (target >= 360) target -= 360;
if (now_yaw >= 360) now_yaw -= 360;
// 计算调整量
int adjust = target - now_yaw;
// 归一化 adjust 到 -180 到 180 的范围
if (adjust > 180) {
adjust -= 360;
} else if (adjust < -180) {
adjust += 360;
}
// 调整电机速度
car_main(speed - adjust * B, speed + adjust * B);
}
/*
// 直线调整方案
void yaw_adjust(float target, float now_yaw, int speed, int B){
if(target >= 360) target = target - 360;
if(now_yaw >= 360) now_yaw = now_yaw-360;
int adjust = target - now_yaw;
//if(adjust >= )
//如果旋转需要过大角度
if(adjust >= 180 || adjust <= -180)
{
if(adjust > 0){
adjust = 360 - adjust;
}
else{
adjust = -adjust - 360;
}
}
car_main(speed - adjust * B, speed + adjust * B);
}*/
void turn_yaw(float target, float now_yaw){
while(1){
int ccccc = 10;
if(target - now_yaw >= 0) car_main(-ccccc,ccccc);
else car_main(ccccc,-ccccc);
now_yaw = yaw_out();
if(target - now_yaw + 1>=target - now_yaw && target - now_yaw-1 < target - now_yaw) break;
}
}
// 任务1
void test1(float yaw_init){
//delay_ms(200);
float pitch=0,roll=0,yaw=0; //欧拉角
float now_yaw = yaw;
int track[8];
int speed = 400; // 直线行驶速度
int BS = 3; // 偏角倍数
int time_cnt = 0; // 行驶时间计数
int time_cnt_max = 32*1000*100; // 32MHZ约等于 1s
int target_time = 0; // 当有疑似目标时,记录时间
int write_min = 5; // 当出现最小白色数时为疑似目标
int white = 0;
int en = 1;
while(en == 1)
{
white = tracking_7(track);
//获取欧拉角
now_yaw = yaw_out();
yaw_adjust(yaw_init, now_yaw, 300, BS);
if(track[1] == 1 ) {
car_main(100,-100);
//delay_ms(2000);
return;
}
if(track[7] == 1 ) {
car_main(-100,100);
//delay_ms(2000);
return;
}
//delay_ms(5);
if(white >= 1 )
{
en = 0;
return;
}
}
}
//任务2
void test2(float yaw_init,int speed)
{
test1(yaw_init);
car_stop();
BEEP_delay(100, 2);
track_adjust(speed);
car_stop();
BEEP_delay(100, 2);
yaw_init = yaw_init + 180;
if(yaw_init >= 360){
yaw_init = yaw_init - 360;
}
int now = yaw_out();
while(1){
int ccccc = 10;
if(yaw_init - now >= 0) car_main(-ccccc,ccccc);
else car_main(ccccc,-ccccc);
now = yaw_out();
if(yaw_init - now + 1>=yaw_init - now && yaw_init - now -1 < yaw_init - now) break;
}
test1(yaw_init);
car_stop();
BEEP_delay(100, 2);
track_adjust(speed);
car_stop();
BEEP_delay(100, 2);
return;
}
// 任务3
void test3(float yaw_init, int speed){
int yaw_init_180 = yaw_init;
if(yaw_init_180 >= 360) yaw_init_180 = yaw_init_180 - 360;
yaw_init = yaw_init - 33;
if(yaw_init < 0) yaw_init = 360-yaw_init;;
turn_yaw(yaw_init,yaw_init);
test1(yaw_init);
car_stop();
car_main(-100,100);
delay_ms(1000);
BEEP_delay(100, 2);
track_adjust(speed);
car_stop();
BEEP_delay(100, 2);
/*
yaw_init = yaw_init - 90;
if(yaw_init < 0){
yaw_init = 360 + yaw_init;
}*/
/*
int now = yaw_out();
while(1){
int ccccc = 10;
if(yaw_init_180 - now >= 0) car_main(-ccccc,ccccc);
else car_main(ccccc,-ccccc);
now = yaw_out();
if(yaw_init_180 - now + 1>=yaw_init_180 - now && yaw_init_180 - now -1 < yaw_init_180 - now) break;
}*/
test1( yaw_init_180-180+ 42);
car_stop();
car_main(100,-100);
delay_ms(1000);
BEEP_delay(100, 2);
track_adjust(speed);
car_stop();
BEEP_delay(100, 2);
//test2(dd,speed);
return;
}
// 任务4
void test4(float yaw_init, int speed){
for(int i = 0; i<4;i++){
test3(yaw_init,speed);
car_stop();
BEEP_delay(100, 2);
}
}
// 寻迹
void track_adjust(int speed)
{
int track[8];
int track_old[8]; //记录先前一次的寻迹表
int Mobile_records =0;
int BW = 0;
int BW_N = 55;
int cnt = 0;
int cnt_max = 32*1000/2 + 5000 + 500 ;
while(1)
{
int write = 0;
write = tracking_7(track);
if(track[1] == 1) Mobile_records = -1; // 需要左转+
else if(track[7] == 1) Mobile_records = 1; // 需要右转
else Mobile_records = 0;
if(write <= 0 && Mobile_records == 0) // 全为 0 且不是由寻迹不当导致
{
cnt++;
}
else cnt = 0;
if(cnt >= cnt_max) return;
if(track[1] == 1) BW = -5;
else if(track[7] == 1) BW = 3;
else if(track[2] == 1) BW = -3;
else if(track[6] == 1) BW = 3;
else if(track[3] == 1) BW = -1;
else if(track[5] == 1) BW = 1;
else if(track[4] == 1) BW = 0;
car_main(speed + BW*BW_N, speed - BW*BW_N);
}
}
六、总结
在圆满完成本次任务之际,我们也不得不正视在项目实施过程中遭遇的几项挑战。首先,一个显著的问题是系统偶尔会出现卡死现象,初步分析后怀疑MCU可能遭遇了不明原因的复位。这一突发状况目前尚未明确其具体根源,但它无疑提醒我们在未来的设计和调试中,需要更加细致地检查电源稳定性、代码逻辑错误以及MCU的复位电路,确保系统的稳定运行。
此外,我们还遭遇了MPU6050传感器数据不准确的问题。面对这一技术障碍,我们并未止步,而是积极寻求解决方案,最终通过创新性的方法绕过了这一难题,确保了项目的顺利推进。值得注意的是,虽然我们的处理方式并非严格遵循嘉立创的移植教程,但实践证明,这种灵活应对的策略同样有效。
展望未来,我们将继续深化对MCU及传感器技术的理解,致力于提升系统的稳定性和精确度。同时,我们也将积极总结经验教训,优化开发流程,为下一次项目的顺利实施奠定更加坚实的基础。我们相信,通过不断学习和努力,我们能够克服更多技术难题,推动项目向更高水平迈进。
参考资料:
1. 立创·地猛星MSPM0G3507开发板
2. MPU6050稳定姿态获取(代码)
3. 24电赛H题主控代码(代码)