【2024年电赛H题自动行驶小车】省一学长手把手从零教你学MSPM0

一、前前言

第十二届浙江省大学生电子设计竞赛终于落下帷幕了,名单已公示,几家欢喜几家愁?我觉得每一位经历过电赛的朋友都称得上英雄,我想我们所学到的并非是“省一等”或“成功参赛奖”一个头衔能囊括的,相信真正有价值的东西会在人生的道路上生根发芽。

我写这篇博客的最初目的,也只是想用代码的方式记录这段伟大的时光。

二、前言

2024年省赛题目如下:

不难看出,题目的难点只有两个:

1、如何熟练使用TI MSPM0系列的板子?

2、无黑线部分如何尽可能保持直行?

对于第一个问题,我先给出我的解决方法:

1、【2024电赛】TI MSPM0快速入门课

2、LP-MSPM03507学习资料汇总

第一个链接是B站UP主Torris-Yin的基础教学视频,我觉得讲的很好,故上附链接推荐给各位。

第二个链接是我自己整理的纯代码形式的G3507资料,仅供参考!

第二个问题,我的解决方法是购买高精度的陀螺仪模块——JY61P、MPU6050。(不推荐使用MPU6050,需要软件去零漂)

好了,难点都解决了,现在发现其实拿下省一也没难度吧!

三、整体框架

基本思路就是:有线巡线,无线靠巡航角

接下来进入正题,我会尽量按顺序一一介绍各模块的配置与使用,然后将各模块整合一块。

源码下附全开源,可根据需求跳转查看相应部分。

功底不深,多多包涵!

四、驱动电机

1、PWM配置

PWM Period Count:自动重装载值,相当于CUBEMX中的ARR

PWM Frequency:    PWM输出频率,建议选择15k ~ 20kHz

2、PWM输通道及占空比配置

Channel Update Mode:死区PWM宽度设置,此处不设置死区影响不大。

3、正反转IO口配置

此四口仅配置为普通输出IO口即可

4、代码填写

AIN1、2控制左马达的正反转:AIN1为高,AIN2为低,则为正转;反之相反。

BIN1、2同理。

/* 全局宏定义 */
#define AIN1_High       DL_GPIO_setPins(DIR_PORT, DIR_AIN1_PIN)
#define AIN1_Low        DL_GPIO_clearPins(DIR_PORT, DIR_AIN1_PIN)
#define AIN2_High       DL_GPIO_setPins(DIR_PORT, DIR_AIN2_PIN)
#define AIN2_Low        DL_GPIO_clearPins(DIR_PORT, DIR_AIN2_PIN)
#define BIN1_High       DL_GPIO_setPins(DIR_PORT, DIR_BIN1_PIN)
#define BIN1_Low        DL_GPIO_clearPins(DIR_PORT, DIR_BIN1_PIN)
#define BIN2_High       DL_GPIO_setPins(DIR_PORT, DIR_BIN2_PIN)
#define BIN2_Low        DL_GPIO_clearPins(DIR_PORT, DIR_BIN2_PIN)



/*
    * 初始化
*/
void Init(void)
{
    /* 关闭所有输出 */
    AIN1_Low;
    AIN2_Low;
    BIN1_Low;
    BIN2_Low;

    /* 使能PWM波输出 */
    DL_Timer_startCounter(PWM_0_INST);
}

至此,驱动电机部分完成。

五、读取编码器数值

此部分包含两个部分:配置IO口读取编码器配置、配置定时器定时读取编码值(此时的编码值相当于小车速度值)

编码器Port配置:GPIO_EncoderA\B

1、编码器IO口初始化配置

2、配置IO口及中断

Internal Resistor:上下拉电阻配置,由于读取的编码器有效电平为高电平,故选择配置为下拉电阻

Input Filter:           输入电平滤波,降低误读率

其余三个IO口配置同理。

3、代码填写

此代码选择的是四分频的编码器度数方式,精度更高。

