2WD远程控制小车——程序设计

1. 简介

本文主要包括ESP32开发板的程序和ESP8266开发板的程序。涉及到的内容有OLED显示、DHT22的数据采集、ADC电压测量、TB6612驱动和PS2接收。

2. 电路连接

控制板ESP32的主要电路连接如下图所示:
在这里插入图片描述
遥控器ESP8266的主要电路连接如下图所示:
在这里插入图片描述

3. 软件设计

3.1 OLED显示

OLED显示的示例代码如下:

#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>

U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ 22, /* data=*/ 21, /* reset=*/ U8X8_PIN_NONE);   // All Boards without Reset of the Display

void setup(void) {
  u8g2.begin();
}

void loop(void) {
  u8g2.clearBuffer();         // clear the internal memory
  u8g2.setFont(u8g2_font_ncenB08_tr); // choose a suitable font
  u8g2.drawStr(0,10,"Hello SSD1306!");  // write something to the internal memory
  u8g2.sendBuffer();          // transfer internal memory to the display
  delay(1000);  
}

为了方便显示,定义了一个显示函数:

char str1[22];
char str2[22];
char str3[22];
char str4[22];
char str5[22];
...
void drawoled(char * str1, char* str2, char* str3, char* str4, char* str5)
{
	// Need 104 ms to run this function
	// Serial.printf("0 millis()=%d \n", millis());
	u8g2.setFont(u8g2_font_ncenB08_tr); // font size is 8x6
	u8g2.clearBuffer();  // clear the internal memory
	u8g2.setCursor(0, 12);
	u8g2.print(str1);
	u8g2.setCursor(0, 24);
	u8g2.print(str2);
	u8g2.setCursor(0, 36);
	u8g2.print(str3);
	u8g2.setCursor(0, 48);
	u8g2.print(str4);
	u8g2.setCursor(0, 60);
	u8g2.print(str5);
	u8g2.sendBuffer();  // transfer internal memory to the 
	// Serial.printf("1 millis()=%d \n", millis());
}

str1等可以通过如下语句进行赋值后操作:

sprintf(str1, "msgid:%d t:%d", msgid, millis());

3.2 DHT22

部分代码:

#include "DHT.h"
...
#define DHTPIN 17  // DHT22
#define DHTTYPE DHT22 // DHT型号
...
// 初始化DHT传感器
DHT dht(DHTPIN, DHTTYPE);
...
void mqttIntervalPost()
{
	// Reading temperature or humidity takes about 250 milliseconds!
	// Sensor readings may also be up to 2 seconds 'old' (its a very slow sensor)
	h = dht.readHumidity();
	// Read temperature as Celsius (the default)
	t = dht.readTemperature();
	// Read temperature as Fahrenheit (isFahrenheit = true)
	f = dht.readTemperature(true);

	// Check if any reads failed and exit early (to try again).
	if (isnan(h) || isnan(t) || isnan(f)) {
		Serial.println(F("Failed to read from DHT sensor!"));
		return;
	}

	// Compute heat index in Fahrenheit (the default)
	float hif = dht.computeHeatIndex(f, h);
	// Compute heat index in Celsius (isFahreheit = false)
	float hic = dht.computeHeatIndex(t, h, false);
	...
}

3.3 ADC电压测量

在主控板上,电池分压后的测试点接入ESP32的GPIO36。
部分代码如下:

#define ANALOG_PIN_0  36
...
vol_value = analogRead(ANALOG_PIN_0);
vol = vol_value*0.003630;
Serial.printf("ADC on Pin(%d)=%d, vol:%.2f\n",ANALOG_PIN_0,vol_value, vol);

3.4 TB6612驱动

TB6612的PWM输入接ESP32的GPIO16,部分代码如下:

