RTT小师弟 · 2022年05月17日

NUC980开发板DIY项目大挑战:室内环境采集监测系统

本文由RT-Thread论坛用户@纯白酱原创发布:https://club.rt-thread.org/as...

项目描述

使用新唐公司的NUC980,开发一款室内环境监测平台。采集端通常位于家庭中的室内,采集传感器数据,如温湿度数据,光照数据,空气质量数据等,读取完成后,打包成json格式的数据,通过以太网,使用http post方式传输传感器数据。

设备清单

主控板:NUC980-IOT
传感器扩展板:板载多种传感器,基于立创EDA制作,已开源,开源地址为:立创EDA-传感器扩展板
服务器:基于腾讯云搭建的一款云服务器,运行的是Windows server 2019,已安装thingsboard开源物联网云平台。

传感器扩展板简介

定位模块:基于华大北斗的TAU1202,默认波特率为115200,双频定位,亚米级定位,定位效果优异。

光照度:基于vishy的vcnl4040

温湿度:基于盛思锐的SHT30

二氧化碳浓度/空气质量指数(TVOC):基于盛思锐的SGP30

PM2.5:基于攀藤科技的PMS7003

甲醛:基于达特的WS-K-S甲醛传感器模组

大气压强:基于歌尔电子的SPL06-007(可以等效替代SPL06-001)

姿态:基于invensense的mpu-6050

重要提示

1、服务器部分,运行的thingsboard社区版,可以自用/商用,本次是运行在腾讯云的服务器上的,也可以运行在树莓派等嵌入式linux平台上,方便用户管理传感器数据,确保传感器数据不流通至外网。
2、传感器扩展板支持多种传感器,通信接口为I2C和串口。本次只用到I2C接口,且I2C接口的姿态传感器并未使用(因为感觉室内不需要监测自身的姿态数据)。串口接口的传感器并未使用。

测试截图

QQ图片20220515164301.png
QQ图片20220515165127.png

开发流程

采集端开发流程

一、RT-Thread Studio下载并安装

https://www.rt-thread.org/page/studio.html中,下载并安装RT-Thread Studio,并提前注册好RT-Thread账号,并在安装好后登录RT-Thread Studio

二、安装开发板资源包

1、打开RT-Thread Studioccb65c3e1812a315b5b758b98cb415ca.png

2、进入主页面后,点击SDK Manager,可以安装本次活动的开发板NUC980-IOT的支持包
b32599090bbc15745d25f30b0cbb7e92.png

3、拖动右侧的滑块到下面,选择NK-980IOT,安装BSP资源包
e403b5cedb9886d4eabbe28bbb45d23b.png.webp

4、使用两根microusb数据线,分别连接两根USB接口(在左上方),将拨码开关均调节至off模式,即boot from usb模式,此模式可以下载程序用,例如下载到DDR中直接运行程序,方便程序调试阶段,不间断更新程序。或者下载到板载SPI NAND中,并通过调节拨码开关均至ON,按图中最左侧RESET按键后,即可从SPI NAND中运行程序,即正常发出给客户时,运行程序的模式。
ff87923ab651bdfa1d5df371501f8528.png.webp

两个USB接口分别为虚拟串口和调试下载的功能,同时连接,方便观测调试数据和下载程序。

三、创建工程

选择文件-新建-RT-THhread项目,创建基于开发板资源包的工程
2e85b76b95d566af314439efb0dc7db8.png.webp

点击基于开发板,并选择开发板为NK-980IOT,自定义取名工程名称,注意不要与现有的工程名称重复,填写完毕后,点击完成,并等待IDE创建工程,约半分钟到两分钟左右,具体时间由开发者的电脑性能决定。
337f404df2a38bb9eff6ca68e656e4f6.png.webp

四、编译程序

创建好工程后,双击project name/application/main.c,可以看到main.c中有一个点灯例程。
c55f39c01884295cc05facf5e4fb94d5.png.webp

