基于STM32和oneNET云平台的数据收罗体系(MQTT协议)

曹旭辉  金牌会员 | 2024-6-23 14:12:44 | 来自手机 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 474|帖子 474|积分 1422


前言

该篇为基于stm32+esp8266通过mqtt协议毗连onenet物联网云平台,单片机部分将收罗到的数据(温湿度、光照强度、压强等等)上传至云平台服务器,云平台可下发指令操控单片机,实现远程通讯。
一、onenet云平台产品创建

1. 第一步,注册账号后点击右上角 控制台

2. 第二步,看左上角 选择切换旧版本

3. 第三步,左上角,全部产品中选择多协议接入

4. 点击添加产品,填好产品信息(红框重点)

5. 选择添加设备

6. 至此,完成产品创建,示比方下:

二、硬件选择



  • stm32开辟板
    原子哥、野火老师等各类开辟板都可。
  • esp8266 WiFi模块
    ATK-ESP-01或01S都可
  • 各类传感器
    DHT11温湿度传感器、GY-39光强度传感器、MQ-2烟雾传感器、led灯等等
三、计划理念


  • 本项目需将传感器收罗到的数据打印在串供词自己查看,以是这里需消费一个串口
  • esp8266需通过串口毗连服务器,吸收云服务器发送来的数据,以是这里也需消费一个串口
  • 单片机(客户端)需每隔一段时间向云服务器发送ping命令(心跳包),用于保持和服务器毗连,因为长时间没给服务器发送数据会被服务器逼迫踢下线,以是这里需消费一个定时器
  • 每隔一段时间需检测云服务器那边有没有向这边发送指令,并将串口二吸收到的数据依次放入MQTT吸收缓冲器中,并及时处理,以是这里需消费一个定时器
  • 将传感器收罗到的数据每隔一段时间重发更新,so,这里也须要消费一个定时器
综上,共消费两个串口,三个定时器。
云端及时检测与远程操控:


四、实战编程

1. 传感器部分

(1)DHT11
  1. /*-------------------------------------------------*/
  2. /*函数名:复位DHT11                                */
  3. /*参  数:无                                       */
  4. /*返回值:无                                       */
  5. /*-------------------------------------------------*/  
  6. void DHT11_Rst(void)          
  7. {                 
  8.   DHT11_IO_OUT();         //设置IO输出模式
  9.   DHT11_OUT(0);         //拉低IO
  10.   DelayMs(30);      //拉低至少18ms,我们拉低30
  11.   DHT11_OUT(1);         //拉高IO
  12.   DelayUs(30);      //主机拉高20~40us,我们拉高30us
  13. }
  14. /*-------------------------------------------------*/
  15. /*函数名:等待DHT11的回应                           */
  16. /*参  数:无                                       */
  17. /*返回值:1错误 0正确                               */
  18. /*-------------------------------------------------*/
  19. char DHT11_Check(void)           
  20. {   
  21.         char timeout;                            //定义一个变量用于超时判断  
  22.        
  23.         timeout = 0;                             //超时变量清零   
  24.         DHT11_IO_IN();                           //IO设置输入模式
  25.     while((DHT11_DQ_IN == 1) && (timeout < 70))//DHT11会拉低40~50us,我们等待70us超时时间       
  26.         {         
  27.                 timeout++;                           //超时变量+1
  28.                 DelayUs(1);                                //延时1us
  29.         }
  30.         if(timeout >= 70)return 1;               //如果timeout>=70,说明是因为超时退出的while循环,返回1表示错误
  31.         else timeout = 0;                        //反之,说明是因为等到了DHT11拉低IO,退出的while循环,正确并清零timeout
  32.     while((DHT11_DQ_IN == 0) && (timeout < 70))//DHT11拉低后会再次拉高40~50us,,我们等待70us超时时间       
  33.         {                
  34.                 timeout++;                           //超时变量+1
  35.                 DelayUs(1);                          //延时1us
  36.         }
  37.         if(timeout >= 70)return 2;               //如果timeout>=70,说明是因为超时退出的while循环,返回2表示错误  
  38.         return 0;                                //反之正确,返回0
  39. }
  40. /*-------------------------------------------------*/
  41. /*函数名:读取一个位                                */
  42. /*参  数:无                                       */
  43. /*返回值:1或0                                     */
  44. /*-------------------------------------------------*/
  45. char DHT11_Read_Bit(void)                          
  46. {
  47.         char timeout;                                     //定义一个变量用于超时判断  
  48.        
  49.         timeout = 0;                               //清零timeout       
  50.         while((DHT11_DQ_IN == 1) && (timeout < 40))//每一位数据开始,是12~14us的低电平,我们等40us
  51.         {   
  52.                 timeout++;                             //超时变量+1
  53.                 DelayUs(1);                            //延时1us
  54.         }
  55.         timeout = 0;                               //清零timeout       
  56.         while((DHT11_DQ_IN == 0) && (timeout < 60))//接下来,DHT11会拉高IO,根据拉高的时间判断是0或1,我们等60us
  57.         {  
  58.                 timeout++;                             //超时变量+1
  59.                 DelayUs(1);                            //延时1us
  60.         }
  61.         DelayUs(35);                               //延时35us
  62.         if(DHT11_DQ_IN)return 1;                   //如果延时后,是高电平,那么本位接收的是1,返回1
  63.         else return 0;                                       //反之延时后,是低电平,那么本位接收的是0,返回0
  64. }
  65. /*-------------------------------------------------*/
  66. /*函数名:读取一个字节                              */
  67. /*参  数:无                                       */
  68. /*返回值:数据                                      */
  69. /*-------------------------------------------------*/
  70. char DHT11_Read_Byte(void)   
  71. {        
  72.     char i;                                                       //定义一个变量用于for循环  
  73.         char dat;                                              //定义一个变量用于保存数据
  74.         dat = 0;                                                //清除保存数据的变量
  75.         for (i = 0; i < 8; i++){                              //一个字节8位,循环8次       
  76.                    dat <<= 1;                                            //左移一位,腾出空位   
  77.             dat |= DHT11_Read_Bit();                              //读取一位数据
  78.     }                                                    
  79.     return dat;                                                   //返回一个字节的数据
  80. }
  81. /*-------------------------------------------------*/
  82. /*函数名:读取一次数据温湿度                         */
  83. /*参  数:temp:温度值                               */
  84. /*参  数:humi:湿度值                               */
  85. /*返回值:1错误 0正确                               */
  86. /*-------------------------------------------------*/
  87. char DHT11_Read_Data(char *temp, char *humi)   
  88. {        
  89.         char buf[5];                                         //一次完整的数据有5个字节,定义一个缓冲区
  90.        
  91.         char i;                                              //定义一个变量用于for循环  
  92.         DHT11_Rst();                                         //复位DHT11       
  93.         if(DHT11_Check() == 0)                                                             //判断DHT11回复状态=0的话,表示正确,进入if
  94.         {
  95.                 for(i = 0; i < 5; i++){                          //一次完整的数据有5个字节,循环5次               
  96.                         buf[i] = DHT11_Read_Byte();                  //每次读取一个字节
  97.                 }
  98.                 if((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])//判断数据校验,前4个字节相加应该等于第5个字节,正确的话,进入if       
  99.                 {     
  100.                         //u1_printf("%d\r\n",buf[0]);         //温度数据
  101.                         //u1_printf("%d\r\n",buf[1]);
  102.                         //u1_printf("%d\r\n",buf[2]);        //湿度数据
  103.                         //u1_printf("%d\r\n",buf[3]);
  104.                         //u1_printf("%d\r\n",buf[4]);
  105.                         *humi = buf[0];                              //湿度数据,保存在humi指针指向的地址变量中
  106.                         *temp = buf[2];                                                             //温度数据,保存在temp指针指向的地址变量中
  107.                 }else return 1;                                  //反之,数据校验错误,直接返回1
  108.         }else return 2;                                      //反之,如果DHT11回复状态=1的话,表示错误,进入else,直接返回2
  109.        
  110.         return 0;                                                 //读取正确返回0   
  111. }  
  112. /*-------------------------------------------------*/
  113. /*函数名:初始化DHT11                              */
  114. /*参  数:无                                       */
  115. /*返回值:1错误 0正确                              */
  116. /*-------------------------------------------------*/             
  117. char DHT11_Init(void)
  118. {
  119.         GPIO_InitTypeDef GPIO_InitStructure;                  //定义一个IO端口参数结构体
  120.        
  121.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能PA端口时钟
  122.        
  123.         GPIO_InitStructure.GPIO_Pin =  GPIO_Pin_6;            //准备设置PA8
  124.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;     //速率50Mhz
  125.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;             //推免输出方式
  126.         GPIO_Init(GPIOA, &GPIO_InitStructure);                      //设置PA8       
  127.         DHT11_Rst();                                          //复位DHT11
  128.         return DHT11_Check();                                 //返回DHT11的回复状态
  129. }
