道生物联 TurMass™ 技术社区
直播中

HonestQiao

9年用户 577经验值
擅长:嵌入式技术
私信 关注

【道生物联TKB-623评估板试用】基于TKB-623远程通信的大棚环境监测系统

因为搭了一个大棚,为了方便检测大棚的信息,使用DFRobot FireBeetle 2 ESP32-C5,做了一个大棚环境监测系统,具体可见:FireBeetle 2 ESP32-C5之大棚环境监测系统

在之前的大棚环境监测系统系统中,我让ESP32-C5通过WiFi网络,连接到MQTT服务器,发布采集到的温湿度等信息。

现在,有了TKB-623,我进行了升级,让ESP32-C5通过TKB-623的远程无线通信功能,来发布采集到的信息。

相较于WiFi网络的方式,使用TKB-623无需再提供WiFi网络接入,远程采集数据更方便了。如果是在大规模种植农作物的大棚应用环境中,将大幅节省成本,简化检测设备部署环境。

下面,就分享基于TKB-623远程通信的大棚环境监测系统的具体过程。

一、系统设计

因为 FireBeetle 2 ESP32-C5之大棚环境监测系统 是在大棚中使用,并且集成了太阳能电池板,所以做了休眠和唤醒设计,系统的运行状态如图:
deepseek_mermaid_20251113_feea72.png

检测系统使用的传感器包括温湿度传感器、其他传感器、光线传感器,数据采集部分的逻辑流程如下:

deepseek_mermaid_20251113_085d6d.png

系统的整体运行流程如下:
deepseek_mermaid_20251113_b745cb.png

因为原有的已经实现了基于MQTT的通信,能够稳定运行了,我的做法是实现一个MQTT的伪装类,来接管MQTT的调用。

参考Arduino中Adafruit_MQTT的实现,做了TBK-623调用的伪造类实现,仅需要实现技术的功能即可。
参考之前 ESP32结合TKB-623评估板收发信息 中的处理,来做对应的具体实现。

初始化流程:

构造函数 → 引脚初始化 → 串口配置 → 模块唤醒 → AT命令测试 → 工作模式配置

数据发布流程:

publish() → 连接检查 → JSON序列化 → 十六进制转换 → AT+SENDB命令 → 响应验证

需要实现的类包括:
deepseek_mermaid_20251113_4ec289.png

deepseek_mermaid_20251113_73cd85.png

二、硬件准备
因为使用了电池和太阳能电池板,所以要考虑低功耗运行,实际运行时,让系统休眠300s后自动唤醒。
ESP32-C5本身支持深度随眠模式,并且 DFRobot FireBeetle 2 ESP32-C5 扩展板上,还有一个引脚,可以控制3.3V电压的输出:
image.png
具体位于扩展板上的如下位置:
image.png

另外,TKB-623本身也支持低功耗休眠模式:
image.png

可以通过AT指令,设置休眠唤醒参数:
image.png

因为是连接到了ESP32-C5,所以我使用了引脚唤醒模式:
image.png

唤醒引脚使用了GPIO_0:
image.png

要让TKB-623进入随眠模式,只需要使用下面的AT指令即可:
image.png

最终,具体的硬件连线如下:

FireBeetle 2 ESP32-C5扩展板 传感器引脚
3V SHT30-VIN
GND SHT30-GND
C-10 SHT30-SCL
D-9 SHT30-SDA
3V BMP180-3.3
GND BMP180-GND
C-10 BMP180-SCL
D-9 BMP180-SDA
3V 光线传感器-3.3
GND 光线传感器-GND
2 光线传感器-A
BAT+ 锂电池+
BAT- 锂电池-
BAT_ADC-IO1 电池电压检测
VIN:5V 太阳能电池板+
GND 太阳能电池板-
SPI-24 TKB-623: UART_RXD
SPI-23 TKB-623: UART_TXD
SPI-3V3 TKB-623: 3V3_M
SPI-GND TKB-623: GND
3 TKB-623: GPIO_0

三、代码开发
FireBeetle 2 ESP32-C5之大棚环境监测系统
中,已经提供了原有的MQTT版本的代码,这里主要说明TK8620_MQTT部分。

