一、概述

之前通过串口调试助手+ESP8266已经连接上阿里云了

[ESP8266连接阿里云(串口调试助手)](https://blog.csdn.net/xyx0610/article/details/122006527
"ESP8266连接阿里云(串口调试助手)")

这次我们使用STM32+ESP8266来接入阿里云,其实本质上就是STM32和ESP8266进行串口通信,相当于使用STM32代替之前使用的串口调试助手来发送AT指令。只是发送的字符串数据可能有些细节需要注意一下。


二、保存连接的AT指令

定义几个字符串数据保存需要发送的AT指令



//注意:
//末尾记得加\r\n
//字符串里面的 " 需要转义所以前面需要加 \才能得到正确的"
//字符串里面的 ,(逗号) 需要使用\转义,而一个\在字符串中需要使用\\

u8 HotSpot_Connect[]="AT+CWJAP=\"WIFI名称\",\"WiFi密码\"\r\n";//连接热点AT指令

//接入阿里云的AT指令
u8 MQTTUSERCFG[]="AT+MQTTUSERCFG=0,1,\"NULL\",\"用户名\",\"密码\",0,0,\"\"\r\n";//每一个"前面加'\'
u8 MQTTCLIENTID[]="AT+MQTTCLIENTID=0,\"ClienId|\"\r\n";//每一个逗号前加一个'\',在双引号里面就要用'\\'
u8 MQTTCONN[]="AT+MQTTCONN=0,\"ProductKey.iot-as-mqtt.cn-shanghai.aliyuncs.com\",1883,1\r\n";

其中在使用时需要替换的有 WIFI名称,WIFI密码,用户名,密码,ClientId,ProductKey

这些参数全部改成你自己的参数


三、配置串口

这部分应该比较简单,随便去哪复制粘贴改改就好了

我使用的是STM32F103C8T6的串口2,下面是配置代码



void Uart2_init(u32 bound){
      //GPIO端口设置
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
     
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);    //使能USART2,GPIOA时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
     
    //USART2_TX   GPIOA.2
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA.2
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;    //复用推挽输出
    GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA2
    
    //USART2_RX      GPIOA.3初始化
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;//PA10
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA3
    
    //Usart2 NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1 ;//抢占优先级1
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;        //子优先级0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;            //IRQ通道使能
    NVIC_Init(&NVIC_InitStructure);    //根据指定的参数初始化VIC寄存器
    
    //USART 初始化设置
    USART_InitStructure.USART_BaudRate = bound;//串口波特率
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
    USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;    //收发模式
    
    USART_Init(USART2, &USART_InitStructure); //初始化串口2
    USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启串口接收中断
    USART_Cmd(USART2, ENABLE);                    //使能串口2

}

四、使用串口发送AT指令

串口配置好了,需要发送的AT指令也保存好了,接下来把AT指令逐个字符循环发送出去就好了,下面是发送代码(WIFI连接配置一次就好了,ESP8266会自动连接的)



void Net_Things_Init(void)
{
    int j=0;
    
    //连接wifi wifi变动时可以使用
//    for(j=0;HotSpot_Connect[j]!='\0';j++)    
//    {
//        USART_SendData(USART2,HotSpot_Connect[j]);
//        while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
//        //while((USART2->SR&0X40)==0);这个方法没用
//    }
//    printf("%s\r\n",RX_Data);
//    delay_ms(1000);
    
    //循环发送AT指令
    for(j=0;MQTTUSERCFG[j]!='\0';j++)    
    {
        USART_SendData(USART2,MQTTUSERCFG[j]);
        while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
    }
    delay_ms(1000);//发送完一个指令后延时一会 这个值自己随便设置的
    
    for(j=0;MQTTCLIENTID[j]!='\0';j++)    
    {
        USART_SendData(USART2,MQTTCLIENTID[j]);
        while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
    }
    delay_ms(1000);//发送完一个指令后延时一会 这个值自己随便设置的
    
    for(j=0;MQTTCONN[j]!='\0';j++)    
    {
        USART_SendData(USART2,MQTTCONN[j]);
        while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
    }
    delay_ms(1000);//发送完一个指令后延时一会 这个值自己随便设置的
}

然后在主函数里面调用这个初始化函数就好了

注意: 我测试的时候如果只是初始化一次连上去的概率不大(不知道是什么问题),所以我都是初始化两次,也就是调用两次初始化函数来让它连接上去。


五、获取订阅Topic的信息

这个其实就是串口的中断接收,要么就是发一个字符接收一个字符,要么就是自己定义一个数据接收协议。比如我接收到字符 '!'
开始接收,然后接收到'#' 时停止接收。或者就使用原子的接收到 \r\n(0x0a 0x0d) 就停止接收这样都可以。

我使用接收到字符 '!' 开始接收,然后接收到'#' 时停止接收的方法。中断接收配置如下



