jjzjj

STM32-esp8266-MQTT服务器通信

于小猿Sup 2024-12-12 原文

文章目录

硬件

STM32F103C8T6

ESP-01S

软件

  • SYS->Debug->Serial Wire
  • RCC->HSE->Crystal/Ceramic Resonator
  • PC13->GPIO_Out
  • USART1->Mode->Asynchronous ,参数默认
  • USART2>Mode->Asynchronous ,参数默认
  • NVIC->USART2 global interrupt->Enabled

串口1与上位机tongxin

串口2与服务器通信

服务器

本次使用的MQTT服务器,有钱的朋友们也可以自行去阿里云购买一个自己的云服务器。

EMQX。EMQX Cloud 是 EMQ 公司推出的一款面向物联网领域的 MQTT 消息中间件产品。作为全球首个全托管的 MQTT 5.0 公有云服务,EMQX Cloud 提供了一站式运维代管、独有隔离环境的 MQTT 消息服务。在万物互联的时代,EMQX Cloud 可以帮助您快速构建面向物联网领域的行业应用,轻松实现物联网数据的采集、传输、计算和持久化。

此网站提供了一个免费在线MQTT 5服务器

Brokerbroker-cn.emqx.io
TCP端口1883
Websocket端口8083
TCP/TLS端口8883
Websocket/TLS端口8084
CA证书文件broker.emqx.io-ca.crt

代码编写

程序中主要的文件有

  • esp8266.c esp8266.h
  • MqttKit.c MqttKit.h
  • onenet.c onenet.h
  • cJSON.c cJSON.h

将上述文件分别添加到Inc和Src中,可能会遇到以下情况

这时只要重新加载一下CMakeLists.txt

esp8266

模块使用的是安心可得ESP-01S

ESP-01S 是由安信可科技开发的 Wi-Fi 模块,该模块核心处理器ESP8266 在较小尺寸封装中集成了业界领先的 Tensilica L106 超低功耗 32 位微型MCU,带有16位精简模式,主频支持 80 MHz 和 160 MHz,支持 RTOS,集成Wi-Fi MAC/ BB/RF/PA/LNA。ESP-01S Wi-Fi 模块支持标准的 IEEE802.11 b/g/n 协议,完整的TCP/IP 协议栈。用户可以使用该模块为现有的设备添加联网功能,也可以构建独立的网络控制器。

esp8266.h

#ifndef __esp_H
#define __esp_H

#include "main.h"

#define REV_OK    0  //接收完成标志
#define REV_WAIT   1  //接收未完成标志


typedef _Bool        uint1;
typedef unsigned char   uint8;
typedef char         int8;
typedef unsigned short  uint16;
typedef short        int16;
typedef unsigned int    uint32;
typedef int             int32;
typedef unsigned int   size_t;

void Usart_SendString(UART_HandleTypeDef *USARTx, unsigned char *str, unsigned short len);

void UsartPrintf(UART_HandleTypeDef *huart, char *fmt,...);

void ESP8266_Init(void);//esp8266初始化

void ESP8266_Clear(void); //清空缓存

void ESP8266_SendData(unsigned char *data, unsigned short len);//发送数据

unsigned char *ESP8266_GetIPD(unsigned short timeOut);//获取平台返回的数据

#endif

下面介绍以下几个重要功能函数