复制代码
(2)MQ-2
  1. void Adc_Init(void)
  2. {
  3.     ADC_InitTypeDef ADC_InitStructure;
  4.         GPIO_InitTypeDef GPIO_InitStructure;
  5.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |RCC_APB2Periph_ADC1, ENABLE );          //使能ADC1通道时钟
  6.         RCC_ADCCLKConfig(RCC_PCLK2_Div6);   //设置ADC分频因子6 72M/6=12,ADC最大时间不能超过14M
  7.         //PA1 作为模拟通道输入引脚                        
  8.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
  9.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;                //模拟输入引脚
  10.         GPIO_Init(GPIOA, &GPIO_InitStructure);       
  11.         ADC_DeInit(ADC1);  //复位ADC1
  12.         ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;        //ADC工作模式:ADC1和ADC2工作在独立模式
  13.         ADC_InitStructure.ADC_ScanConvMode = DISABLE;        //模数转换工作在单通道模式
  14.         ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;        //模数转换工作在单次转换模式
  15.         ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;        //转换由软件而不是外部触发启动
  16.         ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;        //ADC数据右对齐
  17.         ADC_InitStructure.ADC_NbrOfChannel = 1;        //顺序进行规则转换的ADC通道的数目
  18.         ADC_Init(ADC1, &ADC_InitStructure);        //根据ADC_InitStruct中指定的参数初始化外设ADCx的寄存器   
  19.   
  20.         ADC_Cmd(ADC1, ENABLE);        //使能指定的ADC1
  21.        
  22.         ADC_ResetCalibration(ADC1);        //使能复位校准  
  23.          
  24.         while(ADC_GetResetCalibrationStatus(ADC1));        //等待复位校准结束
  25.        
  26.         ADC_StartCalibration(ADC1);         //开启AD校准
  27.         while(ADC_GetCalibrationStatus(ADC1));         //等待校准结束
  28. //        ADC_SoftwareStartConvCmd(ADC1, ENABLE);                //使能指定的ADC1的软件转换启动功能
  29. }
  30. /**************************** ADC转换值获取函数 ****************************
  31. 功  能:获取ADC模块转换后的数值
  32. 参  数:无
  33. 返回值:存放ADC转换值
  34. ***************************************************************************/
  35. u16 Get_adcvalue(void)
  36. {
  37.         //设置指定ADC的规则组通道,一个序列,采样时间
  38.         ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5 );        //ADC1,ADC通道,采样时间为239.5周期                                      
  39.   
  40.         ADC_SoftwareStartConvCmd(ADC1, ENABLE);                //使能指定的ADC1的软件转换启动功能       
  41.          
  42.         while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC ));//等待转换结束
  43.         return ADC_GetConversionValue(ADC1);        //返回最近一次ADC1规则组的转换结果
  44. }
  45. u16 Get_Adc_Average(u8 times)
  46. {
  47.         u32 temp_val=0;
  48.         u8 t;
  49.         for(t=0;t<times;t++)
  50.         {
  51.                 temp_val+=Get_adcvalue();
  52.                 DelayMs(5);
  53.         }
  54.         return temp_val/times;
  55. }
复制代码
(3)LED
  1. /*-------------------------------------------------*/
  2. /*函数名:初始化LED函数                                   */
  3. /*参  数:无                                       */
  4. /*返回值:无                                       */
  5. /*-------------------------------------------------*/
  6. void LED_Init(void)
  7. {             
  8.         GPIO_InitTypeDef GPIO_InitStr;
  9.        
  10.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB ,ENABLE);     //使能时钟
  11.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE ,ENABLE);
  12.        
  13.             
  14.        
  15.         GPIO_InitStr.GPIO_Mode=GPIO_Mode_Out_PP;    //结构体变量赋值
  16.         GPIO_InitStr.GPIO_Pin=GPIO_Pin_5;
  17.         GPIO_InitStr.GPIO_Speed=GPIO_Speed_50MHz;
  18.        
  19.         GPIO_Init(GPIOB,&GPIO_InitStr);       //初始化GPIOB
  20.         GPIO_SetBits(GPIOB,GPIO_Pin_5);       //赋予 GPIO_Pin_5 初始为高电平
  21.        
  22.        
  23.         GPIO_InitStr.GPIO_Mode=GPIO_Mode_Out_PP;  
  24.         GPIO_InitStr.GPIO_Pin=GPIO_Pin_5;
  25.         GPIO_InitStr.GPIO_Speed=GPIO_Speed_50MHz;
  26.        
  27.         GPIO_Init(GPIOE,&GPIO_InitStr);
  28.         GPIO_SetBits(GPIOE,GPIO_Pin_5);
  29. }
  30. /*-------------------------------------------------*/
  31. /*函数名:LED开启                                  */
  32. /*参  数:无                                       */
  33. /*返回值:无                                       */
  34. /*-------------------------------------------------*/
  35. void LED_On(void)
  36. {                       
  37.         GPIO_ResetBits(GPIOB, GPIO_Pin_5);                                                  //PD2 输出低
  38. }
  39. /*-------------------------------------------------*/
  40. /*函数名:LED关闭                                  */
  41. /*参  数:无                                       */
  42. /*返回值:无                                       */
  43. /*-------------------------------------------------*/
  44. void LED_Off(void)
  45. {               
  46.         GPIO_SetBits(GPIOB, GPIO_Pin_5);                                                  //PD2 输出高
  47. }
