涂鸦智能植物生长机lite(嵌入式篇)

Posted 三明治开发社区

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了涂鸦智能植物生长机lite(嵌入式篇)相关的知识,希望对你有一定的参考价值。

上一篇文章我们分享了DIY一个涂鸦智能植物生长机需要的硬件以及结构件的物料,接下来我们这篇文章我们主要来聊一下嵌入式部分。

1、产品创建

创建产品

  • 选择自定义方案,输入产品名称,选择通讯协议为WIFI+蓝牙,点击创建产品。
  • 添加标准功能,选择“开关”、“水泵开关”、“倒计时”、“倒计时剩余时间”、“故障告警”,功能点名称、枚举值等可自行编辑修改。

选择标准功能

要实现所有的设备功能,还需要根据功能需求自行创建额外的DP功能点。点击添加功能按钮,编辑功能点名称、标识名,勾选数据类型和数据传输类型即可完成功能点创建。

  • 添加红灯设置、绿灯设置、蓝灯设置、暖灯设置四个DP点,用于设置四路 LED 灯的亮度,实现 RGBW 调光的功能。
  • 添加灯光开关,布尔型DP点,用于控制灯的开关。
  • 添加土壤湿度等级,枚举型DP点,用于设置水泵自动浇水的阈值等级。

添加自定义功能

  • 设定完功能点后,下一步点击设备面板,选择app的面板样式。推荐选择开发调试面板,比较直观,且可以开到dp数据包的接收和发送,方便开发阶段调试使用。

至此,产品的创建基本完成,可以正式开始嵌入式软件部分的开发。

2、嵌入式开发

嵌入式代码基于BK7231平台,使用涂鸦通用Wi-Fi SDK进行SOC开发,具体代码可下载查看demo例程。

1.应用层入口

打开demo例程,其中的apps文件夹内就是demo的应用代码。应用代码结构如下:

├── src	
|    ├── plant_driver
|    |      └── plant_pwm.c         //驱动灯板,调用soc层的pwm接口进行再封装
|    ├── plant_soc               //tuya SDK soc层接口相关文件
|    ├── tuya_device.c           //应用层入口文件
|    ├── app_plant.c             //植物生长机主要应用层
|    └── plant_control.c         //植物生长机各个功能组件的控制逻辑相关
|
├── include				//头文件目录
|    ├── plant_driver
|    |      └── plant_pwm.h
|    ├── plant_soc
|    ├── tuya_device.h
|    ├── app_plant.h
|    └── plant_control.h
|
└── output              //编译产物

打开tuya_device.c文件,找到device_init函数:

OPERATE_RET device_init(VOID)
{
  OPERATE_RET op_ret = OPRT_OK;
  
  UCHAR_T connect_mode = 0;

  PR_NOTICE("goto device_init!!!");

  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,
  };

  connect_mode = GWCM_OLD;

  op_ret = tuya_iot_wf_soc_dev_init_param(connect_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_plant_init(APP_PLANT_NORMAL);
  if(OPRT_OK != op_ret) {
      PR_ERR("plant init err!");
      return op_ret;
  }

  return op_ret;
}

在BK7231t平台的SDK环境中,该函数为重要的应用代码入口,设备上电后平台适配层运行完一系列初始化代码后就会调用该函数来进行应用层的初始化操作。

