做小车的以往版本选型历史记录
- 原型机-1:树莓派2b + UNO R3 + L298N
最初入坑的版本,2017年那时候啥都不会,直接搞了逗逼版本 - 原型机-2:NodeMCU v2(CP2102)+L293D扩展板
第一次做的正规点的版本,带有舵机的云台,近光灯和远光灯
- MK.1.1:NodeMCU v2(CP2102)+L293D扩展板
基本开始按这个版本定型了 - MK.2.1:NodeMCU v3(CH340)+L293D驱动模块
这个版本是内网用的,APP不是Blynk,是别人开发的,可以使用语音控制和通过手机陀螺仪控制,不过自由度没有Blynk高,最近也没怎么折腾了 - MK.2.2:NodeMCU v2(CP2102)+L293D扩展板
MK1为摇杆版,MK2系列为按钮版.原型机是使用舵机控制摇摆摄像头的,所以MK1保留了舵机控制功能,MK2时候因为直接用了现成的云台摄像头,大幅度节省空间.
一路以来的坑
最初入门是使用树莓派,通过树莓派插网卡后,串口输出指令连接到Blynk,体型巨笨重.来发现ArduinoIDE写的ESP8266系列可以省那么多功夫,树莓派就直接弃坑了,直接使用NodeMCU + L293D电机扩展板的,后来做的体积小了很多.最初功能很简陋,只能固定SSID,不能重置,也没有OLED显示等等...直到现在保留了两个版本,基于这两个版本上改进修复.博客是最近才开的,所以以前的版本都还没公开.
- 参考-1,参考-2,参考-3,
-
MK1
- 使用舵机控制云台,安装了外置摄像头等,探照灯属于车身重的支架,同时驱动四个电机,不适合大电流电机急速转动.所以使用摇杆控制PWM信号驱动电机,车身转弯有点类似真车,是通过左右轮子速度差来实现前进/退后式转弯驱动方向拐弯(专业术语不知道怎么称呼这种方式),需要较大的转弯空间.即左2个轮正转1023,右2个正转轮512,使用12.6V锂电池驱动,电机是串联(标称3-9V/个的TT马达).
- MK2
- 属于小型车辆,使用按钮控制,由于车身小,适合驱动2个电机的小车.转弯是两边车轮反转,可以实现原地360°转弯,车速较快.最好不要驱动4个电机(特别是并联),会出现"虚脱",亲测试过会导致L293D过热直接冒火,请使用大功率点的电机驱动(如:L298N或者L2934路版本),这样要重写一下电机驱动的代码加上没时间,也就没搞了.
需要准备的环境和材料
环境
ArduinoIDE编译通过的库版本:
- IDE版本:1.8.10
- ESP8266库:2.4.2
- Blynk版本:0.6.1
- ArduinoJson:5.13.3
- WiFiManager:0.14.4
PlatformIO编译通过的库版本:
- Blynk:0.6.1
- ArduinoJson:5.13.4
- WifiManager:0.14
- ESP8266:2.0.4 / 2.1.1 / 2.2.0 / 2.2.3
2020-01-13:在PlatformIO 2.3.2板库下编译会出错,2.2.3编译成功,尽量不要用高版本的板库.平常工作忙,具体我也没查清楚是啥原因.
材料
- ESP8266 X 1
- L293D电机扩展板 X 1
- 电压传感器 X 1(逻辑电平3.3 V)
- 自锁开关 X 1
- 按钮开关 X 1(或者直接用按钮模块取代)
- 10K电阻 X 1
- TT马达 X 1
- 0.96'OLED X 1(SSD1306主控)
- 7.4 v两串电池组 X 1(超过该电压的记得移除连接在VIN VM一起的跳线帽)
接线图
(点击图片放大)
使用PlatformIO(Linux环境)的刷入方法
- 下载python工具:
sudo pip install -U pip setuptools
sudo pip install -U platformio
- 下载仓库:
不行的可以直接去我的github下载:https://github.com/chrisxs/Blynk_WiFi_RC_Car_MK2.5
git clone https://github.com/chrisxs/Blynk_WiFi_RC_Car_MK2.5.git
(点击图片放大)
- 进入目录:
cd Blynk_WiFi_RC_Car_MK2.5
-
运行编译:
platformio run
(点击图片放大)
platformio run -e generic -t upload
- 编译并上传
PlatformIO会自动帮你下载好所需要的衣依赖库,推荐用此方法,需要修改代码的话可以进入图形化IDE里open project
一下就好了(见下图).
当然,如果你熟悉EPStool
的话你也可以使用该工具直接上传二进制文件,编译的结果可以在/bin
目录里找到
MAC和Windows环境操作差不多,这里就不分开写了,具体参考PlatformIO官方文档.觉得实在看不懂的可以看下一节使用ArduinoIDE的方法
使用ArduinoIDE
这里我就不上图了,同时不推荐使用这个方法,因为很麻烦要一个个的下载库,不小心的话你可能会下错
IDE版本:1.8.10
- 先下载好依赖库,在IDE里面都可以搜到
- 接下来是老办法,直接在IDE写代码:
(手机浏览器显示不正常的话点击左上角的三个点点)
#define BLYNK_PRINT Serial
#include <FS.h> //this needs to be first, or it all crashes and burns...
#include <Arduino.h>
#include <ESP8266WiFi.h> //https://github.com/esp8266/Arduino
#include <ESP8266mDNS.h> //https://github.com/esp8266/Arduino
#include <ESP8266WebServer.h>
#include <BlynkSimpleEsp8266.h>
//needed for library
#include <DNSServer.h>
#include <WiFiManager.h> //https://github.com/tzapu/WiFiManager
#include <WiFiUdp.h>
#include <ArduinoJson.h> //https://github.com/bblanchon/ArduinoJson
#include <ArduinoOTA.h>
#include <Wire.h> // Only needed for Arduino 1.6.5 and earlier
#include "SSD1306Wire.h" // legacy include: `#include "SSD1306.h"
SSD1306Wire display(0x3c, D7, D6);
BlynkTimer timer; //clean 为电压传感器引用计时器,名称为timer
const int voltagePin = A0; //电压传感器引脚
float R1 = 30000; //声明R1电阻值,即为:30KΩ
float R2 = 7500; //声明R2电阻值,即为:7.5KΩ
const int LEDPin = LED_BUILTIN;
static const uint8_t pwm_A = 5;
static const uint8_t pwm_B = 4;
static const uint8_t dir_A = 0;
static const uint8_t dir_B = 2;
int motor_speed = 0;
int motor_speed_V;
const int ResetButton = D5; //设置用于自定义重置WIFI存储信息的按键引脚
char blynk_token[34] = "请输入你的TokenKEY";
bool shouldSaveConfig = false;
void Blink();
void STOP()
{ //刹车
analogWrite(pwm_A, 0);
analogWrite(pwm_B, 0);
}
void backward()
{ //后退
analogWrite(pwm_A, motor_speed);
analogWrite(pwm_B, motor_speed);
digitalWrite(dir_A, HIGH);
digitalWrite(dir_B, HIGH);
}
void forward()
{ //前进
analogWrite(pwm_A, motor_speed);
analogWrite(pwm_B, motor_speed);
digitalWrite(dir_A, LOW);
digitalWrite(dir_B, LOW);
}
void turnleft()
{ //左转
analogWrite(pwm_A, motor_speed);
analogWrite(pwm_B, motor_speed);
digitalWrite(dir_A, LOW);
digitalWrite(dir_B, HIGH);
}
void turnright()
{ //右转
analogWrite(pwm_A, motor_speed);
analogWrite(pwm_B, motor_speed);
digitalWrite(dir_A, HIGH);
digitalWrite(dir_B, LOW);
}
void saveConfigCallback()
{
Serial.println("Should save config");
shouldSaveConfig = true;
}
void voltageRead()
{ //电压传感器自定义函数
int VoltageState = analogRead(voltagePin); //暂存A0的数值,类型为整数(0-1023)
float Votage = (3.313 * VoltageState * (R1 + R2)) / (1024 * R2) - 0.4; //暂存A0的数值,类型为浮点
Blynk.virtualWrite(V0, Votage); //写入blynk的V0
}
BLYNK_WRITE(V8) //APP的调速杆插件,最低启动值600-1023
{
int vel = param.asInt();
//int motor_speed_V;
motor_speed_V = map(vel, 0, 1023, 600, 1023);
motor_speed = motor_speed_V;
}
BLYNK_WRITE(V1)
{
int FowardButton = param.asInt();
if (FowardButton == 1)
{
forward();
}
else
{
STOP();
}
}
BLYNK_WRITE(V2)
{
int BackwardButton = param.asInt();
if (BackwardButton == 1)
{
backward();
}
else
{
STOP();
}
}
BLYNK_WRITE(V3)
{
int TurnleftButton = param.asInt();
if (TurnleftButton == 1)
{
turnleft();
}
else
{
STOP();
}
}
BLYNK_WRITE(V4)
{
int TurnrightButton = param.asInt();
if (TurnrightButton == 1)
{
turnright();
}
else
{
STOP();
}
}
BLYNK_CONNECTED()
{
display.clear();
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.setFont(ArialMT_Plain_16);
display.drawString(0, 0, "Blynk server");
display.drawString(0, 15, "is conneted...");
display.display();
STOP();
Blynk.syncAll();
Blink();
digitalWrite(LEDPin, LOW);
}
void Blink()
{
digitalWrite(LEDPin, HIGH);
delay(500);
digitalWrite(LEDPin, LOW);
delay(500);
digitalWrite(LEDPin, HIGH);
delay(500);
digitalWrite(LEDPin, LOW);
delay(500);
digitalWrite(LEDPin, HIGH);
delay(500);
digitalWrite(LEDPin, LOW);
delay(500);
digitalWrite(LEDPin, HIGH);
}
void setup()
{
Serial.begin(115200);
// initial settings for motors off and direction forward
display.init();
display.flipScreenVertically();
display.setFont(ArialMT_Plain_10);
display.clear();
display.clear();
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.setFont(ArialMT_Plain_16);
display.drawString(0, 0, "Device is booting");
display.display();
pinMode(pwm_A, OUTPUT); // PMW A
pinMode(pwm_B, OUTPUT); // PMW B
pinMode(dir_A, OUTPUT); // DIR A
pinMode(dir_B, OUTPUT); // DIR B
pinMode(LEDPin, OUTPUT);
int ResetButtonState = digitalRead(ResetButton);
pinMode(ResetButton, INPUT);
digitalWrite(LEDPin, HIGH);
//从FS json中读取配置
Serial.println("mounting FS...");
if (SPIFFS.begin())
{
Serial.println("mounted file system");
if (SPIFFS.exists("/config.json"))
{
//未见存在提取并读取
Serial.println("reading config file");
File configFile = SPIFFS.open("/config.json", "r");
if (configFile)
{
Serial.println("opened config file");
size_t size = configFile.size();
// 分配一个缓冲区来存储文件的内容。
std::unique_ptr<char[]> buf(new char[size]);
configFile.readBytes(buf.get(), size);
DynamicJsonBuffer jsonBuffer;
JsonObject &json = jsonBuffer.parseObject(buf.get());
json.printTo(Serial);
if (json.success())
{
Serial.println("\nparsed json");
strcpy(blynk_token, json["blynk_token"]);
}
else
{
Serial.println("failed to load json config");
}
configFile.close();
}
}
}
else
{
Serial.println("failed to mount FS");
}
//end read
// The extra parameters to be configured (can be either global or just in the setup)
// After connecting, parameter.getValue() will get you the configured value
// id/name placeholder/prompt default length
WiFiManagerParameter custom_blynk_token("blynk", "序列号", blynk_token, 34);
//WiFiManager
//Local intialization. Once its business is done, there is no need to keep it around
WiFiManager wifiManager;
//set config save notify callback
wifiManager.setSaveConfigCallback(saveConfigCallback);
//在这里增加参数
wifiManager.addParameter(&custom_blynk_token);
if (ResetButtonState == HIGH)
{
Serial.println("Getting Reset ESP Wifi-Setting.......");
display.clear();
display.setFont(ArialMT_Plain_16);
display.drawString(0, 0, "Getting Reset for ");
display.drawString(0, 15, "ESP Wifi-Setting.......");
display.display();
wifiManager.resetSettings();
delay(5000);
Blink();
Serial.println("Formatting FS......");
display.clear();
display.setFont(ArialMT_Plain_16);
display.drawString(0, 0, "Formatting FS......");
display.display();
SPIFFS.format();
delay(5000);
Blink();
Serial.println("Done,Reboot.");
display.clear();
display.setFont(ArialMT_Plain_16);
display.drawString(0, 0, "Done,Reboot.");
display.display();
Blink();
ESP.reset();
}
//fetches ssid and pass and tries to connect
//if it does not connect it starts an access point with the specified name
//here "AutoConnectAP"
//and goes into a blocking loop awaiting configuration
if (!wifiManager.autoConnect("Blynk_WIFI_Car_MK2.5"))
{
Serial.println("failed to connect and hit timeout");
delay(3000);
//reset and try again, or maybe put it to deep sleep
ESP.reset();
delay(5000);
}
//if you get here you have connected to the WiFi
Serial.println("WIFI is connected");
//read updated parameters
strcpy(blynk_token, custom_blynk_token.getValue());
//save the custom parameters to FS
if (shouldSaveConfig)
{
Serial.println("saving config");
DynamicJsonBuffer jsonBuffer;
JsonObject &json = jsonBuffer.createObject();
json["blynk_token"] = blynk_token;
File configFile = SPIFFS.open("/config.json", "w");
if (!configFile)
{
Serial.println("failed to open config file for writing");
}
json.printTo(Serial);
json.printTo(configFile);
configFile.close();
//end save
}
Serial.println("local ip");
Serial.println(WiFi.localIP());
ArduinoOTA.setHostname("Blynk_WIFI_Car_MK2.5");
ArduinoOTA.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH)
{
type = "sketch";
}
else
{ // U_SPIFFS
type = "filesystem";
}
// NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
Serial.println("Start updating " + type);
});
ArduinoOTA.onEnd([]() {
Serial.println("\nEnd");
});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR)
{
Serial.println("Auth Failed");
}
else if (error == OTA_BEGIN_ERROR)
{
Serial.println("Begin Failed");
}
else if (error == OTA_CONNECT_ERROR)
{
Serial.println("Connect Failed");
}
else if (error == OTA_RECEIVE_ERROR)
{
Serial.println("Receive Failed");
}
else if (error == OTA_END_ERROR)
{
Serial.println("End Failed");
}
});
ArduinoOTA.begin();
Serial.println("Ready");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
Blynk.config(blynk_token, "你的域名", 8080);
timer.setInterval(1000L, voltageRead);
}
void loop()
{
int VoltageState = analogRead(voltagePin); //暂存A0的数值,类型为整数(0-1023)
float Votage = (3.3 * VoltageState * (R1 + R2)) / (1024 * R2) - 0.4; //暂存A0的数值,类型为浮点
display.clear();
display.setTextAlignment(TEXT_ALIGN_LEFT);
display.setFont(ArialMT_Plain_10);
display.drawString(0, 0, "IP: " + String(WiFi.localIP().toString()));
display.drawString(0, 10, "MAC: " + String(WiFi.macAddress()));
display.drawString(0, 20, "SSID: " + String(WiFi.SSID()));
display.drawString(0, 30, "RSSI: " + String(WiFi.RSSI()) + " dB");
display.drawString(0, 40, "Votage: " + String(Votage, 2) + " V");
display.drawString(0, 50, " Connection Activated .");
display.display();
Blynk.run();
timer.run();
ArduinoOTA.handle();
Blynk.virtualWrite(V5, "IP地址:", WiFi.localIP().toString());
Blynk.virtualWrite(V6, "MAC地址:", WiFi.macAddress());
Blynk.virtualWrite(V7, "SSID:", WiFi.SSID(), " RSSI:", WiFi.RSSI(), " dB");
}
- 先编译一下,然后上传即可,如果有错,请检查一下库的版本有没有对上
关于代码里的电压计算公式
有偏差的可以自行修正一下,毕竟不是很精确的传感器,计算方法和电路图如下:
(点击图片放大)
公式很简单:Vin = Vout * (R2/(R1+R2))
R1 = 30000
,R2 = 7500
和Vout
可以通过使用Vout =(analogvalue * 3.3/1024)
模拟输入中可以计算得出结果。
注意:esp8266的模拟量接口只支持3.3V!
APP端的设置与WIFI配网操作流程
配网
上传后,你会看见一个热点名为"Blynk_WIFI_Car_MK2.5
",代码中我没设密码,请自行修改.具体流程如下图,图中的操作不是小车的,只是引用一下,但是操作一样,不具体说了,就是连接你路由或者手机热点WIFI-SSID就行:
一般手机连接后会自动弹出验证界面,如果连接后没有弹出,那就就手动打开你的浏览器,输入192.168.4.1
进入验证界面
APP端设置
如下图,按途中虚拟引脚的编号设置即可(详细可以参考代码)
点击图片放大
设备操作
开稽!
更新Note:
2020-02-21
MK2.5
- 优化代码
- 修改
platformio.ini
文件,让IDE自动下载好相关依赖库,先用命令行运行:
- 编译命令
platformio run
- 其他命令
platformio run
- 编译platformio run -e generic -t upload
- 编译并上传platformio run -t clean
- 移除已编译文件
IDE会自动安装,如果是第一次运行,会自动下载相关依赖库(包含对应的版本)
2020-01-26:
MK2.5
引入新功能
1.加入OLED驱动,显示启动/重置/网络/电压等相关信息;
2.加入调速功能,使用滑动条设置PWM速度;
2020-01-17:
MK.I:
引脚变动
- 项目-取消舵机、远近光灯的功能和所占引脚、资源,控制摄像头拍摄直接使用带云台和夜视功能的成品,照明功能用红外夜视代替;
- 加入复位功能按钮(D5),按下按钮不放,点“Reset”,进入WIFI配网;
小变动
-
精简车身,轻量化,链接线路简化,代码简化,电源使用改用8.4V锂电驱动;
- 保留摇杆控制(电机接线与MK2版本相反);
- 保留电量电压采集(代码保留);
其他更新(MK.1/MK2共有)
- 加入OTA更新功能;
- 加入实时显示SSID、IP、MAC、RSSI信息功能;
2020-01-17:
MK.II:
变动
- 修改电机驱动代码,使内网版APP控制界面与BLYNK物联网平台保持一致;
- 统一反馈标准,如重置引脚、配网LED提示、平台接入提示