当前位置:   article > 正文

《嵌入式 - 模块》RW007 WIFI模块联网与使用(二)

rw007

《嵌入式 - 模块》RW007 WIFI模块联网与使用(一)

4 DHT11获取温湿度

4.1 DHT11获取温湿度

RT-Thread提供了DHT11的驱动软件包,配置如下:

在这里插入图片描述

DHT11默认使用的 GPIO是 GPIOB_12。当然这是可以修改的,在 dht11_sample.c 中修改以下代码:

#define DHT11_DATA_PIN    GET_PIN(E, 0)
  • 1

笔者这里使用PE0。

程序编译下载到板卡后,会在串口中每 1s 打印一次温湿度数据。

在这里插入图片描述

既然驱动有现成的,那么只需要写个相应的应用代码即可。后文需要将得到温湿度值上传至云端,应用部分的代码如下:

#include "dht11_thread.h"

/* Modify this pin according to the actual wiring situation */
#define DHT11_DATA_PIN    GET_PIN(E, 0)

rt_dht11 dht11;

static void read_temp_entry(void *parameter)
{
    rt_device_t dev = RT_NULL;
    struct rt_sensor_data sensor_data;
    rt_size_t res;
    rt_uint8_t get_data_freq = 1; /* 1Hz */

    dev = rt_device_find("temp_dht11");
    if (dev == RT_NULL)
    {
        return;
    }

    if (rt_device_open(dev, RT_DEVICE_FLAG_RDWR) != RT_EOK)
    {
        rt_kprintf("open device failed!\n");
        return;
    }

    rt_device_control(dev, RT_SENSOR_CTRL_SET_ODR, (void *)(&get_data_freq));

    while (1)
    {
        res = rt_device_read(dev, 0, &sensor_data, 1);

        if (res != 1)
        {
            rt_kprintf("read data failed! result is %d\n", res);
            rt_device_close(dev);
            return;
        }
        else
        {
            if (sensor_data.data.temp >= 0)
            {
                dht11.temp = (sensor_data.data.temp & 0xffff) >> 0;      // get temp
                dht11.humi = (sensor_data.data.temp & 0xffff0000) >> 16; // get humi
                rt_kprintf("temp:%d, humi:%d\n" ,dht11.temp, dht11.humi);
            }
        }

        rt_thread_delay(1000);
    }
}

static int dht11_thread(void)
{
    rt_thread_t dht11_thread;

    dht11_thread = rt_thread_create("dht_tem",
                                     read_temp_entry,
                                     RT_NULL,
                                     1024,
                                     RT_THREAD_PRIORITY_MAX / 2,
                                     20);
    if (dht11_thread != RT_NULL)
    {
        rt_thread_startup(dht11_thread);
    }

    return RT_EOK;
}

/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(dht11_thread, dht11 thread);

static int rt_hw_dht11_port(void)
{
    struct rt_sensor_config cfg;
    
    cfg.intf.user_data = (void *)DHT11_DATA_PIN;
    rt_hw_dht11_init("dht11", &cfg);

    return RT_EOK;
}
INIT_COMPONENT_EXPORT(rt_hw_dht11_port);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83

4.2 DHT11总结

RT-Thread包含DHT11驱动,这里就没有去造轮子了,但是作为学习者,还是要去了解DHT11的原理及具体实现。这部分内容在笔者博客的STM32系列的外设篇已详细阐述,下面就DHT11驱动在RTT中的实现做个总结。

DHT11是采用单总线通讯的传感器,有的设备没有硬件单总线,DHT11的支持包采用 GPIO 模拟单总线时序。DHT11 的一次完整读时序需要 20ms,时间过长,故无法使用关中断或者关调度的方式实现独占 CPU 以保证时序完整正确。因此可能出现读取数据失败的情况。

DHT11的典型电路如下图所示。

在这里插入图片描述

DATA 用于微处理器与 DHT11之间的通讯和同步,采用单总线数据格式,一次通讯时间4ms左右,数据分小数部分和整数部分,具体格式在下面说明,当前小数部分用于以后扩展,现读出为零.操作流程如下:
一次完整的数据传输为40bit,高位先出。

数据格式:8bit湿度整数数据+8bit湿度小数数据
+8bi温度整数数据+8bit温度小数数据
+8bit校验和

