第3章_UART 开发基础

文章目录

  • 第3章 UART 开发基础
    • 3.1 同步传输与异步传输
      • 3.1.1 概念与示例
      • 3.1.2 差别
    • 3.2 UART 协议与操作方法
      • 3.2.1 UART 协议
      • 3.2.2 STM32H5 UART 硬件结构
      • 3.2.3 RS485 协议
    • 3.3 UART 编程
      • 3.3.1 硬件连接
      • 3.3.2 三种编程方式
      • 3.3.3 查询方式
      • 3.3.4 中断方式
      • 3.3.5 DMA 方式
    • 3.4 效率最高的 UART 编程方法
      • 3.4.1 IDLE 中断
      • 3.4.2 DMA 发送/DMA+IDLE 接收
    • 3.5 在 RTOS 里使用 UART
      • 3.5.1 程序框架
      • 3.5.2 编写程序
      • 3.5.3 面向对象封装 UART

第3章 UART 开发基础

3.1 同步传输与异步传输

3.1.1 概念与示例

使用生活例子来说明什么是同步、异步:

  • 同步:朋友打电话说到我家吃饭,我在家里等他们
  • 异步:朋友没有提前打招呼,突然就到我家来了

它们的差别在于:有没有使用一种方法“实现约好时间”。

在电子产品中, 使用同步传输时, 一般涉及两个信号:

  • 时钟信号:用来通知对方要读取数据了
  • 数据信号:用来传输数据

同步传输示例如下:

  • 时钟信号:打电话,起约定作用
  • 数据信号:传输数据

img

异步传输示例如下:

img

使用异步信号传输数据时,双方遵守相同的约定:

  • 起始信号:发送方可以通知接收方"注意了,我要开始传输数据了"
  • 数据的表示: 怎么表示逻辑 1,怎么表示逻辑 0。

以红外遥控器解码器为例,它向单片机发出的数据格式如下:

  • 起始信号:解码器发出一个 9ms 的低电平、 4.5ms 的高电平, 用来同时对方说"开始了"

img

  • 表示一位数据
    • 逻辑 1:0.56ms 的低电平+1.69ms 的高电平
    • 逻辑 0:0.56ms 的低电平+0.56ms 的高电平

img

  • 接收方、发送方都遵守这样的约定, 就可以使用一条线传输数据

3.1.2 差别

同步传输异步传输
信号线多:时钟信号、数据信号少:只需要数据信号
速率可变, 提高时钟信号频率即可双方提前约定
抗干扰能力

3.2 UART 协议与操作方法

3.2.1 UART 协议

通用异步收发器简称 UART,即“Universal Asynchronous Receiver Transmitter”, 它用来传输串行数据:发送数据时,CPU 将并行数据写入 UART,UART 按照一定的格式在一 根电线上串行发出;接收数据时, UART 检测另一根电线上的信号,将串行数据收集放在缓 冲区中,CPU 即可读取 UART 获得这些数据。 UART 之间以全双工方式传输数据,最精简的连 线方法只有三根电线:TxD 用于发送数据, RxD 用于接收数据,GND 用于给双方提供参考电 平,连线如图所示:

img

UART 使用标准的 TTL/CMOS 逻辑电平(0~5V、0~3.3V、0~2.5V 或 0~1.8V 四种)来表 示数据,高电平表示 1,低电平表示 0。进行长距离传输时,为了增强数据的抗干扰能力、 提高传输长度, 通常将 TTL/CMOS 逻辑电平转换为 RS-232 逻辑电平, 3~12V 表示 0,-3~- 12V 表示 1。

TxD、RxD 数据线以“位”为最小单位传输数据。帧(frame)由具有完整意义的、不可 分割的若干位组成,它包含开始位、数据位、较验位(需要的话)和停止位。发送数据之前,

UART 之间要约定好数据的传输速率(即每位所占据的时间,其倒数称为波特率)、数据的传

