[STM32]从零开始的STM32串口使用教程(小白向)

news2024/9/20 0:58:47

一、我们为什么需要串口?

        在嵌入式通信中,我们常常把像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”都是用来表示这个中断的优先级的,我们这里只有一个中断,不存在打断的问题,所以优先级高低都可以。

我们下面将这些代码添加到我们原本的代码中:

我们将代码编译,下载到开发板中,并且打开我们的串口助手:

这里我们点击发送以后,我们的串口也收到的数据,证明我们的回环测试时成功的。

七、结语

        学习了上面的知识,相信你对串口已经有了一定的了解。当然,上面的知识是非常浅显的,对于串口,我们仍然需要更深入的学习。作为一种日常使用非常多的调试接口,也希望大家能灵活运用起来!谢谢大家!

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

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

相关文章

在 RT-Thread 上使用单色屏 UI 库 - U8G2

U8g2 是一个用于嵌入式设备的单色图形库。U8g2支持单色OLED和LCD,并支持如SSD1306等多种类型的OLED驱动。 U8g2的官方开源地址为:https://github.com/olikraus/u8g2 由于官方已经对 RT-Thread 做了一定的适配,并且存在于 RT-Thread 的软件包…

用Python实现时间序列模型实战——Day 15: 时间序列模型的选择与组合

一、学习内容 1. 模型选择的标准与方法(如 AIC、BIC) 在时间序列建模中,模型的选择是非常重要的,常用的模型选择标准包括 AIC (Akaike Information Criterion) 和 BIC (Bayesian Information Criterion)。 AIC (Akaike Informat…

U盘管控软件|禁止拷贝怎么解决?防止U盘拷贝的6款专业软件推荐

某公司员工将U盘插入公司电脑拷贝了重要的研发资料,随后该员工跳槽至竞争对手公司,导致公司商业机密外泄,造成了巨大的经济损失。 随着移动设备的普及,U盘的使用为工作带来了便利,但同时也增加了数据泄密的风险。这一案…

记一次knife4j文档请求异常 SyntaxError: Unexpected token ‘<‘, ... is not valid JSON

knife4j页面报错问题定位 前几天开发新接口,开发完成后想使用knife4j测试一下接口功能,突然发现访问页面报错提示:knife4j文档请求异常,但之前运行还是正常的,想想会不会与升级依赖有关系,启动其他微服务发…

非标金属零件加工的质量与效率是如何体现的?

在现代工业制造领域中,非标金属零件加工以其独特的方式实现了质量与效率的完美融合,成为推动众多行业发展的关键力量。时利和将详细解析非标金属零件加工的质量与效率是如何体现的? 一、先进设备保障高效加工 非标金属零件加工往往依赖于先进的加工设备…

从文字到世界:2024外语阅读大赛报名开启,赛氪网全程护航

中国外文局CATTI项目管理中心与中国外文界联合宣布,将举办2024年外语阅读大赛,旨在激发外语学习兴趣,选拔并培养优秀的语言应用人才,同时向世界展示和传播具有中国特色的优秀文化。此次大赛旨在激发外语学习兴趣,选拔优…

arm64高速缓存基础知识

高速缓存的替换策略 随机法:随机地确定替换的高速缓存行,由一个随机数产生器产生随机数来确认替换行 FIFO法:选择最先调入的高速缓存行进行替换 LRU法:最少使用的行优先替换。 高速缓存的共享属性 内部共享的高速缓存通常指的…

50ETF期权交易对手方是谁?对手方怎么交易?

今天期权懂带你了解50ETF期权交易对手方是谁?对手方怎么交易?上证50ETF期权投资作为目前金融业呼声较高的投资品种,其实与其他类型的金融产品相比,风险是相对较低的了,因为对于投资者而言,亏损只会影响到权…

《mkcert介绍及使用》

🤖 作者简介:水煮白菜王 ,一位资深前端劝退师 👻 👀 文章专栏: 前端专栏 ,记录一下平时在博客写作中,总结出的一些开发技巧✍。 感谢支持💕💕💕 关…

如何在 DigitalOcean Droplet 云服务器上部署 Next.js 应用

Next.js 是一个流行的 React 框架,可轻松构建服务器渲染的 React 应用程序。在本教程中,我们将介绍如何使用 Nginx 作为反向代理,在 DigitalOcean 的 droplet 云主机上部署 Next.js 应用程序。以下是逐步指南,假设你已经准备好部署…

数学建模笔记—— 线性规划

数学建模笔记—— 线性规划 线性规划1. 模型引出1.1 线性规划模型的三要素1.2 线性规划模型建立步骤1.3 线性规划的表现形式1.4 线性规划的模型特点 2.典型例题3. python代码求解3.1 求解KK升级的问题3.2 求解投资收益问题 线性规划 在人们的生产实践中,经常会遇到…

jenkins工具的介绍和gitlab安装

使用方式 替代手动,自动化拉取、集成、构建、测试;是CI/CD持续集成、持续部署主流开发模式中重要工具;必须组件 jenkins-gitlab,代码公共仓库服务器(至少6G内存);jenkins-server,需…

【LoRa】对TX与RX的TOA时间的理解

目录 1 前言2 解释说明3 延申-计算TOA 1 前言 大家可能已经知道在射频传输中,TOA(Time on Air)是指本包传输所需时间。本章主要来讨论如何理解这段时间,并从发送端和接收端一起来理解。本章是基于LoRa所做的测试,但我…

OutBrain原生广告新赛道助力欧美流量变现优势与实战策略

揭秘欧美流量变现新蓝海:OutBrain原生广告的优势与实战策略 在数字化时代,流量即金钱的观念深入人心,尤其是对于欧美市场而言,高效、精准的流量变现策略成为了众多网站主、博主及内容创作者关注的焦点。OutBrain,作为…

凭什么要买净水器?

近年来,购买净水器的人越来越多,净水器似乎成了一件必不可少的家电。 但仍有部分人保持着传统的观念,认为喝自来水烧开的白开水是最健康的,净水器都是商家炒作出来的,不可信。 可事实是这样吗?其实你家水…

多语言ASO – 本地化的10个技巧

ASO优化是一个复杂的领域,即使你只关注讲英语的用户。如果您想面向国际受众并在全球范围内发展您的应用程序业务,您必须在App Store和Google Play Store上本地化应用程序的产品页面。不过,应用程序商店本地化的过程也有很多陷阱。 应用商店本…

U盘数据恢复秘籍:四款软件的亲测推荐!

U盘作为便捷的存储工具被广泛使用,但随之而来的是数据丢失的风险。面对U盘中的宝贵资料不慎丢失,如何高效、安全地恢复成为关键。推荐几款亲测好用的U盘数据恢复软件! 第一款:福昕数据恢复 直达链接:www.pdf365.cn/f…

elementui Cascader 级联选择器的使用总结

实现效果 技术要点总结如下: 1、点击添加自动增加多行,实现自主选择增加多条节点数据 2、节点地址使用的是Cascader 级联选择器,需要动态生成,涉及到一个技术要点是:因v-modal只能获取value不能获取label,故…

CCF编程能力等级认证GESP—C++7级—20240907

CCF编程能力等级认证GESP—C1级真题 单选题(每题 2 分,共 30 分)判断题(每题 2 分,共 20 分)编程题 (每题 25 分,共 50 分)小杨寻宝矩阵移动 单选题(每题 2 分,共 30 分&…

Spring AOP,通知使用,spring事务管理,spring_web搭建

spring AOP AOP概述 AOP面向切面编程是对面向对象编程的延续(AOP (Aspect Orient Programming),直译过来就是 面向切面编程,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。) 面向切面编…