【项目展示】基于CW32的遥控循迹小车

例程链接

https://pan.baidu.com/s/14KtIh6EcJYq_kQCItm9tTw?pwd=8mhq

提取码:8mhq

一、概述

CW32循迹、遥控小车具有循迹和遥控两种功能,小车的硬件模块由CW32F030C8T6小蓝板、智能小车控制底板、BT04-E 蓝牙模块、OLED屏幕、TB6612和红外循迹模块组成,电源采用可充电锂电池供电,建议不要使用 1.5V 干电池供电。

图1 CW32小车

二、硬件部分

2.1主控板

小车主控板由小蓝板和控制底板组成,小蓝板通过排母与控制底板相连,控制底板上还预留了按键等功能。主控板的原理图分别如下:

图2-1 小车控制底板原理图1

图2-2 小车控制底板原理图2

2.2蓝牙模块

蓝牙模块采用 BT04-E 模块,为单独小板,通过排母插在小车主控板上:

图2-3 BT04-E 模块

通过模块背面丝印可以确定与主控板的连接线序,主控板上为蓝牙预留的位置如下:

图2-4 主控板蓝牙位置

查找CW32F030的数据手册可知 PA2 和 PA3 为其串口2,调用串口2对其发送信息即可通过串口蓝牙助手接收对应的消息。

2.3循迹模块

循迹模块通过排线与主控底板相连,参考原理图里的红外对管接口所对应的引脚。

图2-5 循迹模块

循迹模块的工作原理:传感器的红外发射二极管不断发射红外线,当发射出的红外线没有被反射回来或被反射回来但强度不够大时,红外接收管一直处于关断状态,此时模块的 CH 端为高电平,通过比较器后输出为低电平,指示 LED 被点亮; 当被检测物体出现在检测范围内时,红外线被反射回来且强度足够大,红外接收管饱和,此时模块的输出端为低电平,经过比较器后输出为高电平,LED 灯熄灭。由于黑色会吸收红外线,所以总结为:检测到黑线--灯亮、输出低电平;未检测到黑线--灯灭、输出高电平。

图2-6 循迹模块原理图

2.4TB6612芯片

TB6612是一款常用的双路直流电机驱动器芯片,常用于控制小型电动机或机器人的运动。该芯片具有高效、可靠和灵活的特点,适用于各种电气控制应用。

图2-7 TB6612 和控制底板

TB6612芯片具有以下主要特性:

  1. 双路驱动:TB6612可以控制两个直流电机的转动,支持正转、反转和停止功能。因此,它可以同时控制两个电机的运动,实现平稳的双轮驱动或其他双电机配置。
  2. 高电流输出:该芯片能够提供高达1.2A的持续输出电流,并且具有1.5A的瞬时峰值电流能力。这使得TB6612在控制较大功率电机时表现出色,适用于一些对功率要求较高的应用场景。
  3. 低功耗:TB6612在待机模式下的功耗非常低,可以有效延长电池寿命,适用于依赖电池供电的设备和机器人。
  4. 内置保护功能:芯片内部集成了过温保护、过电流保护和欠压锁定等保护功能,可以保护电机和芯片本身免受损坏或过载的风险。
  5. 灵活的控制接口:TB6612支持多种控制接口,包括PWM控制、频率锁定和直接控制模式等,可以根据具体需求选择合适的控制方式。

TB6612 可以控制两路电机,分别由 AIN1、AIN2、PWMA、BIN1、BIN2、PWMB组成,下面是AIN和BIN不同输入时控制电机的转动方向真值表。PWMA 和 PWMB 输入不同占空比的 PWM 波可以控制电机的转速快慢。

IN1IN2电机状态
00制动
01正转
10反转
11制动

三、软件部分

3.1循迹模块检测判断

循迹模块检测,根据 4 个灯的亮灭情况共有 16 种状态,每种状态对应小车在黑线上的一种情况,根据不同的情况有不同的控制策略。

