IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> RT-Thread官方的智能家居DIY的 学习笔记 -> 正文阅读

[嵌入式]RT-Thread官方的智能家居DIY的 学习笔记

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

本文是对学习 RT-Thread官方的智能家居DIY项目的总结,这里是基于RT-Thread Stduio开发的,利用stm32f103作为节点发送数据,用stm32f407作为网关上传数据。(这里只做了一个节点)


提示:以下是本篇文章正文内容,下面案例可供参考

一、节点发送数据

1.用ds18b20获取温度

1. 1添加 ds18b20软件包,其配置如下,示例代码可以不使能

在这里插入图片描述
添加了该软件包之后,传感器设备(Sensor)会自动勾选上,如下图所示在这里插入图片描述


1.2 阅读 ds18b20源码,了解认识 sensor 框架


在下图所示中,打开 sensor_dallas_ds18b20.c 文件
在这里插入图片描述
Sensor 设备的作用是:为上层提供统一的操作接口,提高上层代码的可重用性。sensor驱动框架的整体框架图
它为上层提供的是标准 device 接口open/close/read/write/control ,为底层驱动提供的是简单的 ops 接口:fetch_data/control。并且框架支持 module(模块),为底层存在耦合的传感器设备提供服务。

以下只讲解底层驱动 ops(operations:操作命令)接口
ops(操作函数)包含两个函数指针, 一个的作用是获取传感器数据(fetch_data),另一个的作用是通过控制命令控制传感器(control)。其定义如下

struct rt_sensor_ops
{
    rt_size_t (*fetch_data)(struct rt_sensor_device *sensor, void *buf, rt_size_t len);
    rt_err_t (*control)(struct rt_sensor_device *sensor, int cmd, void *arg);
};

ops 其实就是一个结构体,用来存放接口函数,其具体在本示例中应用如下

static struct rt_sensor_ops sensor_ops =
{
    ds18b20_fetch_data,
    ds18b20_control
};

ds18b20_fetch_data 函数的定义如下:

/**
  * @brief  sensor 框架中的ops接口的定义(结构体)成员的  fetch_data()作用是获取传感器数据
  * @param  sensor 支持3中方式,中断、轮询、FIFO,这里使用轮询的方式读取 ds18b20
  * @retval 返回温度数据 或者 0
  */
static rt_size_t ds18b20_fetch_data(struct rt_sensor_device *sensor, void *buf, rt_size_t len)
{
    RT_ASSERT(buf);

    if (sensor->config.mode == RT_SENSOR_MODE_POLLING)
    {
        return _ds18b20_polling_get_data(sensor, buf);
    }
    else
        return 0;
}

ds18b20_control 函数定义如下

/**
  * @brief  sensor 框架中的ops接口的定义(结构体)成员的  control() 作用是通过控制命令控制传感器
  * @param  由于 ds18b20 不支持 control
  * @retval 所以直接返回 RT_EOK
  */
static rt_err_t ds18b20_control(struct rt_sensor_device *sensor, int cmd, void *args)
{
    rt_err_t result = RT_EOK;

    return result;
}

有了以上的ops 接口,另外还需要注册一个 sensor 设备(任何设备都需要注册才能使用),这样上层才能找到这个传感器设备,进而进行控制。传感器设备注册如下所示:

/**
  * @brief 传感器设备注册
    * 完成 sensor 的 ops 的对接之后还要注册一个sensor设备,这样上层才能找到这个传感器设备,进而进行控制
  * @param rt_hw_ds18b20_init()
  * @retval RT_EOK or -RT_ERROR
  */