想对编码器模式了解更多?详见【STM32+HAL】直流电机PID控制

/*
    * 编码器读取函数
    * 编码器A、B分别对应编码器A、B的引脚
    * EncoderA_CNT、EncoderB_CNT    分别对应编码器A、B的计数值
    * EncoderA_Port、EncoderB_Port  分别对应编码器A、B的端口
*/
void Encodering(void)
{
    EncoderA_Port = DL_GPIO_getEnabledInterruptStatus(GPIO_EncoderA_PORT, GPIO_EncoderA_PIN_0_PIN | GPIO_EncoderA_PIN_1_PIN);
    EncoderB_Port = DL_GPIO_getEnabledInterruptStatus(GPIO_EncoderB_PORT, GPIO_EncoderB_PIN_2_PIN | GPIO_EncoderB_PIN_3_PIN);
    
    /* 编码器A */
    if((EncoderA_Port & GPIO_EncoderA_PIN_0_PIN) == GPIO_EncoderA_PIN_0_PIN)        //编码器A-Pin0
    {
        if(!DL_GPIO_readPins(GPIO_EncoderA_PORT,GPIO_EncoderA_PIN_1_PIN))   EncoderA_CNT--;
        else                                                                EncoderA_CNT++;
    }
    else if((EncoderA_Port & GPIO_EncoderA_PIN_1_PIN) == GPIO_EncoderA_PIN_1_PIN)   //编码器A-Pin1
    {
        if(!DL_GPIO_readPins(GPIO_EncoderA_PORT,GPIO_EncoderA_PIN_0_PIN))   EncoderA_CNT++;
        else                                                                EncoderA_CNT--;
    }
    DL_GPIO_clearInterruptStatus(GPIO_EncoderA_PORT, GPIO_EncoderA_PIN_0_PIN|GPIO_EncoderA_PIN_1_PIN);


    /* 编码器B */
    if((EncoderB_Port & GPIO_EncoderB_PIN_2_PIN) == GPIO_EncoderB_PIN_2_PIN)
    {
        if(!DL_GPIO_readPins(GPIO_EncoderB_PORT,GPIO_EncoderB_PIN_3_PIN))   EncoderB_CNT--;
        else                                                                EncoderB_CNT++;
    }
    else if((EncoderB_Port & GPIO_EncoderB_PIN_3_PIN) == GPIO_EncoderB_PIN_3_PIN)
    {
        if(!DL_GPIO_readPins(GPIO_EncoderB_PORT,GPIO_EncoderB_PIN_2_PIN))   EncoderB_CNT++;
        else                                                                EncoderB_CNT--;
    }
    DL_GPIO_clearInterruptStatus(GPIO_EncoderB_PORT, GPIO_EncoderB_PIN_2_PIN|GPIO_EncoderB_PIN_3_PIN);
}





/**************************************************************************
Function: Read encoder count per unit time
Input   : TIMX:Timer
Output  : none
函数功能:单位时间读取编码器计数
入口参数:TIMX:定时器
返回  值:速度值
**************************************************************************/
void Read_Encoder(void)
{
    EncoderA_VEL = EncoderA_CNT;
    EncoderA_CNT = 0;
    EncoderB_VEL = EncoderB_CNT;
    EncoderB_CNT = 0;
}

编码器定时器配置:TIMER_Encoder_Read

1、定时器初始化配置

2、配置定时器周期及中断

因配置为周期向下计数模式,故中断对应的选择Zero event,即定时器计数到0触发中断。

3、代码填写

TIMER_Encoder_Read_INST_IRQHandler:定时器中断服务函数,每20ms进入中断读取编码值,并对速度变量赋值。

