摘要:本文介绍如何使用MicroPython语言控制超声波传感器和舵机实现小车的自动避障功能

在介绍Python版的自动避障小车之前,先介绍一下用Python如何控制OLED显示屏,所使用的显示屏模块还是SSD1306芯片驱动的0.96寸OLED-IIC显示屏模块。

首先打开Mixly软件,然后选择“Python ESP32”开发板。对于0.96寸OLED显示屏的功能位于“外接显示”、“OLED显示屏”功能组中。首先需要创建一个IIC对象,这里需要配置的是SCL和SDA引脚,频率使用默认值100000就可以了。接下来初始化一个128x64的OLED对象,如下图所示:

esphome esp32 舵机 esp32控制舵机代码_esp32

然后就是利用“OLED显示屏”功能模块中的各种输出功能来布置屏幕显示的内容了。我这里还是只显示文字“欢迎来到一起玩儿科技!”和一个内置的心形。配置页面输出的内容如下所示:

esphome esp32 舵机 esp32控制舵机代码_esp32_02

好了,接下来看一下运行的效果吧。如下图所示。

esphome esp32 舵机 esp32控制舵机代码_esphome esp32 舵机_03

下面就来看一下Mixly为我们生成的代码。

import machine
import oled128x64
from expression_picture import Heart


i2c_extend = machine.SoftI2C(scl = machine.Pin(22), sda = machine.Pin(21), freq = 100000)
oled = oled128x64.OLED(i2c_extend,address=0x3c,font_address=0x3A0000)

oled.image(Heart,x = 63,y = 0,size = 1)
oled.shows('欢迎来到',x = 0,y = 0,size = 1,space = 0,center = False)
oled.shows('一起玩儿',x = 0,y = 14,size = 1,space = 0,center = False)
oled.shows('科技!',x = 0,y = 28,size = 1,space = 0,center = False)

可以看到,这个程序的工作过程。首先是根据配置的参数生成了IIC对象,然后是创建了OLED对象的实例,最后是利用oled的内置方法显示了图形和文字信息。oled还可以画各种图形,有兴趣的可以自己尝试一下。

前边已经用C语言在Arduino IDE中实现了自动避障小车。本文介绍一下用Python如何实现自动避障小车。小车的接线方法和之前保持一致,不清楚的可以参考之前的文章。

因为程序较之前只使用单独模块的复杂一些,因此这次使用的是Thonny开发工具来编写Python代码。在开发之前,先来确认一下开发环境。Thonny的安装在前边已经讲解过了,首先打开Thonny,然后连接ESP32开发板。这时就可以看到ESP32处理器内保存的文件,首先要确保servo.py和sonar.py文件是存在的。如下图所示:

esphome esp32 舵机 esp32控制舵机代码_esphome esp32 舵机_04

因为这两个文件是支持舵机的Servo类和超声波传感器的Sonar类。有这两个类,我们就不需要再去自己实现底层通信协议了。只要直接使用这两个类就可以控制舵机的运动和超声波传感器的测量了。如果没有这两个文件,那么就参考之前的文章,在Mixly中测试一下你的舵机和激光测距模块,Mixly会把这两个文件传到你的EXP32处理器中。

这次的开发,首先是定义一个Car对象,这个Car对象包含了对小车运动的基本功能。例如:前进、后退、停止、转弯等。具体每一个方法的实现方法与C语言基本相同,只是控制ESP32输出PWM控制信号的方法不同而已。在C语言中定义的常量,在Python类中,可以转换为类的私有变量,也是集中放置在类的开始位置,这样有利于将来的维护工作。实现后的Car类如下所示:

"""
Car

MicroPython library for the Car
===========================================
一起玩儿科技  2024-01-07
"""

import machine
import time