int rt_hw_ds18b20_init(const char *name, struct rt_sensor_config *cfg)
{
    rt_int8_t result;
    rt_sensor_t sensor_temp = RT_NULL;  /* 定义一个sensor 的结构体指针 */
    
    if (!ds18b20_init((rt_base_t)cfg->intf.user_data)) /* ds18b20硬件初始化,成功则返回0 */
    {
        /* 为结构体分配内存 */
        sensor_temp = rt_calloc(1, sizeof(struct rt_sensor_device));
        if (sensor_temp == RT_NULL)
            return -1;
/* sensor 初始化 前相关参数的配置 */
        sensor_temp->info.type       = RT_SENSOR_CLASS_TEMP;
        sensor_temp->info.vendor     = RT_SENSOR_VENDOR_DALLAS;
        sensor_temp->info.model      = "ds18b20";
        sensor_temp->info.unit       = RT_SENSOR_UNIT_DCELSIUS;
        sensor_temp->info.intf_type  = RT_SENSOR_INTF_ONEWIRE;
        sensor_temp->info.range_max  = SENSOR_TEMP_RANGE_MAX;
        sensor_temp->info.range_min  = SENSOR_TEMP_RANGE_MIN;
        sensor_temp->info.period_min = 5;

        rt_memcpy(&sensor_temp->config, cfg, sizeof(struct rt_sensor_config));
        sensor_temp->ops = &sensor_ops;
/* 注册传感器设备 */
        result = rt_hw_sensor_register(sensor_temp, name, RT_DEVICE_FLAG_RDONLY, RT_NULL);
        if (result != RT_EOK)
        {
            LOG_E("device register err code: %d", result);
            goto __exit;
        }

    }
    else
    {
        LOG_E("DS18B20 init failed! Please check the connection!");
        goto __exit;
    }
    
    return RT_EOK;
    
__exit:
    if (sensor_temp)
        rt_free(sensor_temp);
    return -RT_ERROR;     
}

调用 rt_hw_ds18b20_init() 函数即可完成硬件初始化,以及注册设备。

以上就是 ops 底层驱动所用的操作。

1.3 创建线程读取温度

注意:得先调用 rt_hw_ds18b20_init() 函数完成初始化,具体实现如下所示:

/* ds18b20 初始化 */
static int rt_hw_ds18b20_port(void)
{
    struct rt_sensor_config cfg;

    cfg.intf.user_data = (void *)DS18B20_DATA_PIN;  /* 把数据引脚存储到结构体 */
    rt_hw_ds18b20_init("ds18b20", &cfg);            /* 初始化 */

    return RT_EOK;
}
INIT_COMPONENT_EXPORT(rt_hw_ds18b20_port);

线程创建如下所示:

ds18b20_thread = rt_thread_create("18b20tem", read_temp_entry, "temp_ds18b20",
                                  640, RT_THREAD_PRIORITY_MAX / 2, 20);
if (ds18b20_thread != RT_NULL)
{
    rt_thread_startup(ds18b20_thread);
}   

“temp_ds18b20” 这个是线程入口函数中得参数,用于查找设备。
线程入口函数如下:

/* 线程入口函数:从 sensor 中获取温度数据并通过邮箱 发送给另外一个线程 */
static  void  read_temp_entry(void *parameter)
{
    struct  tmp_msg   msg;           /* 声明一个结构体,用来存储温度和时间戳 */
    rt_device_t  dev = RT_NULL;
    rt_size_t   res;

    dev = rt_device_find(parameter);    /* 查找设备,失败则返回退出*/
    if(dev == RT_NULL)
    {
        rt_kprintf("can't find device:%s\n",parameter);
        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 *)100);    /*设置输出频率: 100Hz */

    while(1)
    {
        res = rt_device_read(dev, 0, &sensor_data, 1);  /* 从 sensor 读取1个字节的数据 */
        if (res!=1)
        {
            rt_kprintf("read data failed! size is %d\n",res);
            rt_device_close(dev);
            return;
        }
        else
        {
            /* 申请一个内存块,若内存池,满了则挂起线程等待 */
     //       msg = rt_mp_alloc(tmp_msg_mp, RT_WAITING_FOREVER);
            msg.timestamp = sensor_data.timestamp;  /* 获取时间戳*/
            msg.int_value = sensor_data.data.temp;  /* 获取温度 */
   //         rt_mb_send(tmp_msg_mb, (rt_ubase_t)msg); /*发送邮件 */
            rt_mq_send(tmp_msg_mq, &msg, sizeof msg); /* 发送消息队列 */
          //  msg = NULL;
        }
        rt_thread_mdelay(500);      /* 该延时函数主要是为了阻塞线程,从而能进行调度*/

    }
}

2.用nrf24l01 发送数据

2.1 添加 nrf软件包,并开启相应的设备(用到SPI)

在这里插入图片描述
在这里插入图片描述
注意:这里的nrf24l01软件包,要选择 v1.0.0 版本

按照 board.h 中的 SPI CONFIG 部分内容提示,进行相应操作
在这里插入图片描述
以上只是注册了SPI总线设备,另外还需要 注册spi 从设备并挂载在 总线上,具体实现如下所示:

static int rt_hw_nrf24l01_init(void)
{
    __HAL_RCC_GPIOG_CLK_ENABLE();
    rt_hw_spi_device_attach("spi2", "spi20", GPIOG, GPIO_PIN_7);

    return RT_EOK;
}
INIT_COMPONENT_EXPORT(rt_hw_nrf24l01_init);

2.2 创建nrf24l01线程

创建线程:

nrf24l01_thread  = rt_thread_create("nrfsend", nrf24l01_send_entry, RT_NULL,
                                        1024, RT_THREAD_PRIORITY_MAX / 2, 20);
    if (nrf24l01_thread != RT_NULL)
    {
        rt_thread_startup(nrf24l01_thread);
    }

线程入口函数: 这里完成了 nrf24l01 的初始化,以及发送数据(发送给另外一个 nrf24l01,即所谓的网关)

static  void nrf24l01_send_entry(void *parameter)
{
    struct  tmp_msg  msg;
    struct  hal_nrf24l01_port_cfg   halcfg;
    nrf24_cfg_t cfg;
    uint8_t rbuf[32+1] = {0};
    uint8_t tbuf[32]= {0};


    nrf24_default_param(&cfg);
    halcfg.ce_pin = NRF24L01_CE_PIN;
    halcfg.spi_device_name = NRF24L01_SPI_DEVICE;
    cfg.role = ROLE_PTX;    //表示 发送模式
//  cfg.role = ROLE_PRX;   因为一开始用了这个,出现的问题是:只能发送一次,然后fish组件用不了

    cfg.ud = &halcfg;
    cfg.use_irq = 0;
    nrf24_init(&cfg);

    while(1)
    {
        rt_thread_mdelay(500);

        if (rt_mq_recv(tmp_msg_mq, &msg, sizeof msg, RT_WAITING_FOREVER) == RT_EOK)
        {
            if (msg.int_value >=0)
            {
                /* 把数据存储到tbuf */
                rt_sprintf((char *)tbuf, "%d,+%3d.%d",msg.timestamp,msg.int_value / 10,msg.int_value % 10);
            }
            else
            {
                rt_sprintf((char *)tbuf, "%d,-%2d.%d",msg.timestamp,msg.int_value / 10,msg.int_value % 10);
            }

            rt_kputs((char *)tbuf);
            rt_kputs("\n");
     //       rt_mp_free(msg); /* 释放内存*/
       //     msg = RT_NULL;   /* 请务必要做 */
        }
        if (nrf24_ptx_run(rbuf, tbuf, rt_strlen((char *)tbuf))<0)
        {
            rt_kputs("Send failed! >>> ");
        }

    }
}

以上就是节点的所有内容,源码下载
提取码:imad

二、网关接收数据并上传至ONENET

2.1 nrf24l01 节点接收数据

2.1.1 创建nrf24l01 线程,该线程用来接收数据并保存到 ringbuffer 后发送事件

  nrf24l01_thread  = rt_thread_create("nrfrecv", nrf24l01_receive_entry, RT_NULL,
                                        2048, RT_THREAD_PRIORITY_MAX / 2, 20);
    if (nrf24l01_thread != RT_NULL)
    {
        rt_thread_startup(nrf24l01_thread);
    }

2.1.2 线程入口函数

