当前位置:   article > 正文

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

jy61p的零漂

一、前前言

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

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

二、前言

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同理。

  1. /* 全局宏定义 */
  2. #define AIN1_High DL_GPIO_setPins(DIR_PORT, DIR_AIN1_PIN)
  3. #define AIN1_Low DL_GPIO_clearPins(DIR_PORT, DIR_AIN1_PIN)
  4. #define AIN2_High DL_GPIO_setPins(DIR_PORT, DIR_AIN2_PIN)
  5. #define AIN2_Low DL_GPIO_clearPins(DIR_PORT, DIR_AIN2_PIN)
  6. #define BIN1_High DL_GPIO_setPins(DIR_PORT, DIR_BIN1_PIN)
  7. #define BIN1_Low DL_GPIO_clearPins(DIR_PORT, DIR_BIN1_PIN)
  8. #define BIN2_High DL_GPIO_setPins(DIR_PORT, DIR_BIN2_PIN)
  9. #define BIN2_Low DL_GPIO_clearPins(DIR_PORT, DIR_BIN2_PIN)
  10. /*
  11. * 初始化
  12. */
  13. void Init(void)
  14. {
  15. /* 关闭所有输出 */
  16. AIN1_Low;
  17. AIN2_Low;
  18. BIN1_Low;
  19. BIN2_Low;
  20. /* 使能PWM波输出 */
  21. DL_Timer_startCounter(PWM_0_INST);
  22. }

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

五、读取编码器数值

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

编码器Port配置:GPIO_EncoderA\B

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

2、配置IO口及中断

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

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

其余三个IO口配置同理。

3、代码填写

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

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

  1. /*
  2. * 编码器读取函数
  3. * 编码器A、B分别对应编码器A、B的引脚
  4. * EncoderA_CNT、EncoderB_CNT 分别对应编码器A、B的计数值
  5. * EncoderA_Port、EncoderB_Port 分别对应编码器A、B的端口
  6. */
  7. void Encodering(void)
  8. {
  9. EncoderA_Port = DL_GPIO_getEnabledInterruptStatus(GPIO_EncoderA_PORT, GPIO_EncoderA_PIN_0_PIN | GPIO_EncoderA_PIN_1_PIN);
  10. EncoderB_Port = DL_GPIO_getEnabledInterruptStatus(GPIO_EncoderB_PORT, GPIO_EncoderB_PIN_2_PIN | GPIO_EncoderB_PIN_3_PIN);
  11. /* 编码器A */
  12. if((EncoderA_Port & GPIO_EncoderA_PIN_0_PIN) == GPIO_EncoderA_PIN_0_PIN) //编码器A-Pin0
  13. {
  14. if(!DL_GPIO_readPins(GPIO_EncoderA_PORT,GPIO_EncoderA_PIN_1_PIN)) EncoderA_CNT--;
  15. else EncoderA_CNT++;
  16. }
  17. else if((EncoderA_Port & GPIO_EncoderA_PIN_1_PIN) == GPIO_EncoderA_PIN_1_PIN) //编码器A-Pin1
  18. {
  19. if(!DL_GPIO_readPins(GPIO_EncoderA_PORT,GPIO_EncoderA_PIN_0_PIN)) EncoderA_CNT++;
  20. else EncoderA_CNT--;
  21. }
  22. DL_GPIO_clearInterruptStatus(GPIO_EncoderA_PORT, GPIO_EncoderA_PIN_0_PIN|GPIO_EncoderA_PIN_1_PIN);
  23. /* 编码器B */
  24. if((EncoderB_Port & GPIO_EncoderB_PIN_2_PIN) == GPIO_EncoderB_PIN_2_PIN)
  25. {
  26. if(!DL_GPIO_readPins(GPIO_EncoderB_PORT,GPIO_EncoderB_PIN_3_PIN)) EncoderB_CNT--;
  27. else EncoderB_CNT++;
  28. }
  29. else if((EncoderB_Port & GPIO_EncoderB_PIN_3_PIN) == GPIO_EncoderB_PIN_3_PIN)
  30. {
  31. if(!DL_GPIO_readPins(GPIO_EncoderB_PORT,GPIO_EncoderB_PIN_2_PIN)) EncoderB_CNT++;
  32. else EncoderB_CNT--;
  33. }
  34. DL_GPIO_clearInterruptStatus(GPIO_EncoderB_PORT, GPIO_EncoderB_PIN_2_PIN|GPIO_EncoderB_PIN_3_PIN);
  35. }
  36. /**************************************************************************
  37. Function: Read encoder count per unit time
  38. Input : TIMX:Timer
  39. Output : none
  40. 函数功能:单位时间读取编码器计数
  41. 入口参数:TIMX:定时器
  42. 返回 值:速度值
  43. **************************************************************************/
  44. void Read_Encoder(void)
  45. {
  46. EncoderA_VEL = EncoderA_CNT;
  47. EncoderA_CNT = 0;
  48. EncoderB_VEL = EncoderB_CNT;
  49. EncoderB_CNT = 0;
  50. }