int freq = 2000;    // 频率
int channel = 0;    // 通道
int resolution = 8;   // 分辨率
uint16_t speed = 1;
const int pwmpin = 16;
const int AIN2 = 4;
const int AIN1 = 0;
const int BIN1 = 2;
const int BIN2 = 15;
...
void motorInit()
{	
	pinMode(AIN2, OUTPUT);
	pinMode(AIN1, OUTPUT);
	pinMode(BIN1, OUTPUT);
	pinMode(BIN2, OUTPUT);
	digitalWrite(AIN2, LOW);
	digitalWrite(AIN1, LOW);
	digitalWrite(BIN1, LOW);
	digitalWrite(BIN2, LOW);
	
	ledcSetup(channel, freq, resolution); // 设置通道
	ledcAttachPin(pwmpin, channel);  // 将通道与对应的引脚连接
	ledcWrite(channel, speed*80);  // 输出PWM
}

3.5 PS2接收

在遥控器端,ESP8266的SPI端口与PS2接收器相连,需要安装PS2X库才可以进行开发。部分代码如下:

#include <PS2X_lib.h>  //for v1.6
...
/******************************************************************
 * select modes of PS2 controller:
 *   - pressures = analog reading of push-butttons 
 *   - rumble    = motor rumbling
 * uncomment 1 of the lines for each mode selection
 ******************************************************************/
 //These are our button constants
#define CPSB_SELECT      1
#define CPSB_L3          2
#define CPSB_R3          3
#define CPSB_START       4
#define CPSB_PAD_UP      5
#define CPSB_PAD_RIGHT   6
#define CPSB_PAD_DOWN    7
#define CPSB_PAD_LEFT    8
#define CPSB_L2          9
#define CPSB_R2          10
#define CPSB_L1          11
#define CPSB_R1          12
#define CPSB_GREEN       13
#define CPSB_RED         14
#define CPSB_BLUE        15
#define CPSB_PINK        16

#define CPSB_TRIANGLE    13
#define CPSB_CIRCLE      14
#define CPSB_CROSS       15
#define CPSB_SQUARE      16

#define pressures   false
#define rumble      false
#define PS2_DAT        19  //MISO  19
#define PS2_CMD        23  //MOSI  23
#define PS2_SEL         5  //SS     5
#define PS2_CLK        18  //SLK   18
PS2X ps2x; // create PS2 Controller Class
//right now, the library does NOT support hot pluggable controllers, meaning 
//you must always either restart your Arduino after you connect the controller, 
//or call config_gamepad(pins) again after connecting the controller.
int error = -1;
byte type = 0;
byte vibrate = 0;
int tryNum = 1;
...
void ps2Init()
{
	
  //added delay to give wireless ps2 module some time to startup, before configuring it
  //CHANGES for v1.6 HERE!!! **************PAY ATTENTION*************
  
  while (error != 0) {
    delay(1000);// 1 second wait
    //setup pins and settings: GamePad(clock, command, attention, data, Pressures?, Rumble?) check for error
    error = ps2x.config_gamepad(PS2_CLK, PS2_CMD, PS2_SEL, PS2_DAT, pressures, rumble);
    Serial.print("#try config ");
    Serial.println(tryNum);
    tryNum ++;
  }
  
  Serial.println(ps2x.Analog(1), HEX);
  
  type = ps2x.readType(); 
  switch(type) {
    case 0:
      Serial.println(" Unknown Controller type found ");
      break;
    case 1:
      Serial.println(" DualShock Controller found ");
      break;
    case 2:
      Serial.println(" GuitarHero Controller found ");
      break;
    case 3:
      Serial.println(" Wireless Sony DualShock Controller found ");
      break;
   }
}

void ps2Decode()
{
	ps2x.read_gamepad(false, vibrate); 
	if(ps2x.ButtonPressed(PSB_BLUE))
		motorMove(2);
	if(ps2x.ButtonPressed(PSB_PINK))
		motorMove(4);
	if(ps2x.ButtonPressed(PSB_RED))
		motorMove(6);
	if(ps2x.ButtonPressed(PSB_GREEN))
		motorMove(8);
	if(ps2x.ButtonPressed(PSB_R1))
		motorMove(9);
	if(ps2x.ButtonPressed(PSB_R2))
		motorMove(3);
}

每隔一定时间,需要读取PS2接收器的缓冲器,也即ps2Decode

