如何将四线散热风扇接入涂鸦Cloud平台

一、前言

终于把这个去年烂尾的支线小项目的坑给填完了,其实做这小项目最主要的是想将第三方芯片(ESP32)通过Tuya OS LinkSDK(以下简称LinkSDK)方案接入涂鸦云,正好手上又有个这么四线的散热风扇那就开始整活吧,将手把手教你怎么通过用LinkSDK方案接入涂鸦云

二、介绍

2.1 功能

具体主要实现以下功能

  • 接入涂鸦云

  • 控制开、关

  • 风速调节

  • 转速反馈

  • 2.2 方案

    将某一个设备接入云端实现智能化控制,2023年了,这个估计早已近都被玩烂了。作为一名Tuya开发者,本项目主要和大家分享如何通过第三方芯片快速接入涂鸦平台。将这些功能抽象成一个个DP点,借助涂鸦平台可以实现DP点的上报下发进行控制,从而达到我们需求。

    这里的四线散热风扇中的四线包括(VCC、GND、调速线以及测速线)。由于这种四线风扇是12V供电而ESP32是3.3V所以在硬件上需要转换的电路板,供电问题解决了之后那剩下的也基本上没什么问题了。开关的话直接可以通过一个GPIO进行输出高低电平;风扇的转速调节可以通过改变PWM方式达到;通过前期测试得知这种风扇的转速输出的其实是一种脉宽方波的 那就很好解决了 而这里面的电机只有一对极对 所以可以先通过捕获的方式计算出一个周期内的时间,而电机转一圈大概是在两周周期,所以RPM = 1min/2*T

    关于四线风扇感兴趣的可以参考下这里视频讲解

    2.3 过程

  • 硬件

  • 主控ESP32 *1
    
    四线风扇     *1
    
    电压转换板  *1
  • MCPWM

  • ESP32 有两个 MCPWM 单元,可用于控制不同类型的电机。每个单元都有三对 PWM 输出,共有六对        PWM输出 

    每个 A/B 对都可以由三个定时器定时器 0、定时器 1 和定时器 2 中的任何一个提供时钟。同一个定时器可以用于为一对以上的 PWM 输出提供时钟

    MCPWM 单元的更详细框图如下所示:

    所以从上图中不难发现,MCPWM具有的功能有 OPERATOR、TIMER、CAPTURE、FAULT DETECT、CLOCK

    补充说明一下 由于ESP-IDFv4.3及之前的版本,捕获中断是需要通过中断来实现的

    可以通过调用 注册 MCPWM 中断处理程序mcpwm_isr_register() 进行。

    本项目中主要介绍OPERATOR和CAPTURE的使用(1) OPERATOR要操作连接到MCPWM 单元的电机 可用于改变转速

    首先要配置GPIO口

    接口函数mcpwm_gpio_init/mcpwm_set_pin (两者主要区别在于前者为指定的功能配置GPIO,而后者 是一次性配置所有的GPIO)

    接着配置PWM

    接口函数mcpwm_init

    在结构内mcpwm_config_t设置定时器频率、初始占空比、计数方向和占空比模式,然后向mcpwm_init传入这个结构体指针, 调用mcpwm_init使上面参数生效

    mcpwm_config_t mcpwm_config = {
            .frequency = 5000,
            .cmpr_a = 0,
            .cmpr_b = 0,
            .duty_mode = MCPWM_DUTY_MODE_0,
            .counter_mode = MCPWM_UP_COUNTER,
        };
        mcpwm_init(MCPWM_UNIT_0, MCPWM_TIMER_0, &mcpwm_config);

    最后设置PWM接口函数mcpwm_set_duty要改变 PWM 的占空比调用mcpwm_set_duty并提供以 % 为单位的占空比值。PWM 信号的相位可以通 过调用来改变mcpwm_set_duty_type ,即改变了占空比模式(占空比数值对应高还是对应低)

    static void Fan_PWM_Duty_Set(float set_num)
    {
        mcpwm_set_duty(MCPWM_UNIT_0,
                       MCPWM_TIMER_0,
                       MCPWM_GEN_A,
                       set_num);
        mcpwm_set_duty_type(MCPWM_UNIT_0,
                            MCPWM_TIMER_0,
                            MCPWM_GEN_A,
                            MCPWM_DUTY_MODE_0);
    }

    当使用mcpwm_init后,ESP32会自动调用mcpwm_start启动电机的

    (2) CAPTURE

    1. 首先配置GPIO口,通过mcpwm_init 来设置引脚为捕获输入

    1. 通过调用 启用功能本身 mcpwm_capture_enable 从中选择所需的信号输入mcpwm_capture_signal_t,设置信号边沿mcpwm_capture_on_edge_t和信号计数预分频器

    之前也说了ESP-IDFv4.3及之前的版本,捕获中断是需要通过中断来实现的,涉及到利用寄存器读取中断原因进行分别操作:在上述第二步中,会启用了 32 位捕获定时器,定时器在 APB 时钟的驱动下连续运行,时钟频率通常为 80 MHZ。捕获计时器的值都存储在时间戳寄存器中,当检测到比如有上升沿时会触发中断,可以在中断里面通过mcpwm_capture_signal_get_value 接口获取时间戳,然后将两次的高电平触发中断得到的时间戳数值相减就得到了一个周期内的时间啦。相关代码处理可以参考如下

    void Speed_Back_Init(void)
    {   
        current_cap_value = (uint32_t *)malloc(sizeof(uint32_t));
        previous_cap_value = (uint32_t *)malloc(sizeof(uint32_t));
    
        //创建队列
        cap_queue = xQueueCreate(1, sizeof(capture));
        mcpwm_gpio_init(MCPWM_UNIT_0,
                        MCPWM_CAP_0,
                        CAP_SIG_GPIO);
        gpio_pulldown_en(CAP_SIG_GPIO);
        //初始化捕获
        mcpwm_capture_enable(MCPWM_UNIT_0,
                             MCPWM_SELECT_CAP0,
                             MCPWM_POS_EDGE,
                             0);
        MCPWM[MCPWM_UNIT_0]->int_ena.val = CAP0_INT_EN;     //设置中断位
        //注册中断函数
        mcpwm_isr_register(MCPWM_UNIT_0,
                           cap_isr_register,
                           NULL,
                           ESP_INTR_FLAG_IRAM,              //ESP_INTR_FLAG_IRAM 把中断标志放到IRAM中
                           NULL);
        ESP_LOGI(TAG, "start");
    }
    typedef struct {
        float capture_signal;
        mcpwm_capture_signal_t sel_cap_signal; 
    } capture;
    
    static void IRAM_ATTR cap_isr_register()
    {
        uint32_t intr_status;
        capture evt;
    
        intr_status = MCPWM[MCPWM_UNIT_0]->int_st.val;      //获取中断状态
        if(gpio_get_level(CAP_SIG_GPIO)==1)     //如果是上升沿
        {
            if (intr_status == CAP0_INT_EN)     //判断是否是capture 0中断
            {
                current_cap_value[0] = mcpwm_capture_signal_get_value(MCPWM_UNIT_0, MCPWM_SELECT_CAP0); //get capture signal counter value
                evt.capture_signal = (current_cap_value[0] - previous_cap_value[0]) * 0.0000125;
                previous_cap_value[0] = current_cap_value[0];
                evt.sel_cap_signal = MCPWM_SELECT_CAP0;
                xQueueSendFromISR(cap_queue, &evt, NULL);
            }
            MCPWM[MCPWM_UNIT_0]->int_clr.val = intr_status; // 清除中断标志位
        }
    }

    说明三点:

  • IRAM_ATTR的使用。声明编译后的代码将放置在 ESP32 的内部 RAM,否则代码将放在 Flash 中。ESP32 上的闪存比内部 RAM 慢得多,这样可以加快我们处理速度

  • 0.0000125。这里我们没有使用官方例程demo, 80Mhz其实就是0.0000000125s,而我们为了直接得到ms干脆直接将这个数据放大1000倍,这样算出来的数据直接是以ms为单位的

  • if(gpio_get_level(CAP_SIG_GPIO)==1) //如果是上升沿。mcpwm_capture_signal_get_edge(mcpwm_num, cap_sig);//中断中使用这句必须要进临界区,否则会导致重启。因为比较繁琐,于是干脆用检测GPIO电平的方式


  • 到这里我们其实我们主要功能的实现已经介绍完了。接下来我将从两个方面为大家介绍如何接入涂鸦云:(1) 产品创建: 在涂鸦云平台创建产品,产品功能定义,选择面板,获取授权信息

  • 首先登录涂鸦iot平台,这里以风扇为例 进行创建产品

  • 创建完产品后进行功能定义,具体以实际项目需求为准进行添加,如果标准DP中是没有可以在自定义DP中进行添加

  • 交互面板选择 涂鸦这里提供了公版面板、自定义面板、studio面板进行选择

  • 如果在这里选择不到或说没有显示LinkSDK接入其实也没关系,我们最重要的是拿到授权清单(如果不放心的话可以提交工单让相关品类负责人上架方案,一般来说一个用户创建账号时候会有三个免费的),其实授权清单里面主要就是设备接入涂鸦的凭证 由UUID 和 AUTHKEY 组成。

  • 如果在上图选择不到的话也可以在这里进行购买的,交付方式选择授权清单

    (2) 源码移植:下载LinkSDK, 移植代码,调试及扫码添加设备

    Link SDK源码可以在此仓库下进行拉去

    LinkSDK 源码


    Link SDK 使用 C 语言实现,适用于开发者自主开发硬件设备逻辑业务接入涂鸦 IoT。TuyaOS Link SDK 提供设备激活、DP 上下行和 OTA 等基础业务接口封装,SDK 不依赖具体设备平台及操作系统环境,也可以运行在单任务环境,仅需要支持 TCP/IP 协议栈及提供 SDK 必要的系统依赖接口即可完成接入。整体框架如图所示:

    SDK目录结构简介

    名称

    说明

    examples

    例程

    platform

    平台适配平台移植接口适配

    src

    源码

    utils

    通用模块

    libraries

    外部依赖库 – MQTT client, HTTP client, mbedTLS

    interface

    平台必要移植接口,SDK 功能接口

    certs

    设备私钥,设备证书,服务端 CA 根证书

    将SDK集成到你的平台

    以下各节列出了设备SDK在任何给定平台上成功运行所需的功能

  • 系统

  • void* system_malloc(size_t n); 分配所需的内存空间,并返回一个指向它的指针。

    void* system_calloc(size_t n, size_t size); 分配所需的内存空间,并返回一个指向它的指针, 设置分配的内存初始化为零。

    void system_free(void *ptr); 释放之前调用 system_malloc,system_calloc 或 system_realloc 所分配的内存空间。

    uint32_t system_ticks(); 系统毫秒滴答计数器。

    uint32_t system_timestamp(); 获取时间戳。

  • 网络

  • SDK 需要通过 MQTT 和 HTTP 协议与服务端交互,所有通信需要基于 TLS 连接,需要你的平台具备 TCP/IP 协议栈实现以下 API,SDK 中包含了 Linux环境下基于 mbedTLS 库作为依赖实现的以下接口的示例,如果您平台已基础 mbedTLS,可使用 Linux 平台下的 platform/linux/mbedtls/network_mbedtls_wrapper.c 接口封装适配快速接入; 如果您的平台没有 mbedTLS 可以参考 mbedtls 移植指导 完成移植。

    int network_tls_init(Network *pNetwork, const TLSConnectParams *TLSParams); 初始化 TLS Network 网络连接管理结构对象。

    int network_tls_connect(Network *pNetwork, const TLSConnectParams *TLSParams); 建立 TLS 连接,TLSParams 参数为可选参数,如果传入参数为 NULL,默认使用初始化的连接参数。

    int network_tls_write(Network*, unsigned char*, size_t); Write to the TLS network buffer.

    int network_tls_read(Network*, unsigned char*, size_t); Read from the TLS network buffer.

    int network_tls_disconnect(Network *pNetwork); 断开 TLS 连接。

    int network_tls_destroy(Network *pNetwork); 释放 TLS 连接上下文。

  • 数据持久化

  • SDK 在运行过程中需要持久化储存一些配置信息在你的设备中,需要平台提供持久化的 KV 接口。

    int local_storage_set(const char* key, const uint8_t* buffer, size_t length); 写入数据到kv系统中。

    int local_storage_get(const char* key, uint8_t* buffer, size_t* length); 从kv系统中读取数据。

    int local_storage_del(const char* key); 从kv系统中删除数据。


    这里已经做好了ESP32的适配可以直接在github上进行拉去,就可以进行使用了

    ESP32平台下的适配

    拉去后需要将该文件夹进行替换

    最终文件目录效果就是这样子的

    最后需要在tuya_config.h中换上你的pid、uuid、authkey

    使用LinkSDK开发的设备自身需要有联网能力,网络连接部分的实现需要自己完成的。编译程序会输出二维码,使用涂鸦APP通过扫码方式将设备进行绑定(其实就是将uuid, pid这些发送过去),涂鸦会下发token从而激活设备,这一部分是涂鸦做的。

    应用开发快速开始

    实例化一个设备对象 tuya_iot_client_t client 并初始化它,tuya_iot_config_t 为初始化PRODUCT ID,授权信息等配置参数:

    static void tuya_link_app_task(void *pvParameters)
    {
        int ret = OPRT_OK;
    
        /* Initialize Tuya device configuration */
        ret = tuya_iot_init(&client, &(const tuya_iot_config_t){
            .software_ver = "1.0.0",
            .productkey = TUYA_PRODUCT_KEY,
            .uuid = TUYA_DEVICE_UUID,
            .authkey = TUYA_DEVICE_AUTHKEY,
            .storage_namespace = "tuya_kv",
            .event_handler = user_event_handler_on
        });
    
        assert(ret == OPRT_OK);
    
        /* Start tuya iot task */
        tuya_iot_start(&client);
    
        for(;;) {
            /* Loop to receive packets, and handles client keepalive */
            tuya_iot_yield(&client);
        }
    }

    定义应用层事件回调,回调函数用于应用层接收 SDK 事件通知,如数据功能点(DP)下发,云端连接状态通知

    static void user_event_handler_on(tuya_iot_client_t* client, tuya_event_msg_t* event)
    {
        switch(event->id){
        case TUYA_EVENT_BIND_START:
            example_qrcode_print(client->config.productkey, client->config.uuid);
            break;
    
        case TUYA_EVENT_MQTT_CONNECTED:
            TY_LOGI("Device MQTT Connected!");
            break;
    
        case TUYA_EVENT_DP_RECEIVE:
            tuya_iot_dp_download(client, (const char*)event->value.asString);
            break;
    
        default:
            break;
        }
    }

    DP点上报与下发。DP点下发是通过CJSON格式进行的,这里需要对下发的数据类型进行格式解析,然后传入对应业务应用层的接口即可实现云端对设备的控制

    #define SWITCH_DP_ID_KEY        "1"  //对应的是哪个dpid
    #define FAN_SPEED_DP_ID_KEY     "3"
    
    void tuya_iot_dp_download(tuya_iot_client_t* client, const char* json_dps)
    {
        TY_LOGD("Data point download value:%s", json_dps);
    
        /* Parsing json string to cJSON object */
        cJSON *dps = cJSON_Parse(json_dps);
        if (dps == NULL) {
            TY_LOGE("JSON parsing error, exit!");
            return;
        }
    
        /* Process dp data */
        cJSON *switch_obj = cJSON_GetObjectItem(dps, SWITCH_DP_ID_KEY);
        if (cJSON_IsTrue(switch_obj)) {
            _FAN_DRIVE_Handle->Fan_Switch(Fan_ON);
    
        } else if (cJSON_IsFalse(switch_obj)) {
            _FAN_DRIVE_Handle->Fan_Switch(Fan_OFF);
        }
    
        cJSON *speed_obj = cJSON_GetObjectItem(dps, FAN_SPEED_DP_ID_KEY);
        if(speed_obj!=NULL)
        {
            ESP_LOGI(TAG, "speed = %.1f", (double)speed_obj->valueint);
            _FAN_DRIVE_Handle->Fan_PWM_Duty_Set((double)speed_obj->valueint);
        }
    
        /* relese cJSON DPS object */
        cJSON_Delete(dps);
    
        /* Report the received data to synchronize the switch status. */
        tuya_iot_dp_report_json(client, json_dps);
    }

    同理对应的DP点上报也需要转化成CJSON进行上报到云端,这样面板上就可以显示相应的数据了。这里以转速上报处理为例

    void Capture_Signal(void *pvParameters)
    {
        uint16_t RPM = 0;
        uint32_t signal_val = 0;
        char dps[50];
        capture evt;
    
        while(1)
        {
            xQueueReceive(cap_queue, &evt, portMAX_DELAY);
            if(evt.sel_cap_signal == MCPWM_SELECT_CAP0)
            {
                signal_val = (uint32_t)evt.capture_signal;
                ESP_LOGI(TAG, "signal_val : %d ms", signal_val);
                //计算转速 r/min
                signal_val = (2*signal_val) % 1000;
                RPM = 60000 / signal_val; 
                ESP_LOGI(TAG, "RPM : %d", RPM);
                //转化成CJSON格式 进行DP点上报 数值型
                snprintf(dps, sizeof(dps), "{\"101\":%d}", RPM);
                tuya_iot_dp_report_json(&client, dps);          
            }
            vTaskDelay(10000/portTICK_RATE_MS);                 //每10s上报一次
        }
    }

    通过这张图可以更好的理解一下数据链路过程

    LinkSDK接入官方文档已经也的很清楚了(从入门到起飞),也可点击下面链接进行查看

    涂鸦官方文档

    当所有的进行适配好,业务层逻辑写好后就可以进行编译了

    然后打开涂鸦智能APP进行扫码绑定设备

    这样就添加完成了!进入面板后就能看到当时在IOT平台上选择的面板开关、风速调节、转速反馈都会在上面进行显示

    我这里选择的四线散热风扇 电流是0.2A的其转速对应2000RPM,和店家咨询了下大概转速误差5%~15%左右,满转速下测的结果还算是合理的,为了不频繁的进行上报这里设置的每10s进行上报一次转速信息。此外还可以通过在涂鸦iot平台的配置,可以很轻松地的对接到天猫精灵、亚马逊等平台实现实现第三方语音控制。

    OK那么这个小项目就完结了,我们下一个项目继续!

    物联沃分享整理
    物联沃-IOTWORD物联网 » 如何将四线散热风扇接入涂鸦Cloud平台

    发表评论