复制代码
2. ESP8266

  1. /*-------------------------------------------------*/
  2. /*函数名:初始化WiFi的复位IO                       */
  3. /*参  数:无                                       */
  4. /*返回值:无                                       */
  5. /*-------------------------------------------------*/
  6. void WiFi_ResetIO_Init(void)
  7. {
  8.         GPIO_InitTypeDef GPIO_InitStructure;                    //定义一个设置IO端口参数的结构体
  9.         RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA , ENABLE); //使能PA端口时钟
  10.        
  11.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;               //准备设置PA4
  12.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;       //速率50Mhz
  13.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;               //推免输出方式
  14.         GPIO_Init(GPIOA, &GPIO_InitStructure);                        //设置PA4
  15.         RESET_IO(1);                                            //复位IO拉高电平
  16. }
  17. /*-------------------------------------------------*/
  18. /*函数名:WiFi发送设置指令                          */
  19. /*参  数:cmd:指令                                */
  20. /*参  数:timeout:超时时间(100ms的倍数)          */
  21. /*返回值:0:正确   其他:错误                      */
  22. /*-------------------------------------------------*/
  23. char WiFi_SendCmd(char *cmd, int timeout)
  24. {
  25.         WiFi_RxCounter = 0;                                                   //WiFi接收数据量变量清零                        
  26.         memset(WiFi_RX_BUF, 0, WiFi_RXBUFF_SIZE);                             //清空WiFi接收缓冲区
  27.         WiFi_printf("%s\r\n", cmd);                                          //发送指令
  28.         while(timeout--)                                                                                //等待超时时间到0
  29.         {                                                  
  30.                 DelayMs(100);                                                 //延时100ms
  31.                 if(strstr(WiFi_RX_BUF, "OK"))                                      //如果接收到OK表示指令成功
  32.                         break;                                                                               //主动跳出while循环
  33.                 u1_printf("%d ", timeout);                                         //串口输出现在的超时时间
  34.         }                       
  35.         u1_printf("\r\n");                                                  //串口输出信息
  36.         if(timeout <= 0)return 1;                                                    //如果timeout<=0,说明超时时间到了,也没能收到OK,返回1
  37.         else return 0;                                                                                 //反之,表示正确,说明收到OK,通过break主动跳出while
  38. }
  39. /*-------------------------------------------------*/
  40. /*函数名:WiFi复位                                 */
  41. /*参  数:timeout:超时时间(100ms的倍数)          */
  42. /*返回值:0:正确   其他:错误                      */
  43. /*-------------------------------------------------*/
  44. char WiFi_Reset(int timeout)
  45. {
  46.         RESET_IO(0);                                              //复位IO拉低电平
  47.         DelayMs(500);                                                    //延时500ms
  48.         RESET_IO(1);                                                     //复位IO拉高电平       
  49.         while(timeout--)                                                                          //等待超时时间到0
  50.         {                                                
  51.                 DelayMs(100);                                           //延时100ms
  52.                 if(strstr(WiFi_RX_BUF, "ready"))                         //如果接收到ready表示复位成功
  53.                         break;                                                                            //主动跳出while循环
  54.                 u1_printf("%d ", timeout);                               //串口输出现在的超时时间
  55.         }
  56.         u1_printf("\r\n");                                        //串口输出信息
  57.         if(timeout <= 0)return 1;                                          //如果timeout<=0,说明超时时间到了,也没能收到ready,返回1
  58.         else return 0;                                                                              //反之,表示正确,说明收到ready,通过break主动跳出while
  59. }
  60. /*-------------------------------------------------*/
  61. /*函数名:WiFi加入路由器指令                       */
  62. /*参  数:timeout:超时时间(1s的倍数)            */
  63. /*返回值:0:正确   其他:错误                     */
  64. /*-------------------------------------------------*/
  65. char WiFi_JoinAP(int timeout)
  66. {               
  67.         WiFi_RxCounter = 0;                                    //WiFi接收数据量变量清零                        
  68.         memset(WiFi_RX_BUF, 0, WiFi_RXBUFF_SIZE);              //清空WiFi接收缓冲区
  69.         WiFi_printf("AT+CWJAP="%s","%s"\r\n", SSID, PASS); //发送指令       
  70.         while(timeout--)                                                                           //等待超时时间到0
  71.         {                                   
  72.                 DelayMs(1000);                                                //延时1s
  73.                 if(strstr(WiFi_RX_BUF, "OK"))   //如果接收到WIFI GOT IP表示成功
  74.                         break;                                                                  //主动跳出while循环
  75.                 u1_printf("%d ", timeout);                         //串口输出现在的超时时间
  76.         }
  77.         u1_printf("\r\n%s\r\n", WiFi_RX_BUF);
  78.         u1_printf("\r\n");                                            //串口输出信息
  79.         if(timeout <= 0)return 1;                              //如果timeout<=0,说明超时时间到了,也没能收到WIFI GOT IP,返回1
  80.         return 0;                                              //正确,返回0
  81. }
  82. /*-------------------------------------------------*/
  83. /*函数名:连接TCP服务器,并进入透传模式            */
  84. /*参  数:timeout: 超时时间(100ms的倍数)        */
  85. /*返回值:0:正确  其他:错误                      */
  86. /*-------------------------------------------------*/
  87. char WiFi_Connect_Server(int timeout)
  88. {       
  89.         WiFi_RxCounter=0;                                      //WiFi接收数据量变量清零                        
  90.         memset(WiFi_RX_BUF,0,WiFi_RXBUFF_SIZE);         //清空WiFi接收缓冲区   
  91.         WiFi_printf("AT+CIPSTART="TCP","%s",%d\r\n", ServerIP, ServerPort);//发送连接服务器指令
  92.         while(timeout--)                                                                //等待超时与否
  93.         {                           
  94.                 DelayMs(100);                                     //延时100ms       
  95.                 if(strstr(WiFi_RX_BUF, "CONNECT"))          //如果接受到CONNECT表示连接成功
  96.                         break;                                  //跳出while循环
  97.                 if(strstr(WiFi_RX_BUF, "CLOSED"))           //如果接受到CLOSED表示服务器未开启
  98.                         return 1;                               //服务器未开启返回1
  99.                 if(strstr(WiFi_RX_BUF, "ALREADY CONNECTED"))//如果接受到ALREADY CONNECTED已经建立连接
  100.                         return 2;                               //已经建立连接返回2
  101.                 u1_printf("%d ", timeout);                   //串口输出现在的超时时间  
  102.         }
  103.         u1_printf("\r\n");                              //串口输出信息
  104.         if(timeout <= 0)return 3;                       //超时错误,返回3
  105.         else                                            //连接成功,准备进入透传
  106.         {
  107.                 u1_printf("连接服务器成功,准备进入透传\r\n"); //串口显示信息
  108.                 WiFi_RxCounter = 0;                          //WiFi接收数据量变量清零                        
  109.                 memset(WiFi_RX_BUF, 0, WiFi_RXBUFF_SIZE);    //清空WiFi接收缓冲区     
  110.                 WiFi_printf("AT+CIPSEND\r\n");               //发送进入透传指令
  111.                 while(timeout--)                                                         //等待超时与否
  112.                 {                           
  113.                         DelayMs(100);                            //延时100ms       
  114.                         if(strstr(WiFi_RX_BUF, "\r\nOK\r\n\r\n>"))//如果成立表示进入透传成功
  115.                                 break;                                   //跳出while循环
  116.                         u1_printf("%d ", timeout);                //串口输出现在的超时时间  
  117.                 }
  118.                 if(timeout <= 0)return 4;                      //透传超时错误,返回4       
  119.         }
  120.         return 0;                                             //成功返回0       
  121. }
  122. /*-------------------------------------------------*/
  123. /*函数名:WiFi_Smartconfig                         */
  124. /*参  数:timeout:超时时间(1s的倍数)            */
  125. /*返回值:0:正确   其他:错误                     */
  126. /*-------------------------------------------------*/
  127. char WiFi_Smartconfig(int timeout)
  128. {
  129.        
  130.         WiFi_RxCounter=0;                                           //WiFi接收数据量变量清零                        
  131.         memset(WiFi_RX_BUF,0,WiFi_RXBUFF_SIZE);                     //清空WiFi接收缓冲区     
  132.         while(timeout--)                                                                        //等待超时时间到0
  133.         {                                          
  134.                 DelayMs(1000);                                                         //延时1s
  135.                 if(strstr(WiFi_RX_BUF, "connected"))                               //如果串口接受到connected表示成功
  136.                         break;                                                  //跳出while循环  
  137.                 u1_printf("%d ", timeout);                                 //串口输出现在的超时时间  
  138.         }       
  139.         u1_printf("\r\n");                                          //串口输出信息
  140.         if(timeout <= 0)return 1;                                     //超时错误,返回1
  141.         return 0;                                                   //正确返回0
  142. }
  143. /*-------------------------------------------------*/
  144. /*函数名:等待加入路由器                           */
  145. /*参  数:timeout:超时时间(1s的倍数)            */
  146. /*返回值:0:正确   其他:错误                     */
  147. /*-------------------------------------------------*/
  148. char WiFi_WaitAP(int timeout)
  149. {               
  150.         while(timeout--){                               //等待超时时间到0
  151.                 DelayMs(1000);                                             //延时1s
  152.                 if(strstr(WiFi_RX_BUF, "WIFI GOT IP"))         //如果接收到WIFI GOT IP表示成功
  153.                         break;                                                                                                                            //主动跳出while循环
  154.                 u1_printf("%d ", timeout);                     //串口输出现在的超时时间
  155.         }
  156.         u1_printf("\r\n");                              //串口输出信息
  157.         if(timeout <= 0)return 1;                         //如果timeout<=0,说明超时时间到了,也没能收到WIFI GOT IP,返回1
  158.         return 0;                                       //正确,返回0
  159. }
  160. /*-------------------------------------------------*/
  161. /*函数名:WiFi连接服务器                           */
  162. /*参  数:无                                       */
  163. /*返回值:0:正确   其他:错误                     */
  164. /*-------------------------------------------------*/
  165. char WiFi_Connect_IoTServer(void)
  166. {       
  167.         u1_printf("准备复位模块\r\n");                   //串口提示数据
  168.         if(WiFi_Reset(50))                                                                //复位,100ms超时单位,总计5s超时时间
  169.         {                             
  170.                 u1_printf("复位失败,准备重启\r\n");              //返回非0值,进入if,串口提示数据
  171.                 return 1;                                   //返回1
  172.         }else u1_printf("复位成功\r\n");                 //串口提示数据
  173.        
  174.         u1_printf("准备设置STA模式\r\n");                //串口提示数据
  175.         if(WiFi_SendCmd("AT+CWMODE=1",50))//设置STA模式,100ms超时单位,总计5s超时时间
  176.         {            
  177.                 u1_printf("设置STA模式失败,准备重启\r\n");   //返回非0值,进入if,串口提示数据
  178.                 return 2;                                   //返回2
  179.         }else u1_printf("设置STA模式成功\r\n");          //串口提示数据
  180.        
  181.         if(wifi_mode==0) //如果联网模式=0:SSID和密码写在程序里
  182.         {                              
  183.                 u1_printf("准备取消自动连接\r\n");            //串口提示数据
  184.                 if(WiFi_SendCmd("AT+CWAUTOCONN=0",50))                 //取消自动连接,100ms超时单位,总计5s超时时间
  185.                 {      
  186.                         u1_printf("取消自动连接失败,准备重启\r\n"); //返回非0值,进入if,串口提示数据
  187.                         return 3;                                  //返回3
  188.                 }else u1_printf("取消自动连接成功\r\n");        //串口提示数据
  189.                                
  190.                 u1_printf("准备连接路由器\r\n");                //串口提示数据       
  191.                 if(WiFi_JoinAP(30))//连接路由器,1s超时单位,总计30s超时时间
  192.                 {                          
  193.                         u1_printf("连接路由器失败,准备重启\r\n");  //返回非0值,进入if,串口提示数据
  194.                         return 4;                                 //返回4       
  195.                 }else u1_printf("连接路由器成功\r\n");         //串口提示数据                       
  196.         }
  197.        
  198.         u1_printf("准备设置透传\r\n");                    //串口提示数据
  199.         if(WiFi_SendCmd("AT+CIPMODE=1",50))                          //设置透传,100ms超时单位,总计5s超时时间
  200.         {           
  201.                 u1_printf("设置透传失败,准备重启\r\n");       //返回非0值,进入if,串口提示数据
  202.                 return 8;                                    //返回8
  203.         }else u1_printf("设置透传成功\r\n");              //串口提示数据
  204.        
  205.         u1_printf("准备关闭多路连接\r\n");                //串口提示数据
  206.         if(WiFi_SendCmd("AT+CIPMUX=0",50))                                  //关闭多路连接,100ms超时单位,总计5s超时时间
  207.         {            
  208.                 u1_printf("关闭多路连接失败,准备重启\r\n");   //返回非0值,进入if,串口提示数据
  209.                 return 9;                                    //返回9
  210.         }else u1_printf("关闭多路连接成功\r\n");          //串口提示数据
  211.          
  212.         u1_printf("准备连接服务器\r\n");                  //串口提示数据
  213.         if(WiFi_Connect_Server(100))                                       //连接服务器,100ms超时单位,总计10s超时时间
  214.         {            
  215.                 u1_printf("连接服务器失败,准备重启\r\n");     //返回非0值,进入if,串口提示数据
  216.                 return 10;                                   //返回10
  217.         }else u1_printf("连接服务器成功\r\n");            //串口提示数据       
  218.         return 0;                                        //正确返回0
  219. }
  220.        
