设备接入ONENET(3)STM32 + ESP8266(MQTT协议)接入云 :官方例程移植笔记(HAL+LL库)

2023-05-16

重要提示:由于OneNET版本迭代,导致鉴权方式可能变更,若程序无法连接,请参考官方手册或再OneNET论坛搜索相关内容

  • 移植本意应该是指通过修改,使得运行原先在另一个平台可运行的程序。
  • STM32CubeMx 本文简称 Mx

1. 概述

麒麟座的例程使用的是标准库,ST后续推出更新的 LL库 和 HAL库,其中LL库与标准库类似,而 HAL 则更倾向于兼容通用性,体积和效率则没有优势,如果你想在自己的工程中使用麒麟座的例程,那么移植工作是难以避免的。

移植工作主要是硬件的设配,所以首先需要了解麒麟镇开发板例程使用的基本情况:

STM32F1
Uart1输出打印口
Uart2ESP8266 串口
GPIOB.5ESP8266 复位

在本文工程中,硬件配置如下:

STM32F1
Uart1ESP8266 串口
SWO输出调试口
GPIOB.5ESP8266 复位

2. 移植流程与思路

我移植工程时,通常先做一部分的准备工作,再开始具体针对硬件特性的移植:
准备工作:

  • ① 创建一个模板工程:这个工程可以输出调试信息,可以是常用的串口或者是ST-LINK/J-LINK SWDIO 输出
  • ② 外设初始化并测试:逐个外设的初始化并测试,如本文用到串口,需要初始化串口,并测试它的收发和中断情况
  • ③ 分析硬件层接口函数:无论上层逻辑多么复杂,最终它都需要调用一个函数去控制外设,以 printf 函数为例,它最终都需要调用一个能输出一个字符的函数,去将信息展示出来,我们通过改变这个函数,就可以让信息输出到串口,或者是ST-LINK等设备中,你甚至可以打印到OLED屏幕上。
  • ④ 对比分析源工程和目标工程:这部分工作需要我们对源工程和目标工程都有比较基础的理解,例如:
    • 以本文为例,想把标准库函数移植到 Mx 生成的工程中,那么 Mx 的 mian.h 包含了外设头文件,所以 标准中所有类似 #include "usart.h" 之类的头文件就应该换成 mian.h,另外,stm32f10x.h 是标准库中关于外设地址的共定义,若使用 Mx 生成的工程,该文件名为 stm32f106xb.h,它被mian.h包含了(不是直接包含),所以,#include "stm32f10x.h" 语句也是需要替换为include "mian.h" 语句。
    • 延时函数:驱动通常包含延时函数,不同的库延时函数命名不同,单位也可能不同,虽然一般都是 ms,本文源工程延时函数为DelayXms();,需要替换为目标工程库函数的 HAL_Delay(),单位不需换算,因为都是毫秒。
    • … …
  • ⑤ 拷贝源码文件:这一步分析我们需要移植的代码,将文件拷贝到自己的工程中,注意,在MDK中不只是将文件放在工程目录下,还需要在工程中添加一次,如果没记错 TrueStudio 会自动读取工程下的文件,添加到工中,然后是添加头文件路径。

接下来是具体的移植。

3. 麒麟座 标准 移植 LL 库

新建一个基础工程与初始化外设,使用的是 Mx ,此处不再赘述,在麒麟镇中,需要移植的源码都在其工程目录[NET]文件夹中,将其添加到本文工程如下所示:
在这里插入图片描述
接着添加头文件路径:
在这里插入图片描述

3.1 替换外设接口头文件

如前文所述,本文使用 Mx 工程,所以外设头文件统一为 main.h,所以将esp8266.conenet.c 中头文件替换:

在这里插入图片描述
在这里插入图片描述

3.2 替换输出调试信息

麒麟镇使用以下语句打印信息:

	UsartPrintf(USART_DEBUG, "OneNet_SendData\r\n");

而本文使用:

	printf("OneNet_SendData\r\n");

所以直接查找替换:
在这里插入图片描述
将所有 UsartPrintf(USART_DEBUG,替换成printf(
在这里插入图片描述

3.3 延时函数

函数函数也是同理:
在这里插入图片描述

3.4 esp8266 接口函数(发送)

程序处理了字符串,最后是通过以下函数将数据发送到 ESP8266 的:

void Usart_SendString(USART_TypeDef *USARTx, unsigned char *str, unsigned short len)
{

	unsigned short count = 0;
	
	for(; count < len; count++)
	{
		USART_SendData(USARTx, *str++);									//发送数据
		while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);		//等待发送完成
	}

}

本文将其改为:

void Usart_SendString(USART_TypeDef *USARTx, unsigned char *str, unsigned short len)
{

	unsigned short count = 0;
	
	for(; count < len; count++)
	{
		LL_USART_TransmitData8(USARTx, *str++);									//发送数据
		while(!LL_USART_IsActiveFlag_TC(USARTx));		                        //等待发送完成
	}
}

注意到,这个函数在源工程的 usart.c 中,这文件只需要这个函数,所以不需要复制整个文件,只需要把和这个函数添加到 esp8266.c 中即可。另外,这函数需要第一个参数来确定串口设备,而本文使用的是串口1,源工程使用功串口2,所以工程中,所有的语句:

Usart_SendString(USART2, (unsigned char *)cmd, strlen((const char *)cmd));

需要改为:

Usart_SendString(USART1, (unsigned char *)cmd, strlen((const char *)cmd));

3.5 esp8266 接口函数(接收)

esp8266 使用串口中断来接收数据:

void USART2_IRQHandler(void)
{

	if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) //接收中断
	{
		if(esp8266_cnt >= sizeof(esp8266_buf))	esp8266_cnt = 0; //防止串口被刷爆
		esp8266_buf[esp8266_cnt++] = USART2->DR;
		
		USART_ClearFlag(USART2, USART_FLAG_RXNE);
	}

}