编码器定时器配置:TIMER_Encoder_Read

1、定时器初始化配置

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

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

3、代码填写

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

  1. /*
  2. * 编码器初始化
  3. */
  4. void Encoder_Init(void)
  5. {
  6. /* 关闭所有输出 */
  7. AIN1_Low;
  8. AIN2_Low;
  9. BIN1_Low;
  10. BIN2_Low;
  11. /* 使能编码器定时器、GPIO中断 */
  12. NVIC_EnableIRQ(GPIO_EncoderA_INT_IRQN);
  13. NVIC_EnableIRQ(GPIO_EncoderB_INT_IRQN);
  14. NVIC_EnableIRQ(TIMER_Encoder_Read_INST_INT_IRQN);
  15. DL_Timer_startCounter(TIMER_Encoder_Read_INST);
  16. /* 使能PWM波输出 */
  17. DL_Timer_startCounter(PWM_0_INST);
  18. }
  19. /*
  20. * 编码器读取中断服务函数
  21. * 读取编码器数值
  22. */
  23. void TIMER_Encoder_Read_INST_IRQHandler(void)
  24. {
  25. switch (DL_TimerG_getPendingInterrupt(TIMER_Encoder_Read_INST)){
  26. case DL_TIMER_IIDX_ZERO:
  27. Read_Encoder(); //赋值编码器数值
  28. // printf("%d,%d\r\n",EncoderA_VEL,EncoderB_VEL);
  29. break;
  30. default:
  31. break;
  32. }
  33. }

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

六、八路灰度传感器模块

1、IO口配置

普通输入IO口配置即可。

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

2、全局宏定义

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

  1. #define P1 DL_GPIO_readPins(Sensor_P1_PORT,Sensor_P1_PIN)
  2. #define P2 DL_GPIO_readPins(Sensor_P2_PORT,Sensor_P2_PIN)
  3. #define P3 DL_GPIO_readPins(Sensor_P3_PORT,Sensor_P3_PIN)
  4. #define P4 DL_GPIO_readPins(Sensor_P4_PORT,Sensor_P4_PIN)
  5. #define P5 DL_GPIO_readPins(Sensor_P5_PORT,Sensor_P5_PIN)
  6. #define P6 DL_GPIO_readPins(Sensor_P6_PORT,Sensor_P6_PIN)
  7. #define P7 DL_GPIO_readPins(Sensor_P7_PORT,Sensor_P7_PIN)
  8. #define P8 DL_GPIO_readPins(Sensor_P8_PORT,Sensor_P8_PIN)

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

七、串口获取巡航角

 1、串口配置

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

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