static void nrf24l01_receive_entry(void *parameter)
{
    struct  recvdata    buf;
    struct  recvdata    *buf_mp;
    static  char  str_data_p0[64];
    struct hal_nrf24l01_port_cfg halcfg;
    nrf24_cfg_t cfg;

    nrf24_default_param(&cfg);    // 先配置默认值
    halcfg.ce_pin = NRF24L01_CE_PIN;
    halcfg.spi_device_name = NRF24L01_SPI_DEVICE;
    cfg.role = ROLE_PRX;  //角色:接收数据
    cfg.ud = &halcfg;     // 用来传递底层的配置
    cfg.use_irq = 0;      // 是否使用中断
    nrf24_init(&cfg);    // 初始化

    while (1)
    {
        if (!rx_pipe_num_choose())
        {
            /* 通过sscanf 解析收到的数据 */
            if (sscanf((char *)RxBuf_P0,"%d,+%f",&buf.timestamp_p0,&buf.temperature_p0)!=2)
            {
                /* 通过sscanf 解析收到的数据 */
                if (sscanf((char *)RxBuf_P0,"%d,-%f",&buf.timestamp_p0,&buf.temperature_p0)!=2)
                {
                   buf.temperature_p0 = 0;
                   buf.timestamp_p0 = 0;
                }
                buf.temperature_p0 = -buf.temperature_p0;
            }
            if (0!=buf.temperature_p0)
            {
                sprintf(str_data_p0, "%d,%f\n", buf.timestamp_p0, buf.temperature_p0);
                /* 将数据存放到ringbuffer里 */
                rt_ringbuffer_put(recvdatabuf_p0, (rt_uint8_t *)str_data_p0, strlen(str_data_p0));
                /* 收到数据,并将数据存放到ringbuffer里后,才发送事件 */
                rt_event_send(recvdata_event, WRITE_EVENT_P0);
            }
            /* 申请一块内存 要是内存池满了,  就挂起等待 */
           buf_mp =rt_mp_alloc(tmp_msg_mp, RT_WAITING_FOREVER);
           buf_mp->temperature_p0 = buf.temperature_p0;
           buf_mp->timestamp_p0 = buf.timestamp_p0;
           rt_mb_send(tmp_msg_mb, (rt_ubase_t)buf_mp);
           buf_mp =NULL;
        }
        rt_thread_mdelay(500);
    }
}

2.2 往 sd card 写入数据

2.2.1添加 SDIO 、DFS、Fatfs 、Libc、POSIX

在这里插入图片描述
按照下图对 sdio 进行相应的配置

在这里插入图片描述
完成以上配置,则可以看到 " sd0 " 设备存在了

2.2.2 将文件系统挂载到 RT-Thread 的DFS 上

创建线程:

int stm32_sdcard_mount(void)
{
    rt_thread_t tid;    /* 定义线程句柄 */
    /* 创建线程 */
    tid = rt_thread_create("sd_mount", sd_mount, RT_NULL, 2048, RT_THREAD_PRIORITY_MAX - 2, 20);
    if (tid!=RT_NULL)
    {
        rt_thread_startup(tid);
    }
    else
    {
        LOG_E("create sd_mount thread err!");
    }
    return RT_EOK;
}
INIT_APP_EXPORT(stm32_sdcard_mount);

线程入口函数:

/* 线程入口函数:sd card 等待初始化  */
void  sd_mount(void *parameter)
{
    while(1)
    {
        rt_thread_mdelay(500);
        if (rt_device_find("sd0")!=RT_NULL)
        {
            if (dfs_mount("sd0", "/", "elm", 0, 0)==RT_EOK)
            {
                LOG_I("sd card mount to '/'");
                break;
            }
            else
            {
                LOG_W("sd card mount to '/' failed!");
            }
        }
    }
}

2.2.3 接收事件, 往sd 卡写入数据

创建线程:

DFS_thread_p0 = rt_thread_create("DFSsaveP0", save_recv_data_entry, RT_NULL,
                                2048, RT_THREAD_PRIORITY_MAX / 2 - 1, 20);
  if (DFS_thread_p0 != RT_NULL)
  {
      rt_thread_startup(DFS_thread_p0);
  }

线程入口函数:

static  void  save_recv_data_entry(void *parameter)
{
//    FILE  *recvdatafile_p0 =RT_NULL;
    rt_uint32_t     set;
    static  int  writebuffer[1024];
    rt_size_t   size;
    while(1)
    {
        /* 接收感兴趣的事件 WRIE_EVENT,以永久等待方式去接收 */
        if (rt_event_recv(recvdata_event, WRITE_EVENT_P0, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &set)!=RT_EOK)
        {
            continue;
        }

            /* 接收感兴趣的事件 WRIE_EVENT,以1000ms 超时方式去接收 */
            if (rt_event_recv(recvdata_event, WRITE_EVENT_P0, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, rt_tick_from_millisecond(1000), &set)==RT_EOK)
            {
                /* 判断写入的数据大小到没到所设置的 ringbuffer 的阈值*/
                if (rt_ringbuffer_data_len(recvdatabuf_p0) > THRESHOLD)
                {
                    /* 到阈值就直接写数据 */
                    recvdatafile_p0 = fopen("recvdata_p0.csv", "ab+");
                    if (recvdatafile_p0!=RT_NULL)
                    {
                        while(rt_ringbuffer_data_len(recvdatabuf_p0))
                        {
                            size = rt_ringbuffer_get(recvdatabuf_p0, (rt_uint8_t *)writebuffer, THRESHOLD);
                            fwrite(writebuffer, 1,size,recvdatafile_p0);
                        }
                        fclose(recvdatafile_p0);
                    }
                }
                /* 阈值没到就继续接收感兴趣的事件 WRITE_EVENT,以1000ms超时方式接收 */
                continue;
            }
            /* 100ms 到了,还没有收到感兴趣事件,这时候不管到没到阈值,直接写  */
            recvdatafile_p0 = fopen("recvdata_p0.csv", "ab+");
            if (recvdatafile_p0 !=RT_NULL)
            {
                while(rt_ringbuffer_data_len(recvdatabuf_p0))
                {
                    size = rt_ringbuffer_get(recvdatabuf_p0, (rt_uint8_t *)writebuffer, THRESHOLD);
                    fwrite(writebuffer, 1, size, recvdatafile_p0);
                }
                fclose(recvdatafile_p0);
            }
    }

}