复制代码
3. 定时器

(1)初始化
  1. /*-------------------------------------------------*/
  2. /*函数名:定时器2使能10s定时                        */
  3. /*参  数:无                                       */
  4. /*返回值:无                                       */
  5. /*-------------------------------------------------*/
  6. void TIM2_ENABLE_10S(void)
  7. {
  8.         TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;             //定义一个设置定时器的变量
  9.         NVIC_InitTypeDef NVIC_InitStructure;                           //定义一个设置中断的变量       
  10.        
  11.         NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);                //设置中断向量分组:第2组 抢先优先级:0 1 2 3 子优先级:0 1 2 3               
  12.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);            //使能TIM2时钟       
  13.         TIM_DeInit(TIM2);                                              //定时器2寄存器恢复默认值       
  14.         TIM_TimeBaseInitStructure.TIM_Period = 20000-1;                    //设置自动重装载值
  15.         TIM_TimeBaseInitStructure.TIM_Prescaler = 36000-1;             //设置定时器预分频数
  16.         TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数模式
  17.         TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;    //1分频
  18.         TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);            //设置TIM2
  19.        
  20.         TIM_ClearITPendingBit(TIM2, TIM_IT_Update);                    //清除溢出中断标志位
  21.         TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);                     //使能TIM2溢出中断   
  22.         TIM_Cmd(TIM2, ENABLE);                                         //开TIM2                          
  23.        
  24.         NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;                //设置TIM2中断
  25.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;      //抢占优先级2
  26.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;             //子优先级1
  27.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                //中断通道使能
  28.         NVIC_Init(&NVIC_InitStructure);                                //设置中断
  29. }
  30. /*-------------------------------------------------*/
  31. /*函数名:定时器3使能30s定时                       */
  32. /*参  数:无                                       */
  33. /*返回值:无                                       */
  34. /*-------------------------------------------------*/
  35. void TIM3_ENABLE_30S(void)
  36. {
  37.         TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;             //定义一个设置定时器的变量
  38.         NVIC_InitTypeDef NVIC_InitStructure;                           //定义一个设置中断的变量
  39.        
  40.         NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);                //设置中断向量分组:第2组 抢先优先级:0 1 2 3 子优先级:0 1 2 3               
  41.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);           //使能TIM3时钟       
  42.         TIM_DeInit(TIM3);                                              //定时器3寄存器恢复默认值       
  43.         TIM_TimeBaseInitStructure.TIM_Period = 60000-1;                    //设置自动重装载值
  44.         TIM_TimeBaseInitStructure.TIM_Prescaler = 36000-1;             //设置定时器预分频数
  45.         TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数模式
  46.         TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;    //1分频
  47.         TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);            //设置TIM3
  48.        
  49.         TIM_ClearITPendingBit(TIM3, TIM_IT_Update);                    //清除溢出中断标志位
  50.         TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);                     //使能TIM3溢出中断   
  51.         TIM_Cmd(TIM3, ENABLE);                                         //开TIM3                          
  52.        
  53.         NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;                //设置TIM3中断
  54.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;      //抢占优先级2
  55.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;             //子优先级0
  56.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                //中断通道使能
  57.         NVIC_Init(&NVIC_InitStructure);                                //设置中断
  58. }
  59. /*-------------------------------------------------*/
  60. /*函数名:定时器3使能2s定时                        */
  61. /*参  数:无                                       */
  62. /*返回值:无                                       */
  63. /*-------------------------------------------------*/
  64. void TIM3_ENABLE_2S(void)
  65. {
  66.         TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;             //定义一个设置定时器的变量
  67.         NVIC_InitTypeDef NVIC_InitStructure;                           //定义一个设置中断的变量
  68.        
  69.         NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);                //设置中断向量分组:第2组 抢先优先级:0 1 2 3 子优先级:0 1 2 3               
  70.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);           //使能TIM3时钟
  71.         TIM_DeInit(TIM3);                                              //定时器3寄存器恢复默认值       
  72.         TIM_TimeBaseInitStructure.TIM_Period = 20000-1;                    //设置自动重装载值
  73.         TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1;              //设置定时器预分频数
  74.         TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数模式
  75.         TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;    //1分频
  76.         TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure);            //设置TIM3
  77.        
  78.         TIM_ClearITPendingBit(TIM3, TIM_IT_Update);                    //清除溢出中断标志位
  79.         TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);                     //使能TIM3溢出中断   
  80.         TIM_Cmd(TIM3, ENABLE);                                         //开TIM3                          
  81.        
  82.         NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;                //设置TIM3中断
  83.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;      //抢占优先级1
  84.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;             //子优先级0
  85.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                //中断通道使能
  86.         NVIC_Init(&NVIC_InitStructure);                                //设置中断
  87. }
  88. /*-------------------------------------------------*/
  89. /*函数名:定时器4初始化                            */
  90. /*参  数:arr:自动重装值   0~65535                */
  91. /*参  数:psc:时钟预分频数 0~65535                */
  92. /*返回值:无                                       */
  93. /*说  明:定时时间:arr*psc*1000/72000000  单位ms  */
  94. /*-------------------------------------------------*/
  95. void TIM4_Init(unsigned short int arr, unsigned short int psc)
  96. {
  97.         TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;              //定义一个设置定时器的变量
  98.         NVIC_InitTypeDef NVIC_InitStructure;                            //定义一个设置中断的变量
  99.        
  100.         NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);                 //设置中断向量分组:第2组 抢先优先级:0 1 2 3 子优先级:0 1 2 3               
  101.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);            //使能TIM4时钟       
  102.     TIM_TimeBaseInitStructure.TIM_Period = arr;                         //设置自动重装载值
  103.         TIM_TimeBaseInitStructure.TIM_Prescaler = psc;                  //设置定时器预分频数
  104.         TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数模式
  105.         TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;     //1分频
  106.         TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure);             //设置TIM4
  107.        
  108.         TIM_ClearITPendingBit(TIM4, TIM_IT_Update);                     //清除溢出中断标志位
  109.         TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);                      //使能TIM4溢出中断   
  110.         TIM_Cmd(TIM4, DISABLE);                                         //先关闭TIM4                          
  111.        
  112.         NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;                 //设置TIM4中断
  113.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;       //抢占优先级1
  114.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;              //子优先级0
  115.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                 //中断通道使能
  116.         NVIC_Init(&NVIC_InitStructure);                                 //设置中断
  117. }