本文使用的是串口1,硬件也接入串口1,所以发生中断会进入串口1中断:

//文件:stm32f10x_it.c
void USART1_IRQHandler(void)
{
  /* USER CODE BEGIN USART1_IRQn 0 */
  extern void ESP8266_IRQHandler(void);
  ESP8266_IRQHandler();
  /* USER CODE END USART1_IRQn 0 */
  /* USER CODE BEGIN USART1_IRQn 1 */

  /* USER CODE END USART1_IRQn 1 */
}

通过调用ESP8266_IRQHandler()的方法,是程序处理可以写在其他文件中:

//文件:esp8266.c
void ESP8266_IRQHandler(void)
{

	if(LL_USART_IsActiveFlag_RXNE(USART1)) //接收中断
	{
		if(esp8266_cnt >= sizeof(esp8266_buf))
        {            
            esp8266_cnt = 0; //防止串口被刷爆
        }
        esp8266_buf[esp8266_cnt++] = USART1->DR;
        LL_USART_ClearFlag_RXNE(USART1);
	}
}

3.6 esp8266 初始化

源文件 esp8266 初始化中初始化了所使用的串口,但在 Mx 在已经初始化了,所以需要删除,顺便将复位信号改正:

void ESP8266_Init(void)
{
	
//	GPIO_InitTypeDef GPIO_Initure;
//	
//	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

//	//ESP8266复位引脚
//	GPIO_Initure.GPIO_Mode = GPIO_Mode_Out_PP;
//	GPIO_Initure.GPIO_Pin = GPIO_Pin_5;					//GPIOB5-复位
//	GPIO_Initure.GPIO_Speed = GPIO_Speed_50MHz;
//	GPIO_Init(GPIOB, &GPIO_Initure);
//	
//	GPIO_WriteBit(GPIOB, GPIO_Pin_5, Bit_RESET);
//	HAL_Delay(250);
//	GPIO_WriteBit(GPIOB, GPIO_Pin_5, Bit_SET);
//	HAL_Delay(500);

    HAL_GPIO_WritePin(ESP_RST_GPIO_Port,ESP_RST_Pin,GPIO_PIN_RESET);
    HAL_Delay(250);
    HAL_GPIO_WritePin(ESP_RST_GPIO_Port,ESP_RST_Pin,GPIO_PIN_SET);
    HAL_Delay(500);
	
    ESP8266_Clear();
  
	printf("1. AT\r\n");
	while(ESP8266_SendCmd("AT\r\n", "OK"))
	HAL_Delay(500);
	
	printf("2. CWMODE\r\n");
	while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
	HAL_Delay(500);
	
	printf("3. CWJAP\r\n");
	while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP"))
	HAL_Delay(500);
	
	printf("4. CIPSTART\r\n");
	while(ESP8266_SendCmd(ESP8266_ONENET_INFO, "CONNECT"))
	HAL_Delay(500);
	
	printf("5. ESP8266 Init OK\r\n");

}

3.7 数据下发响应

OneNet_RevPro() 函数响应数据的内容,并打开和关闭LED灯,本文并没有对应的接口,所以将打开 LED 灯接口都换成打印:
在这里插入图片描述

另外,源文件发送数据是led状态:

unsigned char OneNet_FillBuf(char *buf)
{
	
	char text[16];
	
	memset(text, 0, sizeof(text));
	
	strcpy(buf, "{");
	
	memset(text, 0, sizeof(text));
	sprintf(text, "\"Red_Led\":%d,", led_status.Led5Sta);
	strcat(buf, text);
	
	memset(text, 0, sizeof(text));
	sprintf(text, "\"Green_Led\":%d,", led_status.Led4Sta);
	strcat(buf, text);
	
	memset(text, 0, sizeof(text));
	sprintf(text, "\"Yellow_Led\":%d,", led_status.Led3Sta);
	strcat(buf, text);
	
	memset(text, 0, sizeof(text));
	sprintf(text, "\"Blue_Led\":%d", led_status.Led2Sta);
	strcat(buf, text);
	
	strcat(buf, "}");
	
	return strlen(buf);

}

这里你可以修改为任何值,然后看看效果如何。

四、测试

本文测试时候未接复位IO口,完成以上操作后,可以通过onenet平台下发命令来测试通信,根据说明文档:

1.修改esp8266.c下的wifi账号及密码
2.修改onenet.c下的proid、auth_info和devid
3.指令说明:
1.命令直接下发:
redled:1 打开红灯
greenled:1 打开绿灯
yellowled:1 打开黄灯
blueled:1 打开蓝灯
同理,1替换为0则是关闭
2.应用命令填写方式:
redled:{V}, 控制红灯;开关开值1,开关关值0
greenled:{V}, 控制绿灯;开关开值1,开关关值0
yellowled:{V}, 控制黄灯;开关开值1,开关关值0
blueled:{V}, 控制蓝灯;开关开值1,开关关值0

五、附件

链接:onenet_stm32_mqtt
提取码:1234

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

设备接入ONENET(3)STM32 + ESP8266(MQTT协议)接入云 :官方例程移植笔记(HAL+LL库) 的相关文章

随机推荐