输格式(即有多少个数据位、是否使用较验位、是奇较验还是偶较验、有多少个停止位)。 数据传输流程如下:

  • 平时数据线处于“空闭”状态(1 状态)。
  • 当要发送数据时,UART 改变 TxD 数据线的状态(变为 0 状态)并维持 1 位的时间──这 样接收方检测到开始位后,再等待 1.5 位的时间就开始一位一位地检测数据线的状态 得到所传输的数据。
  • UART 一帧中可以有 5、6、7 或 8 位的数据,发送方一位一位地改变数据线的状态将它 们发送出去,首先发送最低位。
  • 如果使用较验功能, UART 在发送完数据位后,还要发送 1 个较验位。有两种较验方法: 奇较验、偶较验──数据位连同较验位中, “1”的数目等于奇数或偶数。
  • 最后, 发送停止位, 数据线恢复到“空闭”状态(1 状态)。停止位的长度有 3 种: 1 位、 1.5 位、 2 位。

下图演示了 UART 使用 7 个数据位、偶较验、2 个停止位的格式传输字符“A ”(二进制 值为 0b01000001)时, TTL/CMOS 逻辑电平、 RS-232 逻辑电平对应的波形。

img

双方约定了“传输一 bit 数据的时间”, 就可以算出 1 秒内能传输多少 bit 数据, 这 被称为“比特率”, 又经常被称为“波特率”。两者有什么关系?

假设发送方 A 能精确控制信号的电压, 接收方 B 也能精确识别电压, 双方如此约定:

电压范围表示的两 bit 数据
0~0.7V0b00
~0.8 1.5V0b01
1.6~2.3V0b10
~2.4 3.3V0b11

那么要传输一个字节的数据, 比如 0x78,它的二进制数为 0b01,11,10,00,只需要传 输 4 次(假设 1ms 改变一次电压, 假设先传输低位):

  • 第 1ms,A 设置电压为 0V,B 识别出电压后,认为收到了 bit1 为 0、bit0 为 0
  • 第 2ms,A 设置电压为 1.6V,B 识别出电压后, 认为收到了 bit3 为 1、bit2 为 0
  • 第 3ms,A 设置电压为 2.4V,B 识别出电压后, 认为收到了 bit5 为 1、bit4 为 1
  • 第 4ms,A 设置电压为 0.8V,B 识别出电压后, 认为收到了 bit7 为 0、bit6 为 1

只需要 4ms,就传输了 4 个状态,但是传输了 8bit 数据:波特率*2=比特率。

假设发送方 A 精确控制信号电压的能力比较差,只能保证 00.7V、1.83.3V 的电压比 较稳定;接收方 B 识别电压的能力也不够精确,只能保证可以识别出 0~0.7V、1.8 3.3V 的 电压, 于是双方约定:

电压范围表示的 1 bit 数据
0~0.7V0
1.8~3.3V1

那么要传输一个字节的数据, 比如 0x78,它的二进制数为 0b01111000,需要传输 8 次 (假设 1ms 改变一次电压,假设先传输低位):

  • 第 1ms,A 设置电压为 0V,B 识别出电压后,认为收到了 bit0 为 0
  • 第 2ms,A 设置电压为 0V,B 识别出电压后,认为收到了 bit1 为 0
  • 第 3ms,A 设置电压为 0V,B 识别出电压后,认为收到了 bit2 为 0
  • 第 4ms,A 设置电压为 3.3V,B 识别出电压后, 认为收到了 bit3 为 1
  • 第 5ms,A 设置电压为 3.3V,B 识别出电压后, 认为收到了 bit4 为 1
  • 第 6ms,A 设置电压为 3.3V,B 识别出电压后, 认为收到了 bit5 为 1
  • 第 7ms,A 设置电压为 3.3V,B 识别出电压后, 认为收到了 bit6 为 1
  • 第 8ms,A 设置电压为 0V,B 识别出电压后,认为收到了 bit7 为 0

需要 8ms,传输 8 个状态, 传输了 8bit 数据:波特率=比特率。

所以,波特率: 1 秒内传输信号的状态数(波形数)。比特率: 1 秒内传输数据的 bit 数。如果一个波形, 能表示 N 个 bit,那么:波特率 * N = 比特率。

3.2.2 STM32H5 UART 硬件结构

img

3.2.3 RS485 协议

使用 RS485 协议传输数据时, 电路图如下:

img