复制代码
(2)中断
  1. /*-------------------------------------------------*/
  2. /*函数名:定时器4中断服务函数。处理MQTT数据          */
  3. /*参  数:无                                       */
  4. /*返回值:无                                       */
  5. /*-------------------------------------------------*/
  6. void TIM4_IRQHandler(void)
  7. {
  8.         if(TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)//如果TIM_IT_Update置位,表示TIM4溢出中断,进入if       
  9.         {                       
  10.                 memcpy(&MQTT_RxDataInPtr[2], Usart2_RxBuff, Usart2_RxCounter);  //拷贝数据到接收缓冲区
  11.                 MQTT_RxDataInPtr[0] = Usart2_RxCounter/256;                          //记录数据长度高字节
  12.                 MQTT_RxDataInPtr[1] = Usart2_RxCounter%256;                                                 //记录数据长度低字节
  13.                 MQTT_RxDataInPtr += RBUFF_UNIT;                                        //指针下移
  14.                 if(MQTT_RxDataInPtr == MQTT_RxDataEndPtr)                             //如果指针到缓冲区尾部了
  15.                         MQTT_RxDataInPtr = MQTT_RxDataBuf[0];                            //指针归位到缓冲区开头
  16.                 Usart2_RxCounter = 0;                                                //串口2接收数据量变量清零
  17.                 TIM_SetCounter(TIM3, 0);                                             //清零定时器3计数器,重新计时ping包发送时间
  18.                 TIM_Cmd(TIM4, DISABLE);                                                                 //关闭TIM4定时器
  19.                 TIM_SetCounter(TIM4, 0);                                                         //清零定时器4计数器
  20.                 TIM_ClearITPendingBit(TIM4, TIM_IT_Update);                                      //清除TIM4溢出中断标志        
  21.         }
  22. }
  23. /*-------------------------------------------------*/
  24. /*函数名:定时器3中断服务函数                      */
  25. /*参  数:无                                       */
  26. /*返回值:无                                       */
  27. /*-------------------------------------------------*/
  28. void TIM3_IRQHandler(void)
  29. {
  30.         if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)//如果TIM_IT_Update置位,表示TIM3溢出中断,进入if       
  31.                 {  
  32.                 switch(pingFlag)                                         //判断pingFlag的状态
  33.                 {                              
  34.                         case 0:                                                        //如果pingFlag等于0,表示正常状态,发送Ping报文  
  35.                                         MQTT_PingREQ();                 //添加Ping报文到发送缓冲区  
  36.                                         break;
  37.                         case 1:                                                        //如果pingFlag等于1,说明上一次发送到的ping报文,没有收到服务器回复,所以1没有被清除为0,可能是连接异常,我们要启动快速ping模式
  38.                                         TIM3_ENABLE_2S();             //我们将定时器6设置为2s定时,快速发送Ping报文
  39.                                         MQTT_PingREQ();                        //添加Ping报文到发送缓冲区  
  40.                                         break;
  41.                         case 2:                                                        //如果pingFlag等于2,说明还没有收到服务器回复
  42.                         case 3:                                            //如果pingFlag等于3,说明还没有收到服务器回复
  43.                         case 4:                                            //如果pingFlag等于4,说明还没有收到服务器回复       
  44.                                         MQTT_PingREQ();                  //添加Ping报文到发送缓冲区
  45.                                         break;
  46.                         case 5:                                                        //如果pingFlag等于5,说明我们发送了多次ping,均无回复,应该是连接有问题,我们重启连接
  47.                                         connectFlag = 0;        //连接状态置0,表示断开,没连上服务器
  48.                                         TIM_Cmd(TIM3, DISABLE); //关TIM3                                
  49.                                         break;                       
  50.                 }
  51.                 pingFlag++;                                              //pingFlag自增1,表示又发送了一次ping,期待服务器的回复
  52.                 TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除TIM3溢出中断标志        
  53.         }
  54. }
  55. /*-------------------------------------------------*/
  56. /*函数名:定时器2中断服务函数                      */
  57. /*参  数:无                                       */
  58. /*返回值:无                                       */
  59. /*-------------------------------------------------*/
  60.         extern u16 ADC_Val;
  61. void TIM2_IRQHandler(void)
  62. {
  63.         char humidity;                                //定义一个变量,保存湿度值
  64.         char temperature;                        //定义一个变量,保存温度值       
  65.                                
  66.         char head1[3];
  67.         char temp[50];                                //定义一个临时缓冲区1,不包括报头
  68.         char tempAll[100];                        //定义一个临时缓冲区2,包括所有数据
  69.        
  70.         int        dataLen = 0;                        //报文长度
  71.         ADC_Val_Disp(10,20);    //气敏检测
  72.         if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET)       
  73.         {
  74.                 DHT11_Read_Data(&temperature,&humidity);//读取温湿度值
  75.                 memset(temp,    0, 50);                                    //清空缓冲区1
  76.                 memset(tempAll, 0, 100);                                //清空缓冲区2
  77.                 memset(head1,   0, 3);                                        //清空MQTT头
  78.                 sprintf(temp,"{"MQ":"%d","TM":"%d","HM":"%d"}",
  79.                                                   (ADC_Val/100), temperature, humidity);//构建报文
  80.                 head1[0] = 0x03;                                                 //固定报头
  81.                 head1[1] = 0x00;                                                 //固定报头
  82.                 head1[2] = strlen(temp);                                  //剩余长度       
  83.                 sprintf(tempAll, "%c%c%c%s", head1[0], head1[1], head1[2], temp);
  84.                
  85.                 u1_printf("\r\n"); //串口显示相关数据
  86.                 u1_printf("%s\r\n", tempAll + 3);
  87.                
  88.                 dataLen = strlen(temp) + 3;
  89.                 MQTT_PublishQs0(Data_TOPIC_NAME,tempAll, dataLen);//添加数据,发布给服务器
  90.                
  91.                 TIM_ClearITPendingBit(TIM2, TIM_IT_Update);          
  92.         }
  93. }
复制代码
4. 串口

