玩树莓派小车时,光让它自己跑可不够有意思——上周跟着教程装好了小车底盘、接好电机,这次终于到了“手动操控”的环节。原本以为直接写个GPIO控制脚本就行,结果发现需要实时响应按键指令,最后用Tornado框架搭了个简单的Web服务,实现了电脑键盘和鼠标都能控制小车。今天把完整步骤拆解开,新手也能跟着做出来。
要让按键控制小车,本质是“实时接收指令→控制GPIO引脚电平→驱动电机转动”。这里有两个关键问题要解决:
另外,还需要准备这些工具:
树莓派默认没有装Tornado,而且控制GPIO需要RPi.GPIO
库(如果没装的话),先把这两个库装好。
如果是官方Raspbian系统,一般自带这个库;如果没有,执行以下命令安装:
sudo apt-get update
sudo apt-get install python-rpi.gpio # Python2版本
# 如果你用Python3,装这个:
# sudo apt-get install python3-rpi.gpio
推荐用pip安装,简单快捷。如果没装pip,先装pip:
# 安装pip(Python2)
sudo apt-get install python-pip
# 安装Tornado
sudo pip install tornado
如果pip安装慢,也可以用源码安装(备用方案):
# 下载Tornado源码包
wget https://pypi.python.org/packages/source/t/tornado/tornado-4.3.tar.gz
# 解压
tar xvzf tornado-4.3.tar.gz
# 进入目录编译安装
cd tornado-4.3
python setup.py build
sudo python setup.py install
在写代码前,必须先把树莓派和电机驱动模块(以L298N为例)接好线,不然代码跑起来小车也不会动。这里用的是树莓派的BOARD引脚编号(按物理引脚顺序编号,不容易乱)。
树莓派BOARD引脚 | 电机驱动模块(L298N) | 作用 |
---|---|---|
11 | IN1 | 控制左电机正转/反转 |
12 | IN2 | 控制左电机正转/反转 |
13 | IN3 | 控制右电机正转/反转 |
15 | IN4 | 控制右电机正转/反转 |
5V | 12V(需外接电源) | 给电机供电(树莓派5V不够,必须外接) |
GND | GND | 共地(必须接,否则会烧模块) |
接线注意:电机驱动模块的电源一定要外接(比如12V电池),直接用树莓派的5V引脚供电会导致树莓派死机,甚至烧硬件!
整个控制逻辑分两部分:Python后端(用Tornado搭Web服务,处理GPIO控制)和HTML前端(提供按键界面,发送指令给后端)。
新建一个car_control.py
文件,代码里包含了GPIO初始化、小车动作函数(前进、后退、转弯)和Tornado服务配置:
#!/usr/bin/python
# coding: utf8
import RPi.GPIO as GPIO
import time
import tornado.ioloop
import tornado.web
import tornado.httpserver
import tornado.options
from tornado.options import define, options
# 定义Web服务端口(默认80,浏览器访问时不用输端口)
define("port", default=80, type=int)
# 定义GPIO引脚(对应树莓派BOARD引脚)
IN1 = 11 # 左电机控制1
IN2 = 12 # 左电机控制2
IN3 = 13 # 右电机控制1
IN4 = 15 # 右电机控制2
def init():
"""初始化GPIO引脚"""
GPIO.setmode(GPIO.BOARD) # 使用BOARD引脚编号
# 设置4个引脚为输出模式
GPIO.setup(IN1, GPIO.OUT)
GPIO.setup(IN2, GPIO.OUT)
GPIO.setup(IN3, GPIO.OUT)
GPIO.setup(IN4, GPIO.OUT)
# ------------------- 小车动作函数 -------------------
def forward(tf):
"""前进:左右电机同时正转"""
init()
GPIO.output(IN1, GPIO.HIGH)
GPIO.output(IN2, GPIO.LOW)
GPIO.output(IN3, GPIO.HIGH)
GPIO.output(IN4, GPIO.LOW)
time.sleep(tf) # 持续tf秒
GPIO.cleanup() # 释放GPIO资源
def reverse(tf):
"""后退:左右电机同时反转"""
init()
GPIO.output(IN1, GPIO.LOW)
GPIO.output(IN2, GPIO.HIGH)
GPIO.output(IN3, GPIO.LOW)
GPIO.output(IN4, GPIO.HIGH)
time.sleep(tf)
GPIO.cleanup()
def left(tf):
"""左转:右电机正转,左电机停转"""
init()
GPIO.output(IN1, GPIO.LOW)
GPIO.output(IN2, GPIO.LOW)
GPIO.output(IN3, GPIO.HIGH)
GPIO.output(IN4, GPIO.LOW)
time.sleep(tf)
GPIO.cleanup()
def right(tf):
"""右转:左电机正转,右电机停转"""
init()
GPIO.output(IN1, GPIO.HIGH)
GPIO.output(IN2, GPIO.LOW)
GPIO.output(IN3, GPIO.LOW)
GPIO.output(IN4, GPIO.LOW)
time.sleep(tf)
GPIO.cleanup()
def pivot_left(tf):
"""后左转:左电机反转,右电机停转"""
init()
GPIO.output(IN1, GPIO.LOW)
GPIO.output(IN2, GPIO.HIGH)
GPIO.output(IN3, GPIO.LOW)
GPIO.output(IN4, GPIO.LOW)
time.sleep(tf)
GPIO.cleanup()
def pivot_right(tf):
"""后右转:右电机反转,左电机停转"""
init()
GPIO.output(IN1, GPIO.LOW)
GPIO.output(IN2, GPIO.LOW)
GPIO.output(IN3, GPIO.LOW)
GPIO.output(IN4, GPIO.HIGH)
time.sleep(tf)
GPIO.cleanup()
def p_left(tf):
"""原地左转:左电机反转,右电机正转"""
init()
GPIO.output(IN1, GPIO.LOW)
GPIO.output(IN2, GPIO.HIGH)
GPIO.output(IN3, GPIO.HIGH)
GPIO.output(IN4, GPIO.LOW)
time.sleep(tf)
GPIO.cleanup()
def p_right(tf):
"""原地右转:左电机正转,右电机反转"""
init()
GPIO.output(IN1, GPIO.HIGH)
GPIO.output(IN2, GPIO.LOW)
GPIO.output(IN3, GPIO.LOW)
GPIO.output(IN4, GPIO.HIGH)
time.sleep(tf)
GPIO.cleanup()
# ------------------- Tornado请求处理 -------------------
class IndexHandler(tornado.web.RequestHandler):
"""处理Web请求:GET显示页面,POST处理按键指令"""
def get(self):
# 访问树莓派IP时,返回前端HTML页面
self.render("index.html")
def post(self):
# 接收前端发送的按键指令(比如w、a、s、d)
key = self.get_argument('k')
sleep_time = 0.1 # 每次动作持续0.1秒,避免动作太猛
init() # 初始化GPIO
# 根据按键指令执行对应动作
if key == 'w':
forward(sleep_time)
elif key == 's':
reverse(sleep_time)
elif key == 'a':
left(sleep_time)
elif key == 'd':
right(sleep_time)
elif key == 'q':
pivot_left(sleep_time)
elif key == 'e':
pivot_right(sleep_time)
elif key == 'z':
p_left(sleep_time)
elif key == 'x':
p_right(sleep_time)
self.write(key) # 给前端返回确认信息
# ------------------- 启动Web服务 -------------------
if __name__ == '__main__':
tornado.options.parse_command_line()
# 配置Tornado路由:访问根路径时,交给IndexHandler处理
app = tornado.web.Application(handlers=[(r"/", IndexHandler)])
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port) # 监听配置的端口
print("小车控制服务已启动,浏览器访问树莓派IP即可控制!")
tornado.ioloop.IOLoop.instance().start() # 启动事件循环
在和car_control.py
同一目录下,新建一个templates
文件夹(Tornado默认从这个文件夹读取HTML模板),然后在templates
里新建index.html
文件,代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>树莓派小车控制</title>
<!-- 引入jQuery,方便发送POST请求 -->
<script src="http://libs.baidu.com/jquery/1.9.0/jquery.js"></script>
</head>
<body>
<script type="text/javascript">
// 发送按键指令给后端
function sendKey(key) {
$.post('/', {k: key}, function(){});
}
$(function(){
// 1. 键盘控制:按下对应键发送指令
window.document.onkeydown = function(env) {
env = env || window.event;
var keyCode = env.keyCode;
// W(87)=前进,S(83)=后退,A(65)=左转,D(68)=右转
if (keyCode == 87) sendKey('w');
if (keyCode == 83) sendKey('s');
if (keyCode == 65) sendKey('a');
if (keyCode == 68) sendKey('d');
// Q(81)=后左转,E(69)=后右转,Z(90)=原地左转,X(88)=原地右转
if (keyCode == 81) sendKey('q');
if (keyCode == 69) sendKey('e');
if (keyCode == 90) sendKey('z');
if (keyCode == 88) sendKey('x');
};
// 2. 鼠标控制:按住按钮持续发送指令
var timer = null;
// 前进按钮(按住持续前进)
$('.forward').mousedown(function() {
timer = setInterval(function() {
sendKey('w');
}, 100); // 每100毫秒发一次指令,避免动作卡顿
});
// 左转按钮
$('.left').mousedown(function() {
timer = setInterval(function() {
sendKey('a');
}, 100);
});
// 右转按钮
$('.right').mousedown(function() {
timer = setInterval(function() {
sendKey('d');
}, 100);
});
// 后退按钮
$('.back').mousedown(function() {
timer = setInterval(function() {
sendKey('s');
}, 100);
});
// 松开鼠标时停止发送指令
$('#control-panel span').mouseup(function() {
clearInterval(timer);
});
});
</script>
<!-- 控制按钮样式:3x3网格,中间空着,四周是功能键 -->
<style type="text/css">
#control-panel {
width: 150px;
height: 150px;
background: #eee;
margin: 50px auto;
}
#control-panel span {
width: 50px;
height: 50px;
float: left;
text-align: center;
line-height: 50px;
cursor: pointer;
}
#control-panel span.active {
background: #ff6699;
color: white;
}
</style>
<!-- 控制按钮面板 -->
<div id="control-panel">
<span></span>
<span class="active forward">前进(W)</span>
<span></span>
<span class="active left">左转(A)</span>
<span></span>
<span class="active right">右转(D)</span>
<span></span>
<span class="active back">后退(S)</span>
<span></span>
</div>
</body>
</html>
在树莓派终端进入代码所在目录,执行以下命令启动服务(需要sudo权限,因为控制GPIO需要管理员权限):
sudo python car_control.py
如果看到“小车控制服务已启动,浏览器访问树莓派IP即可控制!”,说明服务正常启动了。
在树莓派终端执行ifconfig
(或ip addr
),找到局域网IP(比如192.168.1.105
)。
打开电脑浏览器,在地址栏输入树莓派的IP(比如http://192.168.1.105
),会看到一个3x3的控制面板:
sudo
,否则无法控制GPIO,会报“Permission denied”错误。如果觉得基础控制不够过瘾,还可以加这些功能:
总之,树莓派小车的乐趣就在于不断折腾——从接线到写代码,再到调试动作,每一步成功都特别有成就感。如果跟着做的时候遇到问题,欢迎评论区交流,一起踩坑一起进步!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。