文档
MN316 NB-IoT驱动代码 (STM32 HAL库)
MCU: STM32F103 | UART2 DMA+IDLE中断 | MN316 AT指令 | OneNET MQTT物模型
一、GPIO与UART初始化
/* ======================== nbiot_config.h ======================== */
#ifndef NBIOT_CONFIG_H
#define NBIOT_CONFIG_H
#include "stm32f1xx_hal.h"
#include <stdint.h>
/* ---- 硬件引脚定义 (根据实际电路修改) ---- */
#define NB_PWK_PORT GPIOB
#define NB_PWK_PIN GPIO_PIN_8 /* PWRKEY */
#define NB_RST_PORT GPIOB
#define NB_RST_PIN GPIO_PIN_9 /* RESET */
#define NB_NETLED_PORT GPIOC
#define NB_NETLED_PIN GPIO_PIN_6 /* NETLIGHT */
/* MN316 UART: USART2, 9600bps, 8N1, TTL 3.3V */
extern UART_HandleTypeDef huart2;
/* ---- 缓冲区 ---- */
#define NBIOT_RX_BUF_SIZE 1024
#define NBIOT_TX_BUF_SIZE 1024
/* ---- 时序参数 ---- */
#define MN316_PWRKEY_PULSE 1200 /* PWRKEY拉低时间 ms (≥1s) */
#define NBIOT_UPLOAD_INTERVAL_MS 60000 /* 上报间隔 ms */
#define NBIOT_MAX_RETRY 3
/* ---- OneNET Topic宏 ---- */
#define ONENET_TOPIC_PROP_POST "$sys/%s/%s/thing/property/post"
#define ONENET_TOPIC_EVENT_POST "$sys/%s/%s/thing/event/post"
#define ONENET_TOPIC_CMD_RECV "$sys/%s/%s/thing/service/+/invoke"
/* ---- 枚举 ---- */
typedef enum {
NBIOT_STATE_OFF = 0,
NBIOT_STATE_INIT,
NBIOT_STATE_ATTACHED,
NBIOT_STATE_CONNECTED,
NBIOT_STATE_ERROR,
} NBIoT_State_t;
typedef enum {
NBIOT_PROTO_MQTT = 0,
NBIOT_PROTO_TCP = 1,
} NBIoT_Protocol_t;
/* ---- 配置结构体 ---- */
typedef struct {
char server_ip[32];
uint16_t server_port;
char product_id[32];
char device_name[32];
char token[64];
char nb_band[8]; /* "5,8" 表示 B5+B8 */
NBIoT_Protocol_t protocol;
} NBIoT_Config_t;
/* ---- 传感器数据结构体 ---- */
typedef struct {
uint32_t timestamp;
float ph;
float turbidity;
float conductivity;
float tds;
float temperature;
float humidity;
} SensorData_t;
#endif /* NBIOT_CONFIG_H */
/* ======================== nbiot_hardware_init.c ======================== */
/**
* @brief MN316硬件初始化: GPIO + USART2 DMA
* 调用时机: 系统启动时, 早于NBIoT_Init()
*/
#include "nbiot_config.h"
#include "stm32f1xx_hal.h"
extern UART_HandleTypeDef huart2;
extern DMA_HandleTypeDef hdma_usart2_rx;
void NBIoT_Hardware_Init(void)
{
GPIO_InitTypeDef gpio = {0};
/* ---- 1. 使能GPIO时钟 ---- */
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
/* ---- 2. PWRKEY: 推挽输出, 默认高 ---- */
gpio.Pin = NB_PWK_PIN;
gpio.Mode = GPIO_MODE_OUTPUT_PP;
gpio.Pull = GPIO_PULLUP;
gpio.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(NB_PWK_PORT, &gpio);
HAL_GPIO_WritePin(NB_PWK_PORT, NB_PWK_PIN, GPIO_PIN_SET);
/* ---- 3. RESET: 推挽输出, 默认高 ---- */
gpio.Pin = NB_RST_PIN;
HAL_GPIO_Init(NB_RST_PORT, &gpio);
HAL_GPIO_WritePin(NB_RST_PORT, NB_RST_PIN, GPIO_PIN_SET);
/* ---- 4. NETLIGHT: 输入浮空 (可选, 仅用于监控) ---- */
gpio.Pin = NB_NETLED_PIN;
gpio.Mode = GPIO_MODE_INPUT;
gpio.Pull = GPIO_NOPULL;
HAL_GPIO_Init(NB_NETLED_PORT, &gpio);
/* ---- 5. USART2 DMA接收初始化 (不在这里启动, SendCmd中按需启动) ---- */
/* 前提: MX_USART2_UART_Init()已完成, huart2.Init.BaudRate=9600 */
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
}
/**
* @brief 在stm32f1xx_it.c的USART2_IRQHandler中调用此函数
* 实现UART IDLE中断 + DMA接收
*
* stm32f1xx_it.c 示例:
*
* void USART2_IRQHandler(void)
* {
* if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE)) {
* NBIoT_UART_IdleCallback(); // 调用下面这个函数
* __HAL_UART_CLEAR_IDLEFLAG(&huart2);
* }
* HAL_UART_IRQHandler(&huart2);
* }
*/
static volatile uint8_t rx_buf[1024];
static volatile uint16_t rx_len = 0;
static volatile uint8_t rx_ready = 0;
void NBIoT_UART_IdleCallback(void)
{
/* 停止DMA, 获取已接收字节数 */
HAL_UART_DMAStop(&huart2);
rx_len = NBIOT_RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(huart2.hdmarx);
rx_ready = 1;
}
/* 获取接收缓冲指针 (供nbiot.c使用) */
uint8_t* NBIoT_GetRxBuf(void) { return (uint8_t*)rx_buf; }
uint16_t NBIoT_GetRxLen(void) { return rx_len; }
uint8_t NBIoT_IsRxReady(void) { return rx_ready; }
void NBIoT_ClearRxReady(void) { rx_ready = 0; }
二、AT指令收发驱动(核心)
/* ======================== nbiot.c (核心驱动) ======================== */
#include "nbiot_config.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* ---- 内部状态 ---- */
static NBIoT_State_t s_state = NBIOT_STATE_OFF;
static NBIoT_Config_t s_cfg = {0};
static volatile uint8_t s_mqtt_connected = 0;
static volatile uint8_t s_mqtt_timeout = 0;
/* 外部引用 */
extern UART_HandleTypeDef huart2;
extern uint8_t* NBIoT_GetRxBuf(void);
extern uint16_t NBIoT_GetRxLen(void);
extern uint8_t NBIoT_IsRxReady(void);
extern void NBIoT_ClearRxReady(void);
/* ---- 启动UART DMA+IDLE接收 ---- */
static void UART_StartRx(void)
{
uint8_t *buf = NBIoT_GetRxBuf();
HAL_UART_DMAStop(&huart2);
NBIoT_ClearRxReady();
memset(buf, 0, NBIOT_RX_BUF_SIZE);
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
HAL_UART_Receive_DMA(&huart2, buf, NBIOT_RX_BUF_SIZE);
}
/* ---- 响应内容检查 ---- */
static uint8_t RxContains(const char *needle)
{
if (needle == NULL || needle[0] == '\0') return 0;
return (strstr((char*)NBIoT_GetRxBuf(), needle) != NULL) ? 1 : 0;
}
/**
* @brief 发送AT指令并等待期望响应
* @param cmd AT指令字符串 (不含\r\n)
* @param expected 期望响应的子串, NULL表示只等OK
* @param timeout_ms 超时时间(ms)
* @return HAL_OK/HAL_TIMEOUT/HAL_ERROR
*/
HAL_StatusTypeDef NBIoT_SendCmd(const char *cmd,
const char *expected,
uint32_t timeout_ms)
{
uint32_t tick = HAL_GetTick();
HAL_StatusTypeDef status = HAL_TIMEOUT;
uint8_t *buf = NBIoT_GetRxBuf();
/* 启动DMA接收 */
UART_StartRx();
/* 发送AT指令 (末尾自动加\r\n) */
HAL_UART_Transmit(&huart2, (uint8_t*)cmd, strlen(cmd), 1000);
HAL_UART_Transmit(&huart2, (uint8_t*)"\r\n", 2, 100);
/* 等待IDLE中断完成或超时 */
while ((HAL_GetTick() - tick) < timeout_ms) {
if (NBIoT_IsRxReady()) {
uint16_t len = NBIoT_GetRxLen();
buf[len] = '\0';
/* 检查MQTT超时URC */
if (strstr((char*)buf, "+MQTTTO") != NULL) {
s_mqtt_timeout = 1;
s_mqtt_connected = 0;
}
/* 匹配期望 */
if (expected != NULL && strstr((char*)buf, expected) != NULL) {
status = HAL_OK;
goto exit;
}
if (strstr((char*)buf, "OK\r\n") != NULL) {
status = (expected == NULL) ? HAL_OK : HAL_ERROR;
goto exit;
}
if (strstr((char*)buf, "ERROR") != NULL) {
status = HAL_ERROR;
goto exit;
}
/* 未匹配, 继续拼接接收 */
NBIoT_ClearRxReady();
if (len < NBIOT_RX_BUF_SIZE - 1) {
HAL_UART_Receive_DMA(&huart2, buf + len,
NBIOT_RX_BUF_SIZE - len);
}
}
__WFI(); /* 让出CPU, 降低功耗 */
}
exit:
HAL_UART_DMAStop(&huart2);
return status;
}
/* ---- 查询网络附着 ---- */
uint8_t NBIoT_CheckAttach(void)
{
if (NBIoT_SendCmd("AT+CGATT?", "+CGATT:1", 3000) == HAL_OK)
return 1;
return 0;
}
/* ---- 查询信号强度 (返回dBm) ---- */
int16_t NBIoT_GetSignal(void)
{
if (NBIoT_SendCmd("AT+CSQ", "OK", 3000) != HAL_OK)
return 0;
char *p = strstr((char*)NBIoT_GetRxBuf(), "+CSQ:");
if (p != NULL) {
int rssi = atoi(p + 5);
if (rssi != 99) return -113 + rssi * 2;
}
return 0;
}
三、MN316 开机与网络注册
/* ======================== 开机与注册流程 ======================== */
/**
* @brief MN316开机: PWRKEY拉低 ≥1秒
*/
static HAL_StatusTypeDef MN316_PowerOn(void)
{
/* PWRKEY拉低 */
HAL_GPIO_WritePin(NB_PWK_PORT, NB_PWK_PIN, GPIO_PIN_RESET);
HAL_Delay(MN316_PWRKEY_PULSE); /* 默认1200ms */
HAL_GPIO_WritePin(NB_PWK_PORT, NB_PWK_PIN, GPIO_PIN_SET);
HAL_Delay(2000); /* 等待模块启动 */
/* AT探活 */
if (NBIoT_SendCmd("AT", "OK", 5000) != HAL_OK) {
return HAL_ERROR;
}
return HAL_OK;
}
/**
* @brief MN316网络注册完整流程
* 频段设置 → 射频开启 → 网络附着 → PDN激活
*/
static HAL_StatusTypeDef MN316_NetworkRegister(void)
{
char cmd[64];
uint8_t retry = 0;
/* 1. 关闭射频 (CFUN=0才能配置频段) */
NBIoT_SendCmd("AT+CFUN=0", "OK", 5000);
HAL_Delay(500);
/* 2. 自动连接 */
NBIoT_SendCmd("AT+NCONFIG=AUTOCONNECT,1", "OK", 3000);
HAL_Delay(100);
/* 3. 设置频段 (B5电信 + B8移动) */
snprintf(cmd, sizeof(cmd), "AT+NBAND=%s", s_cfg.nb_band);
NBIoT_SendCmd(cmd, "OK", 3000);
HAL_Delay(100);
/* 4. 开启射频 */
if (NBIoT_SendCmd("AT+CFUN=1", "OK", 10000) != HAL_OK) {
/* 失败则软复位重试 */
NBIoT_SendCmd("AT+NRB", "REBOOTING", 5000);
HAL_Delay(5000);
NBIoT_SendCmd("AT", "OK", 5000);
NBIoT_SendCmd("AT+CFUN=1", "OK", 10000);
}
HAL_Delay(1000);
/* 5. 附着网络 */
NBIoT_SendCmd("AT+CGATT=1", "OK", 10000);
HAL_Delay(500);
/* 6. 检查附着状态 (带重试) */
while (retry < NBIOT_MAX_RETRY) {
if (NBIoT_CheckAttach()) {
s_state = NBIOT_STATE_ATTACHED;
return HAL_OK;
}
retry++;
HAL_Delay(3000);
}
s_state = NBIOT_STATE_ERROR;
return HAL_ERROR;
}
四、MQTT连接OneNET
/* ======================== MQTT连接OneNET平台 ======================== */
/**
* @brief 检查MQTT是否已配置正确的参数
*/
static uint8_t IsMQTTConfigured(void)
{
if (NBIoT_SendCmd("AT+MQTTCFG?", "OK", 5000) != HAL_OK)
return 0;
if (RxContains(s_cfg.server_ip) && RxContains(s_cfg.device_name))
return 1;
return 0;
}
/**
* @brief 检查MQTT是否已连接 (状态5=已连接)
*/
static uint8_t IsMQTTConnected(void)
{
if (NBIoT_SendCmd("AT+MQTTSTAT?", "OK", 5000) != HAL_OK)
return 0;
return RxContains("+MQTTSTAT:5");
}
/**
* @brief MN316 MQTT连接OneNET完整流程
* MQTTCFG → MQTTOPEN → 订阅reply/command/event
*/
static HAL_StatusTypeDef MN316_OneNET_MQTT_Connect(void)
{
char cmd[512];
uint8_t poll = 0;
/* 先释放旧连接 */
NBIoT_SendCmd("AT+MQTTDISC", NULL, 3000);
NBIoT_SendCmd("AT+MQTTDEL", NULL, 2000);
HAL_Delay(500);
/* 1. 配置MQTT参数 */
snprintf(cmd, sizeof(cmd),
"AT+MQTTCFG=\"%s\",%d,\"%s\",60,\"%s\",\"%s\",1,0",
s_cfg.server_ip, s_cfg.server_port,
s_cfg.device_name, s_cfg.product_id, s_cfg.token);
if (NBIoT_SendCmd(cmd, "OK", 12000) != HAL_OK) {
/* MQTTCFG偶发不回OK,补查配置结果 */
HAL_Delay(1500);
if (!IsMQTTConfigured()) {
return HAL_ERROR;
}
}
HAL_Delay(500);
/* 2. 打开MQTT连接 */
NBIoT_SendCmd("AT+MQTTOPEN=1,1,0,0,0,\"\",\"\"", "OK", 15000);
HAL_Delay(3000);
/* 3. 轮询等待连接建立 */
while (poll < 5) {
if (IsMQTTConnected()) break;
poll++;
HAL_Delay(2000);
}
if (!IsMQTTConnected()) {
s_mqtt_connected = 0;
return HAL_ERROR;
}
s_mqtt_connected = 1;
s_mqtt_timeout = 0;
HAL_Delay(500);
/* 4. 订阅属性回复Topic */
snprintf(cmd, sizeof(cmd),
"AT+MQTTSUB=\"$sys/%s/%s/thing/property/post/reply\",0",
s_cfg.product_id, s_cfg.device_name);
NBIoT_SendCmd(cmd, "OK", 5000);
/* 5. 订阅命令下发Topic */
snprintf(cmd, sizeof(cmd),
"AT+MQTTSUB=\"$sys/%s/%s/thing/service/+/invoke\",0",
s_cfg.product_id, s_cfg.device_name);
NBIoT_SendCmd(cmd, "OK", 5000);
/* 6. 订阅事件回复Topic */
snprintf(cmd, sizeof(cmd),
"AT+MQTTSUB=\"$sys/%s/%s/thing/event/post/reply\",0",
s_cfg.product_id, s_cfg.device_name);
NBIoT_SendCmd(cmd, "OK", 5000);
return HAL_OK;
}
五、完整初始化流程
/* ======================== 模块总初始化 ======================== */
/**
* @brief MN316模块完整初始化
* 开机 → 查询信息 → 关闭PSM/eDRX → 网络注册 → MQTT连接
*/
HAL_StatusTypeDef NBIoT_Init(const NBIoT_Config_t *config)
{
memcpy(&s_cfg, config, sizeof(NBIoT_Config_t));
s_state = NBIOT_STATE_INIT;
/* 1. 硬件初始化 (GPIO已在别处完成, 此处仅确保UART就绪) */
/* 2. 开机 */
if (MN316_PowerOn() != HAL_OK) {
s_state = NBIOT_STATE_ERROR;
return HAL_ERROR;
}
HAL_Delay(500);
/* 3. 查询模块信息 */
NBIoT_SendCmd("AT+CGMM", "OK", 2000); /* 型号 */
HAL_Delay(100);
NBIoT_SendCmd("AT+CGSN=1", "OK", 2000); /* IMEI */
HAL_Delay(100);
NBIoT_SendCmd("AT+CIMI", "OK", 2000); /* IMSI */
HAL_Delay(100);
/* 4. 关闭低功耗 (调试阶段保持常在线) */
NBIoT_SendCmd("AT+CPSMS=0", "OK", 2000);
HAL_Delay(100);
NBIoT_SendCmd("AT+CEDRXS=0", "OK", 2000);
HAL_Delay(100);
/* 5. 网络注册 */
if (MN316_NetworkRegister() != HAL_OK) {
return HAL_ERROR;
}
/* 6. MQTT连接 */
if (s_cfg.protocol == NBIOT_PROTO_MQTT) {
if (MN316_OneNET_MQTT_Connect() == HAL_OK) {
s_state = NBIOT_STATE_CONNECTED;
} else {
s_state = NBIOT_STATE_ERROR;
return HAL_ERROR;
}
}
return HAL_OK;
}
六、物模型数据上报(完整可运行示例)
/* ======================== OneNET物模型属性上报 ======================== */
/**
* @brief OneNET物模型属性上报
* Topic: $sys/{pid}/{device}/thing/property/post
* Payload: JSON → HEX (MN316 MQTTPUB要求hex编码)
*
* @param data 传感器数据
* @param alarm_mask 报警位掩码
* @return HAL_OK / HAL_ERROR
*/
HAL_StatusTypeDef NBIoT_MQTT_PropertyReport(const SensorData_t *data,
uint8_t alarm_mask)
{
/* 连接断开则重连 */
if (!s_mqtt_connected || s_mqtt_timeout) {
if (MN316_OneNET_MQTT_Connect() != HAL_OK)
return HAL_ERROR;
}
int16_t signal = NBIoT_GetSignal();
/* ---- 构造JSON Payload ---- */
char json[512];
int json_len = snprintf(json, sizeof(json),
"{"
"\"id\":\"%lu\","
"\"version\":\"1.0\","
"\"params\":{"
"\"ph\":{\"value\":%.2f},"
"\"turbidity\":{\"value\":%.1f},"
"\"conductivity\":{\"value\":%.1f},"
"\"tds\":{\"value\":%.1f},"
"\"temperature\":{\"value\":%.1f},"
"\"humidity\":{\"value\":%.1f},"
"\"alarm_mask\":{\"value\":%d},"
"\"signal_strength\":{\"value\":%d}"
"}"
"}",
data->timestamp,
data->ph, data->turbidity,
data->conductivity, data->tds,
data->temperature, data->humidity,
alarm_mask, (int)signal);
if (json_len <= 0 || json_len >= (int)sizeof(json))
return HAL_ERROR;
/* ---- JSON → HEX字符串 ---- */
char hex[1024];
int hex_len = 0;
for (int i = 0; i < json_len; i++) {
hex_len += snprintf(hex + hex_len, sizeof(hex) - hex_len,
"%02X", (uint8_t)json[i]);
}
/* ---- 构造Topic ---- */
char topic[100];
snprintf(topic, sizeof(topic), ONENET_TOPIC_PROP_POST,
s_cfg.product_id, s_cfg.device_name);
/* ---- MQTT发布 (hex编码) ---- */
char pub_cmd[1536];
snprintf(pub_cmd, sizeof(pub_cmd),
"AT+MQTTPUB=\"%s\",0,0,0,%d,%s",
topic, json_len, hex);
if (NBIoT_SendCmd(pub_cmd, "OK", 10000) != HAL_OK) {
s_mqtt_connected = 0;
return HAL_ERROR;
}
/* 等待reply检查 */
HAL_Delay(2000);
return HAL_OK;
}
七、完整使用示例(main.c)
/* ======================== main.c 完整示例 ======================== */
#include "nbiot_config.h"
/* 全局变量 */
NBIoT_Config_t g_nb_config = {
.server_ip = "183.230.40.96", /* OneNET MQTT接入IP */
.server_port = 6002,
.product_id = "your_product_id",
.device_name = "your_device_name",
.token = "your_access_token",
.nb_band = "5,8", /* B5电信 + B8移动 */
.protocol = NBIOT_PROTO_MQTT,
};
SensorData_t g_sensor_data = {0};
int main(void)
{
/* ---- 系统初始化 ---- */
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_USART2_UART_Init(); /* 9600bps, 8N1 */
/* ---- MN316硬件初始化 ---- */
NBIoT_Hardware_Init();
/* ---- MN316模块初始化 (开机+注册+MQTT连接) ---- */
HAL_StatusTypeDef ret = NBIoT_Init(&g_nb_config);
if (ret != HAL_OK) {
/* 初始化失败处理: 可重试或进入错误状态 */
while (1) {
HAL_Delay(1000);
}
}
/* ---- 主循环: 周期上报 ---- */
uint32_t last_upload = 0;
while (1) {
uint32_t now = HAL_GetTick();
if (now - last_upload >= NBIOT_UPLOAD_INTERVAL_MS) {
last_upload = now;
/* 模拟传感器数据 */
g_sensor_data.timestamp = now / 1000;
g_sensor_data.ph = 7.2f;
g_sensor_data.turbidity = 15.3f;
g_sensor_data.conductivity = 350.0f;
g_sensor_data.tds = 245.0f;
g_sensor_data.temperature = 25.1f;
g_sensor_data.humidity = 60.5f;
/* 上报属性 */
ret = NBIoT_MQTT_PropertyReport(&g_sensor_data, 0);
if (ret != HAL_OK) {
/* 上报失败, 下次循环自动重试 */
}
}
/* 其他任务... */
HAL_Delay(100);
}
}
八、中断服务函数 (stm32f1xx_it.c)
/* ======================== stm32f1xx_it.c ======================== */
#include "stm32f1xx_hal.h"
extern UART_HandleTypeDef huart2;
/**
* @brief USART2中断服务函数
* 处理DMA接收完成 + UART IDLE中断
*/
void USART2_IRQHandler(void)
{
/* IDLE中断: 一帧AT响应接收完毕 */
if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE)) {
__HAL_UART_CLEAR_IDLEFLAG(&huart2);
NBIoT_UART_IdleCallback(); /* 记录已接收长度, 置rx_ready=1 */
}
/* HAL库通用处理 (DMA中断等) */
HAL_UART_IRQHandler(&huart2);
}
九、调试技巧
9.1 串口监控
可在MCU端添加一个调试串口将MN316的收发数据透传出来:
void NBIoT_DebugPassthrough(void)
{
/* 收到MN316数据时 */
if (NBIoT_IsRxReady()) {
uint8_t *buf = NBIoT_GetRxBuf();
uint16_t len = NBIoT_GetRxLen();
/* 通过调试串口输出 */
HAL_UART_Transmit(&hlpuart1, buf, len, 100);
}
}
9.2 常见问题排查
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
AT 无响应 |
供电不足/PWRKEY时序 | 检查VBAT波形,确认PWRKEY拉低≥1s |
+CGATT:0 |
无信号/SIM卡异常 | AT+CSQ查信号,AT+CPIN?查SIM状态 |
AT+MQTTOPEN失败 |
服务器IP/端口错 | ping服务器,检查token是否正确 |
+MQTTTO频繁 |
信号弱/keepalive过短 | 调整天线位置,延长keepalive到120s |
| MQTTPUB返回ERROR | hex长度参数错误 | 确认第5参数=原始JSON字节数(非hex长度) |