- 串口通信

USART串口通信

目录

USART串口通信

回顾

USART串口通信

1、通信分类与作用

2、串口通信的相关参数(重点)

3、位协议层 -- RS232协议

4、STM32F103 中的串口外设

5、调试串口编程

-- (1)串口初始化:时钟、IO、外设

-- (2)串口发送

-- (3)串口接收

-- 补充

-- 应用

6、中断

usart.c完整代码


回顾

定时器的构成:
计数器:计数
时钟:为计数器提供计数频率
重装栽植:计数的最大值
-- 计数器从 0 开始计数(向上计数),一直到计数值等于重装载值,计数值会自动清零。

阻塞:程序里使用了延时函数
实现非阻塞的目的:提高 cpu 利用率 每个任务之间互不干扰

-- 基础阶段应该掌握的知识:

  • 单片机的运行流程(从上往下,顺序执行)
  • 单片机的基本知识(GPIO 中断)
  • 代码:代码的基本编写(代码框架)

  • 从这章开始真正进入单片机的开发阶段。

-- 开发阶段:

  • 首先要掌握的就是开发方法(或者是开发的流程)。

  • 代码会偏向应用函数。

  • 相同类型的模块(通信方式(接口)相同的模块),开发是一致的

通信方式(其实就是接口是一致的,学习单片机其实就是学习它的各种接口),如IO,TIM,USART,I2C,SPI,ADC,DAC,PWM等。

USART串口通信

  • 今天先学习串口类设备如何进行开发

1、通信分类与作用

有线: 485 232 can
无线:WiFi 蓝牙 zigbee 4G
-- 这些通信都用来用作设备与设备之间的通信

SPI IIC USART -- 单片机和模块之间的通信


  • 这章讲的usart 特点:串行 异步 全双工

  • 特点的含义

串行:串行数据传输时,数据是一一位地在通信线上传输的 

alt text

并行:并行通信传输中有多个数据位,同时在两个设备之间传输。 

alt text

异步:发送端和接收端没有相同的时钟线。
-- 因为双方工作不在相同的频率,传输的数据会丢失,所以在异步通信时双方要规定波特率
Tip:波特率(bps)实际就是数据传输的速度

同步:发送端和接收端有相同的时钟线。- 两个设备要工作的相同的频率下

单工:一个设备只能发送或是接收 收音机
半双工:同一时间内,只能发送或是接收 对讲机
全双工:同一时间内,能发送也能接收


2、串口通信的相关参数(重点)

-- 串口类设备
如何识别该设备是串口类设备?
-- 从物理硬件接口(物理层)来看(一定要有下面图这几根线(引脚接口)TX,RX,GND),有这些引脚就是串口类设备) 

alt text

 -- 怎么接线也要注意一下

  • 怎么确保对方收到了数据?

-- 为了保证通信,双方都会存在通信协议
保证通信(通信流程、通信协议)
双方设备通信 A 和 B,设备 A 向设备 B 传输 1 字节内容。
1、 设备 A 如何确认设备 B 是否收到了信息?
设备 B 向设备 A 发送应答信号。
2、 设备 B 如何确认收到的数据是正确的?
校验
-- 在串口通信中,没有操作1,波特率在某种程度上解决了操作1的问题,但没有很好的解决。
-- 对于串口通信,保证了操作2,-- 用RS232协议

3、位协议层 -- RS232协议

  • 协议:数据传输格式

  • 数据传输将数据分成了4部分,分别为起始位,数据位,奇偶校验位,停止位

RS232协议将数据分为1个起始位,8个数据位,1个奇偶校验位,1个停止位: 1 8 0 1

校验位:奇偶校验 CRC校验
奇校验:数据位+奇偶检验位 里面的1奇数个
偶校验:数据位+奇偶检验位 里面的1偶数个
01010101 1 如果是奇校验,在后面补个1
01010101 0 如果是偶校验,在后面补个0

  • 设备 A 向设备 B 发送 8000 位数据 双方通信波特率 9600, 问:数据传输完毕,花费多长时间?

串口一次发送 8 位数据,需要发送 1000 次, 串口发送一次数据花费 10/9600s
(10/9600)*1000 = 100/96 S = 25/24s

4、STM32F103 中的串口外设

  • STM32F103ZET6:一共有 5 个 USART1 USART2 USART3 UART4 UART5
  • 单片机它可以同时驱动 5 个串口设备

