娱乐小项目-树莓派履带小车
快速使用
1.小车上电,开关在电源插口旁边
2.上电之后用电脑查看局域网WIFI,密码是12345678,固定IP是192.168.50.1
3.安装VNC软件:20240324_树莓派履带车\工具
4.打开VNC软件
5.在这个界面下
按ctrl+alt+t,弹出终端
6.输入python pi.py
7.再按按ctrl+alt+t,弹出终端,输入python camer1.py(如果没有弹出摄像头界面,就试试python camer.py;python camer2.py;python camer3.py)
8.打开软件20240324_树莓派履带车\dist\mycar
9.正常操作软件
1.设备连接
小车上电,开关在电源接头的旁边,上电之后等待1分钟,发现前一分钟有不稳定的情况
小车自己有一个WIFI,密码是12345678,固定IP是192.168.50.1
连接这个网络
在电脑上打开命令行,输入命令ping 192.168.50.1,确认网络联通性,
通过命令行SSH登录设备
这个方式是电脑自带的,不需要安装什么软件,相对来说比较简单
如果在这方面有什么需求,可以自行用vscode或者xshell这类软件登录
还有一个是VNC登录设备,用于摄像头控制,安装包和配套资料一起给过来了
需要换源,不然国内网络可能会导致更新有问题(目前板子里面东西太多了,没法完成更多软件的安装)
修改/etc/apt/source.list的内容
# 官方软件源
deb http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ buster main contrib non-free rpi
deb-src http://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ buster main contrib non-free rpi
# 更新固件的软件源
deb http://mirrors.tuna.tsinghua.edu.cn/raspberrypi/ buster main ui
执行apt-get update
2.官方例程的使用
控制小车的运动
把下面的命令,一条条敲到终端运行(可以复制,不必一个字母一个字母的敲)
cd /home/pi/SmartCar
./CarRun
执行完车子会在原地运动,执行ctrl+c,结束程序
控制灯光
cd /home/pi/SmartCar ./ColorLED 执行ctrl+c,结束程序
配套的硬件程序都在/home/pi/SmartCar目录下
3.控制端程序
目前硬件几个关键的传感器或者部分有损坏,所以做了一个桌面的应用程序,不然车里面没什么好说的了
下面简单说一下程序的功能,需要自己安装python的开发环境,这个参考网上教程,就不多说了
soket连接部分
引用相关的库
import tkinter as tk
import time
import socket
import threading
实现socket连接,写收发的相关代码
HOST = '' # 主机IP地址
PORT = 1234 # 端口号
MESSAGE_SIZE = 1024 # 消息大小
bind_ip = "192.168.50.73"
# 创建UDP套接字
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.bind((bind_ip, 1234))
def send_message(message):
sock.sendto(message.encode(), ('192.168.50.1', 1234))
def receive_message():
while True:
data, address = sock.recvfrom(MESSAGE_SIZE)
print(f"Received message: {data.decode()} from {address}")
# 在此处处理接收到的消息
# 创建接收消息的线程
receive_thread = threading.Thread(target=receive_message)
receive_thread.daemon = True
receive_thread.start()
这段代码是一个简单的UDP网络通信的示例。它使用Python的socket模块来创建一个UDP套接字并进行数据的发送和接收。
代码中的变量解释如下:
- HOST: 主机的IP地址(为空字符串表示绑定到所有可用的网络接口)
- PORT: 端口号
- MESSAGE_SIZE: 消息的最大大小
- bind_ip: 用于绑定套接字的IP地址
接下来的代码执行以下操作:
- 创建一个UDP套接字对象,使用socket.socket(socket.AF_INET, socket.SOCK_DGRAM)语句,指定地址族为IPv4,套接字类型为数据报套接字。
- 设置套接字选项,使用sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)和sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)语句,分别启用套接字地址重用和广播功能。
- 使用sock.bind((bind_ip, 1234))将套接字绑定到指定的IP地址和端口号。
接下来定义了两个函数:
- send_message(message)函数用于发送消息。它使用sock.sendto(message.encode(), ('192.168.50.1', 1234))语句将消息编码为字节流并发送给目标地址。
- receive_message()函数用于接收消息。它通过循环不断接收数据,使用sock.recvfrom(MESSAGE_SIZE)语句接收数据和发送方的地址。然后打印接收到的消息和发送方的地址。
最后,代码创建了一个线程receive_thread,并将其设置为守护线程(daemon),然后启动线程。这个线程用于在后台不断接收消息,并在接收到消息时处理消息。
总的来说,这段代码创建了一个UDP套接字,绑定到指定的IP地址和端口号,然后通过一个线程来接收消息,并在接收到消息时进行处理。另外还提供了一个函数用于发送消息。
键盘消息响应
# 创建运动按键的回调函数
def move_forward_mouse():
log_text.insert(tk.END, "前进\n")
log_text.see(tk.END)
send_message("move_forward")
# 在此处编写前进的代码
def stop_forward_mouse():
log_text.insert(tk.END, "停止前进\n")
log_text.see(tk.END)
send_message("stop_forward")
# 在此处编写停止前进的代码
def move_backward_mouse():
log_text.insert(tk.END, "后退\n")
log_text.see(tk.END)
send_message("move_backward")
# 在此处编写后退的代码
def stop_backward_mouse():
log_text.insert(tk.END, "停止后退\n")
log_text.see(tk.END)
send_message("stop_backward")
# 在此处编写停止后退的代码
def turn_left_mouse():
log_text.insert(tk.END, "左转\n")
log_text.see(tk.END)
send_message("turn_left")
# 在此处编写左转的代码
def stop_left_mouse():
log_text.insert(tk.END, "停止左转\n")
log_text.see(tk.END)
send_message("stop_left")
# 在此处编写停止左转的代码
def turn_right_mouse():
log_text.insert(tk.END, "右转\n")
log_text.see(tk.END)
send_message("turn_right")
# 在此处编写右转的代码
def stop_right_mouse():
log_text.insert(tk.END, "停止右转\n")
log_text.see(tk.END)
send_message("stop_right")
# 在此处编写停止右转的代码
root.bind("<KeyPress-Up>", move_forward)
root.bind("<KeyRelease-Up>", stop_forward)
root.bind("<KeyPress-Down>", move_backward)
root.bind("<KeyRelease-Down>", stop_backward)
root.bind("<KeyPress-Left>", turn_left)
root.bind("<KeyRelease-Left>", stop_left)
root.bind("<KeyPress-Right>", turn_right)
root.bind("<KeyRelease-Right>", stop_right)
这段代码定义了一系列函数,用作按键事件的回调函数。它们用于处理运动控制按键的按下和释放事件,并在每个事件中执行相应的操作。
每个回调函数的功能如下:
- move_forward_mouse()
: 当按下前进按键时,向文本框(log_text)插入"前进\n"的文本,并将其滚动到末尾可见位置。然后,通过调用send_message("move_forward")发送消息"move_forward"。在函数内部的注释处,你可以编写关于前进操作的代码。
- stop_forward_mouse()
: 当释放前进按键时,向文本框(log_text)插入"停止前进\n"的文本,并将其滚动到末尾可见位置。然后,通过调用send_message("stop_forward")发送消息"stop_forward"。在函数内部的注释处,你可以编写关于停止前进操作的代码。
最后的代码段将这些回调函数与特定的按键事件绑定。它使用root.bind(, )的形式,将按键事件和相应的回调函数进行绑定。具体来说:
- 和事件与move_forward_mouse()和stop_forward_mouse()回调函数绑定,用于前进操作。
- 和事件与move_backward_mouse()和stop_backward_mouse()回调函数绑定,用于后退操作。
- 和事件与turn_left_mouse()和stop_left_mouse()回调函数绑定,用于左转操作。
- 和事件与turn_right_mouse()和stop_right_mouse()回调函数绑定,用于右转操作。
这些绑定意味着当用户按下或释放相应的按键时,与之对应的回调函数将被调用。例如,当用户按下前进按键时,move_forward_mouse()函数将被调用,文本框将显示"前进\n",并向指定的地址发送"move_forward"的消息。
这段代码定义了一组按键事件的回调函数,用于处理运动控制按键的按下和释放事件。每个回调函数负责更新日志文本框的内容、发送相应的消息,并在函数内部的注释处编写实际的运动控制代码。最后,通过绑定按键事件和回调函数,将它们与特定的按键操作关联起来。
4.设备端程序
soket连接部分
引用相关的库
# -*- coding: utf-8 -*-
import signal
import sys
import socket
import time
- signal模块用于处理信号,例如用于进程间通信或中断处理。
- sys模块提供了与Python解释器和运行时环境交互的函数。
- socket模块提供了网络编程相关的功能。
- time模块提供了时间相关的函数。
- 树莓派里面,使用中文的注释,需要设置代码的编码,# -*- coding: utf-8 -*-就是用于编码格式的设置
基本程序部分
# 定义信号处理函数
def signal_handler(sig, frame):
print("Received KeyboardInterrupt, exiting...")
sys.exit(0)
# 注册信号处理函数
signal.signal(signal.SIGINT, signal_handler)
# 创建 socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 设置超时时间
sock.settimeout(0.1) # 设置超时时间为10秒
# 绑定到本地端口
port = 1234
address = ('', port)
sock.bind(address)
try:
motor_init()
while True:
try:
# 接收数据
data, address = sock.recvfrom(1024)
message = data.decode()
print(message)
if message.strip() == "open beep":
GPIO.output(buzzer, GPIO.LOW)
elif message.strip() == "close beep":
GPIO.output(buzzer, GPIO.HIGH)
elif message.strip() == "red":
GPIO.output(red, GPIO.HIGH)
elif message.strip() == "green":
GPIO.output(green, GPIO.HIGH)
elif message.strip() == "blue":
GPIO.output(blue, GPIO.HIGH)
elif message.strip() == "open video":
pass
elif message.strip() == "close video":
pass
elif message.strip() == "photo":
pass
elif message.strip() == "move_forward":
run(0.5)
elif message.strip() == "stop_forward":
brake(0.5)
elif message.strip() == "move_backward":
back(0.5)
elif message.strip() == "stop_backward":
brake(0.5)
elif message.strip() == "turn_left":
left(0.5)
elif message.strip() == "stop_left":
brake(0.5)
elif message.strip() == "turn_right":
right(0.5)
elif message.strip() == "stop_right":
brake(0.5)
else:
response = "命令识别失败"
sock.sendto(message.encode(), address)
# print(f"Received message: {data.decode()} from {address}")
except socket.timeout:
pass
except Exception as e:
print("Error occurred:", e)
finally:
sock.close()
sys.exit()
实现了一个基于UDP协议的服务器端程序。它包含了信号处理函数、网络通信、GPIO控制等功能。
首先,定义了一个信号处理函数signal_handler,用于处理SIGINT信号(即键盘中断信号,通常由Ctrl+C触发)。在信号处理函数中,打印一条退出消息并调用sys.exit(0)退出程序。
然后,使用socket模块创建了一个UDP socket对象sock,并设置了超时时间为0.1秒。
接下来,通过sock.bind(address)将socket绑定到本地端口1234。
在一个主循环中,不断接收来自客户端的数据。使用sock.recvfrom(1024)接收数据,并将接收到的数据解码为字符串。根据接收到的消息内容,执行相应的操作,如打开/关闭蜂鸣器、控制GPIO引脚输出等。如果接收到未知的消息内容,则发送一个"命令识别失败"的响应消息。
最后,使用sock.sendto(message.encode(), address)将响应消息发送回客户端。
异常处理部分捕获任何异常,并打印出错误信息。
最后,在finally块中关闭socket并调用sys.exit()退出程序。
5.使用步骤
电脑上用python打开程序
python mycar.py
树莓派上登录VNC之后,用命令行打开程序,按ctrl+alt+t
python pi.py
通过鼠标点击软件或者用方向键可以控制程序
6.摄像头使用
代码解释
# -*- coding: utf-8 -*-
import cv2
# 创建摄像头对象
cap = cv2.VideoCapture(1) # 0 表示默认摄像头,如果有多个摄像头可以尝试不同的索引值
# 检查摄像头是否成功打开
if not cap.isOpened():
print("无法打开摄像头")
exit()
# 循环读取摄像头数据
while True:
# 逐帧捕获图像
ret, frame = cap.read()
# 检查图像是否成功获取
if not ret:
print("无法获取图像")
break
# 在窗口中显示图像
cv2.imshow('Camera', frame)
# 按下 'q' 键退出循环
if cv2.waitKey(1) == ord('q'):
break
# 释放摄像头资源
cap.release()
# 关闭窗口
cv2.destroyAllWindows()
使用OpenCV库进行摄像头图像捕获和显示的简单示例。
首先,导入了cv2模块,这是OpenCV库的Python接口。
接下来,通过cv2.VideoCapture()函数创建了一个摄像头对象cap,并指定摄像头的索引值为1。如果有多个摄像头可用,可以尝试不同的索引值。(硬件不稳定,有时候要自己尝试用哪个,输入命令ls /dev/video*,从数值最小的那个往上加)
然后,通过调用cap.isOpened()方法检查摄像头是否成功打开。如果摄像头无法打开,则打印一条错误消息并退出程序。
在一个主循环中,使用cap.read()方法逐帧读取摄像头数据。ret是一个布尔值,表示是否成功获取到图像帧,frame是捕获到的图像帧。
然后,检查图像是否成功获取。如果未成功获取到图像帧,则打印一条错误消息并跳出循环。
接下来,使用cv2.imshow()方法在一个名为"Camera"的窗口中显示图像帧。
通过调用cv2.waitKey(1)等待用户按键输入,如果按下键盘上的'q'键,则退出主循环。
循环结束后,使用cap.release()释放摄像头资源。
最后,调用cv2.destroyAllWindows()关闭显示图像的窗口。
7.二值化程序
程序
#--coding:utf-8 --
import cv2
# 创建摄像头对象
cap = cv2.VideoCapture(1) # 0 表示默认摄像头,如果有多个摄像头可以尝试不同的索引值
# 检查摄像头是否成功打开
if not cap.isOpened():
print("无法打开摄像头")
exit()
# 循环读取摄像头数据
while True:
# 逐帧捕获图像
ret, frame = cap.read()
# 检查图像是否成功获取
if not ret:
print("无法获取图像")
break
# 将图像转换为灰度图
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 进行二值化处理
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 在窗口中显示二值化后的图像
cv2.imshow('Camera', binary)
# 按下 'q' 键退出循环
if cv2.waitKey(1) == ord('q'):
break
# 释放摄像头资源
cap.release()
# 关闭窗口
cv2.destroyAllWindows()
8.颜色识别
#--coding:utf-8 --
import cv2
import numpy as np
# 定义要识别的颜色范围(以HSV颜色空间为基准)
lower_red = np.array([0, 100, 100])
upper_red = np.array([10, 255, 255])
lower_green = np.array([35, 100, 100])
upper_green = np.array([85, 255, 255])
lower_blue = np.array([100, 100, 100])
upper_blue = np.array([130, 255, 255])
lower_white = np.array([0, 0, 200])
upper_white = np.array([180, 30, 255])
# 创建摄像头对象
cap = cv2.VideoCapture(0) # 0 表示默认摄像头,如果有多个摄像头可以尝试不同的索引值
# 检查摄像头是否成功打开
if not cap.isOpened():
print("无法打开摄像头")
exit()
# 循环读取摄像头数据
while True:
# 逐帧捕获图像
ret, frame = cap.read()
# 检查图像是否成功获取
if not ret:
print("无法获取图像")
break
# 将图像转换为HSV颜色空间
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# 根据颜色范围创建掩码
mask_red = cv2.inRange(hsv, lower_red, upper_red)
mask_green = cv2.inRange(hsv, lower_green, upper_green)
mask_blue = cv2.inRange(hsv, lower_blue, upper_blue)
mask_white = cv2.inRange(hsv, lower_white, upper_white)
# 对原始图像和掩码进行按位与操作
result_red = cv2.bitwise_and(frame, frame, mask=mask_red)
result_green = cv2.bitwise_and(frame, frame, mask=mask_green)
result_blue = cv2.bitwise_and(frame, frame, mask=mask_blue)
result_white = cv2.bitwise_and(frame, frame, mask=mask_white)
# 在窗口中显示识别结果
cv2.imshow('Red', result_red)
cv2.imshow('Green', result_green)
cv2.imshow('Blue', result_blue)
cv2.imshow('White', result_white)
# 按下 'q' 键退出循环
if cv2.waitKey(1) == ord('q'):
break
# 释放摄像头资源
cap.release()
# 关闭窗口
cv2.destroyAllWindows()
首先定义了要识别的颜色范围,这里以HSV颜色空间为基准,使用np.array()创建了一个包含最低和最高颜色值的数组。
然后,我们将图像转换为HSV颜色空间,使用cv2.cvtColor()函数将BGR图像转换为HSV图像。
接下来,我们根据颜色范围创建了一个掩码,使用cv2.inRange()函数根据最低和最高颜色值在HSV图像中创建一个二值掩码。
然后,我们使用cv2.bitwise_and()函数对原始图像和掩码进行按位与操作,得到识别结果。
最后,我们在窗口中显示识别结果,并通过按下键盘上的'q'键来退出循环。
9.WEB服务控制
在电脑的浏览器输入下面的网址,会进入树莓派的WEB控制
http://yahboom4wd:8889
如要实现RGB灯的控制
点击运行就可以了
10.小车循迹
采样python调用可执行文件的方式,了解linux下程序进程的开启和关闭
实现了两个shell脚本
run.sh
#!/bin/bash
/home/pi/SmartCar/tracking &
stop.sh
#!/bin/bash
pkill -f tracking
通过subprocess实现对外部程序的调用
elif message.strip() == "tracking":
subprocess.Popen("/home/pi/run.sh", shell=True)
elif message.strip() == "stop":
brake(0.5)
subprocess.Popen("/home/pi/stop.sh", shell=True)
循迹逻辑
void main()
{
//wiringPi初始化
wiringPiSetup();
//初始化电机驱动IO口为输出方式
pinMode(Left_motor_go, OUTPUT);
pinMode(Left_motor_back, OUTPUT);
pinMode(Right_motor_go, OUTPUT);
pinMode(Right_motor_back, OUTPUT);
//创建两个软件控制的PWM脚
softPwmCreate(Left_motor_pwm,0,255);
softPwmCreate(Right_motor_pwm,0,255);
//定义按键接口为输入接口
pinMode(key, INPUT);
//定义四路循迹红外传感器为输入接口
pinMode(TrackSensorLeftPin1, INPUT);
pinMode(TrackSensorLeftPin2, INPUT);
pinMode(TrackSensorRightPin1, INPUT);
pinMode(TrackSensorRightPin2, INPUT);
//调用按键扫描函数
// key_scan();
while(1)
{
//检测到黑线时循迹模块相应的指示灯亮,端口电平为LOW
//未检测到黑线时循迹模块相应的指示灯灭,端口电平为HIGH
TrackSensorLeftValue1 = digitalRead(TrackSensorLeftPin1);
TrackSensorLeftValue2 = digitalRead(TrackSensorLeftPin2);
TrackSensorRightValue1 = digitalRead(TrackSensorRightPin1);
TrackSensorRightValue2 = digitalRead(TrackSensorRightPin2);
//四路循迹引脚电平状态
// 0 0 X 0
// 1 0 X 0
// 0 1 X 0
//以上6种电平状态时小车原地右转,速度为250,延时80ms
//处理右锐角和右直角的转动
if ( (TrackSensorLeftValue1 == LOW || TrackSensorLeftValue2 == LOW) && TrackSensorRightValue2 == LOW)
{
spin_right(150, 150);
delay(80);
}
//四路循迹引脚电平状态
// 0 X 0 0
// 0 X 0 1
// 0 X 1 0
//处理左锐角和左直角的转动
else if ( TrackSensorLeftValue1 == LOW && (TrackSensorRightValue1 == LOW || TrackSensorRightValue2 == LOW))
{
spin_left(150, 150);
delay(80);
}
// 0 X X X
//最左边检测到
else if ( TrackSensorLeftValue1 == LOW)
{
spin_left(150, 150);
// delay(10);
}
// X X X 0
//最右边检测到
else if ( TrackSensorRightValue2 == LOW )
{
spin_right(150, 150);
// delay(10);
}
//四路循迹引脚电平状态
// X 0 1 X
//处理左小弯
else if ( TrackSensorLeftValue2 == LOW && TrackSensorRightValue1 == HIGH)
{
left(0, 150);
}
//四路循迹引脚电平状态
// X 1 0 X
//处理右小弯
else if (TrackSensorLeftValue2 == HIGH && TrackSensorRightValue1 == LOW)
{
right(150, 0);
}
//四路循迹引脚电平状态
// X 0 0 X
//处理直线
else if (TrackSensorLeftValue2 == LOW && TrackSensorRightValue1 == LOW)
{
run(150, 150);
}
//当为1 1 1 1时小车保持上一个小车运行状态
}
return;
}
这段代码是一个示例的C语言程序,用于控制基于树莓派的循迹小车。下面是对代码的解释:
- wiringPiSetup():这是WiringPi库的初始化函数,用于初始化树莓派的GPIO引脚。
- pinMode():这些函数用于设置引脚的输入或输出模式。例如,pinMode(Left_motor_go, OUTPUT)将Left_motor_go引脚设置为输出模式。
- softPwmCreate():这些函数用于创建软件控制的PWM脚。在这里,创建了Left_motor_pwm和Right_motor_pwm两个软件PWM引脚。
- pinMode(key, INPUT):将key引脚设置为输入模式,用于接收按键输入。
- pinMode(TrackSensorLeftPin1, INPUT)等:将四个循迹红外传感器引脚设置为输入模式。
- while(1):进入主循环,程序将在这里一直运行。
- 检测循迹传感器状态:通过digitalRead()函数读取循迹传感器引脚的电平状态,将结果存储在相应的变量中(例如TrackSensorLeftValue1)。
- 根据传感器状态执行相应的动作:使用一系列的条件语句(if和else if)来判断传感器的状态,并根据情况执行相应的动作,如转弯、直行或停止。
- delay():延时函数,用于控制小车的动作持续时间。
- return:函数结束。