以前也用单片机做过WIFI小车,但是单片机没有自带WIFI,仍然需要用到小路由器作为图传和控制信号传输。既然肯定要用到路由器,那何不直接用路由器作为主控呢,这样就省掉了单片机。这次作为主控的GL-inet迷你路由,64M内存,16M Flash,支持OPENWRT系统,自带刷不死Uboot,Uart调试接口已焊好,另预留5个GPIO接口,可以充分满足DIY爱好者的需要。5个GPIO制作WIFI小车勉强够用。首先来一张完成图。

一、需要用到的材料

  1. Gl-inet路由器(有TPLink WR703n应该也可以);
  2. L298N电机驱动模块;
  3. 2节18650电池;
  4. 小车底盘(带电机);
  5. LM2596S降压模块(输出5V电压)。

二、硬件组装

一个最基本的WIFI小车至少需要2路电机控制前进、后退、左转、右转。GL-inet的5个gpio需要用其中4个来控制两路电机。以下是电路连接说明。

路由器GPIO接口,从左到右依次是21、22、18、19、20、GND,其中22、18、19、20分别连接电机驱动板IN1、IN2、IN3、IN4。

三、程序代码

GL-inet路由需要刷openwrt-gl-ser2net.bin,因为原版的固件,打开摄像头图像连接要进行登录管理后台,刷完这个固件就可以直接打开。

  1. 主控(/usr/lib/lua/xiaoche.lua)采用LUA脚本编写,代码如下。
require("gpio")

--分割字符串函数
function Split(szFullString, szSeparator)  
    local nFindStartIndex = 1  
    local nSplitIndex = 1  
    local nSplitArray = {}  
    while true do  
        local nFindLastIndex = string.find(szFullString, szSeparator, nFindStartIndex)  
        if not nFindLastIndex then  
            nSplitArray[nSplitIndex] = string.sub(szFullString, nFindStartIndex, string.len(szFullString))  
            break  
        end  
        nSplitArray[nSplitIndex] = string.sub(szFullString, nFindStartIndex, nFindLastIndex - 1)  
        nFindStartIndex = nFindLastIndex + string.len(szSeparator)  
        nSplitIndex = nSplitIndex + 1  
    end  
    return nSplitArray  
end
--小车控制相关GPIO:左电机19,20;右电机18,22
--小车前进
function Forward()
    writeGPIO(20,0)
    writeGPIO(19,1)
    writeGPIO(18,1)
    writeGPIO(22,0)
end
--小车后退
function Backward()
    writeGPIO(20,1)
    writeGPIO(19,0)
    writeGPIO(18,0)
    writeGPIO(22,1)
    
end
--小车左转弯
function Left()
    writeGPIO(20,0)
    writeGPIO(19,1)
    writeGPIO(18,0)
    writeGPIO(22,1)
end
--小车右转弯
function Right()
    writeGPIO(20,1)
    writeGPIO(19,0)
    writeGPIO(18,1)
    writeGPIO(22,0)
end
--小车停车
function Stop()
    writeGPIO(20,0)
    writeGPIO(19,0)
    writeGPIO(18,0)
    writeGPIO(22,0)
end

local WebService = {}

function WebService.Run()
    local GET = os.getenv("QUERY_STRING")
    --解析GET数据
    local list = Split(GET,'=')
    local c = list[2]
    --操作GPIO
    configureOutGPIO(20)
    configureOutGPIO(19)
    configureOutGPIO(18)
    configureOutGPIO(22)
    local status="Stop"
    if(c=="forward")
    then
        Forward()
        status="Forward"
    elseif(c=="backward")
    then
        Backward()
        status="Backward"
    elseif(c=="left")
    then
        Left()
        status="Left"
    elseif(c=="right")
    then
        Right()
        status="Right"
    else
        Stop()
        status="Stop"
    end
    --返回数据
    io.write("Content-type: text/html\nPragma: no-cache\n\n")
    io.write(status)
end

return WebService
  1. 需要用到一个GPIO操作函数库(/usr/lib/lua/gpio.lua),代码如下。
--@author: Ewelina, Rafa, Rafa
--GPIO utilities

--Writes 'what' to 'where'
function writeToFile (where,what)
    local fileToWrite=io.open(where, 'w')
    fileToWrite:write(what)
    fileToWrite:close()    
end
--Reads a character from file 'where' and returns the string
function readFromFile (where)
    local fileToRead=io.open(where, 'r')
    fileStr = fileToRead:read(1)
    fileToRead:close()    
    return fileStr
end

--Returns true if file exists
function file_exists(name)
   local f=io.open(name,"r")
   if f~=nil then io.close(f) return true else return false end
