四线散热风扇接入涂鸦Cloud

这篇具有很好参考价值的文章主要介绍了四线散热风扇接入涂鸦Cloud。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、前言

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

二、介绍

2.1 功能

具体主要实现以下功能

  • 接入涂鸦云

  • 控制开、关

  • 风速调节

  • 转速反馈

esp32 涂鸦,物联网,iot,Powered by 金山文档

2.2 方案

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

esp32 涂鸦,物联网,iot,Powered by 金山文档
esp32 涂鸦,物联网,iot,Powered by 金山文档

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

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

2.3 过程

  • 硬件

主控ESP32 *1

四线风扇     *1

电压转换板  *1
  • MCPWM

ESP32 有两个 MCPWM 单元,可用于控制不同类型的电机。每个单元都有三对 PWM 输出,共有六对        PWM输出 
esp32 涂鸦,物联网,iot,Powered by 金山文档

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

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

esp32 涂鸦,物联网,iot,Powered by 金山文档

所以从上图中不难发现,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平台,这里以风扇为例 进行创建产品

esp32 涂鸦,物联网,iot,Powered by 金山文档
esp32 涂鸦,物联网,iot,Powered by 金山文档
  • 创建完产品后进行功能定义,具体以实际项目需求为准进行添加,如果标准DP中是没有可以在自定义DP中进行添加

esp32 涂鸦,物联网,iot,Powered by 金山文档
  • 交互面板选择 涂鸦这里提供了公版面板、自定义面板、studio面板进行选择

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

esp32 涂鸦,物联网,iot,Powered by 金山文档

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

esp32 涂鸦,物联网,iot,Powered by 金山文档
esp32 涂鸦,物联网,iot,Powered by 金山文档

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

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

LinkSDK 源码


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

esp32 涂鸦,物联网,iot,Powered by 金山文档

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平台下的适配

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

esp32 涂鸦,物联网,iot,Powered by 金山文档

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

esp32 涂鸦,物联网,iot,Powered by 金山文档

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

esp32 涂鸦,物联网,iot,Powered by 金山文档

使用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上报一次
    }
}

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

esp32 涂鸦,物联网,iot,Powered by 金山文档

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

涂鸦官方文档

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

esp32 涂鸦,物联网,iot,Powered by 金山文档
esp32 涂鸦,物联网,iot,Powered by 金山文档

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

esp32 涂鸦,物联网,iot,Powered by 金山文档
esp32 涂鸦,物联网,iot,Powered by 金山文档

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

esp32 涂鸦,物联网,iot,Powered by 金山文档
esp32 涂鸦,物联网,iot,Powered by 金山文档

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

OK那么这个小项目就完结了,我们下一个项目继续!文章来源地址https://www.toymoban.com/news/detail-692069.html

到了这里,关于四线散热风扇接入涂鸦Cloud的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • U8g2库的STM32硬件SPI(DMA)移植教程(HAL、OLED显示、四线SPI、DMA)

    本文教你把U8g2图形库移植到STM32上,基于STM32的硬件SPI、CubeMX U8g2库Github网址:https://github.com/olikraus/u8g2 U8g2库CSDN镜像网址:https://gitcode.net/mirrors/olikraus/u8g2?utm_source=csdn_github_accelerator 硬件准备:STM32C8T6(STM32系列芯片)、0.96寸OLED(128×64)、J-Link(或其他) 引脚连接: 出自此

    2024年02月09日
    浏览(50)
  • STM32智能桌面风扇

    目录 一、功能介绍 二、硬件清单 三、模块详解  1)电机驱动  2)舵机驱动  3)定时关闭  4)温度传感器  5)FLASH  6)按键控制  7 )多级菜单  8)主函数  四、源码可私 一、功能介绍 (1)输入电压为DC12-24V; 电源模块 (2)支持不同风扇挡位调节风速,每个挡位有对应

    2024年02月04日
    浏览(28)
  • 基于STM32的智能风扇系统

    目录 1、概述 2、硬件组成 3、PWM调速原理 4、L298N电机驱动控制原理 5、红外线遥控编码原理 6、软件设计 7、实物测试 该智能风扇以STM32单片机为核心,结合红外遥控、人体感应、蓝牙数据传输、电机驱动等模块,实现风扇的启动、正反转、多级调速等功能,并通过物理按键、

    2023年04月10日
    浏览(28)
  • stm32毕设 stm32 wifi远程温控风扇系统

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天

    2024年02月19日
    浏览(52)
  • 【ESP32】19.手机蓝牙风扇控制实验(BluetoothSerial库)

    【ESP32】18.舵机实验(Servo库) 先放电路连接图: ESP32最为核心的就是自带蓝牙和WiFi功能。 其中蓝牙支持两种模式,一直是经典蓝牙,另一种是低功耗蓝牙,这个实验是利用经典蓝牙和串口进行通讯。 同时这个实验涉及到安卓手机端软件,这部分教程会在下边的专栏更新,

    2024年02月09日
    浏览(84)
  • 毕业设计 stm32 wifi远程温控风扇系统

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天

    2024年02月20日
    浏览(71)
  • STM32学习-基于STM32F1具有控制菜单的温控小风扇

    本文仅作个人学习记录,非教程,内容不完整,仅供参考,请勿用于商业用途。 使用ADC读取环境温度,根据环境温度设置PWM占空比,从而控制FAN转速。控制菜单功能:1.设置特定转速,2.切换为手动模式任意控制FAN转速。 整个系统并不复杂:MCU负责运行代码并输出信号;NTC是

    2024年02月06日
    浏览(241)
  • 单片机毕设 stm32 wifi远程温控风扇系统

    🔥 这两年开始毕业设计和毕业答辩的要求和难度不断提升,传统的毕设题目缺少创新和亮点,往往达不到毕业答辩的要求,这两年不断有学弟学妹告诉学长自己做的项目系统达不到老师的要求。 为了大家能够顺利以及最少的精力通过毕设,学长分享优质毕业设计项目,今天

    2024年02月19日
    浏览(60)
  • STM32 Proteus仿真红外检测PWM调速温控风扇-0073

    STM32 Proteus仿真红外检测PWM调速温控风扇-0073 Proteus 仿真小实验: STM32 Proteus仿真红外检测PWM调速温控风扇-0073 功能: 硬件组成:STM32F103C6单片机 +LCD1602显示器+DS18B20温度传感器+人检测 按下说明有人+L298驱动电机模拟风扇 1.按键模拟人体红外探测,一旦检测到人后,开始自动

    2024年02月16日
    浏览(44)
  • 手把手教你写stm32f103智能风扇

    本系统可以分为两个模式来进行运行,分别为手动模式和自动模式,同时,在上电进入系统后,还会有一个模式选择的界面产生。 模式选择:在此界面中,可以通过按键K1来控制模式选择,两个模式分别为手动模式和自动模式;通过按键K2可以进入模式。 手动模式:在手动模

    2023年04月17日
    浏览(66)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包