void IR_Check(void)
{
  IR_Sensor[0] = GPIO_ReadPin(CW_GPIOB,GPIO_PIN_12);  //存放循迹模块输入值
  IR_Sensor[1] = GPIO_ReadPin(CW_GPIOB,GPIO_PIN_13);
  IR_Sensor[2] = GPIO_ReadPin(CW_GPIOB,GPIO_PIN_14);
  IR_Sensor[3] = GPIO_ReadPin(CW_GPIOB,GPIO_PIN_15);

  /*********************************只有一个灯亮****************************/
  if(IR_Sensor[0] == 1 && IR_Sensor[1] == 1 && IR_Sensor[2] == 0 && IR_Sensor[3] == 1)      //略微偏离道路 偏左,需要右转
  { Road_Error = 10; Flag_BaseSpeed = 10; }
  else if(IR_Sensor[0] == 1 && IR_Sensor[1] == 0 && IR_Sensor[2] == 1 && IR_Sensor[3] == 1) //略微偏离道路 偏右,需要左转
  { Road_Error = -10; Flag_BaseSpeed = 10; }
  else if(IR_Sensor[0] == 1 && IR_Sensor[1] == 1 && IR_Sensor[2] == 1 && IR_Sensor[3] == 0) //较大偏离道路 偏左,需要右转
  { Road_Error = 20; Flag_BaseSpeed = 20; }
  else if(IR_Sensor[0] == 0 && IR_Sensor[1] == 1 && IR_Sensor[2] == 1 && IR_Sensor[3] == 1) //较大偏离道路 偏右,需要左转
  { Road_Error = -20; Flag_BaseSpeed =20; }
  /*********************************两个灯亮****************************/
  else if(IR_Sensor[0] == 0 && IR_Sensor[1] == 0 && IR_Sensor[2] == 1 && IR_Sensor[3] == 1) //需要左转
  { Road_Error = -40; Flag_BaseSpeed = 100; }
  else if(IR_Sensor[0] == 0 && IR_Sensor[1] == 1 && IR_Sensor[2] == 0 && IR_Sensor[3] == 1) //直行
  { Road_Error = 0; Flag_BaseSpeed = 0; }
  else if(IR_Sensor[0] == 1 && IR_Sensor[1] == 1 && IR_Sensor[2] == 0 && IR_Sensor[3] == 0) //需要右转
  { Road_Error = 40; Flag_BaseSpeed = 100; }
  else if(IR_Sensor[0] == 1 && IR_Sensor[1] == 0 && IR_Sensor[2] == 1 && IR_Sensor[3] == 0) //直行
  { Road_Error = 0; Flag_BaseSpeed = 0; }
  else if(IR_Sensor[0] == 1 && IR_Sensor[1] == 0 && IR_Sensor[2] == 0 && IR_Sensor[3] == 1) //未偏离道路
  { Road_Error = 0; Flag_BaseSpeed = 0; }
  else if(IR_Sensor[0] == 0 && IR_Sensor[1] == 1 && IR_Sensor[2] == 1 && IR_Sensor[3] == 0) //保持之前的操作
  { Road_Error = 0; Flag_BaseSpeed = 0; }
  /*********************************三个灯亮****************************/
  else if(IR_Sensor[0] == 0 && IR_Sensor[1] == 0 && IR_Sensor[2] == 0 && IR_Sensor[3] == 1) //需要左转
  { Road_Error = -40; Flag_BaseSpeed = 100; }
  else if(IR_Sensor[0] == 0 && IR_Sensor[1] == 0 && IR_Sensor[2] == 1 && IR_Sensor[3] == 0) //需要右转
  { Road_Error = 20; Flag_BaseSpeed = 0; }
  else if(IR_Sensor[0] == 0 && IR_Sensor[1] == 1 && IR_Sensor[2] == 0 && IR_Sensor[3] == 0) //需要左转
  { Road_Error = -20; Flag_BaseSpeed = 0; }
  else if(IR_Sensor[0] == 1 && IR_Sensor[1] == 0 && IR_Sensor[2] == 0 && IR_Sensor[3] == 0) //需要右转
  { Road_Error = 40; Flag_BaseSpeed = 100; }
  /*********************************零、四个灯亮****************************/
  else if(IR_Sensor[0] == 1 && IR_Sensor[1] == 1 && IR_Sensor[2] == 1 && IR_Sensor[3] == 1) //没有检测到线,保持之前的操作
  ;
  else if(IR_Sensor[0] == 0 && IR_Sensor[1] == 0 && IR_Sensor[2] == 0 && IR_Sensor[3] == 0) //全是线,说明在十字路口,保持之前的操作
  ;
}

3.2PID计算控制

PID 计算控制根据红外循迹模块的亮灭情况,分别控制小车的基速和差速,从而控制小车运动的方向。

/**
 * @brief       PID基速控制
 * @param       Encoder:Flag_BaseSpeed ,Target:0
 * @return      基速 PID 计算值
 */
int PID_BaseSpeed(int Encoder,int Target)
{
  float V_Base_Kp = 30,V_Base_Kd = 100; //Kp、Kd
  static float Bias,PID,Last_Bias;      //本次偏差、PID计算值、上次偏差
        
  Bias = Encoder - Target;              //计算本次偏差
  PID = MAXOUTPUT - V_Base_Kp * Bias + V_Base_Kd * (Bias - Last_Bias); //PID计算
  Last_Bias = Bias;                     //存储偏差
  return PID;
}
/**
 * @brief       PID差速控制
 * @param       Encoder:Road_Error ,Target:0
 * @return      差速 PID 计算值
 */
