随着物联网设备的普及,物联网操作系统也成了广大开发者热烈讨论的话题之一,很多的组织和厂商都推出了在物联网节点上的基础软件——物联网操作系统,如国内云服务供应商Alibaba推出的AliOS Things ,Amazon公司的Amazon FreeRTOS,再如开源社区领袖Linux基金会推出的Zephyr,以及在国内知名度很高的RT-Thread等等。
今天我们来对AliOS Things中的示例linkkitapp例程进行一下解析,希望可以为你解惑一二。
系统架构
AliOS Things是什么?
AliOS Things是面向IoT领域的轻量级物联网嵌入式操作系统。致力于搭建云端一体化IoT基础设备。具备极致性能,极简开发、云端一体、丰富组件、安全防护等关键能力,并支持终端设备连接到阿里云Link,可广泛应用在智能家居、智慧城市、新出行等领域。
AliOS Things是阿里巴巴推出的物联网设备端软件框架,物联网设备可以通过AliOS Things设备框架接入阿里云,使用云服务器提供的相关物联网设备服务。
AliOS Things软件框架是基于APACHE2.0协议的开源软件,项目地址为:https://github.com/alibaba/AliOS-Things
AliOS Things软件架构可以从下到上分为四层,硬件和硬件抽象层、AOS操作系统层、应用框架层和应用层,下层组件为上层业务逻辑的实现提供支撑机制。
从底部到顶部,AliOS Things包括:
- 板级支持包(BSP):主要由SoC供应商开发和维护
- 硬件抽象层(HAL):比如WiFi和UART
- 内核:包括Rhino实时操作系统内核、Yloop, VFS, KV 存储
- 协议栈:包括TCP/IP协议栈(LwIP),uMesh网络协议栈
- 安全:安全传输层协议(TLS),可信服务框架(TFS)、可信运行环境(TEE)
- AOS API:提供可供应用软件和中间件使用的API
- 中间件:包括常见的物联网组件和阿里巴巴增值服务中间件
- 示例应用:阿里自主开发的示例代码,以及通过了完备测试的应用程序(比如Alinkapp) 所有的模组都已经被组织成组件,且每个组件都有自己的.mk文件,用于描述它和其它组件间的依赖关系,方便应用开发者按需选用。
我们一般只需要关心示例应用部分,找一个跟自己的需求接近的示例,然后在其上进行更改即可,我们使用的示例就是linkkitapp。
结构框图
目录结构
为什么D5(GPIO14)是一键配网的引脚?
首先我们看一下,D5引脚在哪里?
官方介绍: WeMos D1 mini pins and diagram
https://escapequotes.net/esp8266-wemos-d1-mini-pins-and-diagram/
D5是WeMos D1 mini模块的引脚,GPIO14是ESP8266的引脚,两个标识对应的一个引脚。
那linkkitapp代码中哪里对应着一键配网的代码呢?
程序入口在app_entry.c中的int application_start(int argc, char ** argv) 函数。
在application_start函数中有这样一行:
从函数名上看,这是一个注册事件过滤器的,过滤的目标是按键点击事件,并处理按键点击事件的,“linkkit按键处理”回调函数。
该函数的实现如下:
事件值为VALUE_KEY_CLICK将进入awss配网模式;
事件值为VALUE_KEY_LTCLICK将进行awss复位操作。
经过定位排查,我们知道在 key_poll_func(void * arg) 函数中,实现了判断按键(短按、长按、长长按),然后分别向系统发出不同的按键事件aos_post_event()。
由上我们知道了AliOS定义的配网按键就是GPIO14,也就是mini D1 ESP8266模块的D5脚。。
详细请参考:ESP8266配网--key.c之按键事件分析https://www.jianshu.com/p/fab0ea9e9ad3
ESP8266与主单片机之间的关系
STM32-->ESP8266-->阿里云物联网平台-->手机等终端
我们使用ESP8266模块,只是把ESP8266当做一个数据传输的媒介,STM32与ESP8266模块通过串口进行通信。
STM32获得外部传感器的状态,然后通过串口发送状态至ESP8266模块。
经ESP8266模块内部的AliOS Things对数据包进行打包,然后将数据包通过WiFi网络发送至阿里云物联网平台。
由于手机端与阿里云物联网平台保持着长连接,所以阿里云物联网平台获得到的传感器数据能够及时推送至手机端,进而保持设备端的传感器数据和手机端的数据保持一致性。
手机等终端-->阿里云物联网平台-->ESP8266-->STM32
反过来,通过手机端的云智能APP对设备进行操作,首先会将操作设备的指令推送至阿里云物联网平台,阿里云物联网平台会及时将指令推送至ESP8266模块,ESP8266模块会对收到的指令进行数据转发,将指令通过串口传输给STM32。
由于阿里云物联网平台推荐数据格式为ICA标准数据格式(Alink JSON),STM32端需要对接收到的串口信息进行解析,即对接收到的JSON字符串进行解析,然后对解析后的结果进行判断,根据指令不同进而做不同的动作。
有人可能会说,ESP8266这个单片机性能这么好,不利用它浪费了;
还有人会说对于智能风扇这样的简单应用,只需要ESP8266作为主芯片即可,加上外围电路,多省事多节约成本呀。
的确ESP8266性能很好,只是使用ESP8266的确够用。那我们为什么还要使用上面这种方式,额外浪费一个STM32单片机呢?
这种方式最大的好处就是,降低物联网模块的使用门槛,为什么这么说呢?
上面的通信方式,只需要一个具有串口功能的单片机就可以与ESP8266配合使用,而一般的单片机都有1-2个以上的串口,所以条件很容易就满足了。
这样,我们只需要在你原有的设计方案的基础上,额外占用一个串口,就可以在不改变整体电路设计的基础上,添加物联网功能。
而外围单片机的选择就很自由了,可以根据功能需要,选择便宜的或者性能比ESP8266更优的作为主单片机。
另外一点,如果拿ESP8266作为主单片机的话,那么就要对ESP8266有比较深入的了解,这样无疑增大了难度,当某天需求改变或者ESP8266性能无法满足的时候,我们更换了ESP8266的芯片,那么整体方案都要改,而且你还要重新熟悉一个新的物联网芯片,学习成本太高了。
需要解决的问题
ESP8266 有两个UART:UART0和UART1。
UART0有TX、RX,可以作为系统的打印信息输出接口和数据收发口。
UART1的RX被Flash占用,只有发送引脚TX(GPIO2,即UART1_TXD)可供使用,所以一般作为打印信息输出接口(调试用)。
通常情况下,我们使用UART0和外设通讯,而使用UART1作为日志打印端口。
D1 mini ESP8266模块两个串口的所在位置:
我们使用一个Micro USB线与D1 mini ESP8266模块相连,使用串口会收到如下打印信息:
我们可以看到默认的linkkitapp示例,Log信息是通过UART0发送出来的,而且里面有很多咱们不关心的信息,应该将此部分信息进行屏蔽。
所以我们需要做如下几个工作:
- 将串口的比特率由921600修改为115200(此部分工作可不做);
- 交换UART0和UART1,让UART1输出Log日志;UART0与STM32进行通信;
- 将STM32发上来的信息,通过UART0接收并发送到云端;将云端下发的有用的信息通过UART0转发给STM32。
解决问题1. 串口的初始化api在platform\mcu\esp8266\bsp\driver\uart.c 中,目前的代码是默认如果不设置,uart0波特率是921600。但是一旦初始化了uart1,uart0的波特率会被改为和uart1一样。
void
uart_init_new(uart_dev_t *uart)
{
UART_WaitTxFifoEmpty(UART0);
UART_WaitTxFifoEmpty(UART1);
if (uart == NULL)
{
return;
}
if (uart->port == 1)
{
//printf("port= 1\n ");
//uart1 setting
UART_ConfigTypeDef uart_config;
uart_config.baud_rate = uart->config.baud_rate;
uart_config.data_bits = UART_WordLength_8b;
uart_config.parity = USART_Parity_None;
uart_config.stop_bits = USART_StopBits_1;
uart_config.flow_ctrl = USART_HardwareFlowControl_None;
uart_config.UART_RxFlowThresh = 120;
uart_config.UART_InverseMask = UART_None_Inverse;
//UART_ParamConfig(UART0, &uart_config);
//uart2 setting for log
//uart_config.baud_rate = uart->config.baud_rate;
UART_ParamConfig(UART1, &uart_config);
UART_SetPrintPort(UART1);
//UART_intr_handler_register(uart0_rx_isr, NULL);
ETS_UART_INTR_ENABLE();
}
else
{
//printf("port= 0 \n ");
UART_ConfigTypeDef uart_config;
uart_config.baud_rate = uart->config.baud_rate;
// uart_config.baud_rate = BIT_RATE_921600;
uart_config.data_bits = UART_WordLength_8b;
uart_config.parity = USART_Parity_None;
uart_config.stop_bits = USART_StopBits_1;
uart_config.flow_ctrl = USART_HardwareFlowControl_None;
uart_config.UART_RxFlowThresh = 120;
uart_config.UART_InverseMask = UART_None_Inverse;
UART_ParamConfig(UART0, &uart_config);
UART_IntrConfTypeDef uart_intr;
uart_intr.UART_IntrEnMask = UART_RXFIFO_TOUT_INT_ENA | UART_FRM_ERR_INT_ENA | UART_RXFIFO_FULL_INT_ENA | UART_TXFIFO_EMPTY_INT_ENA;
uart_intr.UART_RX_FifoFullIntrThresh = 100;//10
uart_intr.UART_RX_TimeOutIntrThresh = 10;//2
uart_intr.UART_TX_FifoEmptyIntrThresh = 20;
UART_IntrConfig(UART0, &uart_intr);
UART_SetPrintPort(UART0);
UART_intr_handler_register(uart0_rx_isr, NULL);
ETS_UART_INTR_ENABLE();
}
}
解决问题2. 交换UART0和UART1:修改此文件:AliOS-Things\platform\mcu\esp8266\hal\uart.c
解决问题3.
- 下发有用信息:
- 上发有用信息:
模拟上传属性
经过上面的改造之后,我们只需要向UART0发送JSON格式的数据,即可修改服务器端的数值,比如串口助手中发送:
{"CurrentTemperature":26}
发送完毕,服务器端的当前温度值将会修改为26℃,在运行状态中可以实时看出来当前温度值是实时变化的。
模拟设置属性
手机端APP点击某个按钮之后,将会将数据包发送至ESP8266,ESP8266将有用信息通过UART0的TX引脚发送给STM32,STM32将收到服务器端指令,对此指令进行解析,进而做相应的动作,具体逻辑类似下图所示。
调试真实设备中,对电源开关设置为1,即{"PowerSwitch":1},在串口助手中我们收到指令{"PowerSwitch":1};
我们对电源开关设置为0,即{"PowerSwitch":0},可以看到串口助手中,收到对应的指令{"PowerSwitch":0}。
STM32中我们使用cJSON对上面字符串进行解析即可,然后做相应的动作,即完成了云端对设备的远程控制。
细心的人可能发现了,为什么我们用CurrentTemperature或者PowerSwitch来设置属性呢?其实他们就是我们创建产品的时候,进行功能定义的时候,设置的标识符。
到此为止,本月的“智能风扇”涉及到的知识点基本都已经讲过了,下一篇网文,就对此项目进行最后的收尾,大家敬请期待哈。