/*
    * 编码器初始化
*/
void Encoder_Init(void)
{
    /* 关闭所有输出 */
    AIN1_Low;
    AIN2_Low;
    BIN1_Low;
    BIN2_Low;

    /* 使能编码器定时器、GPIO中断 */
    NVIC_EnableIRQ(GPIO_EncoderA_INT_IRQN);
    NVIC_EnableIRQ(GPIO_EncoderB_INT_IRQN);
    NVIC_EnableIRQ(TIMER_Encoder_Read_INST_INT_IRQN);
    DL_Timer_startCounter(TIMER_Encoder_Read_INST);

    /* 使能PWM波输出 */
    DL_Timer_startCounter(PWM_0_INST);
}




/*
    * 编码器读取中断服务函数
    * 读取编码器数值
*/
void TIMER_Encoder_Read_INST_IRQHandler(void)
{
    switch (DL_TimerG_getPendingInterrupt(TIMER_Encoder_Read_INST)){
        case DL_TIMER_IIDX_ZERO:
            Read_Encoder();            //赋值编码器数值
            // printf("%d,%d\r\n",EncoderA_VEL,EncoderB_VEL);
            break;
        default:
            break;
    }
}

 至此,我们实现了读取马达的速度值。

六、八路灰度传感器模块

1、IO口配置

普通输入IO口配置即可。

因为灰度传感器输出电压一般大于3.3V,建议不要配置上下拉电阻。

2、全局宏定义

检测到黑线时,输出高电平,变量置1。

#define P1          DL_GPIO_readPins(Sensor_P1_PORT,Sensor_P1_PIN)
#define P2          DL_GPIO_readPins(Sensor_P2_PORT,Sensor_P2_PIN)
#define P3          DL_GPIO_readPins(Sensor_P3_PORT,Sensor_P3_PIN)
#define P4          DL_GPIO_readPins(Sensor_P4_PORT,Sensor_P4_PIN)
#define P5          DL_GPIO_readPins(Sensor_P5_PORT,Sensor_P5_PIN)
#define P6          DL_GPIO_readPins(Sensor_P6_PORT,Sensor_P6_PIN)
#define P7          DL_GPIO_readPins(Sensor_P7_PORT,Sensor_P7_PIN)
#define P8          DL_GPIO_readPins(Sensor_P8_PORT,Sensor_P8_PIN)

至此,完成八路灰度传感器基础配置完毕。

七、串口获取巡航角

 1、串口配置

根据JY61P数据使用手册,配置为普通串口中断配置即可。(模块波特率需通过上位机修改,这里不赘述)

若想进一步提高通讯效率,详见文章中的串口DMA传输LP-MSPM03507学习资料汇总

2、代码填写
#include "GYRO.h"

// 定义接收变量
uint8_t RollL, RollH, PitchL, PitchH, YawL, YawH, VL, VH, SUM;
float Pitch,Roll,Yaw;

// 串口接收状态标识
#define WAIT_HEADER1 0
#define WAIT_HEADER2 1
#define RECEIVE_DATA 2

uint8_t RxState = WAIT_HEADER1;
uint8_t receivedData[9];
uint8_t dataIndex = 0;


//发送置偏航角置零命令
void Serial_JY61P_Zero_Yaw(void)
{
    DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0XFF);
	DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0XAA);
	DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0X69);
	DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0X88);
	DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0XB5);
	delay_ms(100);
	DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0XFF);
	DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0XAA);
	DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0X76);
	DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0X00);
	DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0X00);
	delay_ms(100);
	DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0XFF);
	DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0XAA);
	DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0X00);
	DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0X00);
	DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0X00);
    delay_ms(500);
}