4. 阿里云物联网平台相关

无论是主控板上的ESP32还是遥控板上的ESP8266,都接入了阿里云物联网。ESP32/ESP8266首先需要连接上WIFI,然后登录MQTT,再才能上传或获取相应数据。

下面是部分代码:

#include <WiFi.h>
#include <ArduinoJson.h>//json包
#include <PubSubClient.h>
...//wifi信息
#define WIFI_SSID "id"
#define WIFI_PASSWD "pws"
char* id="id";   //定义两个字符串指针常量
char* psw="pws";
...
//阿里云设备三元组,能够唯一确定设备
#define PRODUCT_KEY "a1***"
#define DEVICE_NAME "CAR2WD-ESP32"
#define DEVICE_SECRET "******"
//地区
#define REGION_ID "cn-shanghai"
/* 线上环境域名和端口号,不需要改 */
#define MQTT_SERVER       PRODUCT_KEY ".iot-as-mqtt." REGION_ID ".aliyuncs.com"
#define MQTT_PORT         1883
#define MQTT_USRNAME      DEVICE_NAME "&" PRODUCT_KEY
#define CLIENT_ID         "CAR2WD-ESP32|securemode=3,signmethod=hmacsha1|"
#define MQTT_PASSWD       "******"

#define ALINK_PDATA_FORMAT  "{\"id\":%d,\"version\":\"1.0.0\",\"method\":\"user.p_data\",\"params\":%s}"
#define ALINK_TOPIC_PDATA   "/" PRODUCT_KEY "/" DEVICE_NAME "/user/p_data"

#define ALINK_POST_FORMAT   "{\"id\":%d,\"version\":\"1.0.0\",\"method\":\"thing.event.property.post\",\"params\":%s}"
#define ALINK_TOPIC_POST     "/sys/" PRODUCT_KEY "/" DEVICE_NAME "/thing/event/property/post"

static WiFiClient espClient;
PubSubClient  client(espClient);
...
//初始化wifi信息
const char* ip;
const char* ss = "0";
void wifiInit(char *ssid, char *passphrase)
{
    WiFi.begin(ssid, passphrase);
    delay(3000);    
	
	// 不论WIFI是否连上WiFi.status()都可能为255(WL_NO_SHIELD)
	// 所以需要查看IP值
	ip = WiFi.localIP().toString().c_str();
	while (WiFi.status()!= WL_CONNECTED) {
        WiFi.begin(ssid, passphrase);
		delay(3000);		
		
		sprintf(str2, "WIFI status: %d %d", WiFi.status(), WL_CONNECTED);
		sprintf(str3, "time:%d", millis());
		drawoled("Connecting to WIFI...", str2, str3);
		
		Serial.printf("Connecting to WiFi... %d %d \n",WiFi.status(),WL_CONNECTED);
		if(ip[0] != ss[0])
			break;
	}
	sprintf(str2, "IP: %s", (char *)(WiFi.localIP().toString().c_str()));
	sprintf(str3, "Gateway: %s", (char *)(WiFi.gatewayIP().toString().c_str()));
	drawoled("WIFI connected!", str2, str3, "Connecting to MQTT...","");
	
    Serial.println("Connected to AP");
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());    
    Serial.print("Gateway IP address: ");
    Serial.println(WiFi.gatewayIP());
	
    Serial.print("espClient [");
    client.setServer(MQTT_SERVER, MQTT_PORT);  /* 连接WiFi之后,连接MQTT服务器 */
    client.setCallback(callback);
}

void WIFICheckConnect()
{
	while (WiFi.status()!= WL_CONNECTED) {
        WiFi.begin(WIFI_SSID, WIFI_PASSWD);
		delay(3000);
		ip = WiFi.localIP().toString().c_str();	
		
		sprintf(str2, "WIFI status: %d %d", WiFi.status(), WL_CONNECTED);
		sprintf(str3, "time:%d", millis());
		drawoled("Connecting to WIFI...", str2, str3);
		
		Serial.printf("Connecting to WiFi... %d %d \n",WiFi.status(),WL_CONNECTED);
		if(ip[0] != ss[0])
			break;
	}
}