该函数做了三件事:

  • 调用tuya_iot_wf_soc_dev_init_param()接口进行SDK初始化,配置了工作模式、配网模式,同时注册了各种回调函数并存入了PID(代码中宏定义为PRODECT_KEY)。
     
   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,
   };

   connect_mode = GWCM_OLD;

   op_ret = tuya_iot_wf_soc_dev_init_param(connect_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_plant_init(APP_PLANT_NORMAL);
   if(OPRT_OK != op_ret) {
       PR_ERR("plant init err!");
       return op_ret;
   }

2.应用结构


本demo应用代码主要分三层来实现:

  • 最底层为pwm和各个传感器的驱动代码,封装出传感器的初始化、采集等接口,但由于本demo为植物生长机的小型版本,只使用了ADC采集型的土壤湿度传感器,故此层只涉及到pwm相关接口的调用;
  • 第二层为控制逻辑部分的代码,调用驱动层的传感器接口,实现各个组件的控制逻辑,封装出数据处理轮询接口;
  • 第一层为主要应用层,创建应用任务调用第二层的接口,同时处理DP点数据的上报和接受解析。

第一层就是在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();

        // 创建ADC类传感器数据采集任务
        tuya_hal_thread_create(NULL, "thread_data_get_adc", 512*4, TRD_PRIO_4, sensor_data_get_adc_theard, NULL);

        // 创建数据处理任务
        tuya_hal_thread_create(NULL, "thread_data_deal", 512*4, TRD_PRIO_4, sensor_data_deal_theard, NULL);

        // 创建dp点数据定时循环上报任务
        tuya_hal_thread_create(NULL, "thread_data_report", 512*4, TRD_PRIO_4, sensor_data_report_theard, 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_adc_sensor_data()plant_ctrl_handle()plant_ctrl_all_off()都是第二层的接口,实现在plant_control.c文件中
STATIC VOID sensor_data_get_adc_theard(PVOID_T pArg)
{   
    while(1) {

        PR_DEBUG("plant_get_adc_sensor_data");
        tuya_hal_system_sleep(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) {
        tuya_hal_system_sleep(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) {
        tuya_hal_system_sleep(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例程。

3.灯光控制

本demo方案实现了RGBW四路调光功能,可通过APP调节各个颜色的灯的亮度值,从而调配出植物生长所需要的不同类型的光照。

  • 通过输出PWM波的方式来调整灯的亮度,有关PWM的初始化和输出控制函数接口都实现在plant_pwm.c中,在 plant_device_init() 中初始化PWM,并在plant_ctrl_handle() 中调用实现灯光控制逻辑的接口:
     
USER_PWM_DUTY_T user_pwm_duty = {0};

VOID plant_device_init(VOID)
{
    ......
    plant_pwm_init();
    ......
}

STATIC VOID __set_pwm_duty(VOID)
{
    user_pwm_duty.duty_red = (USHORT_T)(((float)plant_ctrl_data.Red_value/255.0)*1000);
    user_pwm_duty.duty_green = (USHORT_T)(((float)plant_ctrl_data.Green_value/255.0)*1000);
    user_pwm_duty.duty_blue = (USHORT_T)(((float)plant_ctrl_data.Blue_value/255.0)*1000);
    user_pwm_duty.duty_warm = (USHORT_T)(((float)plant_ctrl_data.Warm_value/255.0)*1000);
}

STATIC VOID __initiative_ctrl_module_light(VOID)
{   
    if (plant_ctrl_data.Countdown_set != cancel)
    {
        if(IsThisSysTimerRun(light_timer) == FALSE) {
            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 {
            plant_pwm_set(&user_pwm_duty);
        }
    }else {
        light_flag_min = 0;
        if(TRUE == plant_ctrl_data.Light_switch) {
            plant_pwm_set(&user_pwm_duty);
        }else {
            plant_pwm_off();
        }
    }
    plant_report_data.Countdown_left = light_flag_min;                                                                       
}

VOID plant_ctrl_handle(VOID)
{   
    ......
    __set_pwm_duty();
    __initiative_ctrl_module_light();
}

4.土壤湿度控制

本demo方案使用的土壤湿度检测传感器可以根据土壤的湿度情况输出模拟量,因此代码上就需要通过ADC采集模拟量转换为数字量的方式来监测土壤湿度。

在app_plant.c文件中创建的获取ADC采集任务中循环调用了plant_control.c的plant_get_adc_sensor_data(),所有有关adc采集的代码都放在该函数接口内:

VOID plant_get_adc_sensor_data(VOID)
{   
    tuya_hal_adc_init(&tuya_adc);

    tuya_hal_adc_value_get(TEMP_ADC_DATA_LEN, &soil_moisture_value);

    PR_NOTICE("water_tank_value = %d",soil_moisture_value);

    tuya_hal_adc_finalize(&tuya_adc);
}

获取土壤湿度值后,将该值与预设的三档湿度阈值做比较,决定水泵是否需要打开并浇水。具体与哪一档阈值比较是在app上设置的。除了自动控制浇水的模式外,本demo也可以设置为手动浇水模式,通过面板上的水泵开关直接控制是否浇水。在plant_ctrl_handle()中调用实现控制浇水逻辑的接口:

  • 引入了ADD_WATER_COUNT、ADD_WATER_READY两个变量,实现水泵每开启一段时间后就会关闭一段时间,防止浇水过度。
STATIC VOID __passive_ctrl_module_soil_humidity(VOID)
{   
    if(!ADD_WATER_READY) {
        tuya_gpio_write(WATER_PUMP_PORT, WATER_PUMP_LEVEL);
        ADD_WATER_COUNT++;
        if(ADD_WATER_COUNT >15) {
            ADD_WATER_READY = 1;
            ADD_WATER_COUNT = 0;
        }
    }

    switch (plant_ctrl_data.soil_moisture_level) {
    
    case manual_control: 
        ADD_WATER_COUNT == 0;
        ADD_WATER_READY == 1;
        break;
    case low:
        if(soil_moisture_value > soil_moisture_threshold.Low) {
            if(ADD_WATER_READY) {
                tuya_gpio_write(WATER_PUMP_PORT, !WATER_PUMP_LEVEL);
                ADD_WATER_COUNT++;
                if(ADD_WATER_COUNT > 2) {
                    ADD_WATER_READY = 0;
                }
            }
        }else {
            tuya_gpio_write(WATER_PUMP_PORT, WATER_PUMP_LEVEL);
        }
        break;
    case medium:
        if(soil_moisture_value > soil_moisture_threshold.Medium) {
            if(ADD_WATER_READY) {
                tuya_gpio_write(WATER_PUMP_PORT, !WATER_PUMP_LEVEL);
                ADD_WATER_COUNT++;
                if(ADD_WATER_COUNT > 2) {
                    ADD_WATER_READY = 0;
                }
            }
        }else {
            tuya_gpio_write(WATER_PUMP_PORT, WATER_PUMP_LEVEL);
        }
        break;
    case high:
        if(soil_moisture_value > soil_moisture_threshold.High) {
            if(ADD_WATER_READY) {
                tuya_gpio_write(WATER_PUMP_PORT, !WATER_PUMP_LEVEL);
                ADD_WATER_COUNT++;
                if(ADD_WATER_COUNT > 2) {
                    ADD_WATER_READY = 0;
                }
            }
        }else {
            tuya_gpio_write(WATER_PUMP_PORT, WATER_PUMP_LEVEL);
        }
        break;
    default:
        break;
    }
}

STATIC VOID __initiative_ctrl_module_pump(VOID)
{   
    if(plant_ctrl_data.soil_moisture_level != manual_control) {
        return;        
    }
    
    if(TRUE == plant_ctrl_data.Pump) {
        tuya_gpio_write(WATER_PUMP_PORT, !WATER_PUMP_LEVEL);
    }else {
        tuya_gpio_write(WATER_PUMP_PORT, WATER_PUMP_LEVEL);
    }
    
}

VOID plant_ctrl_handle(VOID)
{   
    ......
    // 水泵自动控制
    __passive_ctrl_module_pump();
	// 水泵手动控制
    __initiative_ctrl_module_pump();
    ......
}

至此,本demo的大部分控制逻辑代码就基本完成了,在完善dp点上报和接收部分后即可开始后续的功能调试。想了解demo代码的其他细节请自行查看demo例程。

5.编译和烧录

在linux终端输入指令运行SDK环境目录下的build_app.sh脚本来编译代码生成固件,指令格式为 sh build_app.sh APP_PATH APP_NAME APP_VERSION:

若出现下图所示提示,则表示编译成功,固件已经生成:

固件生成路径为:apps->APP_PATH->output

将固件烧录至模组即可开始功能调试阶段,有关烧录和授权方式请参照文档: WB系列模组烧录授权

  开发完成,接下来我们就配网试一下吧。

以上是关于涂鸦智能植物生长机lite(嵌入式篇)的主要内容,如果未能解决你的问题,请参考以下文章

涂鸦智能植物生长机lite(硬件篇)

涂鸦智能全功能智慧植物生长系统(嵌入式)

涂鸦智能全功能智慧植物生长系统(硬件)

DIY植物生长机——《取经篇》

植物生长机——《拆机篇》

涂鸦智能全功能智慧植物生长系统(展示)