数据传送正确时校验和数据等于“8bit湿度整数数据+8bit湿度小数数据+8bi温度整数数据+8bit温度小数数据” 所得结果的末8位。

用户MCU发送一次开始信号后,DHT11从低功耗模式转换到高速模式,等待主机开始信号结束后,DHT11发送响应信号,送出40bit的数据,并触发一次信号采集用户可选择读取部分数据。从模式下,DHT11接收到开始信号触发一次温湿度采集,如果没有接收到主机发送开始信号,DHT11不会主动进行温湿度采集.采集数据后转换到低速模式。通讯过程如下图所示:

在这里插入图片描述

熟悉RT-Thread系统都知道,RT-Thread将各个模块进行抽象,为上层提供统一的操作接口,依次提高上层代码的可重用性,自然也提高了应用开发效率。RT-Thread的驱动框架:

在这里插入图片描述

应用程序通过 I/O 设备管理接口获得正确的设备驱动,然后通过这个设备驱动与底层 I/O 硬件设备进行数据(或控制)交互。

I/O设备模型框架位于硬件和应用程序之间,共分成三层,从上到下分别是 I/O 设备管理层、设备驱动框架层、设备驱动层。

  • I/O设备管理层实现了对设备驱动程序的封装。应用程序通过 I/O 设备层提供的标准接口访问底层设备,设备驱动程序的升级、更替不会对上层应用产生影响。这种方式使得设备的硬件操作相关的代码能够独立于应用程序而存在,双方只需关注各自的功能实现,从而降低了代码的耦合性、复杂性,提高了系统的可靠性。

  • 设备驱动框架层是对同类硬件设备驱动的抽象,将不同厂家的同类硬件设备驱动中相同的部分抽取出来,将不同部分留出接口,由驱动程序实现。

  • 设备驱动层是一组驱使硬件设备工作的程序,实现访问硬件设备的功能。它负责创建和注册 I/O 设备,对于操作逻辑简单的设备,可以不经过设备驱动框架层,直接将设备注册到 I/O 设备管理器中。
    更加详细的内容请参看RT-Thread官方手册。

这里主要讲解Sensor驱动,RT-Thread将各个Sensor进行抽象,将不同厂商的Sensor合并为Sensor设备,从而提高了代码的重用性,提高开发效率。既然是总结,这里就只说重点,先看看DHT11设备驱动的时序图:

在这里插入图片描述

传感器数据接收和发送数据的模式分为 3 种:中断模式、轮询模式、FIFO 模式。在使用的时候,这 3 种模式只能选其一,若传感器的打开参数 oflags 没有指定使用中断模式或者 FIFO 模式,则默认使用轮询模式。

oflags 参数支持下列参数:

#define RT_DEVICE_FLAG_RDONLY       0x001     /* 标准设备的只读模式,对应传感器的轮询模式 */
#define RT_DEVICE_FLAG_INT_RX       0x100     /* 中断接收模式 */
#define RT_DEVICE_FLAG_FIFO_RX      0x200     /* FIFO 接收模式 */
  • 1
  • 2
  • 3

DHT11设备比较简单,采用的是轮询模式,其设备注册函数如下:

result = rt_hw_sensor_register(sensor, name, RT_DEVICE_FLAG_RDONLY, RT_NULL);
  • 1

硬件初始化如下:

static int rt_hw_dht11_port(void)
{
    struct rt_sensor_config cfg;
    
    cfg.intf.user_data = (void *)DHT11_DATA_PIN;
    rt_hw_dht11_init("dht11", &cfg);

    return RT_EOK;
}
INIT_COMPONENT_EXPORT(rt_hw_dht11_port);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

注意INIT_COMPONENT_EXPORT表示组件初始化,初始化顺序为4。
好了,DHT11就讲到这里了。

自动初始化

5数据上传到OneNET

5.1 OneNET简介

OneNET 平台是中国移动基于物联网产业打造的生态平台,具有高并发可用、多协议接入、丰富 API 支持、数据安全存储、快速应用孵化等特点,同时,OneNET 平台还提供全方位支撑,加速用户产品的开发速度。