图中定义了板载的三个LED对应的引脚,其中,PG15引脚对应板载的绿色LED灯,但是PG15引脚上电默认为JTAG功能,并非普通GPIO,需要在project name/board/nu_pin_init.c文件中,初始化该引脚,系统会自动调用该函数
1c1ee4aafa4f5618af33392b5c741af2.png.webp

初始化后,板载的三个LED灯则均能正常闪烁
ec1ee4499537746975d07aa2cf8f97bb.png.webp

点击左上角的编译按钮,即可编译好程序,并在project name/Debug目录下生成rtthread.bin文件,初始demo工程,通常编译用时在一分钟左右,时间由电脑CPU性能决定。
f27678dbb08d0a3e14d1d542a0b8e7d3.png.webp

五、下载程序

下载NU_Wrtier软件,链接:https://gitee.com/OpenNuvoton/NUC980_NuCWriter.git,可以git clone该仓库,并在NUC980_NuCWriter/ Release / Win64中运行。

下载好后,运行软件,并参考https://github.com/RT-Thread/rt-thread/tree/master/bsp/nuvoton/nk-rtu980,readme.md中,下载程序到DDR的内容。

使用 NuWriter 将 rtthread.bin 下载到 SDRAM 中,然后运行它。

选择类型:DDR/SRAM << Press Re-Connect >> 选择文件:指定您的 rtthread.bin 文件(在project name/Debug目录下)。 执行地址:0x0 选项:下载并运行 << Press Download >> Enjoy!!!
2770f284faf0d4e873f89965d02feb59.png.webp

六、正式开发程序

根据本次项目需求,确定需要使用的传感器驱动程序和设备驱动程序和数据处理驱动程序,目录如下:

序号名称功能是否已适配软件包?
1温湿度传感器通过I2C,读取温湿度传感器数据
2光照度传感器通过I2C,读取光照度传感器数据否,需要自行根据编写驱动程序
3空气质量传感器通过I2C,读取空气质量传感器数据
4CJSON将读取出的各项传感器数据处理为JSON格式
5以太网驱动通过以太网,传输数据到指定地址
6HTTP POST基于TCP通信,通过HTTP协议,POST方法,向指定地址发送处理好的传感器数据(JSON格式)

根据上述分析,分别添加各项软件包:

双击RT-Thread Setting
74b24ad981258b291280334b70535803.png
添加如图的软件包
e220f0b2ccb53bc8d79507e98674cbd8.png
在硬件栏中使能传感器通信接口I2C2
d3739bda2bce5afd6d8498d9aae47d6a.png.webp
使能完毕后,在main.c中添加初始化sgp30的相关函数

#define SGP30_I2C_BUS_NAME       "i2c2"
#define SGP30_I2C_ADDRESS        0x58

static int rt_hw_sgp30_port(void)
{
    struct rt_sensor_config cfg;

    cfg.intf.type = RT_SENSOR_INTF_I2C;
    cfg.intf.dev_name = SGP30_I2C_BUS_NAME;
    cfg.intf.user_data = (void *) SGP30_I2C_ADDRESS;
    rt_hw_sgp30_init("sg3", &cfg);

    return RT_EOK;
}
INIT_COMPONENT_EXPORT(rt_hw_sgp30_port);

根据论坛,及官方文档,分别调用软件包,并读取传感器数据。由于vcnl4040光照度传感器,并未有软件包适配,我们根据RT-Thread统一的I2C通信驱动,编写读取程序:

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2021-07-09     coolwhite       the first version
 */
#include <rtthread.h>
#define DBG_ENABLE
#define DBG_LEVEL DBG_LOG
#define DBG_SECTION_NAME  "VCNL4040"
#define DBG_COLOR
#include <rtdevice.h>
#include <stdio.h>
#include <ulog.h>
#include <rtdbg.h>



