W5500+树莓派RP2040入门教程之MQTT篇(十二)

news2024/11/28 4:46:38

目录

1 前言

2 什么是MQTT协议?

2.1 特点

2.2 应用

2.3 身份

2.4 消息质量等级

2.5 遗嘱消息

3 硬件介绍

4 硬件接线

5 代码编写

6 移植说明

7 最终现象

8 总结

9 项目链接


1 前言

        随着物联网技术的快速发展,MQTT(Message Queuing Telemetry Transport)协议已成为一种广泛使用的通讯协议,它适用于设备间低带宽、高延迟、不可靠的网络通信。

        W5500是一款集成全硬件 TCP/IP 协议栈的嵌入式以太网控制器,同时也是一颗工业级以太网控制芯片。在以太网应用中使用 W5500 + MQTT应用协议让用户可以更加方便地在设备之间实现远程连接和通信。本教程将介绍W5500以太网MQTT应用的基本原理、使用步骤、应用实例以及注意事项,帮助读者更好地掌握这一技术。

2 什么是MQTT协议?

        MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议)是一种基于发布-订阅模式的轻量级通讯协议,它构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大长处在于,能够以很少的代码和有限的带宽,为衔接远程设备供给实时可靠的音讯效劳。

2.1 特点

  1. 开放消息协议,简单易实现
  2. 发布订阅模式,一对多消息发布
  3. 基于 TCP/IP 网络连接
  4. 1 字节固定报头,2 字节心跳报文,报文结构紧凑
  5. 消息 QoS 支持,可靠传输保证

2.2 应用

        MQTT 协议广泛应用于物联网、移动互联网、智能硬件、车联网、电力能源等领域。

  1. 物联网 M2M 通信,物联网大数据采集
  2. Android 消息推送,WEB 消息推送
  3. 移动即时消息,例如 Facebook Messenger
  4. 智能硬件、智能家具、智能电器
  5. 车联网通信,电动车站桩采集
  6. 智慧城市、远程医疗、远程教育
  7. 电力、石油与能源等行业市场

2.3 身份

        MQTT通信过程中,共有三种身份:

  1. 发布者(Publisher)
  2. 服务器(Broker)
  3. 订阅者(Subscriber)

2.4 消息质量等级

        MQTT设计了3个QoS等级

  • QoS 0:消息最多传递一次,如果当时客户端不可用,则会丢失该消息。
  • QoS 1:消息传递至少 1 次。
  • QoS 2:消息仅传送一次。

        QoS 0是发布者发送完消息之后,不再关心对方有没有收到,也不设置任何重发机制。

        QoS 1包含了简单的重发机制,发布者发送完消息之后会一直等待接收者的ACK,如果没有收到ACK则一直重发,这种模式保证消息至少到达一次,但无法保证消息重复。

        QoS 2设计了重发和重复消息发现机制,保证消息到达对方并严格只到达了一次。

2.5 遗嘱消息

        客户端的遗嘱只在意外断线时才会发布,如果客户端正常的断开了与服务端的连接,这个遗嘱机制是不会启动的,服务端也不会将客户端的遗嘱公布。

        遗嘱消息可以看作是一个简化版的 PUBLISH 消息,他也包含 Topic, Payload, QoS 等字段。遗嘱消息会在设备与服务端连接时,通过 CONNECT 报文指定,然后在设备意外断线时由服务端将该遗嘱消息发布到连接时指定的遗嘱主题(Will Topic)上。

        一般建议设置遗嘱消息内容为client offline!在我们的客户端连接上服务器时,也向遗嘱主题发布一条client online消息。这样遗嘱主题可以告诉我们客户端的一个在线状态。这也是在我们嵌入式应用中为什么喜欢使用MQTT协议通信的原因之一。