2、代码填写
  1. #include "GYRO.h"
  2. // 定义接收变量
  3. uint8_t RollL, RollH, PitchL, PitchH, YawL, YawH, VL, VH, SUM;
  4. float Pitch,Roll,Yaw;
  5. // 串口接收状态标识
  6. #define WAIT_HEADER1 0
  7. #define WAIT_HEADER2 1
  8. #define RECEIVE_DATA 2
  9. uint8_t RxState = WAIT_HEADER1;
  10. uint8_t receivedData[9];
  11. uint8_t dataIndex = 0;
  12. //发送置偏航角置零命令
  13. void Serial_JY61P_Zero_Yaw(void)
  14. {
  15. DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0XFF);
  16. DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0XAA);
  17. DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0X69);
  18. DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0X88);
  19. DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0XB5);
  20. delay_ms(100);
  21. DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0XFF);
  22. DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0XAA);
  23. DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0X76);
  24. DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0X00);
  25. DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0X00);
  26. delay_ms(100);
  27. DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0XFF);
  28. DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0XAA);
  29. DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0X00);
  30. DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0X00);
  31. DL_UART_Main_transmitDataBlocking(UART_JY61P_INST,0X00);
  32. delay_ms(500);
  33. }
  34. // 串口中断处理函数
  35. /*
  36. * 串口接收数据,并解析出角度
  37. * 接收到的数据格式为:0x55 0x53 0xXX 0xXX 0xXX 0xXX 0xXX 0xXX 0xXX 0xXX
  38. * 其中0xXX为角度数据,单位为0.1度
  39. * 角度数据为16位,高8位在前,低8位在后
  40. * 角度数据为正数表示顺时针旋转,负数表示逆时针旋转
  41. * 角度数据为0表示水平
  42. * 角度数据为180表示垂直
  43. * 角度数据为90表示垂直向上
  44. * 角度数据为-90表示垂直向下
  45. * 角度数据为360表示水平向右
  46. * 角度数据为-360表示水平向左
  47. */
  48. void UART_JY61P_INST_IRQHandler(void)
  49. {
  50. uint8_t uartdata = DL_UART_Main_receiveData(UART_JY61P_INST); // 接收一个uint8_t数据
  51. switch (RxState) {
  52. case WAIT_HEADER1:
  53. if (uartdata == 0x55) {
  54. RxState = WAIT_HEADER2;
  55. }
  56. break;
  57. case WAIT_HEADER2:
  58. if (uartdata == 0x53) {
  59. RxState = RECEIVE_DATA;
  60. dataIndex = 0;
  61. } else {
  62. RxState = WAIT_HEADER1; // 如果不是期望的第二个头,重置状态
  63. }
  64. break;
  65. case RECEIVE_DATA:
  66. receivedData[dataIndex++] = uartdata;
  67. if (dataIndex == 9) {
  68. // 数据接收完毕,分配给具体的变量
  69. RollL = receivedData[0];
  70. RollH = receivedData[1];
  71. PitchL = receivedData[2];
  72. PitchH = receivedData[3];
  73. YawL = receivedData[4];
  74. YawH = receivedData[5];
  75. VL = receivedData[6];
  76. VH = receivedData[7];
  77. SUM = receivedData[8];
  78. // 校验SUM是否正确
  79. uint8_t calculatedSum = 0x55 + 0x53 + RollH + RollL + PitchH + PitchL + YawH + YawL + VH + VL;
  80. if (calculatedSum == SUM) {
  81. // 校验成功,可以进行后续处理
  82. if((float)(((uint16_t)RollH << 8) | RollL)/32768*180>180){
  83. Roll = (float)(((uint16_t)RollH << 8) | RollL)/32768*180 - 360;
  84. }else{
  85. Roll = (float)(((uint16_t)RollH << 8) | RollL)/32768*180;
  86. }
  87. if((float)(((uint16_t)PitchH << 8) | PitchL)/32768*180>180){
  88. Pitch = (float)(((uint16_t)PitchH << 8) | PitchL)/32768*180 - 360;
  89. }else{
  90. Pitch = (float)(((uint16_t)PitchH << 8) | PitchL)/32768*180;
  91. }
  92. if((float)(((uint16_t)YawH << 8) | YawL)/32768*180 >180){
  93. Yaw = (float)(((uint16_t)YawH << 8) | YawL)/32768*180 - 360;
  94. }else{
  95. Yaw = (float)(((uint16_t)YawH << 8) | YawL)/32768*180;
  96. }
  97. } else {
  98. // 校验失败,处理错误
  99. }
  100. RxState = WAIT_HEADER1; // 重置状态以等待下一个数据包
  101. }
  102. break;
  103. }
  104. }

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

八、剩余外设配置

1、按键配置

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

按键EN配置同理

2、声光提示IO口

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