(1)初始化
  1. /*-------------------------------------------------*/
  2. /*函数名:初始化串口1发送功能                      */
  3. /*参  数:bound:波特率                            */
  4. /*返回值:无                                       */
  5. /*-------------------------------------------------*/
  6. void Usart1_Init(unsigned int bound)
  7. {                  
  8.     GPIO_InitTypeDef GPIO_InitStructure;     //定义一个设置GPIO功能的变量
  9.         USART_InitTypeDef USART_InitStructure;   //定义一个设置串口功能的变量
  10.    
  11.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); //使能串口1时钟
  12.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  //使能GPIOA时钟
  13.        
  14.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;              //准备设置PA9
  15.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;      //IO速率50M
  16.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;               //复用推挽输出,用于串口1的发送
  17.     GPIO_Init(GPIOA, &GPIO_InitStructure);                 //设置PA9
  18.    
  19.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;             //准备设置PA10
  20.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  //浮空输入,用于串口1的接收
  21.     GPIO_Init(GPIOA, &GPIO_InitStructure);                 //设置PA10
  22.        
  23.         USART_InitStructure.USART_BaudRate = bound;                                    //波特率设置
  24.         USART_InitStructure.USART_WordLength = USART_WordLength_8b;                    //8个数据位
  25.         USART_InitStructure.USART_StopBits = USART_StopBits_1;                         //1个停止位
  26.         USART_InitStructure.USART_Parity = USART_Parity_No;                            //无奇偶校验位
  27.         USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
  28.                                                                                    //如果不使能接收模式
  29.         USART_InitStructure.USART_Mode = USART_Mode_Tx ;                                   //只发模式        
  30.     USART_Init(USART1, &USART_InitStructure);                                      //设置串口1       
  31.         USART_Cmd(USART1, ENABLE);                                                                               //使能串口1
  32. }
  33. /*-------------------------------------------------*/
  34. /*函数名:串口1 printf函数                         */
  35. /*参  数:char* fmt,...  格式化输出字符串和参数     */
  36. /*返回值:无                                       */
  37. /*-------------------------------------------------*/
  38. __align(8) char Usart1_TxBuff[USART1_TXBUFF_SIZE];  
  39. void u1_printf(char * fmt, ...)
  40. {  
  41.         unsigned int i, length;
  42.        
  43.         va_list ap;
  44.         va_start(ap, fmt);
  45.         vsprintf(Usart1_TxBuff, fmt, ap);
  46.         va_end(ap);       
  47.        
  48.         length = strlen((const char*)Usart1_TxBuff);               
  49.         while((USART1->SR&0X40) == 0);
  50.         for(i = 0; i < length; i++)
  51.         {                       
  52.                 USART1->DR = Usart1_TxBuff[i];
  53.                 while((USART1->SR&0X40) == 0);       
  54.         }       
  55. }
  56. /*-------------------------------------------------*/
  57. /*函数名:初始化串口2发送功能                        */
  58. /*参  数:bound:波特率                             */
  59. /*返回值:无                                        */
  60. /*-------------------------------------------------*/
  61. void Usart2_Init(unsigned int bound)
  62. {                  
  63.     GPIO_InitTypeDef GPIO_InitStructure;     //定义一个设置GPIO功能的变量
  64.         USART_InitTypeDef USART_InitStructure;   //定义一个设置串口功能的变量
  65.         NVIC_InitTypeDef NVIC_InitStructure;     //如果使能接收功能,定义一个设置中断的变量
  66.         NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    //设置中断向量分组:第2组 抢先优先级:0 1 2 3 子优先级:0 1 2 3       
  67.       
  68.        
  69.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //使能串口2时钟
  70.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  //使能GPIOA时钟
  71.         USART_DeInit(USART2);                                  //串口2寄存器重新设置为默认值
  72.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;              //准备设置PA2
  73.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;      //IO速率50M
  74.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;               //复用推挽输出,用于串口2的发送
  75.     GPIO_Init(GPIOA, &GPIO_InitStructure);                 //设置PA2
  76.    
  77.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;              //准备设置PA3
  78.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  //浮空输入,用于串口2的接收
  79.     GPIO_Init(GPIOA, &GPIO_InitStructure);                 //设置PA3
  80.        
  81.         USART_InitStructure.USART_BaudRate = bound;                                    //波特率设置
  82.         USART_InitStructure.USART_WordLength = USART_WordLength_8b;                    //8个数据位
  83.         USART_InitStructure.USART_StopBits = USART_StopBits_1;                         //1个停止位
  84.         USART_InitStructure.USART_Parity = USART_Parity_No;                            //无奇偶校验位
  85.         USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
  86.         USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;                       //收发模式
  87.       
  88.     USART_Init(USART2, &USART_InitStructure);                                      //设置串口2       
  89.         USART_ClearFlag(USART2, USART_FLAG_RXNE);                      //清除接收标志位
  90.         USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);            //开启接收中断
  91.     NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;         //设置串口2中断
  92.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级0
  93.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;                  //子优先级0
  94.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                          //中断通道使能
  95.         NVIC_Init(&NVIC_InitStructure);                                  //设置串口2中断
  96.         USART_Cmd(USART2, ENABLE);                                //使能串口2
  97. }
  98. /*-------------------------------------------------*/
  99. /*函数名:串口2 printf函数                          */
  100. /*参  数:char* fmt,...  格式化输出字符串和参数      */
  101. /*返回值:无                                        */
  102. /*-------------------------------------------------*/
  103. __align(8) char USART2_TxBuff[USART2_TXBUFF_SIZE];  
  104. void u2_printf(char* fmt, ...)
  105. {  
  106.         unsigned int i, length;
  107.        
  108.         va_list ap;
  109.         va_start(ap, fmt);
  110.         vsprintf(USART2_TxBuff, fmt, ap);
  111.         va_end(ap);       
  112.        
  113.         length=strlen((const char*)USART2_TxBuff);               
  114.         while((USART2->SR&0X40) == 0);
  115.         for(i = 0; i < length; i++)
  116.         {                       
  117.                 USART2->DR = USART2_TxBuff[i];
  118.                 while((USART2->SR&0X40) == 0);       
  119.         }       
  120. }
  121. /*-------------------------------------------------*/
  122. /*函数名:串口2发送缓冲区中的数据                    */
  123. /*参  数:data:数据                                */
  124. /*返回值:无                                        */
  125. /*-------------------------------------------------*/
  126. void u2_TxData(unsigned char *data)
  127. {
  128.         int        i;       
  129.         while((USART2->SR&0X40) == 0);
  130.         for(i = 1; i <= (data[0] * 256 + data[1]); i++)
  131.         {                       
  132.                 USART2->DR = data[i+1];
  133.                 while((USART2->SR&0X40) == 0);       
  134.         }
  135. }
复制代码
(2)中断
  1. /*-------------------------------------------------*/
  2. /*函数名:串口2接收中断函数(最高优先级,处理接收数据)*/
  3. /*参  数:无                                       */
  4. /*返回值:无                                       */
  5. /*-------------------------------------------------*/
  6. void USART2_IRQHandler(void)   
  7. {                     
  8.         if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)    //如果USART_IT_RXNE标志置位,表示有数据到了,进入if分支
  9.         {  
  10.                 if(connectFlag == 0)                                                              //如果connectFlag等于0,当前还没有连接服务器,处于指令配置状态
  11.                 {                                    
  12.                         if(USART2->DR)
  13.                         {                                                              //处于指令配置状态时,非零值才保存到缓冲区       
  14.                                 Usart2_RxBuff[Usart2_RxCounter] = USART2->DR;//保存到缓冲区       
  15.                                 Usart2_RxCounter++;                                                  //每接收1个字节的数据,Usart2_RxCounter加1,表示接收的数据总量+1
  16.                         }                                       
  17.                 }
  18.                 else
  19.                 {                                                                    //反之connectFlag等于1,连接上服务器了       
  20.                         Usart2_RxBuff[Usart2_RxCounter] = USART2->DR;    //把接收到的数据保存到Usart2_RxBuff中                               
  21.                         if(Usart2_RxCounter == 0)
  22.                         {                                                                                                                                                                         //如果Usart2_RxCounter等于0,表示是接收的第1个数据,进入if分支                               
  23.                                 TIM_Cmd(TIM4, ENABLE);
  24.                         }
  25.                         else                                                                                                                                                                                 //else分支,表示果Usart2_RxCounter不等于0,不是接收的第一个数据
  26.                         {                                                                                                    
  27.                                 TIM_SetCounter(TIM4, 0);  
  28.                         }       
  29.                         Usart2_RxCounter++;                                              //每接收1个字节的数据,Usart2_RxCounter加1,表示接收的数据总量+1
  30.                 }
  31.         }
  32. }