end

--Exports gpio ID to use as an output pin
function configureOutGPIO (id)
    if not file_exists('/sys/class/gpio/gpio'..id..'/direction') then
        writeToFile('/sys/class/gpio/export',id)
    end
    writeToFile('/sys/class/gpio/gpio'..id..'/direction','out')
end

--Exports gpio ID to use as an input pin
function configureInGPIO (id)
    if not file_exists('/sys/class/gpio/gpio'..id..'/direction') then
        writeToFile('/sys/class/gpio/export',id)
    end
    writeToFile('/sys/class/gpio/gpio'..id..'/direction','in')
end

--Reads GPIO 'id' and returns it's value
--@Pre: GPIO 'id' must be exported with configureInGPIO
function readGPIO(id)
    gpioVal = readFromFile('/sys/class/gpio/gpio'..id..'/value')
    return gpioVal
end

--Writes a value to GPIO 'id'
--@Pre: GPIO 'id' must be exported with configureOutGPIO
function writeGPIO(id, val)
    gpioVal = writeToFile('/sys/class/gpio/gpio'..id..'/value',val)
    return true
end
  1. /www/cgi-bin/xc文件代码如下。
#!/usr/bin/lua
local Webservice = require 'xiaoche'
Webservice.Run()
  1. 控制面板/www/xiaoche.html代码。
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=500;target-densitydpi=device-dpi; minimum-scale=0.5; maximum-scale=2,user-scalable=no">
<title>视频小车控制端</title>
<script type="text/javascript" src="jquery-1.9.1.min.js"></script>
<script>
function Down(){
    $.get("cgi-bin/xc?c="+$(this).attr('id'), function(data){
        $("#status").html(data);
    });
}

function Up(){
    $.get("cgi-bin/xc?c=stop", function(data){
        $("#status").html(data);
    });
}

function load (){
    document.getElementById("forward").addEventListener("touchstart", Down);
    document.getElementById("backward").addEventListener("touchstart", Down);
    document.getElementById("left").addEventListener("touchstart", Down);
    document.getElementById("right").addEventListener("touchstart", Down);
    document.getElementById("forward").addEventListener("touchend", Up);
    document.getElementById("backward").addEventListener("touchend", Up);
    document.getElementById("left").addEventListener("touchend", Up);
    document.getElementById("right").addEventListener("touchend", Up);
    //加载摄像头图像
    $("#video").html("<img src='http://"+location.hostname+":8083/?action=stream' style='width:100%;'>");
}
window.addEventListener('load',load,false);
</script>
<style>
html,body{  
    margin:0 auto;
    background:#CCC;
}
.container{
    margin:0 auto;
    width:100%;
    max-width:500px;
    background-color:#CCC;
}
.video{
    width:100%;
    margin:0 auto;
    text-align:center;
}
.control{
    width:100%;
    height:220px;
}
.button_fx{
    width:100%;
    height:100%;
}
</style>
</head>

<body>
<div class="container">
<div id="video" class="video"></div>
<table class="control" border="0" cellpadding="5">
  <tr>
    <td width="15%" height="30%">&nbsp;</td>
    <td width="15%"><input type="button" class="button_fx" value="前进" id="forward"></td>
    <td width="15%">&nbsp;</td>
    <td width="20%" align="center"><div id="status"> </div></td>
    <td width="35%" align="center">摄像头控制</td>
  </tr>
  <tr>
    <td width="15%" height="30%"><input type="button" class="button_fx" value="左转" id="left"></td>
    <td width="15%">&nbsp;</td>
    <td width="15%"><input type="button" class="button_fx" value="右转" id="right"></td>
    <td width="20%">&nbsp;</td>
    <td width="35%"><input type="button" class="button_fx" value="向上" id="gpio21s"></td>
  </tr>
  <tr>
    <td width="15%" height="30%">&nbsp;</td>
    <td width="15%"><input type="button" class="button_fx" value="后退" id="backward"></td>
    <td width="15%">&nbsp;</td>
    <td width="20%">&nbsp;</td>
    <td width="35%"><input type="button" class="button_fx" value="向下" id="gpio21x"></td>
  </tr>
</table>
</div>
</body>
</html>

此外需要用到的/www/jquery-1.9.1.min.js不要忘了下载到相应目录,别的jquery版本也可。

控制小车只要用手机连接路由器,浏览器打开http://路由器IP/xiaoche.html,建议用via浏览器,长按不会跳出菜单影响操作。把路由器设置位中继模式配合内网穿透,还可以在外网远程控制。最后来一段小车演示视频: