STM32F103_ESP8266基于RTOS移植MQTT

news2024/11/20 1:49:00

STM32F103_ESP8266基于RTOS移植MQTT

目录

  • STM32F103_ESP8266基于RTOS移植MQTT
    • 一、准备工作
    • 二、移植mqttclient代码
    • 三、编译包含mqttclient的工程
    • 四、编写ESP8266驱动程序
      • 1、ESP8266 AT命令代码框架
      • 2、UART硬件和抽象层相关代码
      • 3、AT命令发送和解析代码
      • 4、plat_sock网络层相关代码
    • 五、烧录调试
      • 1、堆栈空间分配问题
      • 2、mqtt_yield_thread无法连接Server问题
      • 3、无法订阅主题和无法接收消息问题

MQTT移植参考韦东山老师视频课:

https://video.100ask.net/p/t_pc/course_pc_detail/big_column/p_627a3ae7e4b01a4851fd107f

一、准备工作

  • 硬件基于洋桃IOT开发板,STM32F103C8T6;

  • 软件基于FreeRTOS_ESP8266_MQTT_SourceCode,已经在CubeMX上配置好FreeRTOS;

  • MQTT使用杰杰作者的MATT源码mqttclient,地址是“https://github.com/jiejieTop/mqttclient.git”

在这里插入图片描述

  1. 图接MCU串口1

在这里插入图片描述

  1. 使用STM32F103_RTOS_MQTT工程在MCU上测试ESP8266,连接WIFI,连接TCP服务器并收发数据:
ESP8266 AT命令:
1、WIFI模式配置,AP模式
AT+CWMODE=1
2、列出当前热点
AT+CWLAP
5、接入热点
AT+CWJAP_DEF="TP-LINK_4522","1234567890"
4、设置连接模式
AT+CIPMUX=0
5、创建TCP连接
AT+CIPSTART="TCP","112.125.89.8",44324
6、发送TCP数据
AT+CIPSEND=5
7、断开TCP连接
AT+CIPCLOSE
8、断开热点
AT+CWQAP

在这里插入图片描述

二、移植mqttclient代码

  1. mqttclient源码目录复制到FreeRTOS_ESP8266_MQTT工程

在这里插入图片描述

  1. mqttclient源码中保留‘common’、‘mqtt’、‘mqttclient’、‘network’、‘platform’目录

在这里插入图片描述

  1. 将各目录中的代码添加到keil工程中(.c和.h)

在这里插入图片描述

‘common’中仅添加mqtt_list.c和random.c文件;‘network’中仅添加nettype_tcp.c、nettype_tls.c、network.c文件

三、编译包含mqttclient的工程

mqttclient源码增加到工程中后对工程进行编译,记录编译报错及解决过程:

  1. 找不到头文件"mqtt_config.h"

在这里插入图片描述

头文件"mqtt_config.h"在test目录下,注释后继续编译

在这里插入图片描述

  1. 找不到mbedtls系列头文件

在这里插入图片描述

自定义MQTT_NETWORK_TYPE_NO_TLS宏,再次编译

在这里插入图片描述

  1. 找不到lwip系列头文件

在这里插入图片描述

韦东山老师移植完成的代码中没有找到lwip系列头文件被注释掉了,注释后继续编译

  1. size_t类型未定义

在这里插入图片描述

包含头文件<stdio.h>,再次编译

在这里插入图片描述

  1. socklen_t类型未定义

在这里插入图片描述

同样增加一个socklen_t的定义,再次编译

在这里插入图片描述

  1. platform_net_socket.c文件中各个接口报错

在这里插入图片描述

删除此文件中的各个接口,后期使用ESP8266 AT命令来实现各个接口,再次编译

在这里插入图片描述

  1. 懂定义参数少了一个括号

在这里插入图片描述

在此宏定义的2个文件处修改一下,再次编译

在这里插入图片描述

在这里插入图片描述

  1. 找不到头文件plooc_class.h

在这里插入图片描述

将plooc_class.h文件路径添加到工程中,再次编译

在这里插入图片描述

  1. 仅支持gnu模式(匿名结构体)错误,语法不支持

在这里插入图片描述

在plooc_class.h中自己定义增加宏定义PLOOC_CFG_REMOVE_MEMORY_LAYOUT_BOUNDARY___USE_WITH_CAUTION___编译通过

参考文章:https://blog.csdn.net/studyingdda/article/details/135428348?spm=1001.2014.3001.5501

在这里插入图片描述

四、编写ESP8266驱动程序

ESP8266驱动程序分成4层,用于隔离底层硬件和网络层:

  • platform_net_socket.c:执行什么AT命令才能连接、收、发网络数据
  • ESP8266:提供AT命令函数
  • UART驱动抽象层:执行UART的写、读(从buffer读)
  • UART硬件驱动:发送UART数据,接收UART数据存入buffer

在这里插入图片描述

1、ESP8266 AT命令代码框架

ESP8266 AT命令发送和接收处理代码中有2个任务,2个信号量:

  • AT命令发送任务:发送完AT命令后以一个超时时间等待信号量1
  • AT命令解析任务:永远等待信号量2,获取信号量2后读取环形buffer中的AT命令响应数据并解析,解析完毕后释放信号量1
  • UART3中断:接收中断中接收ESP8266响应的数据,并存放进环形缓冲区,然后释放信号量2

在这里插入图片描述

2、UART硬件和抽象层相关代码

不使用CubeMX自动生成的USART3_IRQHandler代码,自己定义uart3的中断接收函数

  1. 在mqttclient文件夹里创建hardware文件夹存放uart3硬件和抽象层相关代码,stm32_uart3.c和stm32_uart3.h

在这里插入图片描述

stm32_uart3.h代码:

#ifndef _STM32_UART3_H
#define _STM32_UART3_H

void USART3_Write(char *buf, int len);
void USART3_Read(char *c, int timeout);

#endif

stm32_uart3.c代码:

#include "driver_usart.h"
#include <stdio.h>
#include <ring_buffer.h>

static ring_buffer uart3_buffer;   //创建uart3的环形缓冲buffer
extern UART_HandleTypeDef huart3;

void USART3_IRQHandler(void)
{
	/* 如果发生的是RX中断
	 * 把数据读出来, 存入环形buffer
	 */

	uint32_t isrflags	= READ_REG(huart3.Instance->SR);
	uint32_t cr1its 	= READ_REG(huart3.Instance->CR1);
	char c;
	
    /* UART in mode Receiver -------------------------------------------------*/
    /* USART_SR_RXNE接收不为空; USART_CR1_RXNEIE使能接收中断. */
    if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
    {
      c = huart3.Instance->DR;   //将DR数据寄存器中的数据放入变量c
	  ring_buffer_write(c, &uart3_buffer);   //将c写入环形缓冲区
      return;
    }

}

void USART3_Write(char *buf, int len)
{
	int i = 0;
	while (i < len)
	{
        /* 等待数据发送寄存器空 */
		while ((huart3.Instance->SR & USART_SR_TXE) == 0);
		huart3.Instance->DR = buf[i];
		i++;
	}
}

void USART3_Read(char *c, int timeout)
{
	while (1)
	{
		if (0 == ring_buffer_read((unsigned char *)c, &uart3_buffer))
			return;
		else
		{
		}
	}		
}

  1. 在ModuleDrivers文件夹中增加UART和环形缓冲区相关代码,driver_usart.c、driver_usart.h、ring_buffer.c、ring_buffer.h

在这里插入图片描述

ring_buffer.h代码

/*  Copyright (s) 2019 深圳百问网科技有限公司
 *  All rights reserved
 * 
 * 文件名称:ring_buffer.h
 * 摘要:
 *  
 * 修改历史     版本号        Author       修改内容
 *--------------------------------------------------
 * 2021.8.21      v01         百问科技      创建文件
 *--------------------------------------------------
*/
#ifndef __RING_BUFFER_H
#define __RING_BUFFER_H

#include "stm32f1xx_hal.h"

#define BUFFER_SIZE 1024        /* 环形缓冲区的大小 */
typedef struct
{
    volatile unsigned int pW;           /* 写地址 */
    volatile unsigned int pR;           /* 读地址 */
    unsigned char buffer[BUFFER_SIZE];  /* 缓冲区空间 */
} ring_buffer;

/*
 *  函数名:void ring_buffer_init(ring_buffer *dst_buf)
 *  输入参数:dst_buf --> 指向目标缓冲区
 *  输出参数:无
 *  返回值:无
 *  函数作用:初始化缓冲区
*/
extern void ring_buffer_init(ring_buffer *dst_buf);

/*
 *  函数名:void ring_buffer_write(unsigned char c, ring_buffer *dst_buf)
 *  输入参数:c --> 要写入的数据
 *            dst_buf --> 指向目标缓冲区
 *  输出参数:无
 *  返回值:无
 *  函数作用:向目标缓冲区写入一个字节的数据,如果缓冲区满了就丢掉此数据
*/
extern void ring_buffer_write(unsigned char c, ring_buffer *dst_buf);

/*
 *  函数名:int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf)
 *  输入参数:c --> 指向将读到的数据保存到内存中的地址
 *            dst_buf --> 指向目标缓冲区
 *  输出参数:无
 *  返回值:读到数据返回0,否则返回-1
 *  函数作用:从目标缓冲区读取一个字节的数据,如果缓冲区空了返回-1表明读取失败
*/
extern int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf);

#endif /* __RING_BUFFER_H */

ring_buffer.c代码

/*  Copyright (s) 2019 深圳百问网科技有限公司
 *  All rights reserved
 * 
 * 文件名称:ring_buffer.c
 * 摘要:
 *  
 * 修改历史     版本号        Author       修改内容
 *--------------------------------------------------
 * 2021.8.21      v01         百问科技      创建文件
 *--------------------------------------------------
*/

#include "ring_buffer.h"


/*
 *  函数名:void ring_buffer_init(ring_buffer *dst_buf)
 *  输入参数:dst_buf --> 指向目标缓冲区
 *  输出参数:无
 *  返回值:无
 *  函数作用:初始化缓冲区
*/
void ring_buffer_init(ring_buffer *dst_buf)
{
    /* 唤醒缓冲区初始化,将读写指针设置为0 */
    dst_buf->pW = 0;
    dst_buf->pR = 0;
}

/*
 *  函数名:void ring_buffer_write(unsigned char c, ring_buffer *dst_buf)
 *  输入参数:c --> 要写入的数据
 *            dst_buf --> 指向目标缓冲区
 *  输出参数:无
 *  返回值:无
 *  函数作用:向目标缓冲区写入一个字节的数据,如果缓冲区满了就丢掉此数据
*/
void ring_buffer_write(unsigned char c, ring_buffer *dst_buf)
{
    /* 获取环形缓冲区写指针的下一个位置 */
    int i = (dst_buf->pW + 1) % BUFFER_SIZE;
    /* 
    如果环形缓冲区写指针的下一个位置和读指针不相等代表环形缓冲区未写满,若写满则数据直 
   接丢弃 */
    if(i != dst_buf->pR)    // 环形缓冲区没有写满
    {
        /* 将字符C写到唤醒缓冲区写指针的位置 */
        dst_buf->buffer[dst_buf->pW] = c;
        /* 将环形缓冲区的写指针更新为下一个写位置 */
        dst_buf->pW = i;
    }
}

/*
 *  函数名:int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf)
 *  输入参数:c --> 指向将读到的数据保存到内存中的地址
 *            dst_buf --> 指向目标缓冲区
 *  输出参数:无
 *  返回值:读到数据返回0,否则返回-1
 *  函数作用:从目标缓冲区读取一个字节的数据,如果缓冲区空了返回-1表明读取失败
*/
int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf)
{
    /* 如果环形缓冲区当前的读指针与写指针位置相同表示当前环形缓冲区为空 */
    if(dst_buf->pR == dst_buf->pW)
    {
        return -1;
    }
    else
    {
        /* 将当前环形缓冲区读指针位置的数据传给字符c的地址 */
        *c = dst_buf->buffer[dst_buf->pR];
        /* 将环形缓冲区读指针的位置更新为下一个读位置 */
        dst_buf->pR = (dst_buf->pR + 1) % BUFFER_SIZE;
        return 0;
    }
}

driver_usart.h代码

/*  Copyright (s) 2019 深圳百问网科技有限公司
 *  All rights reserved
 * 
 * 文件名称:driver_usart.h
 * 摘要:
 *  
 * 修改历史     版本号        Author       修改内容
 *--------------------------------------------------
 * 2020.6.6      v01        百问科技      创建文件
 *--------------------------------------------------
*/

#ifndef __DRIVER_USART_H
#define __DRIVER_USART_H

#include "stm32f1xx_hal.h"

/*
 *  函数名:void EnableDebugIRQ(void)
 *  输入参数:无
 *  输出参数:无
 *  返回值:无
 *  函数作用:使能USART1的中断
*/
extern void EnableDebugIRQ(void);
extern void EnableUART3IRQ(void);

/*
 *  函数名:void DisableDebugIRQ(void)
 *  输入参数:无
 *  输出参数:无
 *  返回值:无
 *  函数作用:失能USART1的中断
*/
extern void DisableDebugIRQ(void);

#endif /* __DRIVER_USART_H */

driver_usart.h代码

/*  Copyright (s) 2019 深圳百问网科技有限公司
 *  All rights reserved
 * 
 * 文件名称:driver_usart.c
 * 摘要:
 *  
 * 修改历史     版本号        Author       修改内容
 *--------------------------------------------------
 * 2021.8.21      v01         百问科技      创建文件
 *--------------------------------------------------
*/

#include "driver_usart.h"
#include "usart.h"
#include "main.h"
#include "ring_buffer.h"
#include <stdio.h>

static volatile uint8_t txcplt_flag = 0;    // 发送完成标志,1完成0未完成
static volatile uint8_t rxcplt_flag = 0;    // 接收完成标志,1完成0未完成

static volatile uint8_t rx_data = 0;

/*
 *  函数名:void EnableDebugIRQ(void)
 *  输入参数:无
 *  输出参数:无
 *  返回值:无
 *  函数作用:使能USART1的中断
*/
void EnableDebugIRQ(void)
{
    HAL_NVIC_SetPriority(USART1_IRQn, 0, 1);    // 设置USART1中断的优先级
    HAL_NVIC_EnableIRQ(USART1_IRQn);            // 使能USART1的中断
    
    __HAL_UART_ENABLE_IT(&huart1, UART_IT_TC | UART_IT_RXNE);   // 使能USRAT1的发送和接收中断
}


void EnableUART3IRQ(void)
{
    HAL_NVIC_SetPriority(USART3_IRQn, 15, 0);    // 设置USART3中断的优先级
    HAL_NVIC_EnableIRQ(USART3_IRQn);            // 使能USART3的中断

	huart3.Instance->SR &= ~(USART_SR_RXNE);   //清除数据接收寄存器,否则使能中断后会立刻进入一次中断
    __HAL_UART_ENABLE_IT(&huart3, UART_IT_RXNE);   // 使能USRAT3的接收中断
}

/*
 *  函数名:void DisableDebugIRQ(void)
 *  输入参数:无
 *  输出参数:无
 *  返回值:无
 *  函数作用:失能USART1的中断
*/
void DisableDebugIRQ(void)
{
    __HAL_UART_DISABLE_IT(&huart1, UART_IT_TC | UART_IT_RXNE);      // 失能USRAT1的发送和接收中断
    
    HAL_NVIC_DisableIRQ(USART1_IRQn);   // 失能USART1的中断
}

/*
 *  函数名:int fputc(int ch, FILE *f)
 *  输入参数:ch --> 要输出的数据
 *  输出参数:无
 *  返回值:无
 *  函数作用:printf/putchar 标准输出函数的底层输出函数
*/
int fputc(int ch, FILE *f)
{
    txcplt_flag = 0;
    HAL_UART_Transmit_IT(&huart1, (uint8_t*)&ch, 1);
    while(txcplt_flag==0);
	return ch;
}

/*
 *  函数名:int fgetc(FILE *f)
 *  输入参数:
 *  输出参数:无
 *  返回值:接收到的数据
 *  函数作用:scanf/getchar 标准输出函数的底层输出函数
*/
int fgetc(FILE *f)
{
    char c = 0;
    while(ring_buffer_read((unsigned char *)&c, &test_buffer) != 0);
    return c;
}

/*
 *  函数名:void USART1_IRQHandler(void)
 *  输入参数:无
 *  输出参数:无
 *  返回值:无
 *  函数作用:USART1的中断服务函数
*/
void USART1_IRQHandler(void)
{
    unsigned char c = 0;
    if((USART1->SR &(1<<5)) != 0)   // 判断USART1的状态寄存器的第五位即RXNE位是否被置位
    {
        c = USART1->DR; // RXNE=1,表明DR寄存器有值,就将它读出来保存到临时变量中;
        ring_buffer_write(c, &test_buffer); // 将数据保存到环形缓冲区中
    }
    HAL_UART_IRQHandler(&huart1);   // HAL库中的UART统一中断服务函数,通过形参判断是要处理谁的中断
}

/*
 *  函数名:void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
 *  输入参数:huart --> UART的设备句柄,用以指明UART设备是哪一个UART
 *  输出参数:无
 *  返回值:无
 *  函数作用:HAL库中的UART接收完成回调函数
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)   // 判断进来的是否是USART1这个UART设备
    {
        rxcplt_flag = 1;    // 进入此回调函数表明接收指定长度的数据已经完成,将标志置一
    }
}

/*
 *  函数名:void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
 *  输入参数:huart --> UART的设备句柄,用以指明UART设备是哪一个UART
 *  输出参数:无
 *  返回值:无
*  函数作用:HAL库中的UART发送完成回调函数
*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    if(huart->Instance == USART1)   // 判断进来的是否是USART1这个UART设备
    {
        txcplt_flag = 1;    // 进入此回调函数表明发送指定长度的数据已经完成,将标志置一
    }
}

在main函数中使用USART3_Write(“AT\r\n”, 4);来测试MCU UART3能否与ESP8266正常通信:

在这里插入图片描述

在这里插入图片描述

3、AT命令发送和解析代码

AT

// 1. 配置 WiFi 模式
AT+CWMODE=3						//	softAP+station	mode

// 2. 连接路由器
AT+CWJAP="SSID","password"		//	SSID	and	password	of	router

// 3. 查询 ESP8266 设备的 IP 地址
AT+CIFSR

// 响应
+CIFSR:APIP,"192.168.4.1"
+CIFSR:APMAC,"1a:fe:34:a5:8d:c6"
+CIFSR:STAIP,"192.168.3.133"
+CIFSR:STAMAC,"18:fe:34:a5:8d:c6"
OK

// 4. ESP8266 设备作为 TCP client 连接到服务器
AT+CIPSTART="TCP","192.168.3.116",8080			 //protocol,	server	IP	and	port

// 5. ESP8266 设备向服务器器发送数据
AT+CIPSEND=4				//	set	date	length	which	will	be	sent,		such	as	4	bytes	
>test						//	enter	the	data,		no	CR

// 响应
Recv	4	bytes
SEND	OK

// 6. 当 ESP8266 设备接收到服务器器发来的数据,将提示如下信息:
+IPD,n:xxxxxxxxxx				//	received	n	bytes,		data=xxxxxxxxxxx	
  1. 在mqttclient文件夹中创建hal文件夹,包括at_uart_hal.c和at_uart_hal.h文件,作用是对USART3_Write(buf, len)和USART3_Read(c, timeout)函数封装一层供上层AT命令函数收发使用,避免更换硬件后上层需要修改

在这里插入图片描述

at_uart_hal.h代码:

#ifndef _AT_UART_H
#define _AT_UART_H

void HAL_AT_Send(char *buf, int len);
void HAL_AT_Secv(char *c, int timeout);

#endif

at_uart_hal.c代码:

#include <stm32_uart3.h>


void HAL_AT_Send(char *buf, int len)
{
	USART3_Write(buf, len);
}

void HAL_AT_Secv(char *c, int timeout)
{
	/* 从环形缓冲区中得到数据 */
	/* 无数据则阻塞 */

	USART3_Read(c, timeout);
}

  1. 对driver_usart.c和driver_usart.h进行修改,目的是使读uart3数据线程安全,当有一个任务使用USART3_Read还未读到数据时先挂起(等待互斥锁),当uart3串口接收中断接收到数据后再释放唤醒此任务(释放互斥锁)继续读取数据,防止在此任务还未读到数据时其他任务再次调用USART3_Read读取uart3数据

driver_usart.h代码:

#ifndef _STM32_UART3_H
#define _STM32_UART3_H

void USART3_Write(char *buf, int len);
void UART3_Lock_Init(void);   //线程安全: USART3_Read没有读到数据时先挂起,读到数据后其他接口才能够调用USART3_Read
void USART3_Read(char *c, int timeout);

#endif

driver_usart.c代码:

#include "driver_usart.h"
#include <stdio.h>
#include <platform_mutex.h>
#include <ring_buffer.h>
  
static ring_buffer uart3_buffer;   //创建uart3的环形缓冲buffer

extern UART_HandleTypeDef huart3;

static platform_mutex_t uart_recv_mutex;

void USART3_IRQHandler(void)
{
	/* 如果发生的是RX中断
	 * 把数据读出来, 存入环形buffer
	 */

	uint32_t isrflags	= READ_REG(huart3.Instance->SR);
	uint32_t cr1its 	= READ_REG(huart3.Instance->CR1);
	char c;
	
    /* UART in mode Receiver -------------------------------------------------*/
    /* USART_SR_RXNE接收不为空; USART_CR1_RXNEIE使能接收中断. */
    if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
    {
      c = huart3.Instance->DR;   //将DR数据寄存器中的数据放入变量c
	  ring_buffer_write(c, &uart3_buffer);   //将c写入环形缓冲区
	  platform_mutex_unlock_from_isr(&uart_recv_mutex);	  
      return;
    }

}

void UART3_Lock_Init(void)
{
	platform_mutex_init(&uart_recv_mutex);
	platform_mutex_lock(&uart_recv_mutex);  // mutex = 0
    ring_buffer_init(&uart3_buffer);
}

void USART3_Write(char *buf, int len)
{
	int i = 0;
	while (i < len)
	{
        /* 等待数据发送寄存器空 */
		while ((huart3.Instance->SR & USART_SR_TXE) == 0);
		huart3.Instance->DR = buf[i];
		i++;
	}
}

void USART3_Read(char *c, int timeout)
{
	while (1)
	{
		if (0 == ring_buffer_read((unsigned char *)c, &uart3_buffer))
			return;
		else
		{
			platform_mutex_lock_timeout(&uart_recv_mutex, timeout);
		}
	}		
}

其中platform_mutex_lock_timeout函数和platform_mutex_unlock_from_isr函数在杰杰MQTT代码中没有实现,因此自己实现:

platform_mutex.h代码:

/*
 * @Author: jiejie
 * @Github: https://github.com/jiejieTop
 * @Date: 2019-12-15 18:31:33
 * @LastEditTime: 2020-04-27 17:04:46
 * @Description: the code belongs to jiejie, please keep the author information and source code according to the license.
 */
#ifndef _PLATFORM_MUTEX_H_
#define _PLATFORM_MUTEX_H_

#include "FreeRTOS.h"
#include "semphr.h"

typedef struct platform_mutex {
    SemaphoreHandle_t mutex;
} platform_mutex_t;

int platform_mutex_init(platform_mutex_t* m);
int platform_mutex_lock(platform_mutex_t* m);
int platform_mutex_lock_timeout(platform_mutex_t* m, int timeout);   /* DCA add */
int platform_mutex_trylock(platform_mutex_t* m);
int platform_mutex_unlock(platform_mutex_t* m);
int platform_mutex_unlock_from_isr(platform_mutex_t* m);   /* DCA add */

int platform_mutex_destroy(platform_mutex_t* m);

#endif

platform_mutex.c代码:

/*
 * @Author: jiejie
 * @Github: https://github.com/jiejieTop
 * @Date: 2019-12-15 18:27:19
 * @LastEditTime: 2020-04-27 22:22:27
 * @Description: the code belongs to jiejie, please keep the author information and source code according to the license.
 */
#include "platform_mutex.h"

int platform_mutex_init(platform_mutex_t* m)
{
    m->mutex = xSemaphoreCreateMutex();
    return 0;
}

int platform_mutex_lock(platform_mutex_t* m)
{
    return xSemaphoreTake(m->mutex, portMAX_DELAY);
}

/* DCA add */
int platform_mutex_lock_timeout(platform_mutex_t* m, int timeout)
{
    return xSemaphoreTake(m->mutex, timeout);
}



int platform_mutex_trylock(platform_mutex_t* m)
{
    return xSemaphoreTake(m->mutex, 0);
}

int platform_mutex_unlock(platform_mutex_t* m)
{
    return xSemaphoreGive(m->mutex);
}

/* DCA add */
int platform_mutex_unlock_from_isr(platform_mutex_t* m)
{
	static BaseType_t xHigherPriorityTaskWoken = pdFALSE;
    xSemaphoreGiveFromISR(m->mutex, &xHigherPriorityTaskWoken);
	portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
	return pdTRUE;
}


int platform_mutex_destroy(platform_mutex_t* m)
{
    vSemaphoreDelete(m->mutex);
    return 0;
}

  1. 在mqttclient文件夹中创建at文件夹,包括at_command.c和at_command.h,用于编写AT命令数据的发送与解析函数

在这里插入图片描述

at_command.c代码:

#include "at_command.h"
#include <platform_mutex.h>
#include <at_uart_hal.h>
#include <string.h>
#include <stdio.h>
#include <ring_buffer.h>
#include <mqttclient.h>


#define AT_CMD_TIMOUT 1000
#define AT_RESP_LEN   200


static ring_buffer g_packet_buffer;   //存放网络接收数据payload的唤醒buffer, +IPD,len:data中的data

static platform_mutex_t at_ret_mutex;   //发送AT命令或者网络数据后上锁,数据解析任务完成ESP8266响应数据解析后释放互斥锁
static platform_mutex_t at_packet_mutex;   //读取网络接收数据为空时阻塞,数据解析任务完成网络接收数据解析后释放互斥锁

static int g_at_status;   //发送AT命令后的状态,OK、ERROR、TIMEOUT
static char g_at_resp[AT_RESP_LEN];   //AT命令正确响应后保存ESP8266的返回值数据

/* status
 *   0  - ok
 *   -1 - err
 *   -2 - timeout
 */
void SetATStatus(int status)   //设置AT命令发送后的ESP8266响应状态,AT_OK或者AT_ERR
{
	g_at_status = status; 
}

int GetATStatus(void)      //得到AT命令发送后的ESP8266响应状态,AT_OK或者AT_ERR
{
	return g_at_status;
}

int ATInit(void)     //初始化等待AT命令返回的互斥锁、读取网络数据的互斥锁和存放网络接收数据的环形缓冲区
{
	platform_mutex_init(&at_ret_mutex);
	platform_mutex_lock(&at_ret_mutex);  // mutex = 0

	platform_mutex_init(&at_packet_mutex);
	platform_mutex_lock(&at_packet_mutex);  // mutex = 0

	ring_buffer_init(&g_packet_buffer);
	
	return 0;
}

int ATSendData(unsigned char *buf, int len, int timeout)   //AT+CIPSEND后发送数据的函数
{
	int ret;
	int err;
	
	/* 发送网络数据 */
	HAL_AT_Send((char *)buf, len);

	/* 等待结果 
	 * 1 : 成功得到mutex
	 * 0 : 超时返回
	 */
	ret = platform_mutex_lock_timeout(&at_ret_mutex, timeout);   //等待数据解析任务解析完成释放互斥锁或超时
	if (ret)   //成功得到互斥锁
	{
		/* 判断返回值 */
		/* 存储resp */
		err = GetATStatus();
		return err;
	}
	else   //超时等待
	{
		return AT_TIMEOUT;
	}

}

int ATReadData(unsigned char *c, int timeout)   //读取网络数据的函数
{
	int ret;

	do {
		if (0 == ring_buffer_read((unsigned char *)c, &g_packet_buffer))   //读到一个字符的网络接收数据
			return AT_OK;
		else
		{
			ret = platform_mutex_lock_timeout(&at_packet_mutex, timeout);   //网络数据的环形buffer为空,等待数据解析任务解析完毕后释放互斥锁或者超时
			if (0 == ret)   //超时等待
				return AT_TIMEOUT;
		}
	} while (ret == 1);
	
	return 0;
}

/* eg. buf = "AT+CIPMODE=1"
 *     timeout : ms
 */
int ATSendCmd(char *buf, char *resp, int resp_len, int timeout)   //发送AT命令的函数
{
	int ret;
	int err;
	
	/* 发送AT命令 */
	HAL_AT_Send(buf, strlen(buf));
	HAL_AT_Send("\r\n", 2);

	/* 等待结果 
	 * 1 : 成功得到mutex
	 * 0 : 超时返回
	 */
	ret = platform_mutex_lock_timeout(&at_ret_mutex, timeout);   //发送AT命令后等待数据解析任务解析完ESP8266返回的数据并释放互斥锁
    vTaskDelay(200);
    if (ret)   //成功获取互斥锁,即接收到ESP8266响应的数据
	{
		/* 判断返回值 */
		/* 存储resp */
		err = GetATStatus();
		if (!err && resp)   //resp不为空, resp: 发送AT命令后期望的返回值		
		{
            /* 比较实际返回值与期望返回值,若期望返回值的长度大于最大buffer长度则使用buffer的最大使用长度 */
			memcpy(resp, g_at_resp, resp_len > AT_RESP_LEN ? AT_RESP_LEN : resp_len);
		}
		return err;
	}
	else
	{
		return AT_TIMEOUT;
	}

}

#if 0
static int GetCIPSENDResult(char *buf)
{
	if (g_cur_cmd && strstr(g_cur_cmd, "AT+CIPSEND=") && (buf[0] == '>'))
		return 1;	
	else
		return 0;
}
#endif

static int GetSpecialATString(char *buf)   //获取特殊返回值, IPD是接收到网络数据的数据头
{	
	if (strstr(buf, "+IPD,"))
		return 1;
	else
		return 0;
}

static void ProcessSpecialATString(char *buf)   //处理网络接收数据函数
{
	int i = 0;
	int len = 0;	
	
	/* +IPD,78:xxxxxxxxxx */
	{
		/* 解析出长度 */
		i = 0;
		while (1)
		{
			HAL_AT_Secv(&buf[i], (int)portMAX_DELAY);
			if (buf[i] == ':')
			{
				break;
			}
			else
			{
				len = len * 10 + (buf[i] - '0');   //将接收的到网络数据长度由字符转换为整型
			}
			i++;
		}

		/* 读取真正的网络数据 */
		i = 0;
		while (i < len)
		{
			HAL_AT_Secv(&buf[i], (int)portMAX_DELAY);
			if (i < AT_RESP_LEN)
			{
				/* 把数据放入环形buffer */
				ring_buffer_write(buf[i], &g_packet_buffer);
				
				/* wake up */
				/* 解锁唤醒使用ATReadData读网络数据的任务 */
				platform_mutex_unlock(&at_packet_mutex);		
			}
			i++;
		}
	}
}


void ATRecvParser( void * params)   //解析ESP8266返回数据的任务
{
	char buf[AT_RESP_LEN];   //接收ESP8266数据的buffer
	int i = 0;
	
	while (1)
	{
		/* 读取WIFI模块发来的数据:  使用阻塞方式 */
		HAL_AT_Secv(&buf[i], (int)portMAX_DELAY);
		buf[i+1] = '\0';

		/* 解析结果 */
		/* 1. 何时解析?    
		 * 1.1 收到"\r\n"
		 * 1.2 收到特殊字符: "+IPD,"
		 */
		if (i && (buf[i-1] == '\r') && (buf[i] == '\n'))
		{
			/* 得到了回车换行 */

			/* 2. 怎么解析 */
			if (strstr(buf, "OK\r\n"))
			{
				/* 记录数据 */
				memcpy(g_at_resp, buf, i);
				SetATStatus(AT_OK);
				platform_mutex_unlock(&at_ret_mutex);
				i = 0;
			}
			else if (strstr(buf, "ERROR\r\n"))
			{
				SetATStatus(AT_ERR);
				platform_mutex_unlock(&at_ret_mutex);
				i = 0;
			}       
			else if (strstr(buf, "Recv"))
			{
				SetATStatus(AT_OK);
				platform_mutex_unlock(&at_ret_mutex);
				i = 0;
			}
#if 0            
			else if (GetCIPSENDResult(buf))
			{
				SetATStatus(AT_OK);
				platform_mutex_unlock(&at_ret_mutex);
				i = 0;
			}
#endif            

			i = 0;
		}		
		else if (GetSpecialATString(buf))
		{
			ProcessSpecialATString(buf);
			i = 0;
		}
		else
		{
			i++;
		}

		if (i >= AT_RESP_LEN)
			i = 0;
	}
}


/* 以下代码是参考MqttClient源码中test例程: */
static void topic1_handler(void* client, message_data_t* msg)
{
    (void) client;
    MQTT_LOG_I("-----------------------------------------------------------------------------------");
    MQTT_LOG_I("%s:%d %s()...\r\ntopic: %s\r\nmessage:%s", __FILE__, __LINE__, __FUNCTION__, msg->topic_name, (char*)msg->message->payload);
    MQTT_LOG_I("-----------------------------------------------------------------------------------");
}


void MQTT_Client_Task(void *Param)
{
	int err;
	
    mqtt_client_t *client = NULL;
    mqtt_message_t msg;

    memset(&msg, 0, sizeof(msg));

    mqtt_log_init();

    client = mqtt_lease();
	
    mqtt_set_port(client, "xxx");   //MQTT服务器端口号

    mqtt_set_host(client, "xxx.xxx.xxx.xxx");    //MQTT服务器地址
    mqtt_set_client_id(client, random_string(10));
    mqtt_set_user_name(client, random_string(10));
    mqtt_set_password(client, random_string(10));
    mqtt_set_clean_session(client, 0);

    if (0 != mqtt_connect(client))
    {
		printf("mqtt_connect err\r\n");
		vTaskDelete(NULL);
    }

    err = mqtt_subscribe(client, "mcu_test1", QOS0, topic1_handler);
	if (err)
	{
		printf("mqtt_subscribe topic1 err\r\n");
	}

    err = mqtt_subscribe(client, "topic2", QOS1, NULL);
	if (err)
	{
		printf("mqtt_subscribe topic2 err\r\n");
	}

    err = mqtt_subscribe(client, "topic3", QOS2, NULL);
	if (err)
	{
		printf("mqtt_subscribe topic3 err\r\n");
	}

    msg.payload = "Hello world!";
    msg.qos = 0;
	msg.payloadlen = strlen(msg.payload);

    while (1) {
        /* 每间隔5s向主题mcu_test1发送一次"Hello world!" */
        mqtt_publish(client, "mcu_test1", &msg);
        printf("mqtt_publish mcu_test OK\r\n");
        vTaskDelay(5000);
    }

}

at_command.h代码:

#ifndef __AT_COMMAND_H
#define __AT_COMMAND_H

#define AT_OK        0
#define AT_ERR      -1
#define AT_TIMEOUT  -2

int ATInit(void);
void ATRecvParser( void * params);
void MQTT_Client_Task(void *Param);

/* eg. buf = "AT+CIPMODE=1"
 *     timeout : ms
 */
int ATSendCmd(char *buf, char *resp, int resp_len, int timeout);

int ATSendData(unsigned char *buf, int len, int timeout);
int ATReadData(unsigned char *c, int timeout);

int ATReadPacket(char *buf, int len, int *resp_len, int timeout);


#endif

4、plat_sock网络层相关代码

  1. 主要是对platform_net_socket.c文件中各个接口使用ESP8266的AT命令实现

platform_net_socket.c代码:

/*
 * @Author: jiejie
 * @Github: https://github.com/jiejieTop
 * @Date: 2020-01-10 23:45:59
 * @LastEditTime: 2020-04-25 17:50:58
 * @Description: the code belongs to jiejie, please keep the author information and source code according to the license.
 */
#include "mqtt_log.h"
#include "platform_net_socket.h"
#include "at_command.h"

#define TEST_SSID    "DESKTOP-NKFGPI3"   //WIFI名
#define TEST_PASSWD  "0V6u77{3"   //WIFI密码


/* return : < 0 , err 
 * 0 : ok
 */

int platform_net_socket_connect(const char *host, const char *port, int proto)
{
	int err;
	char cmd[100];

	while (1)
	{
		err = ATSendCmd("AT+CIPCLOSE", NULL, 0, 2000);
		
		/* 1. 配置 WiFi 模式 */
		err = ATSendCmd("AT+CWMODE=3", NULL, 0, 2000);
		if (err)
		{
			printf("AT+CWMODE=3 err = %d\n", err);
			//return err;
		}


		/* 2. 连接路由器 */
		/* 2.1 先断开 */
		err = ATSendCmd("AT+CWQAP", NULL, 0, 2000);
		if (err)
		{
			printf("disconnect AP err = %d\n", err);
			//return err;
		}


		/* 2.2 再连接 */
		err = ATSendCmd("AT+CWJAP=\"" TEST_SSID "\",\"" TEST_PASSWD "\"", NULL, 0, 200000);
		if (err)
		{
			printf("connect AP err = %d\n", err);
			//return err;
			continue;
		}

		/* 3. 连接到服务器 */
		if (proto == PLATFORM_NET_PROTO_TCP)
		{
			/* AT+CIPSTART="TCP","192.168.3.116",8080	 */
			sprintf(cmd, "AT+CIPSTART=\"TCP\",\"%s\",%s", host, port);
		}
		else
		{
			sprintf(cmd, "AT+CIPSTART=\"UDP\",\"%s\",%s", host, port);
		}

		err = ATSendCmd(cmd, NULL, 0, 2000);
		if (err)
		{
			printf("%s err = %d\n", cmd, err);
			continue;
		}

		if (!err)
			break;
		
	}
	
	return 0;
}

/* 暂时不需要用到的函数 */
#if 0
int platform_net_socket_recv(int fd, void *buf, size_t len, int flags)
{
    return 0;
}
#endif

/* 返回得到的字节数 */
int platform_net_socket_recv_timeout(int fd, unsigned char *buf, int len, int timeout)
{
	int i = 0;
	int err;

	/* 读数据, 失败则阻塞 */
	while (i < len)
	{
		err = ATReadData(&buf[i], timeout);
		if (err)
		{
			return 0;
		}
		i++;
	}

	return len;
}

/* 暂时不需要用到的函数 */
#if 0

int platform_net_socket_write(int fd, void *buf, size_t len)
{
	return 0;
}
#endif

int platform_net_socket_write_timeout(int fd, unsigned char *buf, int len, int timeout)
{
	int err;
	char cmd[20];

	sprintf(cmd, "AT+CIPSEND=%d", len);
	err = ATSendCmd(cmd, NULL, 0, timeout);
	if (err)
	{
		printf("%s err = %d, timeout = %d\n", cmd, err, timeout);
		return err;
	}
	err = ATSendData(buf, len, timeout);
	if (err)
	{
		printf("ATSendData err = %d\n", err);
		return err;
	}
	
	return len;
}

int platform_net_socket_close(int fd)
{
	return ATSendCmd("AT+CIPCLOSE", NULL, 0, 2000);
}

/* 暂时不需要用到的函数 */
#if 0

int platform_net_socket_set_block(int fd)
{
	return 0;
}

int platform_net_socket_set_nonblock(int fd)
{
	return 0;
}

int platform_net_socket_setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen)
{
	return 0;
}

#endif

五、烧录调试

主要记录调试过程中遇到的几个坑:

  • 堆栈空间分配不足导致MQTT_Client_Task任务无法运行、mqtt_yield_thread无法创建
  • mqtt_yield_thread中无法连接Server
  • 无法订阅主题和无法接收消息问题

1、堆栈空间分配问题

  1. 初始FreeRTOSConfig.h文件中默认堆栈空间配置如下:

在这里插入图片描述

  1. Debug时发现程序无法运行到MQTT_Client_Task任务中

在这里插入图片描述

  1. 把HEAP_SIZE调大到10K后继续调试

在这里插入图片描述

  1. 此时可以运行MQTT_Client_Task任务,但是出现mqtt yield thread creat faile

在这里插入图片描述

  1. 调试后猜测是platform_thread_init创建时堆空间不够

在这里插入图片描述

  1. 把堆空间增大到12

在这里插入图片描述

  1. 可以成功创建platform_thread_init任务了,但是出现无法连接Server问题

在这里插入图片描述

2、mqtt_yield_thread无法连接Server问题

  1. 调试后发现是由于client_state_t结构体中state状态不对导致报错的

在这里插入图片描述

  1. 查看mqtt_get_client_state后state状态是初始化状态

在这里插入图片描述

  1. 尝试注释掉判断state的代码

在这里插入图片描述

  1. 竟然发现可以正常发布消息,但是订阅失败

在这里插入图片描述

此处调试很久发现一直解决不了,最后索性注释掉,注释掉后发现是可以正常连接服务器并发布消息,但是无法订阅主题和接收消息!

3、无法订阅主题和无法接收消息问题

  1. 猜测将state判断注释掉可能会影响订阅主题和接收消息,于是取消注释,继续分析原因,发现出现无法连接Server时是进入了硬件错误

在这里插入图片描述

  1. 逐步分析,也是由于在中state状态不对导致需要停止任务导致的

在这里插入图片描述

  1. 搜索整个代码,将state设置为CLIENT_STATE_CONNECTED状态的只有2个地方,其中一个是重新连接时设置的,所以只有一个地方会将state状态设置为CLIENT_STATE_CONNECTED

在这里插入图片描述

  1. 调试发现代码根本无法运行到设置state为连接状态的地方就发生了硬件错误

其中无法理解的是为什么在MQTT_Client_Task任务中(优先级24)创建了mqtt_yield_thread任务(优先级5)后,就直接去运行mqtt_yield_thread了,没能继续运行platform_thread_init下的打印以及设置state状态的位置。

在这里插入图片描述

  1. 手动在platform_thread_init之前将state设置为CLIENT_STATE_CONNECTED

在这里插入图片描述

  1. 再次运行时发现MCU可以成功订阅主题mcu_test1,向主题发送数据并收到自己的数据,同时其他客户端同样能够收到MCU发送的数据,MCU能够收到其他客户端发送的数据,成功调通!

在这里插入图片描述

调试完成的代码以及杰杰的kawaii mqttclient源码:https://download.csdn.net/download/studyingdda/88741258

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1384381.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【redis基础1】基础数据类型详解和应用案例

博客计划 &#xff0c;我们从redis开始&#xff0c;主要是因为这一块内容的重要性不亚于数据库&#xff0c;但是很多人往往对redis的问题感到陌生&#xff0c;所以我们先来研究一下。 本篇&#xff0c;我们先看一下redis的基础数据类型详解和应用案例。 1.redis概述 以mysql为…

软件测试|Python requests库的安装和使用指南

简介 requests库是Python中一款流行的HTTP请求库&#xff0c;用于简化HTTP请求的发送和处理&#xff0c;也是我们在使用Python做接口自动化测试时&#xff0c;最常用的第三方库。本文将介绍如何安装和使用requests库&#xff0c;以及一些常见的用例示例。 安装requests库 首…

认知觉醒(九)

认知觉醒(九) 专注力——情绪和智慧的交叉地带 第一节 情绪专注&#xff1a;一招提振你的注意力 用元认知来观察自己的注意力是一件很有意思的事情&#xff0c;相信你可以轻易观察到这种现象&#xff1a;身体做着A&#xff0c;脑子却想着B。 跑步的时候&#xff0c;手脚在…

AI与编程学习

在C语言中&#xff0c;指针通常与字符数组或字符串打交道时会涉及到ASCII码的转换&#xff0c;而不是用于表现多位数的第一位。48这个值对应的是ASCII码表中数字字符0的编码。 如果你有一个表示多位数的字符数组&#xff0c;例如&#xff1a; c char number[] "1234&qu…

ssm基于web办事大厅政务预约系统+vue论文

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本办事大厅政务预约系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据…

代码随想录day30 回溯算法最终章

51. N皇后 题目 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回所有不同的 n 皇后问题 的解决方案。 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案&#xff0c;该方案中 Q 和…

xtu oj 1340 wave

题目描述 一个n列的网格&#xff0c;从(0,0)网格点出发&#xff0c;波形存在平波(从(x,y)到(x1,y))&#xff0c;上升波(从(x,y)到(x1,y1))&#xff0c;下降波(从(x,y)到(x1,y−1))三种波形&#xff0c;请问从(0,0)出发&#xff0c;最终到达(n,0)的不同波形有多少种&#xff1f…

第 13 章图

文章目录 13.1 图基本介绍13.1.1 为什么要有图13.1.2 图的举例说明 13.2 图的表示方式13.2.1 邻接矩阵13.2.2 邻接表 13.3 图的快速入门案例13.4 图的深度优先遍历介绍13.4.1 图遍历介绍13.4.2 深度优先遍历基本思想13.4.3 深度优先遍历算法步骤13.4.4 深度优先算法的代码实现 …

TikTok系列算法定位还原x-ss-stub

TikTok的x系列的算法比较有名,很多粉丝也问过,之前没有深入研究,本人工作量也比较大。 我们上次说到TikTok的x-ss-stub的算法就是ccmd5标准库算的,今天要讲细致点,表面这个结论本不是直接将数据md5那么来的,是经过一系列分析来的 上图是上次截图的,这次我们分析整个定位…

【算法】七夕祭

题目 七夕节因牛郎织女的传说而被扣上了「情人节」的帽子。 于是 TYVJ 今年举办了一次线下七夕祭。 Vani 同学今年成功邀请到了 cl 同学陪他来共度七夕&#xff0c;于是他们决定去 TYVJ 七夕祭游玩。 TYVJ 七夕祭和 11 区的夏祭的形式很像。 矩形的祭典会场由 N 排 M 列共…

【linux】awk的基本使用

awk是shell中一个强大的文本处理工具&#xff0c;被用于处理文本和数据 当你心中默念想要使用类似于 处理某一行&#xff0c;处理某一列 的文本 的功能时&#xff0c;就是awk登场的时候了 举个简单的例子是&#xff0c;当我们想知道自己的所有网卡的名字时&#xff0c;可以用i…

【数据结构与算法】之数组系列-20240115

这里写目录标题 一、599. 两个列表的最小索引总和二、724. 寻找数组的中心下标三、面试题 16.11. 跳水板四、35. 搜索插入位置 一、599. 两个列表的最小索引总和 简单 假设 Andy 和 Doris 想在晚餐时选择一家餐厅&#xff0c;并且他们都有一个表示最喜爱餐厅的列表&#xff0c…

鸿蒙开发现在就业前景怎样?

随着科技的不断进步&#xff0c;鸿蒙系统逐渐崭露头角&#xff0c;成为智能设备领域的一颗新星。作为华为自主研发的操作系统&#xff0c;鸿蒙系统拥有着广阔的市场前景和就业机会。那么&#xff0c;鸿蒙开发的就业前景究竟怎样呢&#xff1f; 一、市场需求持续增长 随着鸿蒙…

vivado18.3和modelsim关联

版本关系 首先明确Modelsim与Vivado的联合仿真需要版本号相匹配&#xff0c;Xilinx官方文档UG973中给出了所有版本的Vivado兼容Modelsim的版本情况 Vivado版本号Modelsim版本号Vivado Design Suite 2022.2Mentor Graphics ModelSim DE (2022.2)Vivado Design Suite 2022.1Men…

街机模拟游戏逆向工程(HACKROM)教程:[0]工具

街机hack&#xff0c;从早期的街霸-降龙版 到后期对各种街机的各种改动版本 这些成果&#xff0c;就是对街机游戏的代码进行逆向分析的结果。对于大部份街机游戏&#xff0c;是基于摩托罗拉68000的CPU&#xff0c;使用的是一套特别的汇编指令集。 一、MAME下载 我们想要对游戏…

Map、WeakMap和set、WeakSet

Map map是一个键名和键值可以是任意类型的键值对集合&#xff0c;它按照键值对的插入顺序来排列&#xff0c;如果给同一个键名插入键值&#xff0c;后者会覆盖前者 let map new Map() map.set(1, 1) map.set(string, string) map.set({1: 1}, {1: 1}) console.log(map)可以通…

ospf-gre隧道小练习

全网可达,R5路由表没有其他路由器的路由条目 注:每个路由器都添加了自己的环回,如R1就是1.1.1.1 R1可以分别ping通与R2,R3,R4之间的隧道 R1路由表上有所有路由器环回的路由条目 R5路由表上没有其他路由器的路由条目 实现代码: 首先将各个接口IP配好 边上3个路由器:[R6][R7][R…

鸿蒙Harmony-PersistentStorage--持久化存储UI状态储详解

用简单的心境&#xff0c;对待复杂的人生&#xff0c;方能看淡得失&#xff0c;从容入世&#xff0c;潇洒自如&#xff0c;心变得简单了&#xff0c;世界也就简单了 目录 一&#xff0c;定义 二&#xff0c;限制条件 三&#xff0c;使用 一&#xff0c;定义 LocalStorage和App…

SpringBoot多环境配置以及热部署

多环境配置 使用多环境配置的原因&#xff1a; 在SpringBoot项目的生命周期中&#xff0c;存在不同的环境&#xff0c;例如开发时的环境&#xff0c;测试时的环境&#xff0c;交付使用后的生产环境&#xff0c;每种环境的配置可能不一样&#xff0c;这种情况下可以通过多环境…

LeetCode-1523/1491/860/976

1.在区间范围内统计奇数数目&#xff08;1523&#xff09; 题目描述&#xff1a; 给你两个非负整数 low 和 high 。请你返回 low 和 high 之间&#xff08;包括二者&#xff09;奇数的数目。 思路一&#xff1a; 这里肯定会想到以low和high分别为上下限&#xff0c;然后遍历…