3 硬件介绍

        如果采用传统以太网方式我们接入到以太网中实现MQTT协议的应用,那我们需要按照下面方式进行接线。不仅硬件上比较复杂,而且还需要编写软件协议栈,以及应用层协议交互的代码。

        在这里我向大家推荐一块开发板,它硬件上集成了MCU+MAC+PHY+RJ45,还拥有硬件TCP/IP协议栈。在我们要开发和学习嵌入式设备入网和交互时,仅需这一块开发板就行了。

        W5500-EVB-Pico是一款搭载了以太网芯片的高性能、低成本的开发板。主控芯片采用的是树莓派的RP2040,搭载了双核M0架构处理器,频率最高可达133MHz,还拥有264KB高速SRAM和2MB的板载闪存以及丰富的外设资源。

        其搭载的以太网芯片W5500是一款高性价比的以太网芯片,更是拥有全球独一无二的全硬件TCP/IP协议栈专利技术,在我们开发过程中无需深究协议的交互以及组包过程,只需处理应用层即可!还拥有8个独立的硬件socket,可以同时进行通信互不干扰,无论是工业使用还是学习,W5500-EVB-Pico都是一个非常不错的选择!

        并且W5500这款以太网芯片,供货稳定,久经市场考验,反馈都特别不错,简单稳定,易于上手,可以帮助我们缩短开发周期,项目快速落地!

4 硬件接线

5 代码编写

        程序的运行框图如下所示:

        我们使用的是WIZnet官方的ioLibrary_Driver库。该库支持的协议丰富,操作简单,芯片在硬件上集成了TCP/IP协议栈,该库又封装好了TCP/IP层之上的协议,我们只需简单调用相应函数即可完成协议的应用。

        该例程实现了连接MQTT,进行发布消息,以及监听我们订阅的主题下发的消息,通过USB的方式打印出来。

        首先进行spi初始化,然后是链路状态检测

/* Pin definition */
#define PIN_SCK 18
#define PIN_MOSI 19
#define PIN_MISO 16
#define PIN_CS 17
#define PIN_RST 20

/* W5500 chip is selected */
static inline void wizchip_select(void)
{
    gpio_put(PIN_CS, 0);
}
/* Cancel chip selection W5500 chip */ 
static inline void wizchip_deselect(void)
{
    gpio_put(PIN_CS, 1);
}
/* reset W5500 chip */
void wizchip_reset(void)
{
    gpio_set_dir(PIN_RST, GPIO_OUT);

    gpio_put(PIN_RST, 0);
    sleep_ms(100);

    gpio_put(PIN_RST, 1);
    sleep_ms(100);

    bi_decl(bi_1pin_with_name(PIN_RST, "W5500 RESET"));
}

/* SPI reads the W5500 chip */
static uint8_t wizchip_read(void)
{
    uint8_t rx_data = 0;
    uint8_t tx_data = 0xFF;

    spi_read_blocking(SPI_PORT, tx_data, &rx_data, 1);

    return rx_data;
}
/* SPI writes the W5500 chip */
static void wizchip_write(uint8_t tx_data)
{
    spi_write_blocking(SPI_PORT, &tx_data, 1);
}

static void wizchip_spi_initialize(void)
{
    /* The SPI is initialized with a rate of 5MHz */
    spi_init(SPI_PORT, 5000 * 1000);

    /* Set the pins to SPI mode */
    gpio_set_function(PIN_SCK, GPIO_FUNC_SPI);
    gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI);
    gpio_set_function(PIN_MISO, GPIO_FUNC_SPI);

    /* Initialize the CS pin to the output mode */
    gpio_init(PIN_CS);
    gpio_set_dir(PIN_CS, GPIO_OUT);
    gpio_put(PIN_CS, 1);
}

void wizchip_initialize(void)
{
    uint8_t temp;

    wizchip_spi_initialize();

    /* Deselect the chip: chip select high */
    wizchip_deselect();

    /* CS function register */
    reg_wizchip_cs_cbfunc(wizchip_select, wizchip_deselect);

    /* SPI function register */
    reg_wizchip_spi_cbfunc(wizchip_read, wizchip_write);

    wizchip_reset();

    /* Read version register */
    if (getVERSIONR() != 0x04)
    {
        printf("ACCESS ERR : VERSION != 0x04, read value = 0x%02x\n", getVERSIONR());

        while (1)
        {
            sleep_ms(100);
        }
    }
    /* Check PHY link status */
    do
    {
        if (ctlwizchip(CW_GET_PHYLINK, (void *)&temp) == -1)
        {
            printf(" Unknown PHY link status\n");
            return;
        }
        if (temp == PHY_LINK_ON)
        {
            printf("PHY link\n");
        }
        if (temp == PHY_LINK_OFF)
        {
            printf("PHY is not connected\r\n");
            sleep_ms(1000);
        }

    } while (temp == PHY_LINK_OFF);
}

        我们定义网络地址以及MQTT参数如下:

#define MQTT_SOCKET 1 /* socket used by MQTT */


/* Network address information */
static wiz_NetInfo net_info = {
    .mac = {0x00, 0x08, 0x22, 0x82, 0xed, 0x2e},
    .ip = {192, 168, 1, 20},
    .sn = {255, 255, 255, 0},
    .gw = {192, 168, 1, 1},
    .dns = {8, 8, 8, 8},
    .dhcp = NETINFO_STATIC};

/* MQTT parameter assignment */
mqttconn mqtt_params = {
    .server_ip = {54, 244, 173, 190},
    .port = 1883,
    .clientid = "WIZnet_W5500_EVB_Pico",
    .username = "W5500",
    .passwd = "W5500",
    .pubtopic = "W5500_pub",
    .pubQoS = 0,
    .subtopic = "W5500_sub",
    .subQoS = 0,
    .willtopic = "W5500_will",
    .willQoS = 0,
    .willmsg = "W5500 offline!",
};

        编写MQTT连接函数

void mqtt_conn(void)
{
    MQTTPacket_connectData data = MQTTPacket_connectData_initializer;                                       /* MQTT client structure initialization */
    MQTTPacket_willOptions willdata = MQTTPacket_willOptions_initializer;                                   /* Will subject struct initialization */
    NewNetwork(&n, MQTT_SOCKET);                                                                            /* Specifies the socket to which the MQTT is connected */
    ConnectNetwork(&n, mqtt_params.server_ip, mqtt_params.port);                                            /* Specifies the address and port to connect to the MQTT server */
    MQTTClientInit(&c, &n, 1000, mqtt_send_buff, MQTT_SEND_BUFF_SIZE, mqtt_recv_buff, MQTT_RECV_BUFF_SIZE); /* MQTT client initialization */
    data.willFlag = 1;                                                                                      /* will flag */
    willdata.qos = mqtt_params.willQoS;                                                                     /* will QoS */
    willdata.topicName.lenstring.data = mqtt_params.willtopic;                                              /* will topic */
    willdata.topicName.lenstring.len = strlen(willdata.topicName.lenstring.data);                           /* will topic len */
    willdata.message.lenstring.data = mqtt_params.willmsg;                                                  /* will message */
    willdata.message.lenstring.len = strlen(willdata.message.lenstring.data);                               /* will message len */
    willdata.retained = 0;
    willdata.struct_version = 3;
    data.will = willdata;
    data.MQTTVersion = 4;                         // Server version,The 4 represents version 3.1.1
    data.clientID.cstring = mqtt_params.clientid; // clientid
    data.username.cstring = mqtt_params.username; // username
    data.password.cstring = mqtt_params.passwd;   // password
    data.keepAliveInterval = 30;                  // keepalive
    data.cleansession = 1;                        // clean session flag

    /* Connect MQTT, the maximum number of connections does not exceed the set value */
    for (int i = 0; i < conn_max_err; i++)
    {
        connOK = MQTTConnect(&c, &data);
        printf("Connected:%s\r\n", connOK == 0 ? "success" : "failed");
        if (!connOK)
        {
            break;
        }
        sleep_ms(1000);
    }
    if (connOK)
    {
        while (1)
            ;
    }
}

        编写mqtt订阅参数,参1表示订阅主题名,参2表示订阅QoS等级,参3表示接收该主题时的消息回调函数

void mqtt_sub(char *subtopic, int QoS, messageHandler messageHandler)
{
    /* Subscribe to topics, the maximum number of subscriptions does not exceed the set value */
    for (int i = 0; i < sub_max_err; i++)
    {
        subOK = MQTTSubscribe(&c, subtopic, QoS, messageHandler);
        printf("Subscribing to %s\r\n", subtopic);
        printf("Subscribed:%s\r\n", subOK == 0 ? "success" : "failed");
        if (!subOK)
        {
            break;
        }
        sleep_ms(1000);
    }
    if (subOK)
    {
        while (1)
            ;
    }
}

        再编写我们的订阅主题的消息回调函数

void messageArrived(MessageData *md)
{
    char topicname[64] = {0};
    char msg[512] = {0};
    sprintf(topicname, "%.*s", (int)md->topicName->lenstring.len, md->topicName->lenstring.data);
    sprintf(msg, "%.*s", (int)md->message->payloadlen, (char *)md->message->payload);
    printf("recv:%s,%s\r\n\r\n", topicname, msg);
    mqtt_sendmsg(mqtt_params.pubtopic, 0, msg);
}

        然后是我们的发布消息函数,参1表示发布主题,参2表示发布质量等级,参3表示发布消息内容