OneNET平台是一个基于物联网产业特点打造的生态环境,可以适配各种网络环境和协议类型,现在支持的协议有LWM2M(NB-IOT)、EDP、MQTT、HTTP、MODBUS、JT\T808、TCP透传、RGMP等。用户可以根据不同的应用场景选择不同的接入协议。

OneNET 软件包是 RT-Thread 系统针对 OneNET 平台连接的适配,通过这个组件包可以让设备在 RT-Thread 上非常方便的连接 OneNet 平台,完成数据的发送、接收、设备的注册和控制等功能。

OneNET 软件包数据的上传和命令的接收是基于 MQTT 实现的,OneNET 的初始化其实就是 MQTT 客户端的初始化,初始化完成后,MQTT 客户端会自动连接 OneNET 平台。数据的上传其实就是往特定的 topic 发布消息。当服务器有命令或者响应需要下发时,会将消息推送给设备。

获取数据流、数据点,发布命令则是基于 HTTP Client 实现的,通过 POST 或 GET 将相应的请求发送给 OneNET 平台,OneNET 将对应的数据返回,这样,我们就能在网页上或者手机 APP 上看到设备上传的数据了。

下图是应用显示设备上传数据的流程图:

在这里插入图片描述

下图是应用下发命令给设备的流程图

在这里插入图片描述

5.2准备工作

在 OneNET 云上注册账号

设备接入 OneNET 云之前,需要在平台注册用户账号,OneNET 云平台地址:
https://open.iot.10086.cn

创建产品
账号注册登录成功后,点击开发者中心进入开发者中心界面;

在这里插入图片描述

点击创建产品,输入产品基本参数,如下图所示:

在这里插入图片描述

产品创建成功之后,可以在开发者中心的公开协议产品中找到刚刚创建的产品,点击产品名,可以看到产品的基础信息(如产品ID,接入协议,创建时间,产品 APIkey 等,后面有用):

在这里插入图片描述

接入设备
在开发者中心左侧设备管理中点击添加设备按钮添加设备。

在这里插入图片描述

设备名称我们填入Test。鉴权信息是为了区分每一个不同的设备,如果创建了多个设备,要确保每个设备的鉴权信息都不一样,我们这里填入202106152023,填完之后点击接入设备。

在这里插入图片描述

添加 APIkey

接入设备之后,可以看到设备列表的界面多了一个设备,设备的右边有一些操作设备的按钮,点击查看详情按钮。

在这里插入图片描述

此设备的相关信息就都显示出来了,比如:设备 ID、鉴权信息、设备 APIkey,这些信息需要记下,在ENV配置时会用到。

点击按钮添加 APIkey,APIKey 的名称一般和设备相关联,我们这里填入Test_APIKey,关联设备默认为我们刚刚创建的设备Test。

在这里插入图片描述

到此,云端的操作暂时就完成了。

5.3开启OneNET软件包

打开 RT-Thread Settings,开启 onenet 软件包,勾选onenet 软件包后,进入 onenet 软件包的配置菜单按下图所示配置,里面的信息依据自己的产品和设备的实际情况填写。

在这里插入图片描述

【配置说明】
Enable OneNET sample :开启 OneNET 示例代码
Enable support MQTT protocol :开启 MQTT 协议连接 OneNET 支持
Enable OneNET automatic register device :开启 OneNET 自动注册设备功能
产品ID :配置云端创建产品时获取的产品ID
主/生产 Apikey :配置云端创建产品时获取的产品APIKey
在这里插入图片描述

设备ID :配置云端创建设备时获取的设备ID
身份验证信息 :配置云端创建产品时 用户自定义的鉴权信息 (每个产品的每个设备唯一)
API密钥 :配置云端创建设备时获取的 APIkey

在这里插入图片描述

5.4使用 OneNET

生成工程后,我们可以在工程的 onenet 目录下看到onenet_sample.c文件,该文件是 OneNET 软件包的示例展示,主要是展示用户如何使用 OneNET 软件包上传数据和接收命令。

在使用 OneNET 软件包之前必须要先调用onenet_mqtt_init这个命令进行初始化,初始化完成后设备会自动连接 OneNET 平台。

