微信小程序+UDP+wifi8266+l293d扩展板+Arduino uno遥控小车
序言
前面突然看到自己已经落灰的Arduino板子,感觉自己必须做点什么。正好手上有个8266的模块、有个焊接接好的小车、有个落灰的Arduino板子,好巧。正好前几天上过一篇8266模块和微信小程序进行UDP通信的博文,一切都正好,那开搞吧。
前置知识需要的不多
至少要去看看微信小程序关于UDP通信的文档,当然你看我上面的文章也行,感觉还挺详细的,有电脑就行(滑稽~),下面的代码我都只贴最核心的代码,基本上都会覆盖到,如果需要所有项目的源码请一键三连留下邮箱,接下来直接正题
主要涉及的一些东西
wifi8266:
3V3 3.3V供电,避免使用5V供电
RX UART_RXD,异步串口接受端
RST 外部复位引脚,低电平有效,默认高电平
IO0 GPIO 0引脚;状态:1.悬空:Flash下载模式和工作模式;2.下拉:串口下载模式
EN 使能端口,高电平工作,低电平模块不工作
IO2 GPIO2引脚,开机上电时禁止下拉,默认高电平
UTXD UART_TXD,异步串口发送端
GND GND接地引脚
烧录工具
广告请忽略,我买的就是这一套,挺方便的
L293d扩展板
这个没啥好说的,直接插Arduino板子上,下面是太极创客官网的板子介绍,感兴趣可以去看看,别急着吐槽,需要注意的地方我都会在这里说的。比如这里的2号引脚就很重要,没有焊接,这个很关键,esp-01和Arduino串口通信需要用到,板子上的A0-A5也可以作为软串口通信,但是接上esp-01后它无法正常工作(这个可以自己去验证一下,l293d扩展板其实真没什么东西只是为了扩展Arduino的功能,单块l293d最多只能控制两个电机,如果是两轮驱动完全没必要买这个板子,但是转向你需要搞一个舵机,供电建议使用9v电源同时为Arduino和l293d供电,电压不足会导致很多奇怪的行为)
Arduino uno
这也没啥好说的,感兴趣就自己去太极创客官网看看介绍,主要的硬件差不多就这几个,后面我会贴一个我自己焊接的图,不急
微信小程序
这个是我自己开发的,因为有的页面空着,发布小程序还被微信驳回了,只能测试玩玩了,不想自己写的话可以扫码体验一把,虽然瑕疵有点多,但是测试还是可以的,后面做新的东西我也会在这个上面继续开发,有需求也可以提,等成熟了可以把代码放github
小程序wifi功能核心代码以及运行界面
背景是一个video标签,不要在意内容,这里准备接入Esp32-cam摄像头
代码 html css js
<video class="video-bg" autoplay controls="{{false}}" src="https://vd4.bdstatic.com/mda-kewsvi1w9u7bv42w/v1-cae/mda-kewsvi1w9u7bv42w.mp4?v_from_s=hkapp-haokan-hnb&auth_key=1669192668-0-0-aefa5c11bf88aacc22c048084a76aadb&bcevod_channel=searchbox_feed&pd=1&cd=0&pt=3&logid=0468789483&vid=1260358254659161368&abtest=&klogid=0468789483"></video>
<view class="wificontainer">
<van-row>
<van-col span="12">
<view class="direction" catchtouchmove="move" catchtouchstart="movestart" catchtouchend="moveend" >
<view class="point" style="left:{{left+'px'}};top:{{top+'px'}}">+</view>
</view>
</van-col>
<van-col span="12">
<view class="option">
<view class="btnFore" bindtap="instructions" data-message="accelerate">加速</view>
<view class="btnBack" bindtap="instructions" data-message="decelerate">减速</view>
<view class="btnStop" bindtap="instructions" data-message="stop">停止</view>
</view>
</van-col>
</van-row>
</view>
.video-bg{
width:100vw;
height:100vh;
position:absolute;
z-index:-100;
}
//这里这个style标签只是为了当前显示,在微信小程序中使单个文件
<style>
.wificontainer{
height: 100vh;
width:100vw;
position: relative;
z-index:100;
background: transparent;
position:absolute;
/* background-position: left top;
background-repeat: no-repeat;
background-position: center;
background-size: cover;
background-image: url('https://pic.rmb.bdstatic.com/c614c350d47179742c388cf996c3b23d.jpeg'); */
/* https://pic.rmb.bdstatic.com/c614c350d47179742c388cf996c3b23d.jpeg */
}
.van-row{
height:100%;
text-align: center;
vertical-align: middle;
/* margin:10px; */
}
.van-col{
height:100%;
display: grid;
grid-template-columns:100%;
grid-template-rows: 100%;
justify-items: center;
align-items: center;
/* border:1px solid red; */
}
.direction{
height:140px;
width:140px;
line-height:140px;
border-radius: 50%;
color:#fff;
border:1px solid #ccc;
background-color: rgba(66, 65, 57, 0.2);
box-shadow: 0 0 14px #fff inset;
position: relative;
/* border: 1px solid red; */
}
.point{
width:50px;
height:50px;
line-height: 50px;
display: inline-block;
background-color: rgba(0,0,0,0.4);
box-shadow: 0 0 14px #fff inset;
border-radius: 50%;
position: relative;
/* left:00px; */
/* vertical-align: center; */
color:#fff;
/* position: absolute; */
}
/* 操作按钮容器 */
.option{
height:100%;
width:100%;
display: grid;
align-items: center;
justify-items: center;
grid-template-rows: repeat(4,25%);
grid-template-columns: repeat(4,25%);
}
.btnFore{
/* border:1px solid red; */
display: inline-block;
width:60px;
height:60px;
line-height:60px;
grid-row-start: 2;
grid-row-end:3;
grid-column-start:3;
grid-column-end:4;
color:#fff;
/* margin:auto; */
border:1px solid #ccc;
background-color: rgba(66, 65, 57, 0.2);
box-shadow: 0 0 14px #fff inset;
border-radius: 50%;
}
.btnBack{
display: inline-block;
width:60px;
height:60px;
line-height:60px;
grid-row-start: 3;
grid-row-end:4;
grid-column-start:3;
grid-column-end:4;
color:#fff;
/* margin:auto; */
border:1px solid #ccc;
background-color: rgba(66, 65, 57, 0.2);
box-shadow: 0 0 14px #fff inset;
border-radius: 50%;
/* vertical-align: center; */
}
.btnStop{
display: inline-block;
width:60px;
height:60px;
line-height:60px;
grid-row-start: 2;
grid-row-end:3;
grid-column-start:2;
grid-column-end:3;
color:#fff;
/* margin:auto; */
border:1px solid #ccc;
background-color: rgba(192, 17, 66, 0.8);
box-shadow: 0 0 14px #fff inset;
border-radius: 50%;
/* vertical-align: center; */
}
</style>
这里讲一下,因为微信的渲染规则,导致movetouch等方法的触发会有卡顿的情况,所以做了节流处理,有博主说wxs可以解决,可以去试试
// pages/controls/wifi/wifi.js
import Dialog from '@vant/weapp/dialog/dialog';
// import Notify from '@vant/weapp/notify/notify';
Page({
/**
* 页面的初始数据
*/
data: {
//wifi列表
equipmentList:[],
//需要发送的数据
message:"",
udpScoket:{},
//记录起点的相对位置
originPageX:0,
originPageY:0,
left:0,
top:0,
operating:true,
address:'192.168.43.3',
port:"7788"
},
arrayBufferToString(arr){
var dataview=new DataView(arr);
var ints=new Uint8Array(arr.byteLength);
for(var i=0;i<ints.length;i++){
ints[i]=dataview.getUint8(i);
}
// console.log(ints)
arr=ints;
var str = '',
_arr = arr;
for(var i = 0; i < _arr.length; i++) {
var one = _arr[i].toString(2),
v = one.match(/^1+?(?=0)/);
if(v && one.length == 8) {
var bytesLength = v[0].length;
var store = _arr[i].toString(2).slice(7 - bytesLength);
for(var st = 1; st < bytesLength; st++) {
store += _arr[st + i].toString(2).slice(2);
}
str += String.fromCharCode(parseInt(store, 2));
i += bytesLength - 1;
} else {
str += String.fromCharCode(_arr[i]);
}
}
return str;
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
let that=this;
let is = wx.canIUse('createUDPSocket');
if(is){
//开启UDP监听
this.data.udpScoket = wx.createUDPSocket();
this.data.udpScoket.bind(4321)
this.data.udpScoket.onListening(function (res) {
console.log('监听中.....');
console.log(res);
})
this.data.udpScoket.onError(function(err) {
wx.showToast({
title: err.message,
})
})
this.data.udpScoket.onMessage(function(res) {
console.log(res);
let mess=that.arrayBufferToString(res.message);
console.log(mess)
})
}else{
this.showToast({
title:"当前基础库版本不支持createUDPSocket"
})
}
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady() {
// wx.startLocalServiceDiscovery({
// serviceType: '_http._tcp.',
// success: (res)=> {
// console.log(res)
// }
// })
// wx.onLocalServiceResolveFail((res)=>{
// console.log(res);
// })
// wx.onLocalServiceFound((resp)=>{
// console.log(resp);
// })
},
//向wifi模块发送数据
instructions(e){
let message=e.currentTarget.dataset.message;
console.log(message)
this.data.udpScoket.send({
address: this.data.address,
port: this.data.port,
message: message,
})
},
/**
* 生命周期函数--监听页面显示
*/
onShow() {
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide() {
},
//防抖操作 只执行最后一次
debounce(fn, delay){
let timer = null;
return function(){
clearTimeout(timer);
timer = setTimeout(()=> {
fn.apply(this, arguments);
}, delay)
}
},
//节流操作 间隔执行
throttle(fn, delay){
let that=this;
return function(){
if(that.data.operating) {
that.data.operating = false;
setTimeout(()=> {
fn.apply(this, arguments);
that.data.operating = true;
}, delay)
}
}
},
//计算斜边
maxBorder(l,t){
let leftAbs=Math.abs(l);
let topAbs=Math.abs(t);
let hypotenuse=Math.pow(leftAbs,2)+Math.pow(topAbs,2)
// console.log()
let sqrthy=Math.sqrt(hypotenuse).toFixed()
return sqrthy;
},
//节流方式向wifi模块投递命令
intervalCall(intervalTime,instruction){
this.throttle(()=>{
console.log(instruction)
this.data.udpScoket.send({
address:this.data.address,
port:this.data.port,
message:instruction
})
},intervalTime)();
},
//记录事件触发
movestart(e){
//固定坐标中心点
// console.log("========开始移动给定相对盒子的位置=======")
this.setData({
originPageX:165.30,
originPageY:187.95,
left:0,
top:0
})
},
move(e){
let tempT=e.changedTouches[0].pageY-this.data.originPageY;
let tempL=e.changedTouches[0].pageX-this.data.originPageX;
let sqrt=this.maxBorder(tempL,tempT);
// console.log(sqrt);
if(sqrt<=70){
this.setData({
left:tempL,
top:tempT
})
}else{
// console.log("========移动中的坐标=======")
let calcLeft= (70*((tempL/sqrt).toFixed(2)));
let calcTop=(70*((tempT/sqrt).toFixed(2)));
this.setData({
left:calcLeft,
top:calcTop
})
// console.log(calcLeft,calcTop)
if(calcLeft>0&&calcTop>0){
if(Math.abs(calcLeft)>=Math.abs(calcTop)){
//节流方式发布方向指令
this.intervalCall(50,"right");
}else{
this.intervalCall(50,"backward");
}
}else if(calcLeft<0&&calcTop>0){
if(Math.abs(calcLeft)>=Math.abs(calcTop)){
this.intervalCall(50,"left");
}else{
this.intervalCall(50,"backward");
}
}else if(calcLeft<0&&calcTop<0){
if(Math.abs(calcLeft)>=Math.abs(calcTop)){
this.intervalCall(50,"left");
}else{
this.intervalCall(50,"forward");
}
}else if(calcLeft>0&&calcTop<0){
if(Math.abs(calcLeft)>=Math.abs(calcTop)){
this.intervalCall(50,"right");
}else{
this.intervalCall(50,"forward");
}
}
}
},
moveend(e){
// console.log("========移动终止置位置到起点=======")
// console.log(Object.defineProperty(this.data,"oprating",{value:false}))
// console.log(this.data.oprating)
this.setData({
originPageX:165.30,
originPageY:187.95,
left:0,
top:0
})
},
//udp通信,需要wifi模块与手机接入同一个网段
UDPComunicate(){
const UDP= wx.createUDPSocket();
UDP.bind();
UDP.send({
address:this.address,
port:this.part,
message:this.message
})
},
onUnload() {
if(this.data.udpScoket){
this.data.udpScoket.close();
}
// wx.stopLocalServiceDiscovery({
// success:(res)=>{
// console.log(res);
// },
// fail:(err)=>{
// console.log(err)
// }
// })
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh() {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom() {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage() {
}
})
wifi8266代码
因为微信mDNS没有接入成功所以我把wifi和密码写死的,个人手机热点先改成这样吧,用这个小程序的都通用(滑稽~)
//程序如下:
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include <ESP8266WiFi.h>
#define ssid "wifi8266" //WIFI名字最好别包含特殊符号 不然连接不上
#define password "987654321" //WIFI密码
unsigned int localUDPPort = 7788; //本地监听端口
unsigned int remoteUDPPort = 8877; //远程监听端口
char incomingPacket[255]; // 保存Udp工具发过来的消息
WiFiUDP UDPInstance;//实例化WiFiUDP
void setup()
{
Serial.begin(115200);
delay(10);
Serial.printf("正在连接 %s ", ssid);
WiFi.begin(ssid, password);//连接到wifi
Serial.println();
while (WiFi.status() != WL_CONNECTED)//等待连接
{
delay(600);
Serial.print(".");
}
Serial.println("wifi连接成功");
//启动mdns功能
if(WiFi.status() == WL_CONNECTED) //wifi连接成功后
{
if (UDPInstance.begin(localUDPPort)) { //启动Udp监听服务
Serial.println("监听成功");
Serial.printf("现在收听IP:%s, UDP端口:%d\n", WiFi.localIP().toString().c_str(), localUDPPort); //wifi模块的ip地址在这里获取
} else {
Serial.println("监听失败");
}
}
}
void loop()
{
if (UDPInstance.parsePacket()) //解析包不为空
{
// 读取Udp数据包并存放在incomingPacket
int len = UDPInstance.read(incomingPacket, 255);//返回数据包字节数
if (len > 0)
{
incomingPacket[len] = 0;//清空缓存
//strcmp函数是string compare(字符串比较)的缩写,用于比较两个字符串并根据比较结果返回整数。
//基本形式为strcmp(str1,str2),若str1=str2,则返回零;若str1<str2,则返回负数;若str1>str2,则返回正数。
if (strcmp(incomingPacket, "forward") == 0)
{
Serial.println (incomingPacket);
}else if(strcmp(incomingPacket, "backward") == 0)
{
Serial.printf(incomingPacket);
}else if(strcmp(incomingPacket, "left") == 0)
{
Serial.printf(incomingPacket);
}else if(strcmp(incomingPacket, "right") == 0)
{
Serial.printf(incomingPacket);
}else if(strcmp(incomingPacket, "accelerate") == 0)
{
Serial.printf(incomingPacket);
}else if(strcmp(incomingPacket, "decelerate") == 0)
{
Serial.printf(incomingPacket);
}else if(strcmp(incomingPacket, "stop") == 0)
{
Serial.printf(incomingPacket);
}
else{
Serial.printf("无效命令");
}
}
}
}
//向udp工具发送消息
void sendCallBack(const char *buffer)
{
UDPInstance.beginPacket(UDPInstance.remoteIP(), remoteUDPPort);//配置远端ip地址和端口
UDPInstance.write(buffer); //把数据写入发送缓冲区
UDPInstance.endPacket(); //发送数据
}
L293D代码(叫Arduino代码我感觉更合适)
速度数值可以自己调整,电压推荐9v,6v看起来病秧秧的,模块四个端子默认的频率输出并不相同,介意的可以自己调平
··#include <AFMotor.h>
#include<SoftwareSerial.h>
SoftwareSerial softSerial(2,3);//软串口,用于接收命令
//常量
#define MAX_SPEED 255
//变量
String incomingPacket="";
String directionW="fore";
int speedSet = 0;
int currentSpend=120;
AF_DCMotor motor1(1); // 这4条语句的作用是建立4个直流电机对象,它们的名称分别是:motor1/2/3/4.
AF_DCMotor motor2(2);
AF_DCMotor motor3(3);
AF_DCMotor motor4(4);
void setup() {
delay(100);
//读取wifi模块参数作为指令
softSerial.begin(115200);
Serial.begin(115200);
softSerial.listen();
motor1.setSpeed(120);
motor2.setSpeed(120);
motor3.setSpeed(120);
motor4.setSpeed(120);
motor1.run(RELEASE); // 这4条语句的作用是让4个电机在启动时停止转动
motor2.run(RELEASE);
motor3.run(RELEASE);
motor4.run(RELEASE);
}
//执行流程 指令->微信小程序UDP->wifi8266->Arduino->驱动电机动作
void loop() {
//循环接收指令
if(softSerial.available()>0){
incomingPacket=softSerial.readString();
Serial.println(incomingPacket);
if (incomingPacket== "forward")
{
moveForward();
}else if(incomingPacket== "backward")
{
moveBackward();
}else if(incomingPacket=="left")
{
turnLeft();
}else if(incomingPacket=="right")
{
turnRight();
}else if(incomingPacket== "accelerate")
{
accelerate();
}else if(incomingPacket== "decelerate")
{
decelerate();
}else if(incomingPacket== "stop")
{
moveStop();
}
else{
// Serial.print("未匹配到该指令");
}
}
}
//以下为对应操作指令执行的函数
//加速
void accelerate(){
//这里不修改运动的方向,只做加速
if(currentSpend<=MAX_SPEED-50){
currentSpend+=50;
motor1.setSpeed(currentSpend);
motor2.setSpeed(currentSpend);
motor3.setSpeed(currentSpend);
motor4.setSpeed(currentSpend);
}else{
currentSpend=255;
motor1.setSpeed(currentSpend);
motor2.setSpeed(currentSpend);
motor3.setSpeed(currentSpend);
motor4.setSpeed(currentSpend);
}
}
//减速
void decelerate(){
if(currentSpend>=50){
currentSpend-=50;
motor1.setSpeed(currentSpend);
motor2.setSpeed(currentSpend);
motor3.setSpeed(currentSpend);
motor4.setSpeed(currentSpend);
}else{
currentSpend=0;
motor1.setSpeed(currentSpend);
motor2.setSpeed(currentSpend);
motor3.setSpeed(currentSpend);
motor4.setSpeed(currentSpend);
}
}
//停止
void moveStop() {
motor1.run(RELEASE);
motor2.run(RELEASE);
motor3.run(RELEASE);
motor4.run(RELEASE);
currentSpend=120;
}
//向前
void moveForward() {
delay(100);
//防止电压值过低无法工作
currentSpend=120;
motor1.run(FORWARD);
motor2.run(FORWARD);
motor3.run(FORWARD);
motor4.run(FORWARD);
motor1.setSpeed(currentSpend);
motor2.setSpeed(currentSpend);
motor3.setSpeed(currentSpend);
motor4.setSpeed(currentSpend);
//记录当前的方向,用于转向后的方向保持
directionW=="fore" ;
}
//后退
void moveBackward() {
//防止电压值过低无法工作
currentSpend=120;
//方向
motor1.run(BACKWARD);
motor2.run(BACKWARD);
motor3.run(BACKWARD);
motor4.run(BACKWARD);
//速度
motor1.setSpeed(currentSpend);
motor2.setSpeed(currentSpend);
motor3.setSpeed(currentSpend);
motor4.setSpeed(currentSpend);
//记录当前的方向,用于转向后的方向保持
directionW=="back";
}
//右转
void turnRight() {
if(directionW=="back")
{
motor1.run(BACKWARD);
motor2.run(BACKWARD);
motor3.run(FORWARD);
motor4.run(FORWARD);
delay(300);
motor1.run(BACKWARD);
motor2.run(BACKWARD);
motor3.run(BACKWARD);
motor4.run(BACKWARD);
}else if(directionW=="fore"){
motor1.run(FORWARD);
motor2.run(FORWARD);
motor3.run(BACKWARD);
motor4.run(BACKWARD);
delay(300);
motor1.run(FORWARD);
motor2.run(FORWARD);
motor3.run(FORWARD);
motor4.run(FORWARD);
}else{
}
motor1.setSpeed(120);
motor2.setSpeed(120);
motor3.setSpeed(120);
motor4.setSpeed(120);
}
//左转
void turnLeft() {
if(directionW=="back")
{
motor1.run(FORWARD);
motor2.run(FORWARD);
motor3.run(BACKWARD);
motor4.run(BACKWARD);
delay(300);
motor1.run(BACKWARD);
motor2.run(BACKWARD);
motor3.run(BACKWARD);
motor4.run(BACKWARD);
}else if(directionW=="fore"){
motor1.run(BACKWARD);
motor2.run(BACKWARD);
motor3.run(FORWARD);
motor4.run(FORWARD);
delay(300);
motor1.run(FORWARD);
motor2.run(FORWARD);
motor3.run(FORWARD);
motor4.run(FORWARD);
}else{
}
motor1.setSpeed(120);
motor2.setSpeed(120);
motor3.setSpeed(120);
motor4.setSpeed(120);
}
核心的东西差不多就这些,讲一下需要注意的一些地方,烧录的时候记得把9v去掉或者把跳线拔掉二选一,否则烧板子,还有就是wifi模块工作电压3.3v,这个是真坑啊,扩展板唯一输出3.3v的就是跳线针脚那里,5v降压或者9v降压不推荐,太费劲,我直接接的5v,工作时会有wifi模块发烫的问题,希望它能坚持一段时间,还有就是接线,wifi模块的TX引脚接l293d上那个空的针脚,也就是2号,作为串口通信使用,3号针脚要是要向wifi模块回送数据也可以接(但是真的难焊,毕竟封死了,滑稽~),关于wifi8266模块(也就是esp-01)烧录相关的问题,可以看我开头说的那篇文章,最后上个整机图,快乐就是这么朴实无华文章来源:https://www.toymoban.com/news/detail-493649.html
文章来源地址https://www.toymoban.com/news/detail-493649.html
到了这里,关于微信小程序+UDP+wifi8266+l293d扩展板+Arduino uno遥控小车的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!