一、遇上的一些难点和克服手段

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)

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