树莓派小车2——用GO搭建tcp服务器实现远程控制

很早以前就想做个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内(暂定,可以自己调试,增加或减少延时),如果没有接收到新的指令,就让马达停下,以免小车接收不到信号会一直不停的跑。

安卓客户端程序在另一篇文章里。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值