#Car,智能小车控制对象
class Car:
    
    #控制车轮的GPIO
    __WHEEL_LEFT_1=32
    __WHEEL_LEFT_2=33
    __WHEEL_RIGHT_1=18
    __WHEEL_RIGHT_2=23
    
    #PWM的频率
    __MOTO_PWM_FREQ=10000
    #两个轮子的PWM占空比分辨率
    __SPEED_L=32768
    __SPEED_R=32768
    
    __TURN_TIME=1000

    #初始化方法
    def __init__(self):
        self.__wheel_l1=machine.PWM(machine.Pin(self.__WHEEL_LEFT_1))
        self.__wheel_l2=machine.PWM(machine.Pin(self.__WHEEL_LEFT_2))
        self.__wheel_r1=machine.PWM(machine.Pin(self.__WHEEL_RIGHT_1))
        self.__wheel_r2=machine.PWM(machine.Pin(self.__WHEEL_RIGHT_2))
        self.__wheel_l1.freq(self.__MOTO_PWM_FREQ)
        self.__wheel_l2.freq(self.__MOTO_PWM_FREQ)
        self.__wheel_r1.freq(self.__MOTO_PWM_FREQ)
        self.__wheel_r2.freq(self.__MOTO_PWM_FREQ)

    #改变运行状态方法
    def __change_status(self,wl1,wl2,wr1,wr2):
        self.__wheel_l1.duty_u16(wl1)
        self.__wheel_l2.duty_u16(wl2)
        self.__wheel_r1.duty_u16(wr1)
        self.__wheel_r2.duty_u16(wr2)

    #停止运行方法
    def stop(self):
        self.__change_status(0,0,0,0)

    #向前行进方法
    def run(self):
        self.__change_status(self.__SPEED_L,0,self.__SPEED_R,0)

    #向后倒退方法
    def back(self,period):
        self.__change_status(0,self.__SPEED_L,0,self.__SPEED_R)
        time.sleep_ms(period)
        self.stop()

    #转向方法
    def turn(self,direction):
        if direction==0:
            self.__change_status(self.__SPEED_L,0,0,self.__SPEED_R)
            time.sleep_ms(self.__TURN_TIME)
        elif direction==180:
            self.__change_status(0,self.__SPEED_L,self.__SPEED_R,0)
            time.sleep_ms(self.__TURN_TIME)
        else :
            self.__change_status(0,self.__SPEED_L,self.__SPEED_R,0)
            time.sleep_ms(self.__TURN_TIME*2)
        self.stop()

这里的方法与前面C语言中是一一对应的,除了语法和编码习惯有了一些简单的修改外,每个方法实现的逻辑都是完全一样的,所以在这里就不逐句的一一解释了。不理解的地方可以参考之前的解释。

主程序的实现与C语言依然差别不大,可以说代码都是一一对应的,具体如下所示:

import machine
import servo
import sonar
import time
import car

#超声波传感器引脚定义
ULTRASONIC_TRIG=25
ULTRASONIC_ECHO=26

#停车距离
DISTANCE_LIMIT=20

#舵机的控制引脚
STEERING_ENGINE=19

#上一次舵机的位置
lastDir=90

#控制舵机旋转到某个角度并测量距离障碍物的距离
def turnCheckDistance(direction):
    global lastDir
    if(lastDir!=direction):
        servo.servo180_angle(STEERING_ENGINE,direction)
        time.sleep_ms(500)
        lastDir=direction
    return sonar.Sonar(ULTRASONIC_TRIG,ULTRASONIC_ECHO).checkdist()

#判断左右哪边距离远
def findDirection():
    d0=turnCheckDistance(0)
    d180=turnCheckDistance(180)
    if(d0<DISTANCE_LIMIT and d180<DISTANCE_LIMIT):
        return 270
    else:
        return (0 if d0>d180 else 180)
    
#避障运行
def carAvoidRun(c):
    dist=turnCheckDistance(90)
    if(dist<=DISTANCE_LIMIT):
        c.stop()
        time.sleep_ms(100)
        dist=turnCheckDistance(90);
        if(dist<15):
            c.back(200)
        d=findDirection()
        c.turn(d)
    c.run()  

#舵机初始化到90度的位置
servo.servo180_angle(STEERING_ENGINE,90)
#得到Car对象的实例
c = car.Car()

#循环避障运行
while True:
    carAvoidRun(c)
    time.sleep_ms(100)