RS485 协议里,使用 A、B 差分信号线传输数据: 两线间的电压差为+(2 至 6)V 表示 逻辑 1,电压差为-(2 至 6)V 时表示逻辑 0。它是半双工的传输方式:MCU1 要发送数据 时,从 TxD 引脚把数据发送给电平转换芯片 MAX13487EESA,它把 TxD 的信号转换为差分信 号传递给另一个电平转换芯片 MAX13487EESA,进而转换为 TTL 电平通过 RO 发送到 MCU2 的 RxD 引脚。MCU2 要给 MCU1 发送数据的话,必须等待差分信号线处于空闲状态。

对于软件而言, 使用 RS485 跟普通的 UART 没有区别。

3.3 UART 编程

3.3.1 硬件连接

按照下图连线: 调试、供电、两个 485 互连。

img

3.3.2 三种编程方式

结合 UART 硬件结构, 有 3 种编程方法:

  • 查询方式:
    • 要发送数据时, 先把数据写入 TDR 寄存器, 然后判断 TDR 为空再返回。当然也可以先 判断 TDR 为空, 再写入。
    • 要读取数据时, 先判断 RDR 非空, 再读取 RDR 得到数据。
  • 中断方式:
    • 使用中断方式, 效率更高,并且可以在接收数据时避免数据丢失。
    • 要发送数据时, 使能“TXE”中断(发送寄存器空中断)。在 TXE 中断处理函数里, 从 程序的发送 buffer 里取出一个数据, 写入 TDR。等再次发生 TXE 中断时, 再从程序的发送 buffer 里取出下一个数据写入 TDR。
    • 对于接收数据,在一开始就使能“RXNE”中断(接收寄存器非空) 。这样,UART 接收 到一个数据就会触发中断,在中断程序里读取 RDR 得到数据, 存入程序的接收 buffer。当 程序向读取串口数据时, 它直接读取接收 buffer 即可。
    • 这里涉及的“发送 buffer”、“接收 buffer”,特别适合使用“环形 buffer ”。
  • DMA 方式:
    • 使用中断方式时, 在传输、接收数据时,会发生中断, 还需要 CPU 执行中断处理函数。 有另外一种方法:DMA(Direct Memory Access), 它可以直接在 2 个设备之间传递数据,无需 CPU 参与。

框图如下:

img

设置好 DMA(源、目的、地址增减方向、每次读取数据的长度、读取次数)后,DMA 就 会自动地在 SRAM 和 UART 之间传递数据:

  • 发送时: DMA 从 SRAM 得到数据, 写入 UART 的 TDR 寄存器
  • 接收时: DMA 从 UART 的 RDR 寄存器得到数据, 写到 SRAM 去
  • 指定的数据传输完毕后,触发 DMA 中断;在数据传输过程中,没有中断, CPU 无需处理。

函数如下:

查询方式中断方式DMA 方式
发送HAL_UART_TransmitHAL_UART_Transmit_ITHAL_UART_TxCpltCallbackHAL_UART_Transmit_DMAHAL_UART_TxHalfCpltCallback HAL_UART_TxCpltCallback
接 收HAL_UART_ReceiveHAL_UART_Receive_ITHAL_UART_RxCpltCallbackHAL_UART_Receive_DMAHAL_UART_RxHalfCpltCallback HAL_UART_RxCpltCallback
错误HAL_UART_ErrorCallbackHAL_UART_ErrorCallback

3.3.3 查询方式

本 节 程 序 源 码 为 “ 3_ 程 序 源 码 \01_ 视 频 配 套 的 源 码 \3-4_UART 编程 ( 查 询 方 式)\uart_poll.7z”。
缺点: 发送数据时要死等发送完毕,接收数据时容易丢失。

3.3.4 中断方式

本 节 程 序 源 码 为 “ 3_ 程 序 源 码 \01_ 视 频 配 套 的 源 码 \3-5_UART 编程 ( 中 断 方 式)\uart_int.7z”。
缺点: 需要是事先调用接收函数, 才能通过中断接收数据, 易丢失。

3.3.5 DMA 方式

本 节 程 序 源 码 为 “ 3_ 程序源码 \01_ 视 频 配 套 的 源 码 \3-6_UART 编程 (DMA 方 式)\uart_dma.7z”。
本节讲的是传统 DMA 方式,不涉及“idle 中断”, 它会在后面讲解。 缺点: 需要是事先调用接收函数, 才能通过中断接收数据, 易丢失。