// 串口中断处理函数
/*
    * 串口接收数据,并解析出角度
    * 接收到的数据格式为:0x55 0x53 0xXX 0xXX 0xXX 0xXX 0xXX 0xXX 0xXX 0xXX
    * 其中0xXX为角度数据,单位为0.1度
    * 角度数据为16位,高8位在前,低8位在后
    * 角度数据为正数表示顺时针旋转,负数表示逆时针旋转
    * 角度数据为0表示水平
    * 角度数据为180表示垂直
    * 角度数据为90表示垂直向上
    * 角度数据为-90表示垂直向下
    * 角度数据为360表示水平向右
    * 角度数据为-360表示水平向左
*/
void UART_JY61P_INST_IRQHandler(void) 
{
    uint8_t uartdata = DL_UART_Main_receiveData(UART_JY61P_INST);   // 接收一个uint8_t数据

    switch (RxState) {
    case WAIT_HEADER1:
        if (uartdata == 0x55) {
            RxState = WAIT_HEADER2;
        }
        break;
    case WAIT_HEADER2:
        if (uartdata == 0x53) {
            RxState = RECEIVE_DATA;
            dataIndex = 0;
        } else {
            RxState = WAIT_HEADER1;                                 // 如果不是期望的第二个头,重置状态
        }
        break;
    case RECEIVE_DATA:
        receivedData[dataIndex++] = uartdata;
        if (dataIndex == 9) {
            // 数据接收完毕,分配给具体的变量
            RollL = receivedData[0];
            RollH = receivedData[1];
            PitchL = receivedData[2];
            PitchH = receivedData[3];
            YawL = receivedData[4];
            YawH = receivedData[5];
            VL = receivedData[6];
            VH = receivedData[7];
            SUM = receivedData[8];

            // 校验SUM是否正确
            uint8_t calculatedSum = 0x55 + 0x53 + RollH + RollL + PitchH + PitchL + YawH + YawL + VH + VL;
            if (calculatedSum == SUM) {
                // 校验成功,可以进行后续处理
                if((float)(((uint16_t)RollH << 8) | RollL)/32768*180>180){
                    Roll = (float)(((uint16_t)RollH << 8) | RollL)/32768*180 - 360;
                }else{
                    Roll = (float)(((uint16_t)RollH << 8) | RollL)/32768*180;
                }

                if((float)(((uint16_t)PitchH << 8) | PitchL)/32768*180>180){
                    Pitch = (float)(((uint16_t)PitchH << 8) | PitchL)/32768*180 - 360;
                }else{
                    Pitch = (float)(((uint16_t)PitchH << 8) | PitchL)/32768*180;
                }

                if((float)(((uint16_t)YawH << 8) | YawL)/32768*180 >180){
                    Yaw = (float)(((uint16_t)YawH << 8) | YawL)/32768*180 - 360;
                }else{
                    Yaw = (float)(((uint16_t)YawH << 8) | YawL)/32768*180;
                }
                
            } else {
                // 校验失败,处理错误
            }
            RxState = WAIT_HEADER1;                                 // 重置状态以等待下一个数据包
        }
        break;
    }
}

至此,陀螺仪模块配置完毕。

八、剩余外设配置

1、按键配置

按键一端接IO口,另一端接地即可,建议串联一个1kΩ左右电阻

按键EN配置同理

2、声光提示IO口

题目中要求每经过一个点需声光报警,我们配置为普通输出IO口即可。

3、代码填写
#define LED_High        DL_GPIO_setPins(LED_PORT, LED_PIN_LED_PIN)
#define LED_Low         DL_GPIO_clearPins(LED_PORT, LED_PIN_LED_PIN)


while(1)
{    
    /* 按键扫描 */
    Key_Scan();

    /* LED闪烁 */
    LED_Sound();
}



/*
    * 按键扫描函数
    * 按键按下时,切换mode
    * 按键按下时,切换flag_en确认使能
*/
void Key_Scan(void)
{
    /* 模式切换按键 */
    if(DL_GPIO_readPins(KEY_PORT, KEY_S2_PIN) == 0)
    {
        delay_ms(10);
        if(DL_GPIO_readPins(KEY_PORT, KEY_S2_PIN) == 0)
        {
            while(!DL_GPIO_readPins(KEY_PORT, KEY_S2_PIN));
            mode = (mode + 1) % 5;
        }
    }

    /* 使能按键 */
    if(DL_GPIO_readPins(KEY_PORT, KEY_EN_PIN) == 0)
    {
        delay_ms(10);
        if(DL_GPIO_readPins(KEY_PORT, KEY_EN_PIN) == 0)
        {
            while(!DL_GPIO_readPins(KEY_PORT, KEY_EN_PIN));
            flag_en = 1 - flag_en;
        }
    }
}



