基于arduino与esp32的机械臂小车

        首先声明本项目由本人与另一位博主合作完成,我们都是大一初学者,文章中或多或少会有一些错误,还望各位前辈指正,废话不多说,直入主题。

目录

一、材料清单

二、机械臂篇

(1)机械臂组装

(2)机械臂与arduino开发板的连接

(3)机械臂控制代码

二、小车篇

(1)小车组装

(2)小车与开发板连接

(3)L298N模块科普

(3.1)控制原理

(3.2)直接给arduino开发板供电

(4)控制小车代码展示

四、esp32控制


一、材料清单

arduino uno开发板

L298N电机驱动板

双层4WD四驱小车(含直流驱动电机)

两个18650锂电池和电池盒(别多了)

机械臂套装

若干杜邦线与面包板

充电宝

二、机械臂篇

(1)机械臂组装

        由于购买的商家的不同,机械臂的部分结构也会不一样,组装步骤同样也会不同,所以在此不跟大家讲解组装的步骤,建议大家直接向商家问组装视频,会方便很多, 这里博主用的是一个简单的四轴机械臂,图片如下:

(2)机械臂与arduino开发板的连接

        该部分本人是跟着哔哩哔哩up主“太极创客”学习的,连接图如下:

(3)机械臂控制代码

        建议根据自己的实际情况酌情修改

//机械臂控制
void servoCmd(char servoName, int toPos, int servoDelay){  
  Servo servo2go;  //创建servo对象
   
  int fromPos; //建立变量,存储电机起始运动角度值
   
  switch(servoName){
    case 'b':
      toPos = map(toPos, 0, 63, 0, 180);//与之后esp32使用16进制控制有关,之后会提到,之后的也是
      if(toPos >= baseMin && toPos <= baseMax){
        servo2go = base;
        break;
      } else {
        Serial.println("+Warning: Base Servo Value Out Of Limit!");
        return;
      }
  
    case 'c':
      if(toPos >= clawMin && toPos <= clawMax){    
        servo2go = claw;
        break;
      } else {
        Serial.println("+Warning: Claw Servo Value Out Of Limit!");
        return;        
      }
 
    case 'f':
      toPos = map(toPos, 0, 63, 35, 120);
      if(toPos >= fArmMin && toPos <= fArmMax){
        servo2go = fArm;
        break;
      } else {
        Serial.println("+Warning: fArm Servo Value Out Of Limit!");
        return;
      }
          
    case 'r':
      toPos = map(toPos, 0, 63, 45, 180);
      if(toPos >= rArmMin && toPos <= rArmMax){
        servo2go = rArm;
        break;
      } else {
        Serial.println("+Warning: rArm Servo Value Out Of Limit!");
        return;
      }      
  }
  
  servo2go.write(toPos);
}

二、小车篇

(1)小车组装

        与机械臂处的组装雷同。

(2)小车与开发板连接

        首先,我们先利用L298N模块实现电机的正转、反转,从而驱动小车实现前进、后退及转向

(本来打算AFMotor电机驱动板来实现的,但是由于当时没意识到电压问题,用了四个18650锂电池,把电机烧爆了,有空的铁子们可以试一下)

        因为只有两个马达输出口,想控制四个轮子,着需要将一边的两个轮子接到同一个输出口,大致如下(图片来自@南渊_):

需要注意的是,若在之后运行时出现同一边轮子转动方向相反,可能是两个电机的接线错位了。

(3)L298N模块科普

(3.1)控制原理

电机

运动状态

IN1

IN2

IN3

IN4

电机A

正转

/

/

反转

/

/

停止

/

/

电机B

正转

/

/

反转

/

/

停止

/

/

(3.2)直接给arduino开发板供电

        将模块的5V供电口接到arduino板的5V接口,同时使arduino的GND与模块GND口相接,可以实现单独供电,也要注意用两块18650锂电池即可,注意不要使用南孚电池,因为南孚电池电流过大会导致arduino开发板不定时启动,而且四个南孚电池的电压都不如两个18650来的实在。

(4)控制小车代码展示

        个人建议大家对照的已经连好线路后的车子运行,自己修改一下“HIGH"与“LOW”在各个引脚的情况