3.4 效率最高的 UART 编程方法

3.4.1 IDLE 中断

IDLE,空闲的定义是:总线上在一个字节的时间内没有再接收到数据。

UART 的 IDLE 中断何时发生? RxD 引脚一开始就是空闲的啊, 难道 IDLE 中断一直产生? 不是的。当我们使能 IDLE 中断后,它并不会立刻产生,而是: 至少收到 1 个数据后, 发现 在一个字节的时间里,都没有接收到新数据,才会产生 IDLE 中断。

我们使用 DMA 接收数据时,确实可以提高 CPU 的效率, 但是“无法预知要接收多少数 据”, 而我们想尽快处理接收到的数据。怎么办? 比如我想读取 100 字节的数据, 但是接 收到 60 字节后对方就不再发送数据了, 怎么办? 我们怎么判断数据传输中止了? 可以使用 IDLE 中断。在这种情况下,DMA 传输结束的条件有 3:

  • 接收完指定数量的数据了, 比如收到了 100 字节的数据了,HAL_UART_RxCpltCallback 被调用
  • 总线空闲了: HAL_UARTEx_RxEventCallback 被调用
  • 发生了错误: HAL_UART_ErrorCallback 被调用

使用 IDLE 状态来接收的函数有:

函数回调函数
查询方式HAL_UARTEx_ReceiveToIdle根据返回参数 RxLen 判断是否接收 完毕, 还是因为空闲而返回
中断方式HAL_UARTEx_ReceiveToIdle_IT完毕: HAL_UART_RxCpltCallback 因为空闲而中止:HAL_UARTEx_RxEventCallback
DMA 方式HAL_UARTEx_ReceiveToIdle_DMA传输一半:HAL_UART_RxHalfCpltCallback 完毕:HAL_UART_RxCpltCallback因为空闲而中止:HAL_UARTEx_RxEventCallback
错误HAL_UART_ErrorCallback

3.4.2 DMA 发送/DMA+IDLE 接收

要点有 3:

  • 对于发送:使用“HAL_UART_Transmit_DMA”函数
  • 对于接收:一开始就调用“HAL_UARTEx_ReceiveToIdle_DMA”启动接收
  • 在回调函数“HAL_UART_RxCpltCallback”或“HAL_UARTEx_RxEventCallback”里读取、 存储数据后,再次调用“HAL_UARTEx_ReceiveToIdle_DMA”启动接收

3.5 在 RTOS 里使用 UART

3.5.1 程序框架

本程序的重点在于如何高效地接收数据:

  • 使用 DMA+IDLE 中断的方式接收数据,它会把数据存入临时缓冲区;
  • 在回调函数里:把临时缓冲器的数据写入队列,然后再次使能 DMA
  • AP 读取队列: 如果队列里没有数据则阻塞。

框架如下:

img

3.5.2 编写程序

本 节 程 序 源 码 为 “ 3_ 程 序 源 码 \01_ 视 频 配 套 的 源 码 \3-8_ 在 RTOS 里 使 用 UART\uart_rtos.7z”。

3.5.3 面向对象封装 UART

我们使用多个 UART:UART2、UART4,以初始化为例,有如下函数:

void UART2_Rx_Start(void);
void UART4_Rx_Start(void) ;

对于使用者而言,非常不友好:当 UART 数量增多,他需要记住、使用多个函数名;当 更换某个 UART,他需要修改多处代码。 比如对于如下代码, 当需要更换为 UART4 时, 需要 修改第 1、3 行代码为 UART4 的函数:

 uart2_init(115200, 'N', 8, 1);char *str = “www.100ask.net”;uart2_sendp(str, strlen(str), 100);

把 UART 的操作封装为结构体, 可以解决这个问题。 UART 的操作主要有 3 个函数: 初始 化、发送数据、接收数据。那么可以抽象出如下结构体:

struct UART_Device {char *name;int (*Init)( struct UART_Device *pDev, int baud, char parity, int data_bit, int stop_bit);int (*Send)( struct UART_Device *pDev, uint8_t *datas, uint32_t len, int timeout); int (*RecvByte)( struct UART_Device *pDev, uint8_t *data, int timeout);
};