复制代码
5. MQTT

  1. /*----------------------------------------------------------*/
  2. /*函数名:初始化接收,发送,命令数据的 缓冲区 以及各状态参数  */
  3. /*参  数:无                                                */
  4. /*返回值:无                                                */
  5. /*----------------------------------------------------------*/
  6. void MQTT_Buff_Init(void)
  7. {       
  8.         MQTT_RxDataInPtr=MQTT_RxDataBuf[0];                                  //指向发送缓冲区存放数据的指针归位
  9.         MQTT_RxDataOutPtr=MQTT_RxDataInPtr;                                  //指向发送缓冲区读取数据的指针归位
  10.         MQTT_RxDataEndPtr=MQTT_RxDataBuf[R_NUM-1];                     //指向发送缓冲区结束的指针归位
  11.        
  12.         MQTT_TxDataInPtr=MQTT_TxDataBuf[0];                                         //指向发送缓冲区存放数据的指针归位
  13.         MQTT_TxDataOutPtr=MQTT_TxDataInPtr;                                     //指向发送缓冲区读取数据的指针归位
  14.         MQTT_TxDataEndPtr=MQTT_TxDataBuf[T_NUM-1];           //指向发送缓冲区结束的指针归位
  15.        
  16.         MQTT_CMDInPtr=MQTT_CMDBuf[0];                        //指向命令缓冲区存放数据的指针归位
  17.         MQTT_CMDOutPtr=MQTT_CMDInPtr;                        //指向命令缓冲区读取数据的指针归位
  18.         MQTT_CMDEndPtr=MQTT_CMDBuf[C_NUM-1];                       //指向命令缓冲区结束的指针归位
  19.         MQTT_ConectPack();                                         //发送缓冲区添加连接报文
  20.         MQTT_Subscribe(S_TOPIC_NAME,0);                                  //发送缓冲区添加订阅topic,等级0       
  21.        
  22.         pingFlag = connectPackFlag = subcribePackFlag = 0;   //各个参数清零
  23. }
  24. /*----------------------------------------------------------*/
  25. /*函数名:云初始化参数,得到客户端ID,用户名和密码          */
  26. /*参  数:无                                                */
  27. /*返回值:无                                                */
  28. /*----------------------------------------------------------*/
  29. void IoT_Parameter_Init(void)
  30. {       
  31.         memset(ClientID,0,128);                              //客户端ID的缓冲区全部清零
  32.         sprintf(ClientID,"%s",DEVICEID);                     //构建客户端ID,并存入缓冲区
  33.         ClientID_len = strlen(ClientID);                     //计算客户端ID的长度
  34.        
  35.         memset(Username,0,128);                              //用户名的缓冲区全部清零
  36.         sprintf(Username,"%s",PRODUCTID);                    //构建用户名,并存入缓冲区
  37.         Username_len = strlen(Username);                     //计算用户名的长度
  38.        
  39.         memset(Passward,0,128);                              //用户名的缓冲区全部清零
  40.         sprintf(Passward,"%s",AUTHENTICATION);               //构建密码,并存入缓冲区
  41.         Passward_len = strlen(Passward);                     //计算密码的长度
  42.        
  43.         memset(ServerIP,0,128);  
  44.         sprintf(ServerIP,"%s","183.230.40.39");              //构建服务器域名
  45.         ServerPort = 6002;                                   //服务器端口号6002
  46.        
  47.         u1_printf("服 务 器:%s:%d\r\n",ServerIP,ServerPort); //串口输出调试信息
  48.         u1_printf("客户端ID:%s\r\n",ClientID);               //串口输出调试信息
  49.         u1_printf("用 户 名:%s\r\n",Username);               //串口输出调试信息
  50.         u1_printf("密    码:%s\r\n",Passward);               //串口输出调试信息
  51. }
  52. /*----------------------------------------------------------*/
  53. /*函数名:连接服务器报文                                    */
  54. /*参  数:无                                                */
  55. /*返回值:无                                                */
  56. /*----------------------------------------------------------*/
  57. void MQTT_ConectPack(void)
  58. {       
  59.         int temp,Remaining_len;
  60.        
  61.         Fixed_len = 1;                                                        //连接报文中,固定报头长度暂时先=1
  62.         Variable_len = 10;                                                    //连接报文中,可变报头长度=10
  63.         Payload_len = 2 + ClientID_len + 2 + Username_len + 2 + Passward_len; //连接报文中,负载长度      
  64.         Remaining_len = Variable_len + Payload_len;                           //剩余长度=可变报头长度+负载长度
  65.        
  66.         temp_buff[0]=0x10;                         //固定报头第1个字节 :固定0x01               
  67.         do{                                        //循环处理固定报头中的剩余长度字节,字节量根据剩余字节的真实长度变化
  68.                 temp = Remaining_len%128;              //剩余长度取余128
  69.                 Remaining_len = Remaining_len/128;     //剩余长度取整128
  70.                 if(Remaining_len>0)                      
  71.                         temp |= 0x80;                      //按协议要求位7置位         
  72.                 temp_buff[Fixed_len] = temp;           //剩余长度字节记录一个数据
  73.                 Fixed_len++;                               //固定报头总长度+1   
  74.         }while(Remaining_len > 0);                 //如果Remaining_len>0的话,再次进入循环
  75.        
  76.         temp_buff[Fixed_len + 0] = 0x00;     //可变报头第1个字节 :固定0x00                   
  77.         temp_buff[Fixed_len + 1] = 0x04;     //可变报头第2个字节 :固定0x04
  78.         temp_buff[Fixed_len + 2] = 0x4D;         //可变报头第3个字节 :固定0x4D
  79.         temp_buff[Fixed_len + 3] = 0x51;         //可变报头第4个字节 :固定0x51
  80.         temp_buff[Fixed_len + 4] = 0x54;         //可变报头第5个字节 :固定0x54
  81.         temp_buff[Fixed_len + 5] = 0x54;     //可变报头第6个字节 :固定0x54
  82.         temp_buff[Fixed_len + 6] = 0x04;         //可变报头第7个字节 :固定0x04
  83.         temp_buff[Fixed_len + 7] = 0xC2;         //可变报头第8个字节 :使能用户名和密码校验,不使用遗嘱,不保留会话
  84.         temp_buff[Fixed_len + 8] = 0x00;          //可变报头第9个字节 :保活时间高字节 0x00
  85.         temp_buff[Fixed_len + 9] = 0x64;         //可变报头第10个字节:保活时间高字节 0x64   100s
  86.        
  87.         /*     CLIENT_ID      */
  88.         temp_buff[Fixed_len+10] = ClientID_len/256;                                                  //客户端ID长度高字节
  89.         temp_buff[Fixed_len+11] = ClientID_len%256;                                                 //客户端ID长度低字节
  90.         memcpy(&temp_buff[Fixed_len+12],ClientID,ClientID_len);                 //复制过来客户端ID字串       
  91.         /*     用户名        */
  92.         temp_buff[Fixed_len+12+ClientID_len] = Username_len/256;                                 //用户名长度高字节
  93.         temp_buff[Fixed_len+13+ClientID_len] = Username_len%256;                                 //用户名长度低字节
  94.         memcpy(&temp_buff[Fixed_len+14+ClientID_len],Username,Username_len);    //复制过来用户名字串       
  95.         /*      密码        */
  96.         temp_buff[Fixed_len+14+ClientID_len+Username_len] = Passward_len/256;        //密码长度高字节
  97.         temp_buff[Fixed_len+15+ClientID_len+Username_len] = Passward_len%256;        //密码长度低字节
  98.         memcpy(&temp_buff[Fixed_len+16+ClientID_len+Username_len],Passward,Passward_len); //复制过来密码字串
  99.         TxDataBuf_Deal(temp_buff, Fixed_len + Variable_len + Payload_len);      //加入发送数据缓冲区
  100. }
  101. /*----------------------------------------------------------*/
  102. /*函数名:SUBSCRIBE订阅topic报文                            */
  103. /*参  数:QoS:订阅等级                                     */
  104. /*参  数:topic_name:订阅topic报文名称                     */
  105. /*返回值:无                                                */
  106. /*----------------------------------------------------------*/
  107. void MQTT_Subscribe(char *topic_name, int QoS)
  108. {       
  109.         Fixed_len = 2;                                                 //SUBSCRIBE报文中,固定报头长度=2
  110.         Variable_len = 2;                                                     //SUBSCRIBE报文中,可变报头长度=2       
  111.         Payload_len = 2 + strlen(topic_name) + 1;                      //计算有效负荷长度 = 2字节(topic_name长度)+ topic_name字符串的长度 + 1字节服务等级
  112.        
  113.         temp_buff[0] = 0x82;                                   //第1个字节 :固定0x82                     
  114.         temp_buff[1] = Variable_len + Payload_len;             //第2个字节 :可变报头+有效负荷的长度       
  115.         temp_buff[2] = 0x00;                                   //第3个字节 :报文标识符高字节,固定使用0x00
  116.         temp_buff[3] = 0x01;                                           //第4个字节 :报文标识符低字节,固定使用0x01
  117.         temp_buff[4] = strlen(topic_name)/256;                 //第5个字节 :topic_name长度高字节
  118.         temp_buff[5] = strlen(topic_name)%256;                           //第6个字节 :topic_name长度低字节
  119.         memcpy(&temp_buff[6], topic_name, strlen(topic_name)); //第7个字节开始 :复制过来topic_name字串               
  120.         temp_buff[6 + strlen(topic_name)] = QoS;               //最后1个字节:订阅等级
  121.        
  122.         TxDataBuf_Deal(temp_buff, Fixed_len + Variable_len + Payload_len);  //加入发送数据缓冲区
  123. }
  124. /*----------------------------------------------------------*/
  125. /*函数名:PING报文,心跳包                                   */
  126. /*参  数:无                                                */
  127. /*返回值:无                                                */
  128. /*----------------------------------------------------------*/
  129. void MQTT_PingREQ(void)
  130. {
  131.         temp_buff[0] = 0xC0;              //第1个字节 :固定0xC0                     
  132.         temp_buff[1] = 0x00;              //第2个字节 :固定0x00
  133.         TxDataBuf_Deal(temp_buff, 2);     //加入数据到缓冲区
  134. }
  135. /*----------------------------------------------------------*/
  136. /*函数名:等级0 发布消息报文                                  */
  137. /*参  数:topic_name:topic名称                              */
  138. /*参  数:data:数据                                         */
  139. /*参  数:data_len:数据长度                                 */
  140. /*返回值:无                                                 */
  141. /*----------------------------------------------------------*/
  142. void MQTT_PublishQs0(char *topic, char *data, int data_len)
  143. {       
  144.         int temp,Remaining_len;
  145.        
  146.         Fixed_len = 1;                              //固定报头长度暂时先等于:1字节
  147.         Variable_len = 2 + strlen(topic);           //可变报头长度:2字节(topic长度)+ topic字符串的长度
  148.         Payload_len = data_len;                     //有效负荷长度:就是data_len
  149.         Remaining_len = Variable_len + Payload_len; //剩余长度=可变报头长度+负载长度
  150.        
  151.         temp_buff[0] = 0x30;                              //固定报头第1个字节 :固定0x30          
  152.         do{                                         //循环处理固定报头中的剩余长度字节,字节量根据剩余字节的真实长度变化
  153.                 temp = Remaining_len%128;                   //剩余长度取余128
  154.                 Remaining_len = Remaining_len/128;      //剩余长度取整128
  155.                 if(Remaining_len>0)                      
  156.                         temp |= 0x80;                            //按协议要求位7置位         
  157.                 temp_buff[Fixed_len] = temp;            //剩余长度字节记录一个数据
  158.                 Fixed_len++;                                     //固定报头总长度+1   
  159.         }while(Remaining_len>0);                    //如果Remaining_len>0的话,再次进入循环
  160.                              
  161.         temp_buff[Fixed_len+0] = strlen(topic)/256;                       //可变报头第1个字节     :topic长度高字节
  162.         temp_buff[Fixed_len+1] = strlen(topic)%256;                                  //可变报头第2个字节     :topic长度低字节
  163.         memcpy(&temp_buff[Fixed_len+2], topic,strlen(topic));             //可变报头第3个字节开始 :拷贝topic字符串       
  164.         memcpy(&temp_buff[Fixed_len + 2 + strlen(topic)], data, data_len);//有效负荷:拷贝data数据
  165.        
  166.         TxDataBuf_Deal(temp_buff, Fixed_len + Variable_len + Payload_len);//加入发送数据缓冲区       
  167. }
  168. /*----------------------------------------------------------*/
  169. /*函数名:处理服务器发来的等级0的推送                          */
  170. /*参  数:redata:接收的数据                                 */
  171. /*返回值:无                                                 */
  172. /*----------------------------------------------------------*/
  173. void MQTT_DealPushdata_Qs0(unsigned char *redata)
  174. {
  175.         int  re_len;                                  //定义一个变量,存放接收的数据总长度
  176.         int  pack_num;                         //定义一个变量,当多个推送一起过来时,保存推送的个数
  177.     int  temp,temp_len;                    //定义一个变量,暂存数据
  178.     int  totle_len;                        //定义一个变量,存放已经统计的推送的总数据量
  179.         int  topic_len;                             //定义一个变量,存放推送中主题的长度
  180.         int  cmd_len;                          //定义一个变量,存放推送中包含的命令数据的长度
  181.         int  cmd_loca;                         //定义一个变量,存放推送中包含的命令的起始位置
  182.         int  i;                                //定义一个变量,用于for循环
  183.         int  local,multiplier;
  184.         unsigned char tempbuff[RBUFF_UNIT];           //临时缓冲区
  185.         unsigned char *data;                   //redata过来的时候,第一个字节是数据总量,data用于指向redata的第2个字节,真正的数据开始的地方
  186.                
  187.         re_len = redata[0]*256+redata[1];                     //获取接收的数据总长度               
  188.         data = &redata[2];                                    //data指向redata的第2个字节,真正的数据开始的
  189.         pack_num = temp_len = totle_len = temp = 0;           //各个变量清零
  190.         local = 1;
  191.         multiplier = 1;
  192.         do{
  193.                 pack_num++;                                       //开始循环统计推送的个数,每次循环推送的个数+1       
  194.                 do{
  195.                         temp = data[totle_len + local];   
  196.                         temp_len += (temp & 127) * multiplier;
  197.                         multiplier *= 128;
  198.                         local++;
  199.                 }while ((temp & 128) != 0);
  200.                 totle_len += (temp_len + local);                  //累计统计的总的推送的数据长度
  201.                 re_len -= (temp_len + local) ;                    //接收的数据总长度 减去 本次统计的推送的总长度      
  202.                 local = 1;
  203.                 multiplier = 1;
  204.                 temp_len = 0;
  205.         }while(re_len!=0);                                    //如果接收的数据总长度等于0了,说明统计完毕了
  206.         u1_printf("本次接收了%d个推送数据\r\n",pack_num);                //串口输出信息
  207.         temp_len = totle_len = 0;                                      //各个变量清零
  208.         local = 1;
  209.         multiplier = 1;
  210.         for(i = 0; i < pack_num; i++)                        //已经统计到了接收的推送个数,开始for循环,取出每个推送的数据
  211.         {                                               
  212.                 do{
  213.                         temp = data[totle_len + local];   
  214.                         temp_len += (temp & 127) * multiplier;
  215.                         multiplier *= 128;
  216.                         local++;
  217.                 }while ((temp & 128) != 0);                               
  218.                 topic_len = data[local + totle_len]*256 + data[local + 1 + totle_len] + 2; //计算本次推送数据中主题占用的数据量
  219.                 cmd_len = temp_len - topic_len;                                                         //计算本次推送数据中命令数据占用的数据量
  220.                 cmd_loca = totle_len + local +  topic_len;                                             //计算本次推送数据中命令数据开始的位置
  221.                 memcpy(tempbuff, &data[cmd_loca], cmd_len);                                              //命令数据拷贝出来                                 
  222.                 CMDBuf_Deal(tempbuff, cmd_len);                                                        //加入命令到缓冲区
  223.                 totle_len += (temp_len + local);                                                         //累计已经统计的推送的数据长度
  224.                 local = 1;
  225.                 multiplier = 1;
  226.                 temp_len = 0;
  227.         }       
  228. }
  229. /*----------------------------------------------------------*/
  230. /*函数名:处理发送缓冲区                                      */
  231. /*参  数:data:数据                                         */
  232. /*参  数:size:数据长度                                                                                      */
  233. /*返回值:无                                                 */
  234. /*----------------------------------------------------------*/
  235. void TxDataBuf_Deal(unsigned char *data, int size)
  236. {
  237.         memcpy(&MQTT_TxDataInPtr[2], data, size);     //拷贝数据到发送缓冲区       
  238.         MQTT_TxDataInPtr[0] = size/256;               //记录数据长度
  239.         MQTT_TxDataInPtr[1] = size%256;               //记录数据长度
  240.         MQTT_TxDataInPtr += TBUFF_UNIT;               //指针下移
  241.         if(MQTT_TxDataInPtr == MQTT_TxDataEndPtr)     //如果指针到缓冲区尾部了
  242.                 MQTT_TxDataInPtr = MQTT_TxDataBuf[0];     //指针归位到缓冲区开头
  243. }
  244. /*----------------------------------------------------------*/
  245. /*函数名:处理命令缓冲区                                                                         */
  246. /*参  数:data:数据                                        */
  247. /*参  数:size:数据长度                                    */
  248. /*返回值:无                                                */
  249. /*----------------------------------------------------------*/
  250. void CMDBuf_Deal(unsigned char *data, int size)
  251. {
  252.         memcpy(&MQTT_CMDInPtr[2], data,size);         //拷贝数据到命令缓冲区
  253.         MQTT_CMDInPtr[0] = size/256;                        //记录数据长度
  254.         MQTT_CMDInPtr[1] = size%256;                  //记录数据长度
  255.         MQTT_CMDInPtr[size+2] = '\0';                 //加入字符串结束符
  256.         MQTT_CMDInPtr += CBUFF_UNIT;                         //指针下移
  257.         if(MQTT_CMDInPtr == MQTT_CMDEndPtr)           //如果指针到缓冲区尾部了
  258.                 MQTT_CMDInPtr = MQTT_CMDBuf[0];                  //指针归位到缓冲区开头
  259. }
复制代码
五、进阶练习

在以上底子上可尝试以下功能:



    • 在裸机的环境上参加及时操作体系,如FreeRTOS


    • 将上传至云平台的数据存入本地数据库


    • 多台客户端同时毗连云服务器


    • 开辟移动APP,通过毗连数据库查看及时数据并可操控stm32端,例:云服务器并通过更新数据库状态改变向客户端发送相干指令实现远端操控。





免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

曹旭辉

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表