3、代码填写
  1. #define LED_High DL_GPIO_setPins(LED_PORT, LED_PIN_LED_PIN)
  2. #define LED_Low DL_GPIO_clearPins(LED_PORT, LED_PIN_LED_PIN)
  3. while(1)
  4. {
  5. /* 按键扫描 */
  6. Key_Scan();
  7. /* LED闪烁 */
  8. LED_Sound();
  9. }
  10. /*
  11. * 按键扫描函数
  12. * 按键按下时,切换mode
  13. * 按键按下时,切换flag_en确认使能
  14. */
  15. void Key_Scan(void)
  16. {
  17. /* 模式切换按键 */
  18. if(DL_GPIO_readPins(KEY_PORT, KEY_S2_PIN) == 0)
  19. {
  20. delay_ms(10);
  21. if(DL_GPIO_readPins(KEY_PORT, KEY_S2_PIN) == 0)
  22. {
  23. while(!DL_GPIO_readPins(KEY_PORT, KEY_S2_PIN));
  24. mode = (mode + 1) % 5;
  25. }
  26. }
  27. /* 使能按键 */
  28. if(DL_GPIO_readPins(KEY_PORT, KEY_EN_PIN) == 0)
  29. {
  30. delay_ms(10);
  31. if(DL_GPIO_readPins(KEY_PORT, KEY_EN_PIN) == 0)
  32. {
  33. while(!DL_GPIO_readPins(KEY_PORT, KEY_EN_PIN));
  34. flag_en = 1 - flag_en;
  35. }
  36. }
  37. }
  38. /*
  39. * LED闪烁函数
  40. */
  41. void LED_Sound(void)
  42. {
  43. if(flag_LED)
  44. {
  45. LED_High;
  46. LED_CNT = LED_CNT + 0.1;
  47. if(LED_CNT >= 800) //闪烁延时
  48. {
  49. LED_Low;
  50. LED_CNT = 0;
  51. flag_LED = 0;
  52. }
  53. }
  54. }

4、控制定时器TIMER_0

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

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

1)设置定时器周期500us

2)开启中断

九、速度环、差速环实现

速度环

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

  1. //速度环PID
  2. #define Kp1 40
  3. #define Ki1 0.13
  4. #define Kd1 0.1
  5. /*=================== 增量式PID控制设计 ===================*/
  6. //左A轮PID
  7. float PID_A(float Encoder,float Target)
  8. {
  9. static float Bias, Last_bias, Last2_bias, Pwm;
  10. Bias = Target - Encoder;
  11. Pwm += Kp1 * (Bias - Last_bias) + Ki1 * Bias + Kd1 * (Bias - 2 * Last_bias + Last2_bias);
  12. Last_bias = Bias;
  13. Last2_bias = Last_bias;
  14. return Pwm;
  15. }
  16. //右B轮PID
  17. float PID_B(float Encoder,float Target)
  18. {
  19. static float Bias, Last_bias, Last2_bias, Pwm;
  20. Bias = Target-Encoder;
  21. Pwm += Kp1 * (Bias - Last_bias) + Ki1 * Bias + Kd1 * (Bias - 2 * Last_bias + Last2_bias);
  22. Last_bias = Bias;
  23. Last2_bias = Last_bias;
  24. return Pwm;
  25. }

差速环

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

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

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

  1. //陀螺仪PID
  2. #define Kp3 1
  3. #define Ki3 0
  4. #define Kd3 0
  5. /*
  6. 函数功能:角度PID
  7. IN: 当前值,目标值
  8. OUT: 输出值
  9. */
  10. float GYRO_Control(float now,float target)
  11. {
  12. static float Bias, Last_bias, Last2_bias, Pwm;
  13. Bias = target-now;
  14. Pwm += Kp3 * (Bias - Last_bias) + Ki3 * Bias + Kd3 * (Bias - 2 * Last_bias + Last2_bias);
  15. Last_bias = Bias;
  16. Last2_bias = Last_bias;
  17. return Pwm;
  18. }
  19. /*
  20. 函数:Incremental_Quantity
  21. 功能:普通直行巡线模式
  22. 参数:无
  23. */
  24. int Incremental_Quantity(void)
  25. {
  26. int value = 0;
  27. if(!P1) //检测到最右端
  28. value += 12;
  29. if(!P2)
  30. value += 9;
  31. if(!P3)
  32. value += 7;
  33. if(!P4)
  34. value += 3;
  35. if(!P5)
  36. value -= 3;
  37. if(!P6)
  38. value -= 7;
  39. if(!P7)
  40. value -= 9;
  41. if(!P8)
  42. value -= 12;
  43. return value;
  44. }