本节为 UART2、UART4 分别构造一个“struct UART_Device”结构体,比如:

strcut UART_Device g_uart2_dev = {“uart2”, uart2_init, uart2_send, uart2_recvbyte};
strcut UART_Device g_uart4_dev = {“uart4”, uart4_init, uart4_send, uart4_recvbyte};

使用时,示例代码如下:

struct UART_Device *pDev = &g_uart2_dev;
pDev->Init(pDev, 115200, 'N', 8, 1);
char *str = “www.100ask.net”;
pDev->Send(pDev, str, strlen(str), 100);

如果要更换串口,只需要修改第 1 行代码, 让它指向 g_uart4_dev 即可:这就是面向 对象编程的优点。

本节代码为: 本节程序源码为“3_程序源码\01_视频配套的源码\3-9_面向对象封装 UART\uart_rtos_all_ok.7z ”

先使用 STM32CubeMX 配置 UART2、UART4,把它们的发送、接收都使用 DMA。如下图配 置:

img

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

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

相关文章

扫描全能王的AI驱动创新与智能高清滤镜技术解析

目录 引言1、扫描全能王2、智能高清滤镜黑科技2.1、图像视觉矫正2.2、去干扰技术 3、实际应用案例3.1、打印文稿褶皱检测3.2、试卷擦除手写3.3、老旧文件处理3.4、收银小票3.5、从不同角度扫描文档 4、用户体验结论与未来展望 引言 在数字化时代背景下,文档扫描功能…

【JavaEE】JVM

