本帖最后由 Andsen 于 2023-8-12 13:54 编辑
众所周知,树莓派可玩性比较高。服务器方向:个人博客、临时中转站、爬虫脚本、提供Web服务;多媒体方向:家庭音影中心、家庭监控;智能家居方向:智能家庭控制中心。其实上面说了这么多树莓派的用途,但是其实很多应用的场景都有相应的产品了。 那为什么还要玩树莓派? 因为,就是玩,就是要折腾。 何不发挥想象力,DIY做些东西呢? 接下来,我将带大家一起来做一辆小车。 需求采用双轮差速方式移动 双轮方式需要的零件相对较少 通过网络远程控制小车 可通过WiFi远程控制,或者配置内网穿透实现任意地点控制。 能够实时传送图像 安装摄像头模块 超声波测距 安装超声波测距模块 小车的结构大致仿照微雪电子的Alphabot2 材料树莓派4B N20减速电机 x 2 43MM橡胶轮胎 x 2 小车万向轮 x 2 TB6612FNG驱动模块 摄像头模块 超声波测距模块 导线、面包板等 TB6612FNG驱动接口VM:电机驱动电源输入(2.74-10.8V) VCC:逻辑电平输入(5.0V) AO1和AO2:接直流电机A BO2和BO1:接直流电机B PWMA:控制电机A的转速 AIN1和AIN2:控制电机A(停止、正转、反转) STBY:正常工作/待机状态控制端 PWMB:控制电机B的转速 BIN1和BIN2:控制电机B(停止、正转、反转) GND:接地 AIN1和AIN2用于控制电机A停止、正转、反转,真值表如下 接线树莓派40PIN引脚对照表 树莓派GPIO和TB6612FNG的接线情况如下表 电机驱动和树莓派4B共用一个5V 3A的电源 实际接线图 测试代码小车需要有前进、后退、左转、右转、停止等5个动作,使用python编写测试代码如下 测试结果将上述代码保存到motorTest.py中,运行测试脚本python3 motorTest.py #motorTest.py #导入 GPIO库 import RPi.GPIO asGPIO import time #设置 GPIO 模式为 BCM GPIO.setmode(GPIO.BCM) #定义引脚 STBY = 27 PWMA = 18 AIN1 = 14 AIN2 = 15 PWMB = 19 BIN1 = 23 BIN2 = 24 #设置 GPIO 的工作方式 GPIO.setup(STBY,GPIO.OUT) GPIO.setup(PWMA,GPIO.OUT) GPIO.setup(AIN1,GPIO.OUT) GPIO.setup(AIN2,GPIO.OUT) GPIO.setup(PWMB,GPIO.OUT) GPIO.setup(BIN1,GPIO.OUT) GPIO.setup(BIN2,GPIO.OUT) pwma =GPIO.PWM(PWMA,300) pwmb =GPIO.PWM(PWMB,300) # 前进或后退(大于零前进,小于零后退) defgoForward(speed): if(speed>=0): GPIO.output(AIN1,GPIO.LOW) GPIO.output(AIN2,GPIO.HIGH) GPIO.output(BIN1,GPIO.LOW) GPIO.output(BIN2,GPIO.HIGH) pwma.start(speed) pwmb.start(speed) time.sleep(0.02) else: GPIO.output(AIN2,GPIO.LOW) GPIO.output(AIN1,GPIO.HIGH) GPIO.output(BIN2,GPIO.LOW) GPIO.output(BIN1,GPIO.HIGH) pwma.start(-speed) pwmb.start(-speed) time.sleep(0.02) # 左转或右转(大于零左转,小于零右转) def turnLeft(speed): if(speed>=0): GPIO.output(AIN2,GPIO.LOW) GPIO.output(AIN1,GPIO.HIGH) GPIO.output(BIN1,GPIO.LOW) GPIO.output(BIN2,GPIO.HIGH) pwma.start(speed) pwmb.start(speed) time.sleep(0.02) else: GPIO.output(AIN1,GPIO.LOW) GPIO.output(AIN2,GPIO.HIGH) GPIO.output(BIN2,GPIO.LOW) GPIO.output(BIN1,GPIO.HIGH) pwma.start(-speed) pwmb.start(-speed) time.sleep(0.02) def motorStop(): GPIO.output(AIN1,GPIO.LOW) GPIO.output(AIN2,GPIO.LOW) GPIO.output(STBY,GPIO.HIGH) #以60%的速度前进 goForward(60) time.sleep(2) #以60%的速度后退 goForward(-60) time.sleep(2) #左转 turnLeft(60) time.sleep(2) #右转 turnLeft(-60) time.sleep(2) #停止 motorStop() pwma.stop() pwmb.stop() GPIO.cleanup() 摄像头模块安装摄像头我使用的是CSI视频接口的摄像头,500万像素,实图如下 摄像头的排线如下,需要将有金属条纹的一面(左图)朝向树莓派的HDMI接口,具有蓝色胶带的一面(右图)朝向USB接口 具体连接方式可参考下图 使树莓派支持摄像头sudo raspi-config 依次选择:InterfaceOptions---Camera---Yes---Finish---Yes 测试拍照:以下命令将使树莓派拍摄一张照片,命名为image.jpg,保存到当前目录下 raspistill -oimage.jpg 录像:以下命令使树莓派拍摄一段5000毫秒的视频,命名为video.h264,保存到当前目录下 raspivid -ovideo.h264 -t 5000 raspivid 的输出是一段未压缩且不含声音的 H.264 视频流,可以使用gpac将其转为常用的mp4格式,以便播放 安装gpac sudo apt installgpac -y 将上述video.h264视频转换为video.mp4,帧率为24 MP4Box -fps 24 -addvideo.h264 video.mp4 通过网页查看视频输出使用motion可以实现简单的远程视频监控 安装motion sudo apt installmotion -y 编辑/etc/default/motion文件,开启守护进程 sudo nano/etc/default/motion 取消注释:start_motion_daemon=yes 编辑/etc/motion/motion.conf文件 sudo nano/etc/motion/motion.conf #将deamon off 改成deamon on deamon on #设置视频分辨率 width 800 height 600 #视频帧率 framerate 24 stream_maxrate 30 #允许非本机访问总控制页面 webcontrol_localhostoff #允许非本机查看视频监控 stream_localhost off 启动motion sudo systemctl startmotion sudo motion 打开浏览器,输入如下url查看视频输出 http://树莓派IP:8080/ 或 http://树莓派IP:8081/ 结束motion进程 sudo killall -TERMmotion HC-SR04超声波模块HC-SR04实图如下,其有四个引脚,分别为Vcc、Trig、Echo、End HC-SR04模块具体参数如下图 HC-SR04模块的工作原理(1)树莓派向 Trig 脚发送一个10us 的脉冲信号。 (2) HC-SR04 接收到信号,开始发送超声波,并把 Echo置为高电平,然后准备接收返回的超声波。 (3) HC-SR04 接收到返回的超声波,把 Echo 置为低电平。 (4)Echo 高电平持续的时间就是超声波从发射到返回的时间间隔。 (5)计算距离: 距离(单位:m) = (startTime - endTime) * 声波速度 / 2 声波速度取 343m/s 。 接线HC-SR04只有4个引脚 代码#导入 GPIO库 import RPi.GPIO asGPIO import time #设置 GPIO 模式为 BCM GPIO.setmode(GPIO.BCM) #定义 GPIO 引脚使用BCM编码 TRIG = 5 ECHO = 6 #设置 GPIO 的工作方式(IN / OUT) GPIO.setup(TRIG,GPIO.OUT) GPIO.setup(ECHO,GPIO.IN) # 获取距离信息 def getDistance(): # 向Trig引脚发送10us的脉冲信号 GPIO.output(TRIG, GPIO.HIGH) time.sleep(0.00001) GPIO.output(TRIG, GPIO.LOW) # 开始发送超声波的时刻 while GPIO.input(ECHO)==0: pass startTime=time.time() # 收到返回超声波的时刻 while GPIO.input(ECHO)==1: pass endTime=time.time() # 计算距离 距离=(声波的往返时间*声速)/2 timeDelta = endTime - startTime distance = (timeDelta * 34300) / 2 return distance if __name__ =='__main__': try: while True: dist = getDistance() print("Distance = {:.2f}cm".format(dist)) time.sleep(1) # 每间隔1秒测量一次 except KeyboardInterrupt: print("Stopped") GPIO.cleanup() 运行结果 外壳安装完成后的小车 Web控制安装bottle库安装pip3 sudo apt installpython3-pip -y 安装bottle pip3 install bottle 代码1. 超声波测距模块# 超声波测距模块 HC-SR04 import RPi.GPIO asGPIO import time classMeasure(object): def __init__(self, GPIO_TRIG, GPIO_ECHO)-> None: super().__init__() self.GPIO_TRIG = GPIO_TRIG self.GPIO_ECHO = GPIO_ECHO GPIO.setmode(GPIO.BCM) #设置 GPIO 的工作方式 (IN / OUT) GPIO.setup(GPIO_TRIG, GPIO.OUT) GPIO.setup(GPIO_ECHO, GPIO.IN) def getDistance(self): # 向Trig引脚发送10us的脉冲信号 GPIO.output(self.GPIO_TRIG, True) time.sleep(0.00001) GPIO.output(self.GPIO_TRIG, False) # 开始发送超声波的时刻 while GPIO.input(self.GPIO_ECHO)==0: pass startTime=time.time() # 收到返回超声波的时刻 while GPIO.input(self.GPIO_ECHO)==1: pass endTime=time.time() # 计算距离距离=(声波的往返时间*声速)/2 timeDelta = endTime - startTime distance = (timeDelta * 34300) / 2 return round(distance,2) 2. 电机驱动模块# MotorControl.py # TB6612FNG电机驱动 # 电机控制 import RPi.GPIO asGPIO ''' self.GPIO_PWM self.GPIO_IN1 self.GPIO_IN2 self.freq self.last_pwm self.pwm ''' class Motor(object): def __init__(self, GPIO_PWM, GPIO_IN1,GPIO_IN2, freq=300) -> None: super().__init__() self.GPIO_PWM = GPIO_PWM self.GPIO_IN1 = GPIO_IN1 self.GPIO_IN2 = GPIO_IN2 self.freq = freq GPIO.setmode(GPIO.BCM) #GPIO.setwarnings(False) GPIO.setup(self.GPIO_PWM, GPIO.OUT) GPIO.setup(self.GPIO_IN1, GPIO.OUT) GPIO.setup(self.GPIO_IN2, GPIO.OUT) self.pwm = GPIO.PWM(self.GPIO_PWM,self.freq) self.last_pwm = 0 self.pwm.start(self.last_pwm) ''' 静态方法 TB6612FNG的STBY引脚 1. GPIO_STBY int BCM编码号 2. status bool 为true则TB6612FNG工作,反之待机默认为false ''' @staticmethod def standby(GPIO_STBY,status=False): GPIO.setmode(GPIO.BCM) #GPIO.setwarnings(False) if status: GPIO.setup(status, GPIO.OUT) GPIO.output(status, True) else: GPIO.output(status, False) ''' 设置PWM占空比 1. dc 占空比 [0,100] ''' def __setPWM(self, dc): if dc != self.last_pwm: self.pwm.ChangeDutyCycle(dc) self.last_pwm = dc ''' 启动 1. speed int 范围[-100,100] 正数则正转,负数则反转 ''' def run(self, speed): if(speed>=0): GPIO.output(self.GPIO_IN1, False) GPIO.output(self.GPIO_IN2, True) self.__setPWM(speed) else: GPIO.output(self.GPIO_IN1, True) GPIO.output(self.GPIO_IN2, False) self.__setPWM(-speed) '''停止''' def stop(self): GPIO.output(self.GPIO_IN1, False) GPIO.output(self.GPIO_IN2, False) self.__setPWM(0) def cleanup(self): self.stop() self.pwm.stop() 3. 小车控制# CarControl.py # 控制小车移动 (前进 后退 左转 右转) # 驱动:TB6612FNG import RPi.GPIO asGPIO from MotorControlimport Motor from Distance importMeasure ''' TB6612FNG 接口 # STBY GPIO_STBY = 27 # 左边电机 GPIO_PWMA = 18 GPIO_AIN1 = 14 GPIO_AIN2 = 15 # 右边电机 GPIO_PWMB = 19 GPIO_BIN1 = 23 GPIO_BIN2 = 24 HC-SR04 接口 GPIO_TRIG = 5 GPIO_ECHO = 6 小车功能 1. 微调 2. 变速 3. 前进 4. 后退 5. 左转 6. 右转 7. 停车 8. 测距 ''' class Car(object): def __init__(self) -> None: super().__init__() '''电机模块''' # STBY引脚定义 self.GPIO_STBY = 27 GPIO.setmode(GPIO.BCM) #GPIO.setwarnings(False) GPIO.setup(self.GPIO_STBY, GPIO.OUT) # 左右两个电机 self.motor_left = Motor(18,14,15) self.motor_right = Motor(19,23,24) # STBY = True TB6612FNG开始工作 Motor.standby(self.GPIO_STBY,True) # 速度 占空比 self.motor_speed = 60 self.motor_left_speed = 60 self.motor_right_speed = 60 # 速度系数用于微调 self.motor_left_coefficient = 1.0 self.motor_right_coefficient = 1.0 '''超声波测距模块''' self.measure = Measure(5, 6) # 微调 使两个电机转速一致 left=True向左微调 反之向右 def fineTuning(self, left): if left: # 向左微调 左边的电机减速 右边的电机加速 self.motor_left_coefficient -= 0.05 self.motor_right_coefficient +=0.05 else: # 向右边微调 左边的电机加速 右边的电机减速 self.motor_left_coefficient += 0.05 self.motor_right_coefficient -=0.05 # 更改速度 [0,100] def setSpeed(self, speed): if speed>100: speed = 100 if speed<0: speed = 0 self.motor_speed = speed self.motor_left_speed =self.motor_left_coefficient * speed self.motor_right_speed =self.motor_right_coefficient * speed # 前进 def forward(self): self.motor_left.run(self.motor_left_speed) self.motor_right.run(self.motor_right_speed) # 后退 def backward(self): self.motor_left.run(-self.motor_left_speed) self.motor_right.run(-self.motor_right_speed) # 左转 def turnLeft(self): self.motor_left.run(-self.motor_left_speed) self.motor_right.run(self.motor_right_speed) # 右转 def turnRight(self): self.motor_left.run(self.motor_left_speed) self.motor_right.run(-self.motor_right_speed) # 停止 def stop(self): self.motor_left.stop() self.motor_right.stop() # 测距 def getDistance(self): return self.measure.getDistance() def getSpeed(self): return self.motor_speed # 释放资源 def cleanup(self): self.motor_left.cleanup() self.motor_right.cleanup() GPIO.cleanup() 4. 获取树莓派信息以下代码用于获取树莓派CPU温度、CPU使用率、RAM使用率 # RaspberryInfo.py # 获取树莓派 CPU温度 内存使用率 CPU使用率 import os # CPU温度 def getCpuTemp(): res = os.popen('vcgencmdmeasure_temp').readline() return(res.replace("temp=","").replace("'C\n","")) # CPU使用率 def getCpuUsage(): return(str(os.popen("top -n1 | awk'/Cpu\(s\):/ {print $2}'").readline().strip())) # RAM信息 def getRAMinfo(): p = os.popen('free') i = 0 while 1: i = i + 1 line = p.readline() if i==2: return(line.split()[1:4]) # 内存使用率 def getRamUsage(): RAM_stats = getRAMinfo() return round(int(RAM_stats[1]) /int(RAM_stats[0])*100,1) 5. 小车Web控制面板通过按钮或者键盘来控制小车移动 <!-- index.html--> <!-- 树莓派小车控制面板 --> <!DOCTYPEhtml> <htmllang="en"> <head> <meta charset="UTF-8"> <meta name="viewport"content="width=device-width, initial-scale=1.0"> <title>Raspberry Car ControlPanel</title> <linkhref="http://cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css"rel="stylesheet" media="screen"> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script> <style type="text/css"> #front { margin-left: 55px; margin-bottom: 3px; } #rear{ margin-top: 3px; margin-left: 55px; } .btn{ background: #62559f; } </style> <script> $(function(){ $("button").mousedown(function(){ console.log(this.id +"mouse down"); $.post("/cmd",this.id,function(data,status){}); }); // 鼠标弹起则停止移动 $("button").mouseup(function(){ console.log(this.id +"mouse up"); $.post("/cmd","stop",function(data,status){}); }) // 键盘控制小车移动 $(document).keydown(function(event){ switch(event.keyCode){ case 87://w console.log("pressW"); $.post("/cmd","forward",function(data,status){}); break; case 83://s console.log("pressS"); $.post("/cmd","backward",function(data,status){}); break; case 65://a console.log("pressA"); $.post("/cmd","turnLeft",function(data,status){}); break; case 68://d console.log("pressD"); $.post("/cmd","turnRight",function(data,status){}); break; case 81://q console.log("pressQ"); $.post("/cmd","speedUp",function(data,status){}); break; case 69://e console.log("pressE"); $.post("/cmd","slowDown",function(data,status){}); break; case 90://z console.log("pressZ"); $.post("/cmd","leftFineTuning",function(data,status){}); break; case 67://c console.log("pressC"); $.post("/cmd","rightFineTuning",function(data,status){}); break; } }); $(document).keyup(function(event){ switch(event.keyCode){ case 87://w case 83://s case 65://a case 68://d case 81://q console.log("keyup"); $.post("/cmd","stop",function(data,status){}); break; } }); }); </script> </head> <body> <divclass="row"> <div class="col-xs-12 col-sm-6col-md-7"> <div class="panelpanel-default"> <divclass="panel-heading"> <h3class="panel-title">实时画面</h3> </div> <divclass="panel-body"> <iframe src="http://192.168.0.100:8081/"width="820" height="620" frameborder="1" name="name" scrolling="auto"></iframe> </div> </div> </div> <div class="col-xs-6col-md-4"> <div class="panelpanel-default"> <div class="panelpanel-default"> <divclass="panel-heading"> <h3class="panel-title">信息</h3> </div> <divclass="panel-body" > <iframeid="car-info" width="320" height="195" src="http://192.168.0.100:8088/info"></iframe> </div> </div> <divclass="panel-heading"> <h3class="panel-title">控制</h3> </div> <divclass="panel-body" style="margin: 30px;margin-left:40px"> <div class="row"> <divclass="col-md-2"></div> <divclass="col-md-2"><button id="forward" class="btnbtn-large btn-primary" type="button"><spanclass="glyphicon glyphicon-triangle-top"aria-hidden="true"></span><br/>前进</button></div> <divclass="col-md-2"></div> </div> <br/> <divclass="row"> <div class="col-md-2"><buttonid="turnLeft" class="btn btn-large btn-primary"type="button"><span class="glyphiconglyphicon-triangle-left"aria-hidden="true"></span><br/>左转</button></div> <divclass="col-md-2"><button id="backward"class="btn btn-large btn-primary" type="button"><spanclass="glyphicon glyphicon-triangle-bottom"aria-hidden="true"></span><br/>后退</button></div> <divclass="col-md-2"><button id="turnRight"class="btn btn-large btn-primary" type="button"><spanclass="glyphicon glyphicon-triangle-right"aria-hidden="true"></span><br/>右转</button></div> </div> <br/> <divclass="row"> <divclass="col-md-2"><button id="speedUp" class="btnbtn-large btn-primary" type="button"><spanclass="glyphicon glyphicon glyphicon-plus-sign"aria-hidden="true"></span><br/>加速</button></div> <divclass="col-md-2"></div> <divclass="col-md-2"><button id="slowDown" class="btnbtn-large btn-primary" type="button"><spanclass="glyphicon glyphicon glyphicon-minus-sign"aria-hidden="true"></span><br/>减速</button></div> </div> <br> <divclass="row"> <div class="col-md-2"><buttonid="leftFineTuning" class="btn btn-large btn-primary"type="button"><span class="glyphiconglyphicon-triangle-left"aria-hidden="true"></span><br/>左微调</button></div> <divclass="col-md-2"></div> <divclass="col-md-2"><button id="rightFineTuning"class="btn btn-large btn-primary" type="button"><spanclass="glyphicon glyphicon-triangle-right"aria-hidden="true"></span><br/>右微调</button></div> </div> </div> </div> </div> </body> </html> 6. 树莓派小车信息页面此页面嵌入到小车Web控制面板页面中,显示小车速度(PWM占空比)、距离、CPU温度、CPU使用率、内存使用率 此页面1秒刷新一次 <!-- info.html--> <!-- 树莓派小车信息面板 --> <!DOCTYPEhtml> <htmllang="en"> <head> <meta charset="UTF-8"> <!--每1秒刷新一次--> <meta http-equiv="refresh"content="1"> <title>信息</title> </head> <body> <p>Speed: {{speed}}</p> <p>Distance: {{distance}}cm</p> <p>CPUTemp: {{cpuTemp}}℃</p> <p>CPUUasge: {{cpuUsage}}%</p> <p>RAMUasge: {{ramUsage}}%</p> </body> </html> 7. 启动# Start.py from bottle importget,post,run,request,template from CarControlimport Car from RaspberryInfoimport * car = Car() def main(status): print("Event: "+status) if status == "forward": car.forward() elif status == "backward": car.backward() elif status == "turnLeft": car.turnLeft() elif status == "turnRight": car.turnRight() elif status == "speedUp": car.setSpeed(car.getSpeed() + 5) elif status == "slowDown": car.setSpeed(car.getSpeed() - 5) elif status == "leftFineTuning": car.fineTuning(True) elif status == "rightFineTuning": car.fineTuning(False) elif status == "stop": car.stop() # 控制台 @get("/") def index(): print("request index.html") return template("index.html") # 控制小车 @post("/cmd") def cmd(): adss=request.body.read().decode() main(adss) return "OK" # 小车信息 @get("/info") def info(): print("Update statusInformation") return template("info.html",speed=car.getSpeed(), distance=car.getDistance(), cpuTemp=getCpuTemp(),cpuUsage=getCpuUsage(), ramUsage=getRamUsage()) run(host='0.0.0.0',port=8088, debug=False) car.cleanup() 实测启动 sudo motion # 打开摄像头 python3 Start.py 在浏览器中输入Url进入树莓派小车控制页面 http://树莓派ip:8088 视频演示
|