stm32f103c8t6+esp8266(esp-01s)+mqtt固件+hal库连接阿里云(最详细+可移植)教程

概要

STM32F103c8t6+ESP8266(esp-01s)+MQTT固件 连接阿里云
里面用到了对串口不定长的数据的DMA+中断的接受方式。不了解的可以看这个篇文章STM32F407的串口接收不定长数据两种方式HAL库

file

一、MQTT固件

对于该项目我们利用了MQTT固件,这个固件可以让我们更加简单的利用MQTT进行数据传输,利用该固件我们不需要对MQTT进行封装,直接用。

file

我们可以去安信可的官网下载MQTT的固件(安信可官网固件下载
在安信可的官网上下载的固件还需要下载下载进esp8266的工具。如果这个有对应的下载工具。也有mqtt的固件
链接:https://pan.baidu.com/s/1gbizlkm997HnCW5H3B7n3A
提取码:8ex1
file

我尝试了一下1471这个固件号的是可以用的,其他的好像型号不对flash大小不够,有专业的可以给我讲解一下,谢谢。

下载的方式是利用串口,可以用wifi的转接板或者别的串口工具。我这边用的时转接板。

file

插上之后选择esp8266下载工具
file

进入之后根据下图进行操作
file

MQTT固件连接阿里云对比AT固件连接云平台来说是更加简单,我们只需要掌握MQTT固件的AT指令就行。
我这里将列出几个关键的指令

//注意:
//"AT+CWJAP=\"WIFI名称\",\"WiFi密码\"\r\n";//连接热点AT指令

//接入阿里云的AT指令
// 设置用户名和密码
AT+MQTTUSERCFG=0,1,"","用户名","密码",0,0,""
//绑定ClienId
AT+MQTTCLIENTID=0,"ix25oHiHCSl.stm32|securemode=2\,signmethod=hmacsha156\,timestamp=1686921535251|"
// 连接网址
AT+MQTTCONN=0,"iot-06z00b28nanp9ew.mqtt.iothub.aliyuncs.com",1883,1
//发送数据格式
AT+MQTTPUB=0,"/sys/ix25oHiHCSl/stm32/thing/event/property/post","{\"params\":{\"temperature\":89\,\"humi\":0\}\,\"version\":\"1.0.0\"}",0,0
// 接收数据
+MQTTSUBRECV:0,"/sys/ix25oHiHCSl/stm32/thing/event/property/post",75,{"params":{"temperature":16.300000,"Humidity":38.600000},"version":"1.0.0"}

二、阿里云账号注册

注册自己的账号之后,进入

file

file

file

file

然后添加设备
file

file

这个mqtt连接参数十分重要,是stm32连接的关键

添加物模型数据

file

这里添加自己的想要的数据,这个很关键。添加成功之后可以在这里看到
file

刚开始的数值为0或者为空

三、stm32f103的配置

1、选择对应芯片,这里使用的是stm32f103c8t6,配置一些下载、时钟

file

file

file

使用系统帮我们配置的时钟
file

2、初始化串口,串口1作为调试打印的串口,串口2作为esp8266的通信串口。串口1不需要开启中断,串口2需要开启中断,并且该项目使用空闲中断+接收的DMA的方式。

file

file

开启usart2的中端
file

开启接收的DMA方式
file

3、cubemx的配置结束,需要加功能的自己添加,这里只是一个最简单的工程。
file

file

三、esp8266的接收和发送

对应一些基础知识和基本的使用,c语言知识我这里就不会做更多的介绍了。另外我们四利用cubemx创建的工程,我们最好按照他的格式去写代码。

1、printf

添加printf打印的支持,方便我们的调试,在usart.h文件中。

#if 1
#pragma import(__use_no_semihosting)
//标准库需要的支持函数
struct __FILE
{
    int handle;

};

FILE __stdout;
//定义_sys_exit()以避免使用半主机模式
void _sys_exit(int x)
{
    x = x;
}
//重定义fputc函数
int fputc(int ch, FILE *f)
{
    while((USART1->SR & 0X40) == 0); //循环发送,直到发送完毕
    USART1->DR = (uint8_t) ch;
    return ch;
}
#endif

2、串口2的DMA接收中断

这里也是稍微提及一下,如果想要更深入的了解,请看我写的另一篇 STM32F407的串口接收不定长数据两种方式HAL库
在这个阶段,我们需要定义一个数组,这个数组用来接收wifi给我们发送的数据,大家可以我一样起同一个名字。

void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */

  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */
  if(__HAL_UART_GET_FLAG(&huart2,UART_FLAG_IDLE) != RESET)    // 空闲中断的标志位
  {
    HAL_UART_DMAStop(&huart2);                               //停止接收
    esp_cnt = ESPBUFF_MAX_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);    // 计算接收的数据长度
    HAL_UART_Transmit(&huart1,esp_buff,esp_cnt,1000);
//    printf("rec = %s\r\n",esp_buff);
    HAL_UART_Receive_DMA(&huart2,esp_buff,ESPBUFF_MAX_SIZE);         // 开启DMA继续接收
    __HAL_UART_CLEAR_IDLEFLAG(&huart2);
  }
  /* USER CODE END USART2_IRQn 1 */
}