文章目录 一、JVM 简介二、JVM 运行流程三、JVM 运行时数据区1、堆(线程共享)2、Java虚拟机栈(线程私有)3、本地方法栈(线程私有)4、程序计数器(线程私有)5、方法区(线程…

如何有效保护生物医药企业隔离网数据导出的安全性?

生物医药企业的核心数据保护至关重要,企业为了保护内部的核心数据,会将网络进行物理隔离,将企业内⽹与外⽹隔离。⽹络隔离后,仍存在重要数据从内网导出至外网的隔离网数据导出需求。以下是一些需要特别保护的核心数据类型&#xf…

【快速排序】| 详解快速排序 力扣912

🎗️ 主页:小夜时雨 🎗️专栏:快速排序 🎗️如何活着,是我找寻的方向 目录 1. 题目解析2. 代码 1. 题目解析 题目链接: https://leetcode.cn/problems/sort-an-array/ 我们上道题讲过快速排序的核心代码&a…

围观AI大佬吴恩达教授开发的Agent智能体

最近 Agent 智能体很火,人工智能领域国际上最权威的学者之一吴恩达教授,不但总结了Agent设计模式,还亲自下场开发了一款翻译Agent。 这个翻译Agent在设计模式和提示词工程等方面都有许多值得学习的地方。老渡拆解一下,跟朋友们分…

你需要明白的JVM相关问题

1、说说内存溢出跟内存泄漏的区别? 内存泄露:申请的内存空间没有被正确释放,导致内存被白白占用。内存溢出:申请的内存超过了可用内存,内存不够了。可能是泄漏导致的。 2、如何判断对象仍然存活?jvm是怎么…

mysql数据库的主从复制

MySQL主从复制的应用场景 当只有一台MySQL服务器要负责读写时,对于安全性,高可用,高并发等需求就不能满足,因此就要建立集群,集群的基础就是主从复制。 原理(过程) MySQL支持的复制类型 基于语…

有关主流编程语言的几个问题及对比

参考:编程语言的历史(https://blog.csdn.net/david_lv/article/details/104765347) 静态与动态语言的优缺点分析 什么是强类型,什么是弱类型?哪种更好些?为什么? 强类型和弱类型的区别 几种常见的开发语言…

【Kubernetes学习】

K8S基础概念一 一、k8s是什么?二、k8s功能三、k8s组件四、k8s概念总结 一、k8s是什么? kubernetes,简称k8s,是一个全新的基于容器技术的分布式架构领先方案,是谷歌严格保密十几年的秘密武器----Borg系统的一个开源版本…

kicad第三方插件安装问题

在使用KICAD时想安装扩展内容,但是遇到下载失败,因为SSL connect error。 因为是公司网络,我也不是很懂,只能另寻他法。找到如下方法可以曲线救国。 第三方插件包目录 打开存放第三方插件存放目录,用于存放下载插件包…

电子电路学习笔记(3)三极管

部分内容参考链接: 电子电路学习笔记(5)——三极管_三极管 箭头-CSDN博客 模拟电子技术基础笔记(4)——晶体三极管_集电结的单向导电性-CSDN博客 硬件基本功-36-三极管Ib电流如何控制Ic电流_哔哩哔哩_bilibili 部分…

【面试系列】数据工程师高频面试题及详细解答

欢迎来到我的博客,很高兴能够在这里和您见面!欢迎订阅相关专栏: ⭐️ 全网最全IT互联网公司面试宝典:收集整理全网各大IT互联网公司技术、项目、HR面试真题. ⭐️ AIGC时代的创新与未来:详细讲解AIGC的概念、核心技术、…

LIDAR360MLS V7.2.0 雷达点云数据处理软件功能介绍

LiDAR360MLS 是三维要素智能提取及分析软件,可灵活衔接市面上主流的多型号车载、背包或手持等移动测量设备采集的激光雷达点云与影像数据。软件基于高效的海量点云数据索引结构和人工智能算法,进行点云数据编辑、提取、分析等处理及道路要素目标的三维矢…

使用新H5标签dialog,实现点击按钮显示分享链接弹出层交互功能

使用新H5标签&#xff0c;实现点击按钮显示分享链接弹出层交互功能 在现代网页开发中&#xff0c;使用新技术和标签来提升用户体验是非常重要的。今天&#xff0c;我们就来聊聊如何利用HTML5的<dialog>标签来实现一个简洁实用的分享链接功能。 在过去&#xff0c;我们通常…

SpringBoot:集成机器学习模型进行预测和分析

引言 机器学习在现代应用程序中扮演着越来越重要的角色。通过集成机器学习模型&#xff0c;开发者可以实现智能预测和数据分析&#xff0c;从而提高应用程序的智能化水平。SpringBoot作为一个强大的框架&#xff0c;能够方便地集成机器学习模型&#xff0c;并提供灵活的部署和…

RT-Thread ENV-Windows v2.0.0安装教程

前言 前几天RT-Thread官方更新了env工具&#xff0c;开源仓库的Kconfig的写法都不大一样了&#xff1b;如果继续用原来的env工具&#xff0c;拉新代码之后很多示例都编译不了 在最新的env工具中menuconfig全面采用kconfiglib&#xff0c;升级env脚本和python版本&#xff0c;改…

Pascal 函数入门示例,及其汇编语言分析

1&#xff0c; Pascal 函数的定义格式 pascal 函数的定义语法格式: FUNCTION 函数名(形式参数表):函数类型; VAR 函数的变量说明; BEGIN 函数体; END; 2&#xff0c;Pascal 函数定义调用示例 order_self.pas 代码&#xff1a; PROGRAM example01;va…

PyTorch之nn.Module、nn.Sequential、nn.ModuleList使用详解

文章目录 1. nn.Module1.1 基本使用1.2 常用函数1.2.1 核心函数1.2.2 查看函数1.2.3 设置函数1.2.4 注册函数1.2.5 转换函数1.2.6 加载函数 2. nn.Sequential()2.1 基本定义2.2 Sequential类不同的实现2.3 nn.Sequential()的本质作用 3. nn.ModuleList参考资料 本篇文章主要介绍…

操作系统精选题(二)(综合模拟题一)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;操作系统 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 前言 简答题 一、进程由计算和IO操作组…

Leica Cyclone 3DR2024 一款功能强大的点云建模软件下载License获取

Leica Cyclone 3DR 2024 是一款功能强大的点云建模软件&#xff0c;使用旨在为用户提供全面的点云管理、自动化的点云分析&#xff0c;结合强大的建模&#xff0c;在一个直观友好的环境中&#xff0c;专注的完成挑战&#xff0c;提高生产力&#xff0c;轻松创建并交付专业的成果…