赞
踩
第十二届浙江省大学生电子设计竞赛终于落下帷幕了,名单已公示,几家欢喜几家愁?我觉得每一位经历过电赛的朋友都称得上英雄,我想我们所学到的并非是“省一等”或“成功参赛奖”一个头衔能囊括的,相信真正有价值的东西会在人生的道路上生根发芽。
我写这篇博客的最初目的,也只是想用代码的方式记录这段伟大的时光。
2024年省赛题目如下:
不难看出,题目的难点只有两个:
1、如何熟练使用TI MSPM0系列的板子?
2、无黑线部分如何尽可能保持直行?
对于第一个问题,我先给出我的解决方法:
第一个链接是B站UP主Torris-Yin的基础教学视频,我觉得讲的很好,故上附链接推荐给各位。
第二个链接是我自己整理的纯代码形式的G3507资料,仅供参考!
第二个问题,我的解决方法是购买高精度的陀螺仪模块——JY61P、MPU6050。(不推荐使用MPU6050,需要软件去零漂)
好了,难点都解决了,现在发现其实拿下省一也没难度吧!
基本思路就是:有线巡线,无线靠巡航角。
接下来进入正题,我会尽量按顺序一一介绍各模块的配置与使用,然后将各模块整合一块。
源码下附全开源,可根据需求跳转查看相应部分。
功底不深,多多包涵!
PWM Period Count:自动重装载值,相当于CUBEMX中的ARR
PWM Frequency: PWM输出频率,建议选择15k ~ 20kHz间
Channel Update Mode:死区PWM宽度设置,此处不设置死区影响不大。
此四口仅配置为普通输出IO口即可
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
Internal Resistor:上下拉电阻配置,由于读取的编码器有效电平为高电平,故选择配置为下拉电阻
Input Filter: 输入电平滤波,降低误读率
其余三个IO口配置同理。
此代码选择的是四分频的编码器度数方式,精度更高。
想对编码器模式了解更多?详见【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
因配置为周期向下计数模式,故中断对应的选择Zero event,即定时器计数到0触发中断。
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;
- }
- }
至此,我们实现了读取马达的速度值。
普通输入IO口配置即可。
因为灰度传感器输出电压一般大于3.3V,建议不要配置上下拉电阻。
检测到黑线时,输出高电平,变量置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)
至此,完成八路灰度传感器基础配置完毕。
根据JY61P数据使用手册,配置为普通串口中断配置即可。(模块波特率需通过上位机修改,这里不赘述)
若想进一步提高通讯效率,详见文章中的串口DMA传输LP-MSPM03507学习资料汇总
- #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;
- }
- }
至此,陀螺仪模块配置完毕。
按键一端接IO口,另一端接地即可,建议串联一个1kΩ左右电阻
按键EN配置同理
题目中要求每经过一个点需声光报警,我们配置为普通输出IO口即可。
- #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;
- }
- }
- }
控制函数需要保证尽可能的不被打断,所以放在定时器里是一个很好的选择。
而放在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);
- }
由于此部分函数过于冗长,故先采用伪代码形式呈现供大家理解。代码虽长,逻辑简单。
基本思路为:通过按键切换巡线模式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;
- }
逻辑不难,大家自行理解吧!
- #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函数代码
- /*
- 按键一:改变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-使能
用惯了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 */
-
CPU不断扫描按键状态,当开启使能时开启巡线函数。
- while (1) {
-
- /* 按键扫描 */
- Key_Scan();
-
- if(flag_en) //使能控制开启
- {
- Follow_Route(); //路径控制总函数函数
- /* LED闪烁 */
- LED_Sound();
- }
- }
每隔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,群内不定期更新代码,以及提供本人博客所有源码
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。