定个固定时间,计时,如果时间一到,此时数据还没写满 ringbuffer 的阈值,这时候就不管数据到没到阈值了,直接将 ringbuffer 里的数据全部写入文件中去。要实现这个思路需要搭配事件集 (event) 使用

2.3 上传数据到ONENET

2.3.1 开启AT和esp8266,并配置ONENET软件包中的相关参数

在这里插入图片描述
如上图所示,添加了 onenet 软件包后,会自动添加 另外3个软件包。
此外,需要填写 esp8266的相关信息,如下图所示:
在这里插入图片描述
esp8266 是通过串口连接,所以记得要配置串口
在这里插入图片描述

2.3.2 MQTT初始化

OneNet 软件包数据的上传和命令的接收是基于 MQTT 实现的,OneNet 的初始化其实就是 MQTT 客户端的初始化。OneNet 软件包提供了一个接口 onenet_mqtt_init,供用户去初始化 MQTT,只有当 MQTT 初始化成功之后,才能做后续的操作,如上传数据到 OneNet 服务器。

static  void  onenet_mqtt_init_entry(void *parameter)
{
    uint8_t onenet_mqtt_init_failed_times;

    /* mqtt 初始化 */
    while(1)
    {
        if (!onenet_mqtt_init())
        {
            /* mqtt 初始化成功之后,释放信号告知 onenet_upload_data_thread 线程可以上传数据了 */
            rt_sem_release(mqttinit_sem);
            return;
        }
        rt_thread_mdelay(100);
        LOG_E("onenet mqtt init failed %d times",onenet_mqtt_init_failed_times++);
    }
}

2.3.3 上传数据到ONENET

在 ESP8266 已经正常连上 WIFI 的前提下,MQTT 初始化成功后,就可以放心大胆的上传数据给 OneNet 了。

static  void  onenet_upload_data_entry(void *parameter)
{
    struct  recvdata  *buf_mp;

    /* 永久等待方式接收信号量,若收不到,该线程会一直挂起 */
    rt_sem_take(mqttinit_sem, RT_WAITING_FOREVER);
    /*后面用不到这个信号量了,把它删除了,回收资源*/
    rt_sem_delete(mqttinit_sem);
    while(1)
    {
        /* 500ms 上传一次数据 */
        rt_thread_delay(rt_tick_from_millisecond(500));
        if (rt_mb_recv(tmp_msg_mb, (rt_ubase_t*)&buf_mp,RT_WAITING_FOREVER)==RT_EOK)
        {
            /* 上传发送节点1的数据到 OneNet服务器,数据流名字是 temperature_p0 */
            if(onenet_mqtt_upload_digit("temperature_p0", buf_mp->temperature_p0)!=RT_EOK)
            {
                rt_kprintf("upload temperature_p0 has an error,try again\n");
            }
            else
            {
                rt_kprintf("onenet upload ok >>> temp_p0:%f\n",buf_mp->temperature_p0);
            }
            rt_kputs("\n\n");
            rt_mp_free(buf_mp);     /* 释放内存块 */
            buf_mp = RT_NULL;       /* 请务必要做 */
        }
    }
}

接收节点源码下载
提取码:y20q

   END
  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-09-03 12:04:50  更:2021-09-03 12:06:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 0:31:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码