file

3、esp8266的函数方法

esp8266的发送函数

void ESP8266_SendString(uint8_t *str,uint8_t len)
{
    uint8_t i=0;
    for(i=0;i<len;i++)
    {
        USART2->DR = *str;
        str++;
        HAL_Delay(1);
    }
}
uint8_t ESP8266_SendCmd(uint8_t *cmd,uint8_t *res)
{
    uint8_t num = 200;
    ESP8266_Clear();
    // 发送指令
    ESP8266_SendString(cmd,strlen((const char *)cmd));
    while(num--)
    {
        if(strstr((const char*)esp_buff,(const char *)res)!=NULL)
        {
            ESP8266_Clear();
            return 0;
        }
        HAL_Delay(10);
    }
    return 1;
}

初始化
我们是在这里开启dma的接收的,还有空闲中断

#define      WIFI_NAME      "m_phone"       // wifi名
#define      WIFI_PASS      "123456678"       // wifi密码
void ESP8266_Init(void)
{
    HAL_UART_Receive_DMA(&huart2,esp_buff,ESPBUFF_MAX_SIZE);    // 开启DMA接收
    __HAL_UART_ENABLE_IT(&huart2,UART_IT_IDLE);      // 开启串口的空闲中断
    while(ESP8266_SendCmd("AT+RST\r\n", "ready"))
    while(ESP8266_SendCmd("AT\r\n","OK")){}
    while(ESP8266_SendCmd("AT+CWMODE=1\r\n","OK")){}
    //加入wifi热点
    while(ESP8266_SendCmd("AT+CWJAP=\""WIFI_NAME"\",\""WIFI_PASS"\"\r\n","OK")){}
    // 设置   
    printf("success");

}

清空esp8266的数组

void ESP8266_Clear()
{
    memset(esp_buff,0,sizeof(esp_buff));
    esp_cnt = 0;
}

四、连接阿里云

1、通过上面的操作我们可以连接wifi热点,后面我们将连接阿里云。
在第二点中我们创建了一个设备,里面提供了连接用到的MQTT参数。
根据这个更改对应的信息,用我提供的是连接不上的,我更改了。

#define      ALI_USERNAME       "stm32&ix25oHiHCSl"                                         // 用户名
#define      ALICLIENTLD            "ix25oHiHCSl.stm32|securemode=2\\,signmethod=hmasha256\\,timestamp=1688993186406|"                // 客户id
#define      ALI_PASSWD         "08d7d8eb8bf44a452813fe04194cdb3b2d6b5ec58accfd115878efb403d0144a9"           // MQTT 密码
#define      ALI_MQTT_HOSTURL   "iot-06z00b14nanp9ew.mqtt.iothub.aliyuncs.com"            // mqtt连接的网址
#define      ALI_PORT           "1883"                // 端口

#define      ALI_TOPIC_SET          "/sys/ix25aHqHCSl/stm32/thing/service/property/set"
#define      ALI_TOPIC_POST         "/sys/ix25aHqHCSl/stm32/thing/event/property/post"
void Ali_Yun_Init(void)
{
    //设置用户名,密码
    while(ESP8266_SendCmd("AT+MQTTUSERCFG=0,1,\"NULL\",\""ALI_USERNAME"\",\""ALI_PASSWD"\",0,0,\"\"\r\n","OK")){}
    HAL_Delay(10);
    // 设置客服id
    while(ESP8266_SendCmd("AT+MQTTCLIENTID=0,\""ALICLIENTLD"\"\r\n","OK")){}

    // 连接腾讯云  AT+MQTTCONN=0,"iot-06z00b28nanp9ew.mqtt.iothub.aliyuncs.com",1883,1
    while(ESP8266_SendCmd("AT+MQTTCONN=0,\""ALI_MQTT_HOSTURL"\",1883,1\r\n","OK")){}

    Ali_Yun_Topic();
}

