STM32USART串口数据包

文章目录

  • 前言
  • 一、介绍部分
    • 数据包两种包装方式(分割数据)
      • HEX数据包
      • 文本数据包
    • 数据包的收发流程
      • 数据包的发送
      • 数据包的接收
        • 固定包长的hex数据包接收
        • 可变包长的文本数据包接收
  • 二、实例部分
    • 固定包长的hex数据包接收
      • 连接线路
      • 代码实现
    • 可变包长的文本数据包接收
      • 连接线路
      • 代码实现
      • 补充


前言

当串口需要发送多个字节的数据时,使用数据包的形式来发送和接收是更加方便的,还可以更好的区分各个字节数据所对应的内容。例如需要发送X,Y,Z来控制陀螺仪传感器的X,Y,Z,就可以使用数据包的格式。


一、介绍部分

数据包两种包装方式(分割数据)

发送一连串数据时,无法精准的确定此数据对应哪一位,所以需要分割数据来使其位置一一对应。或者限制数据的大小来避免数据与包头包尾重复。

1. 使用包头加包尾把数据给包裹起来
在数据与包头包尾有重复时,尽量使用固定包长的方法,以免错误的识别到包头或包尾导致数据错误。
2. 使用一位标志位来确定数据开始

HEX数据包

在这里插入图片描述

文本数据包

在这里插入图片描述

数据包的收发流程

数据包的发送

和普通的数据发送一致,使用一个数组来接收这个数据包,然后使用相关的发送函数即可。

数据包的接收

每接收一个字节的数据,程序就会进入一次中断然后退出中断,所以每个数据的接收是独立的,所以要针对不同的状态进行对应的判断和进行相关的偏移,使这些数据关联起来,也就是状态机思路。

固定包长的hex数据包接收

如图,判断包头为状态s=1,判断成功使状态s=2,则接收数据状态,接收满4个数据后,使状态s=3,判断包尾,判断成功后进入下一轮。
在这里插入图片描述

可变包长的文本数据包接收

在这里插入图片描述

二、实例部分

固定包长的hex数据包接收

使按下按键可以发送递增数据包,将接收的数据包与发送的数据包都显示到OLED上

连接线路

在这里插入图片描述

代码实现