msh />onenet_mqtt_init
[D/onenet.mqtt] Enter mqtt_connect_callback!
[D/[mqtt] ] ipv4 address port: 6002
[D/[mqtt] ] HOST = '183.230.40.39'
[I/ONENET] RT-Thread OneNET package(V1.0.0) initialize success.
msh />[I/[mqtt] ] MQTT server connect success
[D/ onenet.mqtt] Enter mqtt_online_callback!
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在这里插入图片描述

5.4.1上传数据

初始化完成后,用户可以调用onenet_upload_cycle这个命令周期性的往云平台上传数据。输入这个命令后,设备会每隔 5s 向数据流 temperature 上传一个随机值。并将上传的数据打印到 shell 窗口。

msh />onenet_upload_cycle
[D/ onenet.sample] buffer : {"temperature":22}
  • 1
  • 2

在这里插入图片描述

我们打开 OneNET 平台,在设备列表界面选择刚添加的设备并进入数据流展示页面。

在这里插入图片描述

点击temperature数据流左边的小箭头显示数据流信息,我们就可以看到刚刚上传的数据了。

如果用户想往别的数据流发送信息,可以使用以下 API 往云平台上传数据。

onenet_mqtt_publish_digit onenet_mqtt_publish_string
命令格式如下所示:
onenet_mqtt_publish_digit 数据流名称 要上传的数据
onenet_mqtt_publish_string 数据流名称 要上传的字符串
  • 1
  • 2
  • 3
  • 4

输入命令后没有返回错误信息就表示上传成功。示例如下:

msh />onenet_mqtt_publish_digit test 1
msh />onenet_mqtt_publish_string test 1
msh />onenet_mqtt_publish_digit test 2
msh />onenet_mqtt_publish_string test 1
  • 1
  • 2
  • 3
  • 4

5.4.2接收数据

在初始化时,命令响应回调函数默认指向了空,想要接收命令,必须设置命令响应回调函数,在 shell 中输入命令onenet_set_cmd_rsp,就把示例文件里的命令响应回调函数挂载上了,这个响应函数在接收到命令后会把命令打印出来。

msh />onenet_set_cmd_rsp
  • 1

我们点击设备列表界面的下发命令按钮。

在这里插入图片描述

就可以在 shell 中看到云平台下发的命令了。

在这里插入图片描述

5.4.3温湿度数据上传

以上是如何开启和简单使用OneNET,接下来需要将前面获取的温湿度进行上传,修改onenet_sample.c的代码:

/* Includes*********************************************************************/
#include <stdlib.h>
#include <onenet.h>
#include "dht11_thread.h"

#define DBG_ENABLE
#define DBG_COLOR
#define DBG_SECTION_NAME    "onenet.sample"
#if ONENET_DEBUG
#define DBG_LEVEL           DBG_LOG
#else
#define DBG_LEVEL           DBG_INFO
#endif /* ONENET_DEBUG */

#include <rtdbg.h>

#ifdef FINSH_USING_MSH
#include <finsh.h>

extern rt_dht11 dht11;

/* upload temperature and humidity*/
static void onenet_upload_entry(void *parameter)
{
    while (1)
    {
        //dht11
        //temperature
        if (onenet_mqtt_upload_digit("temperature", dht11.temp) < 0)
        {
            LOG_E("upload has an error, please check the network");
            break;
        }
        else
        {
            LOG_D("buffer : {\"temperature\":%d}", dht11.temp);
        }

        rt_thread_delay(rt_tick_from_millisecond(2 * 1000));

        //humidity
        if (onenet_mqtt_upload_digit("humidity", dht11.humi) < 0)
        {
            LOG_E("upload has an error, please check the network");
            break;
        }
        else
        {
            LOG_D("buffer : {\"humidity\":%d}", dht11.humi);
        }

        rt_thread_delay(rt_tick_from_millisecond(5 * 1000));
    }
}

int onenet_upload_cycle(void)
{
    rt_thread_t tid;

    tid = rt_thread_create("onenet_send",
                           onenet_upload_entry,
                           RT_NULL,
                           2 * 1024,
                           RT_THREAD_PRIORITY_MAX / 3 - 1,
                           5);
    if (tid)
    {
        rt_thread_startup(tid);
    }

    return 0;
}
MSH_CMD_EXPORT(onenet_upload_cycle, send data to OneNET cloud cycle);