int l1=A0;
int l2=A1;
int r1=A2;
int r2=A3;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);

  pinMode(r1,OUTPUT);
  pinMode(r2,OUTPUT);
  pinMode(l1,OUTPUT);
  pinMode(l2,OUTPUT);
}
//向前
void forward()
{
  digitalWrite(l1,LOW);
  digitalWrite(l2,HIGH);
  digitalWrite(r1,LOW);
  digitalWrite(r2,HIGH);
}
//后退
void back()
{
  digitalWrite(l1,HIGH);
  digitalWrite(l2,LOW);
  digitalWrite(r1,HIGH);
  digitalWrite(r2,LOW);
}
//左转
void left()
{
  digitalWrite(l1,HIGH);
  digitalWrite(l2,LOW);
  digitalWrite(r1,LOW);
  digitalWrite(r2,HIGH);
  delay(250);//为了只转动一定的角度,过0.25秒后停止转动,可以自己再调整
  digitalWrite(l1,LOW);
  digitalWrite(l2,LOW);
  digitalWrite(r1,LOW);
  digitalWrite(r2,LOW);
  return ;
}
//右转
void right()
{
  digitalWrite(l1,LOW);
  digitalWrite(l2,HIGH);
  digitalWrite(r1,HIGH);
  digitalWrite(r2,LOW);
  delay(250);
  digitalWrite(l1,LOW);
  digitalWrite(l2,LOW);
  digitalWrite(r1,LOW);
  digitalWrite(r2,LOW);
  return ;
}
//停止
void _stop()
{
  digitalWrite(l1,LOW);
  digitalWrite(l2,LOW);
  digitalWrite(r1,LOW);
  digitalWrite(r2,LOW);
}

四、esp32控制

(1)控制指令

        控制小车与机械臂一共有9种指令

(2)mqtt简介

  • 基于Publish/Subscribe(发布订阅)模式的物联网通信协议
  • 简单易实现
  • 支持Qos(服务质量)
  • 报文精简
  • 基于TCP/IP

(3)mqtt服务端搭建

        本次搭建mqtt服务端的服务器系统为Centos7.3

        下面为数据传输时的格式(均为二进制)

前进后退左转右转停止
00000110
00000011
00000010
00000001
00000100

 

大臂小臂底盘钳子
010xxxxx
10xxxxx
11xxxxx
00000101


        1、添加 EPEL 软件库

yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm


        2、安装 Mosquitto

yum install mosquitto


        3、配置Mosquitto
        设置用户名和密码
        创建一个Mosquitto将用于验证连接的密码文件。使用mosquitto_passwd来创建这个文件

sudo mosquitto_passwd -c /etc/mosquitto/passwd your-username


        your-username:你自己设置的账号名
        输入指令后,系统要求输入两次密码。
        编辑Mosquitto配置文件:
        进入目录:/etc/mosquitto,编辑mosquitto.conf,具体内容请参考文件内说明(全英文)。
        4、运行Mosquitto

systemctl start mosquitto.service

 (4)基于esp32实现mqtt协议

#include <WiFi.h>
#include <PubSubClient.h>
const char* ssid     = "wifi名称";
const char* password = "wifi密码";


#define JDQ 16


const char* MQTT_SERVER  = "服务器公网IP";
const int   MQTT_PORT    = 1883;   //mqtt服务开放的端口
const char* MQTT_USRNAME = "用户名";
const char* MQTT_PASSWD  = "密码";
const char* TOPIC = "car";         //订阅的频道
const char* CLIENT_ID    = "scy-mqtt-client";  //当前设备的clientid标志

WiFiClient espClient;
PubSubClient  client(espClient);
long lastMsg = 0;


void setup()
{
  Serial.begin(9600);                    
  delay(10);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }

  pinMode(JDQ, OUTPUT);
  client.setServer(MQTT_SERVER, MQTT_PORT); //设定MQTT服务器与使用的端口,1883是默认的MQTT端口
  client.setCallback(callback);        //设定回调方式,当ESP8266收到订阅消息时会调用此方法
}

int value = 0;

void reconnect() {
  while (!client.connected()) {
    if (client.connect(CLIENT_ID,MQTT_USRNAME,MQTT_PASSWD)) {
      // 连接成功时订阅主题
      client.subscribe(TOPIC);
    } else {
      delay(5000);
    }
  }
}

void callback(char* topic, byte* payload, unsigned int length) {
  for (int i = 0; i < length; i++) {
    Serial.write(payload[i]); // 通过串口向arduino传递指令
  }
}


void loop()
{
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
}