串口配置Serial.c

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>uint8_t TxData[4];	// 发送数据包
uint8_t RxData[4];  // 接收数据包
uint8_t RxFlag;void Serial_Init(void){// 开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);// 初始化引脚GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		// 复用推挽输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;	// A9 发送数据GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		// 50Hz翻转速度GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		// 上拉输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;	// A10 接收数据GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		// 50Hz翻转速度GPIO_Init(GPIOA, &GPIO_InitStructure);// 初始化串口配置USART_InitTypeDef USART_InitStructure;USART_InitStructure.USART_BaudRate = 9600; // 串口波特率USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 不使用流控USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 串口模式,发送+接收USART_InitStructure.USART_Parity = USART_Parity_No; // 无校验USART_InitStructure.USART_StopBits = USART_StopBits_1; // 选择一位停止位USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 不需要校验位,八位字长USART_Init(USART1,&USART_InitStructure);// 开启中断USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//初始化NVICNVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	// 分组NVIC_InitTypeDef NVIC_InitStructure;// 中断通道NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;// 中断通道使能NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;// 抢占优先级NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;// 响应优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStructure);// USART1使能USART_Cmd(USART1,ENABLE);
}// 发送函数
void USART_SendByte(uint8_t Byte){USART_SendData(USART1,Byte);// 等待写入完成,写入完成之后会将标志位自动清0while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
}// 发送数组函数
void USART_SendArray(uint8_t *Array,uint16_t Length){uint8_t i = 0;for(i=0;i<Length;i++){USART_SendData(USART1,Array[i]);// 等待写入完成,写入完成之后会将标志位自动清0while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);}
}// 发送字符串函数
void USART_SendString(uint8_t *String){uint8_t i = 0;for(i=0;String[i]!='\0';i++){USART_SendData(USART1,String[i]);// 等待写入完成,写入完成之后会将标志位自动清0while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);}
}// 返回X的Y次方
uint32_t Serial_Pow(uint32_t X,uint32_t Y){uint32_t Result = 1;while(Y--){Result *= X;}return Result;
}
// 发送数字函数
void USART_SendNum(uint32_t Num,uint16_t Length){uint8_t i = 0;for(i=0;i<Length;i++){USART_SendByte(Num / Serial_Pow(10,Length-i-1) % 10 + 0x30);// 等待写入完成,写入完成之后会将标志位自动清0while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);}
}//重定向fputc函数,fputc是printf函数的底层,printf通过不停的调用fputc来达到输出的效果
//重定向到串口
int fputc(int ch,FILE *f){USART_SendByte(ch);return ch;
}// 封装使用sprintf输出到串口
void Serial_Printf(char *format, ...)
{char String[100];va_list arg;							// 可变参数列表va_start(arg, format);		// 从format开始接收可变参数vsprintf(String, format, arg);va_end(arg);USART_SendString((uint8_t*)String);
}// 获取RxFlag
uint8_t USART_GetRxFlag(void){if(RxFlag == 1){RxFlag = 0;return 1;}return 0;
}void USART_SendPacket(void){USART_SendByte(0xff);				// 发送包头USART_SendArray(TxData,4);	// 发送数据USART_SendByte(0xfe);				// 发送包尾
}//中断函数
void USART1_IRQHandler(void){static uint8_t RxStatus = 0;		// 数据包状态static uint8_t RxCount = 0;if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET){uint8_t RXDATA = USART_ReceiveData(USART1);	// 获取接收到的数据if(RxStatus == 0){if(RXDATA == 0xff){RxStatus = 1;}}else if(RxStatus == 1){RxData[RxCount] = RXDATA;RxCount++;if(RxCount >= 4){RxCount = 0;RxStatus = 2;}}else if(RxStatus == 2){if(RXDATA == 0xfe){RxStatus = 0;RxFlag = 1;}}}
}

按键button.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"/*** @brief  初始化Button相关端口* @param 	无* @retval 无*/
void Button_Init(void){// 初始化时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		// 上拉输出,按下为0GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		// 50Hz翻转速度GPIO_Init(GPIOB, &GPIO_InitStructure);// 使这两个端口默认高电平,不然初始化后默认是低电平GPIO_SetBits(GPIOB, GPIO_Pin_1);
}
/*** @brief  返回所按按键值* @param 	无* @retval KeyNum 按键值*/
uint8_t Key_Num(void){uint8_t KeyNum = 0;if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)==0){Delay_ms(20);while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)==0);Delay_ms(20);KeyNum = 1;}if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)==0){Delay_ms(20);while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)==0);Delay_ms(20);KeyNum = 11;}return KeyNum;
}

主函数main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "button.h"uint8_t KeyNum;int main(void)
{OLED_Init();OLED_ShowString(1, 1, "TxData:");OLED_ShowString(3, 1, "RxData:");Button_Init();Serial_Init();TxData[0] = 0x01;TxData[1] = 0x02;TxData[2] = 0x03;TxData[3] = 0x04;//USART_SendPacket();while (1){KeyNum = Key_Num();if(KeyNum == 1){TxData[0] ++;TxData[1] ++;TxData[2] ++;TxData[3] ++;USART_SendPacket();OLED_ShowHexNum(2,1,TxData[0],2);OLED_ShowHexNum(2,4,TxData[1],2);OLED_ShowHexNum(2,7,TxData[2],2);OLED_ShowHexNum(2,10,TxData[3],2);}if(USART_GetRxFlag() == 1){OLED_ShowHexNum(4,1,RxData[0],2);OLED_ShowHexNum(4,4,RxData[1],2);OLED_ShowHexNum(4,7,RxData[2],2);OLED_ShowHexNum(4,10,RxData[3],2);}}
}

可变包长的文本数据包接收

主要功能为接收字符串,通过特定的字符串来实现led的点亮和关闭,并把相关信息显示在OLED上