void Ali_Yun_Topic(void)
{
    //"AT+MQTTPUB=0,\"发布的主题\",\"";
    while(ESP8266_SendCmd("AT+MQTTSUB=0,\""ALI_TOPIC_SET"\",0\r\n","OK")){}

    while(ESP8266_SendCmd("AT+MQTTSUB=0,\""ALI_TOPIC_POST"\",0\r\n","OK")){}
}

在上面的代码中,订阅和发布的主题都是不一样的,要根据自己的设备进行更改。

file

五、数据上报和数据解析

对于发送和阿里云下发的数据都是一个json格式
发送的格式:
AT+MQTTPUB=0,"/sys/ix25oHiHCSl/stm32/thing/event/property/post","{\"params\":{\"temperature\":1\,\"Humidity\":1}\,\"version\":\"1.0.0\"}",0,0
接收数据的格式:
+MQTTSUBRECV:0,"/sys/ix25oHiHCSl/stm32/thing/service/property/set",121,{"method":"thing.service.property.set","id":"1469885784","params":{"Humidity":45.3,"temperature":25.5},"version":"1.0.0"}
所以我们主要是对数据进行组装和解析
我们这里用到了cJSON,这是一个开源的项目,大家可以自己在网上找一下,可以在网盘中下载,就一个cJSON.h和一个cJSON.c两个文件。

1、发送数据

我们是模拟了两个数据,temp_value、humi_value,大家可以把自己想要的数据上传

//阿里云数据上传
void Ali_Yun_Send(void)
{
    uint8_t msg_buf[1024];
    uint8_t params_buf[1024];
    uint8_t data_value_buf[24];
    uint16_t move_num = 0;
    cJSON *send_cjson = NULL;
    char *str = NULL;
    int i=0;

    printf("str = %p\r\n",&str);

    cJSON *params_cjson = NULL;
    memset(msg_buf,0,sizeof(msg_buf));
    memset(params_buf,0,sizeof(params_buf));
    memset(data_value_buf,0,sizeof(data_value_buf));
    // "{\\\"params\\\":{\\\"temperature\\\":%f\\,\\\"Humidity\\\":%f\\}\\,\\\"version\\\":\\\"1.0.0\\\"}"

    send_cjson = cJSON_CreateObject();   // 创建cjson

    // 构建发送的json
    params_cjson = cJSON_CreateObject();

//============================================== 发送的数据================================================
    printf("cjson发送数据 temp_value = %f\r\n",temp_value);
    printf("cjson发送数据 humi_value = %f\r\n",humi_value);

    cJSON_AddNumberToObject(params_cjson,"temperature",temp_value++);
    cJSON_AddNumberToObject(params_cjson,"Humidity",humi_value++);

//============================================== 发送的数据================================================
    // 加入主的json数据中
    cJSON_AddItemToObject(send_cjson, "params", params_cjson);
    cJSON_AddItemToObject(send_cjson,"version",cJSON_CreateString("1.0.0"));
    str = cJSON_PrintUnformatted(send_cjson);

    printf("json格式 = %s\r\n",str);

    // 加转义字符
    for(i=0;*str!='\0';i++)
    {
        params_buf[i] = *str;
        if(*(str+1)=='"'||*(str+1)==',')
        {
            params_buf[++i] = '\\';
        }
        str++;
        move_num++;
    }
    str = str - move_num;
    printf("params_buf = %s\r\n",params_buf);
    // 整理所有数据
    sprintf((char *)msg_buf,"AT+MQTTPUB=0,\""ALI_TOPIC_POST"\",\"%s\",0,0\r\n",params_buf);
    printf("开始发送数据:%s\r\n",msg_buf);
    ESP8266_SendCmd(msg_buf,"OK");
    ESP8266_Clear();
    cJSON_Delete(send_cjson);
    if(str!=NULL){
        free(str);
        str = NULL;
        printf("释放str空间成功\r\n");
    }
}

