摘要:本文介绍循迹小车的软件实现方法
接下来就来实现具体的Car类了。在这个类中,包含了与小车相关的基本属性和方法。涉及到的属性包括以下这些:
- 控制两个轮子运动的GPIO引脚
- PWM信号分辨率
- PWM信号频率
- PWM的通道
- PWM占空比率
涉及到小车的行为方法包括:
- 向前行驶
- 停止
- 向左转弯
- 向右转弯
- 原地向左转弯
- 原地向右转弯
规划好属性和方法之后,就可以实现类的定义了。具体的定义如下所示:
/********************************* car.h by 一起玩儿科技 2024/01/16 ********************************/ #pragma once #ifndef _YQWKJ_CAR_ #define _YQWKJ_CAR_ #include <Arduino.h> #define CAR_STOP 0 #define CAR_RUN 1 #define CAR_TURN_LEFT 2 #define CAR_TURN_LEFT_FAST 3 #define CAR_TURN_RIGHT 4 #define CAR_TURN_RIGHT_FAST 5 class Car { public: // 构造函数 Car(); Car(uint8_t l1,uint8_t l2,uint8_t r1,uint8_t r2); // 初始化方法 void begin(); void begin(uint32_t pwmFreq, uint8_t pwmResolutionsBit); // 设置PWM通道 void setChannel(uint8_t c1,uint8_t c2,uint8_t c3,uint8_t c4); // 设置PWM占空比率 void changeSpeed(uint32_t speedl,uint32_t speedr); // 向前行驶 void run(); // 停止行驶 void stop(); // 原地左转 void turnLeftFast(); // 原地右转 void turnRightFast(); // 左转 void turnLeft(); // 右转 void turnRight(); private: // 两轮的默认驱动引脚 uint8_t l1=32,l2=33,r1=18,r2=23; // PWM分辨率 uint8_t pwmResolutionsBit=12; // PWM频率 uint32_t pwmFreq=10000; // PWM默认的通道 uint8_t channels[4]={0,1,2,3}; // PWM默认占空比率 uint32_t speedl=pow(2,11),speedr=pow(2,11); // 修改小车的行驶状态 void changeStatus(uint32_t wl1,uint32_t wl2,uint32_t wr1,uint32_t wr2); }; #endif |
接下来就是实现这个类了,具体每个方法的编写方法都不复杂,都是以前见过的代码。具体实现方法如下:
/********************************* car.c by 一起玩儿科技 2024/01/16 ********************************/ #include "car.h" // 无参构造函数 Car::Car() {} // 构造函数 Car::Car(uint8_t l1,uint8_t l2,uint8_t r1,uint8_t r2) { this->l1=l1; this->l2=l2; this->r1=r1; this->r2=r2; } // 初始化方法 void Car::begin() { uint8_t wheels_pin[] = {l1,l2,r1,r2}; // 初始化LEDC PWM控制器 for (int i = 0; i < 4; i++) { ledcSetup(channels[i], this->pwmFreq, this->pwmResolutionsBit); ledcAttachPin(wheels_pin[i], channels[i]); } } // 初始化方法 void Car::begin(uint32_t pwmFreq, uint8_t pwmResolutionsBit) { this->pwmFreq=pwmFreq; this->pwmResolutionsBit=pwmResolutionsBit; begin(); } // 设置PWM通道 void Car::setChannel(uint8_t c1,uint8_t c2,uint8_t c3,uint8_t c4) { channels[0]=c1; channels[1]=c2; channels[2]=c3; channels[3]=c4; } // 修改占空比分辨率,这将改变车轮的转速 void Car::changeSpeed(uint32_t speedl,uint32_t speedr) { this->speedl=speedl; this->speedr=speedr; } // 向前直行 void Car::run() { changeStatus(speedl, 0, speedr,0); } // 停止行驶 void Car::stop() { changeStatus(0,0,0,0); } // 左转 void Car::turnLeft() { changeStatus(0,0,speedr,0); } // 右转 void Car::turnRight() { changeStatus(speedl,0,0,0); } // 原地左转 void Car::turnLeftFast() { changeStatus(0,speedl,speedr,0); } // 原地右转 void Car::turnRightFast() { changeStatus(speedl,0,0,speedr); } // 改变PWM输出状态 void Car::changeStatus(uint32_t wl1,uint32_t wl2,uint32_t wr1,uint32_t wr2) { ledcWrite(channels[0], wl1); ledcWrite(channels[1], wl2); ledcWrite(channels[2], wr1); ledcWrite(channels[3], wr2); } |
Car类每个方法调用的函数都是之前多次使用过的,在这里就不再进一步的解释了。
接下来要实现的就是主程序了。在这个主程序的实现中,两个关键的地方就是初始化函数和中断函数。在实现这两个函数之前,先看一下这个程序中定义的常量,主要配置的内容就是接收循迹模块状态的GPIO引脚。如下所示:
// 配置GPIO #define SENSOR_LEFT_D1 27 #define SENSOR_LEFT_D2 26 #define SENSOR_RIGHT_D1 21 #define SENSOR_RIGHT_D2 4 |
而程序用到的全局变量只有一个,就是生成了一个Car对象的实例,如下所示:
Car car; |
程序的初始化工作主要包括以下一些内容:
- 初始化串口,主要为了输出调试信息
- 初始化小车对象
- 初始化循迹模块接入的引脚
- 绑定中断处理方法
- 小车的运动状态初始化
具体的实现代码如下:
void setup() { // put your setup code here, to run once: Serial.begin(115200); // 初始化小车对象的实例 car.begin(); pinMode(SENSOR_LEFT_D1,INPUT); pinMode(SENSOR_LEFT_D2,INPUT); pinMode(SENSOR_RIGHT_D1,INPUT); pinMode(SENSOR_RIGHT_D2,INPUT); delay(10000); // 延迟小车运行
// 绑定引脚中断 attachInterrupt(SENSOR_LEFT_D1, tracking_isr, CHANGE); attachInterrupt(SENSOR_LEFT_D2, tracking_isr, CHANGE); attachInterrupt(SENSOR_RIGHT_D1, tracking_isr, CHANGE); attachInterrupt(SENSOR_RIGHT_D2, tracking_isr, CHANGE); // 初始化一下小车的运动状态 tracking_isr(); } |
接下来就是最后一个处理小车中断的方法tracking_isr()的实现了,在这个方法中,要实现之前规划的几种循迹模块状态对应的处理方法。因为具体的方法都已经实现了,在这里,只需要根据循迹模块的状态,调用相应的方法就可以了。具体的实现如下所示:
// 循迹模块状态变化的处理中断方法 void tracking_isr() { // 检测每个传感器是否在黑线上 bool l1=digitalRead(SENSOR_LEFT_D1); bool l2=digitalRead(SENSOR_LEFT_D2); bool r1=digitalRead(SENSOR_RIGHT_D1); bool r2=digitalRead(SENSOR_RIGHT_D2); // car.run(); if(l1&&l2&&r1&&r2 ) { // 向前直行 car.run(); } if(l1==false&&l2==false&&r1==false&&r2==false) { // 向前直行 car.run(); } else if(l1&&r1){ // 向前直行 car.run(); } else if(l2) { // 原地左转 car.turnLeftFast(); } else if(l1) { // 左转 car.turnLeft(); } else if(r2) { // 原地右转 car.turnRightFast(); } else if(r1) { // 右转 car.turnRight(); } } |
好了,整个循迹小车的的功能实现就全部完成了。接下来就可以编译,上传程序,来进行测试一下了,看看是不是可以达到当初的设计目的了。
在进行测试和调试的过程中,需要注意以下几个地方:
- 首先应该进行模拟运行调试。用东西把小车支起来,让其车轮悬空,并可以自由的旋转。然后,在白纸上画好之前设计时的几种情况,然后放到循迹传感器的下边,首先保证循迹模块是设计时的状态,看一下,车轮的运转是不是按照预先设想的那样。如果,不是,应该分析代码,找出原因。
- 在模拟场景都通过的情况下,就可以到实际场地进行测试了。在测试之前,要把小车摆到轨迹线上,调整循迹模块的调节电位器,保证每一个循迹传感器都能正确的识别场地的轨迹线。同时还要考虑不同光线的影响,进行不同角度,不同位置的测试,确保循迹传感器可以可靠的反映出轨迹线来。
- 如果循迹模块的识别始终存在误判的情况,可以适当的增加或者减小循迹模块与地面的距离。通常循迹模块与地面的距离不大于2.5厘米。
- 要确保中间的两个循迹模块间的距离大于黑色轨迹线的宽度。也就是要保证两个传感器可以同时落在轨迹线的两侧。
- 刚开始调试的时候,要让小车的速度尽量的慢,等到初步测试都正常了,可以适当的调快行驶的速度。
好了,最后祝大家调试顺利!调试中遇到问题,可以给我留言。