连接线路

在这里插入图片描述

代码实现

led配置led.c

#include "stm32f10x.h"                  // Device header/*** @brief  初始化LED相关端口,让LED所在端口可以被直接赋值* @param 	无* @retval 无*/
void LED_Init(void){// 初始化时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 配置LED所在端口GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;		// 通用推挽输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		// 50Hz翻转速度GPIO_Init(GPIOA, &GPIO_InitStructure);// 使这两个端口默认高电平,不然初始化后默认是低电平GPIO_SetBits(GPIOA, GPIO_Pin_1);
}
/*** @brief  LED1亮* @param 	无* @retval 无*/
void LED1_On(void){GPIO_ResetBits(GPIOA, GPIO_Pin_1);
}
/*** @brief  LED1关* @param 	无* @retval 无*/
void LED1_Off(void){GPIO_SetBits(GPIOA, GPIO_Pin_1);
}
/*** @brief  LED2亮* @param 	无* @retval 无*/
void LED2_On(void){GPIO_ResetBits(GPIOA, GPIO_Pin_2);
}
/*** @brief  LED2关* @param 	无* @retval 无*/
void LED2_Off(void){GPIO_SetBits(GPIOA, GPIO_Pin_2);
}
/*** @brief  LED1取反* @param 	无* @retval 无*/
void LED1_Reverse(void){// 读取端口状态,根据状态取反if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_1) == 0)GPIO_SetBits(GPIOA, GPIO_Pin_1);elseGPIO_ResetBits(GPIOA, GPIO_Pin_1);
}
/*** @brief  LED2取反* @param 	无* @retval 无*/
void LED2_Reverse(void){// 读取端口状态,根据状态取反if(GPIO_ReadOutputDataBit(GPIOA,GPIO_Pin_2) == 0)GPIO_SetBits(GPIOA, GPIO_Pin_2);elseGPIO_ResetBits(GPIOA, GPIO_Pin_2);
}

