STM32单片机接入IoTDA主要用到的就是串口透传,所以把无线模块配置为透传模式连上服务器即可,本次实验通过有人4G模块"WH-LET-7S1"接入华为云IoTDA具体流程如下图。
1. WH-LET-7S1模块配置——建立TCP连接
模块手册见官网【说明书】WH-LTE-7S1&WH-LTE-7S1-GN 说明书-济南有人物联网技术有限公司官网
下载配置软件【设置软件】Cat-1系列 设置软件-济南有人物联网技术有限公司官网
1.1. 配置模块
依次点击下图按键进行设置
1.将模块通过串口与电脑相连,打开串口进行设置
2.获取当前参数,点击后可以收到目前模块的连接信息在图中5号位置
3.进入通讯状态,如果地址端口均正确点击后可进行通信
4.如需修改连接地址端口,点击此按键进行连接,修改后点击设置并保存参数,再点击进入通讯状态。
华为云IoTDA地址:
iot-mqtts.cn-north-4.myhuaweicloud.com,端口:1883
1.2. 设置模块特性
- 设置模块,启用串口AT指令,后续操作中会根据连接超时发送AT指令复位模块再次连接;
- 启动信息设置好后能够根据此信息判断模块是否进入正常工作模式。
2. STM32搭建工程
本次实验只需要用到两个串口以及一个定时器,串口均采用DMA+空闲中断接收:
- 串口1作为调试串口打印调试信息,
- 串口3作为与4G模块连接的通信来上报消息和解析命令,
- 定时器TIM1作为操作失败超时处理。(通过定时器实现mqtt连接超时,发布失败等问题,避免延时造成的程序卡死问题)
2.1. CubeMX配置如下:
2.1.1配置串口1——调试串口不做接收使用
2.1.2配置串口3——DMA接收
2.1.3配置Tim1,更新中断
2.1.4配置NVIC
2.1.5配置时钟——选择外部高速时钟
2.1.6SYS配置——SWD下载
2.1.7时钟选择
2.1.8工程生成
GENERATE CODE 生成工程
生成工程后在工程中创建User文件夹并包含在工程中用来存放用户编写的文件
2.2. 移植MQTT库
- 下载源码:GitHub - eclipse/paho.mqtt.embedded-c: Paho MQTT C client library for embedded systems. Paho is an Eclipse IoT project (https://iot.eclipse.org/)(打不开的话可以导入gitee中打开)
- 将MQTTPacket内的全部内容添加到Keil工程中
- 在User文件夹中创建MQTT文件夹存放MQTT库,并添加到工程目录中以及包含对应.h文件
2.3. 创建4G模块通信文件
2.3.1. 模块初始化函数
uint8_t lte_rest[10]={0x74,0x65,0x73,0x74,0x23,0x41,0x54,0x2B,0x5A,0x0D};
void LTE_Init(void)
{
//4G模块复位模块
HAL_UART_Transmit(LTE_UART,lte_rest,9,0xffff);
is_mqtt_connected = 0; //连接状态清零
connect_overtime = 0; //连接超时标志
mqtt_publish_count = 0; //发布计数清零
publish_overtime = 0; //发布超时清零
ping_overtime = 0;
}
在程序初始化时调用该函数,或者在mqtt连接失败有异常情况时调用;主要目前为重启4G模块清除相关标志位。
模块复位指令在1.2设置模块特性中进行设置,模块指令为命令密码与AT+Z组合为“test#AT+Z[0D]”。
2.3.2. 4G模块连接MQTT
void LTE_Mqtt_Connect(uint8_t *ack_data)
{
if(strstr((char *)ack_data,"LTE OK"))//判断lte模块是否启动
{
//mqtt 连接
Mqtt_Connect();
}
if(ack_data[0]>>4 == CONNACK) //查看报文类型,判断mqtt是否连接成功
{
//mqtt 订阅消息
memset(ex_mqtt_topic,0,TOPIC_LENGTH);
sprintf(ex_mqtt_topic,Topic_CMD);
Mqtt_Subscribe(ex_mqtt_topic);
printf("mqtt connect success\r\n");
}
if(ack_data[0]>>4 == SUBACK) //查看报文类型,判断订阅消息是否成功
{
is_mqtt_connected = 1;//连接成功
connect_overtime = 0;//超时计数清零
printf("mqtt subscribe success\r\n");
}
}
该函数入参为串口接收到的数据,在mqtt未连接成功时调用连接,如果收到4G模块启动消息说明模块启动成功进行连接,再根据收到的mqtt服务器返回的指令判断当前连接是否成功,消息订阅是否成功。
2.3.3. 封装MQTT相关函数
Mqtt连接函数,将Mqtt连接参数进行拼装后串口透传进行发送
void Mqtt_Connect(void)
{
MQTTPacket_connectData data = MQTTPacket_connectData_initializer;
int buflen = sizeof(mqtt_parse_buf);
data.keepAliveInterval = 120; //(S)会话时长,超过下线
data.cleansession = 1;
data.clientID.cstring = MYclientID;
data.username.cstring = MYusername;
data.password.cstring = MYpassword;
/*拼接MQTT连接语句*/
memset(mqtt_parse_buf,0,buflen);
int len = MQTTSerialize_connect(mqtt_parse_buf, buflen, &data);//拼装mqtt内容
HAL_UART_Transmit(LTE_UART, mqtt_parse_buf, len, 0xffff);
}
Mqtt主题订阅,入参为订阅主题
void Mqtt_Subscribe(char * subtopic_buf)
{
static uint16_t picket_id = 1;
MQTTString topicString = MQTTString_initializer;
int req_qos = 0, len = 0;
int buflen = sizeof(mqtt_parse_buf);
topicString.cstring = subtopic_buf;
memset(mqtt_parse_buf,0,buflen);
len = MQTTSerialize_subscribe(mqtt_parse_buf, buflen, 0, ++picket_id, 1, &topicString, &req_qos);
HAL_UART_Transmit(LTE_UART, mqtt_parse_buf, len, 0xffff);
}
Mqtt发布,发布完成后有相应的计数,再收到回复后进行消除,如发布失败一定时间后将重启模块
/**
* @brief mqtt 发布
* @param topic:主题 payload:内容
* @retval
*/
void Mqtt_Publish(char * topic, unsigned char * payload)
{
MQTTString topicString = MQTTString_initializer;
topicString.cstring = topic;
int buflen = sizeof(mqtt_parse_buf);
uint8_t dup = 0;//重复
int qos =1;
uint8_t retain = 0;
uint16_t packetid = 3;
uint8_t ret =0;
memset(mqtt_parse_buf,0,buflen);
int len = MQTTSerialize_publish(mqtt_parse_buf, buflen, dup, qos, retain, packetid, topicString, payload, strlen((char *)payload));
ret = HAL_UART_Transmit(LTE_UART, mqtt_parse_buf, len, 0xffff);
printf("send_ret:%d\r\n",ret);
//发布消息数量标志自加用于判断是否发布成功
mqtt_publish_count++;
}
Mqtt Ping消息
void Mqtt_ping(void)
{
int buflen = sizeof(mqtt_parse_buf);
int len = MQTTSerialize_pingreq(mqtt_parse_buf, buflen);
HAL_UART_Transmit(LTE_UART, mqtt_parse_buf, len, 0xffff);
mqtt_ping_count++;
}
Mqtt通信异常处理
void Mqtt_Err_Deal(void)
{
//mqtt 异常处理
if(is_mqtt_connected==0)//mqtt 未连接成功
{
if(connect_overtime > 30)//30s 超时
{
printf("mqtt connect error\r\n");
printf("LTE reset\r\n");
LTE_Init();
}
}else{
//mqtt 连接成功 发布失败
if(publish_overtime > 30)//30s 超时
{
printf("mqtt publish error\r\n");
printf("LTE reset\r\n");
LTE_Init();
}
//ping失败
if(ping_overtime > 130)//130s 超时
{
printf("mqtt ping error\r\n");
printf("LTE reset\r\n");
LTE_Init();
}
//发送 ping信息
if(ping_timecount > 60)//60s发送一次ping消息
{
ping_timecount = 0;
Mqtt_ping();
printf("ping send end\r\n");
}
}
}
该异常信息是通过定时器计数实现,需要配合定时器使用,定时器回调函数如下:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim1)
{
count_100ms++;
if((count_100ms%10) == 0)
{
/*1s*/
count_100ms = 0;
if(is_mqtt_connected == 0)//MQTT未连接成功
{
connect_overtime++; //mqtt连接超时计数
ping_timecount = 0;
}else{
ping_timecount++;//连接成功mqtt心跳包开始计数
}
if(mqtt_publish_count>0)//mqtt发布超时计数
{
publish_overtime++;
}
if(mqtt_ping_count>0)//mqtt 心跳超时计数
{
ping_overtime++;
}
}
if(count_100ms > 10)
{
count_100ms = 0;
}
}
}
至此能够实现利用Mqtt协议接入华为云订阅主题以及异常解决,后续将实现华为云的属性上报,命令解析等