/*
************************************************************
*  函数名称:  Usart_SendString
*
*  函数功能:  串口数据发送
*
*  入口参数:  USARTx:串口组
*           str:要发送的数据
*           len:数据长度
*
*  返回参数:  无
*
*  说明:       
************************************************************
*/
void Usart_SendString(UART_HandleTypeDef *USARTx, unsigned char *str, unsigned short len)
{
   uint16_t j;
   for(j=0;j<len;j++)//循环发送数据
   {
      while((USART2->SR&0X40)==0);//循环发送,直到发送完毕
      USART2->DR=*str++;
   }
}
/*
************************************************************
*  函数名称:  UsartPrintf
*
*  函数功能:  格式化打印
*
*  入口参数:  USARTx:串口组
*           fmt:不定长参
*
*  返回参数:  无
*
*  说明:       
************************************************************
*/
void UsartPrintf(UART_HandleTypeDef *huart, char *fmt,...)
{
    int printf_TXBUFFERSIZE=1026;
    char buffer[printf_TXBUFFERSIZE];
    int i=0;
    va_list arg_ptr;
    va_start(arg_ptr,fmt);
    vsnprintf(buffer,printf_TXBUFFERSIZE+1,fmt,arg_ptr);
    while((i<printf_TXBUFFERSIZE)&&buffer[i])
    {
        HAL_UART_Transmit(huart,(uint8_t *)&buffer[i++],1,0xFFFF);
    }
    va_end(arg_ptr);

}

上述两个函数在usart.c中都没有定义,需要自己定义串口数据发送和格式化打印函数。

esp8266初始化函数

void ESP8266_Init(void)
{
   __HAL_UART_ENABLE_IT(&huart2,UART_IT_RXNE);//使能串口接收中断
   ESP8266_Clear();

    UsartPrintf(&huart1, "> 0. AT - 测试MCU-8266通讯\r\n");
    while(ESP8266_SendCmd("AT\r\n", "OK"))
        HAL_Delay(500);

    UsartPrintf(&huart1, "> 1. AT+RST - 软复位8266\r\n");
    ESP8266_SendCmd("AT+RST\r\n", "");
    HAL_Delay(500);
    ESP8266_SendCmd("AT+CIPCLOSE\r\n", "");
    HAL_Delay(500);
    UsartPrintf(&huart1, "> 2. AT+CWMODE=1,1 - 设置8266工作模式为STA\r\n");
    while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
        HAL_Delay(500);

    UsartPrintf(&huart1, "> 3. AT+CWDHCP=1,1 - 使能STA模式下DHCP\r\n");
    while(ESP8266_SendCmd("AT+CWDHCP=1,1\r\n", "OK"))
        HAL_Delay(500);

    UsartPrintf(&huart1, "> 4. AT+CWJAP - 连接WIFI\r\n");
    while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP"))
        HAL_Delay(500);

    UsartPrintf(&huart1, "> 5. AT+CIPSTART - 连接服务器\r\n");
    while(ESP8266_SendCmd(ESP8266_ONENET_INFO, "CONNECT"))
        HAL_Delay(500);
    ESP8266_INIT_OK = 1;
    UsartPrintf(&huart1, "> 6. ESP8266 Init OK - ESP8266初始化成功\r\n");
}

通过串口1实时打印esp8266的状态,串口2通过esp8266发送指令连接wifi、连接服务器等

ESP8266_WIFI_INFOESP8266_ONENET_INFO两个宏定义为

#define WIFI_SSID                  "******"                      // WIFI的名称 必须用2.4G的wifi不能用5G的,且不能用中文、空格
#define WIFI_PSWD                 "******"             // WIFI密码

#define SERVER_HOST                   "broker.emqx.io"         // MQTT服务器域名或IP
#define SERVER_PORT                   "1883"                      // MQTT服务器端口(一般为1883不用改)

#define ESP8266_WIFI_INFO        "AT+CWJAP=\"" WIFI_SSID "\",\"" WIFI_PSWD "\"\r\n"

#define ESP8266_ONENET_INFO       "AT+CIPSTART=\"TCP\",\"" SERVER_HOST "\"," SERVER_PORT "\r\n"

在移植过程中只需修改上述宏即可

main.c

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "esp8266.h"
#include "onenet.h"
/* USER CODE END Includes */

