赞
踩
当今社会生活中对观赏鱼的饲养已经成为了一种潮流,目前人们开始追求更加便捷的饲养方式而且保证其存活率。新一代的家居设计以拥有丰富实用的补氧、换水、杀菌和温控功能、更加人性化的设计,而受到大众的青睐。本文基于鱼缸的智能化需求,提出了一款多传感器数据融合的智能鱼缸,采用RA6M4单片机和onenet物联网平台,对多个传感器的信息进行融合和互补来管理鱼缸以及感知水环境。用户利用电脑本地端、网页客户端或手机APP(还在开发测试中)实现对智能鱼缸的水温、气压、光照强度等传感采集,实现补氧气、蜂鸣器提示和杀菌的本地控制、室外异地远程控制及各传感器根据实际环境自动调节执行器的工作状态。
本研究的智能鱼缸系统是属于智能家居的一个具体产品。鱼缸是大众家庭中比较常见的一种饲养鱼类的容器,但它的实际意义不仅仅是一个鱼缸、而是家庭中的一道风景。鱼缸中新鲜快活的鱼儿、娓娓动人的小虾、碧波荡漾的水草、浮浮沉沉的浮萍,十分巧妙地将水环境生态系统的美景融合在一个鱼缸中,形成了一幅动静相宜的画卷。
目前,国内市场上有许多功能不一致的产品,其中大多数是非智能,单一恒温控制、增氧、照明系统。如果一套多功能鱼缸无法智能控制多个功能模块和单个设备,则使用起来会很不灵活且效率低下,并且整体性能无法提高。
因此,针对这一系列问题提化了一种多传感器数据融合的智能鱼缸系统。首先对智能鱼缸系统进行总体设计,其次分别介绍系统的硬件设计和软件选择。硬件设计介绍了控制器、传感器、执行器以及电源的设计方案;软件设计包含多传感器数据融合与执行器协同系统、本地监控系统和物联网远程监控系统。其中多传感器首先采用均值滤波得到智能鱼缸的水体温度、水面气压、缸外温度,光照强度等数据,将鱼缸的水体温度和水面气压进行模糊逻辑推理,模糊控制水泵的工作状态进而调整补氧气速率;将鱼缸的缸外温度和光照强度也进行模糊逻辑推理,模糊控制水泵的工作状态进而调整补氧气速率。后期将项目的系统硬件与软件进行结合,经过多次测试,预计实现水循环、蜂鸣器提示和光照控制的功能,并实现移动终端的远程实时控制。该设计通过多传感器数据融合进行水泵和紫外线灯模糊控制,不仅仅是对于家用鱼缸的管理与改进,还对于类似的智能家居产品的研究与生产有较高的实用价值。
本系统以RA6M4芯片作为系统主控,并围绕其设计了一系列外围电路。系统通过由多种组件函数库完成RT-Thread Studio 项目工程搭建,对使用 I2C、ADC、PWM 、UART等通信协议的各类外设模块进行控制。同时通过 MQTT 协议发布主题订阅主题方式接入onenet物联网平台,以实现通过物联网对系统进行远程监测以及后期控制。系统结构框图如图 1 所示,以RA6M4为主控,通过间接或直接的方式对外围电路、外围模块进行控制。电源、控制器、执行器、传感器、鱼缸共同构成系统的硬件监测执行部分,通讯中的电脑端、手机端、中移互联云平台为软件交互部分。
图 1 系统结构框图
硬件部分由控制器、传感器、执行器、电源以及鱼缸组成。硬件原理图如下
项目工程见立创EDA:Intelligent fish tank system
控制器选用RA6M4单片机,RA6M4配合RT-Thread Studio 编程而不用51单片机或者STM32单片机,主频高达200MHz, 采用Arm Cortex?-M33 内核,IO口全部引出,采用MOS管输出稳定且可承载一定功率,8路UART串口,10路PWM,还设计了Arduino UNO板载接口,可通过WiFi接入网络来智能控制执行器,让使用者更好地进行人机交互,使用起来更加方便。RA6M4性能强而且功率低,对比相同性能的主控板,RA6M4的性价比更高。
Renesas RA6M4开发板环境配置参照:【基于 RT-Thread Studio的CPK-RA6M4 开发板环境搭建】
传感器由各个具体功能模块构成:BMP180气压温度传感器、DHT11温湿度传感器、LM35温度传感器和光敏电阻光照传感器。
BMP180是一款高精度、小体积、低能耗的压力温度传感器,可以应用在移动设备中;BMP180采用强大的8-pin陶瓷无引线芯片承载(LCC)超薄封装,可以通过I2C总线直接与各种微处理器相连。为模糊控制水泵的制氧速率提供输入外界气压和外界温度两变量。使用方法见【Renesas RA6M4开发板之I2C读取BMP180气压温度】
修改接口代码:
bmp180_sample.c
#include "bmp180.h"
#include "bmp180_sample.h"
/*
* Copyright (c) 2020 panrui <https://github.com/Prry/rtt-bmp180>
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2020-03-12 panrui the first version
*/
#include <rtthread.h>
#include <rtdevice.h>
#include "sensor.h"
#include "bsp_api.h"
rt_device_t baro_dev = RT_NULL, temp_dev = RT_NULL;
struct rt_sensor_data baro_data,temp_data;
rt_size_t res0 = 0, res1 = 1;
rt_uint8_t chip_id;
void read_baro_entry(void)
{
baro_dev = rt_device_find("baro_bmp180");
if (baro_dev == RT_NULL)
{
rt_kprintf("not found baro_bmp180 device\r\n");
return;
}
if (rt_device_open(baro_dev, RT_DEVICE_FLAG_RDONLY) != RT_EOK)
{
rt_kprintf("open baro_180 failed\r\n");
return;
}
temp_dev = rt_device_find("temp_bmp180");
if (temp_dev == RT_NULL)
{
rt_kprintf("not found temp_bmp180 device\r\n");
return;
}
if (rt_device_open(temp_dev, RT_DEVICE_FLAG_RDONLY) != RT_EOK)
{
rt_kprintf("open temp_bmp180 failed\r\n");
return;
}
rt_device_control(baro_dev, RT_SENSOR_CTRL_SET_ODR, (void *)(1));/* 1Hz read */
rt_device_control(temp_dev, RT_SENSOR_CTRL_SET_ODR, (void *)(1));/* 1Hz read */
rt_device_control(temp_dev, RT_SENSOR_CTRL_GET_ID, (void*)&chip_id);
rt_kprintf("bmp180 chip ID [0x%X]\n", chip_id);
}
unsigned char *bmp_read(void)
{
static unsigned char bmp[2];
res0 = rt_device_read(baro_dev, 0, &baro_data, 1);
res0 = rt_device_read(temp_dev, 0, &temp_data, 1);
if (res0==0 || res1==0)
{
rt_kprintf("read data failed! result is %d,%d\n", res0, res1);
rt_device_close(baro_dev);
rt_device_close(temp_dev);
}
bmp[0]=baro_data.data.baro/1000;
bmp[1]=temp_data.data.temp/10-44;
rt_kprintf("baro[%dPa],temp[%d.%dC],timestamp[%d]\r\n", baro_data.data.baro,
temp_data.data.temp/10-44, temp_data.data.temp%10,
temp_data.timestamp);
return bmp;
}
int rt_hw_bmp180_port(void)
{
struct rt_sensor_config cfg;
cfg.intf.dev_name = "i2c1"; /* i2c bus */
cfg.intf.user_data = (void *)0x77; /* i2c slave addr */
rt_hw_bmp180_init("bmp180", &cfg); /* bmp180 */
read_baro_entry();
return RT_EOK;
}
INIT_COMPONENT_EXPORT(rt_hw_bmp180_port);
bmp180_sample.h
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-07-25 Asus the first version
*/
#ifndef _BMP180_SAMPLE_H_
#define _BMP180_SAMPLE_H_
void read_baro_entry(void);
unsigned char *bmp_read(void);
int rt_hw_bmp180_port(void);
#endif /* PACKAGES_BMP180_LATEST_BMP180_SAMPLE_H_ */
DHT11数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感技术,确保产品具有可靠性与卓越的长期稳定性,成本低、相对湿度和温度测量、快响应、抗干扰能力强、信号传输距离长、数字信号输出、精确校准。传感器包括一个电容式感湿元件和一个NTC测温元件,并与一个高性能8位单片机相连接。使用方法见【Renesas RA6M4开发板之DHT11温湿度读取】
修改接口代码:
dhtxx_sample.c
#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#include "dhtxx_sample.h"
#include "dhtxx.h"
#include "bsp_api.h"
#include <stdio.h>
#include <string.h>
#define DATA_PIN BSP_IO_PORT_01_PIN_02//dht温湿度端口定义
/* cat_dhtxx sensor data by dynamic */
rt_uint8_t *cat_dhtxx(void)
{
static rt_uint8_t dht[2];
dht_device_t sensor = dht_create(DATA_PIN);
if(dht_read(sensor)) {
rt_int32_t temp = dht_get_temperature(sensor);
rt_int32_t humi = dht_get_humidity(sensor);
rt_kprintf("Temp: %d.%d 'C, Humi: %d '% \n", temp/10-2, temp%10, humi/10);
dht[0] = temp/10-4,dht[1] = humi/10;
}
else {
rt_kprintf("Read dht sensor failed.\n");
}
rt_thread_mdelay(100);
dht_delete(sensor);
return dht;
}
//#ifdef FINSH_USING_MSH
//MSH_CMD_EXPORT(cat_dhtxx, read dhtxx humidity and temperature);
//#endif
dhtxx_sample.h
#ifndef _DHTXX_SAMPLE_H_
#define _DHTXX_SAMPLE_H_
rt_uint8_t *cat_dhtxx(void);
#endif /* PACKAGES_DHTXX_LATEST_EXAMPLES_DHTXX_SAMPLE_H_ */
LM35温度传感器
LM35线性温度传感器是一种得到广泛使用的温度传感器,工作温度范围较大,模块适用于许多特殊场合,测量温度范围为0℃~100℃,输出电压与温度成正比线性关系,0°C时输出为0V,温度每上升1℃,输出电压增加10mV,LM35线性温度传感器使用ADC读取 (需要校准),可以非常容易地实现与环境温度感知相关的互动效果。本实验将LM35传感器建议封装使其可以稳定读取水体温度,使用方法见【Renesas RA6M4开发板之Arduino六路ADC采样】
光敏电阻光照传感器
光敏电阻是用硫化镉或硒化镉等半导体材料制成的特殊电阻器,其工作原理是基于内光电效应。光照愈强,阻值就愈低,随着光照强度的升高,电阻值迅速降低,亮电阻值可小至1KΩ以下。光敏电阻对光线十分敏感,其在无光照时,呈高阻状态,暗电阻一般可达1.5MΩ,使用ADC读取 (需要校准)。使用方法见【Renesas RA6M4开发板之Arduino六路ADC采样】
两个ADC接口代码
adc.c
#include"adc.h"
#define ADC_NAME "adc0" /* ADC 设 备 名 称 */
#define LM35_ADC_CHANNEL 0 /* LM35_ADC水体温度通 道 */
#define light_ADC_CHANNEL 1 /* light_ADC光敏通 道 */
#define REFER_VOLTAGE 330 /* 参 考 电 压 3.3V,数 据 精 度 乘 以100保 留2位 小 数*/
#define CONVERT_BITS (1 << 12) /* 转 换 位 数 为12位 */
rt_adc_device_t adc_dev; /* adc设备句柄 */
rt_err_t ret = RT_EOK;
int adc_Initiation(void)
{
rt_err_t ret = RT_EOK;
/* 查 找 设 备 */
adc_dev = (rt_adc_device_t)rt_device_find(ADC_NAME);
if (adc_dev == RT_NULL)
{
rt_kprintf("adc sample run failed! can't find %s device!\n", ADC_NAME);
ret = RT_ERROR;
}
else
{
rt_kprintf("rt_sensor find %s device init success!\n", ADC_NAME);
}
return ret;
}
rt_uint8_t *adc_read()
{
rt_int16_t LM35=0,light=0;
static rt_uint8_t adc[2], adc_value[4], max = 5;
for (int var = 0; var < max; ++var) {
/* 使 能 设 备 */
ret = rt_adc_enable(adc_dev, LM35_ADC_CHANNEL);
ret = rt_adc_enable(adc_dev, light_ADC_CHANNEL);
rt_thread_mdelay(var);
/* 1读 取 采 样 值 */
LM35 = LM35+ rt_adc_read(adc_dev, LM35_ADC_CHANNEL);
rt_thread_mdelay(var);
/* 2读 取 采 样 值 */
light = light + rt_adc_read(adc_dev, light_ADC_CHANNEL);
/* 关 闭 通 道 */
ret = rt_adc_disable(adc_dev, LM35_ADC_CHANNEL);
ret = rt_adc_disable(adc_dev, light_ADC_CHANNEL);
rt_thread_mdelay(50);
}
LM35 = LM35/max,light = light/max;
// rt_kprintf("the LM35 is :%d \n", LM35);
/* 转 换 为 对 应 温 度 值 */
LM35 = LM35*(REFER_VOLTAGE/40.96); //转化为温度
adc_value[0] = LM35 / 100-22;
adc_value[1] = LM35 % 100;
rt_kprintf("the voltage is :%d.%02d `C\n", adc_value[0], adc_value[1]);
// rt_kprintf("the light is :%d \n", light);
/* 转 换 为 对 应 电 压 值 */
light = light * REFER_VOLTAGE / CONVERT_BITS;
adc_value[2] = light /100;
adc_value[3] = light %100;
rt_kprintf("the voltage is :%d.%02d lx\n", adc_value[2], adc_value[3]);
adc[0] = LM35/100-22;
adc[1] = light;
return adc;
}
INIT_APP_EXPORT(adc_Initiation);
adc.h
#ifndef _ADC_H_
#define _ADC_H_
#include <rtthread.h>
#include <rtdevice.h>
#include <stdio.h>
#include <stdlib.h>
int adc_Initiation(void);
rt_uint8_t *adc_read();
#endif /* _ADC_H_ */
结合鱼缸实际需求,执行器包括水泵、UV杀菌灯、OLED显示屏和蜂鸣器。
pwmled.c
#include "pwmled.h"
#define PWM_DEV_NAME "pwm8" /* PWM设备UV灯名称 */
#define PWM2_DEV_NAME "pwm7" /* PWM设备水泵名称 */
#define PWM_DEV_CHANNEL 0 /* PWM通道 */
#define period 500000 /* 周期为0.5ms,单位为纳秒us */
struct rt_device_pwm *pwm_dev; /* PWM设备句柄 */
struct rt_device_pwm *pwm2_dev; /* PWM2设备句柄 */
int pwm_Initiation(void)
{
rt_err_t rt_err = RT_EOK;
rt_uint16_t pulse = 0; /* 初始使能为零 */
/* 查找UV灯设备 */
pwm_dev = (struct rt_device_pwm *)rt_device_find(PWM_DEV_NAME);
if (pwm_dev == RT_NULL)
{
rt_kprintf("pwm sample run failed! can't find %s device!\n", PWM_DEV_NAME);
rt_err = RT_NULL;
}
else {
rt_kprintf("rt_sensor find %s device init success!\n", PWM_DEV_NAME);
}
/* 查找水泵设备 */
pwm2_dev = (struct rt_device_pwm *)rt_device_find(PWM2_DEV_NAME);
if (pwm2_dev == RT_NULL)
{
rt_kprintf("pwm sample run failed! can't find %s device!\n", PWM2_DEV_NAME);
rt_err = RT_NULL;
}
else {
rt_kprintf("rt_sensor find %s device init success!\n", PWM2_DEV_NAME);
}
/* 设置PWM周期和脉冲宽度默认值 */
rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, pulse);
rt_pwm_set(pwm2_dev, PWM_DEV_CHANNEL, period, pulse);
/* 使能设备 */
rt_pwm_enable(pwm_dev, PWM_DEV_CHANNEL);
rt_pwm_enable(pwm2_dev, PWM_DEV_CHANNEL);
return rt_err;
}
/*a,b范围在0~10*/
void pwm_action(rt_uint8_t a,rt_uint8_t b)
{
if (a>=10) {
a=10;
}
else if (a<=0) {
a=0;
}
if (b>=10) {
b=10;
}
else if (b<=5) {
b=0;
}
if (a==0&&b==0) {
rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, 0);//UV灯
rt_pwm_set(pwm2_dev, PWM_DEV_CHANNEL, period, 0);//水泵
} else {
rt_pwm_set(pwm_dev, PWM_DEV_CHANNEL, period, period*0.1*a);//UV灯
rt_pwm_set(pwm2_dev, PWM_DEV_CHANNEL, period, period*0.7+period*0.03*b);//水泵
}
}
INIT_APP_EXPORT(pwm_Initiation);
pwmled.h
#ifndef _PWMLED_H_
#define _PWMLED_H_
#include <rtthread.h>
#include "hal_data.h"
#include <rtdevice.h>
int pwm_Initiation(void);
void pwm_action(rt_uint8_t a,rt_uint8_t b);
#endif /* _PWMLED_H_ */
本模块选用的是0.96寸OLED显示屏,通讯采用IIC协议,采用能实时显示水体温度、缸外温度、外界气压和光照强度数值,此模块是由RA6M4用户按键来触发响应(5S后熄屏),默认状态不显示。使用方法见【Renesas RA6M4开发板之I2C驱动ssd1306 OLED屏幕】
采用TMB09A03型 DC3V 有源蜂鸣器,属于有源连续声。此模块采用普通GPIO输出控制,当传感器达到一定的阈值会在本地提醒用户执行相关操作,达到交互使用的效果。使用方法见【Renesas RA6M4开发板之按键和LED的GPIO】
结合鱼缸日常供电需求,每天整体电控系统所需能量表达式如下;
Q
=
Q
L
+
Q
P
+
Q
C
Q=Q_L+Q_P+Q_C
Q=QL+QP+QC
其中
Q
L
Q_L
QL是UV杀菌灯每天消耗的能量;
Q
P
Q_P
QP是水泵每天消耗的能量;
Q
C
Q_C
QC是控制器和其他传感器、执行器等每天消耗的能量。
t为对应的使用时间,结合实验分析,各参数如下表:
每天大约需要消耗60焦耳的电能(估算),因此采用18650电池USB供电,电源供电采用四节18650锂电池2000mA和太阳能电池板搭配。系统搭配UPS不间断供电系统,该供电系统通过监测18650锂电池的电压,如果光伏发电使锂电池达到3.7V,则不启动USB供电,反而言之。
太阳能电池板,采用5V200mA单晶太阳能板三片并联发电,太阳能电池板白天通过环境光给电池充电和系统供电。可以有效缓解18650锂电池供电需求。
本次实验采用市面常用的意品宠物用品专营店的240型高清热弯中小型桌面鱼缸,采用汽车级浮法玻璃工艺,使透光率达到92%。整体构成仿生态循环过滤,模拟大自然水流环境,循环净水,鱼缸结构如图2。尺寸是240mm170mm285mm,盛水容积大约8升。
图 2 鱼缸结构
软件设计包括多传感器数据融合与执行器协同系统、本地监控系统和物联网远程监控系统,其中RA6M4软件架构如下。
本项目软件架构主要采用上图所示的三个线程完成,其中:
CMD_Theard线程:负责完成传感器数据和执行器数据采集矫正;
LM_Theard线程:负责系统中传感器数据和执行器数据采集,通过拼接自定义串口协议,采用Uart串口通讯实现上下位机的联动;
Onenet_Theard线程:负责系统中传感器数据和执行器数据采集,通过esp8266上报到Onenet平台,以及云端数据下发解析执行等功能。
其中多传感器首先采用卡尔曼滤波得到智能鱼缸的水体温度、水面气压、光照强度数据,将鱼缸的水体温度和水面气压进行模糊逻辑推理,模糊控制水泵的工作状态。
系统获取的传感器数据通常不能够直接供应用使用(存在噪声干扰:低频噪声或高频噪声),一般通过一种或者多种滤波算法结合,对原始数据进行滤波处理,在保证滤波后数据实时性要求的前提下,获取相对稳定无噪声的数据用于实际的应用系统。
平均滤波,取一定数量的原始数据,进行累加后取平均,从而获取较稳定的输出数据,主要滤除原始数据中的高频噪声。为了减少电源供电扰动主动放弃第一次采样,累加后续5次数据平均。
模糊控制结构如图3所示, 以鱼缸环境传感器数据处理的结果作为输入值, 一般包括:大气压强、温度、湿度、光照强度等;模糊控制输出用于执行器, 如水泵、加热设备、UV紫外线灯等。
图 3 模糊控制结构
由于多输出的模糊控制算法过于复杂, 因此可利用模糊控制器的解耦特性, 将模糊控制系统分解为多个单输出的子系统,系统模糊控制如表1所示。例如可根据温度和气压控制水泵的打开时间, 本文将以此为例设计模糊控制器。
表 1 系统模糊控制
输入主参 | 输入副参 | 输出结果 |
---|---|---|
缸外温度 | 气压 | 水泵工作时间 |
缸内温度 | 光照 | UV灯工作时间 |
假想实验环境:室内的温度为0℃30℃,而鱼缸水体温度在0℃20℃,定义输入量温度的基本论域为[0, 20]。
定义5个模糊子集(NB、NS、ZO、PS、PB) 和高斯型隶属度函数,其中σ取值为2, NB、NS、ZO、PS、PB隶属度函数中均值x取值分别为0、5、10、15、20。因此温度的范围域和隶属度函数曲线如图4所示:
图 4 温度的范围域和隶属度函数曲线
根据前期调查,一般春季武汉市的气压为100000pa~101000pa, 定义输入量气压的基本论域为[100000, 101000]。定义5个模糊子集 (NB、NS、ZO、PS、PB) 和高斯型隶属度函数, 其中σ取值为125, NB、NS、ZO、PS、PB隶属度函数中均值x取值分别为100000、100250、100500、100750、101000。因此气压的范围域和隶属度函数曲线如图5所示:
图 5 气压的范围域和隶属度函数曲线
水循环的每次水泵开启时间一般为0~20分钟, 定义输出量水循环时间基本论域为[0, 20]。分为5个模糊子集:关闭(NB)稍短(NS)中等(ZO)稍长(PS)长(PB),定义三角型隶属度函数,因此水循环时间的范围域和隶属度函数曲线如图6所示
图 6 水循环时间的范围域和隶属度函数曲线
采用“if Temp and Pres then Motor”形式的模糊推理方法, 其中Temp、Temp为输入模糊子集, Motor为输出模糊子集。根据实践调试经验得出表2所示模糊控制规则表, 又称模糊关系矩阵, 共25条控制规则:
表 2 模糊控制规则表
系统采用Mamdani模糊推理法, 本设计采用重心法, 即取模糊隶属度函数曲线与坐标围成面积的重心作为最终输出值。
使用Matlab的Fuzzy Logic工具箱进行系统仿真, 观察系统输入输出。例如取温度Temp=10℃、气压Pres=100500pa, 则输出的水循环时间Motor=3.14min,给定输入经模糊规则推理输出如图7所示:
图 7 给定输入经模糊规则推理输出
系统仿真输出曲面如图8所示, 输出曲面总体光滑, 稳定可靠, 可将养殖环境参数控制在最佳值附近。
图 8 系统仿真输出曲面
近似函数代替为:
用户可以实时本地观察鱼缸状态,OLED显示器会事件型滚动屏幕,休眠状态是屏幕熄屏,因此休眠时间将远大于滚动显示时间以达到节能效果。OLED显示器滚动屏幕时将采集的传感器数据、执行器状态数据和电池电压数据实时动态显示。本地端设计了直接控制方式,优先级为1级,根据当前鱼缸环境的实际情况,个性化调节鱼缸的水泵、光照等,实现智能鱼缸的精准控制。
本文的智能鱼缸系统接入onenet物联网云平台,以实现物联网远程控制。采用onenet库与接口函数,并通过手机 APP 或手机语音的方式来实现远程操作。利用公有云服务进行数据传输,进而对其进行远程操控,使鱼缸能够作为智能家居被用户使用。
采用Uart0实现本地端串口通讯,串口通讯分为接收协议和发送协议,上位机采用Serial Studio,使用方法见:【Renesas RA6M4开发板之Serial studio串口交互】
根据实际要求,确定通信协议传输格式。
名称 | 16进制 | 大小 |
---|---|---|
Head | 0x5a | 1字节 |
Buzzer | 0x00/0x01 | 1字节 |
UVLED | 0x00~0x0a | 1字节 |
Moter | 0x00~0x0a | 1字节 |
Command | 0x00~0x02 | 1字节 |
LED3 | 0x00/0x01 | 1字节 |
Data | 0x00~0xff | 3字节 |
Num | 0x0b | 1字节 |
Verify | 0xa5 | 1字节 |
通信协议格式:
0x00
保持,0x01
手动修改,0x02
自动控制;0x0b
;5A 01 00 00 01 01 00 00 00 0B A5
开灯5A 01 00 00 01 00 00 00 00 0B A5
关灯5A 00 00 00 01 00 00 00 00 0B A5
蜂鸣器打开5A 00 0a 00 01 00 00 00 00 0B A5
UV灯打开5A 00 00 00 02 01 00 00 00 0B A5
自动具体查看数据解析线程函数control_thread_entry
传输协议采用一串字符编码,其中包含帧头,分隔符,帧尾
名称 | 标记 |
---|---|
帧头 | /*KAANSATQRO, |
分隔符 | , |
帧尾 | */ |
中间监测鱼缸数据依次为:水体温度(LM35),光照强度,警报(蜂鸣器),板载LED3,外部大气压(BMP180),外部温度1(BMP180),外部温度2(DHT11),外部湿度(DHT11),UV灯(PWM1),水泵增氧机(PWM2)
具体查看数据解析线程函数serial_thread_entry
Serial Studio传感器映射部分参数配置
Serial Studio的Json映射文件
{
"frameEnd": "*/",
"frameStart": "/*KAANSATQRO,",
"groups": [
{
"datasets": [
{
"alarm": 0,
"fft": false,
"fftSamples": 1024,
"graph": true,
"led": false,
"log": false,
"max": 0,
"min": 0,
"title": "水体温度(LM35)",
"units": "℃",
"value": "%1",
"widget": "gauge"
},
{
"alarm": 0,
"fft": false,
"fftSamples": 1024,
"graph": true,
"led": false,
"log": false,
"max": 0,
"min": 0,
"title": "光照强度",
"units": "Lux",
"value": "%2",
"widget": ""
},
{
"alarm": 0,
"fft": false,
"fftSamples": 1024,
"graph": false,
"led": true,
"log": false,
"max": 0,
"min": 0,
"title": "警报(蜂鸣器)",
"units": "bool",
"value": "%3",
"widget": ""
},
{
"alarm": 0,
"fft": false,
"fftSamples": 1024,
"graph": false,
"led": true,
"log": false,
"max": 0,
"min": 0,
"title": "板载LED3",
"units": "bool",
"value": "%4",
"widget": ""
},
{
"alarm": 0,
"fft": false,
"fftSamples": 1024,
"graph": true,
"led": false,
"log": false,
"max": 0,
"min": 0,
"title": "外部大气压(BMP180)",
"units": "KPa",
"value": "%5",
"widget": ""
},
{
"alarm": 0,
"fft": false,
"fftSamples": 1024,
"graph": true,
"led": false,
"log": false,
"max": 0,
"min": 0,
"title": "外部温度1(BMP180)",
"units": "℃",
"value": "%6",
"widget": "gauge"
},
{
"alarm": 0,
"fft": false,
"fftSamples": 1024,
"graph": true,
"led": false,
"log": false,
"max": 0,
"min": 0,
"title": "外部温度2(DHT11)",
"units": "℃",
"value": "%7",
"widget": "gauge"
},
{
"alarm": 0,
"fft": false,
"fftSamples": 1024,
"graph": true,
"led": false,
"log": false,
"max": 0,
"min": 0,
"title": "外部湿度(DHT11)",
"units": "%",
"value": "%8",
"widget": "gauge"
},
{
"alarm": 0,
"fft": false,
"fftSamples": 1024,
"graph": true,
"led": false,
"log": false,
"max": 0,
"min": 0,
"title": "UV灯(PWM1)",
"units": "%",
"value": "%9",
"widget": ""
},
{
"alarm": 0,
"fft": false,
"fftSamples": 1024,
"graph": true,
"led": false,
"log": false,
"max": 0,
"min": 0,
"title": "水泵增氧机(PWM2)",
"units": "%",
"value": "%10",
"widget": ""
}
],
"title": "智能鱼缸监测系统",
"widget": ""
}
],
"separator": ",",
"title": "test"
}
打印效果:
11:42:35.098 -> hello RT-Thread!
11:42:35.586 -> /*KAANSATQRO,25,134,1,0,82,25,0,0,0,0*/
11:42:37.005 -> /*KAANSATQRO,25,138,1,0,97,25,26,79,0,0*/
11:42:38.426 -> /*KAANSATQRO,24,138,1,0,97,25,26,80,0,0*/
11:42:39.833 -> /*KAANSATQRO,25,138,1,0,97,25,26,80,0,0*/
11:42:41.256 -> /*KAANSATQRO,24,138,1,0,97,25,26,80,0,0*/
11:42:42.678 -> /*KAANSATQRO,24,138,1,0,97,25,26,80,0,0*/
11:42:44.081 -> /*KAANSATQRO,24,138,1,0,97,25,26,80,0,0*/
11:42:45.496 -> /*KAANSATQRO,25,138,1,0,97,25,26,80,0,0*/
11:42:46.919 -> /*KAANSATQRO,25,138,1,0,97,25,26,80,0,0*/
11:42:48.332 -> /*KAANSATQRO,24,138,1,0,97,25,26,80,0,0*/
11:42:49.750 -> /*KAANSATQRO,26,138,1,0,97,25,26,80,0,0*/
11:42:51.160 -> /*KAANSATQRO,25,138,1,0,97,25,26,80,0,0*/
11:42:52.573 -> /*KAANSATQRO,25,138,1,0,97,25,26,80,0,0*/
本地端接口代码:
uart.c
/*
* 程序清单:这是一个串口设备接收不定长数据的示例代码
* 例程导出了 uart_dma_sample 命令到控制终端
* 命令调用格式:uart_dma_sample uart2
* 命令解释:命令第二个参数是要使用的串口设备名称,为空则使用默认的串口设备
* 程序功能:通过串口 uart2 输出字符串"hello RT-Thread!",并通过串口 uart2 输入一串字符(不定长),再通过数据解析后,使用控制台显示有效数据。
*/
#include"uart.h"
#define LED3_PIN BSP_IO_PORT_01_PIN_06
#define buzzer_PIN BSP_IO_PORT_04_PIN_03//蜂鸣器定义
#define SAMPLE_UART_NAME "uart0"
#define DATA_CMD_END '\r' /* 结束位设置为 \r,即回车符 */
#define ONE_DATA_MAXLEN 12 /* 不定长数据的最大长度 */
/* 用于接收消息的信号量 */
static struct rt_semaphore rx_sem;
static rt_device_t serial;
/* 接收数据回调函数 */
static rt_err_t uart_rx_ind(rt_device_t dev, rt_size_t size)
{
/* 串口接收到数据后产生中断,调用此回调函数,然后发送接收信号量 */
if (size > 0)
{
rt_sem_release(&rx_sem);
}
return RT_EOK;
}
static char uart_sample_get_char(void)
{
char ch;
while (rt_device_read(serial, 0, &ch, 1) == 0)
{
rt_sem_control(&rx_sem, RT_IPC_CMD_RESET, RT_NULL);
rt_sem_take(&rx_sem, RT_WAITING_FOREVER);
}
return ch;
}
/* 数据解析线程 */
static void control_thread_entry(void)
{
char ch;
char data[ONE_DATA_MAXLEN];
static char i = 0;
unsigned char *adc_value, *bmp_value;
while (1)
{
ch = uart_sample_get_char();
if(ch == DATA_CMD_END)
{
data[i++] = '\0';
rt_kprintf("data= 0x");//CMD uart7打印 uart0接收16进制数
for (int var = 0; var < ONE_DATA_MAXLEN-1; ++var) {
rt_kprintf("%x",data[var]);
}
rt_kprintf("\r\n");
rt_thread_mdelay(1);
if (data[0]==0x5a&&data[10]==0xa5) {//校验头部和尾部
if (data[4]==0x00) {//保持命令
rt_kprintf("Keep on \r\n");
}
else if (data[4]==0x01) {//修改命令//操作LED灯亮
rt_kprintf("Action \r\n");
rt_pin_write(LED3_PIN, data[5]);//蜂鸣器操作相反
pwm_action(data[2],data[3]);//灯和水泵
rt_pin_write(buzzer_PIN, data[1]);//蜂鸣器操作相反
}
else if (data[4]==0x02) {
rt_kprintf("Automatic \r\n");
adc_value = adc_read();
bmp_value = bmp_read();
data[2] = 10*adc_value[0]/35*(330-adc_value[1])/330;//水泵
data[3] = 10*adc_value[0]/30*(101-bmp_value[0])/5;//水泵
pwm_action(data[2],data[3]);//灯和水泵
}
}
else {//校验识别,返回数据不合法
rt_kprintf("data input Illegal ! please input :5A 00 00 00 01 01 00 0B 00 00 A5 \r\n",data);
}
rt_device_write(serial, 0, &data, sizeof(data));//uart0打印接收指令
for (int var = 0; var < sizeof(data); ++var) {
data[var] = 0x00;
}//数组清零
i = 0;//重新获取
continue;
}
i = (i >= ONE_DATA_MAXLEN-1) ? ONE_DATA_MAXLEN-1 : i;
data[i++] = ch;
}
}
/*uart0串口输出线程*/
void serial_thread_entry(rt_uint8_t a,rt_uint8_t b)
{
unsigned char *adc_value, gpio_value[2], *bmp_value, *dht_value, pwm_value[2];
char str1[45];
char HeadByte[45] ="/*KAANSATQRO";
adc_value = adc_read();
gpio_value[0] = rt_pin_read(buzzer_PIN),gpio_value[1] = rt_pin_read(LED3_PIN);
bmp_value = bmp_read();
dht_value = cat_dhtxx();
pwm_value[0] = a,pwm_value[1] = b;
rt_sprintf(str1, "%s,%d,%d", HeadByte, adc_value[0],adc_value[1]);
rt_sprintf(str1, "%s,%d,%d", str1,gpio_value[0],gpio_value[1]);
rt_sprintf(str1, "%s,%d,%d", str1,bmp_value[0],bmp_value[1]);
rt_sprintf(str1, "%s,%d,%d", str1,dht_value[0],dht_value[1]);
rt_sprintf(str1, "%s,%d,%d", str1,pwm_value[0],pwm_value[1]);
rt_sprintf(str1, "%s%s", str1,"*/ \r\n");
rt_device_write(serial, 0, str1, sizeof(str1) );
rt_thread_mdelay(500);
}
static int uart_data_sample(int argc, char *argv[])
{
rt_err_t ret = RT_EOK;
char uart_name[RT_NAME_MAX];
char str[] = "hello RT-Thread!\r\n";
if (argc == 2)
{
rt_strncpy(uart_name, argv[1], RT_NAME_MAX);
}
else
{
rt_strncpy(uart_name, SAMPLE_UART_NAME, RT_NAME_MAX);
}
/* 查找系统中的串口设备 */
serial = rt_device_find(uart_name);
if (!serial)
{
rt_kprintf("find %s failed!\n", uart_name);
return RT_ERROR;
}
/* 初始化信号量 */
rt_sem_init(&rx_sem, "rx_sem", 0, RT_IPC_FLAG_FIFO);
/* 以中断接收及轮询发送模式打开串口设备 */
rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);
/* 设置接收回调函数 */
rt_device_set_rx_indicate(serial, uart_rx_ind);
/* 发送字符串 */
rt_device_write(serial, 0, str, (sizeof(str) - 1));
/* 创建 control_thread 线程 */
// rt_thread_t control_thread = rt_thread_create("control", (void (*)(void *parameter))control_thread_entry, RT_NULL, 1024, 30, 10);
rt_thread_t control_thread = rt_thread_create("control", (void (*)(void *parameter))control_thread_entry, RT_NULL, 1024, 25, 10);
/* 创建成功则启动线程 */
if (control_thread != RT_NULL)
{
rt_thread_startup(control_thread);
}
else
{
ret = RT_ERROR;
}
return ret;
}
/* 导出到 msh 命令列表中 */
//MSH_CMD_EXPORT(uart_data_sample, uart device sample);
INIT_APP_EXPORT(uart_data_sample);
uart.h
/*
* Copyright (c) 2006-2021, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-07-25 Asus the first version
*/
#ifndef _UART_H_
#define _UART_H_
#include "hal_data.h"
#include <rtthread.h>
#include <rtdevice.h>
#include <stdio.h>
#include <string.h>
#include "pwmled.h"
#include "adc.h"
#include "bmp180_sample.h"
#include "dhtxx_sample.h"
//static rt_err_t uart_rx_ind(rt_device_t dev, rt_size_t size);
//static char uart_sample_get_char(void);
//static void control_thread_entry(void);
void serial_thread_entry(rt_uint8_t a,rt_uint8_t b);
//static int uart_data_sample(int argc, char *argv[]);
#endif /* SRC_UART_H_ */
16进制指令:
5A 01 00 00 01 01 00 00 00 0B A5
开灯5A 01 00 00 01 00 00 00 00 0B A5
关灯5A 00 00 00 01 00 00 00 00 0B A5
蜂鸣器打开5A 00 0a 00 01 00 00 00 00 0B A5
UV灯打开5A 00 00 00 02 01 00 00 00 0B A5
自动由于本人从7月25号困在中高风险区,电脑和硬件28号到,并且在实验室是可以连接wifi,由于条件有限,我所处管控区没有wifi,设备连接手机热点是无法登入onenet,因此暂时还没试验完,后期会补充onenet平台。
代码地址:
视频地址:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。