void mqtt_sendmsg(char *pubtopic, int QoS, char *msg)
{
    MQTTMessage pubmessage = {
        .qos = QoS,
        .retained = 0,
        .dup = 0,
        .id = 0,
    };
    pubmessage.payload = msg;
    pubmessage.payloadlen = strlen(pubmessage.payload);
    MQTTPublish(&c, pubtopic, &pubmessage);
    printf("publish:%s,%s\r\n\r\n", pubtopic, pubmessage.payload);
}

        再编写一个1ms定时器回调函数,把mqtt库中的1ms定时器注册进来

bool repeating_timer_callback(struct repeating_timer *t)
{
    MilliTimer_Handler(); /* Register the 1mm MQTT timer */
    return true;
}

        最后我们在主函数中初始化后依次调用即可

int main()
{
    struct repeating_timer timer;
    stdio_init_all();
    sleep_ms(3000);
    printf("W5500 mqtt example.\r\n");

    wizchip_initialize(); /* Initialize the SPI and PHY detection */

    wizchip_setnetinfo(&net_info); /* Set network address information */

    print_network_information(net_info);                                       /* Print network address information */
    add_repeating_timer_ms(1, repeating_timer_callback, NULL, &timer);         /* Turns on a 1-millisecond timer */
    mqtt_conn();                                                               /* Connect to the MQTT server */
    mqtt_sub(mqtt_params.subtopic, mqtt_params.subQoS, messageArrived);  /* Subscribe to Topics */
    mqtt_sendmsg(mqtt_params.willtopic, mqtt_params.willQoS, "W5500 online!"); /* Release the online news */
    while (true)
    {
        MQTTYield(&c, 30); /* keepalive MQTT */
        sleep_ms(100);
    }
}

6 移植说明

        如果你想在你的开发板上加入W5500芯片实现以太网功能,则你可以购买一个W5500IO模块。基于例程进行移植。步骤如下

  1. 将工程中的ioLibrary_Driver文件夹移植到你的工程下。
  2. 将port文件夹下的w5500_spi.c以及w5500_spi.h文件进行移植并对应修改
  3. 将mqtt_client.c文件移植到你的项目中,并对应修改

        如需移植教程请在评论区留言:移植教程;后续我会根据反馈情况发布一个移植教程。

7 最终现象

​​​​​​​         在MQTTX工具上,我们新建连接,服务器和端口号以及MQTT版本都设置成与开发板一致即可。并添加一个订阅W5500_pub(即开发板发布的主题)和W5500_will(即开发板遗嘱主题)。

        我们按住RUN运行按钮然后用USB先连接到电脑,此时开发板会虚拟成U盘,我们只需要把编译好的文件复制进U盘中即可。

        在网线没有连接至开发板时,USB会一直提示网络接口未连接。

        在接入网线之后,会打印网络地址信息以及连接订阅状态,并向遗嘱主题发布一条客户端上线消息。

        此时MQTTX工具上便可收到来自开发板上线的消息。

        我们在对话框下面将MQTTX的发布主题改为W5500_sub(即开发板的订阅主题),并发布一条消息。

        开发板上也是同样的,将接收到的消息以及发布的消息通过USB打印出来。

        最后,我们将开发板上的网线断开,服务器发现开发板没有定时发送心跳包,认为异常断开,会向遗嘱主题发送开发板掉线消息。

8 总结

        至此,我们通过简单配置开发板之后实现了连接MQTT服务器,并且发布了一条消息给其他客户端,也能接收到来自订阅的消息。在我们的使用过程中,可以直接将例程中的初始化以及发布,订阅函数进行移植,并根据自己的业务需求进行修改即可。总而言之,硬件集成了TCP/IP协议栈的W5500芯片可以帮助我们在开发时无需太过关注协议的底层及组包过程,只需要按照官方提供的库进行传入我们的参数即可,这帮助我们的项目快速落地。对初次接触以太网模块的小伙伴们也比较友好,会更容易上手。

9 项目链接

MQTT例程icon-default.png?t=N7T8https://gitee.com/wiznet-hk/w5500-evb-pico-routine/tree/master/examples/mqtt_client

