摘要:本文介绍使用光敏电阻实现追光小车
追光小车的基本功能就是可以向着光强更强的地方行驶。在这一基本目标的指引下,是有很多种方案可以选择的。至于哪种方案更合适,则取决于你要实现什么样的追光功能。例如对于一个普通小车,可以实现跟随光源左右摆动,这是最简单的追光动作了。复杂一点儿的可以追随光源向前、向后运动。如果是麦克纳姆轮小车,那就还可以做左右移动的动作,甚至各个方向的平移都可以通过光源来控制实现。
在本文所实现的追光小车,其功能主要是可以在光的指引下向左行驶、向右行驶或者执行,当指引光线消失的时候,小车停止运行。先看一下光敏电阻传感器的安装方法,如下图所示:
下面来看一下追光小车的具体接线方法:
模块 | 引脚 | 连接对象 |
L298N模块 | IN1 | ESP32的P18 |
IN2 | ESP32的P23 | |
IN3 | ESP32的P32 | |
IN4 | ESP32的P33 | |
OUT1、OUT2 | 右前轮TT电动机 | |
OUT3、OUT4 | 左前轮TT电动机 | |
+12V | 电池的正极 | |
GND | 电池的负极和ESP32的GND(分别连接) | |
+5V | ESP32的+5V(开发时不需要连接,运行时连接) | |
左一光敏电阻模块 | AO输出 | ESP32的P34 |
VCC | ESP32的+3.3V | |
GND | ESP32的GND | |
左二光敏电阻模块 | AO输出 | ESP32的P35 |
VCC | ESP32的+3.3V | |
GND | ESP32的GND | |
右一光敏电阻模块 | AO输出 | ESP32的P15 |
VCC | ESP32的+3.3V | |
GND | ESP32的GND | |
右二光敏电阻模块 | AO输出 | ESP32的P25 |
VCC | ESP32的+3.3V | |
GND | ESP32的GND |
在这里需要提醒的是,四个光敏电阻使用的电源电压为3.3V,这样AO输出的电压才不会超过3.3V。ESP32扩展板上面有个5V和3V3的跳线,可以将那两排排针的电压通过跳线设置成3.3V,在开发这个实验的时候,需要改变一下跳线的位置。
根据前面光敏电阻模块的安装布局,追光小车的实现的功能大致如下:
- 借用之前循迹小车实现的Car类,在这里就不进一步的解释了,直接拿过来用就可以了。
- 实现一个环境检测功能,让小车可以原地转动一定的角度,并且多次进行数据的采样,然后计算出采样数据的平均值,作为光敏模块的初值。
在这里提醒一下,光敏电阻模块对光的反馈还是很灵敏的,因此测试环境一方面是不能太亮,不能是太阳直射的地方,另一方面是光要比较均匀,小车放置在地上,不能有明显的影子。
- 根据实时采集的光敏电阻数值的变化量,来判断小车该往哪个方向行走。
- 利用ESP32的蓝牙功能,实现远程调试,并且可以通过远程发送命令来控制小车。
下面就来分段看一下追光小车的实现方法。首先还是定义一系列的常量,其中包括光敏电阻模块所使用的引脚,光敏电阻的数量,光强测试的间隔以及次数等。如下所示:
// 光敏电阻GPIO #define PHOTORESISTER_LEFT_1 34 #define PHOTORESISTER_LEFT_2 35 #define PHOTORESISTER_RIGHT_1 15 #define PHOTORESISTER_RIGHT_2 25 // GPIO config end #define PHOTORESISTER_NUMBERS 4 #define TEST_TIME_INTERVAL 200 //测试时间间隔 #define TEST_TIMES 40 //测试次数 |
接下来就是实现几个全局变量了。具体使用的全局变量如下所示:
// 全局常量 // 光敏电阻引脚数组 const uint8_t photoresister_pin[] = { PHOTORESISTER_LEFT_1, PHOTORESISTER_LEFT_2, PHOTORESISTER_RIGHT_1, PHOTORESISTER_RIGHT_2 }; // 记录每一个光敏电阻的初值 uint16_t envir_values[PHOTORESISTER_NUMBERS] = { 0, 0, 0, 0 }; // 所有光敏电阻总的初值 uint16_t total_value = 0; // 小车对象 Car car; // 蓝牙串口对象 BluetoothSerial SerialBT; // 小车运行状态 uint8_t status_run = 0; |
每个变量我都增加了注释,可以结合下面的程序来理解。下面就是环境初值的测试方法了。这个方法的执行过程大致是让首先让小车朝一个方向原地转动起来,然后按照预先设计的间隔,依次采集4个光敏电阻模块的测量值,并计算求和。最后,再算出测量的平均值以及总值。这两个值将作为后边进行判断的阈值使用。具体的实现代码如下:
// 测试环境初值 void test_environment() { // 原地测试光线强度 car.turnLeft(); for (int i = 0; i < TEST_TIMES; i++) { delay(TEST_TIME_INTERVAL); for (int j = 0; j < PHOTORESISTER_NUMBERS; j++) { envir_values[j] += analogRead(photoresister_pin[j]); } } car.stop(); // 计算求平均数 for (int i = 0; i < PHOTORESISTER_NUMBERS; i++) { envir_values[i] = envir_values[i] / TEST_TIMES; total_value += envir_values[i]; SerialBT.print(envir_values[i]); SerialBT.print(" "); } SerialBT.println(); } |
接下来就是追光行走方法了。这个方法就是采集光敏电阻的输出值,然后与初始阈值进行比较,如果总体光强明显变强,那么就说明有光照在小车前,小车需要运动,然后再判断运动的方向,如果左侧光强明显强于右侧,则向左转弯行驶,如果右侧光强明显强于左侧,则小车向右转弯行驶,否则小车就执行。具体代码如下所示:
// 采集每一个光敏电阻的输出值,并返回总值 uint16_t test_photoresister(uint16_t* vals) { uint16_t total = 0; for (int i = 0; i < PHOTORESISTER_NUMBERS; i++) { vals[i] = analogRead(photoresister_pin[i]); total += vals[i]; } return total; } // 追光行走 void follow_spot() { uint16_t test_values[PHOTORESISTER_NUMBERS]; uint16_t total = test_photoresister(test_values); SerialBT.print("O:"); SerialBT.print(total_value); SerialBT.print("\tc:"); SerialBT.print(total); if (total * 1.1 <= total_value) { // 是否明显光线变强 uint16_t l_total = test_values[0] + test_values[1] - envir_values[0] - envir_values[1]; uint16_t r_total = test_values[2] + test_values[3] - envir_values[2] - envir_values[3]; if (fabs(l_total) * 1.05 < fabs(r_total)) { // 是否右侧变化强于左侧 car.turnRight(); } else if (fabs(l_total) > fabs(r_total) * 1.05) { // 是否左侧变化强于右侧 car.turnLeft(); } else { car.run(); } } else { car.stop(); } } |
好了,追光小车的主要方法都实现了。可以看到,有了前面的Car类,小车的各种行驶就变得异常的简单了,都是调用一个方法就实现了。这就是封装的好处,可以让我们专注于追光有关的业务逻辑,而不用再关心小车控制的问题。
最后再介绍一下初始化方法和主程序loop循环。初始化的代码如下:
void setup() { // 初始化串口 Serial.begin(115200); SerialBT.begin("ESP32"); car.begin(); SerialBT.println("Setup finished!"); } |
这个初始化的方法也是异常的简单。就是初始化了串口、蓝牙串口和小车。
在主程序中,主要是实现了通过蓝牙接收的命令来控制小车的运行。当蓝牙收到字符“r”(run的简称)时,表示小车开始追光运行。当蓝牙收到字符“t”(test的简称)时,表示小车需要对环境光线进行测试。当蓝牙收到字符“s”(stop的简称)时,表示小车停止运行。具体的实现单模如下所示:
void loop() { while (SerialBT.available()) { char c = SerialBT.read(); if ('r' == c) { status_run = 1; } else if ('t' == c) { test_environment(); } else if ('s'==c) { status_run = 0; car.stop(); } SerialBT.print(c); } if(status_run==1) { follow_spot(); } delay(100); } |
好了,整个追光小车的实现方法就介绍完了。之前有人私信问我怎么用手机控制小车,相信通过这个例子,已经为你展示了一种最简单的方法。你可以把之前的代码改进一下,就能在手机或者电脑上通过蓝牙来控制小车的行驶了。