在繁忙而紧凑的学习和工作生活中,我们能够放松下来走进自然的机会越来越少。越来越多的用户喜欢自行种植和培育绿色植物,为生活加一点绿色。忙碌的工作和学习挤占了我们的生活空间,很难抽出精力照料自己心爱的绿植。智能植物生长机解决了这个难题,填补了市场空白,为众多园艺爱好者带来了专业的种植服务和能力。
在众多智能植物生长机品牌中,我们挑选了一款最为常见的智能植物生长机,在其基础上进行改进,融入涂鸦 IoT 能让其功能更加丰富。
通过查询资料,在不消耗太高成本情况下,智能植物生长机可实现以下功能:
在确定了产品原型后,您还可以增加亚克力温室罩,让自制的植物生长机更美观。
在已设定的功能的基础上,可以拆分为以下功能逻辑,来实现植物生长机的智能化操作。
为实现 涂鸦智能 App 远程控制,本教程选用涂鸦 WB3S 云模组 作为主控。涂鸦三明治 Wi-Fi SoC 主控板(WB3S)是方便开发者快速实现各种智能硬件产品原型的一款开发板。由于板载 ADC 接口只有一个,在硬件上需要开发者进行接口拓展,本教程选用一颗 RS2255 开关芯片 实现接口拓展需求。
接线原理图(点击下载)
PCB 设计图(点击下载)
在涂鸦三明治 Wi-Fi SoC 主控板(WB3S)的基础上,可以保留原有的 ADC 接口,另外拓展了 A1、A2、A3 三个模拟接口。
模拟接口使用
根据原理图不难发现,如果要读取 A1、A2、A3 三个模拟接口的话,嵌入式程序首先要读取 ADC 数值。ADC 具体与 A1、A2、A3 中哪个模拟接口接在一起,需要通过 PWM0、PWM1 两个管脚的高低电平控制 RS2255。具体模式如下:
使用接口 | PWM0 | PWM1 |
---|---|---|
A1 | 1 | 0 |
A2 | 0 | 1 |
A3 | 1 | 1 |
照度监测方面,选取一个 BH1750 照度检测传感器来实现。BH 1750 照度检测传感器搭载了一个 BH1750FVI,是 I2C 总线接口的数字环境光传感器 IC。可以准确读取 1-65535 lx 的环境照度。如下图所示:
BH1750 接线原理图:
I/O 口介绍:
温湿度监测采用 SHT21 温湿度传感器。SHT21 具有完全标定、I2C 数字输出、低功耗、优异的长期稳定性等功能特点。
说明:如果无法获取 SHT21 温湿度传感器,可以使用 HTU21D 来替代。
SHT21 温湿度传感器接线原理图:
I/O 口介绍:
土壤湿度监测使用土壤湿度传感器,它是一个简易的水分传感器,可检测土壤的水分,表面镀镍而不易生锈,延长使用寿命,感应面积宽提高导电性能。传感器采用双输出模式,数字量输出简单,模拟量输出更精确。灵敏度可以通过下图中蓝色电位器调节阀值来调节。电压比较器采用 LM393 芯片,工作稳定,信号干净。
土壤湿度传感器接线原理图:
I/O 口介绍:
水箱水位监测使用一个水位传感器实现。其具有水量到模拟量的转换、可塑性强、输出为基础模拟值、低功耗、灵敏度高、可以直接与微处理器或其他逻辑电路相连接的特点,适合各种开发板和控制器。
水位传感器接线原理图:
I/O 口介绍:
补光功能通过普通植物生长机上的补光灯。此次选用的植物生长机的补光由暖色、红色、红外、蓝色四种颜色的 2835 LED 灯珠组成,共有 114 颗灯珠,暖光:红光:远红外:蓝光的灯珠数量比为 25:9:2:2。
可以使用两路 PWM 进行控制,其中一路控制蓝光,另一路控制暖光、红光和远红外,实现蔬菜、瓜果花卉两种工作模式。
灯板亮暖光、红光、远红外、蓝光四种色光。此模式下灯板的光谱图测试报告如下:
在蔬菜模式下,灯板测试参数如下图:
灯板亮暖光、红光、远红外三种色光。此模式下灯板的光谱图测试报告如下:
在瓜果花卉工作模式下,灯板测试参数如下图:
I/O 口介绍:
通过测试,只有在另一路工作时蓝光才能调节,使用时需要注意。其他两路 PWM 的灯串替换此款灯板。
加湿功能采用一路继电器控制一个 5V 加湿传感器实现。芯片设计工作频率为 108KHz。芯片预留了水位监测控制引脚(8 脚),可实现枯水断电,以保护雾化片不会因为缺水而干烧。
加湿传感器接线原理图:
I/O 口介绍:
植物温度控制可以采用一路继电器来控制一个 75W 远红外加热灯实现。具有热效率高、发热效果好、遇水防爆、导热散热强、潮湿环境可用等优点。您可以根据需求添加散热风扇。
(可选)散热风扇:本教程沿用普通植物生长机的 DC 12V 0.18A 的风扇。通过一个 AC220V-DC12V 的变压器与加热灯并联,一同被继电器控制。
通风功能通过一路继电器控制两个 6CM 12V 0.5A 大风量风扇实现。通风功能可以让温室的温湿度降低,并有助于植物的授粉,让环境与外界进行气体交换。
水箱加水功能,由一路继电器控制一个 12V 水泵实现。可从其他水池或水井向水箱供水,也可根据具体情况把水泵换成 12V 自来水阀。
功率:24W
流量:5L/min
进水压力:0.48MPa
浇水功能通过一路继电器控制两个 12V 直流隔膜泵实现,把水箱里的水抽向植物土壤达到浇水的目的。
一般情况下,普通控制器不能直接控制比其电压高的电路。要想控制其他供电网络上的设备,使用继电器无疑是一个不错的选择。
您可以选用两个一路继电器和一个四路继电器,控制加湿、加温、水箱加水、通风、浇水功能。
一路继电器
四路继电器
继电器管脚
引脚符号 | 引脚定义及功能 |
---|---|
DC+ | 供电电源正极 |
DC- | 供电电源地 |
IN或INxx | 控制信号输入端 |
NO或NOx | 负载输入端,常开 |
COM或COMx | 负载输入端,公共端 |
NC或NCx | 负载输入端,常闭 |
为了快速完成硬件模型设计,可以使用一个结构比较合适的普通植物生长机,在其基础上进行改造。
本次教程选用以下这款土培植物生长机,配合双模式灯板、大容量的土盆,更易于改装。
进行硬件拆机前,选用的植物生长机包含了以下功能:
整机有 AC 220V、DC 12 V、DC 5V、DC 3.3V 四种供电网络。本次改造的植物生长机涉及的集中供电网络采用以下方式完成:
AC 220V:接在 AC220V 50Hz 市电上。
DC 12 V:通过一个 S-120-12 开关电源接在市电AC 220V 上。
12V 散热风扇因为需要与加热灯联动,与加热灯相同的逻辑启动,为了节省 I/O 口,建议通过一个 220V 转 12V AC-DC 降压模块输出是 12V 400mA 的隔离开关电源模块,并联在加热灯的两端为散热风扇供电。
DC 5V:用于继电器供电、超声波加湿器供电,并降为 3.3V 为控制板和所有传感器供电。
模块参数:
DC 3.3V:通过 涂鸦三明治直流供电电源板 获得。该电源开发板具有 DC 12V、DC 5V 两个输入接口。
在 DC 12V 输入时,两个 SY8012B 芯片同时工作,为其他开发板部件提供 12V、5V、3.3V 直流供电。
在 DC 5V 输入时,一个 SY8012B 工作,为其他开发板部件提供 5V、3.3V 直流供电。
亚克力罩先采用 Adobe AutoCAD 等建模软件进行概念设计,最后生成工厂可加工的文件。相关设计注意点如下所示:
本教程设计了一款简单的亚克力温室罩,供您参考,您可以根据需求改进或者重新设计。点击下载激光雕刻 2D 图纸 ZIP 文件。
参考模型正面图:
参考模型背面图:
以下为涉及的物料清单,您可以根据设备需求做适当调整。
物料名称 | 规格 | 数量 |
---|---|---|
涂鸦三明治直流供电电源板 | 默认 | 1 |
自制涂鸦三明治 Wi-Fi 控制板 | 自制 | 1 |
涂鸦 USB 转 UART串口 | 默认 | 2 |
电子线 1007 26AWG 红 | 10米 | 1 |
电子线 1007 26AWG 黄 | 10米 | 1 |
电子线 1007 26AWG 黑 | 10米 | 1 |
常用热缩管包 | Φ2/3/4/5/6/8.0MM | 1 |
40P 彩排杜邦线 | 公对公 | 3 |
40P 彩排杜邦线 | 公对母 | 3 |
40P 彩排杜邦线 | 母对母 | 3 |
红黑并线 | RVB2*0.5 | 10 |
220V 转 12V AC-DC 降压模块 | 输出12V400mA | 1 |
全铜主机电源线机箱电源线 | 默认3*0.5平方 | 1 |
AC 电源插座 | 3PAC | 1 |
继电器模块 | 5V,四路 | 1 |
继电器模块 | 5V,一路 | 1 |
水位传感器 | 默认 | 1 |
光强度光照度模块 | BH1750 | 1 |
土壤湿度计检测模块 | 默认 | 1 |
HTU21 高精度温湿度传感器模块 | HTU21D | 1 |
穿墙免焊固定面板 | 12P 对接整套 | 2 |
陶瓷灯头爬宠爬虫加热灯灯座 | 二代陶瓷灯头 | 1 |
迷你陶瓷加热灯 | 75W | 1 |
电动喷雾器管子(用于水箱加水) | 1米 | 2 |
农用电动喷雾器电机(用于水箱加水) | 521内回流 | 1 |
上水壶茶壶茶具电硅胶无异味进水管(用于浇水) | 默认 | 2 |
上水壶配件水泵(用于浇水) | 2号上水泵 | 2 |
12V 转 5V 5A 直流电源降压模块 | 默认 | 1 |
12V 电脑机箱电源功放变频器散热风 | 6CM 12V 大风量 | 2 |
220 转 12V 10A 开关电源 | 12V10A | 1 |
水位保护加湿器模块 | 默认 | 1 |
有机玻璃胶(亚克力胶水) | 默认 | 1 |
不锈钢橱碰珠吸合器 | 免打孔5只装 | 1 |
缠绕管 10mm | 白色 | 1 |
缠绕管 8mm | 白色 | 1 |
缠绕管 6mm | 白色 | 1 |
缠绕管 4mm | 白色 | 1 |
尼龙垫片(用于绝缘安装电源板) | M360.5 | 1 |
M3 螺母 | M3 304(50粒) | 1 |
M3*16 螺丝 | M3*16(100个) | 1 |
M4*40 螺丝 | M4*40(20个) | 1 |
铜柱 M3*40 | M3*40(20个) | 1 |
沉头螺丝套装 | 304 沉头套装(M3-M5) | 1 |
高透明亚克力板定制 | 非标尺寸等制 | 1 |
亚克力透明合页 | 亚克力加大号(654210) | 4 |
基础土培植物生长机 | 土培 | 1 |
可以找到有切割设备的工厂或者工作室,按照上一步设计的亚克力模型 AutoCAD 图纸切割外壳零件。再按照图纸将外壳使用亚克力胶水组装起来。
在所有的物料准备好以后,就可以进行元件安装和接线了。您可以根据自己的个人喜好安装,安装时您需要注意以下几点:
加湿装置的制作可以很灵活,以下为制作示例,您可以根据收集的物料进行改动制作。
选择一个有内盖的瓶子:
固定吸水棉。
内盖钻孔,内盖孔径大小调整到可以固定吸水棒上端,在瓶内放置沙子或小石块固定吸水棒下段。
将水位感应线通过内盖孔插入瓶内。
固定起雾装置。
将起雾装置按如图所示方式放置在吸水棒上。
将外盖固定上,注意不能压太紧,压太紧不能出雾。如果没有类似的瓶子可以用热熔胶固定。
将所有元件安装好以后,再用绕线管整理线束,整体效果如下图所示。
完成整机搭建后,还需要在 IoT 平台上创建智能产品,创建产品后您才能获得相关嵌入式开发包,进行智能设备的 TuyaOS 开发。该产品代表了智能设备在 IoT 平台上的功能映射、包含了相关授权信息、产品配置等,方便植物生长机与 IoT 平台的通信。本小节步骤介绍了您在平台上创建植物生长机的主要步骤,详细步骤请参考 选品类创建产品。
进入 涂鸦智能 IoT 平台,点击 创建产品。
选择小家电 > 宠物 > 植物生长机。
选择自定义方案,输入产品名称,选择通讯协议为 Wi-Fi +蓝牙,点击创建产品。
在添加标准功能时,选择开关、水泵开关、当前温度、当前湿度、倒计时、倒计时剩余时间、故障告警。
(可选)要实现所有的设备功能,还需要根据植物生长机的功能需求自行创建额外的功能点。
点击添加功能按钮,编辑功能点名称、标识名,勾选数据类型和数据传输类型即可完成功能点创建。
添加最大温度、最小温度、最大湿度、最小湿度四个DP,用于设置植物生长机的温湿度控制区间。
添加水箱水量,数值型只上报类DP,用于上报水箱内水量的剩余情况。
添加灯光颜色,枚举型DP,用于控制补光灯颜色。
添加自动补光,布尔型DP,用于控制设备切换手动定时补光模式和自动补光模式。
设定完功能点后,下一步点击设备面板,选择 App 的面板样式。推荐选择开发调试面板,比较直观,且可以开启 DP 数据包的接收和发送,方便开发阶段调试使用。
至此,产品的创建基本完成,可以正式开始嵌入式软件部分的开发。
本 Demo 方案的嵌入式代码基于 BK7231 平台,使用涂鸦通用 Wi-Fi SDK 进行 SoC 低代码开发,具体环境可以拉取涂鸦 GitHub 库上的 Demo 例程或者直接下载已经包含了 SDK 环境的 Demo 例程。本小节采用了方式一来下载开发包。
下载方式二:在 GitHub 了解代码详情
将 Git 库克隆至本地后,打开文件目录,找到 apps
文件夹。apps
文件夹用于存放应用层代码,也就是demo的代码。在这里创建一个文件夹,命名为 bk7231t_plant_grow_mach_demo
,与 Demo 有关的所有源文件、头文件以及编译产物都将放到该文件夹中。
如果您是第一次在该平台上进行嵌入式开发,建议将 apps
文件下的 bk7231t_bl0973_1_plug_demo
中的tuya_device.c
和 tuya_device.h
文件都复制过来。
打开 tuya_device.c
文件,找的 device_init
函数:
OPERATE_RET device_init(VOID)
{
OPERATE_RET op_ret = OPRT_OK;
TY_IOT_CBS_S wf_cbs = {
status_changed_cb,\
gw_ug_inform_cb,\
gw_reset_cb,\
dev_obj_dp_cb,\
dev_raw_dp_cb,\
dev_dp_query_cb,\
NULL,
};
op_ret = tuya_iot_wf_soc_dev_init_param(hw_get_wifi_mode(),WF_START_SMART_FIRST,\
&wf_cbs,NULL,PRODECT_KEY,DEV_SW_VERSION);
if(OPRT_OK != op_ret) {
PR_ERR("tuya_iot_wf_soc_dev_init_param error,err_num:%d",op_ret);
return op_ret;
}
op_ret = tuya_iot_reg_get_wf_nw_stat_cb(wf_nw_status_cb);
if(OPRT_OK != op_ret) {
PR_ERR("tuya_iot_reg_get_wf_nw_stat_cb is error,err_num:%d",op_ret);
return op_ret;
}
op_ret= app_dltj_init(APP_DLTJ_NORMAL);
if(OPRT_OK != op_ret) {
PR_ERR("dltj init err!");
return op_ret;
}
op_ret = app_switch_init(APP_SW_MODE_NORMAL);
if(op_ret != OPRT_OK) {
return op_ret;
}
return op_ret;
}
在 BK7231T 平台的 SDK 环境中,device_init
函数为重要的应用代码入口。设备上电后,BK7231T 平台适配层运行完一系列初始化代码后就会调用该函数来初始化应用层。该函数主要做三件事:
调用 tuya_iot_wf_soc_dev_init_param()
接口初始化 SDK,配置了工作模式、配网模式,同时注册了各种回调函数并存入了固件 Key 和PID。
TY_IOT_CBS_S wf_cbs = {
status_changed_cb,\
gw_ug_inform_cb,\
gw_reset_cb,\
dev_obj_dp_cb,\
dev_raw_dp_cb,\
dev_dp_query_cb,\
NULL,
};
op_ret = tuya_iot_wf_soc_dev_init_param(hw_get_wifi_mode(),WF_START_SMART_FIRST,\
&wf_cbs,NULL,PRODECT_KEY,DEV_SW_VERSION);
if(OPRT_OK != op_ret) {
PR_ERR("tuya_iot_wf_soc_dev_init_param error,err_num:%d",op_ret);
return op_ret;
}
调用 tuya_iot_reg_get_wf_nw_stat_cb()
注册设备网络状态回调函数。
op_ret = tuya_iot_reg_get_wf_nw_stat_cb(wf_nw_status_cb);
if(OPRT_OK != op_ret) {
PR_ERR("tuya_iot_reg_get_wf_nw_stat_cb is error,err_num:%d",op_ret);
return op_ret;
}
调用应用层初始化函数:
op_ret= app_dltj_init(APP_DLTJ_NORMAL);
if(OPRT_OK != op_ret) {
PR_ERR("dltj init err!");
return op_ret;
}
op_ret = app_switch_init(APP_SW_MODE_NORMAL);
if(op_ret != OPRT_OK) {
return op_ret;
}
由于该文件是从别的 Demo 中复制过来的,所以这里调用的也是别的 Demo 的函数。此时就需要再实现一个本 Demo 自己的应用初始化函数:新建一个 app_plant.c
和对应头文件,实现 app_plant_init()
函数,然后在 device_init
中调用。
本 Demo 应用代码主要分三层来实现:
第一层就是在 app_plant.c
文件中实现的,大致内容如下:
app_plant_init()
调用第二层封装出的设备初始化接口,创建应用任务。
OPERATE_RET app_plant_init(IN APP_PLANT_MODE mode)
{
OPERATE_RET op_ret = OPRT_OK;
if(APP_PLANT_NORMAL == mode) {
// IO、传感器、pwm等初始化
plant_device_init();
// 创建I2C类传感器数据采集任务
xTaskCreate(sensor_data_get_iic_theard,"thread_data_get_iic",512,NULL,TRD_PRIO_3,NULL);
// 创建ADC类传感器数据采集任务
xTaskCreate(sensor_data_get_adc_theard,"thread_data_get_adc",512,NULL,TRD_PRIO_4,NULL);
// 创建数据处理任务
xTaskCreate(sensor_data_deal_theard,"thread_data_deal",512,NULL,TRD_PRIO_4,NULL);
// 创建dp点数据定时循环上报任务
xTaskCreate(sensor_data_report_theard,"thread_data_report",512,NULL,TRD_PRIO_4,NULL);
}else {
// 非产测模式
}
return op_ret;
}
app_report_all_dp_status()
上报所有DP数据:
VOID app_report_all_dp_status(VOID)
{
OPERATE_RET op_ret = OPRT_OK;
INT_T dp_cnt = 0;
dp_cnt = 12;
TY_OBJ_DP_S *dp_arr = (TY_OBJ_DP_S *)Malloc(dp_cnt*SIZEOF(TY_OBJ_DP_S));
if(NULL == dp_arr) {
PR_ERR("malloc failed");
return;
}
memset(dp_arr, 0, dp_cnt*SIZEOF(TY_OBJ_DP_S));
dp_arr[0].dpid = DPID_SWITCH_P;
dp_arr[0].type = PROP_BOOL;
dp_arr[0].time_stamp = 0;
dp_arr[0].value.dp_value = plant_ctrl_data.Switch;
......
op_ret = dev_report_dp_json_async(NULL,dp_arr,dp_cnt);
Free(dp_arr);
if(OPRT_OK != op_ret) {
PR_ERR("dev_report_dp_json_async relay_config data error,err_num",op_ret);
}
PR_DEBUG("dp_query report_all_dp_data");
return;
}
任务函数。
任务内循环调用的plant_get_iic_sensor_data()
、plant_get_adc_sensor_data()
、plant_ctrl_handle()
、plant_ctrl_all_off()
都是第二层的接口,实现在plant_control.c
文件中。
STATIC VOID sensor_data_get_iic_theard(PVOID_T pArg)
{
while(1) {
PR_DEBUG("plant_get_i2c_sensor_data");
vTaskDelay(TASKDELAY_SEC);
if(TRUE == plant_ctrl_data.Switch) {
plant_get_iic_sensor_data();
}
}
}
STATIC VOID sensor_data_get_adc_theard(PVOID_T pArg)
{
while(1) {
PR_DEBUG("plant_get_adc_sensor_data");
vTaskDelay(TASKDELAY_SEC*2);
if(TRUE == plant_ctrl_data.Switch) {
plant_get_adc_sensor_data();
}
}
}
STATIC VOID sensor_data_deal_theard(PVOID_T pArg)
{
while(1) {
vTaskDelay(TASKDELAY_SEC);
if(TRUE == plant_ctrl_data.Switch) {
plant_ctrl_handle();
}else {
plant_ctrl_all_off();
}
}
}
STATIC VOID sensor_data_report_theard(PVOID_T pArg)
{
while(1) {
vTaskDelay(TASKDELAY_SEC*5);
app_report_all_dp_status();
}
}
deal_dp_proc()
处理接受到的 DP 数据,通过识别 DP ID 来进行相应的数据接收处理。
VOID deal_dp_proc(IN CONST TY_OBJ_DP_S *root)
{
UCHAR_T dpid;
dpid = root->dpid;
PR_DEBUG("dpid:%d",dpid);
switch (dpid) {
case DPID_SWITCH_P:
PR_DEBUG("set switch:%d",root->value.dp_bool);
plant_ctrl_data.Switch = root->value.dp_bool;
break;
case DPID_PUMP:
PR_DEBUG("set pump:%d",root->value.dp_bool);
plant_ctrl_data.Pump = root->value.dp_bool;
break;
......
default:
break;
}
return;
}
实现了上述的几个函数后,应用层代码的大概结构就确定下来了。接下来,需要实现上文提到的被调用的第二层接口,这些接口都放在 Demo 的 plant_control.c
文件中。在下文内容中,将按照温湿度、光照、土壤水份等不同的控制功能一步步解说 Demo 例程。
要实现温湿度控制,首先要做的就是采集到温湿度。本 Demo 方案采集温湿度的方式是使用 SHT21 温湿度传感器,该传感器是 I2C 协议通讯,因此您首先需要根据该传感器的 技术手册 编写传感器驱动代码。在完成驱动代码后,再封装出传感器的初始化、数据采集、数据换算等接口。
本 Demo 有关 SHT21 传感器的驱动和外部接口都实现在 sht21.c
文件中,封装的外部接口都在 plant_control.c
中被调用:
tuya_sht21_init(sht21_init_t* param)
传感器初始化,请求参数为一个包含 SDA、SCL 对应 I/O 口和解析度的结构体的指针。
typedef struct
{
UCHAR_T SDA_PIN; ///< SDA pin
UCHAR_T SCL_PIN; ///< SCL pin
sht21_resolution_t RESOLUTION;
}sht21_init_t;
在plant_control.c
中定义这个结构体变量,并在plant_device_init()
里调用初始化。
#define IIC_SDA_PIN (6)
#define IIC_SCL_PIN (7)
STATIC sht21_init_t sht21_int_param = {IIC_SDA_PIN, IIC_SCL_PIN, SHT2x_RES_10_13BIT};
VOID plant_device_init(VOID)
{
// SHT21 IIC driver init
tuya_sht21_init(&sht21_int_param);
}
初始化完成后,就可以获取环境温湿度了。因为植物生长机需要不断的获取环境参数,所以代码上也需要不断的使用传感器数据获取接口。在上一节内容中,提到在 app_plant.c
文件中有创建过各个功能逻辑任务,其中有一个任务函数循环调用了 plant_control.c
的 plant_get_iic_sensor_data()
函数,所以您需要在该接口中调用 SHT21 传感器的数据采集接口 tuya_sht21_measure()
和计算接口 tuya_sht21_cal_RH()
,两个接口的传参都为一个用于切换是获取温度还是湿度的枚举值。
VOID plant_get_iic_sensor_data(VOID)
{
SHORT_T hum;
SHORT_T temp;
tuya_sht21_init(&sht21_int_param);
hum = tuya_sht21_measure(HUMIDITY);
device_data.humidity = tuya_sht21_cal_RH(hum);
if(device_data.humidity > 0){ // 剔除小于0的无效湿度值
plant_report_data.Humidity_current = (UCHAR_T)device_data.humidity;
PR_NOTICE("humidity = %d",plant_report_data.Humidity_current);
}
temp = tuya_sht21_measure(TEMP);
device_data.temperature = tuya_sht21_cal_temperature(temp);
plant_report_data.Temp_current = (UCHAR_T)device_data.temperature;
PR_NOTICE("tempre = %d",plant_report_data.Temp_current);
}
获取到环境温湿度值后,就可以和温湿度设定值做一个判断。在创建产品的时候新建了最大最小温度和最大最小湿度四个自定义功能点,所以可以在手机 App 上进行设置,并通过云端下发给设备。下发的 DP 数据在 app_plant.c
的 deal_dp_proc()
函数中进行处理。
VOID deal_dp_proc(IN CONST TY_OBJ_DP_S *root)
{
UCHAR_T dpid;
dpid = root->dpid;
PR_DEBUG("dpid:%d",dpid);
switch (dpid) {
......
case DPID_TEMP_MAX:
PR_DEBUG("set temp max:%d",root->value.dp_value);
plant_ctrl_data.Temp_max = root->value.dp_value;
break;
case DPID_HUMIDITY_MAX:
PR_DEBUG("set humidity max:%d",root->value.dp_value);
plant_ctrl_data.Humidity_max = root->value.dp_value;
break;
case DPID_TEMP_MIN:
PR_DEBUG("set temp min:%d",root->value.dp_value);
plant_ctrl_data.Temp_min = root->value.dp_value;
break;
case DPID_HUMIDITY_MIN:
PR_DEBUG("set humidity min:%d",root->value.dp_value);
plant_ctrl_data.Humidity_min = root->value.dp_value;
break;
......
default:
break;
}
return;
}
在 app_plant.c
中创建过一个任务用于数据判断和 I/O 设备控制,该任务循环调用了 plant.control.c
中的 plant_ctrl_handle()
函数,所有有关具体的控制逻辑实现都放在 plant_ctrl_handle()
中。本 Demo 方案用于控制温湿度的器件为一个加湿器、一个加热灯和一个风扇,通过继电器实现用 I/O 的高低电平控制这些器件的开和关。控制 I/O 电平需要用到 SDK 封装好的接口 tuya_gpio_inout_set()
和 tuya_gpio_write()
。温湿度控制相关代码如下:
#define HUMIDIFIER_PORT (24)
#define HUMIDIFIER_LEVEL LOW
#define HEATING_ROD_PORT (20)
#define HEATING_ROD_LEVEL LOW
#define COOL_DOWN_FAN_PORT (21)
#define COOL_DOWN_FAN_LEVEL LOW
STATIC VOID __ctrl_gpio_init(CONST TY_GPIO_PORT_E port, CONST BOOL_T high)
{
// 设置 I/O 为输出模式
tuya_gpio_inout_set(port, FALSE);
// 设置 I/O 电平
tuya_gpio_write(port, high);
}
VOID plant_device_init(VOID)
{
// SHT21 IIC driver init
tuya_sht21_init(&sht21_int_param);
// gpio init
__ctrl_gpio_init(HUMIDIFIER_PORT, HUMIDIFIER_LEVEL);
__ctrl_gpio_init(COOL_DOWN_FAN_PORT, COOL_DOWN_FAN_LEVEL);
__ctrl_gpio_init(HEATING_ROD_PORT, HEATING_ROD_LEVEL);
}
STATIC VOID __passive_ctrl_module_temp_humidity(VOID)
{
if(device_data.humidity < plant_ctrl_data.Humidity_min) {
tuya_gpio_write(HUMIDIFIER_PORT, !HUMIDIFIER_LEVEL);
}else {
tuya_gpio_write(HUMIDIFIER_PORT, HUMIDIFIER_LEVEL);
}
if(device_data.temperature < plant_ctrl_data.Temp_min) {
tuya_gpio_write(HEATING_ROD_PORT, !HEATING_ROD_LEVEL);
}else {
tuya_gpio_write(HEATING_ROD_PORT, HEATING_ROD_LEVEL);
}
if((device_data.temperature > plant_ctrl_data.Temp_max)||(device_data.humidity > plant_ctrl_data.Humidity_max)) {
tuya_gpio_write(COOL_DOWN_FAN_PORT,!COOL_DOWN_FAN_LEVEL);
}else {
tuya_gpio_write(COOL_DOWN_FAN_PORT,COOL_DOWN_FAN_LEVEL);
}
}
VOID plant_ctrl_handle(VOID)
{
PR_DEBUG("enter ctrl handle");
__passive_ctrl_module_temp_humidity();
}
本 Demo 方案使用的 BH1750 照度检测传感器使用的也是 I2C 协议的。根据该传感器的 数据手册 编写传感器驱动代码。在完成驱动代码后,封装出传感器的初始化、数据采集等接口。本 Demo 有关 BH1750 传感器的驱动和外部接口都实现在 bh1750.c
文件中,封装的外部接口都在 plant_control.c
中被调用:
tuya_bh1750_init(sht21_init_t* param)
传感器初始化,传参为一个包含 SDA、SCL 对应 I/O 的结构体的指针。
typedef struct
{
UCHAR_T SDA_PIN; ///< SDA pin
UCHAR_T SCL_PIN; ///< SCL pin
}bh1750_init_t;
在 plant_control.c
中定义这个结构体变量,并在 plant_device_init()
里调用初始化:
#define IIC_SDA_PIN (6)
#define IIC_SCL_PIN (7)
STATIC bh1750_init_t bh1750_int_param = {IIC_SDA_PIN, IIC_SCL_PIN};
VOID plant_device_init(VOID)
{
......
// SHT21 IIC driver init
tuya_bh1750_init(&bh1750_int_param);
......
}
初始化完成后,在 plant_get_iic_sensor_data()
函数里调用 BH1750 传感器的数据采集接口 tuya_bh1750_get_bright_value()
获取光照强度值。由于调试过程中发现光照传感器和温湿度传感器的采集中间不加延时的话会影响通讯,因此做了点改动让每次进入 plant_get_iic_sensor_data()
函数时只启用其中一个传感器:
VOID plant_get_iic_sensor_data(VOID)
{
SHORT_T hum;
SHORT_T temp;
switch (IIC_SELECT_FLAG)
{
case 0:
tuya_sht21_init(&sht21_int_param);
hum = tuya_sht21_measure(HUMIDITY);
device_data.humidity = tuya_sht21_cal_RH(hum);
if(device_data.humidity > 0){ // 剔除小于0的无效湿度值
plant_report_data.Humidity_current = (UCHAR_T)device_data.humidity;
PR_NOTICE("humidity = %d",plant_report_data.Humidity_current);
}
temp = tuya_sht21_measure(TEMP);
device_data.temperature = tuya_sht21_cal_temperature(temp);
plant_report_data.Temp_current = (UCHAR_T)device_data.temperature;
PR_NOTICE("tempre = %d",plant_report_data.Temp_current);
IIC_SELECT_FLAG = 1;
break;
case 1:
tuya_bh1750_init(&bh1750_int_param);
device_data.light_intensity_value = tuya_bh1750_get_bright_value();
PR_NOTICE("light_intensity_value = %d",device_data.light_intensity_value);
IIC_SELECT_FLAG = 0;
break;
default:
break;
}
}
光照控制方面的设定值没有创建对应的 DP,无法用 App 设置,只在代码内设定了一个数值,通过控制灯光亮度来使光照传感器采集值不断逼近该数值,并给定了一个误差范围值来防止在亮度临界点时发生的灯光不断闪烁的现象。
#define ACCURACY (2000) // 误差范围值
#define light_value_set (12000) // 亮度设定值 unit:lux
本 Demo 方案通过输出 PWM 波的方式来控制灯板的亮度,有关 PWM 的初始化和输出控制函数接口都实现在 plant_pwm.c
中,在plant_device_init()
中初始化PWM,并在 plant_ctrl_handle()
中调用实现灯光控制逻辑的接口:
USER_PWM_DUTY_T user_pwm_duty = {0,0};
VOID plant_device_init(VOID)
{
......
plant_pwm_init();
......
}
STATIC VOID __passive_ctrl_module_light(VOID)
{
if(IIC_SELECT_FLAG){ // 若上一次启用的 I2C 传感器为温湿度传感器
return;
}
if((TRUE == plant_ctrl_data.Auto_switch)) { // 自动补光开关为开
USHORT_T current = device_data.light_intensity_value;
USHORT_T set = light_value_set;
if((current - set) > ACCURACY) { // 当前光照强度大于设定值且不在误差范围内
if((current - set) >= 200) {
if(plant_ctrl_data.Bright_value >= 50)plant_ctrl_data.Bright_value -= 50;
}else if((current - set) > 150) {
if(plant_ctrl_data.Bright_value >= 20)plant_ctrl_data.Bright_value -= 20;
}else {
if(plant_ctrl_data.Bright_value >= 1)plant_ctrl_data.Bright_value--;
}
}else if((set - current) > ACCURACY) { // 当前光照强度小于设定值且不在误差范围内
if((set - current) >= 200) {
if(plant_ctrl_data.Bright_value <= 950)plant_ctrl_data.Bright_value += 50;
}else if((set - current) > 150) {
if(plant_ctrl_data.Bright_value <= 980)plant_ctrl_data.Bright_value += 20;
}else {
if(plant_ctrl_data.Bright_value <= 999)plant_ctrl_data.Bright_value++;
}
}
}
}
STATIC VOID __initiative_ctrl_module_light(VOID)
{
if(TRUE == plant_ctrl_data.Auto_switch) { // 自动补光开关为开
PR_NOTICE("Ligth open !!!!");
if(plant_ctrl_data.Light_color == red) { // 灯光颜色设为红灯
user_pwm_duty.duty_red = plant_ctrl_data.Bright_value;
user_pwm_duty.duty_blue = 0;
}else if(plant_ctrl_data.Light_color == blue) { // 灯光颜色设为蓝灯
user_pwm_duty.duty_blue = plant_ctrl_data.Bright_value;
user_pwm_duty.duty_red = 0;
}else {
user_pwm_duty.duty_blue = plant_ctrl_data.Bright_value;
user_pwm_duty.duty_red = user_pwm_duty.duty_blue;
}
plant_pwm_set(&user_pwm_duty);
}else { // 自动补光开关为关 用户手动定时控制
if(plant_ctrl_data.Light_color == red) {
user_pwm_duty.duty_red = 1000;
user_pwm_duty.duty_blue = 0;
}else if(plant_ctrl_data.Light_color == blue) {
user_pwm_duty.duty_blue = 1000;
user_pwm_duty.duty_red = 0;
}else {
user_pwm_duty.duty_red = 1000;
user_pwm_duty.duty_blue = 1000;
}
if((IsThisSysTimerRun(light_timer) == FALSE)&&(plant_ctrl_data.Countdown_set != cancel)) {
light_flag_min = (USHORT_T)plant_ctrl_data.Countdown_set * 60;
plant_pwm_set(&user_pwm_duty);
sys_start_timer(light_timer,1000*60,TIMER_CYCLE);
}else if(plant_ctrl_data.Countdown_set == cancel) {
user_pwm_duty.duty_blue = 0;
user_pwm_duty.duty_red = 0;
plant_pwm_set(&user_pwm_duty);
light_flag_min = 0;
sys_stop_timer(light_timer);
}else if(IsThisSysTimerRun(light_timer) == TRUE) {
plant_pwm_set(&user_pwm_duty);
}
// 保存定时剩余时间 单位分钟
plant_report_data.Countdown_left = light_flag_min;
}
}
VOID plant_ctrl_handle(VOID)
{
......
__passive_ctrl_module_light();
__initiative_ctrl_module_light();
}
本 Demo 方案使用的土壤湿度检测传感器可以根据土壤的湿度情况输出模拟量,因此代码上就需要通过 ADC 采集模拟量转换为数字量的方式来监测土壤湿度。
在 app_plant.c
文件中,创建的获取 ADC 采集任务中循环调用了 plant_control.c
的plant_get_adc_sensor_data()
,有关 ADC 采集的代码都放在该函数接口内:
VOID plant_get_adc_sensor_data(VOID)
{ // 控制开关模拟芯片选择土壤湿度的通道
rs2255_channel_checkout(SOIL_MOISTURE_SENSOR_PORT);
tuya_hal_adc_init(&tuya_adc);
tuya_hal_adc_value_get(TEMP_ADC_DATA_LEN, &device_data.soil_humidity);
PR_NOTICE("soil_humidity = %d",device_data.soil_humidity);
tuya_hal_adc_finalize(&tuya_adc);
}
获取土壤湿度值后,根据湿度情况控制水泵是否打开来实现自动浇水。在plant_ctrl_handle()
中调用实现控制浇水逻辑的接口。
引入了ADD_WATER_COUNT
、ADD_WATER_READY
两个变量,实现水泵每开启一段时间后就会关闭一段时间,防止浇水过度。
STATIC VOID __passive_ctrl_module_soil_humidity(VOID)
{
if(device_data.soil_humidity > plant_ctrl_data.Soil_humidity_threshold) {
if(ADD_WATER_READY) {
tuya_gpio_write(WATER_VALVE_PORT, !WATER_VALVE_LEVEL);
ADD_WATER_COUNT++;
if(ADD_WATER_COUNT > 5) {
ADD_WATER_READY = 0;
}
} else{
tuya_gpio_write(WATER_VALVE_PORT, WATER_VALVE_LEVEL);
ADD_WATER_COUNT++;
if(ADD_WATER_COUNT >15) {
ADD_WATER_READY = 1;
ADD_WATER_COUNT = 0;
}
}
}else {
ADD_WATER_READY = 1;
ADD_WATER_COUNT = 0;
tuya_gpio_write(WATER_VALVE_PORT, WATER_VALVE_LEVEL);
}
}
VOID plant_ctrl_handle(VOID)
{
......
__passive_ctrl_module_soil_humidity();
......
}
给土壤浇水的水泵是从水箱中抽水的。当水箱中的水量过少时则,需要另一个水泵给水箱加水。控制水箱自动加水会使用水位传感器,该传感器通过输出模拟量的大小来反映测量区域水位的位置。因此,和土壤湿度传感器一样,水位传感器也使用 ADC 采集模拟量转换数字量:
rs2255_init()
为模拟开关芯片的初始化函数,用于解决 I/O 不够用的问题。由于使用的控制脚和做为SDA、SCL的是同一对 I/O,所以在每次采集 ADC 数据时都要重新初始化。I2C 传感器的初始化也同理。
VOID plant_get_adc_sensor_data(VOID)
{
rs2255_init();
switch (ADC_SELECT_FLAG)
{
case 0:
rs2255_channel_checkout(WATER_SENSOR_PORT);
tuya_hal_adc_init(&tuya_adc);
tuya_hal_adc_value_get(TEMP_ADC_DATA_LEN, &device_data.water_tank_value);
PR_NOTICE("water_tank_value = %d",device_data.water_tank_value);
ADC_SELECT_FLAG = 1;
break;
case 1:
rs2255_channel_checkout(SOIL_MOISTURE_SENSOR_PORT);
tuya_hal_adc_init(&tuya_adc);
tuya_hal_adc_value_get(TEMP_ADC_DATA_LEN, &device_data.soil_humidity);
PR_NOTICE("soil_humidity = %d",device_data.soil_humidity);
ADC_SELECT_FLAG = 0;
break;
default:
break;
}
tuya_hal_adc_finalize(&tuya_adc);
}
在 plant_ctrl_handle()
中调用实现水箱水量控制的接口。
#define WATER_PUMP_PORT (22)
#define WATER_PUMP_LEVEL LOW
STATIC VOID __initiative_ctrl_module_pump(VOID)
{
// 根据水位传感器值转换剩余水量百分比,用于上报
if(device_data.water_tank_value < 1700) {
plant_report_data.Water_remain = 10;
}else if(device_data.water_tank_value < 2500) {
plant_report_data.Water_remain = 25;
}else if(device_data.water_tank_value < 2700) {
plant_report_data.Water_remain = 50;
}else if(device_data.water_tank_value < 2900) {
plant_report_data.Water_remain = 75;
}else if(device_data.water_tank_value >= 3000) {
plant_report_data.Water_remain = 100;
}
if(TRUE == plant_ctrl_data.Pump){ // 若水泵开关为开
PR_NOTICE("water pump open !!!!");
tuya_gpio_write(WATER_PUMP_PORT,!WATER_PUMP_LEVEL);
}else {
tuya_gpio_write(WATER_PUMP_PORT,WATER_PUMP_LEVEL);
}
if(device_data.water_tank_value >= 3000) { // 当水量接近测量上限时关闭水泵
PR_NOTICE("water tank is full !!!!");
tuya_gpio_write(WATER_PUMP_PORT,WATER_PUMP_LEVEL);
plant_ctrl_data.Pump = FALSE;
}
}
VOID plant_ctrl_handle(VOID)
{
......
__initiative_ctrl_module_pump();
......
}
至此,本 Demo 的大部分控制逻辑代码就基本完成了。在完善 DP 发送和接收部分后,即可开始后续的功能调试。如果您想了解 Demo 代码的更多细节,可以自行查看 Demo 例程。
在 Linux 终端输入命令运行 SDK 环境目录下的 build_app.sh
脚本来编译代码生成固件。固件生成路径为 apps
> APP_PATH
> output
。
命令格式:
build_app.sh <APP_PATH> <APP_NAME> <APP_VERSION>
命令示例:
/home/share/samba/ci/ty_iot_wf_bt_sdk_bk7231t$ sudo sh build_app.sh apps/bk7231t_plant_grow_mach_demo bk7231t_plant_grow_mach_demo 1.0.0
成功返回示例:若出现下图所示提示,则表示编译成功,固件已经生成。
将固件烧录至模组后,即可开始功能调试阶段。有关烧录和授权方式请参考 WB 系列模组烧录授权。
烧录授权完成后,植物生长机就可以正常配网了。先连接 Wi-Fi,打开蓝牙,按照配网流程成功配网后,即可使用手机 App 控制植物生长机。本教程采用涂鸦智能 App 作为移动控制端,您也可以采用其他方式构建移动应用,详情请参考 客户端。
以下内容为本 Demo 设备调试流程和结果:
在涂鸦智能 App 上为植物生长机打开开关,设定灯光定时,灯板成功点亮。
设定灯光颜色,灯板成功变色。
设定灯光定时为 Cancel,灯板熄灭。
打开自动补光开关,灯板点亮,亮度不停改变,直至逼近预设值后停止。
在涂鸦智能 App 上,可通过当前温度和当前湿度一栏看到环境温湿度值。
设定最小湿度为比当前湿度大的值,可以看到加湿器成功启动。湿度上升至最小湿度以上时,加湿器成功关闭。
设定最小温度为比当前温度大的值,可以发现加热灯成功启动。温度上升至最小温度以上时,加热灯停止运作。
按照同样的方法,分别设定最大温度和湿度值为比环境温湿度还要小的值,可以看到风扇都会打开。此风扇兼顾除湿和降温两个功能。
可通过水箱水量一栏看到大概的水量百分比。
在涂鸦智能 App 上为植物生长机打开水泵开关,水箱水泵成功开启。
将水位传感器插入水中至水刚好末过传感器测量区的位置,模拟水箱满水的状态,水泵停止运作,App 上的水泵开关被复位。
水箱水位变为 100。
全部功能调试完成后,一款包含自动手机 App 远程遥控、数据监测(温湿度、光照、土壤湿度、水箱水位等)、远程自动控制(补光、加湿、加温、通风、水箱加水、浇水等)的植物生长机即完成制作。
至此,恭喜您完成了一款智能植物生长机的原型开发。基于涂鸦 IoT 平台,您可以更加方便的搭建更多智能产品原型,加速智能产品的开发流程。