一、遇上的一些难点和克服手段
1.1 红外对管如何高效使用?
硬件上我采取的是八个外红对管,软件上我们采用了红外对管位置PID,如最左边的红外对管识别到了黑线,那就把小车当前的位置标记为-4,这个位置数组为[-4,-3,-2,-1,1,2,3,4]。
红外对管的PID输入是小车位置,期望目标值是位置0,输出是需要调节的转速,直接应用到车上,便可以完成转弯和巡线。
但是这样由于缺乏一个红外对管对应0的状态,所以小车会一直处于-1和1来回振荡,反应到车上的表现,就是小车一直频繁地微摆抽搐。解决方法有两种:
1、硬件上,买奇数路的红外,如九个,把最中间的定义为位置0
2、软件上,不让上面那个位置数组完全对称,我们把其中一个选作为位置0,这样可以保证小车可以走直线,然后需要左右尽量对称,这样能有时候偏离黑线的时候,可以救车救的回来。还有就是我们选择0状态位于第5个数码管,是因为这道题全是左拐,这样可以提高左拐的识别准确率。
实际上需要对着代码先测试一下,可以发现,当输入位置为负,小车现在车头往右偏,需要小车改变转速后往左拐,0为直行,正则同理反之。
float g_TrackStateWeight_array[8] = { -6, -5, -2.5, -1, 0, 1, 2.5, 5}; // 循迹位置状态权重
1.2 红外PID的调试经验?
经过我们的调试经验,一般只需要P和D即可,P保证小车拐弯能拐的过去,说人话就是两个轮子的差速更大一点,D是减小小车的振荡晃动幅度。最好每个速度都调节一个红外PID,不然跑起来不稳定。
1.3 如何正确分辨大小圈,即y型路口?
这个问题是我个人觉得这道题最难的几个点
在测试过程中,我们发现y型路口在合适的红外对管摆放下,只会同时识别到两个,一个偏左一个偏右,这是由赛道和红外摆放位置结构决定的,可以举一反三。(其实我觉得这个应该可以拿来识别直角路口,但我没有测试过)
我们每次进行循迹的时候,用一个数组把小车当前所有红外对管的数字量状态保存下来(1为白线,0为黑线),因为y型路口是同时识别到两个。那么需要左拐,即走内圈,就把偏左的那个红外状态给PID去计算,如果需要直行,就把偏右的那个红外状态给PID去计算。这样可以准确让小车走大小圈。
1.4 如何正确识别到终点线和y型路口的次数?
这个问题也比较棘手,而且如果处理不好,会影响赛题完成度和成功率。
之所以需要正确识别到这些的次数,是因为我们可以根据这些次数的变化,去更改小车的代码,题目第三题要求在运行过程中更改大小圈噢。在实际测试下来,终点线的次数基本可以稳定识别到,但是y型路口的次数还是很容易误判的。
难点:我们是10ms进行一次PID调速,也就是说PID的采样和控制周期都是10ms,那么总会有10ms小车处于转速裸奔状态。假设小车运行速度0.5m/s,即50cm/s,10ms小车可以走0.5cm,而终点线题目要求是2cm宽,由于小车姿态不可能一直稳定,转速也会更改,所以小车虽然只经过一次终点线但在代码上会识别到多次终点线。
一开始我们是想用一个恰当的滤波值,比如识别到4次算1次,后面就发现效果极差。因为小车会识别到多少次完全是随机的。
最后我们采取,当识别到第一次终点线,即有4个红外对管同时识别到黑线时,我们进入距离计算,当这段距离>0.3m以后,我们认为算经过了一个终点线。这样可以非常准确的稳定识别圈数,如果小车走得稳的话,经过测试可以把这个滤波距离减少到0.05m,但实际上没有必要,因为这道题不需要,距离大稳妥一点。
但,y型路口的次数就不是很稳定了,实现原理也可以跟上述的终点线一样。。看1.5
1.5 第二题如何实现?
从车追车用较快的速度,当识别到与主车20cm左右的距离时,改回跟主车一样的速度。
难点在于超声波防干扰,我是没有研究出来,但是周围站的人不要太多就问题不大。
1.6 第三题如何实现?
第三题,第一圈跟随走大圈,第二圈从车走小圈超车,主车走大圈,第三圈反之。
1、如何在代码里恰到好处的位置更改走大小圈的值呢?
那就需要准确的识别到剩余圈数和剩余岔路口,但是岔路口的识别很有问题,经常性误判(可能是我们红外的问题),这个误判是极其要命的。所以我们采取了更为暴力的做法,效果还可以。
上面提到识别终点线是用距离去滤波,因为过了终点线后,紧接着就是弯道了,所以我们再用一次距离滤波,当行驶过了1m(距离自行测试,我们是1m),即完整的出了弯道进入直道,我们在此时更改大小圈的标志位,让小车接下来走指定路线。
2、老是撞车?
因为小车实际表现很难走的距离相差刚好20cm(这样勉强可以超车不撞车),所以我们让小车只要进入超车,即内圈,就在内圈加速,过了弯就减速回原来的速度,此时拉开了30多cm,让后面的车追,这样保证了超车过弯的时候不会撞车。实现的代码段就在过弯道的那段滤波加速。
1.7 第四题如何实现?
我们设置了剩余终点数量为3,启动的时候为3,第一次过终点线时为2,然后识别到停车点为1,此时停车,死延时,然后再死延时一段没有红外循迹的,让小车盲跑过了那段空白赛道即可。
从车甚至不用死延时,这点距离没啥干扰。
二、一些我未解决的bug
1、我们发送赛题和更改赛题运行代码,是通过按键中断来实现的。不知道为什么经常性出现主车发错赛题和从车跑错赛题,我也没找到问题所在,我猜测是没有进行中断消抖的处理。(交完才想起来,后面有空再改改看。)
2、速度最多只测试到1.0m/s,时间不够,再往上没有测试了,但跑赛题1m/s够呛(技术有限诶),有待优化。
三、 一些建议和疑惑
1、电源问题
电机驱动供电是12V但有一个引脚是5V,芯片供电是3.3V,切记不能用同一个稳压模块出来的5V接一起,电机舵机这种非常容易出现高电压,而且是完全随机的,接上去不会马上烧完全是运气问题。因为我买的是系统板,芯片上面是有一颗ldo的,我以为应该没事,就用了同一个稳压模块出来的5V给系统板供电,最后还是烧掉了,还烧了个超声波模块。另外我们的循迹模块也比较贵,也接了一个单独的稳压5V。总之,电机的电源得自己单独用一个稳压模块就对了!所以我的电路板最好更改一下,方便接线。
2、pid控制周期和采样周期
小车的pid调节转速和红外循迹我都是采用10ms改一次,如果你更改这个周期,pid需要重新调整,不同赛道,不同电池的电量,都会影响小车跑起来的效果。
问:如果把周期10ms改成5ms会不会更精细一点,反应更快呢?我没有尝试过
3、编码器读取转速滤波
这个也是我没有尝试的,经过测试电机读取的转速会出现一个周期性毛刺,我们无论怎么调节转速pid都消去不了,所以可能编码器读取的值进行滤波会好一点。可能这对小车高速行驶的平稳度有帮助,毕竟这些毛刺都是噪音。
4、小车编码器选几线?
我们一辆11线,一辆13线,明显13线的那辆精细特别多,在低速和高速表现都远强于11线的那辆,所以有钱就买贵的。
四、采用的设备
芯片用的是STM32F103RCT6系统板
循迹用的是感为八路循迹模块
超声波用的是HC-SR04
蓝牙用的是HC-08
电机13线用的是MG513,11线用的是JGB37-520电机
屏幕用的是1.8存TFT彩屏
五、红外循迹和超声波的代码
5.1 红外循迹 .c
#include "main.h"
uint8_t g_Track_CurveTimes = 0; // 识别到岔路口的次数
float g_Track_ThisState = 0; // 小车目前状态
uint8_t g_New_Drive_State; // 最新的驾驶状态
uint8_t g_Last_Drive_State; // 上一次驾驶状态
PID g_Track_Pid; // 创建红外PID
void Track_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
TCRT1_GPIO_CLK_ENABLE(); // 开启GPIOB时钟
TCRT2_GPIO_CLK_ENABLE(); // 开启GPIOB时钟
TCRT3_GPIO_CLK_ENABLE(); // 开启GPIOB时钟
TCRT4_GPIO_CLK_ENABLE(); // 开启GPIOB时钟
TCRT5_GPIO_CLK_ENABLE(); // 开启GPIOB时钟
TCRT6_GPIO_CLK_ENABLE(); // 开启GPIOB时钟
TCRT7_GPIO_CLK_ENABLE(); // 开启GPIOB时钟
TCRT8_GPIO_CLK_ENABLE(); // 开启GPIOB时钟
GPIO_InitStruct.Pin = TCRT1_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(TCRT1_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = TCRT2_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(TCRT2_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = TCRT3_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(TCRT3_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = TCRT4_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(TCRT4_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = TCRT5_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(TCRT5_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = TCRT6_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(TCRT6_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = TCRT7_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(TCRT7_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = TCRT8_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(TCRT8_GPIO_PORT, &GPIO_InitStruct);
// PID_Init(&g_Track_Pid, 0.0700 , 0.0002, 0.1, 0.5, 0.5, 0); // 红外PID初始化
Track_Speed_Choose(0.5);
}
uint8_t g_TrackState_array[8]; // 存储红外状态的数组
float g_TrackStateWeight_array[8] = { -6, -5, -2.5, -1, 0, 1, 2.5, 5}; // 修改后的循迹状态权重
uint8_t g_TrackStateOrder_array[8] = {99}; // 记录具体是哪个红外对管检测到黑线
uint8_t g_Check_Number = 0; // 同时亮灭的红外对管的数量
/**
* 函 数:检查红外循迹的当前亮灭状态
* 参 数:无
* 返 回 值:无
*/
void Check_Track_State()
{
g_TrackState_array[0] = Read_Tcrt_1();
g_TrackState_array[1] = Read_Tcrt_2();
g_TrackState_array[2] = Read_Tcrt_3();
g_TrackState_array[3] = Read_Tcrt_4();
g_TrackState_array[4] = Read_Tcrt_5();
g_TrackState_array[5] = Read_Tcrt_6();
g_TrackState_array[6] = Read_Tcrt_7();
g_TrackState_array[7] = Read_Tcrt_8();
int i = 0, j = 0;
for (i = 0; i < 8; i++) // 遍历所有红外对管
{
if (g_TrackState_array[i] == BLACK_LINE) // 如果红外对管对应着黑线
{
g_TrackStateOrder_array[j] = i; // 记录哪个红外对管记录着黑线
g_Check_Number++; // 总数量++
j++;
}
}
}
/**
* 函 数:选择让小车走大圈还是小圈
* 参 数:circle:大小圈
* 返 回 值:无
*/
void Track_Choose_Circle(uint8_t circle)
{
if(circle == SMALL_CIRCLE)
{
g_Track_ThisState = g_TrackStateWeight_array[g_TrackStateOrder_array[0]]; // 把权重值赋值给红外状态,从左往右,内圈左边先识别到
}
else if(circle == BIG_CIRCLE)
{
g_Track_ThisState = g_TrackStateWeight_array[g_TrackStateOrder_array[1]]; // 把权重值赋值给红外状态,从左往右,外圈右边后识别到
}
}
/**
* 函 数:检查红外循迹的当前亮灭状态,并修改大小圈的参数,和进行圈数的加减
* 参 数:car_huart:小车的句柄
* 返 回 值:无
*/
void Track(volatile Car *car_huart)
{
static uint8_t circle_mileage_flag;
static uint8_t temp = 1;
Check_Track_State();
// 小车正常行驶下,红外对管识别到的数量为1
if (g_Check_Number == 1)
{
g_Track_ThisState = g_TrackStateWeight_array[g_TrackStateOrder_array[0]]; // 把权重值赋值给红外状态
g_New_Drive_State = NORMAL;
}
// 当遇见岔路口的时候,红外对管同时识别到的数量为2
else if (g_Check_Number == 2)
{
Track_Choose_Circle(car_huart->circle); // 选择走大小圈
g_New_Drive_State = JUNCTION;
}
// 当遇见起点或者终点的时候,红外对管同时识别到的数量 == 4
else if (g_Check_Number >= 4)
{
g_New_Drive_State = TERMINAL;
}
PID_Calc(&g_Track_Pid, g_Track_ThisState, 0); // 红外PID的计算
g_speed_pid1.targetValue = g_Inital_Speed - g_Track_Pid.output; // 更改目标速度
g_speed_pid2.targetValue = g_Inital_Speed + g_Track_Pid.output; // 更改目标速度
g_Check_Number = 0; // 总的检测红外对管数量清0
if(car_huart->circle_num != 1) // 不是最后一次碰见终点
{
if (g_New_Drive_State == TERMINAL)
{
circle_mileage_flag = 1; // 把计算距离的标志位置1,开始计算距离
}
}
else if (car_huart->circle_num == 1) // 最后一次碰见终点,碰到就要停止了,不用计算距离
{
if (g_New_Drive_State == TERMINAL)
{
car_huart->circle_num--; // 把距离减到0
Serial_Printf(&g_uart3_handle, "circle_num: %d\r\n", car_huart->circle_num);
}
}
if(g_Rx_mode == QUESTION4)
{
/* 在识别到终点后的计算 */
if(circle_mileage_flag == 1)
{
if(car_huart->circle_mileage < 0.05) // 小于0.3m,计算距离
{
BEEP(1); // 蜂鸣器开
car_huart->circle_mileage = car_huart->circle_mileage + g_Inital_Speed / 100;
}
else // 大于0.3m
{
BEEP(0); // 蜂鸣器关
car_huart->circle_mileage = 0; // 清除距离数
circle_mileage_flag = 0; // 距离标志位置0,不再计算距离
car_huart->circle_num--;
Serial_Printf(&g_uart3_handle, "circle_num: %d\r\n", car_huart->circle_num);
}
}
}
else
{
/* 在识别到终点后的计算 */
if(circle_mileage_flag == 1)
{
if(car_huart->circle_mileage < 0.3) // 小于0.3m,计算距离
{
BEEP(1); // 蜂鸣器开
car_huart->circle_mileage = car_huart->circle_mileage + g_Inital_Speed / 100;
}
else // 大于0.3m
{
BEEP(0); // 蜂鸣器关
car_huart->circle_mileage = 0; // 清除距离数
circle_mileage_flag = 0; // 距离标志位置0,不再计算距离
car_huart->circle_num--;
Serial_Printf(&g_uart3_handle, "circle_num: %d\r\n", car_huart->circle_num);
}
}
}
}
/**
* 函 数:不同的速度选择不同的红外PID
* 参 数:speed:期望速度
* 返 回 值:无
*/
void Track_Speed_Choose(double speed)
{
if(speed == 0.3) // 当速度为0.3时,设置一套PID
{
PID_Init(&g_Track_Pid, 0.030 , 0.0000, 0.35, 1, 1, 0); // 红外PID初始化
}
else if(speed == 0.4) // 当速度为0.4时,设置一套PID
{
PID_Init(&g_Track_Pid, 0.035 , 0.0000, 0.40, 1, 1, 0); // 红外PID初始化
}
else if(speed == 0.5) // 当速度为0.5时,设置一套PID
{
PID_Init(&g_Track_Pid, 0.040, 0.0000, 0.45, 1, 1, 0); // 红外PID初始化
}
else if(speed == 0.6) // 当速度为0.6时,设置一套PID
{
PID_Init(&g_Track_Pid, 0.045 , 0.0000, 0.85, 1, 1, 0); // 红外PID初始化
}
else if(speed == 0.7) // 当速度为0.7时,设置一套PID
{
PID_Init(&g_Track_Pid, 0.050 , 0.0000, 0.90, 1, 1, 0); // 红外PID初始化
}
else if(speed == 0.8) // 当速度为0.8时,设置一套PID
{
PID_Init(&g_Track_Pid, 0.060 , 0.00000, 0.90, 1, 1, 0); // 红外PID初始化
}
else if(speed == 0.9) // 当速度为0.9时,设置一套PID
{
PID_Init(&g_Track_Pid, 0.070 , 0.00000, 0.90, 1.5, 1.5, 0); // 红外PID初始化
}
else if(speed == 1) // 当速度为1.0时,设置一套PID
{
PID_Init(&g_Track_Pid, 0.080 , 0.00000, 0.90, 1.5, 1.5, 0); // 红外PID初始化
}
}
5.2 红外循迹.h
#ifndef __TRACK_H
#define __TRACK_H
#include "./BSP/PID/pid.h"
#include "./SYSTEM/sys/sys.h"
/* 引脚 定义 */
/* TB6612驱动电机1的正反转引脚定义 */
#define TCRT8_GPIO_PORT GPIOC
#define TCRT8_GPIO_PIN GPIO_PIN_15
#define TCRT8_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)
#define TCRT7_GPIO_PORT GPIOC
#define TCRT7_GPIO_PIN GPIO_PIN_14
#define TCRT7_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)
#define TCRT6_GPIO_PORT GPIOC
#define TCRT6_GPIO_PIN GPIO_PIN_13
#define TCRT6_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)
#define TCRT5_GPIO_PORT GPIOA
#define TCRT5_GPIO_PIN GPIO_PIN_1
#define TCRT5_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)
#define TCRT4_GPIO_PORT GPIOA
#define TCRT4_GPIO_PIN GPIO_PIN_4
#define TCRT4_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
#define TCRT3_GPIO_PORT GPIOA
#define TCRT3_GPIO_PIN GPIO_PIN_5
#define TCRT3_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0)
#define TCRT2_GPIO_PORT GPIOC
#define TCRT2_GPIO_PIN GPIO_PIN_5
#define TCRT2_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOC_CLK_ENABLE(); }while(0)
#define TCRT1_GPIO_PORT GPIOB
#define TCRT1_GPIO_PIN GPIO_PIN_1
#define TCRT1_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0)
#define Read_Tcrt_1() HAL_GPIO_ReadPin(TCRT1_GPIO_PORT, TCRT1_GPIO_PIN)
#define Read_Tcrt_2() HAL_GPIO_ReadPin(TCRT2_GPIO_PORT, TCRT2_GPIO_PIN)
#define Read_Tcrt_3() HAL_GPIO_ReadPin(TCRT3_GPIO_PORT, TCRT3_GPIO_PIN)
#define Read_Tcrt_4() HAL_GPIO_ReadPin(TCRT4_GPIO_PORT, TCRT4_GPIO_PIN)
#define Read_Tcrt_5() HAL_GPIO_ReadPin(TCRT5_GPIO_PORT, TCRT5_GPIO_PIN)
#define Read_Tcrt_6() HAL_GPIO_ReadPin(TCRT6_GPIO_PORT, TCRT6_GPIO_PIN)
#define Read_Tcrt_7() HAL_GPIO_ReadPin(TCRT7_GPIO_PORT, TCRT7_GPIO_PIN)
#define Read_Tcrt_8() HAL_GPIO_ReadPin(TCRT8_GPIO_PORT, TCRT8_GPIO_PIN)
#define BLACK_LINE 0 // 黑线
#define WHITE_LINE 1 // 白线
#define SMALL_CIRCLE 0 // 小圈
#define BIG_CIRCLE 1 // 大圈
#define NORMAL 1
#define JUNCTION 2
#define TERMINAL 3
extern PID g_Track_Pid;
extern uint8_t g_Track_CurveTimes;
void Track_Init(void);
void Track(volatile Car *car_huart);
void Track_Choose_Circle(uint8_t circle);
void Track_Speed_Choose(double speed);
#endif
5.3 从车的超声波.c
#include "./BSP/HC-SR04/hcsr04.h"
#include "main.h"
/**
* @brief 初始化HC-SR04相关IO口, 并使能时钟
* @param 无
* @retval 无
*/
void HCSR04_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
TRIG_GPIO_CLK_ENABLE(); /* TRIG时钟使能 */
gpio_init_struct.Pin = TRIG_GPIO_PIN; /* TRIG引脚 */
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(TRIG_GPIO_PORT, &gpio_init_struct); /* 初始化LED0引脚 */
TRIG(0); /* 初始化为低电平 */
gtim_tim5_cap_ch3_init(0XFFFF, HCSR04_PSC - 1); /* 以1Mhz的频率计数 捕获 */
}
float Length_Error()
{
static double Drive_Length = -1.37; // 单位:m
Drive_Length = Drive_Length + 0.7 / 100;
return g_Rx_Length - (Drive_Length * 100); // 单位:cm
}
/**
* @brief 超声波获取障碍物距离
* @param car_huart:小车的句柄
* @param ultrasonic_isopen:是否计算障碍物距离
可设置参数: ULTRASONIC_OPEN: 计算距离
ULTRASONIC_CLOSE:关闭计算距离,返回默认值200mm
* @retval 无
*/
double HCSR04_Get_Distance(volatile Car *car_huart, uint8_t ultrasonic_isopen)
{
if (ultrasonic_isopen == ULTRASONIC_OPEN)
{
static double distance = 0.0; /* 距离 */
if(car_huart->ultrasonic_flag == 1) /* 每65ms发送一次超声波,在定时器7中断里标志位置1 */
{
if (g_tim5ch3_cap_sta & 0X80) /* 成功捕获到了一次高电平 */
{
uint32_t counts = 0;
uint32_t value = 0;
uint32_t high_time = 0;
counts = g_tim5ch3_cap_sta & 0X3F; /* 溢出多少次 */
value = g_tim5ch3_cap_val; /* 当前捕获值 */
counts *= 65536; /* 溢出时间总和 */
counts += value; /* 得到总的高电平时间 */
// high_time = counts / (HCSR04_TIME); /* 得到实际的高电平us */
high_time = counts;
distance = high_time * 0.35 / 2; /* 距离 = 时间 * 速度,即distance = temp * 340m/s = temp * 0.34mm / us *//* 单位:mm */
distance = distance - CAR_BODY_ERROR; /* 得到修正后的距离值 */
g_tim5ch3_cap_sta = 0; /* 开启下一次捕获*/
car_huart->ultrasonic_flag = 0; /* 清除超声波标志位 */
return distance; /* 返回这个周期的距离值 */
}
else /* 如果还未捕获到输入 */
return distance; /* 返回的是上个周期的距离*/
}
else /* 若在65ms周期内获取了距离值,还未进入下一个周期*/
return distance; /* 那在这个周期内,一直返回同个周期的距离值 */
}
else if(ultrasonic_isopen == ULTRASONIC_CLOSE) /* 如果超声波是关闭状态 */
return 200; /* 返回默认距离200mm */
else /* 参数输入有误 */
return 20000; /* 返回错误值 */
}
/**
* @brief 超声波根据距离获得轮子的差速
* @param car_huart:小车结构体指针
* @param ultrasonic_isopen:是否计算障碍物距离
可设置参数: ULTRASONIC_OPEN: 计算距离
ULTRASONIC_CLOSE:关闭计算距离,返回默认值200mm
* @retval 差速
*/
double HCSR04_Speed(volatile Car *car_huart, uint8_t ultrasonic_isopen, double error_distance)
{
double distance;
static uint8_t pursue_flag = 1;
distance = HCSR04_Get_Distance(car_huart, ultrasonic_isopen);
if(g_Rx_mode == QUESTION2) /* 问题二 */
{
if(distance >= 600 && pursue_flag == 1) // 追车阶段,且未追上过,大加速
return 0.2;
if(distance >= 600 && pursue_flag == 0) // 已经追上车,距离 > 400,把距离值视为异常值,保持原速
return 0;
if(distance >= 210 && distance < 600 && pursue_flag == 1) // 未追上,距离较远,小加速
return 0.2;
if(distance >= 210 && distance < 600 && pursue_flag == 0) // 未追上,距离较远,小加速
return 0.05;
if(distance < 210 && distance >= 190) // 距离适中,保持速度
{
while(pursue_flag == 1 && error_distance < 50) // 只执行一次,
{
LED1(0);
Track_Speed_Choose(0.5); // 问题二追车完毕,改回巡航跟车速度
pursue_flag = 0;
}
return 0;
}
if(distance < 190 && distance >= 90) // 距离较近,减速
{
while(pursue_flag == 1 && error_distance < 50) // 只执行一次,
{
LED1(0);
Track_Speed_Choose(0.5); // 问题二追车完毕,改回巡航跟车速度
pursue_flag = 0;
}
return -0.05;
}
if(distance < 90) // 距离小于90mm,为异常值
return 0;
}
else /* 非问题二的时候 */
{
if(distance >= 600) // 判断为异常值
return 0;
else if(distance >= 210 && distance < 600) // 距离较远,小加速
return 0.05;
else if(distance < 210 && distance >= 190) // 距离适中,保持速度
return 0;
else if(distance < 190 && distance >= 90) // 距离较近,减速
return -0.05;
else // 距离小于90mm,为异常值
return 0;
}
return 0;
}
5.4 从车的超声波.h
#ifndef _HCSR04_H
#define _HCSR04_H
#include "./SYSTEM/sys/sys.h"
#include "main.h"
#define TRIG_GPIO_PORT GPIOA
#define TRIG_GPIO_PIN GPIO_PIN_11
#define TRIG_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOA_CLK_ENABLE(); }while(0) /* PA口时钟使能 */
#define HCSR04_PSC 72 /* 时基单元PSC72分频,基准频率1MHz */
#define HCSR04_TIME 72 / HCSR04_PSC /* 时基单元PSC72分频,基准频率1MHz */
/* 如果PSC是36,那么就是2MHz, 记2个数字为1us, 所以得到的计数值得除2, 所以HCSR04_TIME为2 */
/* 如果PSC是18,那么就是4MHz, 记4个数字为1us, 所以得到的计数值得除4, 所以HCSR04_TIME为4 */
/* 如果PSC是1 ,那么就是72MHz,记72个数字为1us,所以得到的计数值得除72,所以HCSR04_TIME为72 */
#define ULTRASONIC_OPEN 1
#define ULTRASONIC_CLOSE 0
#define TRIG(x) do{ x ? \
HAL_GPIO_WritePin(TRIG_GPIO_PORT, TRIG_GPIO_PIN, GPIO_PIN_RESET) : \
HAL_GPIO_WritePin(TRIG_GPIO_PORT, TRIG_GPIO_PIN, GPIO_PIN_SET); \
}while(0) /* TRIG翻转 */
void HCSR04_init(void);
double HCSR04_Get_Distance(volatile Car *car_huart, uint8_t ultrasonic_isopen);
double HCSR04_Speed(volatile Car *car_huart, uint8_t ultrasonic_isopen, double error_distance);
float Length_Error (void);
#endif
六、演示效果
6.1 赛题1
6.2 赛题2
6.3 赛题3
6.4 赛题4
6.5 小车图片
七、结语
因为这是我们学校的校电赛练手的题目,不然也不会2024年才做了,没有太多时间去优化了,现在大三了,接下来准备期末考试、暑假实习和秋招,打算好好补补c语言和单片机基本功,有大佬可以内推一下嘛,希望能顺利找到工作。
如果有哪里讲得不对的地方,或者讲错了,欢迎指正!!
如果需要源码,劳烦点赞然后评论留下你的邮箱,我发给你。(现在还没优化,如果不介意的话 ---- 2024.6.10)