/*
    * LED闪烁函数
*/
void LED_Sound(void)
{
    if(flag_LED)
    {
        LED_High;
        LED_CNT  = LED_CNT + 0.1;
        if(LED_CNT >= 800)         //闪烁延时
        {
            LED_Low;
            LED_CNT = 0;
            flag_LED = 0;
        }
    }
}

4、控制定时器TIMER_0

控制函数需要保证尽可能的不被打断,所以放在定时器里是一个很好的选择。

而放在while里的优点是执行周期短,故考虑两者可尽量降低定时器周期

1)设置定时器周期500us

2)开启中断

九、速度环、差速环实现

速度环

速度环的目标值为设定目标速度,测试参数时需先关闭差速环,仅测量速度环,并对目标速度Motor_Left赋初值,通过上位机查看当前速度与目标速度的数值曲线来调节PID参数。

//速度环PID
#define   Kp1   	40
#define   Ki1     	0.13
#define   Kd1  		0.1


/*=================== 增量式PID控制设计 ===================*/
//左A轮PID
float PID_A(float Encoder,float Target)
{
	static float Bias, Last_bias, Last2_bias, Pwm;
	Bias = Target - Encoder;
	Pwm += Kp1 * (Bias - Last_bias) + Ki1 * Bias + Kd1 * (Bias - 2 * Last_bias + Last2_bias);

	Last_bias = Bias;
	Last2_bias = Last_bias;
	return Pwm;
}


//右B轮PID
float PID_B(float Encoder,float Target)
{
	static float Bias, Last_bias, Last2_bias, Pwm;
	Bias = Target-Encoder;
	Pwm += Kp1 * (Bias - Last_bias) + Ki1 * Bias + Kd1 * (Bias - 2 * Last_bias + Last2_bias);

	Last_bias = Bias;
	Last2_bias = Last_bias;
	return Pwm;
}

差速环

差速环的目标值为控制量的目标值,即陀螺仪的差速环GYRO_Control的目标值应始终为0(直行),输入为当前巡航角,输出为应偏移数值(bias)

巡线的差速环Incremental_Quantity略有不同,可以这么理解:当所有光电都返回为识别到白色,则两轮差速值为0;若离车体中心的两光电检测到黑线,则返回一个较小的偏差值;距离越远,返回的偏差值越大。

差速环的参数调节应在速度环之后。

//陀螺仪PID
#define   Kp3       1
#define   Ki3       0
#define   Kd3  	    0

/*
    函数功能:角度PID
    IN: 当前值,目标值
    OUT: 输出值
*/
float GYRO_Control(float now,float target)
{
	static float Bias, Last_bias, Last2_bias, Pwm;
	Bias = target-now;
	Pwm += Kp3 * (Bias - Last_bias) + Ki3 * Bias + Kd3 * (Bias - 2 * Last_bias + Last2_bias);

	Last_bias = Bias;
	Last2_bias = Last_bias;
	return Pwm;
}



/*
    函数:Incremental_Quantity
    功能:普通直行巡线模式
    参数:无
*/
int Incremental_Quantity(void)
{
    int value = 0;
    if(!P1) //检测到最右端
        value += 12;
    if(!P2) 
        value += 9;
    if(!P3)
        value += 7;
    if(!P4)
        value += 3;
    if(!P5)
        value -= 3;
    if(!P6)
        value -= 7;
    if(!P7)
        value -= 9;
    if(!P8)
        value -= 12;
    return value;
}

速度环+差速环