alt text

5、调试串口编程

  • 通信双方 单片机和ch340

alt text

  • 串口通信编程:串口初始化,串口发送,串口接收
-- (1)串口初始化:时钟、IO、外设
  • 先看数据手册中该外设在哪条线上 

    alt text

  • 看原理图知道串口连接的引脚 

    alt text

  • 然后配置相应的时钟(A端口的时钟和USART1的时钟(外设自身也有时钟))

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);			//看数据手册在哪条线上 - APB2
  • 配置IO
//IO PA9/PA10GPIO_InitTypeDef GPIO_InitStructure = {0};						//定义结构体变量,并且将结构体变量赋初值GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; 						//引脚GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;			//速度GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//PA9 复用推挽GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; 							//引脚GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;		//浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);
  • 配置外设(配置串口)

-- 使用固件库使用手册 

alt text

-- 串口初始化函数原型 

alt text

-- 找到例子,复制到工程中(在例子的基础上更改) 

alt text

    USART_InitTypeDef USART_InitStructure = {0};USART_InitStructure.USART_BaudRate = 9600;   					 			//波特率  常用的是4800 9600  115200USART_InitStructure.USART_WordLength = USART_WordLength_8b;	//数据位长度USART_InitStructure.USART_StopBits = USART_StopBits_1;			//停止位长度USART_InitStructure.USART_Parity = USART_Parity_No;				//奇偶校验(这里写不使用)USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制失能(不使用硬件流控制)USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//模式USART_Init(USART1, &USART_InitStructure);USART_Cmd(USART1,ENABLE);//开启串口    //使能或者失能 USART 外设(一般外设都要写这个)
-- (2)串口发送

-- 在固件库使用手册中找到相应函数 

alt text

-- 如果只写发送数据的语句,那么怎么知道上次的数据是否已经发送完了,如果上次的数据没有发送完的情况下,再次发送数据的话,就会覆盖上次的数据,所以需要判断上次的数据是否发送完成。

-- 判断数据是否发送完成 -- 采用状态寄存器 

alt text

-- 发送数据空标志位为1,表示数据发送完成,反之标志位为0,上一次的数据还没有发送完成,所以需要等待,直到标志位为1,表示数据发送完成,再发送数据。 

alt text

//发送
void usart1_tx(uint8_t data)//参数就是要发送的数据
{while(USART_GetFlagStatus(USART1, USART_FLAG_TXE )== RESET){}//上次的数据发送完成,为空表示上个数据已经发送完成//等待的时候是还没有发送,一旦发送完成,就变成1了USART_SendData(USART1, data);
}
-- (3)串口接收

alt text

-- 接收数据寄存器非空标志位为1,表示数据接收完成,反之标志位为0,上一次的数据还没有接收完成,所以需要等待,直到标志位为1,表示数据接收完成,再接收数据。

alt text

//接收uint8_t usart1_rx(void)
{while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE )== RESET){} 			//接收数据寄存器非空标志位uint8_t RxData = USART_ReceiveData(USART1);return RxData;
}
-- 补充
  • 将printf输出的数据直接输出到串口上,将它写在usart.c中,然后在usart.h中声明。这样就可以直接使用printf函数了。
int fputc(int a,FILE *p)//重定向函数
{usart1_tx(a);return a;
}
-- 应用
  • main.c