#define HW_ADR 0x60
rt_uint32_t test;
rt_uint32_t Illuminance_als;
static struct rt_i2c_bus_device *i2c_bus = RT_NULL; /* I2C总线设备句柄 */
rt_uint8_t l;
rt_uint8_t h;
static rt_err_t vcnl4040_write(struct rt_i2c_bus_device *bus, uint8_t hwadr, uint8_t reg, rt_uint8_t *data)
{
    rt_uint8_t buf[3];
    struct rt_i2c_msg msgs;
    rt_uint32_t buf_size = 1;
    buf[0] = reg; //cmd
    if (data != RT_NULL)
    {
        buf[1] = data[0];
        buf[2] = data[1];
        buf_size = 3;
    }

    msgs.addr = hwadr;
    msgs.flags = RT_I2C_WR;
    msgs.buf = buf;
    msgs.len = buf_size;

    /* 调用I2C设备接口传输数据 */
    if (rt_i2c_transfer(bus, &msgs, 1) == 1)
    {
        return RT_EOK;
    }
    else
    {
        return -RT_ERROR;
    }
}
rt_err_t read_regs(struct rt_i2c_bus_device *bus, uint8_t reg, rt_uint8_t *data)
{
    struct rt_i2c_msg msgs[2];
    rt_int8_t res = 0;
    msgs[0].addr = HW_ADR; /* Slave address */
    msgs[0].flags = RT_I2C_WR; /* Write flag */
    msgs[0].buf = &reg; /* Slave register address */
    msgs[0].len = 1; /* Number of bytes sent */

    msgs[1].addr = HW_ADR; /* Slave address */
    msgs[1].flags = RT_I2C_RD; /* Read flag */
    msgs[1].buf = data; /* Read data pointer */
    msgs[1].len = 2; /* Number of bytes read */

    if (rt_i2c_transfer((struct rt_i2c_bus_device *) bus, msgs, 2) == 2)
    {
        res = RT_EOK;
    }
    else
    {
        res = -RT_ERROR;
    }
}
void vcnl4040_init(const char *name)
{
    /* 查找I2C总线设备,获取I2C总线设备句柄 */
    i2c_bus = (struct rt_i2c_bus_device *) rt_device_find(name);

    if (i2c_bus == RT_NULL)
    {
        rt_kprintf("can't find %s device!\n", name);
    }
    else
    {
        rt_uint8_t cmd[2];
        cmd[0] = 0x00;
        cmd[1] = 0x00;
        vcnl4040_write(i2c_bus, HW_ADR, 0x00, cmd);
        rt_kprintf("vncl4040 init sussess at %s", name);
        rt_thread_mdelay(500);
    }
}
void test_vcnl4040()
{
    rt_uint8_t data[2];
    data[0] = 0x00;
    data[1] = 0x00;
    rt_uint8_t data1[2];
    data1[0] = 0x00;
    data1[1] = 0x00;
    rt_uint8_t als_l;
    rt_uint8_t als_h;
    read_regs(i2c_bus, 0x09, data1);
    l = data[0];
    h = data[1];
    als_l = data1[0];
    als_h = data1[1];
    rt_uint16_t als_data;
    als_data = als_h;
    als_data <<= 8;
    als_data += als_l;
    Illuminance_als = als_data / 10;
    LOG_I("Illuminance_als=%d\n",Illuminance_als);
}

在application文件夹中,创建一个cloud_send.c文件,在其中,调用cjson软件包,并处理传感器数据为JSON格式:

    char *thingsboard_sensor_send;
    cJSON *csensor = cJSON_CreateObject();
    cJSON *cbaro = cJSON_CreateNumber(baro);
    cJSON *cco2concentration = cJSON_CreateNumber(co2concentration);
    cJSON *ctvocconcentration = cJSON_CreateNumber(tvocconcentration);
    cJSON *chumidity = cJSON_CreateNumber(humi1);
    cJSON *ctemperature = cJSON_CreateNumber(temp1);
    cJSON *cIlluminance_als = cJSON_CreateNumber(Illuminance_als);
    cJSON_AddItemToObject(csensor, "Illuminance_als", cIlluminance_als);
    cJSON_AddItemToObject(csensor, "baro", cbaro);
    cJSON_AddItemToObject(csensor, "co2concentration", cco2concentration);
    cJSON_AddItemToObject(csensor, "tvocconcentration", ctvocconcentration);
    cJSON_AddItemToObject(csensor, "humidity", chumidity);
    cJSON_AddItemToObject(csensor, "temperature", ctemperature);
    thingsboard_sensor_send = cJSON_Print(csensor);