为方便理解,这里暂时使用了部分伪代码,基本思想为:括号里为判断当前是否落在黑线上语句,若有一个在黑线上,则bias赋值为bias1,即巡线差速值;若全不在黑线上,bias赋值为bias2,即陀螺仪差速值。然后在中值速度Speed_Middle的基础上加减bias,自此实现了差速环控制。

#define  Limit		3800			//PWM波限幅
/*************************************/
float Speed_Middle = 30;			                    //中值速度
int Motor_Left, Motor_Right;		                    //左右马达占空比	


/**************************************************************************
Function: Control function
Input   : none
Output  : none
函数功能:控制小车巡线
入口参数:无
返回  值:无
**************************************************************************/
void Control(void)
{
    float TargetA, TargetB;                         //目标速度
    float bias1, bias2, bias;                       //巡线、陀螺仪偏差

    bias1 = Incremental_Quantity();
    bias2 = GYRO_Control(Yaw, 0);
    bias = (是否检查到黑线) ? bias1 : bias2;

    TargetA = Speed_Middle - bias;
    TargetB = Speed_Middle + bias;

    Motor_Left  = (int)PWM_Limit(PID_A(EncoderA_VEL,TargetA), Limit, -Limit);    //占空比限幅
    Motor_Right = (int)PWM_Limit(PID_B(EncoderB_VEL,TargetB), Limit, -Limit);
    Set_Pwm(Motor_Left, Motor_Right);
}




/*
    函数功能:设置PWM比较值
    IN: 左马达占空比,右马达占空比
    OUT: 无
*/
void Set_Pwm(int Left, int Right)
{
    if(Left > 0)    {AIN1_High;AIN2_Low;}      //左正转
    else            {AIN1_Low;AIN2_High;}      //左反转
    DL_Timer_setCaptureCompareValue(PWM_0_INST, myabs(Left), DL_TIMER_CC_0_INDEX);

    if(Right > 0)   {BIN1_High;BIN2_Low;}      //右正转
    else            {BIN1_Low;BIN2_High;}      //右反转
    DL_Timer_setCaptureCompareValue(PWM_0_INST, myabs(Right), DL_TIMER_CC_1_INDEX);
}

十、四种巡线模式

1、整体代码

由于此部分函数过于冗长,故先采用伪代码形式呈现供大家理解。代码虽长,逻辑简单。

基本思路为:通过按键切换巡线模式mode后,小车进入不同的if语句块,每个语句块放置各个模式的顺序执行代码。

#include "Sensor.h"

/*
    函数功能:路径控制总函数
    参数:无
*/
int Follow_Route(void)
{
    static int cnt = 0;
    /* 模式一 */
    if(mode == 1)
    {
        Follow_Route_Mode1();     //模式一时,仅需判断一次黑线
        return 1;
    }

    /* 模式二 */
    else if(mode == 2)
    {
        Follow_Route_Mode2();    //模式二时,通过改变flag标志位实现四段路径的切换
        return 2;
    }

    /* 模式三 */
    else if(mode == 3)
    {
        Follow_Route_Mode3(0);      //仅进行一次八字绕圈
        return 3;
    }

    /* 模式四 */
    else if(mode == 4)
    {
        Follow_Route_Mode4();      //循环绕四圈
        return 4;
    }
    return 0;
}

2、全部代码

逻辑不难,大家自行理解吧!

#include "Sensor.h"

#define Black_CNT1          30                               //mode = 1 黑线持续次数
#define Black_CNT2          30                               //mode = 2 黑线持续次数
#define Black_CNT3          50                               //mode = 3、4 黑线持续次数

#define White_CNT           1000                              //白线持续次数  
#define White_CNT2          5000                              //mode = 2 白线持续次数
#define White_CNT3          7000                              //mode = 3、4 白线持续次数


extern volatile int flag;                                  //flag状态位:0-暂停;1-AB;2-BC;3-CD;4-DA;
extern volatile bool flag_LED;
extern volatile int mode;                                  //mode状态位:0-暂停;



