前言
做拓展项目需要要用GPS定位数据以及超声设计,之前没有用过ESP-12F这个开发板去做过,都是用到STM32板子,今天尝试用Arduino环境进行开发,并实现GPS经纬度数据获取以及超声测距
一、芯片介绍
这款芯片的可用io口还是比较少的,只适合做小型的设备控制,不用看图上有很多io口,其实他很多都不能用,否则就会导致启动错误,我已经踩过坑了
ESP-12F可用输出引脚列表
GPIO编号 | 物理引脚 | 特殊限制 | 最大输出电流 | PWM支持 | 推荐用途 |
---|---|---|---|---|---|
GPIO4 | Pin 10 | 无 | 12mA | ✔️ | LED控制、传感器驱动 |
GPIO5 | Pin 11 | 启动时需高电平 | 12mA | ✔️ | 继电器、数字输出 |
GPIO12 | Pin 14 | 启动时需低电平 | 12mA | ✔️ | 按键输入、低功耗设备 |
GPIO13 | Pin 15 | 无 | 12mA | ✔️ | SPI MOSI、数据总线 |
GPIO14 | Pin 16 | 启动时需高电平 | 12mA | ✔️ | SPI CLK、高速信号 |
GPIO15 | Pin 17 | 必须启动时低电平 | 12mA | ✔️ | 接地设备控制 |
禁止使用的输出引脚
GPIO编号 | 限制原因 |
---|---|
GPIO0 | 启动模式选择(需外部上拉) |
GPIO2 | 内部上拉,启动时需高电平 |
GPIO16 | 仅支持开漏输出 |
二、材料准备
准备这是四个材料就行了
ESP8266开发板 | ESP8266-Node |
显示屏-0.96寸 | 显示屏-0.96寸 |
GPS模块 | ATGM336H |
GPS是每一秒自动进行发送数据,当GPS状态的闪烁,就说明获取到了卫星定位了
超声波模块 | HC-SR04 |
三、代码编写
这个的话,我主要调一个重要部分,最后会附上源码,因为我代码里面带了联网部分,我这边就先暂时把联网部分屏蔽掉的
1 超声测距部分
一个io设置为输入,一个设置为输出
// 检测距离
void Distance_text(void) {
// 发送触发信号
digitalWrite(trigPin, LOW); // 确保先拉低Trig引脚
delayMicroseconds(2); // 维持2微秒
digitalWrite(trigPin, HIGH); // 触发脉冲
delayMicroseconds(10); // 维持10微秒
digitalWrite(trigPin, LOW); // 结束触发
// 测量回波脉冲持续时间
distance = float(pulseIn(echoPin, HIGH) * 17) / 1000;
if ((int)distance < 50) {
} else {
}
// 显示结果
// Serial.print("disc:");
// if (distance > 0) {
// Serial.print(distance);
// Serial.println(" cm");
// } else {
// Serial.println("out");
// }
}
2 GPS部分
使用模拟串口,进行数据接收和解析。并且这里利用了显示了时区,我们的时区需要+8H,就是当前时间,程序已经完成这部分
void gpsParser() {
while (gpsSerial.available()) {
char Res = gpsSerial.read();
if (Res == '$') {
point1 = 0;
memset(USART_RX2_BUF, 0, sizeof(USART_RX2_BUF));
}
USART_RX2_BUF[point1++] = Res;
// 检测GPRMC/GNRMC语句
if (point1 >= 6 && USART_RX2_BUF[3] == 'R' && USART_RX2_BUF[4] == 'M' && USART_RX2_BUF[5] == 'C') {
if (Res == 'n') {
memset(Save_Data.GPS_Buffer, 0, sizeof(Save_Data.GPS_Buffer));
memcpy(Save_Data.GPS_Buffer, USART_RX2_BUF, point1);
Save_Data.isGetData = true;
point1 = 0;
}
}
if (point1 >= sizeof(USART_RX2_BUF) - 1) {
point1 = 0;
}
}
}
void parseGpsBuffer() {
char* subString;
char* subStringNext;
char usefullBuffer[2] = { 0 };
int commaCount = 0;
subString = strtok(Save_Data.GPS_Buffer, ",");
while (subString != NULL) {
switch (commaCount) {
case 1: // UTC时间
strncpy(Save_Data.UTCTime, subString, sizeof(Save_Data.UTCTime));
break;
case 2: // 数据有效性
strncpy(usefullBuffer, subString, sizeof(usefullBuffer));
break;
case 3: // 纬度
strncpy(Save_Data.latitude, subString, sizeof(Save_Data.latitude));
break;
case 4: // N/S
strncpy(Save_Data.N_S, subString, sizeof(Save_Data.N_S));
break;
case 5: // 经度
strncpy(Save_Data.longitude, subString, sizeof(Save_Data.longitude));
break;
case 6: // E/W
strncpy(Save_Data.E_W, subString, sizeof(Save_Data.E_W));
break;
}
subString = strtok(NULL, ",");
commaCount++;
}
Save_Data.isUsefull = (usefullBuffer[0] == 'A');
Save_Data.isParseData = true;
}
void printGpsBuffer() {
if (Save_Data.isUsefull) {
// 转换经度
longitude_sum = atof(Save_Data.longitude);
int longitude_int = (int)(longitude_sum / 100);
float longitude = longitude_int + (longitude_sum - longitude_int * 100) / 60;
// 转换纬度
latitude_sum = atof(Save_Data.latitude);
int latitude_int = (int)(latitude_sum / 100);
float latitude = latitude_int + (latitude_sum - latitude_int * 100) / 60;
// 转化为经纬度
longitude_sum = longitude_int + ((longitude_sum / 100 - longitude_int) * 100) / 60;
latitude_sum = latitude_int + ((latitude_sum / 100 - latitude_int) * 100) / 60;
// 时间解析修正
String utcTime = Save_Data.UTCTime;
if (utcTime.length() >= 6) {
// 安全解析
hour = utcTime.substring(0, 2).toInt(); // 小时
minute = utcTime.substring(2, 4).toInt(); // 分钟
second = utcTime.substring(4, 6).toInt(); // 秒
// 时区调整(UTC+8)
hour += 8;
// 处理溢出
if (hour >= 24) {
hour -= 24;
// 日期+1(需补充日期处理逻辑)
} else if (hour < 0) {
hour += 24;
// 日期-1
}
// 时间有效性验证
if (hour < 0 || hour >= 24 || minute < 0 || minute >= 60 || second < 0 || second >= 60) {
Serial.println("Invalid Time Data");
return;
}
格式化输出
Serial.printf("Local Time: %02d:%02d:%02dn", hour, minute, second);
} else {
Serial.println("UTC Time Format Error");
}
Serial.print("Latitude: ");
Serial.print(latitude, 6);
Serial.print(" ");
Serial.println(Save_Data.N_S);
Serial.print("Longitude: ");
Serial.print(longitude, 6);
Serial.print(" ");
Serial.println(Save_Data.E_W);
Serial.println("---------------------");
} else {
Serial.println("GPS Data Invalid");
}
}
3 完整代码
编译芯片的选择:ESPino(ESP-12)Module
#include <SoftwareSerial.h>
#include <PubSubClient.h>
#include <Ticker.h>
#include <ESP8266WiFi.h>
//OLED
#include <U8g2lib.h>
#include <Wire.h>
//JSON
#include <ArduinoJson.h>
// 设置wifi接入信息(请根据您的WiFi信息进行修改)
const char* ssid = "NET";
const char* password = "12345678";
const char* mqttServer = "iot-06z00axdhgfk24n.mqtt.iothub.aliyuncs.com";
// 如以上MQTT服务器无法正常连接,请前往以下页面寻找解决方案
// http://www.taichi-maker.com/public-mqtt-broker/
Ticker ticker;
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
int count; // Ticker计数用变量
int count_2;
// ****************************************************
// 注意!以下需要用户根据然也物联平台信息进行修改!否则无法工作!
// ****************************************************
const char* mqttUserName = "smartdevice&h9sjy2rtcTI"; // 服务端连接用户名(需要修改)
const char* mqttPassword = "5ba9463de09043190a8a743647518c46b1db6e1bfc2e0021198efe3384580772"; // 服务端连接密码(需要修改)
const char* clientId = "h9sjy2rtcTI.smartdevice|securemode=2,signmethod=hmacsha256,timestamp=1739158837609|"; // 客户端id (需要修改)
const char* subTopic = "/broadcast/h9sjy2rtcTI/test1"; // 订阅主题(需要修改)
const char* pubTopic = "/broadcast/h9sjy2rtcTI/test2"; // 订阅主题(需要修改)
const char* willTopic = "/broadcast/h9sjy2rtcTI/test2"; // 遗嘱主题名称(需要修改)
// ****************************************************
//遗嘱相关信息
const char* willMsg = "esp8266 offline"; // 遗嘱主题信息
const int willQos = 0; // 遗嘱QoS
const int willRetain = false; // 遗嘱保留
const int subQoS = 1; // 客户端订阅主题时使用的QoS级别(截止2020-10-07,仅支持QoS = 1,不支持QoS = 2)
const bool cleanSession = true; // 清除会话(如QoS>0必须要设为false)
// LED 配置
#define LED_BUILTIN 15
#define LED 2
bool ledStatus = HIGH;
int Waning = 0;
int Ledstate = 0;
// OLED
#define SCL 14
#define SDA 12
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, SCL, SDA, /*reset=*/U8X8_PIN_NONE);
char str[50]; //拼接字符使用
// 定义超声波传感器引脚
#define trigPin 5
#define echoPin 4
float distance;
// 配置软串口(根据实际接线修改引脚)
SoftwareSerial gpsSerial(15, 13); // RX: D8, TX: D7
// GPS数据结构
struct {
char GPS_Buffer[128];
char UTCTime[12];
char latitude[16];
char N_S[2];
char longitude[16];
char E_W[2];
bool isGetData = false;
bool isParseData = false;
bool isUsefull = false;
} Save_Data;
// 全局变量
char USART_RX2_BUF[256];
unsigned char point1 = 0;
float longitude_sum, latitude_sum;
int hour, minute, second;
void setup() {
Serial.begin(9600); // 启动串口通讯
gpsSerial.begin(9600); // GPS模块常用波特率
gpsSerial.println("GPS Parser Started");
//OELD
u8g2.begin();
u8g2.setFont(u8g2_font_t0_16_tf); //设定字体 u8g2_font_ncenB08_tr
// //设置ESP8266工作模式为无线终端模式
// WiFi.mode(WIFI_STA);
// // 连接WiFi
// connectWifi();
// // 设置MQTT服务器和端口号
// mqttClient.setServer(mqttServer, 1883);
// mqttClient.setCallback(receiveCallback);
// // 连接MQTT服务器
// connectMQTTserver();
// Ticker定时对象
ticker.attach(1, tickerCount);
// LED和蜂鸣器
pinMode(LED_BUILTIN, OUTPUT); // 设置板上LED引脚为输出模式
pinMode(LED, OUTPUT); // 设置板上LED引脚为输出模式
digitalWrite(LED, ledStatus); // 启动后关闭板上LED
digitalWrite(LED_BUILTIN, ledStatus); // 启动后关闭板上LED
pinMode(trigPin, OUTPUT); // 设置Trig引脚为输出模式
pinMode(echoPin, INPUT); // 设置Echo引脚为输入模式
}
void loop() {
// // 如果开发板未能成功连接服务器,则尝试连接服务器
// if (!mqttClient.connected()) {
// connectMQTTserver();
// } else {
//1秒更新一次
if (count_2 >= 1) {
Distance_text(); // 测量距离
u8g2.clearBuffer();
// 解析 GPS
if (Save_Data.isGetData) {
parseGpsBuffer();
Save_Data.isGetData = false;
}
if (Save_Data.isParseData) {
printGpsBuffer();
Save_Data.isParseData = false;
}
// LED
if (Ledstate) {
digitalWrite(LED_BUILTIN, HIGH); // 启动
} else {
digitalWrite(LED_BUILTIN, LOW);
gpsSerial.println("欢迎使用");
Serial.print("欢迎使用");
}
if (ledStatus == LOW) {
ledStatus = HIGH;
digitalWrite(LED, ledStatus);
} else {
ledStatus = LOW;
digitalWrite(LED, ledStatus);
}
if ((int)longitude_sum > 1) {
sprintf(str, "Log: %.6f", longitude_sum);
u8g2.drawStr(0, 16, str);
sprintf(str, "Lat: %.6f", latitude_sum);
u8g2.drawStr(0, 32, str);
sprintf(str, "Time: %02d:%02d:%02d", hour, minute, second);
u8g2.drawStr(0, 48, str);
} else {
sprintf(str, "Log: error", longitude_sum);
u8g2.drawStr(0, 16, str);
sprintf(str, "Lat: error", latitude_sum);
u8g2.drawStr(0, 32, str);
sprintf(str, "Time:error", hour, minute, second);
u8g2.drawStr(0, 48, str);
}
sprintf(str, "Disc: %.2f CM", distance);
u8g2.drawStr(0, 64, str);
u8g2.sendBuffer(); //显示
count_2 = 0;
}
//4秒发送一次
if (count >= 4) {
// 每隔4秒钟发布一次信息
// pubMQTTmsg();
count = 0;
}
}
gpsParser();
// 处理信息以及心跳
mqttClient.loop();
}
// 连接WiFi
void connectWiFi() {
WiFi.begin(ssid, password);
//Serial.print("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
// Serial.println("nConnected! IP: " + WiFi.localIP().toString());
}
//计时器
void tickerCount() {
count++;
count_2++;
}
// 连接MQTT服务器并订阅信息
void connectMQTTserver() {
// 根据ESP8266的MAC地址生成客户端ID(避免与其它ESP8266的客户端ID重名)
/* 连接MQTT服务器
boolean connect(const char* id, const char* user,
const char* pass, const char* willTopic,
uint8_t willQos, boolean willRetain,
const char* willMessage, boolean cleanSession);
若让设备在离线时仍然能够让qos1工作,则connect时的cleanSession需要设置为false
*/
if (mqttClient.connect(clientId, mqttUserName,
mqttPassword, willTopic,
willQos, willRetain, willMsg, cleanSession)) {
// Serial.print("MQTT Server Connected. ClientId: ");
// Serial.println(clientId);
// Serial.print("MQTT Server: ");
// Serial.println(mqttServer);
subscribeTopic(); // 订阅指定主题
} else {
// Serial.print("MQTT Server Connect Failed. Client State:");
// Serial.println(mqttClient.state());
delay(5000);
}
}
// 收到信息后的回调函数
void receiveCallback(char* topic, byte* payload, unsigned int length) {
// Serial.print("Message Received [");
// Serial.print(topic);
// Serial.print("] ");
// for (int i = 0; i < length; i++) {
// Serial.print((char)payload[i]);
// }
//解析数据
massage_parse_json((char*)payload);
//回传数据
// pubMQTTmsg();
}
// 订阅指定主题
void subscribeTopic() {
// 通过串口监视器输出是否成功订阅主题以及订阅的主题名称
// 请注意subscribe函数第二个参数数字为QoS级别。这里为QoS = 1
if (mqttClient.subscribe(subTopic, subQoS)) {
//Serial.println(subTopic);
} else {
//Serial.print("Subscribe Fail...");
connectMQTTserver(); // 则尝试连接服务器
}
}
// 发布信息
void pubMQTTmsg() {
char pubMessage[254];
// 数据流
MQTT_FillBuf(pubMessage);
// 实现ESP8266向主题发布信息
if (mqttClient.publish(pubTopic, pubMessage)) {
//Serial.println("Publish Topic:");Serial.println(pubTopic);
//Serial.println("Publish message:");
//Serial.println(pubMessage);
} else {
subscribeTopic(); // 则尝试连接服务器
//Serial.println("Message Publish Failed.");
}
}
// ESP8266连接wifi
void connectWifi() {
u8g2.clearDisplay(); // 清屏
sprintf(str, "Wait ... ");
u8g2.drawStr(0, 32, str);
u8g2.sendBuffer(); //显示
WiFi.begin(ssid, password);
//等待WiFi连接,成功连接后输出成功信息
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
// Serial.print(".");
}
//Serial.println("WiFi Connected!");
}
// 数据封装
unsigned char MQTT_FillBuf(char* buf) {
char text[256];
memset(text, 0, sizeof(text));
strcpy(buf, "{");
if ((int)latitude_sum > 1) {
memset(text, 0, sizeof(text));
sprintf(text, ""lat":"%.6f",", latitude_sum); // Temp是数据流的一个名称,temper是温度值
strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, ""log":"%.6f",", longitude_sum); // Temp是数据流的一个名称,temper是温度值
strcat(buf, text);
}
memset(text, 0, sizeof(text));
sprintf(text, ""disc":"%d",", (int)distance); // Temp是数据流的一个名称,temper是温度值
strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, ""waring":"%d",", Waning); // Temp是数据流的一个名称,temper是温度值
strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, ""led":"%d"", digitalRead(LED_BUILTIN)); // Temp是数据流的一个名称,temper是温度值
strcat(buf, text);
memset(text, 0, sizeof(text));
sprintf(text, "}");
strcat(buf, text);
return strlen(buf);
}
// 解析json数据
void massage_parse_json(char* message) {
// 声明一个 JSON 文档对象
StaticJsonDocument<200> doc;
// 解析 JSON 数据
DeserializationError error = deserializeJson(doc, (const char*)message);
// 检查解析是否成功
if (error) {
// Serial.print("Failed to parse JSON: ");
// Serial.println(error.c_str());
return;
}
// 从解析后的 JSON 文档中获取键值对
int cmd = doc["cmd"];
JsonObject data = doc["data"];
switch (cmd) {
case 1:
Ledstate = data["led"];
count = 1;
break;
case 2:
if((int)latitude_sum > 1){
sprintf(str, "当前时间:%02d:%02d:%02d", hour, minute, second);
gpsSerial.println(str);
}else{
sprintf(str, "请先到开阔地带进行定位定位", hour, minute, second);
gpsSerial.println(str);
}
count = 1;
break;
case 3:
count = 1;
break;
}
}
// 检测距离
void Distance_text(void) {
// 发送触发信号
digitalWrite(trigPin, LOW); // 确保先拉低Trig引脚
delayMicroseconds(2); // 维持2微秒
digitalWrite(trigPin, HIGH); // 触发脉冲
delayMicroseconds(10); // 维持10微秒
digitalWrite(trigPin, LOW); // 结束触发
// 测量回波脉冲持续时间
distance = float(pulseIn(echoPin, HIGH) * 17) / 1000;
if ((int)distance < 50) {
} else {
}
// 显示结果
// Serial.print("disc:");
// if (distance > 0) {
// Serial.print(distance);
// Serial.println(" cm");
// } else {
// Serial.println("out");
// }
}
void gpsParser() {
while (gpsSerial.available()) {
char Res = gpsSerial.read();
if (Res == '$') {
point1 = 0;
memset(USART_RX2_BUF, 0, sizeof(USART_RX2_BUF));
}
USART_RX2_BUF[point1++] = Res;
// 检测GPRMC/GNRMC语句
if (point1 >= 6 && USART_RX2_BUF[3] == 'R' && USART_RX2_BUF[4] == 'M' && USART_RX2_BUF[5] == 'C') {
if (Res == 'n') {
memset(Save_Data.GPS_Buffer, 0, sizeof(Save_Data.GPS_Buffer));
memcpy(Save_Data.GPS_Buffer, USART_RX2_BUF, point1);
Save_Data.isGetData = true;
point1 = 0;
}
}
if (point1 >= sizeof(USART_RX2_BUF) - 1) {
point1 = 0;
}
}
}
void parseGpsBuffer() {
char* subString;
char* subStringNext;
char usefullBuffer[2] = { 0 };
int commaCount = 0;
subString = strtok(Save_Data.GPS_Buffer, ",");
while (subString != NULL) {
switch (commaCount) {
case 1: // UTC时间
strncpy(Save_Data.UTCTime, subString, sizeof(Save_Data.UTCTime));
break;
case 2: // 数据有效性
strncpy(usefullBuffer, subString, sizeof(usefullBuffer));
break;
case 3: // 纬度
strncpy(Save_Data.latitude, subString, sizeof(Save_Data.latitude));
break;
case 4: // N/S
strncpy(Save_Data.N_S, subString, sizeof(Save_Data.N_S));
break;
case 5: // 经度
strncpy(Save_Data.longitude, subString, sizeof(Save_Data.longitude));
break;
case 6: // E/W
strncpy(Save_Data.E_W, subString, sizeof(Save_Data.E_W));
break;
}
subString = strtok(NULL, ",");
commaCount++;
}
Save_Data.isUsefull = (usefullBuffer[0] == 'A');
Save_Data.isParseData = true;
}
void printGpsBuffer() {
if (Save_Data.isUsefull) {
// 转换经度
longitude_sum = atof(Save_Data.longitude);
int longitude_int = (int)(longitude_sum / 100);
float longitude = longitude_int + (longitude_sum - longitude_int * 100) / 60;
// 转换纬度
latitude_sum = atof(Save_Data.latitude);
int latitude_int = (int)(latitude_sum / 100);
float latitude = latitude_int + (latitude_sum - latitude_int * 100) / 60;
// 转化为经纬度
longitude_sum = longitude_int + ((longitude_sum / 100 - longitude_int) * 100) / 60;
latitude_sum = latitude_int + ((latitude_sum / 100 - latitude_int) * 100) / 60;
// 时间解析修正
String utcTime = Save_Data.UTCTime;
if (utcTime.length() >= 6) {
// 安全解析
hour = utcTime.substring(0, 2).toInt(); // 小时
minute = utcTime.substring(2, 4).toInt(); // 分钟
second = utcTime.substring(4, 6).toInt(); // 秒
// 时区调整(UTC+8)
hour += 8;
// 处理溢出
if (hour >= 24) {
hour -= 24;
// 日期+1(需补充日期处理逻辑)
} else if (hour < 0) {
hour += 24;
// 日期-1
}
// 时间有效性验证
if (hour < 0 || hour >= 24 || minute < 0 || minute >= 60 || second < 0 || second >= 60) {
Serial.println("Invalid Time Data");
return;
}
格式化输出
Serial.printf("Local Time: %02d:%02d:%02dn", hour, minute, second);
} else {
Serial.println("UTC Time Format Error");
}
Serial.print("Latitude: ");
Serial.print(latitude, 6);
Serial.print(" ");
Serial.println(Save_Data.N_S);
Serial.print("Longitude: ");
Serial.print(longitude, 6);
Serial.print(" ");
Serial.println(Save_Data.E_W);
Serial.println("---------------------");
} else {
Serial.println("GPS Data Invalid");
}
}
编译完成后图片
四、实现效果
开启串口会输出
五、 参考
Arduino IDE 使用安装以及ESP32库的导入(离线)https://blog.csdn.net/herui_2/article/details/135296814?spm=1001.2014.3001.5501
ESP32/ESP8622 -- 使用MQTT协议连接云平台(带图文说明)https://herui.blog.csdn.net/article/details/135317019?spm=1001.2014.3001.5502
联系方式 微信号:13648103287