处理好传感器数据后,调用webclient软件包,发送传感器数据到指定地址:

/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2022-05-05     11618       the first version
 */
/*
 * Copyright (c) 2006-2022, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2018-08-03    chenyong      the first version
 */

#include <string.h>

#include <rtthread.h>
#include <webclient.h>
#include <cJson.h>
#define POST_RESP_BUFSZ                10240
#define POST_HEADER_BUFSZ              10240
#define POST_LOCAL_URI                 "http://124.220.198.111:8080/api/v1/QLYe1sufLVvb2e2K3lFb/telemetry"
extern double temp1;
extern double humi1;
extern double baro;
extern int32_t co2concentration;
extern int32_t tvocconcentration;
extern rt_uint32_t Illuminance_als;
/* send HTTP POST request by common request interface, it used to receive longer data */
static int webclient_post_comm(const char *uri, const void *post_data, size_t data_len)
{
    struct webclient_session* session = RT_NULL;
    unsigned char *buffer = RT_NULL;
    int index, ret = 0;
    int bytes_read, resp_status;

    buffer = (unsigned char *) web_malloc(POST_RESP_BUFSZ);
    if (buffer == RT_NULL)
    {
        rt_kprintf("no memory for receive response buffer.\n");
        ret = -RT_ENOMEM;
        goto __exit;
    }

    /* create webclient session and set header response size */
    session = webclient_session_create(POST_HEADER_BUFSZ);
    if (session == RT_NULL)
    {
        ret = -RT_ENOMEM;
        goto __exit;
    }

    /* build header for upload */
    webclient_header_fields_add(session, "Content-Length: %d\r\n", strlen(post_data));
    webclient_header_fields_add(session, "Content-Type: application/octet-stream\r\n");

    /* send POST request by default header */
    if ((resp_status = webclient_post(session, uri, post_data, data_len)) != 200)
    {
        rt_kprintf("webclient POST request failed, response(%d) error.\n", resp_status);
        ret = -RT_ERROR;
        session == RT_NULL;
        goto __exit;
    }
    __exit: if (session)
    {
        webclient_close(session);
        session == RT_NULL;
    }

    if (buffer)
    {
        web_free(buffer);
    }
    session == RT_NULL;
    return ret;
}

/* send HTTP POST request by simplify request interface, it used to received shorter data */
static int webclient_post_smpl(const char *uri, const char *post_data, size_t data_len)
{
    char *response = RT_NULL;
    char *header = RT_NULL;
    size_t resp_len = 0;
    int index = 0;

    webclient_request_header_add(&header, "Content-Length: %d\r\n", strlen(post_data));
    webclient_request_header_add(&header, "Content-Type: application/octet-stream\r\n");

    if (webclient_request(uri, header, post_data, data_len, (void **) &response, &resp_len) < 0)
    {
        rt_kprintf("webclient send post request failed.");
        web_free(header);
        return -RT_ERROR;
    }

    rt_kprintf("webclient send post request by simplify request interface.\n");

    if (header)
    {
        web_free(header);
    }

    if (response)
    {
        web_free(response);
    }

    return 0;
}

