整体功能:电脑上观看小车前方画面,通过电脑方向键控制小车前后运动、左右转弯,如前进后退键,按下前进或后退,松开停车,左右同理
关键技术部分:PC端:使用pygame编写上位机,作为服务器
树莓派端:图像获取、客户端编程、IO电平控制
STM32端:IO输入、PWM输出
电路端:STM32电路设计、H桥驱动电路设计
写在前面:局域网超视距遥控小车是笔者大四时的心愿,无奈当时没有时间和能力,研二才有机会继续完成,从车上的电路设计到树莓派程序,到局域网通信,到电脑上的UI设计到近期才全部能串起来,借这一DIY项目彩排一下老师的项目和实践近期所学。电脑端本计划写个像样的上位机,但没找的理想的键盘事件算法,但可以用pyqt5的按钮做控制,对使用Python写软件有兴趣的小伙伴移步专栏https://blog.csdn.net/qq_36071362/category_9697208.html
环境陈述:
1.老年时代的飞思卡尔c车,双电机,舵机转向
2.STM32C8T6单片机,keil,配双H桥驱动电路
3.树莓派4B,16G卡,Raspbian系统
4.Windows10,pycharm,Python3.7
功能陈述:
1.stm32制做底层控制板,H桥驱动,可直接接航模遥控器遥控
2.树莓派做小车端的图像获取和WiFi连接工具,运行socket的客户端
3.电脑端(Windows10)运行socket的服务端,显示树莓派传回的画面,检测电脑键盘方向键
控制流程:
电脑的服务端程序获取到方向键按下
→方向和前后信息传输至树莓派
→树莓派将画面实时传给电脑(一直传)将方向信息传给STM32
→32控制H桥控制电机正反转、控制舵机左右转
通信方式:
1.树莓派与电脑:WiFi,电脑和树莓派连接到同一路由器
2.树莓派与STM32:4个IO(因为作烧了树莓派的串口。。。。)
3.STM32与电机舵机:pwm,频率不同的脉宽调制信号,电机:10000hz,舵机:50hz
相关资料:
1.code1:STM32的小车控制程序-keil,c语言
2.code2:树莓派上的Python写的client程序,通过socket传输画面给电脑
3.code3:电脑上的Python写的server程序,通过socket接收画面,检测键盘按键,发送小车动作指令
4.schdoc、pcbdoc:电路设计文件
所做工作:
1.底层控制电路设计、焊接,作者前几年干过。。这版借师弟学习机会由他设计焊接板子,板子集成STM32与双H桥电机驱动电路
2.树莓派程序编写调试,树莓派端作为客户端需要从CSI摄像头获取画面通过WiFi传输至电脑,同时获取来自电脑的控制信息
3.电脑上编写服务器程序,使用pygame获取方向按键(没找的别的合适的包),显示树莓派的摄像头画面,发送方向控制信息
未来功能规划:
1.人脸跟踪人脸识别
2.目标识别
3.语音识别(语音控制)
4.xxxxx
下面贴出源码及介绍:
电脑端(服务端):
实现功能:
(1)通过socket实时接收树莓派的相机画面图片,并使用pygame显示
(2)在pygame的画面中,检测键盘方向键按下弹起情况,按下时分别通过socket发送“sta1”,“sta2”, “fta1”,“fta2”,弹起时若是前后键(即方向上下键),发送“sta0”,若是左右键,发送“fta0”
import socket
import cv2
import numpy
import pygame
# 服务器端的IP地址
address = ("192.168.2.240", 8088)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(address)
s.listen(True)
conn, addr = s.accept()
sta = '0'
fta = '0'
def check_keydown_events(event, sock):
if event.key == pygame.K_UP:
sta = "sta1"
print(sta)
sock.send(sta.encode())
elif event.key == pygame.K_DOWN:
sta = "sta2"
print(sta)
sock.send(sta.encode())
elif event.key == pygame.K_RIGHT:
fta = "fta1"
print(fta)
sock.send(fta.encode())
elif event.key == pygame.K_LEFT:
fta = "fta2"
print(fta)
sock.send(fta.encode())
#print(sta)
#print(fta)
def check_keyup_events(event, sock):
if event.key == pygame.K_UP:
sta = "sta0"
print(sta)
sock.send(sta.encode())
elif event.key == pygame.K_DOWN:
sta = "sta0"
print(sta)
sock.send(sta.encode())
elif event.key == pygame.K_RIGHT:
fta = "fta0"
print(fta)
sock.send(fta.encode())
elif event.key == pygame.K_LEFT:
fta = "fta0"
print(fta)
sock.send(fta.encode())
#
#
def check_events(sock):
for event in pygame.event.get():
# print("oooooo")
if event.type == pygame.KEYDOWN:
check_keydown_events(event, sock) #检测按键,发送数据
print("1111111")
elif event.type == pygame.KEYUP:
check_keyup_events(event, sock)
print("2222222")
def recvall(sock, count):
buf = b''
start = "okok"
start = start.encode()
start = start.decode()
start = start.encode()
while count:
newbuf = sock.recv(count)
check_events(sock)
#sock.send(start)
if not newbuf: return None
buf += newbuf
count -= len(newbuf)
return buf
pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("遥控平台")
while 1:
length = recvall(conn, 16)
stringData = recvall(conn, int(length))
# stringData = stringData.encode()
data = numpy.fromstring(stringData, dtype='uint8')
decimg = cv2.imdecode(data, 1)
#cv2.imshow('SERVER', decimg)
cv2.imwrite('messigray.bmp', decimg)
decimg1 = pygame.image.load('messigray.bmp')
rect = decimg1.get_rect()
screen.blit(decimg1, (0,0))
pygame.display.update()
# key = cv2.waitKeyEx(0)
# print('key =', key)
if cv2.waitKey(10) == 27:
break
s.close()
cv2.destroyAllWindows()
树莓派端(客户端):
实现功能:
(1)通过socket发送摄像头的画面图片
(2)接收socket中的方向和前后动作信息,即sta0-2和fta0-2,1和2分别是前进后退,左转右转,0是恢复,即停车或舵机回正
(3)通过IO传输从WiFi(socket)接收到的动作信息,因为串口占用,故使用4个IO,通过不同电平状态传输小车动作,因为转向动作和前后动作各包含三种状态(前后左右,回正和停车,不调速),故各分配两个引脚控制方向和前后
import socket
import cv2,time
import numpy
import RPi.GPIO as GPIO
import pigpio
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
# 设置引脚
data1 = 4
data2 = 17
speed1 = 27
speed2 = 22
address = ('192.168.2.240', 8088)
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect(address)
capture = cv2.VideoCapture(0)
ret, frame = capture.read()
encode_param=[int(cv2.IMWRITE_JPEG_QUALITY),90]
GPIO.setup(data1, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(data2, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(speed1, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(speed2, GPIO.OUT, initial=GPIO.LOW)
def forward():
GPIO.setup(data1, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(data2, GPIO.OUT, initial=GPIO.HIGH)
def back():
GPIO.setup(data1, GPIO.OUT, initial=GPIO.HIGH)
GPIO.setup(data2, GPIO.OUT, initial=GPIO.LOW)
def stop():
GPIO.setup(data1, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(data2, GPIO.OUT, initial=GPIO.LOW)
def right():
GPIO.setup(speed1, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(speed2, GPIO.OUT, initial=GPIO.HIGH)
def left():
GPIO.setup(speed1, GPIO.OUT, initial=GPIO.HIGH)
GPIO.setup(speed2, GPIO.OUT, initial=GPIO.LOW)
def retu():
GPIO.setup(speed1, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(speed2, GPIO.OUT, initial=GPIO.LOW)
str222 = "123"
str222 = str222.encode
#sock.send(str222);
while ret:
ret, frame = capture.read()
#frame = frame[200:480,0:640]
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
result, imgencode = cv2.imencode('.jpg', frame, encode_param)
data = numpy.array(imgencode)
stringData = data.tostring()
strrr = str(len(stringData)).ljust(16)
strrr = strrr.encode()
sock.send(strrr);
sock.send(stringData);
# 用来非阻塞机制通信,0x40参数非常重要
try:
sdata = sock.recv(4, 0x40)
sdata=sdata.decode()
except BlockingIOError as e:
sdata = "nnn"
print(sdata)
#print("35553")
if sdata == "fta0":
retu()
if sdata == "fta1":
right()
if sdata == "fta2":
left()
if sdata == "sta0":
stop()
if sdata == "sta1":
forward()
if sdata == "sta2":
back()
#sdata = sock.recv(4)
#if len(sdata)>0:
print(sdata)
#time.sleep(0.5)
#sdata = sock.recv(2)
#if len(sdata)>0:
# print(sdata)
#print(frame.shape)
#frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
#decimg=cv2.imdecode(data,1)
#cv2.imshow('CLIENT',frame)
sock.close()
cv2.destroyAllWindows()
STM32端(底盘控制端):
实现功能:
(1)检测树莓派输出引脚的电平状态,获取目标动作
(2)从(1)的信息中根据动作控制舵机转动
(3)从(1)的信息中根据动作控制电机动作
#include "led.h"
#include "delay.h"
#include "sys.h"
#include "key.h"
#include "usart.h"
#include "exti.h"
#include "wdg.h"
#include "timer.h"
#include "control.h"
#include "pwm_in.h"
#include "LQ12864.h"
float angle_x;
float angle_y;
float angle_z;
char stxt[20];
int angle;
int speed;
int sta = 0;
int FXsta = 0;
int default_speed = 500;
int main(void)
{
SystemInit();
delay_init(72); //ÑÓʱ³õʼ»¯
NVIC_Configuration();//ÅäÖÃÖжÏ
//KEY_Init(); //°´¼ü¶Ë¿Ú³õʼ»¯
TIM2_PWM_Init(); //ÐγÉPWM²¨ÐÎ
TIM1_PWM_Init(); //ÐγÉPWM²¨ÐÎ
//Timerx_Init(1000-1,72-1);//10KhzµÄ¼ÆÊýƵÂÊ£¬¼ÆÊýµ½5000Ϊ1ms
TIM4_Cap_Init(0xffff,71); //ÒÔ1MhzµÄƵÂʼÆÊý
LED_Init(); //LCDÒý½Å³õʼ»¯ÔÚÕâÀïÃæ
LCD_Init(); //A0,A1ÓëÒ£¿ØÆ÷´«³åÍ»
speed = 600;
angle = 8570;
TIM_SetCompare2(TIM1, angle);//TIM1µÄÕ¼¿Õ±È·´ÏòÊä³ö 8650Öмä ×î×ó8500 ×îÓÒ8800
while(1)
{
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_9) == 0 && GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_10) == 0) speed=0; //Í£³µ
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_9) == 0 && GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_10) == 1) {sta=1;speed=default_speed;} //Ç°½ø
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_9) == 1 && GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_10) == 0) {sta=2;speed=default_speed;} //ºóÍË
// if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_9) == 1 && GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_10) == 1) sta=4; //±¸ÓÃ---
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11) == 0 && GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_12) == 0) angle=8555; //»ØÕý
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11) == 0 && GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_12) == 1) angle=8755; //ÓÒת
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11) == 1 && GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_12) == 0) angle=8395; //×óת
// if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_11) == 1 && GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_12) == 1) speed=500; //±¸ÓÃ----
//Èí¼þÒ£¿Ø
if(sta == 1)//Ç°½ø
{
TIM_SetCompare1(TIM2, speed);
TIM_SetCompare2(TIM2, 0);
TIM_SetCompare3(TIM2, speed);
TIM_SetCompare4(TIM2, 0);
}
if(sta == 2)//µ¹³µ
{
TIM_SetCompare1(TIM2, 0);
TIM_SetCompare2(TIM2, speed);
TIM_SetCompare3(TIM2, 0);
TIM_SetCompare4(TIM2, speed);
}
if(speed == 0)//Í£³µ
{
TIM_SetCompare1(TIM2, 0);
TIM_SetCompare2(TIM2, 0);
TIM_SetCompare3(TIM2, 0);
TIM_SetCompare4(TIM2, 0);
}
TIM_SetCompare2(TIM1, angle);//TIM1µÄÕ¼¿Õ±È·´ÏòÊä³ö 8650Öмä ×î×ó8500 ×îÓÒ8800
}
}