/*
    函数功能:路径控制总函数
    参数:无
*/
int Follow_Route(void)
{
    static int cnt = 0;
    /* 模式一 */
    if(mode == 1)
    {
        if(flag == 1)                                                    //AB段末尾
        {
            if(P1 || P2 || P3 || P4 || P5 || P6 || P7 || P8)        //任一点检测到黑线
            {
                cnt++;
                // printf("%d\r\n", cnt);
                if(cnt > Black_CNT1)                                     //检测到黑线持续5次
                {
                    flag_LED = 1;
                    flag = 0;
                    cnt = 0;
                    return 0;
                }
            }
            else   cnt = 0;                                         //连续检测黑线判定
        }   
    }

    /* 模式二 */
    else if(mode == 2)
    {
        /* B点判定 */
        if(flag == 1)
        {
            if(P1 || P2 || P3 || P4 || P5 || P6 || P7 || P8)        //任一点检测到黑线
            {
                cnt++;
                if(cnt > Black_CNT2)             //检测到黑线持续5次
                {
                    flag_LED = 1;
                    flag = 2;               //进入状态二
                    cnt = 0;
                    return 2;
                }
            }
            else   cnt = 0;
        }
        /* C点判定 */
        else if(flag == 2)
        {
            if(!(P1 || P2 || P3 || P4 || P5 || P6 || P7 || P8))                  //所有点检测到白线
            {
                cnt++;
                if(cnt > White_CNT2)               //检测到白线持续White_CNT2次
                {
                    flag_LED = 1;
                    flag = 3;
                    cnt = 0;
                    return 3;
                }
            }
            else   cnt = 0;
        }
        /* D点判定 */
        else if(flag == 3)
        {
            if(P1 || P2 || P3 || P4)        //任一点检测到黑线
            {
                cnt++;
                if(cnt > Black_CNT2)                //检测到黑线持续Black_CNT2次
                {
                    flag_LED = 1;
                    flag = 4;
                    cnt = 0;
                    return 4;
                }
            }
            else   cnt = 0;
        }
        /* A点(终点)判定 */
        else if(flag == 4)
        {
            if(!(P1 || P2 || P3 || P4 || P5 || P6 || P7 || P8))                  //所有点检测到白线
            {
                cnt++;
                if(cnt > White_CNT4 )                  //检测到白线持续White_CNT4次
                {
                    flag_LED = 1;
                    flag = 0;           //一圈结束
                    cnt = 0;
                    return 0;
                }
            }
            else   cnt = 0;
        }
    }

    /* 模式三 */
    else if(mode == 3)
    {
        Follow_Route_Mode3(0);      //仅进行一次绕圈
        return 3;
    }

    /* 模式四 */
    else if(mode == 4)
    {
        Follow_Route_Mode4();      //循环绕四圈
        return 4;
    }
    return 0;
}




/*
    函数功能:模式三路径控制
    参数:  stop:循环停止标志位
    stop - 1:循环继续
    stop - 0:循环一次
*/
int Follow_Route_Mode3(int stop)
{
    static int cnt3 = 0;
    /* C点判定 */
    if(flag == 1)
    {
        if(P5 || P6 || P7 || P8)        //车体左侧任一点检测到黑线
        {
            cnt3++;
            if(cnt3 > Black_CNT3)               //检测到黑线持续Black_CNT3次
            {
                flag_LED = 1;
                flag = 2;               //进入下一状态
                cnt3 = 0;
                return 2;
            }
        }
        else   cnt3 = 0;
    }

    /* B点判定 */
    else if(flag == 2)
    {
        if(!(P1 || P2 || P3 || P4 || P5 || P6 || P7 || P8))              //所有点检测到白线
        {
            cnt3++;
            if(cnt3 > White_CNT3)                             //检测到白线持续White_CNT3次
            {
                flag_LED = 1;
                flag = 3;
                cnt3 = 0;
                return 3;
            }
        }
    }

    /* D点判定 */
    else if(flag == 3)
    {
        if(P1 || P2 || P3 || P4)        //车体右侧任一点检测到黑线
        {
            cnt3++;
            if(cnt3 > Black_CNT3)                      //检测到黑线持续Black_CNT3次
            {
                flag_LED = 1;
                flag = 4;
                cnt3 = 0;
                return 4;
            }
        }
    }

    /* A点判定 */
    else if(flag == 4)
    {
        if(!(P1 || P2 || P3 || P4 || P5 || P6 || P7 || P8))             //所有点检测到白线
        {
            cnt3++;
            if(cnt3 > White_CNT3)                      //检测到白线持续White_CNT3次
            {
                flag_LED = 1;
                flag = stop;                     //停止
                cnt3 = 0;
                return 0;
            }
        }
    }
    return 0;
}