开发板资料icon-default.png?t=N7T8http://docs.wiznet.io/Product/iEthernet/W5500/w5500-evb-pico

MQTT3.1.1 协议手册icon-default.png?t=N7T8http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.pdf

ioLibrary_Driver库icon-default.png?t=N7T8https://github.com/Wiznet/ioLibrary_Driver/tree/ce4a7b6d07541bf0ba9f91e369276b38faa619bd

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1042078.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微分算子法求解常系数线性微分方程特解

1.微分算子法求解常系数线性微分方程特解 参考资料&#xff1a;全网讲解最清楚的微分算子法&#xff01; 1.1 微分算子法的思路 1.2 f ( x ) e α x f(x)e^{\alpha x} f(x)eαx 型 1.3 f ( x ) sin ⁡ β x f(x)\sin\beta x f(x)sinβx 或 f ( x ) cos ⁡ β x f(x)\co…

oracle VM virtualbox 自动挂载共享目录

目标&#xff1a; 1 安装增强插件 2 设置共享目录 ![在这里插入图片描述](https://img-blog.csdnimg.cn/a3ef43aa3a934e4691bad53874f6b427.png 3 修改fstab sudo chmod 777 /etc/fstab vi /etc/fstab 增加一行&#xff1a; pc /mnt/pc vboxsf defaults 0 0 例子&#xff1a…

tensor维度变换

作用函数不变大小改变shapeview / reshape删减与增加维度squeeze / unsqueeze维度扩展expand / repeat矩阵转置&#xff0c;单次和多次交换操作t / transpose / permute 1、 view reshape view与reshape效果一致&#xff0c;且可以通用。直接以view为例&#xff1a; a torc…

CH08_搬迁特性

搬迁函数&#xff08;Move Function&#xff09; 曾用名&#xff1a;搬迁函数&#xff08;Move Method&#xff09; class Account{get overdraftCharge(){...}... }class AccountType{get overdraftCharge(){...}... }动机 模块化是优秀软件设计的核心所在&#xff0c;好的模…

C语言自定义类型讲解:结构体,枚举,联合(2)

&#x1f435;本篇文章将会对位段、枚举和联合的相关知识进行讲解 1. 位段&#x1f4da; 1.1 什么是位段 位段的声明和结构体类似&#xff0c;但是有两点不同&#xff1a; 1.位段的成员必须是int&#xff0c;unsigned int&#xff0c;signed int (C99之后也可以是其他成员&am…

Redis 线程模式

Redis 是单线程吗&#xff1f; Redis 单线程指的是 [接收客户端请求 -> 解析请求 -> 进行数据读写操作 -> 发送数据给客户端] 这个过程是由一个线程 (主线程) 来完成的&#xff0c;这也是常说的 Redis 是单线程的原因。 但是 &#xff0c;Redis 程序不是单线程的&am…

nginx 反向代理 负载均衡 动静分离

一样东西的诞生通常都是为了解决某些问题&#xff0c;对于 Nginx 而言&#xff0c;也是如此。 比如&#xff0c;你出于无聊写了一个小网站&#xff0c;部署到 tomcat 之后可以正常访问 但是后来&#xff0c;你的这个小网站因为内容很诱人逐步的火了&#xff0c;用户越来越多&a…

C#开发的OpenRA游戏之雷达地图

C#开发的OpenRA游戏之雷达地图 从前面的游戏里,就可以看到在上面按钮下面留有一个区域,这个区域的作用,就是用来显示一个雷达地图,如下图所示: 从雷达地图来看,可以清楚地看到全局的动态,自己的兵力分布,还有自己的建筑分布,矿产分布等等。 在这里就来对这个雷达地图…

Python编程:使用PIL进行JPEG图像压缩的简易教程

摘要: 本文介绍了如何使用Python编程语言和wxPython图形用户界面库进行JPEG图像的压缩。通过添加滑块控件&#xff0c;我们可以调整压缩质量&#xff0c;并将压缩后的照片另存为原来的名称加上后缀"压缩质量数字"的新文件。 C:\pythoncode\new\image2small.py 完整…

AI编程助手 Amazon CodeWhisperer 全面解析与实践