void USART2_IRQHandler(void)
{
    u8 Res=0;
    if (USART_GetFlagStatus(USART2, USART_FLAG_ORE) != RESET)//注意!不能使用if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)来判断  
    {  
        USART_ClearFlag(USART2, USART_FLAG_ORE); //读SR其实就是清除标志
           USART_ReceiveData(USART2);          
    }
    
            
    if(USART_GetFlagStatus(USART2, USART_FLAG_RXNE)!=RESET)
    {
        USART_ClearITPendingBit(USART2, USART_IT_RXNE);

        Res =USART2->DR;    //读取接收到的数据
           
        if(Res == '!')
        {
              startBit = 1;
            g_num = 0;
            memset(RX_Data, 0x00, sizeof(RX_Data));//清零
        }
        if(startBit == 1)
        {
               RX_Data[g_num] = Res;
        }  
        if (startBit == 1 && Res == '#') 
        {
            startBit = 0;
            
            //打印接收到的数据
            RX_Data[g_num]='\0';//加入结束符,方便打印显示
            printf("%s",RX_Data);
        }
        g_num++;
    
    }
}

配置好后我们在云平台发布消息,然后可以通过串口查看接收到的数据。

2026-01-25T13:09:01.png

下面是蓝牙调试助手收到的信息(因为我的核心板没有usb 转ttl,所以使用蓝牙模块来查看数据)

这是自己写的一个简单app,调试数据相对方便点

2026-01-25T13:09:09.png


六、发布Topic

和之前的连接比较像,先保存AT指令,然后通过串口进行发送



//拆分成两段是为了在中间插入自己想要发的数据
//要是固定发送数据也可以直接组合成一段
u8 PubMsgTopic1[] = "AT+MQTTPUB=0,\"发布的主题\",\"";
u8 PubMsgTopic2[] = "\",1,0\r\n";

发送Topic的具体实现



void PubTopic(u8 *msg)
{
    int j=0;
    //发送第一段
    for(j=0;PubMsgTopic1[j]!='\0';j++)    
    {
        USART_SendData(USART2,PubMsgTopic1[j]);
        while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
    }
    
    //插入要发送的信息
    for(j=0;msg[j]!='\0';j++)    
    {
        USART_SendData(USART2,msg[j]);
        while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
    }
    
    //发送最后一段
    for(j=0;PubMsgTopic2[j]!='\0';j++)    
    {
        USART_SendData(USART2,PubMsgTopic2[j]);
        while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
    }
    delay_ms(1000);
}

七、发送物模型数据

在产品的功能定义里面有功能定义,这些是所有设备的一个物模型

2026-01-25T13:09:31.png

我产品创建的时候选择的是智能家居,所以默认有温度和湿度两个功能,当然你可以点击编辑草稿创建自定义的功能

2026-01-25T13:09:37.png

然后物模型上报的Topic在这个位置

2026-01-25T13:09:44.png

物模型上报的时候数据需要满足一定格式才能被识别,不能随便发送

数据格式:

2026-01-25T13:09:52.png

(物模型的属性是指各个功能的标识符)

接下来我们先保存AT指令



//第一段
u8 DeviceProPost[]="AT+MQTTPUB=0,\"物模型上报topic\",\"{\\\"method\\\":\\\"thing.service.property.set\\\"\\,\\\"id\\\":\\\"2012934115\\\"\\,\\\"params\\\":";
//例子:
//我的物模型上报topic:/sys/a1B70cUJHWr/${deviceName}/thing/event/property/post
//${deviceName}改成你需要上报的设备 我这里就是XYX-1
//id后面的值可以随便设置

u8 DeviceProPost2[]="{\\\"CurrentHumidity\\\":";
//CurrentHumidity 是我设备物模型中当前温度的标识符

u8 DeviceProPost3[]="\\,\\\"CurrentTemperature\\\":";
//CurrentTemperature 是我设备物模型中当前湿度的标识符

//结束段
u8 DeviceProPost4[]="}\\,\\\"version\\\":\\\"1.0.0\\\"}\",1,0\r\n";

2026-01-25T13:10:02.png

然后直接使用串口发送这些AT指令,当然AT指令分段也是为了要在其中插入动态的数据

下面是物模型上报的代码



void Property_Post(u8 temp,u8 humd)
{
    int j=0;
    for(j=0;DeviceProPost[j]!='\0';j++)    
    {
        USART_SendData(USART2,DeviceProPost[j]);
        while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
    }
    for(j=0;DeviceProPost2[j]!='\0';j++)    
    {
        USART_SendData(USART2,DeviceProPost2[j]);
        while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
    }
    
    //发送十位
    USART_SendData(USART2,humd/10+48);
    while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
    //发送个位
    USART_SendData(USART2,humd%10+48);
    while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
    
    for(j=0;DeviceProPost3[j]!='\0';j++)    
    {
        USART_SendData(USART2,DeviceProPost3[j]);
        while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
    }
    
    //发送十位
    USART_SendData(USART2,temp/10+48);
    while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
    //发送个位
    USART_SendData(USART2,temp%10+48);
    while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
    
    for(j=0;DeviceProPost4[j]!='\0';j++)    
    {
        USART_SendData(USART2,DeviceProPost4[j]);
        while(USART_GetFlagStatus(USART2,USART_FLAG_TC)==RESET);//等待发送完成
    }
    delay_ms(1000);
}

测试,连接到云后,在需要发送的地方直接调用



Property_Post(26,31);//温度26 湿度31

然后到云平台就可以查看到了

2026-01-25T13:10:25.png

这样就可以配合实际的温湿度模块做一个超简单的智能家居小项目了!