/*
    函数功能:模式四路径控制
*/
int Follow_Route_Mode4(void)
{
    Follow_Route_Mode3(1);  //循环三圈不停止
    Follow_Route_Mode3(1);
    Follow_Route_Mode3(1);
    Follow_Route_Mode3(0);      //停止
    return 0;
}

十一、模块整合

核心控制代码放在main.c文件中,关键函数接口主要从Sensor.cControl.c两个文件中引出。

main.c通过按键扫描来选择开启哪个模式

Sensor.c包含各模式的巡线流程代码

Control.c包含各模式速度环及差速环的PID函数代码

1、各个全局标志位代码的含义与赋值方式
/* 
按键一:改变mode值以切换当前模式
按键二:使能flag_en以确认当前模式
flag : 状态机思想,每结束一段路径通过软件切换当前模式数
*/


volatile int flag = 1;                                  //flag状态位:0-暂停;1-AB段;2-BC段;3-CD段;4-DA段;
volatile int mode = 0;                                  //mode状态位:0-暂停;1-模式一... ...;
volatile int flag_en = 0;                               //flag_en状态位:0-不使能;1-使能

2、初始化配置

用惯了HAL库代码书写习惯,,相信大家能明白我的意思!

    SYSCFG_DL_init();
  /* USER CODE BEGIN 2 */

/* GYRO初始化 */
Serial_JY61P_Zero_Yaw();
NVIC_EnableIRQ(UART_JY61P_INST_INT_IRQN);
delay_ms(500);

/* 编码器及PWM初始化 */
Encoder_Init();

/* 使能控制定时器 */
NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);
DL_Timer_startCounter(TIMER_0_INST);

  /* USER CODE END 2 */

3、while(1)填写

CPU不断扫描按键状态,当开启使能时开启巡线函数。

    while (1) {

    /* 按键扫描 */
    Key_Scan();

    if(flag_en)         //使能控制开启
    {
        Follow_Route(); //路径控制总函数函数
        /* LED闪烁 */
        LED_Sound();
    }
}
4、定时器控制中断

每隔500us进入控制中断,且只在使能情况下开启控制函数。

/*
    * 定时器0中断服务函数
*/
void TIMER_0_INST_IRQHandler(void)
{
    switch (DL_TimerA_getPendingInterrupt(TIMER_0_INST)) {
    case DL_TIMERA_IIDX_ZERO:
        //flag位判断
        if(flag == 0 || flag_en == 0 || mode == 0)      Set_Pwm(0,0);
        else if(flag_en)                                Control();
    break;
    default:
    break;
    }
}

十二、源码提供

夸克网盘:提取码:miXa

百度网盘:提取码:6666

Gitee:Automatic_Driving

CSDN:Automatic-Driving

十三、结语

本人能力有限,代码未必是最优解,若有可改进之处望在评论区留言。

若想看看其他方案,欢迎前往我好友的博客【2024电赛H题自动行使小车】 省一获奖方案整理汇

如有小伙伴想交流学习心得,欢迎加入群聊751950234,群内不定期更新代码,以及提供本人博客所有源码

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值