一、前前言
第十二届浙江省大学生电子设计竞赛终于落下帷幕了,名单已公示,几家欢喜几家愁?我觉得每一位经历过电赛的朋友都称得上英雄,我想我们所学到的并非是“省一等”或“成功参赛奖”一个头衔能囊括的,相信真正有价值的东西会在人生的道路上生根发芽。
我写这篇博客的最初目的,也只是想用代码的方式记录这段伟大的时光。
二、前言
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.c、Control.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
十三、结语
本人能力有限,代码未必是最优解,若有可改进之处望在评论区留言。
如有小伙伴想交流学习心得,欢迎加入群聊751950234,群内不定期更新代码,以及提供本人博客所有源码