OneOS及工具下载目前OneOS代码已经开源,开发者可以前往 OneOS仓库[5] 或者 OneOS官网[6] 进行下载。
OneOS-Cube是OneOS操作系统基于命令行的开发工具,提供系统配置、项目编译构造、包贡献下载等功能。OneOS-Cube工具的下载及使用说明可以访问 OneOS-Cube环境搭建[7] 进行学习。
硬件环境准备本文使用OneOS官方的万耦创世L475开发板作为硬件平台。
万耦创世L475开发板使用了STM32L475VGT6芯片,STM32L4系列MCU可以根据微处理器运行时不同的应用需求来适时调整电压从而实现功耗的动态平衡。该功能适用于STOP模式下的低功耗外设(LP UART、LP定时器)、安全和保密特性、大量智能外设,以及诸如运算放大器、比较器、LCD、12位DAC和16位ADC(硬件过采样)等先进的低功耗模拟外设。
关于万耦开发板详细的文档说明可以参考 万耦开发板开发文档[8]
软件环境准备万耦开发板的示例工程需要准备以下的软件环境:
•MDK 开发环境 安装 MDK-ARM 5.27 (正式版或评估版,5.14 版本及以上版本均可),推荐使用keil最新版本,避免不必要的问题,安装方法可以参考 Keil MDK安装。•连接并安装Jlink驱动,便于进行下载调试;
为了深入开发流程,开发者可以使用本文提供的空白示例工程进行开发,一步一步的完成一个简易物联网应用的开发。
点亮万耦开发板完成万耦开发板的硬件连接,如下图所示:
将本文提供的空白示例工程解压缩后放置在OneOS/projects目录下,如下图所示:
进入stm32l475-cmcc-oneos-demo目录下,打开MDK工程,点击左上角的Build按钮,等待编译完成。
当看到0 Error(s), 0 Warning(s)的输出时说明编译完成,此时点击左上角的Download按钮,将程序下载到开发板中。
当程序下载完成后,串口将输出Hello OneOS!,代表开发板中的程序运行成功!
读取传感器数据OneOS在各类外设的基础上抽象出了设备驱动模型,有效提高了代码可复用性、可移植性,模块分层解耦,降低了各层的开发难度。
本文将使用OneOS中Sensor框架提供的API读取万耦开发板上板载的AHT10温/湿度传感器和AP3216C光强/接近位置传感器的数据。
Sensor框架配置在stm32l475-cmcc-oneos-demo右键打开OneOS-Cube工具,使用menuconfig进行Sensor框架的配置,配置如下所示:
- (Top) → Drivers→ I2C
- [*] Using I2C device drivers
- [*] Use GPIO to simulate I2C
- (10) simulate I2C bus delay(us)
- [ ] Enable I2C1 BUS (software simulation) ----
- [ ] Enable I2C2 BUS (software simulation) ----
- [*] Enable I2C3 BUS (software simulation) --->
- [ ] Enable I2C4 BUS (software simulation) ----
- (Top) → Drivers→ Sensors
- [*] Using sensor device drivers
- [ ] Enable sht20 ----
- [*] Enable aht10 --->
- (soft_i2c3) aht10 i2c bus name
- (0x38) aht10 i2c addr(7bit)
- [ ] Enable adxl345 ----
- [ ] Enable bh1750 ----
- [ ] Enable bmp180 ----
- [ ] Enable mpu6xxx --->
- [ ] Enable ak8963 ----
- [ ] Enable lsm6dsl ----
- [*] Enable ap3216c --->
- (soft_i2c3) ap3216c i2c bus name
- (0x1e) ap3216c i2c addr
完成配置后退出menuconfig并保存配置,使用命令scons --ide=mdk5更新MDK工程设置, 命令执行完成后重新打开MDK工程。
Sensor相关的代码和组将会被加到MDK工程中。在main函数中添加以下的代码,读取并打印传感器的数值。
- /* 包含头文件 */
- #include <os_kernel.h>
- #include <sensors/sensor.h>
- #include <stdio.h>
- static os_device_t *sensor_temp = OS_NULL;
- static os_device_t *sensor_humi = OS_NULL;
- static os_device_t *sensor_light = OS_NULL;
- static os_device_t *sensor_ps = OS_NULL;
- /* 传感器初始化 */
- static void sensor_init(void)
- {
- /* 根据传感器设备名称查找设备 */
- sensor_temp = os_device_find("temp_aht10");
- OS_ASSERT(sensor_temp != OS_NULL);
- sensor_humi = os_device_find("humi_aht10");
- OS_ASSERT(sensor_humi != OS_NULL);
- sensor_light = os_device_find("li_ap3216c");
- OS_ASSERT(sensor_light != OS_NULL);
- sensor_ps = os_device_find("pr_ap3216c");
- OS_ASSERT(sensor_ps != OS_NULL);
- /* 打开传感器设备 */
- os_device_open(sensor_temp, OS_DEVICE_FLAG_RDWR);
- os_device_open(sensor_humi, OS_DEVICE_FLAG_RDWR);
- os_device_open(sensor_light, OS_DEVICE_FLAG_RDWR);
- os_device_open(sensor_ps, OS_DEVICE_FLAG_RDWR);
- }
- int main(void)
- {
- printf("Hello, OneOS!\n");
- struct os_sensor_data sensor_data;
- sensor_init();
- /* 读取温度传感器数据 */
- os_device_read(sensor_temp, 0, &sensor_data, sizeof(struct os_sensor_data));
- printf("temp: %.2f\n", (double)sensor_data.data.temp / 1000);
- /* 读取湿度传感器数据 */
- os_device_read(sensor_humi, 0, &sensor_data, sizeof(struct os_sensor_data));
- printf("humi: %.2f\n", (double)sensor_data.data.humi / 1000);
- /* 读取光照传感器数据 */
- os_device_read(sensor_light, 0, &sensor_data, sizeof(struct os_sensor_data));
- printf("light: %.2f\n", (double)sensor_data.data.light / 1000);
- /* 读取接近传感器数据 */
- os_device_read(sensor_ps, 0, &sensor_data, sizeof(struct os_sensor_data));
- printf("ps: %d\n", sensor_data.data.raw);
- return 0;
- }
编译并下载到万耦开发板运行,可以通过串口看到打印的传感器数值。
关于OneOS驱动及Sensor设备的详细说明可以访问 Sensor设备用户开发[9] 进行学习。
建立线程读取传感器上面的示例代码只能读取和打印一次传感器数值,可以在OneOS中建立一个任务来循环读取和打印传感器数值。任务是 OneOS 操作系统中最小的调度单位。将上述的代码修改如下:
- ......
- /* 传感器初始化 */
- static void sensor_init(void)
- {
- ......
- }
- static void sensor_read_entry(void *parameter)
- {
- struct os_sensor_data sensor_data;
- /* 循环读取传感器数据 */
- while (1)
- {
- printf("---------Sensor Data--------\n");
- /* 读取温度传感器数据 */
- os_device_read(sensor_temp, 0, &sensor_data, sizeof(struct os_sensor_data));
- printf("temp: %.2f\n", (double)sensor_data.data.temp / 1000);
- /* 读取湿度传感器数据 */
- os_device_read(sensor_humi, 0, &sensor_data, sizeof(struct os_sensor_data));
- printf("humi: %.2f\n", (double)sensor_data.data.humi / 1000);
- /* 读取光照传感器数据 */
- os_device_read(sensor_light, 0, &sensor_data, sizeof(struct os_sensor_data));
- printf("light: %.2f\n", (double)sensor_data.data.light / 1000);
- /* 读取接近传感器数据 */
- os_device_read(sensor_ps, 0, &sensor_data, sizeof(struct os_sensor_data));
- printf("ps: %d\n", sensor_data.data.raw);
- printf("----------------------------\n");
- os_task_mdelay(3000); /* 延时3秒 */
- }
- }
- int main(void)
- {
- printf("Hello, OneOS!\n");
- sensor_init();
- /* 创建传感器读取任务 */
- os_task_t *sensor_read_task = os_task_create("sensor_read",
- sensor_read_entry,
- OS_NULL,
- 1024,
- 20,
- 5);
- if (sensor_task != OS_NULL)
- {
- os_task_startup(sensor_read_task);
- }
- return 0;
- }
编译并下载到万耦开发板运行,可以通过串口看到打印的循环读取的传感器数值。
关于OneOS任务管理及调度的详细内容可以访问 任务管理及调度[10] 进行学习。
通过消息队列传递传感器数据传感器读取任务读取的传感器数据可以保存在结构体中,然后发送至消息队列中进行保存。传感器处理任务可以从消息队列中读取传感器数据并进行处理。修改上述的示例代码中,新建一个消息队列和传感器处理线程,传感器处理线程从消息队列中读取数据并将数据按照指定的格式打印出来。
- #include <os_kernel.h>
- #include <sensors/sensor.h>
- #include <stdio.h>
- ......
- static os_mq_t *sensor_mq = OS_NULL;
- typedef struct sensor_data
- {
- float temp;
- float humi;
- float light;
- int ps;
- } sensor_data_t;
- static void sensor_init(void)
- {
- ......
- }
- static void sensor_read_entry(void *parameter)
- {
- struct os_sensor_data tmp_data;
- sensor_data_t data;
- /* 循环读取传感器数据 */
- while (1)
- {
- /* 读取温度传感器数据 */
- os_device_read(sensor_temp, 0, &tmp_data, sizeof(struct os_sensor_data));
- data.temp = (float)tmp_data.data.temp / 1000;
- /* 读取湿度传感器数据 */
- os_device_read(sensor_humi, 0, &tmp_data, sizeof(struct os_sensor_data));
- data.humi = (float)tmp_data.data.humi / 1000;
- /* 读取光照传感器数据 */
- os_device_read(sensor_light, 0, &tmp_data, sizeof(struct os_sensor_data));
- data.light = (float)tmp_data.data.light / 1000;
- /* 读取接近传感器数据 */
- os_device_read(sensor_ps, 0, &tmp_data, sizeof(struct os_sensor_data));
- data.ps = tmp_data.data.raw;
- os_mq_send(sensor_mq, &data, sizeof(sensor_data_t), OS_IPC_WAITING_FOREVER);
- os_task_mdelay(3000); /* 延时3秒 */
- }
- }
- static void sensor_process_entry(void *parameter)
- {
- sensor_data_t data;
- os_size_t data_size;
- while (1)
- {
- os_mq_recv(sensor_mq, &data, sizeof(sensor_data_t), OS_IPC_WAITING_FOREVER, &data_size);
- printf("---------Sensor Data--------\n");
- printf("temp: %.2f\n", data.temp);
- printf("humi: %.2f\n", data.humi);
- printf("light: %.2f\n", data.light);
- printf("ps: %d\n", data.ps);
- printf("----------------------------\n");
- }
- }
- int main(void)
- {
- printf("Hello, OneOS!\n");
- sensor_init();
- sensor_mq = os_mq_create("sensor_mq", sizeof(sensor_data_t), 10, OS_IPC_FLAG_PRIO);
- OS_ASSERT(sensor_mq != OS_NULL);
- /* 创建传感器读取任务 */
- os_task_t *sensor_read_task = os_task_create("sensor_read",
- sensor_read_entry,
- OS_NULL,
- 1024,
- 20,
- 5);
- OS_ASSERT(sensor_read_task != OS_NULL);
- os_task_startup(sensor_read_task);
- /* 创建传感器处理任务 */
- os_task_t *sensor_process_task = os_task_create("sensor_process",
- sensor_process_entry,
- OS_NULL,
- 2048,
- 15,
- 5);
- OS_ASSERT(sensor_process_task != OS_NULL);
- os_task_startup(sensor_process_task);
- return 0;
- }
关于OneOS消息队列及任务间数据通信的详细内容可以访问 消息队列[11] 进行学习。
通过MQTT接入OneNET Studio
MQTT组件OneOS操作系统通过移植Paho-MQTT软件包来实现MQTT协议功能,开发者可以通过OneOS Cube中的menuconfig图形化界面轻松的将MQTT组件集成到自己的应用中,MQTT组件的配置如下:
- (Top) → Components→ Network→ Protocols→ MQTT
- [*] Enable Paho MQTT
- [ ] Enable Paho MQTT TLS encrypt
- [ ] Enable Paho MQTT sample
关于MQTT组件的配置及API说明可以访问 MQTT用户编程手册[12] 进行学习。
cJSON组件OneNET Studio使用 OneJSON 作为物模型通信协议,OneJSON协议是针对物联网开发领域设计的一种数据交换规范,数据格式是JSON,用于设备端和物联网平台的双向通信,更便捷地实现和规范了设备端和物联网平台之间的业务数据交互。
OneOS操作系统通过cJSON组件提供JSON格式数据创建和解析的能力,cJSON组件的配置如下:
- (Top) → Thirdparty→ cJSON
- [*] cJSON: Ultralightweight JSON parser in ANSI C
关于cJSON的详细说明可以访问 cJSON[13] 进行学习。
Molink组件OneOS操作系统通过Molink模组互联套件来提供设备与平台间互联互通的能力。Molink通过架构设计和模组适配实现了对不同的通信模组的统一控制,并向上层框架和应用提供统一的API接口,使开发者不必关心不同模组之间的差异即可完成网络相关应用的开发。
本文以使用ML302为例,ML302 是中国移动最新推出的 LTE Cat.1 模块。ML302 支持 TD- LTE/FDD- LTE 通信制式,采用 LCC+LGA 封装方式。丰富的 Internet 协议、行业标准接口和功能,支持 Windows、Linux 和 Android 驱动。ML302 可广泛应用到 M2M多个领域中,如共享、金融支付、POC、工业控制等。
Molink中ML302的配置如下所示:
- (Top) → Components → Network → Molink → Enable IoT modules support → Modules → 4G CAT1 Modules Support → ML302 → ML302
- [*] Enable ML302 Module Object Auto Create
- (uart4) ML302 Interface Device Name
- (115200) ML302 Interface Device Rate
- (512) The maximum length of AT command data accepted
- -*- Enable ML302 Module General Operates
- -*- Enable ML302 Module Network Service Operates
- [*] Enable ML302 Module Ping Operates
- [*] Enable ML302 Module Ifconfig Operates
- -*- Enable ML302 Module Network TCP/IP Operates
- [*] Enable ML302 Module BSD Socket Operates
- [ ] Enable ML302 Module MQTT Client Operates
如果需要使用其他的通信模组,只需在Molink的菜单下进行重新配置即可,关于Molink的配置及API说明可以访问 Molink用户编程手册[14] 进行学习。
完成上述MQTT、cJSON及Molink组件的配置后,在OneOS-Cube工具中使用命令scons --ide=mdk5更新MDK工程设置
MQTT、cJSON及Molink相关的代码将会被加入到MDK工程中, 编译代码并下载至万耦开发板中,当看到如下的日志输出时,说明ML302模组初始化成功。
示例代码编写如下所示的代码连接到OneNET Studio平台,并将传感器数据发布至平台。
- #include <os_kernel.h>
- #include <sensors/sensor.h>
- #include <MQTTOneOS.h>
- #include <MQTTClient.h>
- #include <cJSON.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- static os_device_t *sensor_temp = OS_NULL;
- static os_device_t *sensor_humi = OS_NULL;
- static os_device_t *sensor_light = OS_NULL;
- static os_device_t *sensor_ps = OS_NULL;
- static os_mq_t *sensor_mq = OS_NULL;
- typedef struct sensor_data
- {
- double temp;
- double humi;
- double light;
- int ps;
- } sensor_data_t;
- static void sensor_init(void)
- {
- ......
- }
- static void sensor_read_entry(void *parameter)
- {
- ......
- }
- #define MQTT_SERVER_IP "218.201.45.7"
- #define MQTT_SERVER_PORT (1883)
- #define MQTT_CLINET_ID "oneos_test"
- #define MQTT_POST_TOPIC "$sys/9HFp6Cf7i4/oneos_test/thing/property/post"
- #define MQTT_COMMAND_TIMEOUT (30000)
- #define MQTT_CLIENT_USERNAME "用户名"
- #define MQTT_CLINET_PASSWORD "用户密码"
- static unsigned char send_buff[512] = {0};
- static unsigned char read_buff[512] = {0};
- static void sensor_process_entry(void *parameter)
- {
- sensor_data_t data;
- os_size_t data_size;
- Network network;
- MQTTClient client;
- MQTTNetworkInit(&network, MQTT_SERVER_IP, MQTT_SERVER_PORT, OS_NULL);
- MQTTClientInit(&client,
- &network,
- MQTT_COMMAND_TIMEOUT,
- send_buff,
- sizeof(send_buff),
- read_buff,
- sizeof(read_buff));
- int rc = network.connect(&network);
- if (0 != rc)
- {
- printf("Establish network failed, check MQTT server ip and port");
- return;
- }
- MQTTPacket_connectData connectData = MQTTPacket_connectData_initializer;
- connectData.clientID.cstring = MQTT_CLINET_ID;
- connectData.username.cstring = MQTT_CLIENT_USERNAME;
- connectData.password.cstring = MQTT_CLINET_PASSWORD;
- if ((rc = MQTTConnect(&client, &connectData)) != 0)
- {
- printf("Return code from MQTT connect is %d", rc);
- return;
- }
- else
- {
- printf("MQTT Client Connected");
- }
- while (1)
- {
- os_mq_recv(sensor_mq, &data, sizeof(sensor_data_t), OS_IPC_WAITING_FOREVER, &data_size);
- printf("---------Sensor Data--------\n");
- /* 四舍五入保留两位小数 */
- data.temp = ((int)(data.temp * 100 + 0.5)) / 100.0;
- data.humi = ((int)(data.humi * 100 + 0.5)) / 100.0;
- data.light = ((int)(data.light * 100 + 0.5)) / 100.0;
- printf("temp: %.2f\n", data.temp);
- printf("humi: %.2f\n", data.humi);
- printf("light: %.2f\n", data.light);
- printf("ps: %d\n", data.ps);
- printf("----------------------------\n");
- if (MQTTIsConnected(&client))
- {
- /* 将数据封装为OneJSON格式 */
- cJSON *onejson_head = cJSON_CreateObject();
- cJSON_AddStringToObject(onejson_head, "id", "123");
- cJSON_AddStringToObject(onejson_head, "version", "1.0");
- cJSON *onejson_params = cJSON_CreateObject();
- cJSON *onejson_temp = cJSON_CreateObject();
- cJSON_AddNumberToObject(onejson_temp, "value", data.temp);
- cJSON *onejson_humi = cJSON_CreateObject();
- cJSON_AddNumberToObject(onejson_humi, "value", data.humi);
- cJSON *onejson_light = cJSON_CreateObject();
- cJSON_AddNumberToObject(onejson_light, "value", data.light);
- cJSON *onejson_ps = cJSON_CreateObject();
- cJSON_AddNumberToObject(onejson_ps, "value", data.ps);
- cJSON_AddItemToObject(onejson_params, "temp", onejson_temp);
- cJSON_AddItemToObject(onejson_params, "humi", onejson_humi);
- cJSON_AddItemToObject(onejson_params, "light", onejson_light);
- cJSON_AddItemToObject(onejson_params, "ps", onejson_ps);
- cJSON_AddItemToObject(onejson_head, "params", onejson_params);
- MQTTMessage message;
- message.qos = QOS1;
- message.retained = 0;
- message.payload = cJSON_Print(onejson_head);
- message.payloadlen = strlen(message.payload);
- /* 上报数据到OneOS Studio平台 */
- rc = MQTTPublish(&client, MQTT_POST_TOPIC, &message);
- if (rc != 0)
- {
- printf("MQTT Client publish error %d\n", rc);
- }
- printf("MQTT Client publish success!\n");
- free(message.payload);
- cJSON_Delete(onejson_head);
- }
- }
- }
- int main(void)
- {
- ......
- }
其中,MQTT设备属性上报的主题为$sys/{pid}/{device-name}/thing/property/post,关于MQTT通信主题的详细内容可以访问 MQTT通信主题[15] 进行学习。
编译代码并下载至万耦开发板中,当看到串口中输出MQTT Client publish success!时表示向OneNET Studio平台的物模型发布新的属性值成功。
打开OneNET Studio的设备详情页面,可以看到OneOS上报的AHT10温/湿度传感器和AP3216C光强/接近位置传感器的数据。
常见问题
1. 我没有ML302模组,如何使用其他模组来完成上述的示例?
OneOS 2021-11-22 08:49
yzw92 2021-11-21 10:37