很早以前就想做个wifi小车,带实时视频功能,可以用手机控制,满足一下自己童年的梦想,以前弄单片机的时候,视频传送这块一直没突破过去,现在发现树莓派还是挺方便的,其实这不是什么新鲜玩意,但是网上没有完整的教程,再者,大多数解决方案用的都是PYTHON,刚好我又比较排斥这门语言。某宝卖的成品没试过,还是自己DIY有意思,通过这段时间摸索,逐渐实现了一部分功能,前不久折腾完PCA9685,基本上解决了小车的驱动问题了,然后自己写了个安卓小程序,目前控制舵机还是挺顺畅的,小车还没完全组装好,最近又去折腾了一下esp8266,还有因为比较懒~
写博客的目的是怕自己时间长了会忘记踩过的坑,做这个的目的,也纯粹是为了好玩,因为自己也是新手,所以我的做法并不具有指导意义,不得不说,有时候光有兴趣和热情是没用的,没有大神指点真的很迷茫。能帮到一些对此感兴趣的朋友最好,假如有高手发现错误并指正,那更是求之不得。
以下代码是配合安卓客户端用的,是作为服务端运行在树莓派上的,不是手机,别弄混了,代码会逐渐更新,最新的代码会发布在下面地址。
https://github.com/ffmydream/wifi_car_go/
文件弄好后,直接命令行 go run wifi_car_go,前提你要装了golang。
要是提示导包错误,请参见我的另一篇博文
package main
import (
"fmt"
"strconv"
"time"
"strings"
"net"
//"github.com/tarm/goserial"
"github.com/op/go-logging"
"github.com/sergiorb/pca9685-golang/device"
"golang.org/x/exp/io/i2c"
"log"
"os/exec"
// "time"
)
const (
I2C_ADDR = "/dev/i2c-1"
ADDR_01 = 0x40
SVO_TYPE = 180 //舵机类型:最大转角
SERVO_HORIZ = 0 //横向舵机
SERVO_VERTIC = 1 //纵向舵机
MOTOR_LEFT_FOR = 2
MOTOR_LEFT_BACK = 3
MOTOR_RIGHT_FOR = 4
MOTOR_RIGHT_BACK = 5
MIN_PULSE = 0
MAX_PULSE = 4095
)
func setAngle(p *device.Pwm, angle int) {
offReg := int((0.5 + float32(angle)/SVO_TYPE*2) * 4096 / 20)
p.SetPulse(0, offReg)
}
func setPercentage(p *device.Pwm, percent int) {
pulseLength := int((MAX_PULSE-MIN_PULSE)*float32(percent)/100 + MIN_PULSE)
p.SetPulse(0, pulseLength)
}
func main() {
ch := make(chan string)
logger := logging.Logger{}
dev, err := i2c.Open(&i2c.Devfs{Dev: I2C_ADDR}, ADDR_01)
if err != nil {
log.Fatal(err)
}
pca := device.NewPCA9685(dev, "Servo Controller", MIN_PULSE, MAX_PULSE, &logger)
pca.Frequency = 50.0
pca.Init()
//simple tcp server
//1.listen ip+port
listener, err := net.Listen("tcp", "192.168.1.166:50000")
if err != nil {
fmt.Printf("listen fail, err: %v\n", err)
return
}
go pacCtr(pca, ch)
//2.accept client request
//3.create goroutine for each request
for {
conn, err := listener.Accept()
if err != nil {
fmt.Printf("accept fail, err: %v\n", err)
continue
}
//create goroutine for each connect
go doLink(conn, ch)
}
}
func doLink(conn net.Conn, ch chan string) {
defer conn.Close()
for {
var buf [128]byte
n, err := conn.Read(buf[:])
if err != nil {
fmt.Printf("read from connect failed, err: %v\n", err)
break
}
str := string(buf[:n])
ch <- str
}
}
func pacCtr(pca *device.PCA9685, ch chan string) {
timeOut := false //pca写0标志位,如果超过时间没有接受到数据,停机,因为只需要发送一次停机指令,配合select的超时选项使用。
//2个舵机通道
servo0 := pca.NewPwm(SERVO_HORIZ)
servo1 := pca.NewPwm(SERVO_VERTIC)
//4个电机通道
motorLeftForward := pca.NewPwm(MOTOR_LEFT_FOR)
motorLeftBackward := pca.NewPwm(MOTOR_LEFT_BACK)
motorRightForward := pca.NewPwm(MOTOR_RIGHT_FOR)
motorRightBackward := pca.NewPwm(MOTOR_RIGHT_BACK)
fmt.Println("Start init servos and motors.")
setAngle(servo0, 90)
setAngle(servo1, 90)
for {
select {
case str := <-ch:
sl := strings.Split(str, ":")
// fmt.Printf("type %v,channel %v,value %v.\n", sl[0], sl[1], sl[2])
switch sl[0] {
case "srvo":
data, _ := strconv.Atoi(sl[2])
switch sl[1] {
case "horiz":
setAngle(servo0, data)
case "vertic":
setAngle(servo1, data)
}
case "car":
data, _ := strconv.Atoi(sl[2])
switch sl[1] {
case "for":
setPercentage(motorLeftBackward, 0)
setPercentage(motorRightBackward, 0)
setPercentage(motorLeftForward, data)
setPercentage(motorRightForward, data)
case "back":
setPercentage(motorLeftForward, 0)
setPercentage(motorRightForward, 0)
setPercentage(motorLeftBackward, data)
setPercentage(motorRightBackward, data)
case "left":
setPercentage(motorLeftForward, 0)
setPercentage(motorLeftBackward, data)
setPercentage(motorRightForward, 0)
setPercentage(motorRightForward, data)
case "right":
setPercentage(motorLeftBackward, 0)
setPercentage(motorLeftForward, data)
setPercentage(motorRightForward, 0)
setPercentage(motorRightBackward, data)
}
case "cmd":
cmd := exec.Command(sl[2])
err := cmd.Run()
if err != nil {
fmt.Println(err.Error())
}
}
timeOut = false
case <-time.After(400 * time.Millisecond):
if timeOut == false {
setPercentage(motorLeftBackward, 0)
setPercentage(motorLeftForward, 0)
setPercentage(motorRightForward, 0)
setPercentage(motorRightBackward, 0)
// fmt.Println("timeOut.")
timeOut = true //操作完成后改变变量状态,以免一直向pca发送停机指令
}
}
}
}
思路很简单,搭建一个TCP服务器,等待安卓客户端的连接,接收到指令后,控制舵机或电机运行,安卓客户端在按下按钮后,持续不断的按相同的时间间隔发送数据,TCP服务器接收并判断该数据是干吗的,是控制舵机、控制马达还是对树莓派系统发送命令,如果是控制马达,在接收该指令后400ms内(暂定,可以自己调试,增加或减少延时),如果没有接收到新的指令,就让马达停下,以免小车接收不到信号会一直不停的跑。
安卓客户端程序在另一篇文章里。