提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
本文是对学习 RT-Thread官方的智能家居DIY项目的总结,这里是基于RT-Thread Stduio开发的,利用stm32f103作为节点发送数据,用stm32f407作为网关上传数据。(这里只做了一个节点)
提示:以下是本篇文章正文内容,下面案例可供参考
一、节点发送数据
1.用ds18b20获取温度
1. 1添加 ds18b20软件包,其配置如下,示例代码可以不使能
添加了该软件包之后,传感器设备(Sensor)会自动勾选上,如下图所示
1.2 阅读 ds18b20源码,了解认识 sensor 框架
在下图所示中,打开 sensor_dallas_ds18b20.c 文件 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 函数的定义如下:
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 函数定义如下
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 设备(任何设备都需要注册才能使用),这样上层才能找到这个传感器设备,进而进行控制。传感器设备注册如下所示:
int rt_hw_ds18b20_init(const char *name, struct rt_sensor_config *cfg)
{
rt_int8_t result;
rt_sensor_t sensor_temp = RT_NULL;
if (!ds18b20_init((rt_base_t)cfg->intf.user_data))
{
sensor_temp = rt_calloc(1, sizeof(struct rt_sensor_device));
if (sensor_temp == RT_NULL)
return -1;
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() 函数完成初始化,具体实现如下所示:
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” 这个是线程入口函数中得参数,用于查找设备。 线程入口函数如下:
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);
while(1)
{
res = rt_device_read(dev, 0, &sensor_data, 1);
if (res!=1)
{
rt_kprintf("read data failed! size is %d\n",res);
rt_device_close(dev);
return;
}
else
{
msg.timestamp = sensor_data.timestamp;
msg.int_value = sensor_data.data.temp;
rt_mq_send(tmp_msg_mq, &msg, sizeof msg);
}
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.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)
{
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");
}
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())
{
if (sscanf((char *)RxBuf_P0,"%d,+%f",&buf.timestamp_p0,&buf.temperature_p0)!=2)
{
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);
rt_ringbuffer_put(recvdatabuf_p0, (rt_uint8_t *)str_data_p0, strlen(str_data_p0));
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);
线程入口函数:
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)
{
rt_uint32_t set;
static int writebuffer[1024];
rt_size_t size;
while(1)
{
if (rt_event_recv(recvdata_event, WRITE_EVENT_P0, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &set)!=RT_EOK)
{
continue;
}
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)
{
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);
}
}
continue;
}
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;
while(1)
{
if (!onenet_mqtt_init())
{
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)
{
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)
{
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
|