速度环+差速环

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

  1. #define Limit 3800 //PWM波限幅
  2. /*************************************/
  3. float Speed_Middle = 30; //中值速度
  4. int Motor_Left, Motor_Right; //左右马达占空比
  5. /**************************************************************************
  6. Function: Control function
  7. Input : none
  8. Output : none
  9. 函数功能:控制小车巡线
  10. 入口参数:无
  11. 返回 值:无
  12. **************************************************************************/
  13. void Control(void)
  14. {
  15. float TargetA, TargetB; //目标速度
  16. float bias1, bias2, bias; //巡线、陀螺仪偏差
  17. bias1 = Incremental_Quantity();
  18. bias2 = GYRO_Control(Yaw, 0);
  19. bias = (是否检查到黑线) ? bias1 : bias2;
  20. TargetA = Speed_Middle - bias;
  21. TargetB = Speed_Middle + bias;
  22. Motor_Left = (int)PWM_Limit(PID_A(EncoderA_VEL,TargetA), Limit, -Limit); //占空比限幅
  23. Motor_Right = (int)PWM_Limit(PID_B(EncoderB_VEL,TargetB), Limit, -Limit);
  24. Set_Pwm(Motor_Left, Motor_Right);
  25. }
  26. /*
  27. 函数功能:设置PWM比较值
  28. IN: 左马达占空比,右马达占空比
  29. OUT: 无
  30. */
  31. void Set_Pwm(int Left, int Right)
  32. {
  33. if(Left > 0) {AIN1_High;AIN2_Low;} //左正转
  34. else {AIN1_Low;AIN2_High;} //左反转
  35. DL_Timer_setCaptureCompareValue(PWM_0_INST, myabs(Left), DL_TIMER_CC_0_INDEX);
  36. if(Right > 0) {BIN1_High;BIN2_Low;} //右正转
  37. else {BIN1_Low;BIN2_High;} //右反转
  38. DL_Timer_setCaptureCompareValue(PWM_0_INST, myabs(Right), DL_TIMER_CC_1_INDEX);
  39. }

十、四种巡线模式

1、整体代码

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

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

  1. #include "Sensor.h"
  2. /*
  3. 函数功能:路径控制总函数
  4. 参数:无
  5. */
  6. int Follow_Route(void)
  7. {
  8. static int cnt = 0;
  9. /* 模式一 */
  10. if(mode == 1)
  11. {
  12. Follow_Route_Mode1(); //模式一时,仅需判断一次黑线
  13. return 1;
  14. }
  15. /* 模式二 */
  16. else if(mode == 2)
  17. {
  18. Follow_Route_Mode2(); //模式二时,通过改变flag标志位实现四段路径的切换
  19. return 2;
  20. }
  21. /* 模式三 */
  22. else if(mode == 3)
  23. {
  24. Follow_Route_Mode3(0); //仅进行一次八字绕圈
  25. return 3;
  26. }
  27. /* 模式四 */
  28. else if(mode == 4)
  29. {
  30. Follow_Route_Mode4(); //循环绕四圈
  31. return 4;
  32. }
  33. return 0;
  34. }

