这是最后一期帖子,我将完成剩下的两个功能模块,并将所有模块整合,一并运行,完成全车自动化控制系统。
首先是可视无线倒车雷达,在给货车或一些其他大型车辆添加倒车雷达,走线会比较麻烦,长期还容易出现老化问题,因此我计划将倒车摄像头改为无线wifi摄像头,将画面通过无线传输实时显示在LCD屏幕上。这样可以大大简化安装难度,只需要从尾灯取电即可。wifi可以用车载的AP,也可以让核桃派自己进入AP模式,让无线摄像头进行连接。
先讲讲无线摄像头的部分。由于我们希望得到无线视频流,因此只要是可以联网的ip摄像头在本项目中都可以应用。那么里我们就使用一种成本比较低的方案,用ESP32CAM进行实现。
开发板上烧录的是arduino ide上的官方CameraWebServer例程。首先我们需要定义我们使用的开发板:
#define CAMERA_MODEL_AI_THINKER
其次是要更改wifi连接信息,也就是以下两行:
const char* ssid = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
上传代码后,如果一切正常,我们可以在串口监视器里看到开发板的ip。
下面我们回到核桃派。首先安装需要的库:
pip3 install opencv-python
接着新建python文件,写入以下代码:
import cv2
url = "http://192.168.1.91:81/stream"
cap = cv2.VideoCapture(url)
while (True):
ret, frame = cap.read()
cv2.imshow('frame', frame)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
注意由于这里需要可视化的界面,所以我们不能再像之前那样在ssh中操作。我们需要接上显示器进入桌面,然后在桌面打开终端,在终端里用python
运行上面的程序,运行后,我们就可以看到已经成功从无线摄像头中获取到图像。
接下来是后视镜智能调整,这个功能目前仅在一些顶配车型中有,我打算把这个功能也一并实现,方便对现有中低配车辆进行升级。后视镜的角度控制本质上是对舵机云台的控制。我准备预设三个角度,一是行车时后视镜角度;二是倒车时后视镜角度;三是停车时后视镜角度。三种状态通过检测汽车档位进行切换。
舵机驱动使用的是pwmio库进行,由于可能使用到硬件pwm,因此在选择引脚时需要特别注意,我们得选择支持硬件PWM的引脚。这里我使用的是PWM1,PI11。完整舵机接线方法如下:
PWM: PI11
VCC: 5V
GND: GND
完成连接后,我们通过以下代码,就可以实现用脉冲时间us的方式控制舵机,并设置舵机居中,即us=1500:
import board
import pwmio
servo = pwmio.PWMOut(board.PI11, frequency=50)
def servo_duty_cycle(pulse_us, frequency=50):
period_us = 1.0 / frequency * 1000000.0
duty_cycle = int(pulse_us / (period_us / 65535.0))
return duty_cycle
servo.duty_cycle = servo_duty_cycle(1500)
现在我们需要一个按键接入来模拟档位变化。按照真实的情景,这里应该使用的是一个多段开关,直接接入GPIO,通过GPIO电平变化来进行判断。但GPIO的开关输入实现在之前已经做过。为了避免重复,也是尽可能体验更多的功能,在这我用了一颗电位器来模拟旋钮换挡。
核桃派本身并不包含ADC,因此这里我们需要外接个ADC模块来完成模拟量采集,并转化为数字信号传递给核桃派。我选用的ADC模块是ADS1115,这一一块16bits的ADC,输出十进制的范围是0-65535,精度比较高。
接线方法如下:
SCL: PI7
SDA: PI8
VCC: 3.3V
GND: GND
A0: 电位器中间
电位器两端一端接3.3V,一端接GND。
先安装对应的库:
pip3 install adafruit-circuitpython-ads1x15
随后通过下面代码初始化ADC。我把电位器接在了A0,因此代码中使用P0进行读取:
import busio
import board
import adafruit_ads1x15.ads1115 as ADS
from adafruit_ads1x15.analog_in import AnalogIn
i2c = busio.I2C(board.SCL1, board.SDA1)
ads = ADS.ADS1115(i2c)
pot = AnalogIn(ads, ADS.P0)
while 1:
print(pot.value)
运行以上代码,可以看到终端中不断打印出ADC读数。转动电位器,可以看到读数发生相应变化。
最后,就是把所有的功能整合在一起,完成项目了。
整合方法一般是把每一个单独的功能封装成一个函数,或是一个类,然后导入主函数中,在需要时进行调用。而在这里,由于我们的python代码运行在一个完整的linux系统上,为了尽可能放大操作系统的优势,同时尽可能减小代码直接的耦合,我们使用另一种方式进行功能整合:直接使用python来运行/停止其他程序。
在这里我们需要预先定义运行和停止的方法,我们新建一个control.py文件,把控制代码写在里面:
import os, signal
def kill(filename):
cmd_run="ps aux | grep {}".format(filename)
pipe=os.popen(cmd_run)
for line in pipe.read().splitlines():
# print(line)
if "python" in line:
pid = int(line.split()[1])
a = os.kill(pid,signal.SIGKILL)
# print("已杀死pid为%s的进程, 返回值是:%s" % (pid, a))
pipe.close()
def run(filename, python = "python"):
os.system(python + " ./" + filename + " &") # 在运行命令后面加上&符号,以另一个线程跑
从以上代码中可以看出,运行代码默认是通过python来运行的。如果有特殊需求,比如要使用sudo
,那么只需要在调用时添加python变量即可。
终止的方法是通过文件名查询所有当前进程,获得pid之后再杀进程实现的。
下面写我们的主程序。主程序逻辑比较清晰,根据挡位不同,也就是电位器所在范围的不同,分为三种情况:
行车挡:将后视镜(舵机)调整为行车时角度,并关闭其他模块;
倒车挡:将后视镜(舵机)调整为倒车时角度,开启倒车雷达,开启无线倒车镜,并关闭其他模块;
驻车挡:将后视镜(舵机)调整为驻车时角度,开启防结冰结霜功能,并关闭其他模块。
由于我是使用python文件调用其他python文件,这里有一点就要特别注意了,当我终止主程序时,由于其他运行中的python文件是在操作系统内被调用的,所以并不会被一同终止。因此,我们还需要再主程序中增加终止主程序时需要终止所有可调用模块的功能。实现代码如下:
import control
def handler(signal, frame):
control.kill("distance.py")
control.kill("temp.py")
control.kill("cam.py")
exit()
signal.signal(signal.SIGTSTP, handler) # Ctrl+Z
signal.signal(signal.SIGINT, handler) # Ctrl+C
至此,所有功能都已完成。完整的代码如下:
import time
import board
import pwmio
servo = pwmio.PWMOut(board.PI11, frequency=50)
def servo_duty_cycle(pulse_us, frequency=50):
period_us = 1.0 / frequency * 1000000.0
duty_cycle = int(pulse_us / (period_us / 65535.0))
return duty_cycle
import busio
import adafruit_ads1x15.ads1115 as ADS
from adafruit_ads1x15.analog_in import AnalogIn
i2c = busio.I2C(board.SCL1, board.SDA1)
ads = ADS.ADS1115(i2c)
pot = AnalogIn(ads, ADS.P0)
import control
def handler(signal, frame):
control.kill("distance.py")
control.kill("temp.py")
control.kill("cam.py")
exit()
import signal
signal.signal(signal.SIGTSTP, handler) # Ctrl+Z
signal.signal(signal.SIGINT, handler) # Ctrl+C
state = 0
old_state = 0
while True:
if(pot.value < 1000):
state = 1
elif(pot.value > 2000):
state = 3
elif(1100 < pot.value < 1900):
state = 2
if state is not old_state:
old_state = state
if state == 2:
servo.duty_cycle = servo_duty_cycle(1000)
control.run("cam.py")
control.run("distance.py", python = "sudo python")
control.kill("temp.py")
if state == 3:
servo.duty_cycle = servo_duty_cycle(1500)
control.kill("cam.py")
control.kill("distance.py")
control.kill("temp.py")
if state == 1:
servo.duty_cycle = servo_duty_cycle(2000)
control.run("temp.py", python = "sudo python")
control.kill("cam.py")
control.kill("distance.py")
time.sleep(0.1)
print(pot.value)
完整项目源代码:*附件:walnutpi.rar