void thingsboard_post_test()
{
    char *uri = RT_NULL;
    char *thingsboard_sensor_send;
    cJSON *csensor = cJSON_CreateObject();
    cJSON *cbaro = cJSON_CreateNumber(baro);
    cJSON *cco2concentration = cJSON_CreateNumber(co2concentration);
    cJSON *ctvocconcentration = cJSON_CreateNumber(tvocconcentration);
    cJSON *chumidity = cJSON_CreateNumber(humi1);
    cJSON *ctemperature = cJSON_CreateNumber(temp1);
    cJSON *cIlluminance_als = cJSON_CreateNumber(Illuminance_als);
    cJSON_AddItemToObject(csensor, "Illuminance_als", cIlluminance_als);
    cJSON_AddItemToObject(csensor, "baro", cbaro);
    cJSON_AddItemToObject(csensor, "co2concentration", cco2concentration);
    cJSON_AddItemToObject(csensor, "tvocconcentration", ctvocconcentration);
    cJSON_AddItemToObject(csensor, "humidity", chumidity);
    cJSON_AddItemToObject(csensor, "temperature", ctemperature);
    thingsboard_sensor_send = cJSON_Print(csensor);

        uri = web_strdup(POST_LOCAL_URI);
        if (uri == RT_NULL)
        {
            rt_kprintf("no memory for create post request uri buffer.\n");

        }

        webclient_post_comm(uri, (void *) thingsboard_sensor_send, rt_strlen(thingsboard_sensor_send));









    if (uri)
    {
        web_free(uri);
    }
    cJSON_Delete(csensor);
    thingsboard_sensor_send=RT_NULL;
    rt_free(thingsboard_sensor_send);
    rt_thread_mdelay(1000);
}

#ifdef FINSH_USING_MSH
#include <finsh.h>
//MSH_CMD_EXPORT_ALIAS(thingsboard_post_test, thingsboard_post_test, webclient post request test.);
#endif /* FINSH_USING_MSH */

七、连接硬件并再次下载程序

将传感器扩展板与NK-980IOT开发板,使用杜邦线连接,并插上网线,使用USB数据线,连接电脑并下载程序,下载程序步骤可以参考上文,不再赘述。
下载完成后,采集端开发告一段落。

云平台开发流程

Thingsboard一款开源的云平台,官网地址为:https://thingsboard.io,用户可在官网,学习到thingsboard的下载(社区版),安装,与使用。

一、登录物联网云平台