最终实现的`TK8620_MQTT.h如下:

#ifndef TK8620_MQTT_H
#define TK8620_MQTT_H

#include <Arduino.h>
#include <HardwareSerial.h>
#include <ArduinoJson.h>

// TK8620 串口配置
#define TK8620_TX 24
#define TK8620_RX 23
#define TK8620_WAKEUP_PIN 3  // TK8620唤醒引脚
#define TK8620_SERIAL_NUM 1
#define TK8620_AT_DELAY   200 // 发送延时

// 模拟 MQTT 错误代码
enum TK8620_Error {
    TK8620_CONNECTION_ACCEPTED = 0,
    TK8620_CONNECTION_REFUSED_PROTOCOL_VERSION = 1,
    TK8620_CONNECTION_REFUSED_IDENTIFIER_REJECTED = 2,
    TK8620_CONNECTION_REFUSED_SERVER_UNAVAILABLE = 3,
    TK8620_CONNECTION_REFUSED_BAD_USERNAME_PASSWORD = 4,
    TK8620_CONNECTION_REFUSED_NOT_AUTHORIZED = 5,
    TK8620_CONNECTION_TIMEOUT = -1,
    TK8620_CONNECTION_LOST = -2,
    TK8620_CONNECTION_FAILED = -3
};

class TK8620_MQTT_Client {
private:
    HardwareSerial* _serial;
    bool _connected;
    bool _sleeping;
    String _server;
    uint16_t _port;
    String _username;
    String _password;

    bool configureModule();
    bool sendATCommand(const char* command);
    bool sendATCommandWithRetry(const char* command, int retries = 3);
    String readSerialResponse();
    String stringToHex(const String& input);
    void wakeupModule();
    void sleepModule();

public:
    TK8620_MQTT_Client(void* client, const char* server, uint16_t port, const char* username, const char* password);

    bool connected();
    bool isSleeping();
    int8_t connect();
    void disconnect();
    bool publish(const char* topic, const char* message);

    // 休眠控制
    void enterSleep();
    void wakeup();

    // 模拟 Adafruit_MQTT 的错误字符串函数
    const char* connectErrorString(int8_t error);
};

class TK8620_MQTT_Publish {
private:
    TK8620_MQTT_Client* _client;
    String _topic;

public:
    TK8620_MQTT_Publish(TK8620_MQTT_Client* client, const char* topic);

    bool publish(const char* message);
    bool publish(float message);
    bool publish(int message);
    bool publish(uint32_t message);
};

// ============================
// TK8620_MQTT_Client 实现
// ============================

TK8620_MQTT_Client::TK8620_MQTT_Client(void* client, const char* server, uint16_t port,
                                      const char* username, const char* password) {
    _serial = &Serial1;
    _connected = false;
    _sleeping = true;
    _server = String(server);
    _port = port;
    _username = String(username);
    _password = String(password);

    // 初始化唤醒引脚
    pinMode(TK8620_WAKEUP_PIN, OUTPUT);
    digitalWrite(TK8620_WAKEUP_PIN, LOW); // 初始状态为低电平,模块休眠
}

void TK8620_MQTT_Client::wakeupModule() {
    if (_sleeping) {
        Serial.println("? 唤醒TK8620模块...");
        digitalWrite(TK8620_WAKEUP_PIN, HIGH); // 高电平唤醒模块
        delay(100); // 等待模块稳定
        _sleeping = false;

        // 等待模块就绪
        unsigned long startTime = millis();
        while (millis() - startTime < 3000) {
            if (sendATCommand("AT")) {
                Serial.println("✅ TK8620模块已唤醒并就绪");
                break;
            }
            delay(TK8620_AT_DELAY);
        }
    }
}

void TK8620_MQTT_Client::sleepModule() {
    if (!_sleeping) {
        Serial.println("? 让TK8620模块进入休眠...");

        // 发送休眠命令
        if (sendATCommand("AT+ENTERSLEEP")) {
            Serial.println("✅ TK8620休眠命令发送成功");
        } else {
            Serial.println("⚠️ TK8620休眠命令发送失败,强制休眠");
        }

        // 无论命令是否成功,都设置唤醒引脚为低电平
        digitalWrite(TK8620_WAKEUP_PIN, LOW);
        _sleeping = true;
        _connected = false;

        // 清空串口缓冲区
        while(_serial->available()) {
            _serial->read();
        }
    }
}

bool TK8620_MQTT_Client::configureModule() {
    Serial.println("? 配置TK8620模块为变长突发模式...");

    bool success = true;

    // 先发送AT测试命令
    if (!sendATCommandWithRetry("AT", 10)) {
        Serial.println("❌ AT测试失败");
        success = false;
    } else {
        Serial.println("✅ AT测试成功");
    }
    delay(TK8620_AT_DELAY);

    // 配置唤醒功能
    if (!sendATCommandWithRetry("AT+WAKEUPCFG=0,1")) {
        success = false;
        Serial.println("❌ 唤醒配置失败");
    } else {
        Serial.println("✅ 唤醒配置成功");
    }
    delay(TK8620_AT_DELAY);

    if (!sendATCommandWithRetry("AT+WORKMODE=21")) {        // 设置工作模式为变长突发模式
        success = false;
        Serial.println("❌ 工作模式设置失败");
    }
    delay(TK8620_AT_DELAY);

    if (!sendATCommandWithRetry("AT+FREQ=490300000,490300000,490300000")) { // 设置频率
        success = false;
        Serial.println("❌ 频率设置失败");
    }
    delay(TK8620_AT_DELAY);

    if (!sendATCommandWithRetry("AT+TXP=15")) {             // 设置发射功率
        success = false;
        Serial.println("❌ 发射功率设置失败");
    }
    delay(TK8620_AT_DELAY);

    if (!sendATCommandWithRetry("AT+RATE=6")) {             // 设置通信速率
        success = false;
        Serial.println("❌ 通信速率设置失败");
    }
    delay(TK8620_AT_DELAY);

    if (!sendATCommandWithRetry("AT+ADDRFILTER=0")) {       // 关闭地址过滤,接收所有数据
        success = false;
        Serial.println("❌ 地址过滤设置失败");
    }
    delay(TK8620_AT_DELAY);

    if (success) {
        Serial.println("✅ TK8620变长突发模式配置完成");
        _connected = true;
    } else {
        Serial.println("⚠️ TK8620配置有部分失败,但将继续运行");
        _connected = true; // 仍然标记为已连接,允许尝试发送
    }

    return success;
}

bool TK8620_MQTT_Client::sendATCommandWithRetry(const char* command, int retries) {
    for (int i = 0; i < retries; i++) {
        if (sendATCommand(command)) {
            return true;
        }
        Serial.print("? 重试命令 (");
        Serial.print(i + 1);
        Serial.print("/");
        Serial.print(retries);
        Serial.println(")...");
        delay(TK8620_AT_DELAY);
    }
    return false;
}

bool TK8620_MQTT_Client::sendATCommand(const char* command) {
    // 确保模块已唤醒
    wakeupModule();

    // 清空接收缓冲区
    while(_serial->available()) {
        _serial->read();
    }

    _serial->println(command);

    #if (DEBUG_MODE == 1)
    Serial.print("? 发送AT: ");
    Serial.println(command);
    #endif

    // 读取响应
    String response = readSerialResponse();

    if (response.length() > 0) {
        #if (DEBUG_MODE == 1)
        Serial.print("? 原始响应: ");
        // 打印原始响应(包括不可见字符)
        for (unsigned int i = 0; i < response.length(); i++) {
            char c = response[i];
            if (c == '\r') {
                Serial.print("\\r");
            } else if (c == '\n') {
                Serial.print("\\n");
            } else {
                Serial.print(c);
            }
        }
        Serial.println();
        #endif

        // 检查响应内容
        if (response.indexOf("AT_OK") >= 0) {
            #if (DEBUG_MODE == 1)
            Serial.println("✅ 检测到AT_OK");
            #endif
            return true;
        } else if (response.indexOf("OK") >= 0) {
            #if (DEBUG_MODE == 1)
            Serial.println("✅ 检测到OK");
            #endif
            return true;
        } else if (response.indexOf("ERROR") >= 0) {
            #if (DEBUG_MODE == 1)
            Serial.println("❌ 检测到ERROR");
            #endif
        }
    } else {
        #if (DEBUG_MODE == 1)
        Serial.println("❌ 无响应");
        #endif
    }

    return false;
}

String TK8620_MQTT_Client::readSerialResponse() {
    String response = "";
    unsigned long startTime = millis();

    // 等待响应开始
    while (millis() - startTime < 3000) {
        if (_serial->available()) {
            break;
        }
        delay(10);
    }

    // 读取所有可用数据
    while (millis() - startTime < 3000) {
        if (_serial->available()) {
            char c = _serial->read();
            response += c;

            // 如果检测到AT_OK或AT_ERROR,可以提前退出
            if (response.indexOf("AT_OK") >= 0 || response.indexOf("AT_ERROR") >= 0) {
                // 再等待一小段时间,看是否有更多数据
                delay(100);
                break;
            }

            // 如果没有新数据一段时间,认为响应结束
            unsigned long lastDataTime = millis();
            while (millis() - lastDataTime < 200) {
                if (_serial->available()) {
                    break;
                }
                delay(10);
            }
            if (millis() - lastDataTime >= 200) {
                break;
            }
        } else {
            delay(10);
        }
    }

    return response;
}

String TK8620_MQTT_Client::stringToHex(const String& input) {
    String hexString = "";
    for (size_t i = 0; i < input.length(); i++) {
        char hex[3];
        sprintf(hex, "%02X", (unsigned char)input[i]);
        hexString += hex;
    }
    return hexString;
}

bool TK8620_MQTT_Client::connected() {
    return _connected && !_sleeping;
}

bool TK8620_MQTT_Client::isSleeping() {
    return _sleeping;
}

int8_t TK8620_MQTT_Client::connect() {
    if (_connected && !_sleeping) {
        return TK8620_CONNECTION_ACCEPTED;
    }

    // 唤醒模块
    wakeupModule();

    // 初始化串口
    _serial->begin(115200, SERIAL_8N1, TK8620_RX, TK8620_TX);
    delay(2000);

    // 清空串口缓冲区
    _serial->flush();
    while(_serial->available()) {
        _serial->read();
    }

    Serial.println("? 初始化TK8620无线模块...");
    Serial.print("  TK8620 TX -> ESP32 引脚 ");
    Serial.println(TK8620_TX);
    Serial.print("  TK8620 RX -> ESP32 引脚 ");
    Serial.println(TK8620_RX);
    Serial.print("  TK8620 WAKEUP -> ESP32 引脚 ");
    Serial.println(TK8620_WAKEUP_PIN);

    if (configureModule()) {
        return TK8620_CONNECTION_ACCEPTED;
    } else {
        return TK8620_CONNECTION_FAILED;
    }
}

void TK8620_MQTT_Client::disconnect() {
    _connected = false;
    // 不立即休眠,让用户决定何时休眠
}

bool TK8620_MQTT_Client::publish(const char* topic, const char* message) {
    if (!_connected || _sleeping) {
        if (connect() != TK8620_CONNECTION_ACCEPTED) {
            return false;
        }
    }

    // 创建JSON对象,包含topic和message
    StaticJsonDocument<200> doc;
    doc["t"] = topic;
    doc["m"] = message;
    // doc["timestamp"] = millis();

    // 序列化JSON
    String jsonString;
    serializeJson(doc, jsonString);

    #if (DEBUG_MODE == 1)
    Serial.println("\n? 准备通过TK8620发送数据:");
    Serial.print("  主题: ");
    Serial.println(topic);
    Serial.print("  消息: ");
    Serial.println(message);
    Serial.print("  原始JSON: ");
    Serial.println(jsonString);
    #endif

    // 转换为十六进制
    String hexData = stringToHex(jsonString);

    #if (DEBUG_MODE == 1)
    Serial.print("  十六进制数据: ");
    Serial.println(hexData);
    #endif

    int retries = 3;
    bool ret = false;
    for(int i=0; i<retries; i++) {
        // 使用AT+SENDB发送十六进制数据
        String atCommand = "AT+SENDB=" + hexData;
        _serial->println(atCommand);

        #if (DEBUG_MODE == 1)
        if (i>0) {
            Serial.print("? 重试AT命令: ");
        } else {
            Serial.print("? 发送AT命令: ");
        }
        Serial.println(atCommand);
        #endif

        // 等待响应
        String response = readSerialResponse();
        if (response.indexOf("AT_OK") >= 0) {
            Serial.println("✅ TK8620发送成功!");
            ret = true;
            break;
        } else {
            Serial.println("❌ TK8620发送失败!");
            #if (DEBUG_MODE == 1)
            Serial.print("  响应: ");
            Serial.println(response);
            #endif
        }
        delay(TK8620_AT_DELAY);
    }
    return ret;
}

void TK8620_MQTT_Client::enterSleep() {
    sleepModule();
}

void TK8620_MQTT_Client::wakeup() {
    wakeupModule();
}

const char* TK8620_MQTT_Client::connectErrorString(int8_t error) {
    switch (error) {
        case TK8620_CONNECTION_ACCEPTED:
            return "Connection accepted";
        case TK8620_CONNECTION_REFUSED_PROTOCOL_VERSION:
            return "Connection refused, unacceptable protocol version";
        case TK8620_CONNECTION_REFUSED_IDENTIFIER_REJECTED:
            return "Connection refused, identifier rejected";
        case TK8620_CONNECTION_REFUSED_SERVER_UNAVAILABLE:
            return "Connection refused, server unavailable";
        case TK8620_CONNECTION_REFUSED_BAD_USERNAME_PASSWORD:
            return "Connection refused, bad user name or password";
        case TK8620_CONNECTION_REFUSED_NOT_AUTHORIZED:
            return "Connection refused, not authorized";
        case TK8620_CONNECTION_TIMEOUT:
            return "Connection timeout";
        case TK8620_CONNECTION_LOST:
            return "Connection lost";
        case TK8620_CONNECTION_FAILED:
            return "Connection failed";
        default:
            return "Unknown error";
    }
}

// ============================
// TK8620_MQTT_Publish 实现
// ============================

TK8620_MQTT_Publish::TK8620_MQTT_Publish(TK8620_MQTT_Client* client, const char* topic) {
    _client = client;
    _topic = String(topic);
}

bool TK8620_MQTT_Publish::publish(const char* message) {
    return _client->publish(_topic.c_str(), message);
}

bool TK8620_MQTT_Publish::publish(float message) {
    char buffer[20];
    dtostrf(message, 1, 2, buffer);
    return publish(buffer);
}

bool TK8620_MQTT_Publish::publish(int message) {
    char buffer[20];
    itoa(message, buffer, 10);
    return publish(buffer);
}

bool TK8620_MQTT_Publish::publish(uint32_t message) {
    char buffer[20];
    utoa(message, buffer, 10);
    return publish(buffer);
}

#endif

这部分的代码逻辑,在前面已经说明过了。

在主代码中,添加一个选项,表示是否启用:

#define ENABLE_TK8620           1     // 启用TK8620无线传输

然后,根据定义来调用对应的头文件即可:

// 根据选择包含不同的头文件
#if (ENABLE_MQTT == 1 && ENABLE_TK8620 == 0)
#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_Client.h"
#elif (ENABLE_TK8620 == 1)
#include "TK8620_MQTT.h"
#define Adafruit_MQTT_Client TK8620_MQTT_Client
#define Adafruit_MQTT_Publish TK8620_MQTT_Publish
#endif

最后,在ESP32-C5进入深度睡眠之前,让TKB-623先进入休眠模式:

#if (ENABLE_SLEEP == 1)
if (loopCnt >= 1 && !skipSleep) {
Serial.println("进入休眠状态," + String(TIME_TO_SLEEP) + "秒后自动唤醒");
Serial.println("长按BOOT按钮可取消休眠");
Serial.flush();

#if (ENABLE_MQTT == 1)
delay(1000);

// 断开MQTT连接
if (mqtt.connected()) {
mqtt.disconnect();
}

#if (ENABLE_TK8620 == 0)
// 断开WiFi以节省功耗
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
#elif (ENABLE_TK8620 == 1)
// 让TK8620模块进入休眠
mqtt.enterSleep();
Serial.println("✅ TK8620模块已进入休眠");
#endif
#endif

// 进入休眠
digitalWrite(POWER_PIN, LOW);                      // 关闭外设供电
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
esp_deep_sleep_start();

}
#endif

经过这样的处理,同一套代码,能够兼容MQTT和TKB-623两种数据发布的模式。

然后,PC端,参考 ESP32结合TKB-623评估板收发信息,将收到的json信息解码,然后发布到MQTT即可。

四、实测运行
修改代码后,编译烧录到ESP32-C5,运行效果如下:
iShot_2025-11-13_10.51.45.png
ESP32-C5启动/唤醒后,会自动进行数据采集,并唤醒TKB-623,并进行运行模式设置:
iShot_2025-11-13_10.51.57.png

然后发送采集到的传感器的数据:
iShot_2025-11-13_10.52.06.png

发送完成后,会自动进入深度随眠,并让TKB-623休眠:
iShot_2025-11-13_10.52.48.png

而在pc接收端,启动后,会线进行TKB-623工作模式配置:
ScreenShot_2025-11-14_103235_448.png

然后,接受到数据后,会转发到MQTT服务器:
ScreenShot_2025-11-14_103047_695.png

在MQTT服务上,也可以看到定时发布的信息:
ScreenShot_2025-11-14_103324_849.png

五、部署运行
测试完成后,将设备放置到大棚里面:
053bf08b1743ee1d0e0df63c21438129.jpg

就可以通过手持终端,查看检测大棚的状态了:
2967fe4c9c2ddfb1f62b1bb271e691b4.jpg

更多回帖

发帖
×
20
完善资料,
赚取积分