串口配置serial.c

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>char RxData[100];  // 接收数据包
uint8_t RxFlag;void Serial_Init(void){// 开启时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);// 初始化引脚GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;		// 复用推挽输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;	// A9 发送数据GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		// 50Hz翻转速度GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;		// 上拉输出GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;	// A10 接收数据GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		// 50Hz翻转速度GPIO_Init(GPIOA, &GPIO_InitStructure);// 初始化串口配置USART_InitTypeDef USART_InitStructure;USART_InitStructure.USART_BaudRate = 9600; // 串口波特率USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 不使用流控USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 串口模式,发送+接收USART_InitStructure.USART_Parity = USART_Parity_No; // 无校验USART_InitStructure.USART_StopBits = USART_StopBits_1; // 选择一位停止位USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 不需要校验位,八位字长USART_Init(USART1,&USART_InitStructure);// 开启中断USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//初始化NVICNVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	// 分组NVIC_InitTypeDef NVIC_InitStructure;// 中断通道NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;// 中断通道使能NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;// 抢占优先级NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;// 响应优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_Init(&NVIC_InitStructure);// USART1使能USART_Cmd(USART1,ENABLE);
}// 发送函数
void USART_SendByte(uint8_t Byte){USART_SendData(USART1,Byte);// 等待写入完成,写入完成之后会将标志位自动清0while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);
}// 发送数组函数
void USART_SendArray(uint8_t *Array,uint16_t Length){uint8_t i = 0;for(i=0;i<Length;i++){USART_SendData(USART1,Array[i]);// 等待写入完成,写入完成之后会将标志位自动清0while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);}
}// 发送字符串函数
void USART_SendString(uint8_t *String){uint8_t i = 0;for(i=0;String[i]!='\0';i++){USART_SendData(USART1,String[i]);// 等待写入完成,写入完成之后会将标志位自动清0while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);}
}// 返回X的Y次方
uint32_t Serial_Pow(uint32_t X,uint32_t Y){uint32_t Result = 1;while(Y--){Result *= X;}return Result;
}
// 发送数字函数
void USART_SendNum(uint32_t Num,uint16_t Length){uint8_t i = 0;for(i=0;i<Length;i++){USART_SendByte(Num / Serial_Pow(10,Length-i-1) % 10 + 0x30);// 等待写入完成,写入完成之后会将标志位自动清0while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);}
}//重定向fputc函数,fputc是printf函数的底层,printf通过不停的调用fputc来达到输出的效果
//重定向到串口
int fputc(int ch,FILE *f){USART_SendByte(ch);return ch;
}// 封装使用sprintf输出到串口
void Serial_Printf(char *format, ...)
{char String[100];va_list arg;							// 可变参数列表va_start(arg, format);		// 从format开始接收可变参数vsprintf(String, format, arg);va_end(arg);USART_SendString((uint8_t*)String);
}//中断函数
void USART1_IRQHandler(void){static uint8_t RxStatus = 0;		// 数据包状态static uint8_t RxCount = 0;if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET){uint8_t RXDATA = USART_ReceiveData(USART1);	// 获取接收到的数据if(RxStatus == 0){if(RXDATA == '@' && RxFlag == 0){RxStatus = 1;RxCount = 0;		// 初始化之前的数据长度}}else if(RxStatus == 1){if(RXDATA == '\r')RxStatus = 2;else{RxData[RxCount] = RXDATA;RxCount++;}}else if(RxStatus == 2){if(RXDATA == '\n'){RxStatus = 0;RxFlag = 1;RxData[RxCount] = '\0';		// 加入字符串结束标志位}}USART_ClearITPendingBit(USART1, USART_IT_RXNE); // 清除标志位}
}

主函数mian.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"
#include "LED.h"
#include <string.h>int main(void)
{OLED_Init();Serial_Init();LED_Init();OLED_ShowString(1, 1, "TxData:");OLED_ShowString(3, 1, "RxData:");while (1){if(RxFlag == 1){OLED_ShowString(4,1,"                "); // 显示前擦除这行OLED_ShowString(4,1,RxData);if(strcmp(RxData,"LED_ON")==0){LED1_On();USART_SendString((uint8_t*)"LED_ON_OK\r\n");OLED_ShowString(2,1,"                ");OLED_ShowString(2,1,"LED_ON_OK");}else if(strcmp(RxData,"LED_OFF")==0){LED1_Off();USART_SendString((uint8_t*)"LED_OFF_OK\r\n");OLED_ShowString(2,1,"                ");OLED_ShowString(2,1,"LED_OFF_OK");}else{USART_SendString((uint8_t*)"ERR_CMD\r\n");OLED_ShowString(2,1,"                ");OLED_ShowString(2,1,"ERR_CMD");}RxFlag = 0;}}
}

补充

这里没有使用函数封装标志位自动清除,在许多读写操作进行时可能因为读取速度太慢导致数据接收错位,所以在主函数中手动把标志位清0,保证把每个数据包接收完毕后才可以开始下一次接收,同时把serial.c的包头判断时加入标志位判断即if(RXDATA == ‘@’ && RxFlag == 0)。


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

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

相关文章

2核4g服务器能支持多少人访问?并发数性能测评

2核4g服务器能支持多少人访问&#xff1f;支持80人同时访问&#xff0c;阿腾云使用阿里云2核4G5M带宽服务器&#xff0c;可以支撑80个左右并发用户。阿腾云以Web网站应用为例&#xff0c;如果视频图片媒体文件存储到对象存储OSS上&#xff0c;网站接入CDN&#xff0c;还可以支持…

【Docker】若依后端项目搭建

一 搭建局域网 1 # 搭建net-ry局域网&#xff0c;用于部署若依项目docker network create net-ry --subnet172.68.0.0/16 --gateway172.68.0.1 # 注意1&#xff1a;关闭宿主机的防火墙&#xff0c;否者容器内部的MySQL、redis等服务&#xff0c;外部访问不了&#xff1b;开放…

JavaScript进阶 (2)

浅拷贝和深拷贝 只针对引用数据类型 浅拷贝 简单数据类型拷贝值&#xff0c;引用数据类型拷贝地址&#xff08;只适合单层&#xff09; 方法&#xff1a; 1.拷贝对象&#xff1a;Object.assgin() 或 展开运算符{...obj} 2.拷贝数组&#xff1a;Array.prototype.concat() 或…

电脑防火墙怎么关?分享2个简单方法

在当今数字化时代&#xff0c;保护计算机免受网络威胁和恶意软件攻击是至关重要的。电脑防火墙作为一种重要的安全措施&#xff0c;可以有效地阻止未经授权的网络访问&#xff0c;保障您的个人信息和数据安全。 然而&#xff0c;有时候在特定情况下&#xff0c;您可能需要临时…

IP风险画像有哪些应用?

IP风险画像&#xff0c;作为网络安全领域的一种重要技术手段&#xff0c;具有广泛的应用场景和深远的影响。通过对IP地址进行多维度的风险评估和特征提取&#xff0c;IP风险画像能够为组织和个人提供更加精准、全面的安全风险评估结果&#xff0c;从而帮助用户更好地识别、防范…

08. Nginx进阶-Nginx动静分离

简介 什么是动静分离&#xff1f; 通过中间件将动态请求和静态请求进行分离。分离资源&#xff0c;减少不必要的请求消耗&#xff0c;减少请求延时。 动静分离的好处 动静分离以后&#xff0c;即使动态服务不可用&#xff0c;静态资源仍不受影响。 动静分离示意图 动静分离…

[前端][死循环]问题发现[easyui]

文章目录 问题描述问题细节 解决思路综合分析 解决办法 问题描述 页面点击按钮跳转弹窗页面回显出数据 此弹窗页面中有年份&#xff0c;类型等&#xff0c;当选中年份/类型会重新触发回显方法(onSelect 中调用方法)&#xff0c;回显对应年份/类型得数据 问题细节 最开始调试…

leetcode 热题 100_找到字符串中所有字母异位词

题解一&#xff1a; 滑动窗口&#xff1a;类似于字符串匹配&#xff0c;但匹配异位词需要包含相同的字母及个数&#xff0c;可以分别用两个数组存储字符串s滑动窗口和字符串p的字母及个数&#xff0c;再用Array.equals()进行比对。对于s.length()<p.length()的情况需要特判。…

2核4g服务器能支持多少人访问?阿里云和腾讯云性能PK

2核4g服务器能支持多少人访问&#xff1f;支持80人同时访问&#xff0c;阿腾云使用阿里云2核4G5M带宽服务器&#xff0c;可以支撑80个左右并发用户。阿腾云以Web网站应用为例&#xff0c;如果视频图片媒体文件存储到对象存储OSS上&#xff0c;网站接入CDN&#xff0c;还可以支持…

【大数据】通过 docker-compose 快速部署 MinIO 保姆级教程

文章目录 一、概述二、MinIO 与 Ceph 对比1&#xff09;架构设计对比2&#xff09;数据一致性对比3&#xff09;部署和管理对比4&#xff09;生态系统和兼容性对比 三、前期准备1&#xff09;部署 docker2&#xff09;部署 docker-compose 四、创建网络五、MinIO 编排部署1&…

web坦克大战小游戏

H5小游戏源码、JS开发网页小游戏开源源码大合集。无需运行环境,解压后浏览器直接打开。有需要的订阅后,私信本人,发源码,含60+小游戏源码。如五子棋、象棋、植物大战僵尸、贪吃蛇、飞机大战、坦克大战、开心消消乐、扑鱼达人、扫雷、打地鼠、斗地主等等。 <!DOCTYPE htm…

苹果电脑专业的Mac垃圾清理工具CleanMyMac X4.14.7

CleanMyMac X是一款专业的Mac清理工具&#xff0c;它具有强大的功能和易用的界面&#xff0c;可以帮助用户快速清理Mac上的无用文件和垃圾&#xff0c;优化系统性能&#xff0c;提升电脑运行速度。 该软件的核心功能包括智能扫描与清理、应用程序管理、隐私保护和系统维护等。…