2、全部代码

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

  1. #include "Sensor.h"
  2. #define Black_CNT1 30 //mode = 1 黑线持续次数
  3. #define Black_CNT2 30 //mode = 2 黑线持续次数
  4. #define Black_CNT3 50 //mode = 3、4 黑线持续次数
  5. #define White_CNT 1000 //白线持续次数
  6. #define White_CNT2 5000 //mode = 2 白线持续次数
  7. #define White_CNT3 7000 //mode = 3、4 白线持续次数
  8. extern volatile int flag; //flag状态位:0-暂停;1-AB;2-BC;3-CD;4-DA;
  9. extern volatile bool flag_LED;
  10. extern volatile int mode; //mode状态位:0-暂停;
  11. /*
  12. 函数功能:路径控制总函数
  13. 参数:无
  14. */
  15. int Follow_Route(void)
  16. {
  17. static int cnt = 0;
  18. /* 模式一 */
  19. if(mode == 1)
  20. {
  21. if(flag == 1) //AB段末尾
  22. {
  23. if(P1 || P2 || P3 || P4 || P5 || P6 || P7 || P8) //任一点检测到黑线
  24. {
  25. cnt++;
  26. // printf("%d\r\n", cnt);
  27. if(cnt > Black_CNT1) //检测到黑线持续5次
  28. {
  29. flag_LED = 1;
  30. flag = 0;
  31. cnt = 0;
  32. return 0;
  33. }
  34. }
  35. else cnt = 0; //连续检测黑线判定
  36. }
  37. }
  38. /* 模式二 */
  39. else if(mode == 2)
  40. {
  41. /* B点判定 */
  42. if(flag == 1)
  43. {
  44. if(P1 || P2 || P3 || P4 || P5 || P6 || P7 || P8) //任一点检测到黑线
  45. {
  46. cnt++;
  47. if(cnt > Black_CNT2) //检测到黑线持续5次
  48. {
  49. flag_LED = 1;
  50. flag = 2; //进入状态二
  51. cnt = 0;
  52. return 2;
  53. }
  54. }
  55. else cnt = 0;
  56. }
  57. /* C点判定 */
  58. else if(flag == 2)
  59. {
  60. if(!(P1 || P2 || P3 || P4 || P5 || P6 || P7 || P8)) //所有点检测到白线
  61. {
  62. cnt++;
  63. if(cnt > White_CNT2) //检测到白线持续White_CNT2次
  64. {
  65. flag_LED = 1;
  66. flag = 3;
  67. cnt = 0;
  68. return 3;
  69. }
  70. }
  71. else cnt = 0;
  72. }
  73. /* D点判定 */
  74. else if(flag == 3)
  75. {
  76. if(P1 || P2 || P3 || P4) //任一点检测到黑线
  77. {
  78. cnt++;
  79. if(cnt > Black_CNT2) //检测到黑线持续Black_CNT2次
  80. {
  81. flag_LED = 1;
  82. flag = 4;
  83. cnt = 0;
  84. return 4;
  85. }
  86. }
  87. else cnt = 0;
  88. }
  89. /* A点(终点)判定 */
  90. else if(flag == 4)
  91. {
  92. if(!(P1 || P2 || P3 || P4 || P5 || P6 || P7 || P8)) //所有点检测到白线
  93. {
  94. cnt++;
  95. if(cnt > White_CNT4 ) //检测到白线持续White_CNT4次
  96. {
  97. flag_LED = 1;
  98. flag = 0; //一圈结束
  99. cnt = 0;
  100. return 0;
  101. }
  102. }
  103. else cnt = 0;
  104. }
  105. }
  106. /* 模式三 */
  107. else if(mode == 3)
  108. {
  109. Follow_Route_Mode3(0); //仅进行一次绕圈
  110. return 3;
  111. }
  112. /* 模式四 */
  113. else if(mode == 4)
  114. {
  115. Follow_Route_Mode4(); //循环绕四圈
  116. return 4;
  117. }
  118. return 0;
  119. }
  120. /*
  121. 函数功能:模式三路径控制
  122. 参数: stop:循环停止标志位
  123. stop - 1:循环继续
  124. stop - 0:循环一次
  125. */
  126. int Follow_Route_Mode3(int stop)
  127. {
  128. static int cnt3 = 0;
  129. /* C点判定 */
  130. if(flag == 1)
  131. {
  132. if(P5 || P6 || P7 || P8) //车体左侧任一点检测到黑线
  133. {
  134. cnt3++;
  135. if(cnt3 > Black_CNT3) //检测到黑线持续Black_CNT3次
  136. {
  137. flag_LED = 1;
  138. flag = 2; //进入下一状态
  139. cnt3 = 0;
  140. return 2;
  141. }
  142. }
  143. else cnt3 = 0;
  144. }
  145. /* B点判定 */
  146. else if(flag == 2)
  147. {
  148. if(!(P1 || P2 || P3 || P4 || P5 || P6 || P7 || P8)) //所有点检测到白线
  149. {
  150. cnt3++;
  151. if(cnt3 > White_CNT3) //检测到白线持续White_CNT3次
  152. {
  153. flag_LED = 1;
  154. flag = 3;
  155. cnt3 = 0;
  156. return 3;
  157. }
  158. }
  159. }
  160. /* D点判定 */
  161. else if(flag == 3)
  162. {
  163. if(P1 || P2 || P3 || P4) //车体右侧任一点检测到黑线
  164. {
  165. cnt3++;
  166. if(cnt3 > Black_CNT3) //检测到黑线持续Black_CNT3次
  167. {
  168. flag_LED = 1;
  169. flag = 4;
  170. cnt3 = 0;
  171. return 4;
  172. }
  173. }
  174. }
  175. /* A点判定 */
  176. else if(flag == 4)
  177. {
  178. if(!(P1 || P2 || P3 || P4 || P5 || P6 || P7 || P8)) //所有点检测到白线
  179. {
  180. cnt3++;
  181. if(cnt3 > White_CNT3) //检测到白线持续White_CNT3次
  182. {
  183. flag_LED = 1;
  184. flag = stop; //停止
  185. cnt3 = 0;
  186. return 0;
  187. }
  188. }
  189. }
  190. return 0;
  191. }
  192. /*
  193. 函数功能:模式四路径控制
  194. */
  195. int Follow_Route_Mode4(void)
  196. {
  197. Follow_Route_Mode3(1); //循环三圈不停止
  198. Follow_Route_Mode3(1);
  199. Follow_Route_Mode3(1);
  200. Follow_Route_Mode3(0); //停止
  201. return 0;
  202. }