登录thingsboard云平台(链接:http://124.220.198.111:8080)链接根据自己服务器部署情况,会有所不同。初学者建议部署在树莓派上,硬件成本较低,传感器数据不易流出外网。
a4769a5fd303068bb22d8e8c0350a32c.png
在登录页面,输入用户名和密码(需要以总管理员的身份创建)

二、创建设备

输入正确的用户名和密码后,登录,即可看到云平台的整体配置界面:
21de4fa15490ef0fd8da3bbea0aca11d.png
我们为了让采集端接入云平台通信,需要先创建设备,点击设备选项:
e286567dbc55197cd316e04d0ba70cb3.png
点击右上角的创建设备:
6a873061c5c1a50244bf8a292d3e5ce6.png
自定义名称,并直接点击添加

三、查看设备凭证

单击test_nuc980,选择复制设备访问令牌
0a42f11825fde292aff10bc16912a146.png
也可以点击管理凭据,手动复制访问令牌
91edd2ab55b97160c2b1c5d56597e104.png

四、在采集端中填写访问令牌

thingsboard支持多种通信协议,在社区版中,通常使用的是HTTP协议、MQTT协议、COAP协议,本次项目主要使用HTTP协议,其他协议请根据官方文档,自行学习。

通过HTTP协议上传传感器数据的通常API接口为:

http(s)://host:port/api/v1/$ACCESS_TOKEN/telemetry

其中,http(s)://host:port替换为通常你部署的服务器地址,可以是公网地址,也可以是内网地址。
$ACCESS_TOKEN即访问令牌,比如test_nuc980设备,对应的访问令牌为:yUtSRivycEvXiamNMC2F

确定好传输地址后,在采集端工程:project name/application/cloud_send.c中,替换默认地址:
484394153bf430d464b219b61644d659.png.webp

五、设备遥测数据查看

烧录好采集端工程,并完成硬件接线后,即可在云平台查看传输的传感器数据
单击设备名称:
596bf0d455b333a6fe974fa333dfee2a.png
点击最新遥测数据:
f7c350ead6ccf6bbe907ee609e54baf4.png
可以看到采集端数据已经发送到云平台,根据最近更新时间,可以观测数据传输是否出现异常。

六、设备数据仪表盘配置

在最新遥测数据中查看,并不能直观的展示出设备数据的长时间情况下的变化趋势等,所以我们需要配置一个仪表盘,以便查看数据。
在主菜单中,单击仪表盘:
4f21da166d6f48ae0a4490c914f436c8.png
点击右上角的“+”,新建一个仪表盘:
a9597bab7fb6d09c6551b876affb538a.png
输入标题,即可添加仪表盘
cf9023b3b874ffb1a81691b97ebc3723.png.webp
添加完成后,点击打开仪表板:
7022cdbe44b8fcc26eb967c6f3fc11bf.png
点击右下角的功能键,并选择创建一个新的部件:
ee084c090709c818de2a28329e5325c3.png
为了更好的展示温湿度、光照度等数据的长时间变化趋势度,选择chart部件:
98f2c5fc4985ad767a253e9007089978.png.webp
选择时间序列图部件:
e75b1a8fcf5488271c84c087b60e8946.png.webp
单击添加数据源:
b4028729d3dcc778b85a33cb17d51202.png
配置实体别名,即绑定设备:
ce83c196db8643d5b82ca26fe8ff5d56.png.webp
配置完毕后,在键名处即可找到对应的传感器数据名称,键名主要由采集端定义,如需修改,请在采集端修改键名与对应的传感器数据:
ab8d5ce3f6d77c8b9250766b0a1ca243.png.webp
本次选择显示温度数据,即键名为temperature的数据,单击蓝色圆圈,可定制折线图的折线颜色:
710046c5ac9b7d8901435c3aa38e2222.png
48d8dc55f18d9d7066eccc5eadfc2408.png.webp
配置好喜欢的颜色后,点击设置,设置该折线图的名称,在此,我们将其命名为温度:
e007fb1c14d255a9bd46235d177f60be.png.webp
点击添加即可,并随后点击右下角的确认更改:
8af30c949654984e071f210676a0f84e.png
即可看到温度的数据变化曲线:
9b4c1f6ee0f1b287d761af3ca40ded2f.png
其他传感器数据的配置方式可以参照上文,以此类推。

七、查看更长时间的传感器数据变化曲线

单击右上角的时间按钮:
a7d5f1ee2a3e48dd9c4f2365d1fb6897.png
可以查看更多时间维度的传感器数据变化折线图(默认为1分钟):
d0c3c6f365ba2c7ed091277b02e0e84e.png.webp
配置好后,折线图即产生变化:
3cd4b96bca30d6c459105576710f41e0.png.webp

流程演示

采集端流程:
nuc980.png
云平台端流程:
thingsboard.png

视频演示

正在编辑,稍后上传

开源链接

链接: https://pan.baidu.com/s/1J_Lh... 提取码: 6sey 复制这段内容后打开百度网盘手机App,操作更方便哦
--来自百度网盘超级会员v5的分享

心得体会

NUC980芯片,之前并未关注过,通过本次活动,让我了解到RT-Thread在NUC980上的运行及其优势所在:开发便利,资源非常丰富(多路串口,多路USB等等),在疫情三年,芯片市场动荡的情况下,NUC980有望部分取代价格极度暴涨的STM32,在工业控制,数据采集等领域获得更大的市场。

备注

thingsboard是一款开源的物联网平台,本次活动所展示的云平台数据,在以下链接中可以看到:NUC980室内环境监测仪表盘,如果有大家有需要测试或者了解这款云平台,可以在文章评论区留言,我可以开放一定数量的测试账号,方便大家体验。

推荐阅读
关注数
8061
内容数
181
小而美的物联网操作系统,经过14年的累积发展,RT-Thread 已经拥有一个国内最大的嵌入式开源社区,同时被广泛应用于能源、车载、医疗、消费电子等多个行业,累积装机量超过4亿台,成为国人自主开发、国内最成熟稳定和装机量最大的开源 RTOS。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息