(5)基于安卓app实现软件操控并用mqtt协议通信

         页面布局如下

        xml代码如下

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:visibility="visible"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="66dp"
        android:layout_height="57dp"
        android:layout_marginEnd="68dp"
        android:layout_marginBottom="9dp"
        android:text="前进"
        app:layout_constraintBottom_toTopOf="@+id/button2"
        app:layout_constraintEnd_toEndOf="@+id/button3" />

    <Button
        android:id="@+id/button5"
        android:layout_width="66dp"
        android:layout_height="57dp"
        android:layout_marginStart="104dp"
        android:layout_marginBottom="104dp"
        android:text="停止"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/button2"
        android:layout_width="66dp"
        android:layout_height="57dp"
        android:layout_marginStart="32dp"
        android:layout_marginBottom="104dp"
        android:text="左转"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/button3"
        android:layout_width="66dp"
        android:layout_height="57dp"
        android:layout_marginStart="72dp"
        android:layout_marginBottom="5dp"
        android:text="右转"
        app:layout_constraintBottom_toTopOf="@+id/button4"
        app:layout_constraintStart_toStartOf="@+id/button4" />

    <Button
        android:id="@+id/button4"
        android:layout_width="66dp"
        android:layout_height="57dp"
        android:layout_marginStart="72dp"
        android:layout_marginTop="8dp"
        android:text="后退"
        app:layout_constraintStart_toStartOf="@+id/button2"
        app:layout_constraintTop_toBottomOf="@+id/button2" />

    <Button
        android:id="@+id/button11"
        android:layout_width="79dp"
        android:layout_height="56dp"
        android:layout_marginEnd="180dp"
        android:layout_marginBottom="220dp"
        android:text="关"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <TextView
        android:id="@+id/textView12"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="208dp"
        android:layout_marginBottom="144dp"
        android:text="小臂"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <SeekBar
        android:id="@+id/seekBar15"
        android:layout_width="269dp"
        android:layout_height="30dp"
        android:max="63"
        android:progress="32"
        android:layout_marginEnd="88dp"
        android:layout_marginBottom="104dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <TextView
        android:id="@+id/textView14"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="208dp"
        android:layout_marginBottom="20dp"
        android:text="底座"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <SeekBar
        android:id="@+id/seekBar16"
        android:layout_width="269dp"
        android:max="63"
        android:progress="32"
        android:layout_height="30dp"
        android:layout_marginEnd="88dp"
        android:layout_marginBottom="164dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <SeekBar
        android:id="@+id/seekBar14"
        android:layout_width="269dp"
        android:max="63"
        android:progress="32"
        android:layout_height="30dp"
        android:layout_marginEnd="88dp"
        android:layout_marginBottom="44dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

    <TextView
        android:id="@+id/textView13"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="208dp"
        android:layout_marginBottom="80dp"
        android:text="大臂"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

本实例使用了 org.eclipse.paho.client.mqttv3-1.2.0.jar 包作为mqtt通讯模块

MainActivity代码如下

package com.example.testapp1;

import androidx.appcompat.app.AppCompatActivity;

import android.annotation.SuppressLint;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.SeekBar;
import android.widget.Toast;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