/* USER CODE BEGIN PV */
const char *devSubTopic[] = {"/mysmarthome/sub"};
const char devPubTopic[] = "/mysmarthome/pub";
uint8_t ESP8266_INIT_OK = 0;//esp8266初始化完成标志
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
    unsigned short timeCount = 0;  //发送间隔变量
    unsigned char *dataPtr = NULL;
  /* 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_USART1_UART_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
    UsartPrintf(&huart1, "Hardware init OK\r\n");
    ESP8266_Init();                //初始化ESP8266
    while(OneNet_DevLink())
        HAL_Delay(500);//接入OneNET
    OneNet_Subscribe(devSubTopic, 1);
    /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
      if(++timeCount >= 200)                           //发送间隔5s
      {
          UsartPrintf(&huart1, "OneNet_Publish\r\n");

          OneNet_Publish(devPubTopic, "MQTT Publish Test");

          timeCount = 0;
          ESP8266_Clear();
      }
      //数据解析
      dataPtr = ESP8266_GetIPD(3);
      if(dataPtr != NULL)
          OneNet_RevPro(dataPtr);

      HAL_Delay(10);
  }
  /* USER CODE END 3 */
}

主函数中,以下这几处都跟stm32与服务器实现发布与订阅有重要作用

OneNet_Subscribe(devSubTopic, 1);//订阅的话题和话题数量
OneNet_Publish(devPubTopic, "MQTT Publish Test");//发布的话题和话题内容
dataPtr = ESP8266_GetIPD(3);//获取平台返回的数据
OneNet_RevPro(dataPtr);//如果数据不为空,则对数据处理

下面是发布和订阅的话题名称,可自行修改

/* USER CODE BEGIN PV */
const char *devSubTopic[] = {"/mysmarthome/sub"};
const char devPubTopic[] = "/mysmarthome/pub";
/* USER CODE END PV */

对发布内容来说,修改msg的值即可确定stm32发布给服务器的内容

void OneNet_Publish(const char *topic, const char *msg)

而stm32订阅服务器的数据(即服务器发布给stm32的数据)需要在OneNet_RevPro函数中对数据进行json处理。

void OneNet_RevPro(unsigned char *cmd)
{

    MQTT_PACKET_STRUCTURE mqttPacket = {NULL, 0, 0, 0};                         //协议包

    char *req_payload = NULL;
    char *cmdid_topic = NULL;

    unsigned short topic_len = 0;
    unsigned short req_len = 0;

    unsigned char type = 0;
    unsigned char qos = 0;
    static unsigned short pkt_id = 0;

    short result = 0;

    char *dataPtr = NULL;
    char numBuf[10];
    int num = 0;

    cJSON *json , *json_value;

    type = MQTT_UnPacketRecv(cmd);
    switch(type)
    {
        case MQTT_PKT_CMD:                                           //命令下发

            result = MQTT_UnPacketCmd(cmd, &cmdid_topic, &req_payload, &req_len);  //解出topic和消息体
            if(result == 0)
            {
                UsartPrintf(&huart1, "cmdid: %s, req: %s, req_len: %d\r\n", cmdid_topic, req_payload, req_len);

                MQTT_DeleteBuffer(&mqttPacket);                            //删包
            }
            break;

        case MQTT_PKT_PUBLISH:                                        //接收的Publish消息

            result = MQTT_UnPacketPublish(cmd, &cmdid_topic, &topic_len, &req_payload, &req_len, &qos, &pkt_id);
            if(result == 0)
            {
                UsartPrintf(&huart1, "topic: %s, topic_len: %d, payload: %s, payload_len: %d\r\n",
                            cmdid_topic, topic_len, req_payload, req_len);
                // 对数据包req_payload进行JSON格式解析
                json = cJSON_Parse(req_payload);//对发布的数据进行json解析
                if (!json)UsartPrintf(&huart1, "Error before: [%s]\n", cJSON_GetErrorPtr());
                else {
                    json_value = cJSON_GetObjectItem(json, "LED");
                    if (json_value->valueint)
                        HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);//开灯
                    else
                        HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);//关灯
                }
                cJSON_Delete(json);
            }
            break;
      case MQTT_PKT_PUBACK:                                         //发送Publish消息,平台回复的Ack
      
         if(MQTT_UnPacketPublishAck(cmd) == 0)
            UsartPrintf(&huart1, "Tips:    MQTT Publish Send OK\r\n");
      break;
      
      default:
         result = -1;
      break;
   }
   
   ESP8266_Clear();                           //清空缓存
   
   if(result == -1)
      return;

   if(strstr((char *)req_payload, "OPEN"))       //搜索"OPEN"
   {
         UsartPrintf(&huart1, "Blue_Led_ON\r\n");

   }
   else if(strstr((char *)req_payload, "CLOED"))  //搜索"CLOED"
   {
         UsartPrintf(&huart1, "Blue_Led_OFF\r\n");
   }
   if(type == MQTT_PKT_CMD || type == MQTT_PKT_PUBLISH)
   {
      MQTT_FreeBuffer(cmdid_topic);
      MQTT_FreeBuffer(req_payload);
   }

}