十一、模块整合

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

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

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

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

1、各个全局标志位代码的含义与赋值方式
  1. /*
  2. 按键一:改变mode值以切换当前模式
  3. 按键二:使能flag_en以确认当前模式
  4. flag : 状态机思想,每结束一段路径通过软件切换当前模式数
  5. */
  6. volatile int flag = 1; //flag状态位:0-暂停;1-AB段;2-BC段;3-CD段;4-DA段;
  7. volatile int mode = 0; //mode状态位:0-暂停;1-模式一... ...;
  8. volatile int flag_en = 0; //flag_en状态位:0-不使能;1-使能

2、初始化配置

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

  1. SYSCFG_DL_init();
  2. /* USER CODE BEGIN 2 */
  3. /* GYRO初始化 */
  4. Serial_JY61P_Zero_Yaw();
  5. NVIC_EnableIRQ(UART_JY61P_INST_INT_IRQN);
  6. delay_ms(500);
  7. /* 编码器及PWM初始化 */
  8. Encoder_Init();
  9. /* 使能控制定时器 */
  10. NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN);
  11. DL_Timer_startCounter(TIMER_0_INST);
  12. /* USER CODE END 2 */

3、while(1)填写

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

  1. while (1) {
  2. /* 按键扫描 */
  3. Key_Scan();
  4. if(flag_en) //使能控制开启
  5. {
  6. Follow_Route(); //路径控制总函数函数
  7. /* LED闪烁 */
  8. LED_Sound();
  9. }
  10. }
4、定时器控制中断

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

  1. /*
  2. * 定时器0中断服务函数
  3. */
  4. void TIMER_0_INST_IRQHandler(void)
  5. {
  6. switch (DL_TimerA_getPendingInterrupt(TIMER_0_INST)) {
  7. case DL_TIMERA_IIDX_ZERO:
  8. //flag位判断
  9. if(flag == 0 || flag_en == 0 || mode == 0) Set_Pwm(0,0);
  10. else if(flag_en) Control();
  11. break;
  12. default:
  13. break;
  14. }
  15. }

十二、源码提供

夸克网盘:提取码:miXa

百度网盘:提取码:6666

Gitee:Automatic_Driving

CSDN:Automatic-Driving

十三、结语

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

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

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

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号