目录 引言Amazon CodeWhisperer简介智能编程助手智能代码建议代码自动补全 提升代码质量代码质量提升安全性检测 支持多平台多语言 用户体验和系统兼容性用户体验文档和学习资源个性化体验系统兼容性 功能全面性和代码质量功能全面性代码生成质量和代码安全性 CodeWhisperer的代…

程序启动-大数据平台搭建

1、启动zookeeper集群 /home/cluster/zookeeper.sh start /home/cluster/zookeeper.sh stop 2、启动hadoop和yarn集群 /home/cluster/hadoop-3.3.6/sbin/start-dfs.sh /home/cluster/hadoop-3.3.6/sbin/start-yarn.sh /home/cluster/hadoop-3.3.6/sbin/stop-dfs.sh /home/clust…

以太坊代币标准ERC20、ERC165、ERC721

两个概念 ERC(Ethereum Request for Comment) 以太坊意见征集稿EIP(Ethereum Improvement Proposals)以太坊改进提案 ERC和EIP用于使得以太坊更加完善&#xff1b;在ERC中提出了很多标准&#xff0c;用的最多的标准就是它的Token标准; 有哪些标准详细见https://eips.ethereum…

预制菜行业数据分析(京东数据挖掘)

最近一段时间&#xff0c;关于预制菜进校园事件的讨论热度高涨。而这两天&#xff0c;核酸大王“张核子”转行开预制菜公司卖方便米饭的消息又被传出&#xff0c;直接让预制菜市场饱受关注。 “预制菜是近两年的风口”&#xff0c;这个结论鲸参谋早在以往的内容中专门讨论过&a…

Java 18的未来:新特性和编程实践

文章目录 引言新特性预览1. 基于值的类的进一步改进2. 模式匹配的增强3. 新的垃圾回收器4. 扩展的模块系统5. 更强大的异步编程 编程实践示例1&#xff1a;基于值的类示例2&#xff1a;模式匹配的增强示例3&#xff1a;新的垃圾回收器 结论 &#x1f389;欢迎来到Java学习路线专…

python机器学习融合模型:Stacking与Blending(附代码)

1 堆叠法Stacking 一套弱系统能变成一个强系统吗&#xff1f; 当你处在一个复杂的分类问题面前时&#xff0c;金融市场通常会出现这种情况&#xff0c;在搜索解决方案时可能会出现不同的方法。 虽然这些方法可以估计分类&#xff0c;但有时候它们都不比其他分类好。在这种情况…

[WUSTCTF2020]颜值成绩查询 布尔注入二分法

这道题很简单 就是sql注入 我们来学习一下如何写盲注脚本 ?stunum1 ?stunum123 正确回显 100 错误 显示 not 。。。 这里很显然就是盲注了 我们来写个语句查询 if(ascii(substr(database(),1,1))>1,1,0)发现回显了 我们可以开始编写脚本跑了 import requests impor…

DeepMind 利用无监督学习开发 AlphaMissense,预测 7100 万种基因突变

类基因组共有 31.6 亿个碱基对&#xff0c;无时无刻不在经历复制、转录和翻译&#xff0c;也随时有着出错突变的风险。 错义突变是基因突变中的一种常见形式&#xff0c;然而人类目前只观察到了其中的一小部分&#xff0c;能够解读的更是只有 0.1%。 准确预测错义突变的作用&am…

Windows10/11显示文件扩展名 修改文件后缀名教程

前言 写这篇文章的原因是由于我分享的教程中的文件、安装包基本都是存在阿里云盘的&#xff0c;下载后需要改后缀名才能使用。 但是好多同学不会改。。 Windows 10 随便打开一个文件夹&#xff0c;在上方工具栏点击 “查看”点击 “查看” 后下方会显示更详细的工具栏然后点…

剪映软件专业版的操作与使用,电脑版与手机版APP同步讲解

一、教程描述 什么是剪映&#xff1f;抖音官方推出的一款视频编辑工具&#xff0c;用于短视频的剪辑制作和在线发布&#xff0c;主要在手机端使用&#xff0c;同时支持PC端&#xff0c;操作简单易上手&#xff0c;功能也十分强大&#xff0c;使用过剪映的用户&#xff0c;都将…

ViT细节与代码解读

最近看到两篇解读ViT很好的文章&#xff0c;备忘记录一下&#xff1a; 先理解细节 1&#xff1a;再读VIT&#xff0c;还有多少细节是你不知道的 再理解代码 1&#xff1a;ViT源码阅读-PyTorch - 知乎