void mqttCheckConnect()
{
    while (!client.connected())
    {
        Serial.println("Connecting to MQTT Server ...");
        if (client.connect(CLIENT_ID, MQTT_USRNAME, MQTT_PASSWD))
        {
            Serial.println("MQTT Connected!");
        }
        else
        {
            sprintf(str1, "msgid:%d t:%d", msgid, millis());
            sprintf(str2, "WIFI status: %d %d", WiFi.status(), WL_CONNECTED);
            sprintf(str3, "time:%d", millis());
            sprintf(str4, "MQTT Connect err: %d!", client.state());	
            drawoled(str1,str2,str3,str4);

            Serial.print("MQTT Connect err:");
            Serial.println(client.state());
            delay(5000);
        }
    }
}
...

void mqttIntervalPost()
{  
	...
	char param[64];
	char jsonBuf[128];
	boolean d;
	// Data to ps2ctrl-esp8266
	sprintf(param, "{\"BatteryVoltage\":%.2f}", vol);
	sprintf(jsonBuf, ALINK_PDATA_FORMAT, msgid, param);
	d = client.publish(ALINK_TOPIC_PDATA, jsonBuf);
	Serial.println(jsonBuf);
	Serial.println(d ? "publish:成功" : "publish:失败");
	// Data to Aliyun IOT
	sprintf(param, "{\"BatteryVoltage\":%.2f, \"Humidity\":%.1f, \"Temperature\":%.1f}", vol, h, t);
	sprintf(jsonBuf, ALINK_POST_FORMAT, msgid, param);
	d = client.publish(ALINK_TOPIC_POST, jsonBuf);
	Serial.println(jsonBuf);
	Serial.println(d ? "publish:成功" : "publish:失败");
	...
}

StaticJsonDocument<200> doc;
void callback(char *topic, byte *payload, unsigned int length)
{
    Serial.print("Message arrived [");
    Serial.print(topic);
    Serial.print("] ");
    payload[length] = '\0';
    Serial.println((char *)payload);

    DeserializationError error = deserializeJson(doc, (char *)payload);
  
    if (!error) //检查反序列化是否成功
    {
        // Read JSON 
        int code = doc["code"];	
		if(code!=0)  // code: =0, empty;
			return;	
		
        int cmdword = doc["params"]["cmdword"];		
		if(cmdword!=0)  // cmdword: =0, empty;
			motorMove(cmdword);
    }
}
...
unsigned long lastMs = 0;
void loop(){              
	if (millis() - lastMs >= ptgap)
	{
		lastMs = millis();
		WIFICheckConnect();
		mqttCheckConnect();
		/* 上报消息心跳周期 */
		mqttIntervalPost();
	}
	// ps2Decode();  // ESP8266需要定时读取PS2接收器缓冲器
	client.loop();
	delay(50);
}

如果编译时遇到错误:

Connecting to MQTT Server ...
MQTT Connect err:2

需要修改库文件PubSubClient.h的内容
MQTT_MAX_PACKET_SIZE必须大于1024,MQTT_KEEPALIVE必须大于等于60
PubSubClient.h的位置在Arduino IDE 【文件】-【首选项】-【设置】-【项目文件夹位置】里面(即C:\Users\Administrator\Documents\Arduino\libraries\PubSubClient)。

5. 最后

以上是ESP32的主要代码,ESP8266的类似。

版权声明:本文为CSDN博主「iqiaoqiao」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/iqiaoqiao/article/details/122802852

生成海报
点赞 0

iqiaoqiao

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

Arduino 水滴检测

实物连接图: 电路图: 代码: void setup() {pinMode(3, OUTPUT);pinMode(2, INPUT); }void loop() {bool va2 digitalR

ESP32_FreeRTOS_Arduino_1_创建任务

关于FreeRTOS 在arduino环境下的应用 一、关于FreeRTOS 1、什么是FreeRTOS FreeRTOS是运行在微控制器上的一种实时操作系统,可以有效的管理任务,合理的分配硬件资源。 举例来