import java.util.HashMap;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class MainActivity extends AppCompatActivity {
    private Button Button_forward;
    private Button Button_back;
    private Button Button_left;
    private Button Button_right;
    private Button Button_stop;
    private Button Button_18dong;  //夹子
    private SeekBar sb_base;
    private SeekBar sb_big;
    private SeekBar sb_small;
    private Handler handler;
    private MqttClient client;
    private MqttConnectOptions options;
    private ScheduledExecutorService scheduler;
    //HashMap<Integer, Integer> sb_value = new HashMap<Integer, Integer>();

    @SuppressLint("HandlerLeak")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //Toast.makeText(this, "hello sss", Toast.LENGTH_LONG).show();
        Object_init();
        Mqtt_init();
        startReconnect();
//        Mqtt_connect();
        //Bt(R.id.button);

        handler = new Handler() {
            @SuppressLint("SetTextI18n")
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                MqttMessage message = new MqttMessage();
                byte[] data = new byte[1];
                switch (msg.what){
                    case 1: //开机校验更新回传
                        //Toast.makeText(MainActivity.this, String.valueOf(msg.obj), Toast.LENGTH_SHORT).show();
                        try {
                            data[0] = (byte) Integer.valueOf(msg.obj.toString()).intValue();
                            message.setPayload(data);
                            client.publish("car", message);
                        } catch (MqttException e) {
                            Toast.makeText(MainActivity.this, e.toString(), Toast.LENGTH_SHORT).show();
                            System.out.println(e.toString());
                            Log.e("aaaaa", e.toString());
                            throw new RuntimeException(e);
                        }
                        break;
                    case 31:
                        Toast.makeText(MainActivity.this, String.valueOf(msg.what), Toast.LENGTH_SHORT).show();
                        break;
                    case 30:
                        Toast.makeText(MainActivity.this, String.valueOf(msg.what), Toast.LENGTH_SHORT).show();
                        break;

                }
            }
        };
    }

    private void Object_init(){
        Bt(R.id.button4);
        Bt(R.id.button2);
        Bt(R.id.button3);
        Bt(R.id.button5);
        Bt(R.id.button11);
        Sb(R.id.seekBar14);
        Sb(R.id.seekBar15);
        Sb(R.id.seekBar16);
    }
    private void Bt(int ID){
        Button bt = findViewById(ID);
//        setContentView(R.layout.activity_main);
        bt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Message msg = new Message();
                msg.what = 1;
                if (ID == R.id.button){
//                    publishmessageplus("car","{\"set_led\":1}");
                    msg.obj = 0b00000110;
                }
                else if (ID == R.id.button3){
                    msg.obj = 0b00000001;
                }
                else if (ID == R.id.button2){
                    msg.obj = 0b00000010;
                }
                else if (ID == R.id.button4){
                    msg.obj = 0b00000011;
                }
                else if (ID == R.id.button5){
                    msg.obj = 0b00000100;
                }
                else if (ID == R.id.button11){
                    if (bt.getText() == "开"){
                        bt.setText("关");
                    }
                    else{
                        bt.setText("开");
                    }
                    msg.obj = 0b00000101;
                }
                handler.sendMessage(msg);
            }
        });
    }
    private void Sb(int ID){
        SeekBar sb = findViewById(ID);
        sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            private byte num;
            @Override
            public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
                num = (byte) i;
            }
            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
                //Toast.makeText(MainActivity.this, String.valueOf(ID), Toast.LENGTH_SHORT).show();
                //System.out.println("hello");
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
                Message msg = new Message();
                if (ID == R.id.seekBar14){
                    msg.what = 1;
                    msg.obj = num | 0b01000000;
                }
                else if(ID == R.id.seekBar15){
                    msg.what = 1;
                    msg.obj = num | 0b10000000;
                }
                else if(ID == R.id.seekBar16){
                    msg.what = 1;
                    msg.obj = num | 0b11000000;
                }
                handler.sendMessage(msg);
            }
        });
    }
    private void Mqtt_init()
    {
        try {
            //host为主机名,test为clientid即连接MQTT的客户端ID,一般以客户端唯一标识符表示,MemoryPersistence设置clientid的保存形式,默认为以内存保存
            client = new MqttClient("tcp://您服务器的公网IP:服务器上mqtt服务所在的端口", String.valueOf(System.currentTimeMillis()),
                    new MemoryPersistence());
            //MQTT的连接设置
            options = new MqttConnectOptions();
            //设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接
            options.setCleanSession(false);
            //设置连接的用户名
            options.setUserName("您的用户名");
            //设置连接的密码
            options.setPassword("您的密码".toCharArray());
            // 设置超时时间 单位为秒
            options.setConnectionTimeout(10);
            // 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
            options.setKeepAliveInterval(20);
            //设置回调
            client.setCallback(new MqttCallback() {
                @Override
                public void connectionLost(Throwable cause) {
                    //连接丢失后,一般在这里面进行重连
                    System.out.println("connectionLost----------");
                    //startReconnect();
                }
                @Override
                public void deliveryComplete(IMqttDeliveryToken token) {
                    //publish后会执行到这里
                    System.out.println("deliveryComplete---------"
                            + token.isComplete());
                }
                @Override
                public void messageArrived(String topicName, MqttMessage message)
                        throws Exception {
                    //subscribe后得到的消息会执行到这里面
                    System.out.println("messageArrived----------");
                    Message msg = new Message();
                    //封装message包
                    msg.what = 3;   //收到消息标志位
                    msg.obj = topicName + "---" + message.toString();
                    //发送messge到handler
                    handler.sendMessage(msg);    // hander 回传
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    private void Mqtt_connect() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    if(!(client.isConnected()) )  //如果还未连接
                    {
                        client.connect(options);
                        Message msg = new Message();
                        msg.what = 31;
                        // 没有用到obj字段
                        handler.sendMessage(msg);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    Message msg = new Message();
                    msg.what = 30;
                    // 没有用到obj字段
                    handler.sendMessage(msg);
                }
            }
        }).start();
    }
    private void startReconnect() {
        scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                if (!client.isConnected()) {
                    Mqtt_connect();
                }
            }
        }, 0 * 1000, 10 * 1000, TimeUnit.MILLISECONDS);
    }

    private void publishmessageplus(String topic,String message2)
    {
        if (client == null || !client.isConnected()) {
            Toast.makeText(MainActivity.this, "client == null || !client.isConnected()", Toast.LENGTH_SHORT).show();
            return;
        }
        MqttMessage message = new MqttMessage();
        message.setPayload(message2.getBytes());
        try {
            client.publish(topic,message);
        } catch (MqttException e) {
            Toast.makeText(MainActivity.this, e.toString(), Toast.LENGTH_SHORT).show();
            e.printStackTrace();
        }
    }
}

最后AndroidManifest.xml文件为

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Testapp1"
        tools:targetApi="31">
        <service android:name="org.eclipse.paho.android.service.MqttService"/>
        <activity
            android:name=".MainActivity"
            android:screenOrientation="landscape"
            android:configChanges="orientation|keyboardHidden|screenSize"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值