一、我们为什么需要串口?
在嵌入式通信中,我们常常把像SPI,USART,I2C这些 串行通信接口,统称为串口。但是在我们的日常使用中,我们说的串口通常是指的USART接口。下面我们就来了解USART接口,USART是一种非常常见的异步/同步接口,主要用于两台设备的通信。比如我们的上位机与下位机,下位机与下位机之间的通信我们都可以采用USART通信。我们也可以使用协议转换芯片,将USART电平信号转换为232.485电平信号从而增加USART通信的稳定性。总的来说,串口通信是在日常设备之间,以及工业控制中使用非常广泛的一种通信接口。下面,我们就来详细的了解一下串口吧!
二、本次教程都有些什么内容
在本次教程中,我们会更注重如何使用串口,而不去深究它的通信电平。因为串口是使用硬件直接产生通信电平,所以在串口通信电平处我也只会做简单的讲解。首先,我会带大家了解同步通信与异步通信的区别,然后我们会简单的了解串口的通信电平,其次我会带大家了解STM32标准库中串口相关的函数,再然后我们带大家了解如何重定向printf,以及重定向的函数为什么要这样写,如何使用中断接收数据。当然,本次的教程建立在你已经完成了STM32LED教程的基础上,对STM32的控制和程序下载的方式都有一定的了解之后,如果你还没有完成STM32LED的例程,可以参考下面的文章:
STM32 LED教程:[STM32]从零开始的STM32 LED教程(小白向)-CSDN博客
三、需要准备什么
本次我们的例程会教大家如何调试串口,所以,我们一个USB转TTL电平的转换器是不可少的,这里我仍然推荐大家使用CH340,这款协议转换芯片在之前的LED例程的串口程序下载部分也使用到了。我们这次需要调试STM32,我们还需要一块STM32的最小板,我这里同样使用STM32F103C6T6,在后面的演示中,我也都会使用这款芯片。同时我也建议大家下载我给的资料,本次的资料中包含的串口调试助手,以及串口的相关驱动,和本次代码的例程。我不建议新手将精力浪费在找资料上,所以,我这里建议大家下载我给出的资料:
串口相关资料:https://pan.baidu.com/s/1_5L0rl4YWRcTjowIf-FRQg?pwd=clxm
提取码:clxm
四、什么是同步通信与异步通信
首先,我们来讲解一下同步通信。在同步通信中,通信的双方会共享同一条时钟线,用以同步发送和接收数据。发送方和接收方都依赖时钟信号来确定何时开始发送或读取每一位数据,因此数据的传输速度较快且稳定。但是在实际生产环境中,为了保证稳定并且高效的数据传输,使用同步通信也可能会占用更多的引脚。这里大家需要记住的是,同步通信的时钟被统一了。我们这里所谓的同步,就是通信设备之间的时钟同步。像我们的SPI通信,I2C通信都属于一种同步通信。它们共同的特点就在于,在通信时,时钟线都是被连接在一起,由一个设备统一控制的。
下面,我们来讲解一下异步通信,异步通信不需要额外的时钟信号,数据的发送和接收是通过约定好的协议(如波特率)来进行的。发送方和接收方通过数据帧中的起始位和停止位来确定数据帧的边界,并以相同的通信频率进行数据传输。比起同步通信,在异步通信中,我们没有统一的时钟线,通信双方的设备都使用提前约定好的通信频率进行数据的传输。这样带来的好处就是,能够使用尽量少的引脚就能够完成通信。例如我们的USART通信就是一种典型的串行异步通信,在USART中,我们只需要使用两根线就能完成非常高效的通信,在某些只存在收或者只存在发的场景中,我们甚至只需要一根线就能完成通信。也是一种非常快捷和方便的通信协议了。
五、USART通信的协议电平
因为USART是异步通信协议,所以这种协议电平一般由硬件产生,我们使用软件很难模拟出异步通信的协议电平。所以这里通信电平并不作为我们本次讲解的重点,大家只需要了解通信电平每一个位的含意,并且在实际的调试中,能够灵活配置和运用即可。下面就让我们来看一下USART的通信电平吧!
在USART通信中,我们四种通信位,分别是,起始位,数据位,奇偶检验位,停止位。在实际的通信种,这些位也是按照这样的顺序排列。下面我们来讲解一下这些通信位在整个通信种有什么作用。
起始位:在串口发送数据前,通常会先发送一个起始位,起始位用来表示接下来要开始发送数据了。在TTL电平中,我们通常将数据线电平拉高,起始位时,我们将数据线拉低,表示数据已经起始,接下来的电平就是数据位,奇偶校验位和停止位。
数据位:数据位是我们真实要传输的数据,一般是八位或者九位,数据位是九位的情况就表示我们附带了奇偶校验位。下面我们来看看奇偶校验位。
奇偶校验位:这一位会紧跟数据位之后,我们可以选择奇校验或者是偶校验亦或是无校验。但是当我们将数据位设置为九位以后,我们就必须选择一种校验方式,校验也会增加我们串口发送数据的时间,所以,通常的我们都会选择无校验。如果你为了防止数据丢失,也可以打开校验,但是请详细了解奇偶校验的校验规则。
停止位:停止位会紧跟数据位之后,一般是一位或者1.5位或者两位。在TTL电平种,我们规定在数据位后将通信线电平拉高为停止位。
上面就是我们USART通信数据位的全部了。我们像停止位与数据位这些可变的通信位在通信时,双方需要设置为相同的,这样才能保证我们的USART通信正常进行。在双方都约定的相同的波特率时,通信双方会自动检测通信线上的电平,在起始电平时开始采集,直到采集完整个数据位之后采集停止位,如果一段通信帧中没有停止位,这段数据就会被认为不是USART通信协议传输的数据,则会被丢弃。所以双方设备如果使用USART通信,我们必须要设置相同的波特率,相同的数据位和停止位。
六、STM32 USART通信的相关函数
STM32 USART相关的函数非常多,这里我们只会讲常用的,并且我们结合工程与实战来理解每个函数的意思。在开始之前,我们同样的将我们之前新建的STM32F103C8T6的标准库工程复制一份到我们的代码文件夹中,因为这次使用的是串口,所以,我这次代码文件夹就叫USART:
将初始例程的文件复制过来以后,我们点击“Project.uvprojx”打开我们的工程:
大家可以看到我们工程中的“Library”文件夹,这个文件夹中包含了我们会使用到的STM32大部分外设的库函数,同样的这里面肯定也包含了USART的相关函数:
我们在“Library”文件夹中找到“stm32f10x_usart.h”文件,让我们打开它:
这个文件包含了USART所有相关的函数,如果以后有USART的函数你忘记了,可以来这个文件中寻找。我们将这个文件往下滑就能看到非常多的文件了:
下面我们就要用这些函数来配置我们的USART,让它可以正常工作并且发送和接收数据,下面我们分情况讨论:
1.串口的初始化配置
我们要使用串口,我们首先就要将串口初始化,下面我们来认识一下串口初始化的函数:
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
这就是我们串口的初始化函数了,它首先要求我们传入一个串口的句柄,用于判断应该配置哪一个串口,第二个参数是一个USART_InitTypeDef类型的结构体指针。和GPIO的配置一样我们在配置串口时同样的要用结构体传参。
在初始化串口本身之前,我们还需要先初始化串口对应的引脚,我们这里在不考虑复用的情况下初始化串口一,对应的也就是我们芯片的PA9和PA10引脚,所以,在使用“USART_init()”之前我们还需要先初始化串口相关的引脚。
既然我们这里要使用串口和串口对应的引脚那么我们还需要初始化这些外设的时钟,我们使用下面的命令来打开串口1和对应GPIO的时钟:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
在打开相关的时钟以后,我们就可以开始初始化相关的GPIO口了,我们同样的使用下面的语句来定义GPIO初始化相关的结构体:
GPIO_InitTypeDef GPIO_InitStructTypeDef;
这里我们的GPIOA9要作为USART1的TX也就是发送引脚,所以,我们这里要将GPIOA9配置为输出模式,因为我们这个引脚已经不作为GPIO来使用了,所以这里我们要采用复用输出,我们使用下面的代码:
GPIO_InitStructTypeDef.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructTypeDef.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructTypeDef.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructTypeDef);
这里的配置和上次例程中的GPIO配置非常像,我们都是先改变结构体中的值,再将我们改变后的结构体的地址传入GPIO的初始化函数。
我们配置完GPIOA9以后,我们现在来配置GPIOA10,GPIOA10作为我们USART的接收引脚,这里我们要将其设置为浮空输入模式,配置方式和上面一样,我们使用下面的代码来配置:
GPIO_InitStructTypeDef.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructTypeDef.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructTypeDef.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructTypeDef);
这里结构体传参的这种方式大家需要多熟悉使用,STM32的许多外设初始化配置都会使用结构体传参。
上面我们将USART1对应的GPIO引脚初始化好了,下面我们就要开始初始化串口本身了。我们刚才也观察了串口初始化用的“USART_Init”函数,这个函数要求我们传入一个“USART_InitTypeDef”类型的结构体指针,我们这里和上面GPIO的初始化一样,我们先用下面的命令将这个类型定义出来:
USART_InitTypeDef USART_InitStructTypeDef;
下面,我们往这个结构体中传入参数,这里提示一下,如果你不知道一个外设配置的结构体中有哪些成员,你可以先把结构体写出来,再在后面加一个点,后面就会提示结构体中的成员了:
我们一般习惯将这些结构体成员都写出来再往里面填入参数:
如上图,我们结构体的成员都写出来了。我们现在开始写入参数。如果你不确定这个成员可以传入哪些参数,你可以将成员的名字复制一份再按下“CTRL+ALT+空格”就可以补全这个成员中的参数:
如果你在按下“CTRL+ALT+空格”没有补全,你可能需要修改一下你输入法的快捷键,我们首先右键我们的输入法点击设置:
点击“按键”:
将这里的勾去掉就可以了:
这样,你的成员中的参数就能够补全了,当然,这里的补全机制也要视情况而定,比如你的结构体中有一个“USART_BaudRate”成员,就不能用这种方法补全,因为这个成员表示的是我们USART通信中的波特率,这个参数是我们自己写的官方并没有定义。有了补全以后,一些忘记的函数也可以用这个快捷键补全。
下面就是我们结构体成员的参数:
USART_InitStructTypeDef.USART_BaudRate = 115200;
USART_InitStructTypeDef.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructTypeDef.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
USART_InitStructTypeDef.USART_Parity = USART_Parity_No;
USART_InitStructTypeDef.USART_StopBits = USART_StopBits_1;
USART_InitStructTypeDef.USART_WordLength = USART_WordLength_8b;
下面我们来解释一下这些成员的对应参数的含义,首先就是“USART_BaudRate”表示的是我们USART通信的波特率,这里我们将波特率设置为115200,这也是我们USART通信中非常常见的一种波特率。“USART_HardwareFlowControl”表示要不要使用硬件流控制,我们这里不使用硬件流控制,“USART_Mode”表示我们串口要使用的模式,这里的RX表示接收模式,这里的TX表示发送模式,我们这里用一个或的符号把两个连接在一起,这就表示我们想将我们的USART设置为发送和接收模式。“USART_Parity”表示奇偶校验位,我们这里不使用奇偶校验。“USART_StopBits”表示我们USART通信中停止位的位数,我们这里采用的是常见的一位停止位。“USART_WordLength”表示我们数据位的长度,我们既然已经将奇偶校验设置为了无奇偶校验那么我们这里的数据位我们将其设置为8位。当我们为结构体中写入上面的内容以后,我们就可以使用串口的初始化函数,将这些配置一次性写入,我们使用下面的命令来初始化USART1:
USART_Init(USART1,&USART_InitStructTypeDef);
在串口初始化完以后,我们使用下面的命令使能我们的串口:
USART_Cmd(USART1,ENABLE);
在完成了上面的步骤以后,我们的USART就已经全部初始化完成了,下面是完整的初始化代码:
#include "stm32f10x.h"
GPIO_InitTypeDef GPIO_InitStructTypeDef;
USART_InitTypeDef USART_InitStructTypeDef;
int main(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructTypeDef.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructTypeDef.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructTypeDef.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructTypeDef);
GPIO_InitStructTypeDef.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitStructTypeDef.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructTypeDef.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructTypeDef);
USART_InitStructTypeDef.USART_BaudRate = 115200;
USART_InitStructTypeDef.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructTypeDef.USART_Mode = USART_Mode_Rx|USART_Mode_Tx;
USART_InitStructTypeDef.USART_Parity = USART_Parity_No;
USART_InitStructTypeDef.USART_StopBits = USART_StopBits_1;
USART_InitStructTypeDef.USART_WordLength = USART_WordLength_8b;
USART_Init(USART1,&USART_InitStructTypeDef);
USART_Cmd(USART1,ENABLE);
while(1)
{
}
}
在以上步骤完成以后,我们的串口初始化就完成了。
2.使用串口发送一个字节的数据测试
在完成了串口的初始化以后,我们就可以用一条发送数据的函数来查看我们串口的初始化是否成功。下面是我们串口发送数据的相关函数:
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
我们可以看到函数的第一个参数是我们串口的句柄,为了让我们选择要使用哪个串口发送数据。第二个参数就是我们要发送的数据了,这里要求的数据类型位uint16_t;作为测试,我们可以定义一个uint16_t变量,并且将其发送出去,我们使用下面的代码来定义一个变量并且赋值,再使用USART发送函数将数据发送出去:
uint16_t num = 0x41;
USART_SendData(USART1,num);
将这段代码加在上面的初始化代码下面即可,这里的十六进制的41对应的是“A”的ASCII码:
在完成以上代码以后,我们将代码编译并且下载到我们的STM32中,如果你在这段代码的定义变量处发生了错误,那就把你定义变量的部分放在main函数的前面。如果你还不会下载代码,请回去看下面的STM32 LED的文章中的代码下载部分:
STM32 LED教程:[STM32]从零开始的STM32 LED教程(小白向)-CSDN博客
将代码下载好以后,我们将CH340的TX接到最小开发板的PA10引脚,也就是我们的接收引脚,我们将CH340的RX接到最小开发板的PA9引脚,也就是我们的发送引脚。这样CH340的发送对应了我们开发板的接收,CH340的接收对应了我们开发板的发送,这样大家都能够接收或者发送数据数据。将USART接口像上面描述一样连接好以后,我们打开我给的资料文件夹中的“串口助手”文件夹:
这里我上传了“正点原子”和“江协科技”的串口助手,在内部的操作上可能会有一些区别,但是大致的操作都是一样的,我这里使用“江协科技”的串口助手来给大家演示,如图中所示。如果你是一位新手,不知道怎么配置串口助手,那么我这里建议你和我使用一样的:
我们打开这个串口助手:
我们可以在串口号处,找到我们CH340的串口,如果你这里有很多串口,你不确定哪一个是CH340可以考虑将其拔下再插入,这样就能分辨出哪一个串口是CH340了。如果你的CH340插入以后这里没有任何的串口号,可以检查一下CH340的驱动是否正确安装,如果你还不会安装CH340的驱动,同样可以去看上面提到的那篇帖子,我们选择到CH340对应的串口号:
这里的波特率我们设置为115200,之前我们在STM32的程序中将波特率设置为了115200为了保证能够正常通信,所以我们这里也要设置为115200。这里的数据位也是同样的,设置为8位,停止位一位,无奇偶校验位。
在配置好上面的内容以后,我们点击“打开串口”,如果看到这样,就表示我们的串口打开成功了,如果你被提示串口打开失败等,可以考虑重新插CH340或者更换一个CH340模块:
我们将“接收模式”处改为“文本模式”:
在以上都配置好以后,我们就可以测试我们的串口了,我们这时在保证串口的接线正确,单片机程序已经烧录,串口助手已经配置正确的前提下,我们按下STM32开发板上的复位键:
如上图,我们可以看到,我们这里当我们按下复位,串口助手就收到了一个数据,正是我们的字符“A”。如果你在这一步中收不到数据,请检查接线是否正确,开发板上BOOT0的跳线帽是否跳到低电平。检查完以上以后仍然不能正常收到数据的话,就需要检查程序是否正确,可以直接复制我的程序或者打开我给的资料中的代码例程进行测试。这一步是下面所有步骤的前提,我们必须使用这一步确定我们串口是正确的才能进行下一步。如果你这里能收到字符,但是不是字符“A”,那就可以检查一下串口的波特率和数据位和停止位等是否设置正确。如果确定以上都没有问题就看看代码中被我们定义的变量里面装到到底是不是0x41。在排查完以上收到的数据依然乱码的话,我们可以点击图中所示处:
这里的编码我们选择“GB2312”:
在你修改完以上配置以后,再次发送字符应该就不会有乱码了。当数据发送正常以后,我们就可以往上层封装函数了。我们后面的函数都会基于这个发送一个字节函数。
3.使用串口发送一个字节的函数
我们现在来封装一个函数,用于使用串口发送一个字节,我们可以直接将这个函数的参数作为我们要发送的数据。下面是我们封装好的函数:
void Serial_SendByte(uint8_t Byte)
{
USART_SendData(USART1, Byte);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
我们这里将函数的参数定义为“uint8_t”在进入函数以后,我们使用串口的发送函数,使用USART1将我们传入的参数发送出去。下面的while持续获取串口的相关标志位,用于判断发送是否完成,只有在发送完成以后,才会退出这个while。我们使用这个函数就能直接将要发送的数据作为参数传入即可。函数的效果也和之前的库中的函数效果一样,我们这里就不过多演示了。下面,我们要基于这个函数继续往上封装函数。
4.使用串口发送一个字符串
我们下面对上面的使用串口发送一个字节的函数进行封装,我们封装一个使用串口发送字符串的函数,我们将我们字符串的首地址作为参数传入其中,代码如下:
void Serial_SendString(char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i ++)
{
Serial_SendByte(String[i]);
}
}
这里我们首先是定义了一个变量“i”,并且在for循环开始时我们将i置为0,作为字符串的索引。我们每次循环都索引到字符串中的下一个元素,在索引到这个元素以后,我们使用发送一个字节的函数将这个数据发送出去。如此往复,直到我们索引到字符串中的“\0”以后程序退出for循环,当索引到“\0”以后我们整个字符串都已经被遍历完了。下面我们来测试一下这个函数,这里我们可以直接将数组写进去:
也可以先定义一个字符串变量,再将定义的字符串作为参数传入函数:
下面是我们所有调用函数的截图:
在上面的代码执行以后,串口应该就能打印出一个“hello world”。我们将代码编译下载到STM32中,我们同样打开串口助手,并且配置好相关参数后打开串口,随后按下STM32的复位:
我们可以看到这里的“hello world”已经打印出来了。如果函数封装正确并且一开始的打印一个字节的函数没有问题的话,这里的发送字符串的函数应该也是不会出问题的。
5.printf的重定向
原本的printf是将字符串打印在屏幕上的,我们这里可以将printf重定向到串口,这样以后我们使用printf打印就将我们的字符串输出到了串口上。下面我们就来看一下printf的重定向应该怎么写。下面是一段重定向printf的代码:
int fputc(int ch, FILE *f)
{
Serial_SendByte(ch);
return ch;
}
在这里,我们并没有直接对printf进行修改,因为在printf的底层实现中调用了“fputc”输出函数,我们直接重定向“fputc”即可,这里我们可以看到“fputc”传入的参数有两个,一个是传入要输出的字符,另一个是一个文件指针,表示输出到哪一个文件,在STM32中,我们可以直接忽略第二个参数。我们直接将传入的字符再次作为参数传入我们发送一个字节的函数这样也就实现了发送。重定向的代码我们应该写在主函数外面作为一个函数存在:
在完成了上面的步骤以后,我们printf的重定向就完成了,下面我们来测试一下,我们直接使用printf打印一个字符串:
我们同样将代码编译,下载。我们连接好串口以后打开串口,按下STM32的复位键:
我们可以看到,我们这里的字符串已经被打印出来了。至此,我们的printf就已经重定向成功了。
6.串口中断函数
下面我们来讲解一下串口中断相关的函数,我们使用串口接收数据时,常常都是使用中断的接收方式,因为不知道数据什么时候来,程序不能一直等待数据,所以,我们通常会采用中断的方式。这种方式的优势就在于,不会影响主程序的执行,只要有数据了我的CPU才去处理。下面我们来看看中断函数的编写。我们的中断函数都被定义在了启动文件中,我们可以去查看:
我们直接将这个函数写出来:
中断函数作为一个独立的函数,也要放在主函数外面。
下面我们往中断函数中写入内容,为了保证在串口产生中断以后,代码能够处理,我们在中断函数中加入以下代码:
void USART1_IRQHandler(void)
{
if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
{
uint8_t Serial_RxData = USART_ReceiveData(USART1);
USART_SendData(USART1,Serial_RxData);
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
在这段代码中,我们可以看到,在串口产生中断以后,我们首先使用一个if语句去判断串口一是不是真的产生了中断,如果是的话,就进入if,进入if以后,我们首先把接收到的数据读取出来,随后,我们就使用发送函数将我们收到的数据发送出去,最后,我们清空了串口1的相关标志位等待下一次中断的产生。但是仅仅写好中断函数我们的串口依旧不能产生中断,因为我们还没有将串口的中断打开,我们也没有在NVIC处将串口的中断打开,我们现在先将串口的中断打开,使用下面的代码:
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
下面我们来配置NVIC的工作组,我们使用下面的命令:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
这里我们使能了串口的接收中断。下面我们开始配置NVIC,NVIC的配置和别的初始化也一样,都是通过结构体来配置的。我们首先将相关的结构体定义出来:
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_IRQChannel”表示的是哪一个中断,这里我们选择了串口1中断,然后“NVIC_IRQChannelCmd”用来表示使能还是不使能,我们这里选择的使能。后面的“NVIC_IRQChannelPreemptionPriority”和“NVIC_IRQChannelSubPriority”都是用来表示这个中断的优先级的,我们这里只有一个中断,不存在打断的问题,所以优先级高低都可以。
我们下面将这些代码添加到我们原本的代码中:
我们将代码编译,下载到开发板中,并且打开我们的串口助手:
这里我们点击发送以后,我们的串口也收到的数据,证明我们的回环测试时成功的。
七、结语
学习了上面的知识,相信你对串口已经有了一定的了解。当然,上面的知识是非常浅显的,对于串口,我们仍然需要更深入的学习。作为一种日常使用非常多的调试接口,也希望大家能灵活运用起来!谢谢大家!