int main()
{usart_init();uint8_t str[] = "123456789";uint8_t len = strlen((char *)str);for(uint8_t i=0;i<len;i++)usart1_tx(str[i]);//usart1_tx(0x1);uint8_t data = usart1_rx();//可以协助我们调试代码,接收到了,mprintf("data:%d\r\n",data);//这里的换行是\r\n					//这里是发送到串口。可以根据是否发送到串口来判断程序是否正常执行}

-- 这里的printf可以用来调试程序,前面已经将printf重定向到串口了,所以可以直接使用printf函数,将数据发送到串口上。在想知道一个模块是否执行时,就可以写一个printf,看是否发送数据来看是否执行这个模块。


  • 使用串口助手向单片机发送数据,控制led状态。例如:发送“1111”,4个led灯全亮;发送“1010”,13灯亮,24灯灭;发送“0000”,4个led灯全灭。(90%)
int main()
{led_init();uint8_t str[] = "123456789";uint8_t len = strlen((char *)str);for(uint8_t i=0;i<len;i++)usart1_tx(str[i]);while(1){uint8_t data[20]={0};for(uint8_t i=0;i<4;i++)data[i] = usart1_rx();if(!strcmp(data,"1111")){LED1(1);LED2(1);LED3(1);LED4(1);}else if(!strcmp(data,"1010")){LED1(1);LED2(0);LED3(1);LED4(0);}else if(!strcmp(data,"0000")){LED1(0);LED2(0);LED3(0);LED4(0);}printf("data:%s\r\n",data);//这里的换行是\r\n					//这里是发送到串口。可以根据是否发送到串口来判断程序是否正常执行}
}
  • 注意这里的换行符必须是\r\n

alt text

alt text


6、中断

-- 因为接收函数中写了,如果读不到数据们将会阻塞等待,会影响后面的程序 

alt text

alt text

  • 所以要采用中断的方式来接收数据,那么初始化函数就要加上中断的初始化

既要初始化中断,还要使能中断源

使能中断源,先从函数库中找相应的函数

alt text

 

alt text

复制例子,并更改

  • usart.c
	//使能中断源			//串口有10个,用哪一个就要开哪一个中断源USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//中断NVIC_InitTypeDef NVIC_InitStructure = {0}; NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能(使能哪个中断通道,就要写哪个(必须写)中断服务函数)NVIC_Init(&NVIC_InitStructure);
  • 中断服务函数的内容

1、判断中断是否发生 

alt text

 2、处理中断 3、清理中断 

alt text

uint8_t usart1_buff[10] = {0};//这些参数还要再usart.h中声明
uint8_t usart1_len = 0;void USART1_IRQHandler(void)//串口收到1字节数据,中断就会被触发一次
{//判断接收中断是否发生if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET){//处理中断:保存数据usart1_buff[usart1_len++] = USART_ReceiveData(USART1);usart1_len %= 10;//对10求余,他就一直会小于10//清理终端USART_ClearITPendingBit(USART1,USART_IT_RXNE);}
}
  • main.c
if(usart1_len == 4)//如果接收到的数据为4位时{if(usart1_buff[0] == '0')LED1(0);else if(usart1_buff[0] == '1')LED1(1);if(usart1_buff[1] == '0')LED2(0);else if(usart1_buff[1] == '1')LED2(1);if(usart1_buff[2] == '0')LED3(0);else if(usart1_buff[2] == '1')LED3(1);if(usart1_buff[3] == '0')LED4(0);else if(usart1_buff[3] == '1')LED4(1);memset(usart1_buff,0,10);//数据处理完毕之后,将这次数据全部清0usart1_len = 0;}
  • 注:数据处理完毕后,记得清理。

usart.c完整代码

#include "usart.h"void usart_init(void)
{//时钟 A端口  USART1(外设自身也有时钟)RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);			//看数据手册在哪条线上 - APB2//IO PA9/PA10GPIO_InitTypeDef GPIO_InitStructure = {0};						//定义结构体变量,并且将结构体变量赋初值GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; 						//引脚GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;			//速度GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//PA9 复用推挽GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; 							//引脚GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;		//浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);USART_InitTypeDef USART_InitStructure = {0};USART_InitStructure.USART_BaudRate = 9600;   					 			//波特率  常用的是4800 9600  115200USART_InitStructure.USART_WordLength = USART_WordLength_8b;	//数据位长度USART_InitStructure.USART_StopBits = USART_StopBits_1;			//停止位长度USART_InitStructure.USART_Parity = USART_Parity_No;				//奇偶校验(这里写不使用)USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//硬件流控制失能(不使用硬件流控制)USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;//模式USART_Init(USART1, &USART_InitStructure);USART_Cmd(USART1,ENABLE);//开启串口    //使能或者失能 USART 外设(一般外设都要写这个)//使能中断源			//串口有10个,用哪一个就要开哪一个中断源USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//中断NVIC_InitTypeDef NVIC_InitStructure = {0}; NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //中断通道NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //响应NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能(使能哪个中断通道,就要写哪个(必须写)中断服务函数)NVIC_Init(&NVIC_InitStructure);}//应用函数//发送
void usart1_tx(uint8_t data)//参数就是要发送的数据
{while(USART_GetFlagStatus(USART1, USART_FLAG_TXE )== RESET){}//上次的数据发送完成,为空表示上个数据已经发送完成//等待的时候是还没有发送,一旦发送完成,就变成1了USART_SendData(USART1, data);
}//接收uint8_t usart1_rx(void)
{while(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET){} 			//接收数据寄存器非空标志位uint8_t RxData = USART_ReceiveData(USART1);return RxData;
}int fputc(int a,FILE *p)//重定向函数
{usart1_tx(a);return a;
}uint8_t usart1_buff[10] = {0};
uint8_t usart1_len = 0;void USART1_IRQHandler(void)//串口收到1字节数据,中断就会被触发一次
{//判断接收中断是否发生if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET){//处理中断:保存数据usart1_buff[usart1_len++] = USART_ReceiveData(USART1);usart1_len %= 10;//对10求余,他就一直会小于10//清理终端USART_ClearITPendingBit(USART1,USART_IT_RXNE);}
}
  • usart.h
#ifndef _USART_H_
#define _USART_H_#include "stdio.h"
#include "STM32f10x.h"void usart_init(void);
void usart1_tx(uint8_t data);
uint8_t usart1_rx(void);int fputc(int a,FILE *p);extern uint8_t usart1_buff[10];
extern uint8_t usart1_len;#endif
s```s

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

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

相关文章

数据结构:队列及其应用

队列&#xff08;Queue&#xff09;是一种特殊的线性表&#xff0c;它的主要特点是先进先出&#xff08;First In First Out&#xff0c;FIFO&#xff09;。队列只允许在一端&#xff08;队尾&#xff09;进行插入操作&#xff0c;而在另一端&#xff08;队头&#xff09;进行删…

某客户Oracle RAC无法启动故障快速解决

某日&#xff0c;9:50左右接到好友协助需求&#xff0c;某个客户Oracle RAC无法启动&#xff0c;并发过来一个报错截图&#xff0c;如下&#xff1a; 和客户维护人员对接后&#xff0c;远程登录服务端进行故障分析。 查看hosts信息&#xff0c;首先进行心跳测试&#xff0c;测…

数据库软题3-专门的集合运算

一、投影&#xff08;筛选列&#xff09; 题1 题2 二、选择(筛选行) 三、连接 3.自然连接 题1-自然连接的属性列数&#xff08;几元关系&#xff09;和元组数 解析&#xff1a; 题2-自然连接的属性列数&#xff08;几元关系&#xff09;和元组数 自然连接后的属性个数 A列…

GNSS定位中自适应调整电离层延迟参数过程噪声的方法

文章目录 前言一、非差非组合PPP模型二、电离层功率谱密度计算三、具体实现方法3.1 不平滑3.2 三阶多项式平滑 参考文献 前言 GNSS定位中不少技术手段如PPP和长基线RTK需要将电离层延迟作为参数估计&#xff0c;电离层延迟的变化通常被描述为随机游走过程&#xff0c;而功率谱密…

解决sortablejs+el-table表格内限制回撤和拖拽回撤失败问题

应用场景&#xff1a; table内同一类型可拖拽&#xff0c;不支持不同类型拖拽&#xff08;主演可拖拽交换位置&#xff0c;非主演和主演不可交换位置&#xff09;,类型不同拖拽效果需还原&#xff0c;试了好几次el-table数据更新了&#xff0c;但是表格样式和数据不能及时保持…

Linux-df命令使用方法

Linux-df&#xff08;disk filesystem&#xff09;命令 df 命令是 Unix 和 Linux 系统中用于报告文件系统磁盘空间使用情况的工具。 df [OPTION]... [FILE]...OPTION 常用选项&#xff08;博主一般df -h用的较多&#xff0c;可读性较好&#xff09; -h&#xff1a;以人类可读的…

如何只用 CSS 制作网格?

来源&#xff1a;how-to-make-a-grid-like-graph-paper-grid-with-just-css 在看 用于打印到纸张的 CSS 这篇文章时&#xff0c;对其中的网格比较好奇&#xff0c;作者提供了 stackoverflow 的链接&#xff0c;就看到了来源的这个问题和众多回复。本文从里面挑选了一些个人比较…

docker简介、安装、基础知识

基础知识 Docker简介&#xff1a; 1.Docker是一种用于构建、发布及运行应用程序的开源项目&#xff0c;他通过容器化技术简化了应用程序的部署和管理 2.Docker是一个开源的应用容器引擎&#xff0c;基于go语言开发&#xff0c;为应用打包、部署平台&#xff0c;而非单纯的虚…

【Redis技术进阶之路】「原理分析系列开篇」探索事件驱动枚型与数据特久化原理实现(数据持久化的实现RDB)

揭秘高效存储模型与数据结构底层实现 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 Redis数据持久化的必要Redis数据持久化的实现RDB的持久化机制RDB文件的创建与载入SAVEBGSAVESAVE与BGSA…

基于SpringBoot+Vue的社区智慧消防管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码 精品专栏&#xff1a;Java精选实战项目…

Python数据分析和可视化详解

Python数据分析和可视化详解 Python 是当前最受欢迎的数据分析和可视化工具之一。凭借其简单的语法和强大的第三方库&#xff0c;Python 为数据科学家、分析师和工程师提供了广泛的工具&#xff0c;用于处理、分析和展示数据。本文将介绍如何使用 Python 进行数据分析与可视化…

8.12 矢量图层面要素单一符号使用五(点符号填充)

8.12 矢量图层面要素单一符号使用五(点符号填充)_mapguide edit composite symbolization 使符号填充面-CSDN博客 目录 前言 点符号填充&#xff08;Point pattern fill&#xff09; QGis设置面符号为点符号填充&#xff08;Point pattern fill&#xff09; 二次开发代码实…

数学建模-线性规划讲解(Matlab版本)

引言 相信不少小伙伴刚开始接触数学建模时&#xff0c;第一个学习的算法就是运筹学的重要分支--数学规划&#xff0c;而数学规划当中重要的分支就是线性规划了。在这里笔者参考了司守奎和孙玺菁老师的《数学建模算法与应用》(第三版&#xff09;这本书&#xff0c;以此来讲讲关…

【HTML5】html5开篇基础(3)

1.❤️❤️前言~&#x1f973;&#x1f389;&#x1f389;&#x1f389; Hello, Hello~ 亲爱的朋友们&#x1f44b;&#x1f44b;&#xff0c;这里是E绵绵呀✍️✍️。 如果你喜欢这篇文章&#xff0c;请别吝啬你的点赞❤️❤️和收藏&#x1f4d6;&#x1f4d6;。如果你对我的…

多元函数微分学基础题

这是基础题&#xff01;&#xff01;原则上必须要在第一轮初学并做完课后习题之后再做这个基础题&#xff0c;不能有错误&#xff08;马虎大意除外&#xff09;或无法解答。如有错误&#xff0c;该单元需要重学&#xff01;&#xff01; 多元函数微分学填空题 一、填空题 如…

在Pytorch中为不同层设置不同学习率来提升性能,优化深度学习模型

在深度学习模型的训练过程中&#xff0c;学习率作为一个关键的超参数&#xff0c;对模型的收敛速度和最终性能有着重大影响。传统方法通常采用统一的学习率&#xff0c;但随着研究的深入&#xff0c;我们发现为网络的不同层设置不同的学习率可能会带来显著的性能提升。本文将详…

基于Java的停车场管理微信小程序 停车场预约系统【源码+文档+讲解】

精彩专栏推荐订阅&#xff1a;在下方主页&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f496;&#x1f525;作者主页&#xff1a;计算机毕设木哥&#x1f525; &#x1f496; 文章目录 一、停车场管理微…

巴鲁夫rfid读头国产平替版——高频RFID读写器

随着RFID技术的不断发展&#xff0c;国内RFID企业的数量也在不断地变多&#xff0c;国产RFID读写器的质量也越来越高。具有着价格实惠、质量可靠等特点&#xff0c;成为了可平替国外RFID产品的首要选择。健永科技的高频RFID读写器JY-H830&#xff0c;是一款可平替巴鲁夫rfid读头…

基于SSM的“实习支教中小学学校信息管理系统”的设计与实现(源码+数据库+文档)

基于SSM的“实习支教中小学学校信息管理系统”的设计与实现&#xff08;源码数据库文档) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统功能结构图 主页 注册页面 师资力量界面 个…

机器学习(5):机器学习项目步骤(二)——收集数据与预处理

1. 数据收集与预处理的任务&#xff1f; 为机器学习模型提供好的“燃料” 2. 数据收集与预处理的分步骤&#xff1f; 收集数据-->数据可视化-->数据清洗-->特征工程-->构建特征集和数据集-->拆分数据集、验证集和测试集 3. 数据可视化工作&#xff1f; a. 作用&…