移植过程中只需要修改下面这部分内容即可

	case MQTT_PKT_PUBLISH:                                        //接收的Publish消息       
		result = MQTT_UnPacketPublish(cmd, &cmdid_topic, &topic_len, &req_payload, &req_len, &qos, &pkt_id);
        if(result == 0)
        {
            UsartPrintf(&huart1, "topic: %s, topic_len: %d, payload: %s, payload_len: %d\r\n",
                        cmdid_topic, topic_len, req_payload, req_len);
            // 对数据包req_payload进行JSON格式解析
            json = cJSON_Parse(req_payload);//对发布的数据进行json解析
            if (!json)UsartPrintf(&huart1, "Error before: [%s]\n", cJSON_GetErrorPtr());
            else {
                json_value = cJSON_GetObjectItem(json, "LED");
                if (json_value->valueint)//json_value > 0且为整形
                    HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);//开灯
                else
                    HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);//关灯
            }
            cJSON_Delete(json);
        }
        break;

发布数据时要以json格式进行发送eg:{"msg": "hello"}

上述中当我们发布{"LED":1},LED等就为高电平,{"LED":0},LED等就为低电平

烧录程序后,连接串口,打开自己的手机热点(必须用2.4G的wifi不能用5G的),然后出现以下情况及表示连接服务器成功。

EMQ服务器的使用

首先登录官网EMQX,找到学习下的MQTT WebSocket客户端

进去之后,点击适用

创建新的连接,自定义Name,Host和Port修改成自己的服务器名即可,然后Connect

STM32发布、服务器订阅

New Subscription->订阅stm32的发布话题/mysmarthome/pub

此时就会看到stm32向服务器发来的消息MQTT Publish Test,实现

STM32订阅、服务器发布

将Payload选为JSON格式发布,topic为/mysmarthome/sub,发布内容为{"LED":1}{"LED":0},实现控制stm32的LED亮灭。

注意!!!,在新建发布与订阅话题中,有一项为QoS,此选项要与STM32中一致

分别在onenet.c中的OneNet_SubscribeOneNet_Publish函数中

MQTT客户端的使用

上面中使用是在线发布与订阅,下面使用MQTT软件实现发布订阅

下载地址MQTT.fx

打开之后点击小齿轮新建连接

然后Apply,Connect之后就可以发布和订阅话题了

订阅话题

发布话题

移植

下面总结下移植时需要修改的参数

参数说明
WIFI_SSIDWIFI的名称 必须用2.4G的wifi不能用5G的,且不能用中文、空格
WIFI_PSWDWIFI密码
SERVER_HOSTMQTT服务器域名或IP
SERVER_PORTMQTT服务器端口(一般为1883不用改)
*devSubTopic[]订阅话题
devPubTopic[]发布话题
OneNet_Publish(const char *topic, const char *msg)发布内容
cJSON_GetObjectItem(json, “LED”);对订阅的命令做出判断