int onenet_publish_digit(int argc, char **argv)
{
    if (argc != 3)
    {
        LOG_E("onenet_publish [datastream_id]  [value]  - mqtt pulish digit data to OneNET.");
        return -1;
    }

    if (onenet_mqtt_upload_digit(argv[1], atoi(argv[2])) < 0)
    {
        LOG_E("upload digit data has an error!\n");
    }

    return 0;
}
MSH_CMD_EXPORT_ALIAS(onenet_publish_digit, onenet_mqtt_publish_digit, send digit data to onenet cloud);

int onenet_publish_string(int argc, char **argv)
{
    if (argc != 3)
    {
        LOG_E("onenet_publish [datastream_id]  [string]  - mqtt pulish string data to OneNET.");
        return -1;
    }

    if (onenet_mqtt_upload_string(argv[1], argv[2]) < 0)
    {
        LOG_E("upload string has an error!\n");
    }

    return 0;
}
MSH_CMD_EXPORT_ALIAS(onenet_publish_string, onenet_mqtt_publish_string, send string data to onenet cloud);

/* onenet mqtt command response callback function */
static void onenet_cmd_rsp_cb(uint8_t *recv_data, size_t recv_size, uint8_t **resp_data, size_t *resp_size)
{
    char res_buf[] = { "cmd is received!\n" };

    LOG_D("recv data is %.*s\n", recv_size, recv_data);

    /* user have to malloc memory for response data */
    *resp_data = (uint8_t *) ONENET_MALLOC(strlen(res_buf));

    strncpy((char *)*resp_data, res_buf, strlen(res_buf));

    *resp_size = strlen(res_buf);
}

/* set the onenet mqtt command response callback function */
int onenet_set_cmd_rsp(int argc, char **argv)
{
    onenet_set_cmd_rsp_cb(onenet_cmd_rsp_cb);
    return 0;
}
MSH_CMD_EXPORT(onenet_set_cmd_rsp, set cmd response function);

#endif /* FINSH_USING_MSH */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132

主要添加了自动初始化OneNET,修改onenet_upload_entry()的内容,添加DHT11温湿度获取的功能。
值得注意的是,联网后才进行OneNET初始化,还可以优化的地方就是配置OneNET成自动化,在初始化中加入网络是否连接的判断,这样就不可不使用终端来打开OneNET,当然啦,上传数据也是如此。另外在上传每条记录后,需要有一定的延时,不然后面的信息上传不成功。

打开设备后,现象如下:

在这里插入图片描述

当然啦,最后调试成功后关闭打印信息,云端则可看到上传的数据。

5.5 OneNET创建应用

点击左侧 【应用管理】 然后点击 【创建应用】。

在这里插入图片描述

然后输入应用名称并点击 【创建】。

在这里插入图片描述

在弹出的窗口中编辑应用,可以拖动左侧元件库中的元件到中间的显示区域

在这里插入图片描述

点击要设置的元件可以在右侧的属性和样式 窗口改变元件的属性。

在这里插入图片描述

最后的效果如下所示:

在这里插入图片描述

当然啦,有兴趣的可以自己慢慢慢去美界面。还可以自己通过温湿度模块获取温湿度传到云端。

6总结

最后,我们来总结下这个项目,整个过程很简单,数据采集,联网,数据上传,当然数据采集和联网可以并行,当联网成功后就可以进行数据传输。

在这里插入图片描述

笔者简单起见,这里就在网关上接个传感器来获得环境信息,如果读者朋友有兴趣可以单独将环境监测的部分提取出来,使用单独的板子获得更多的环境信息,或者使用ZigBee/BLE等组网来监测更多的信息和区域。

网关部分,使用STM32F746一般能满足要求,这里只是简单的传输数据,如果有其他需求,可考虑换用其他网关。

本文使用的云服务是OneNET,当然也可使用其他云服务,阿里云也不错,资料也比较多。

好了,这个项目就到这里了,有兴趣的,一起来玩玩吧。




欢迎访问我的网站

BruceOu的哔哩哔哩
BruceOu的主页
BruceOu的博客
BruceOu的CSDN博客
BruceOu的简书


欢迎订阅我的微信公众号

在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/390892
推荐阅读
相关标签
  

闽ICP备14008679号