第一次发帖,有什么不对的,麻烦指正一下。
以下是我最近完成的一个小项目,不过还有很大的改动优化空间,本篇只是带来一个大体的框架,以及我个人在这个项目所遇到的一些问题,以及我个人对这些问题的解决以及看法,(一定有错的地方,发出来也是希望大家帮忙看看 嘿嘿)
首先,介绍一下本项目,本项目所需的有3块esp32_wroom_32模块,以及温湿度传感器DHT11,继电器开关,光敏电阻,stm32f407,以及一个串口屏(淘晶驰TJC4832T135_001),不过这个串口屏也可以不要,看你自己吧。首先需要解决的是esp32间的数据传输问题,这个我使用的是arduino开发,然后用的是esp_now传输,这个有不了解的我建议去下面的链接了解一下
这是可以无基础的学习的一个视频资源,网上也有很多关于now的介绍,可以到乐鑫的官网去深入了解。
然后第一个肯定是arduino的配置 ,首先去arduino的官网下载那个安装
文件——首选项——附加开发板管理器网址 :添加 https://dl.espressif.com/dl/package_esp32_index.json
——管理库:然后输入ESP32 (选那个带esp32的)然后进行下载就可以了
工具——开发板:选择ESP32 Dev Module;然后选择对应的端口;
然后首先要实现的是三个esp32之间的通信,我选择的是esp32独有esp-now协议
ESP-NOW 是由乐鑫开发的另一款协议,可以使多个设备在没有或不使用 Wi-Fi 的情况下进行通信。这种协议类似常见于无线鼠标中的低功耗 2.4GHz 无线连接——设备在进行通信之前要进行配对。配对之后,设备之间的连接是持续的、点对点的,并且不需要握手协议。
ESP-NOW 广泛应用于智能照明、远程控制、传感器等领域。
对于这个协议想了解更深入的话,可以去以下链接
ESP-NOW - ESP32 - — ESP-IDF 编程指南 latest 文档 (espressif.com)
首先要获取各个esp32的mac地址,因为now协议是通过其mac地址进行数据传输的
#include <WiFi.h>
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
WiFi.mode(WIFI_MODE_STA);
Serial.println(WiFi.macAddress()); //串口输出mac地址
}
void loop() {
// put your main code here, to run repeatedly:
}
将以上代码 编译上传至esp32,即可得到其mac地址
然后以下是now的基本创建过程
#include <WiFi.h>
#include <esp_now.h>
uint8_t MACA[] = {0xC0,0x49,0xEF,0xD3,0x5F,0x28};//a
uint8_t MACB[] = {0xC0,0x49,0xEF,0xD0,0xE2,0xC4};//b
uint8_t MACC[] = {0x40,0x22,0xD8,0x60,0xDD,0xB8};//c
String success; //发送数据成功则存储变量
//接收数据结构体 接受 温湿度以及哪个地方
typedef struct Rdata{
int id;
float humidity; //湿度
float temperature; //温度
bool relay_status; //继电器状态 true为开 false为关
}Rdata;
Rdata myData;
Rdata board1; //用于存储b板的数据
Rdata board2; //用于存储c板的数据
//创建一个包含结构体的数组方便调用读写数据
//发送 结构体,可更改为打开或关闭继电器的信息,以及等等的控制信息
typedef struct Tdata{
int id;
bool relay; //控制继电器状态
int x; //暂留开关
}Tdata;
//创建一个发送的结构体
Tdata IDdata;
//发送数据时的回调函数
void OnDataSend(const uint8_t *mac_addr, esp_now_send_status_t status) //发送数据时的回调函数
{
Serial.print("\r\n status:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "tran succes~!" : "tran failed!");
}
//接受到数据时的回调函数 可在这个函数里选择接收到何种数据 作何等操作
void OnDataRecv(const uint8_t *mac_addr, const uint8_t *incomingData, int len)
{
char macStr[18]; //定义一个字符串数组 用来存储是哪个发来的数据,其mac地址
Serial.print("recv:");
snprintf(macStr,sizeof(macStr),"%02x:%02x:%02x:%02x:%02x:%02x:",
mac_addr[0],mac_addr[1],mac_addr[2],mac_addr[3],mac_addr[4],mac_addr[5]);
Serial.println(macStr); //拼接到macStr 而后打印出来
memcpy(&myData, incomingData, sizeof(myData)); //将接收到的数据存储到mydata中
Serial.printf("id: %u size: %u \n",myData.id,len); //打印出是哪个id发力的信息
if(myData.id == 23) //如果是id为23的发来 后面可以改成字符串 是哪个地方发来的
{
board1.humidity = myData.humidity;
board1.temperature = myData.temperature;
board1.relay_status = myData.relay_status;
Serial.printf("1humidity :%0.2f\n",board1.humidity);
Serial.printf("1temperature:%0.2f\n",board1.temperature);
if(board1.relay_status == true)
Serial.println("1relay_status: Open");
else
Serial.println("1relay_status: Close");
}
else if (myData.id == 26)
{
board2.humidity = myData.humidity;
board2.temperature = myData.temperature;
Serial.printf("2humidity :%0.2f\n",board2.humidity);
Serial.printf("2temperature:%0.2f\n",board2.temperature);
if(board2.relay_status == true)
Serial.println("2relay_status: Open");
else
Serial.println("2relay_status: Close");
}
}
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
WiFi.mode(WIFI_STA);
if(esp_now_init() != ESP_OK)
{
Serial.printf("init esp_now error\n");
return;
}
esp_now_register_send_cb(OnDataSend); //ESPNow初始化成功,注册发送包,获取已发送数据包的状态
// 获取状态是上面那个回调函数所做的是
esp_now_peer_info_t peerInfo; //注册对等点
peerInfo.channel = 0; //对端点用于发送/接受数据的WiFi通道设置为0
peerInfo.encrypt = false; //表示对端点发送/接受的数据是否加密对于本例,设置为false(不加密)
memcpy(peerInfo.peer_addr,MACB,6); //对端点设备地址,将其设置为广播地址
if(esp_now_add_peer(&peerInfo) != ESP_OK) //将“对等点”添加到列表中
{
Serial.println("add erreo");
return;
}
memcpy(peerInfo.peer_addr,MACC,6); //对端点设备地址,将其设置为广播地址
if(esp_now_add_peer(&peerInfo) != ESP_OK) //将“对等点”添加到列表中
{
Serial.println("add erreo");
return;
}
esp_now_register_recv_cb(OnDataRecv);
}
void loop() {
// put your main code here, to run repeatedly:
int i=0;
while(1)
{
i++;
if(i%2 == 0)
IDdata.relay = true;
else
IDdata.relay = false;
//发送的数据 本板id为1 后面可更改为字符串主板---x,y可为控制
IDdata.id = 1;
IDdata.x = 66;
esp_err_t result = esp_now_send(MACC,(uint8_t *)&IDdata,sizeof(IDdata));
if(result == ESP_OK)
Serial.println("发送成功");
else
Serial.println("发送失败");
delay(10000);
}
}
另外两块esp32的代码写法也是大同小异,我最后面都包含有的压缩包
esp间的通信完成后,该考虑的就是主esp32与onenet间的数据传输以及esp32与stm32的串口传输,然后onenet平台的搭建,推荐以下这篇博客
连接完onenet后,可以先尝试一下esp32与其的连接,看看数据是否互通,以下代码也是在csdn上参考一位大哥的,但是现在找不到,挂不了其链接
#include <Arduino.h>
#include "WiFi.h"
#include "PubSubClient.h"
#include "Ticker.h"
const char *ssid = "ylmting_PC"; //wifi名
const char *password = "18811111"; //wifi密码
const char *mqtt_server = "mqtts.heclouds.com"; //onenet 的 IP地址
const int port = 1883; //端口号
#define mqtt_devid "homedev" //设备ID
#define mqtt_pubid "551578" //产品ID
//鉴权信息
#define mqtt_password "version=2018-10-31&res=products%2F551578%2Fdevices%2Fhomedev&et=4096342304&method=md5&sign=CCdJNa17tk%2B88nKW0QWHIw%3D%3D" //鉴权信息
WiFiClient espClient; //创建一个WIFI连接客户端
PubSubClient client(espClient); // 创建一个PubSub客户端, 传入创建的WIFI客户端
char msgJson[75]; //发送信息缓冲区
//信息模板
char dataTemplate[] = "{\"id\":123,\"dp\":{\"Temperature\":[{\"v\":%.2f}],\"Humidity\":[{\"v\":%.2f}]}}"; //
Ticker tim1; //定时器,用来循环上传数据
//连接WIFI相关函数
void setupWifi()
{
delay(10);
Serial.println("连接WIFI");
WiFi.begin(ssid, password);
while (!WiFi.isConnected())
{
Serial.print(".");
delay(500);
}
Serial.println("OK");
Serial.println("Wifi连接成功");
}
//收到主题下发的回调, 注意这个回调要实现三个形参 1:topic 主题, 2: payload: 传递过来的信息 3: length: 长度
void callback(char *topic, byte *payload, unsigned int length)
{
Serial.println("message rev:");
Serial.println(topic);
for (size_t i = 0; i < length; i++)
{
Serial.print((char)payload[i]);
}
Serial.println();
}
//向主题发送模拟的温湿度数据
void sendTempAndHumi()
{
if (client.connected())
{
snprintf(msgJson, 75, dataTemplate, 26.0, 50.0); //将模拟温湿度数据套入dataTemplate模板中, 生成的字符串传给msgJson 75为字节数
Serial.print("public the data:");
Serial.println(msgJson);
client.publish("$sys/551578/homedev/dp/post/json", (uint8_t *)msgJson, strlen(msgJson));
//发送数据到主题
}
}
//重连函数, 如果客户端断线,可以通过此函数重连
void clientReconnect()
{
while (!client.connected()) //再重连客户端
{
Serial.println("reconnect MQTT...");
if (client.connect(mqtt_devid, mqtt_pubid, mqtt_password))
{
Serial.println("connected");
client.subscribe("$sys/551578/homedev/cmd/request/+"); //订阅命令下发主题
}
else
{
Serial.println("failed");
Serial.println(client.state());
Serial.println("try again in 5 sec");
delay(5000);
}
}
}
void setup()
{
Serial.begin(115200); //初始化串口
delay(3000); //这个延时是为了让我打开串口助手
setupWifi(); //调用函数连接WIFI
client.setServer(mqtt_server, port); //设置客户端连接的服务器,连接Onenet服务器, 使用6002端口
client.connect(mqtt_devid, mqtt_pubid, mqtt_password); //客户端连接到指定的产品的指定设备.同时输入鉴权信息
if (client.connected())
{
Serial.println("OneNet is connected!");//判断以下是不是连好了.
}
client.setCallback(callback); //设置好客户端收到信息是的回调
client.subscribe("$sys/551578/homedev/cmd/request/+"); //订阅命令下发主题
tim1.attach(10, sendTempAndHumi); //定时每10秒调用一次发送数据函数sendTempAndHumi
}
void loop()
{
if (!WiFi.isConnected()) //先看WIFI是否还在连接
{
setupWifi();
}
if (!client.connected()) //如果客户端没连接ONENET, 重新连接
{
clientReconnect();
delay(100);
}
client.loop(); //客户端循环检测 用于检测服务器是否有下发信息
}
注意上面那些设备id那些要用自己的哦
连接后,以上esp32-now连接的那仨可以取其中一个,来当作为主esp32用来连接onenet与stm32,将这个连接onenet的代码移植到now连接的代码即可,不过在这里我遇到了第1-2345第5个问题,就是将主esp32 连接wifi后 与另外两个esp32通信的时候,数据就会不稳定,传输时而成功时而失败,最后也只解决了一个次esp32发送数据到主esp32变得稳定.就是将esp32都连接上一个wifi,或者也可以将主esp32设置为ap+sta模式,然后次esp32直接连接主esp32的热点,也就是将那些处于一个wifi组网下。具体的代码资料等我会放在下面。
esp32间的联系完成以及连上onenet平台后实现数据传输后,就可以实现esp32与stm32的串口数据传输了在arduino上可以很方便的实现,
//初始串口
Serial2.begin(115200); //于stm32进行串口连接--使用串口2
//然后在回调函数处,将数据传输过去即可,不过应当注意你数据传输的协议的
//usart2传输协议Temperature:00.00!
// Humidity:00.00!(sprintf(usart2send,"Temperature:%0.2f!",board1.Temperature))
而在stm32端,我在这里就讲解普通的版本,在我的代码压缩包里面有一个freeRTOS版,以下是串口中断的处理函数,可以取得上面的Temperature:00.00 字符串
然后在下面调用哪个sscanf就可以得到温湿度的数据啦
void USART3_IRQHandler(void)
{
//判断接收中断标志是否为1
if(USART_GetITStatus(USART3, USART_IT_RXNE) == SET)
{
//清空标志位
USART_ClearITPendingBit(USART3, USART_IT_RXNE);
//接受数据 先赋值再加
buffer[count++] = USART_ReceiveData(USART3);
//判断接受的字符是否为 '!'
if(buffer[count-1] == '!')
{
//buffer循环赋值给rx_buffer, 但不需要赋值':' 过滤结束标志
for(rx_i=0; rx_i<(count-1); rx_i++)
{
rx_buffer[rx_i] = buffer[rx_i];
}
memset(buffer, 0, sizeof(buffer));
//确保下一帧存放位置从buffer[0]开始
count = 0;
rx_flag = 1;
}
}
}
if(rx_flag == 1)
{
printf("rx_buffer:%s\r\n", rx_buffer);
if(strstr(rx_buffer, "Temperature1"))
{
sscanf(rx_buffer,"Temperature1:%f,Humidity1:%f",&Temperature1,&Humidity1);
//拼接传输至串口屏的数据
sprintf((char *)send_screen_buff,"DHT11.t0.txt=\"%.2f\"\xFF\xFF\xFF",Temperature1);
//往串口屏传输数据
USART_SendString(USART2, send_screen_buff);
//延时2ms,确保数据已到位
delay_ms(2);
//清空字符串
memset(send_screen_buff, 0, sizeof(send_screen_buff));
//拼接湿度数据
sprintf((char *)send_screen_buff,"DHT11.t1.txt=\"%.2f\"\xFF\xFF\xFF",Humidity1);
USART_SendString(USART2, send_screen_buff);
memset(send_screen_buff, 0, sizeof(send_screen_buff));
}
if(strstr(rx_buffer, "Temperature2"))
{
sscanf(rx_buffer,"Temperature2:%f,Humidity2:%f",&Temperature2,&Humidity2);
//拼接传输至串口屏的数据
sprintf((char *)send_screen_buff,"DHT11.t2.txt=\"%.2f\"\xFF\xFF\xFF",Temperature2);
//往串口屏传输数据
USART_SendString(USART2, send_screen_buff);
//延时2ms,确保数据已到位
delay_ms(2);
//清空字符串
memset(send_screen_buff, 0, sizeof(send_screen_buff));
//拼接湿度数据
sprintf((char *)send_screen_buff,"DHT11.t3.txt=\"%.2f\"\xFF\xFF\xFF",Humidity2);
USART_SendString(USART2, send_screen_buff);
memset(send_screen_buff, 0, sizeof(send_screen_buff));
}
printf("Temperature1:%.2f,Humidity1:%.2f",Temperature1,Humidity1);
//清空数组
memset(rx_buffer, 0, sizeof(rx_buffer));
rx_flag = 0;
}
上面就是串口的处理函数,串口2是我连接的一个串口屏。
然后还有一个通过感受光照强度来控制led开关的,因为光敏电阻遇到光得话,阻值会降低,所有我们可以通过ADC来获取其变化,得到那个值,然后根据哪个值来控制灯得开关,这个比较简单。
然后还有一个串口屏传输数据过来时吗,用的中断大同小异,可以根据上面那个串口中断函数自行修改。
if(rx_flag_screen == 1)
{
printf("rx_buffer_screen:%s\r\n", rx_buffer_screen);
USART_SendString(USART3, rx_buffer_screen);
//清空数组
memset(rx_buffer_screen, 0, sizeof(rx_buffer_screen));
rx_flag_screen = 0;
}
上面用到得一个串口传输字符串得函数是
void USART_SendString(USART_TypeDef * pUSARTx, char *str)
{
unsigned int k=0;
while(*(str+k)!='\0')
{
// 发送一个字节数据到USART
USART_SendData(pUSARTx, *(str+k));
// 等待发送数据寄存器为空
while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
k++;
}
while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET); /* TC:传输完成标志 */
}
这个比较通用,可以用在所有串口传输的地方上,建议背诵全文哈哈。
以上还有很多没有讲到的地方。但是暂时没想起来 有什么不了解得欢迎过来一起交流
这个是云平台得最终效果
知识无价,欢迎分享。
链接: https://pan.baidu.com/s/1-veegbGcjwog4RjVP3MMGQ?pwd=2326 提取码: 2326 复制这段内容后打开百度网盘手机App,操作更方便哦