int PID_DiffSpeed(int Encoder,int Target)
{
  float V_Diff_Kp = 80,V_Diff_Ki = 0.08,V_Diff_Kd = 100;//Kp、Ki、Kd
  static float Bias_D,PID_D,Integral_Bias,Last_Bias_D;  //本次偏差、PID计算值、积分累计值、上次偏差
        
  Bias_D = Encoder - Target;  //计算本次偏差
  Integral_Bias += Bias_D;    //积累偏差
  PID_D = V_Diff_Kp * Bias_D + V_Diff_Ki * Integral_Bias + V_Diff_Kd * (Bias_D - Last_Bias_D);//PID计算
  Last_Bias_D = Bias_D;       //存储偏差
  return PID_D;
}
/**
 * @brief       小车控制
 * @param       无
 * @return      无
 */
void Car_Control(void)
{
  OUTPUT_Left = PID_BaseSpeed(Flag_BaseSpeed,0) + PID_DiffSpeed(Road_Error,0);    //左轮占空比计算
  OUTPUT_Right = PID_BaseSpeed(Flag_BaseSpeed,0) - PID_DiffSpeed(Road_Error,0);   //右轮占空比计算
        
  if(OUTPUT_Left > MAXOUTPUT)OUTPUT_Left = MAXOUTPUT;    //限制大小
  else if(OUTPUT_Left < 0)OUTPUT_Left = 0;
  if(OUTPUT_Right > MAXOUTPUT)OUTPUT_Right = MAXOUTPUT;
  else if(OUTPUT_Right < 0)OUTPUT_Right = 0;
        
  GTIM_SetCompare3(CW_GTIM1,OUTPUT_Left);                //左轮
  GTIM_SetCompare4(CW_GTIM1,OUTPUT_Right);               //右轮
}

3.3遥控部分

遥控部分其实就是,通过串口蓝牙接收信息并向对应的方向运动,下面是蓝牙串口的中断服务程序:

// 串口2中断处理函数
void UART2_IRQHandler(void)
{
  unsigned char TxRxBuffer;
  if (USART_GetITStatus(CW_UART2, USART_IT_RC) != RESET)
  {
    USART_ClearITPendingBit(CW_UART2, USART_IT_RC); // 清除中断标志位
    TxRxBuffer = USART_ReceiveData_8bit(CW_UART2);  // 将接收到的数据放入TxRxBuffer
    USART2_RX_BUF[rx2Index] = TxRxBuffer; // 将接收到的数据放入缓冲区
    if (rx2Index < USART2_REC_LEN - 1)    // 做数据长度的限制,留一个字节用于结束字符或者溢出检测
    {
      // 接收到的字符包含 \n 或者 \r 结束接收
      if (USART2_RX_BUF[rx2Index - 1] == '\n' || USART2_RX_BUF[rx2Index - 1] == '\r')
      {
        USART2_RX_BUF[rx2Index] = '\0'; // 在最后一个字节加上空字符,表示字符串结束
      }
      else rx2Index++;
    }
                                
    if(USART2_RX_BUF[0] == 't')Flag_Mode = 1 - Flag_Mode;  //发送字符 ‘t’来切换模式
                                
    if(Flag_Mode == 0)
    {
      if(USART2_RX_BUF[0] == '1')Flag_Start = 1;
      else Flag_Start = 0;
    }
                                
    else if(Flag_Mode == 1)
    {
      if(USART2_RX_BUF[0] == 'w')Flag_Direction = 1;
      else if(USART2_RX_BUF[0] == 's')Flag_Direction = 2;
      else if(USART2_RX_BUF[0] == 'a')Flag_Direction = 3;
      else if(USART2_RX_BUF[0] == 'd')Flag_Direction = 4;
      else Flag_Direction = 9;
    }
      rx2Index = 0; // 清除数据标志
  }
}

四、调试

4.1调试场地

调试场地对小车的要求包括直角、交叉点、弯道等,具体如下图所示:

图4-1 智能小车巡线赛道

4.2调试提示

  • 小车运动主要由基速环和差速环的 PID 控制,可以先将差速环的 PID 参数整定下来,再调基速环的参数。
  • 循迹模块受到环境光的影响较大,最好在光线均匀和充足的环境下调试。
  • 电池电压同样会影响循迹模块的性能,建议不要使用 1.5V 干电池调试,而是使用锂电池。在电池接近没电时循迹模块不能正常工作。
  • 在弯道处如果小车不能及时转向,可以适当降低速度和增大差速环的 Kp 值。
  • 小车在交叉点处的循迹受到速度影响较大,较低的速度可能会使小车无法按照规定路线循迹,可以提高车速或者更换具有编码器的电机做轮式里程计来对该点做预判。
  • 30
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值