上面的代码是利用cjson的对象,组装成一个json格式,但是我们在用at指令发送的时候是要有 '\'这个字符的,所以我们将数据进行了第二次的处理。这里注意要对cjson和str的内存进行释放。不然会出很多问题

2、数据的解析

这里也是对温度和湿度进行解析,需要解析别的也可以自己添加,主要是对cjson的函数使用。如果有必要我后面出一个对cjson的使用文章,主要是cjson的内存释放问题,比较麻烦,我被这个搞了好久。

uint8_t cjson_err_num = 0;  //cjson 解析错误的次数
void Ali_Yun_GetRCV(void)
{
    cJSON *cjson = NULL;
    int num;
    char topic_buff[256];
    char recv_buffer[ESPBUFF_MAX_SIZE];
    char *ptr_recv = strstr((const char *)esp_buff,"+MQTTSUBRECV");
    // "/sys/ix25oHiHCSl/stm32/thing/service/property/set"
    if(ptr_recv!=NULL)  // 存在
    {
        memset(topic_buff,0,sizeof(topic_buff));
        sscanf((char *)esp_buff,"+MQTTSUBRECV:0,%[^,],%d,%s",topic_buff,&num,recv_buffer);
        if(strstr(topic_buff,ALI_TOPIC_SET))      // 判断主题
        {
            printf("========================数据解析开始===========================\r\n");
            printf("接收数据成功,开始解析  %s\r\n",recv_buffer);
            cjson = cJSON_Parse(recv_buffer);
            if(cjson==NULL)
            {
                printf("cjson 解析错误\r\n");
                cjson_err_num++;
                if(cjson_err_num>3){
                    ESP8266_Clear();
                    cjson_err_num = 0;
                }           
                printf("========================数据解析失败===========================\r\n");
            }
            else
            {
                cJSON *json_data = NULL;
                json_data = cJSON_GetObjectItem(cjson,"params");
                cjson_err_num = 0;
                if(json_data==NULL){
                    printf("cjson  没有数据\r\n");
                    return;
                }else
                {
                    printf("cjson 内存大小为 = %d\r\n",sizeof(cjson));
//                  printf("数据接收:%s\r\n",esp_buff);
                    // ====================================解析数据=========================================
                    if(cJSON_GetObjectItem(json_data,"temperature")!=NULL)
                    {
                        temp_value = cJSON_GetObjectItem(json_data,"temperature")->valuedouble;
                        printf("csjon解析成功 temp_value = %f\r\n",temp_value);
                    }
                    if(cJSON_GetObjectItem(json_data,"Humidity")!=NULL)
                    {
                        humi_value = cJSON_GetObjectItem(json_data,"Humidity")->valuedouble;
                        printf("csjon解析成功 Humidity = %f\r\n",humi_value);
                    }       //======================================================================================
                }
                ESP8266_Clear();
                cJSON_Delete(cjson);
                printf("========================数据解析成功===========================\r\n");
            }
        }
    }
}

该方法是利用了sscanf对每一个部分进行分割,取到json格式的部分,交给cjson解析,主要还是注意内存泄漏的问题。

六、主函数

因为c8t6资源本来就少,所以我们这里并没有用到定时器,利用标志位大概取一个时间循环发送数据,对于判断接收标志位的判断也是可以丢到while循环,因为我们对espbuff的清理都是在处理了数组之后再去清空的,所以一般情况是不会造成数据没有接收到的情况。这个发送数据的时间,大家可以利用定时器,我这边就不加了。

int main(void)
{
  /* USER CODE BEGIN 1 */
    uint16_t time = 0;
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_USART1_UART_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  ESP8266_Init();
  Ali_Yun_Init();

  while (1)
  {
      time++;
      if(time>1000)
      {
          Ali_Yun_Send();   // 上传数据
          time = 0;
      }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
      Ali_Yun_GetRCV();
      HAL_Delay(5);
  }
  /* USER CODE END 3 */
}

小结

总的来说这个项目还是比较简单,适合新手对串口通信进一步了解。
第一次写这种文章,请多多指教吧!谢谢!
源码下载

博客内容均系原创,未经允许严禁转载!
您可以通过 RSS 订阅本站文章更新,订阅地址:https://blognas.hwb0307.com/feed/什么是 RSS ?

评论

  1. Windows Chrome
    5 月前
    2023-11-25 14:06:54

    哈哈

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