# ml307_arduino **Repository Path**: weirdor_admin/ml307_arduino ## Basic Information - **Project Name**: ml307_arduino - **Description**: No description available - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-06-10 - **Last Updated**: 2026-06-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # ML307 Arduino Driver — 使用文档 适用于 **ML307R / EC801E / NT26K** LTE Cat.1 4G 模组的 Arduino 驱动库。 基于 [esp-ml307](https://github.com/78/esp-ml307) (by 虾哥 Terrence) 适配为 Arduino 版本。 --- ## 目录 - [1. 功能特性](#1-功能特性) - [2. 支持的模组](#2-支持的模组) - [3. 硬件连接](#3-硬件连接) - [4. 安装](#4-安装) - [5. 快速开始](#5-快速开始) - [6. API 参考](#6-api-参考) - [6.1 AtModem — 模组管理](#61-atmodem--模组管理) - [6.2 AtUart — 底层UART通信](#62-atuart--底层uart通信) - [6.3 TcpClient — TCP 客户端](#63-tcpclient--tcp-客户端) - [6.4 UdpClient — UDP 客户端](#64-udpclient--udp-客户端) - [6.5 HttpClient — HTTP 客户端](#65-httpclient--http-客户端) - [6.6 MqttClient — MQTT 客户端](#66-mqttclient--mqtt-客户端) - [6.7 WebSocketClient — WebSocket 客户端](#67-websocketclient--websocket-客户端) - [7. 网络状态与错误处理](#7-网络状态与错误处理) - [8. 回调机制](#8-回调机制) - [9. 内存管理](#9-内存管理) - [10. 低功耗模式](#10-低功耗模式) - [11. 调试](#11-调试) - [12. 常见问题](#12-常见问题) - [13. 示例索引](#13-示例索引) --- ## 1. 功能特性 | 功能 | 说明 | |------|------| | 自动模组检测 | 上电自动识别 ML307 / EC801E / NT26K,无需手动指定 | | AT 命令通信 | 后台 UART 解析任务,统一处理命令响应、URC 回调和超时 | | TCP / SSL TCP | ML307 使用 `MIPOPEN`,EC801E / NT26K 使用 `QIOPEN` / `QSSLOPEN` | | UDP | UDP 数据报收发,支持远程地址回调 | | HTTP / HTTPS | 基于 TCP / SSL 的 GET / POST / PUT,请求头和响应解析统一处理 | | MQTT | ML307 使用 `MQTT*` 指令,EC801E / NT26K 使用 `QMT*` 指令 | | WebSocket | 基于 TCP / SSL 的 WebSocket 握手、文本帧和二进制帧收发 | | 网络回调 | 异步通知网络注册、断连事件 | | 信号强度 | 实时读取 CSQ (0–31) | | 低功耗 | DTR 引脚控制模组休眠/唤醒 | --- ## 2. 支持的模组 | 模组 | 状态 | 备注 | |------|------|------| | ML307R | ✅ 完全支持 | 推荐使用,AT 指令集最完善 | | ML307A | ✅ 完全支持 | 同 ML307R | | EC801E | ✅ 完全支持 | 需确认固件是否烧录 SSL | | NT26K | ✅ 完全支持 | 使用 EC801E AT 指令集 | > **模组检测逻辑**:库通过 `AT+CGMR` 返回的固件版本字符串自动判断模组类型。 > 若字符串以 `EC801E` / `NT26K` 开头则创建 `EC801EModem` 实例,否则默认创建 `ML307Modem`。 --- ## 3. 硬件连接 ### 3.1 典型接线 ``` ESP32 (DevKit) ML307 模组 ───────────── ────────── GPIO17 (TX2) ────────> RXD (模组接收) GPIO16 (RX2) ────────> TXD (模组发送) GPIO15 ────────> DTR (唤醒引脚,可选,默认 -1 禁用) GPIO13 ────────> RI (来电/唤醒指示,可选,默认 -1 禁用) GND ────────> GND ``` ### 3.2 供电要求 | 项目 | 要求 | |------|------| | 电压 | 3.8V – 4.2V(推荐 4V),部分模组支持 5V | | 峰值电流 | **2A**(注册网络瞬间) | | 待机电流 | 约 10–30mA | | 供电方式 | **独立 DC-DC 模块**,不要从 ESP32 3.3V 引脚取电 | > ⚠️ **务必使用独立电源!** ESP32 的 3.3V LDO 通常只能提供 500–800mA, > 无法满足 4G 模组的峰值电流需求,会导致反复重启或注册失败。 ### 3.3 天线 - 必须连接 **4G LTE 全频段天线**(SMA / IPEX 接口) - **严禁不接天线时注册网络**,否则发射功率无法辐射,可能烧毁模组射频前端 --- ## 4. 安装 ### 方法一:Arduino IDE 库管理器(推荐) > 尚未上架,敬请期待。 ### 方法二:手动安装 ZIP 1. 将 `ML307_Arduino/` 整个文件夹打包为 `ML307_Arduino.zip` 2. Arduino IDE → `项目` → `加载库` → `添加 .ZIP 库...` → 选择 ZIP 文件 ### 方法三:直接复制 将 `ML307_Arduino/` 文件夹复制到 Arduino 的 libraries 目录: - **Windows**: `Documents/Arduino/libraries/` - **macOS**: `~/Documents/Arduino/libraries/` - **Linux**: `~/Arduino/libraries/` --- ## 5. 快速开始 ### 最小示例 ```cpp #include HardwareSerial modemSerial(1); // 使用 ESP32 的 Serial1 std::unique_ptr modem; void setup() { Serial.begin(115200); // 调试串口 // 1. 初始化模组串口 modemSerial.begin(115200, SERIAL_8N1, 16, 17); // 2. 自动检测模组 modem = AtModem::Detect(&modemSerial, 17, 16, -1, -1, 115200); if (!modem) { Serial.println("❌ 模组检测失败,请检查接线和供电"); while (1) delay(1000); } Serial.printf("✅ 检测到模组: %s\n", modem->getModuleRevision().c_str()); // 3. 等待网络注册 NetworkStatus status = modem->waitForNetworkReady(60000); if (status != NET_READY) { Serial.printf("❌ 网络注册失败, 状态码: %d\n", status); while (1) delay(1000); } Serial.println("✅ 网络已注册"); Serial.printf(" 运营商: %s\n", modem->getCarrierName().c_str()); Serial.printf(" 信号强度: %d\n", modem->getCsq()); Serial.printf(" IMEI: %s\n", modem->getImei().c_str()); } void loop() { delay(10000); if (modem->isNetworkReady()) { Serial.printf("[%lu s] 信号强度: %d\n", millis() / 1000, modem->getCsq()); } } ``` **参数说明**(`AtModem::Detect`): ```cpp static std::unique_ptr Detect( HardwareSerial* serial, // 模组使用的串口对象指针 int tx_pin, // ESP32 TX → 模组 RX (GPIO) int rx_pin, // ESP32 RX → 模组 TX (GPIO) int dtr_pin = -1, // DTR 唤醒引脚(-1 = 不使用) int ri_pin = -1, // RI 来电指示引脚(-1 = 不使用) unsigned long baud = 115200, // 初始波特率 unsigned long timeout = 5000 // 检测超时(ms) ); ``` --- ## 6. API 参考 ### 6.1 AtModem — 模组管理 模组的核心管理类,提供网络注册、信息查询和协议客户端工厂方法。 #### 6.1.1 工厂方法 ```cpp static std::unique_ptr AtModem::Detect( HardwareSerial* serial, int tx_pin, int rx_pin, int dtr_pin = -1, int ri_pin = -1, unsigned long baud_rate = 115200, unsigned long timeout_ms = 5000 ); ``` 自动检测模组类型并创建对应实例。 - **返回值**:成功返回 `std::unique_ptr`(实际对象是 `ML307Modem` 或 `EC801EModem`),失败返回空指针 - **内存管理**:无需手动 `delete`,对象离开作用域后自动释放 ```cpp auto modem = AtModem::Detect(&modemSerial, MODEM_TX, MODEM_RX, MODEM_DTR); if (!modem) { Serial.println("Modem detect failed"); } ``` #### 6.1.2 网络管理 ```cpp NetworkStatus waitForNetworkReady(unsigned long timeout_ms = 60000); ``` 阻塞等待网络注册完成。 - `timeout_ms`: 超时时间(毫秒),默认 60 秒 - **返回值**: | 值 | 含义 | |----|------| | `NET_READY` (0) | 网络注册成功 | | `NET_ERROR` (1) | 未指定的网络错误 | | `NET_ERROR_INSERT_PIN` (-1) | SIM 卡未插入或 PIN 码锁定 | | `NET_ERROR_REGISTRATION_DENIED` (-2) | 网络拒绝注册(欠费/无服务) | | `NET_ERROR_TIMEOUT` (-3) | 等待超时 | ```cpp void setFlightMode(bool enable); ``` 飞行模式切换。 - `true`: 关闭射频(`AT+CFUN=4`) - `false`: 恢复正常模式(`AT+CFUN=1`) ```cpp void reboot(); ``` 通过 `AT+NRB` 重启模组,重启后需重新等待网络注册。 #### 6.1.3 状态查询 ```cpp bool isPinReady() const; // SIM 卡 PIN 码状态(READY = true) bool isNetworkReady() const; // 网络是否已注册(stat=1 或 stat=5) ``` #### 6.1.4 模组信息 ```cpp std::string getImei(); // 获取 IMEI(来自 AT+CGSN=1) std::string getIccid(); // 获取 SIM 卡 ICCID(来自 AT+ICCID) std::string getModuleRevision(); // 获取模组固件版本(来自 AT+CGMR) std::string getCarrierName(); // 获取运营商名称(来自 AT+COPS?) int getCsq(); // 获取信号强度 0–31(来自 AT+CSQ) CeregState getRegistrationState(); // 获取详细注册状态(来自 AT+CEREG?) ``` `CeregState` 结构体: ```cpp struct CeregState { int stat; // 0=未注册, 1=已注册(本地), 2=正在搜索, 3=被拒绝, 5=已注册(漫游) std::string tac; // Tracking Area Code std::string ci; // Cell ID int act; // 接入技术: 7=LTE Cat.M1, 9=LTE Cat.NB1 std::string toString();// JSON 格式字符串 }; ``` #### 6.1.5 回调 ```cpp void onNetworkStateChanged(std::function cb); ``` 网络状态变化时触发。`ready` 为 `true` 表示注册成功,`false` 表示掉线。 #### 6.1.6 协议客户端工厂 大写方法返回 `std::unique_ptr`,推荐新代码使用: ```cpp std::unique_ptr CreateTcp(int connectId = 0); std::unique_ptr CreateSsl(int connectId = 0); std::unique_ptr CreateUdp(int connectId = 0); std::unique_ptr CreateHttp(int connectId = 0); std::unique_ptr CreateMqtt(int connectId = 0); std::unique_ptr CreateWebSocket(int connectId = 0); ``` ```cpp auto tcp = modem->CreateTcp(0); auto http = modem->CreateHttp(1); auto mqtt = modem->CreateMqtt(2); ``` - `connectId`: 连接 ID(0–5),用于区分同时存在的多个连接,默认为 0 - 不同类型客户端可共用同一个 connectId(但同一类型不行) --- ### 6.2 AtUart — 底层UART通信 > 通常不需要直接使用此类,由 `AtModem` 内部管理。 ```cpp // 仅当需要直接发送 AT 命令时使用 auto uart = modem->GetAtUart(); uart->sendCommand("AT+CUSTOMCMD", 3000); std::string response = uart->getResponse(); ``` | 方法 | 说明 | |------|------| | `sendCommand(cmd, timeout)` | 发送 AT 命令并等待响应,返回 `bool` | | `sendCommandWithData(cmd, data, len, timeout)` | 发送带二进制数据的 AT 命令 | | `getResponse()` | 获取最近一次命令的响应字符串 | | `getCmeErrorCode()` | 获取 CME ERROR 码(AT 命令失败时) | | `setBaudRate(baud, timeout)` | 切换波特率(如 921600) | | `setDtrPin(high)` | 控制 DTR 引脚电平 | | `setDebug(enable)` | 开启/关闭 AT 命令调试输出 | | `encodeHex(data)` | 将字符串编码为十六进制 | | `decodeHex(hex)` | 将十六进制解码为字符串 | --- ### 6.3 TcpClient — TCP 客户端 #### 6.3.1 创建 ```cpp auto tcp = modem->CreateTcp(0); // 普通 TCP auto ssl = modem->CreateSsl(1); // SSL/TLS TCP ``` #### 6.3.2 方法 ```cpp // 连接 bool connect(const std::string& host, int port, unsigned long timeout_ms = 15000); bool connectSSL(const std::string& host, int port, unsigned long timeout_ms = 15000); // 断开 void disconnect(); // 发送 int send(const std::string& data); int send(const uint8_t* data, size_t len); // 可直接发送二进制数据 // 状态 bool isConnected() const; int getLastError() const; int getConnectId() const; ``` #### 6.3.3 回调 ```cpp void onData(std::function cb); void onDisconnected(std::function cb); void onError(std::function cb); ``` #### 6.3.4 完整示例 ```cpp auto tcp = modem->CreateTcp(0); tcp->onData([](const std::string& data) { Serial.printf("📥 收到 %d 字节: %s\n", data.size(), data.c_str()); }); tcp->onDisconnected([]() { Serial.println("🔌 TCP 连接已断开"); }); tcp->onError([](int err) { Serial.printf("❌ TCP 错误码: %d\n", err); }); if (tcp->connect("example.com", 80)) { tcp->send("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n"); delay(3000); tcp->disconnect(); } ``` --- ### 6.4 UdpClient — UDP 客户端 ```cpp auto udp = modem->CreateUdp(0); udp->onData([](const std::string& data, const std::string& remoteIp, int remotePort) { Serial.printf("📥 UDP 来自 %s:%d -> %s\n", remoteIp.c_str(), remotePort, data.c_str()); }); if (udp->begin("192.168.1.100", 8888)) { udp->send("Hello UDP!"); } // ... udp->stop(); ``` | 方法 | 说明 | |------|------| | `begin(host, port, timeout)` | 绑定目标地址并打开 UDP 通道 | | `send(data)` / `send(buf, len)` | 发送数据 | | `stop()` | 关闭 UDP 通道 | | `isOpen()` | 是否已打开 | | `onData(cb)` | 数据回调 `(data, remoteIp, remotePort)` | --- ### 6.5 HttpClient — HTTP 客户端 #### 6.5.1 方法 ```cpp // 请求 bool get(const std::string& url, unsigned long timeout_ms = 30000); bool post(const std::string& url, const std::string& body, const std::string& contentType = "application/x-www-form-urlencoded", unsigned long timeout_ms = 30000); bool put(const std::string& url, const std::string& body, const std::string& contentType = "application/x-www-form-urlencoded", unsigned long timeout_ms = 30000); // Header 管理 void addHeader(const std::string& key, const std::string& value); void clearHeaders(); // 响应 int getStatusCode() const; std::string getResponseBody() const; std::string getResponseHeader() const; std::string getResponse() const; // headers + "\r\n\r\n" + body // SSL void setCACert(const std::string& caCert); // HTTPS CA 证书(PEM 格式) // 关闭 void close(); ``` #### 6.5.2 示例 ```cpp auto http = modem->CreateHttp(); // GET 请求 if (http->get("http://api.example.com/data")) { Serial.printf("状态码: %d\n", http->getStatusCode()); Serial.printf("响应体: %s\n", http->getResponseBody().c_str()); } // POST 请求(JSON) http->addHeader("Authorization", "Bearer xxxxx"); http->addHeader("X-Device-Id", "esp32-001"); if (http->post("http://api.example.com/report", "{\"temperature\":25.5,\"humidity\":60}", "application/json")) { Serial.printf("上报成功: %d\n", http->getStatusCode()); } http->close(); ``` > **注意**:当前版本 HTTP 客户端基于本库的 `TcpClient` / SSL TCP 实现, > 因此 ML307 与 EC801E / NT26K 共用同一套 HTTP 解析逻辑。默认使用 `Connection: close`, > 需要复用连接时可使用 `SetKeepAlive(true)`。 --- ### 6.6 MqttClient — MQTT 客户端 #### 6.6.1 配置 ```cpp void setKeepAlive(int seconds); // 心跳周期,默认 120s void setCleanSession(bool clean); // Clean Session,默认 true void setWill(const std::string& topic, // 遗嘱消息 const std::string& payload, int qos = 0, bool retain = false); ``` #### 6.6.2 方法 ```cpp bool connect(const std::string& broker, int port, const std::string& clientId, const std::string& username = "", const std::string& password = "", unsigned long timeout_ms = 30000); void disconnect(); bool isConnected() const; bool publish(const std::string& topic, const std::string& payload, int qos = 0, bool retain = false); bool subscribe(const std::string& topic, int qos = 0); bool unsubscribe(const std::string& topic); int getLastError() const; ``` #### 6.6.3 回调 ```cpp void onConnected(std::function cb); void onDisconnected(std::function cb); void onMessage(std::function cb); void onError(std::function cb); ``` #### 6.6.4 完整示例 ```cpp auto mqtt = modem->CreateMqtt(); mqtt->onConnected([]() { Serial.println("✅ MQTT 已连接"); }); mqtt->onMessage([](const std::string& topic, const std::string& payload) { Serial.printf("📨 [%s] %s\n", topic.c_str(), payload.c_str()); // 在这里处理下发的控制指令 }); mqtt->onDisconnected([]() { Serial.println("🔌 MQTT 已断开,需要重连"); }); mqtt->onError([](int err) { Serial.printf("❌ MQTT 错误: %d\n", err); }); // 连接(中国移动 OneNET 示例) if (mqtt->connect("183.230.40.39", 6002, "my-device-001", "product_id", "access_key")) { mqtt->subscribe("cmd/+/request", 1); mqtt->publish("data/report", "{\"status\":\"online\"}", 1); } // 在主循环中定期发送数据 void loop() { if (!mqtt->isConnected()) { // 重连逻辑 mqtt->connect("183.230.40.39", 6002, "my-device-001", "product_id", "access_key"); mqtt->subscribe("cmd/+/request", 1); } static unsigned long lastPub = 0; if (millis() - lastPub > 30000) { lastPub = millis(); char buf[128]; snprintf(buf, sizeof(buf), "{\"csq\":%d,\"uptime\":%lu}", modem->getCsq(), millis() / 1000); mqtt->publish("data/telemetry", buf, 0); } delay(100); } ``` --- ### 6.7 WebSocketClient — WebSocket 客户端 #### 6.7.1 方法 ```cpp bool connect(const std::string& url, unsigned long timeout_ms = 15000); void disconnect(); bool isConnected() const; bool send(const std::string& text); // 发送文本帧 bool sendBinary(const uint8_t* data, size_t len); // 发送二进制帧 int getLastError() const; ``` #### 6.7.2 回调 ```cpp void onMessage(std::function cb); void onConnected(std::function cb); void onDisconnected(std::function cb); void onError(std::function cb); ``` #### 6.7.3 示例 ```cpp auto ws = modem->CreateWebSocket(); ws->onMessage([](const std::string& data, bool isBinary) { if (isBinary) { Serial.printf("📦 WebSocket 二进制消息 %d 字节\n", data.size()); } else { Serial.printf("💬 WebSocket 消息: %s\n", data.c_str()); } }); ws->onConnected([]() { Serial.println("✅ WebSocket 已连接"); }); ws->onDisconnected([]() { Serial.println("🔌 WebSocket 已断开"); }); if (ws->connect("ws://echo.websocket.org")) { ws->send("Hello WebSocket!"); delay(5000); ws->disconnect(); } ``` --- ## 7. 网络状态与错误处理 ### 7.1 状态枚举 ```cpp enum NetworkStatus { NET_READY = 0, // ✅ 网络注册成功 NET_ERROR = 1, // ❌ 未指定错误 NET_ERROR_INSERT_PIN = -1, // ❌ SIM 卡问题 NET_ERROR_REGISTRATION_DENIED = -2, // ❌ 注册被拒绝 NET_ERROR_TIMEOUT = -3 // ❌ 等待超时 }; ``` ### 7.2 完整的网络等待与错误处理 ```cpp NetworkStatus status = modem->waitForNetworkReady(60000); switch (status) { case NET_READY: Serial.println("✅ 网络就绪"); // 开始你的业务逻辑 break; case NET_ERROR_INSERT_PIN: Serial.println("❌ SIM 卡错误"); Serial.println("可能原因:"); Serial.println(" 1. SIM 卡未插入"); Serial.println(" 2. SIM 卡 PIN 码锁定"); Serial.println(" 3. SIM 卡方向插反"); Serial.println(" 4. SIM 卡已欠费/注销"); break; case NET_ERROR_REGISTRATION_DENIED: Serial.println("❌ 网络拒绝注册"); Serial.println("可能原因:"); Serial.println(" 1. 该地区无 LTE Cat.1 覆盖"); Serial.println(" 2. 运营商拒绝接入"); Serial.println(" 3. IMEI 被拉黑(物联网卡换设备)"); break; case NET_ERROR_TIMEOUT: Serial.println("❌ 注册超时"); Serial.println("可能原因:"); Serial.println(" 1. 信号弱 — 更换位置或天线"); Serial.println(" 2. 波特率不匹配 — 尝试 921600"); Serial.println(" 3. 模组未正常启动 — 检查供电"); break; default: Serial.printf("❌ 未知错误: %d\n", status); break; } ``` ### 7.3 网络状态回调的使用 ```cpp modem->onNetworkStateChanged([](bool ready) { if (ready) { // 网络恢复 — 重新订阅主题、重连服务器等 mqtt->connect(BROKER, PORT, CLIENT_ID); } else { // 网络掉线 — 暂停发送、记录日志 Serial.println("⚠️ 网络连接丢失!"); } }); ``` --- ## 8. 回调机制 本库大量使用 `std::function` 回调。支持三种写法: ```cpp // 方式一:Lambda(推荐) tcp->onData([](const std::string& data) { Serial.println(data.c_str()); }); // 方式二:函数指针 void handleData(const std::string& data) { Serial.println(data.c_str()); } tcp->onData(handleData); // 方式三:捕获局部变量 String buffer; tcp->onData([&buffer](const std::string& data) { buffer += data.c_str(); }); ``` > ⚠️ 回调函数在 FreeRTOS URC 分发任务中执行,避免在回调中做耗时操作(如长时间 `delay()`)。 > 如需耗时的处理,请在回调中设置标志位,在主 `loop()` 中处理。 --- ## 9. 内存管理 ### 9.1 所有权规则 | 对象 | 创建方式 | 释放方式 | 说明 | |------|---------|---------|------| | `std::unique_ptr` | `AtModem::Detect(...)` | 自动释放 | 释放时自动清理 `AtUart` | | `std::unique_ptr` | `modem->CreateTcp()` | 自动释放 | 析构时自动 `disconnect()` | | `std::unique_ptr` | `modem->CreateUdp()` | 自动释放 | 析构时自动 `stop()` | | `std::unique_ptr` | `modem->CreateHttp()` | 自动释放 | 析构时自动 `close()` | | `std::unique_ptr` | `modem->CreateMqtt()` | 自动释放 | 析构时自动 `disconnect()` | | `std::unique_ptr` | `modem->CreateWebSocket()` | 自动释放 | 析构时自动 `disconnect()` | ### 9.2 作用域内的安全用法 ```cpp void sendHttpRequest() { auto http = modem->CreateHttp(); if (http->get("http://example.com")) { // 处理响应... } } ``` ### 9.3 Connect ID 管理 多个客户端可以**共存**,使用不同的 `connectId`(0–5): ```cpp auto tcp1 = modem->CreateTcp(0); // TCP 连接 #0 auto tcp2 = modem->CreateTcp(1); // TCP 连接 #1 auto mqtt = modem->CreateMqtt(2); // MQTT 连接 #2 ``` --- ## 10. 低功耗模式 在需要低功耗的电池应用场景下,可通过 DTR 引脚控制模组休眠。 ```cpp auto uart = modem->GetAtUart(); // 让模组休眠(DTR = HIGH) uart->setDtrPin(true); // MCU 也可以进入深度睡眠 // ...MCU 被唤醒后... // 唤醒模组(DTR = LOW) uart->setDtrPin(false); delay(200); // 等待模组唤醒 modem->waitForNetworkReady(30000); ``` > 注意:低功耗模式需要硬件连接 DTR 引脚,且模组固件需支持。 --- ## 11. 调试 ### 11.1 开启 AT 命令日志 ```cpp auto uart = modem->GetAtUart(); uart->setDebug(true); // 所有 AT 命令和响应都会打印到 Serial ``` 输出效果: ``` → AT+CSQ ← +CSQ: 24,99 ← OK → AT+CGSN=1 ← +CGSN: "866234050001234" ← OK ``` ### 11.2 Arduino 日志级别 本库使用 ESP32 Arduino 的日志宏: - `log_i("TAG", "format", ...)` — 信息 - `log_w("TAG", "format", ...)` — 警告 - `log_e("TAG", "format", ...)` — 错误 可在 `setup()` 中设置: ```cpp esp_log_level_set("*", ESP_LOG_INFO); // 显示所有日志 esp_log_level_set("*", ESP_LOG_WARN); // 仅警告和错误 esp_log_level_set("*", ESP_LOG_ERROR); // 仅错误 ``` --- ## 12. 常见问题 ### Q1: 模组检测失败,返回 nullptr **排查步骤**: 1. 检查 TX/RX 引脚是否交叉(ESP32 TX → 模组 RX,ESP32 RX → 模组 TX) 2. 测量模组 VCC 电压是否在 3.8V–4.2V 3. 用万用表测模组开机后电流是否 > 50mA(不接天线时) 4. 打开调试:`uart->setDebug(true)` 查看是否有任何 AT 响应 5. 尝试不同波特率:`AtModem::Detect(..., 921600)` ### Q2: 网络注册超时 **排查步骤**: 1. 确认 SIM 卡方向正确、已激活、有余额 2. 连接天线 3. 移动到窗边或室外(LTE Cat.1 需要良好信号) 4. 检查 `AT+CSQ` 返回值,0–9 极弱,10–19 一般,20–31 良好 5. 检查 `AT+CPIN?` 返回 `+CPIN: READY` ### Q3: TCP/MQTT 连接失败 - 确认模组 APN 设置正确(国内通常自动配置) - 某些物联网卡需要专用 APN:使用 `AT+CSTT="APN"` 配置 - SSL 连接需要模组固件支持,EC801E 购前需确认 ### Q4: 数据发送后没有响应 - TCP/MQTT/WebSocket 的数据接收是通过 URC 回调异步通知的 - 确保已注册对应的 `onData` / `onMessage` 回调 - 检查 connectId 是否匹配 ### Q5: 编译错误 `undefined reference to vTaskDelete` - 确保选择了 **ESP32 开发板**(不是 Arduino Uno/Mega) - 本库依赖 FreeRTOS,仅在 ESP32 上可用 ### Q6: 如何提高通信速度 - 将波特率提高到 921600:`AtModem::Detect(..., 921600)` - ML307 默认支持 115200 和 921600 自适应 --- ## 13. 示例索引 | 示例 | 文件 | 功能说明 | |------|------|----------| | 基础使用 | `examples/BasicUsage/BasicUsage.ino` | 模组检测、网络注册、信息查询 | | HTTP 客户端 | `examples/HTTPClient/HTTPClient.ino` | GET/POST 请求、自定义 Header | | MQTT 客户端 | `examples/MQTTClient/MQTTClient.ino` | 连接 Broker、发布订阅、心跳 | | TCP 客户端 | `examples/TCPClient/TCPClient.ino` | 原始 TCP 连接和数据收发 | | WebSocket | `examples/WebSocketClient/WebSocketClient.ino` | WebSocket 文本消息收发 | --- ## 附录 A: 常用 AT 命令速查 | AT 命令 | 功能 | |---------|------| | `AT` | 测试通信 | | `AT+CGMR` | 查询固件版本 | | `AT+CPIN?` | SIM 卡状态 | | `AT+CSQ` | 信号强度 | | `AT+CEREG?` | 网络注册状态 | | `AT+COPS?` | 运营商信息 | | `AT+CGSN=1` | 读取 IMEI | | `AT+ICCID` | 读取 ICCID | | `AT+CFUN=1` | 正常模式 | | `AT+CFUN=4` | 飞行模式 | | `AT+NRB` / `AT+MREBOOT=0` | 重启模组 | | `AT+MIPOPEN=0,"TCP","host",port,,0` | ML307 建立 TCP 连接 | | `AT+QIOPEN=1,0,"TCP","host",port,0,1` | EC801E / NT26K 建立 TCP 连接 | | `AT+MIPSEND=0,len,...` / `AT+QISEND=0,len` | 发送数据 | | `AT+MIPCLOSE=0` / `AT+QICLOSE=0` | 关闭连接 | ## 附录 B: 文件结构 ``` ML307_Arduino/ ├── src/ │ ├── ML307_Arduino.h ← 主头文件,一键 #include │ ├── AtUart.h / AtUart.cpp ← UART AT 命令收发引擎 │ ├── AtModem.h / AtModem.cpp ← 模组管理基类 + 自动检测 │ ├── TcpClient.h / TcpClient.cpp │ ├── UdpClient.h / UdpClient.cpp │ ├── HttpClient.h / HttpClient.cpp │ ├── MqttClient.h / MqttClient.cpp │ └── WebSocketClient.h / WebSocketClient.cpp ├── examples/ │ ├── BasicUsage/ │ ├── HTTPClient/ │ ├── MQTTClient/ │ ├── TCPClient/ │ └── WebSocketClient/ ├── keywords.txt ├── library.properties └── README.md ``` --- ## 致谢与许可 本项目基于 [esp-ml307](https://github.com/78/esp-ml307) 组件,感谢原作者 [虾哥 Terrence](mailto:terrence@tenclass.com)。 许可证:**Apache 2.0**