Loading... ## 外壳 之前没学习过CAD,所以在这里我使用的建模工具是Tinkercad(一个基于浏览器的3D设计和建模程序),学习成本极低,可快速上手  导出图纸,淘宝定制亚克力板,经过漫长的等待,终于在今天收到了  安装完成后的小车  ## Web控制 以下所有代码均已上传至github [valieo/Raspberry-Car](https://github.com/valieo/Raspberry-Car) ### 安装bottle库 - 安装pip pip是特定于Python的软件包管理器,没有安装pip的按照下述方式安装,安装了跳过此步 安装pip ``` sudo apt install python2-pip -y ``` 或者安装pip3 ``` sudo apt install python3-pip -y ``` > - 如果系统中只安装了Python2,那么就只能使用pip。 > - 如果系统中只安装了Python3,那么既可以使用pip也可以使用pip3,二者是等价的。 > - 如果系统中同时安装了Python2和Python3,则pip默认给Python2用,pip3指定给Python3用。 我在这里安装的是pip3,以下代码也仅在python3下测试通过 - 安装bottle ``` pip3 install bottle ``` ### 代码 #### 1. 超声波测距模块 ```python # 超声波测距模块 HC-SR04 import RPi.GPIO as GPIO import time class Measure(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. 电机驱动模块 ```python # MotorControl.py # TB6612FNG电机驱动 # 电机控制 import RPi.GPIO as GPIO ''' 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. 小车控制 考虑到两个电机的转速可能不完全一致,所以需要一个微调功能。 摄像头模块在[DIY树莓派小车(二)树莓派4B安装并使用摄像头和超声波模块](https://www.valieo.com/archives/51/)中使用motion在8081端口开启了视频输出,可以直接调用,这里不再写有关摄像头模块的代码 ```python # CarControl.py # 控制小车移动 (前进 后退 左转 右转) # 驱动:TB6612FNG import RPi.GPIO as GPIO from MotorControl import Motor from Distance import Measure ''' 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使用率 ```python # RaspberryInfo.py # 获取树莓派 CPU温度 内存使用率 CPU使用率 import os # CPU温度 def getCpuTemp(): res = os.popen('vcgencmd measure_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控制面板 通过按钮或者键盘来控制小车移动 键盘操作方法见下表 | 按键 | W | S | A | D | Q | E | Z | C | | :--: | :--: | :--: | :--: | :--: | :--: | :--: | :----: | :----: | | 功能 | 前进 | 后退 | 左转 | 右转 | 加速 | 减速 | 左微调 | 右微调 | ```python <!-- index.html --> <!-- 树莓派小车控制面板 --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Raspberry Car Control Panel</title> <link href="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("press W"); $.post("/cmd","forward",function(data,status){}); break; case 83://s console.log("press S"); $.post("/cmd","backward",function(data,status){}); break; case 65://a console.log("press A"); $.post("/cmd","turnLeft",function(data,status){}); break; case 68://d console.log("press D"); $.post("/cmd","turnRight",function(data,status){}); break; case 81://q console.log("press Q"); $.post("/cmd","speedUp",function(data,status){}); break; case 69://e console.log("press E"); $.post("/cmd","slowDown",function(data,status){}); break; case 90://z console.log("press Z"); $.post("/cmd","leftFineTuning",function(data,status){}); break; case 67://c console.log("press C"); $.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("key up"); $.post("/cmd","stop",function(data,status){}); break; } }); }); </script> </head> <body> <div class="row"> <div class="col-xs-12 col-sm-6 col-md-7"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">实时画面</h3> </div> <div class="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-6 col-md-4"> <div class="panel panel-default"> <div class="panel panel-default"> <div class="panel-heading"> <h3 class="panel-title">信息</h3> </div> <div class="panel-body" > <iframe id="car-info" width="320" height="195" src="http://192.168.0.100:8088/info"></iframe> </div> </div> <div class="panel-heading"> <h3 class="panel-title">控制</h3> </div> <div class="panel-body" style="margin: 30px;margin-left: 40px"> <div class="row" > <div class="col-md-2"></div> <div class="col-md-2"><button id="forward" class="btn btn-large btn-primary" type="button"><span class="glyphicon glyphicon-triangle-top" aria-hidden="true"></span><br/>前进</button></div> <div class="col-md-2"></div> </div> <br/> <div class="row"> <div class="col-md-2"><button id="turnLeft" class="btn btn-large btn-primary" type="button"><span class="glyphicon glyphicon-triangle-left" aria-hidden="true"></span><br/>左转</button></div> <div class="col-md-2"><button id="backward" class="btn btn-large btn-primary" type="button"><span class="glyphicon glyphicon-triangle-bottom" aria-hidden="true"></span><br/>后退</button></div> <div class="col-md-2"><button id="turnRight" class="btn btn-large btn-primary" type="button"><span class="glyphicon glyphicon-triangle-right" aria-hidden="true"></span><br/>右转</button></div> </div> <br/> <div class="row"> <div class="col-md-2"><button id="speedUp" class="btn btn-large btn-primary" type="button"><span class="glyphicon glyphicon glyphicon-plus-sign" aria-hidden="true"></span><br/>加速</button></div> <div class="col-md-2"></div> <div class="col-md-2"><button id="slowDown" class="btn btn-large btn-primary" type="button"><span class="glyphicon glyphicon glyphicon-minus-sign" aria-hidden="true"></span><br/>减速</button></div> </div> <br> <div class="row"> <div class="col-md-2"><button id="leftFineTuning" class="btn btn-large btn-primary" type="button"><span class="glyphicon glyphicon-triangle-left" aria-hidden="true"></span><br/>左微调</button></div> <div class="col-md-2"></div> <div class="col-md-2"><button id="rightFineTuning" class="btn btn-large btn-primary" type="button"><span class="glyphicon glyphicon-triangle-right" aria-hidden="true"></span><br/>右微调</button></div> </div> </div> </div> </div> </body> </html> ``` #### 6. 树莓派小车信息页面 此页面嵌入到小车Web控制面板页面中,显示小车速度(PWM占空比)、距离、CPU温度、CPU使用率、内存使用率 此页面1秒刷新一次 ```python <!-- info.html --> <!-- 树莓派小车信息面板 --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <!--每1秒刷新一次--> <meta http-equiv="refresh" content="1"> <title>信息</title> </head> <body> <p>Speed: <ruby>speed}}</p> <p>Distance<rp> (</rp><rt> {{distance</rt><rp>) </rp></ruby>cm</p> <p>CPU Temp: <ruby>cpuTemp}}℃</p> <p>CPU Uasge<rp> (</rp><rt> {{cpuUsage</rt><rp>) </rp></ruby>%</p> <p>RAM Uasge: {{ramUsage}}%</p> </body> </html> ``` #### 7. 启动 ```python # Start.py from bottle import get,post,run,request,template from CarControl import Car from RaspberryInfo import * 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 status Information") 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 ``` Web页面如下,可以通过网页上的按钮或者键盘控制小车(局域网内网络延迟很低,画面几乎实时显示) 控制面板右侧显示的是树莓派小车信息,其中Speed是PWM占空比,范围是[0,100],Distance是超声波测距的结果 当小车不能走直线时,如果向右偏,则需要使用左微调,反之使用右微调  遥控视频演示画面(视频中使用充电宝给小车供电) <video src="https://www.valieo.com/usr/uploads/2021/08/4157306050.mp4" style="background-image:url();background-size: cover;"></video> 至此树莓派小车制作完毕 © 允许规范转载 赞 1 如果觉得我的文章对你有用,请随意赞赏
6 条评论
为啥相关信息还是无法显示呢,路径没错呀
我是看b站up主奇乐编程的视频,照着他的做发现电机无法控制……跟着您做了一遍,小车可以跑了。感谢!!!有个小问题,您充电宝输出是多少?5V3A吗?
我用的是小米2C充电宝,输出是5.1V2.4A
路径问题找到了。还有相关信息(测距,速度,温度)什么的无法显示咋办呀??
是不是info.html页面的路径错了呢?你可以更改index.html页面中的iframe标签来更改路径