资源下载

有关STM32-esp8266-MQTT服务器通信的更多相关文章

  1. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  2. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  3. ruby-on-rails - 启动 Rails 服务器时 ImageMagick 的警告 - 2

    最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru

  4. ruby-on-rails - s3_direct_upload 在生产服务器中不工作 - 2

    在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo

  5. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  6. ruby-on-rails - 在 Rails 中调试生产服务器 - 2

    您如何在Rails中的实时服务器上进行有效调试,无论是在测试版/生产服务器上?我试过直接在服务器上修改文件,然后重启应用,但是修改好像没有生效,或者需要很长时间(缓存?)我也试过在本地做“脚本/服务器生产”,但是那很慢另一种选择是编码和部署,但效率很低。有人对他们如何有效地做到这一点有任何见解吗? 最佳答案 我会回答你的问题,即使我不同意这种热修补服务器代码的方式:)首先,你真的确定你已经重启了服务器吗?您可以通过跟踪日志文件来检查它。您更改的代码显示的View可能会被缓存。缓存页面位于tmp/cache文件夹下。您可以尝试手动删除

  7. STM32读取串口传感器数据(颗粒物传感器,主动上传) - 2

    文章目录1.开发板选择*用到的资源2.串口通信(个人理解)3.代码分析(注释比较详细)1.主函数2.串口1配置3.串口2配置以及中断函数4.注意问题5.源码链接1.开发板选择我用的是STM32F103RCT6的板子,不过代码大概在F103系列的板子上都可以运行,我试过在野火103的霸道板上也可以,主要看一下串口对应的引脚一不一样就行了,不一样的就更改一下。*用到的资源keil5软件这里用到了两个串口资源,采集数据一个,串口通信一个,板子对应引脚如下:串口1,TX:PA9,RX:PA10串口2,TX:PA2,RX:PA32.串口通信(个人理解)我就从串口采集传感器数据这个过程说一下我自己的理解,

  8. ruby - 我的 Ruby IRC 机器人没有连接到 IRC 服务器。我究竟做错了什么? - 2

    require"socket"server="irc.rizon.net"port="6667"nick="RubyIRCBot"channel="#0x40"s=TCPSocket.open(server,port)s.print("USERTesting",0)s.print("NICK#{nick}",0)s.print("JOIN#{channel}",0)这个IRC机器人没有连接到IRC服务器,我做错了什么? 最佳答案 失败并显示此消息::irc.shakeababy.net461*USER:Notenoughparame

  9. ruby - Rails 开发服务器、PDFKit 和多线程 - 2

    我有一个使用PDFKit呈现网页的pdf版本的Rails应用程序。我使用Thin作为开发服务器。问题是当我处于开发模式时。当我使用“bundleexecrailss”启动我的服务器并尝试呈现任何PDF时,整个过程会陷入僵局,因为当您呈现PDF时,会向服务器请求一些额外的资源,如图像和css,看起来只有一个线程.如何配置Rails开发服务器以运行多个工作线程?非常感谢。 最佳答案 我找到的最简单的解决方案是unicorn.geminstallunicorn创建一个unicorn.conf:worker_processes3然后使用它:

  10. ruby - Dropbox 类似 git 的服务——没有 rsync 和 inotify - 2

    关于如何使用git设置类似Dropbox的服务,您有什么建议吗?您认为git是解决此问题的合适工具吗?我在考虑使用git+rush解决方案,你觉得怎么样? 最佳答案 检查这个开源项目:https://github.com/hbons/SparkleShare来自项目的自述文件:Howdoesitwork?SparkleSharecreatesaspecialfolderonyourcomputer.Youcanaddremotelyhostedfolders(or"projects")tothisfolder.Theseprojec

随机推荐