目录
前言
软件要求:Keil环境,STM32f10x系列芯片包
硬件要求:STM32F10x系列的单片机两块,ST-LINK下载器一个,杜邦线4根
一、设备间的通信
通常情况下设备间的通信分为串行通信和并行通信
什么是并行通信?串行通信又是什么?
请看下图
当设备1向设备2发送1字节数据时,并行通信会同时发送8个数据位,而串行通信会逐个发送数据位。因此并行通信和串行通信的优缺点如下
本文主要分享串行通信的相关知识,如果对并行通信感兴趣的可以自行百科。
二、串行通信的分类
串行通信按通信方式可分为同步通信、异步通信两类,按数据传输方向可分为单工,半双工、全双工这三类。
1.通信方式
通信方式是指通信双方之间的工作方式或信号传输方式
1.1.同步通信(Synchronous Communication)
两个设备在同一个时钟信号的控制下,进行数据的接收和发送,在时钟的上升沿或下降沿进行发送或传输。需要注意的是这两个设备的工作状态是一样的(同步)
同步通信要求对传输数据的每一位都必须在接收、发送两端严格保持同步,即“位同步”。所以接收、发送两端需要同一个时钟源作为时钟信号。
在发送数据时会先发送“同步字符”来表示数据发送的开始,然后有效的数据信息以连续串行的形式发送,每个时钟周期发送一位数据。其传输信息格式如下
因此每次传输的数据块越大,其非有效数据所占比越小,通信效率越高。
1.2.异步通信(Asynchronous Communication)
异步通信又称起止同步通信,异步通信是把一个字符看作一个独立的信息传送单元,字符与字符之间的传输间隔是任意的,而每一个字符中的每个位是以固定的时间传送。
在异步通信中,接收、发送双方取得同步的办法是采用在字符格式中设置起始位和停止位。传输信息格式如下
因为异步通信方式总是在传送每个字符的头部(起始位)进行一次重新定位,所以即使接收、发送双方的时钟频率存在一定偏差,但只要在起始位后的采样出现错位现象,则数据传输仍可正常进行。因此异步通信的双方可以使用自己的本地时钟。
由于异步传输信息格式的原因,导致传输大量数据时,其非有效数据所占比越大,通信效率越低。
起始位:起始位必须是持续一个比特位时间的逻辑”0“电平,标示传送一个字符的开始。
数据位:数据位为5~8位。数据位紧跟在起始位之后,是被传送字符的有效数据位。传送时,先传 低位,后传高位。
奇偶校验位:奇偶校验位只占1位。可以为奇校验或偶校验,也可以不设置校验位。
停止位:停止位为1位、1.5位、或2位。它一定是逻辑”1“电平,标示传送一个字符的结束。
2.数据传输方向
2.1.单工(Simplex)
数据只在一个方向上进行传送。
2.2.半双工(Semi-Simplex)
数据可以在两个方向上传输。在同一时刻,数据只能在一个方向上传输,它实际上是一种切换方向的单工通信。它不需要独立的接收端和发送端,两者可以合并一起使用一个端口。
2.3.全双工(Full-Simplex)
数据可以同时在两个方向上传输。显而易见,全双工通信是两个单工组合而来的,需要独立的接收端和发送端。
三、STM32串口通信
STM32的串口接口有UART(通用异步收发器)、USART(通用同步异步收发器)两种。不同型号的STM32微控制器拥有不同数量的串口。例如STM32F103C8T6有3个串口外设(USART1、USART2、USART3),而STM32F103RCT6有5个串口外设(USART1、USART2、USART3、USART4、USART5)。
固件库里提供的是USART(universal synchronous asynchronous receiver and transmitter通用同步/异步收/发器),但在设计函数时是UART开头,这是告知读者,使用的是异步通信。
1.上位机串口助手调试
我使用的是STM32F103RCT6的USART1(串口1)来进行串口助手调试,
首先搭建工程,可以参考下面的链接
工程成功搭建后
先在工程的SYSTEM文件下新建两个文件,分别是usart、delay。
然后在usart文件中创建usart.c文件和usart.h文件
随后在delay文件中创建delay.c文件和delay.h文件
最后创建SYSTEM分组并将工程SYSTEM中对应的.c文件添加到SYSTEM分组中,完成后设置好对应的头文件路径
SYSTEM分组添加.c文件
头文件路径设置
然后在usart.c文件写#include ”usart.h“,这时usart.h文件是空的,所以选中usart.h并右击,再Open document ”usart.h“就进入到usart.h文件
usart.h代码如下
#ifndef _USART_H
#define _USART_H
#include "stm32f10x.h"
#include "stdint.h"
#include "stm32f10x_gpio.h"
#include "string.h"
void UART1_Init(u32 bound);
void UART_SendString(USART_TypeDef* USARTx,uint8_t* str);
void UART_Rx_BuffGetData(uint8_t data,uint8_t * buff);
void Clear_RxBuff(uint8_t * rxBuff,uint16_t buffLen);
#endif
根据原理图
usart.c代码如下
#include "usart.h"
//数据包长度
#define DATA_PACKAGE_LEN 100
//串口接收缓存数组
uint8_t USART1_REC_BUFF[DATA_PACKAGE_LEN];//串口1
//接收完成标志
uint8_t Finish=0;
//接收单个数据
uint8_t Rec;
/**
*函 数:串口发送函数
*参 数:USARTx哪个串口进行发送
*参 数:str需要发送的数据
*返回值:无
*/
void UART_SendString(USART_TypeDef* USARTx,uint8_t* str)
{
uint8_t index;
unsigned char len=strlen((const char*)str);
for(index=0;index<len;index++)
{
while(USART_GetFlagStatus(USARTx,USART_FLAG_TC)!=SET);//串口发送完为SET 未发送完为RESET
USART_SendData(USARTx,str[index]);
}
}
/**
*函 数:串口1初始化函数
*参 数:bound串口波特率
*返回值:无
*/
void UART1_Init(uint32_t bound)
{
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能 USART1,GPIOA时钟以及复用功能时钟
//USART1_TX PA.9
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
//USART1_RX PA.10
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;//抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
//USART 初始化设置
USART_InitStructure.USART_BaudRate = bound;//一般设置为9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
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_Rx | USART_Mode_Tx; //收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断
USART_Cmd(USART1, ENABLE); //使能串口
}
/**
*函 数:指定缓存数组获取数据
*参 数:data串口获取到的单个数据
*参 数:buff串口缓存数组
*返回值:无
*/
void UART_Rx_BuffGetData(uint8_t data,uint8_t * buff)
{
static uint8_t Rec_index = 0;//数据包下标
if(';' == data)//当接收到 ';' 时结束
{
buff[Rec_index] = data;
Finish = 1; //正确接收结束,Finish置为1
Rec_index = 0;//数据包下标重置
}else{
if(Rec_index<DATA_PACKAGE_LEN)//数据是否超出数据包大小
{
buff[Rec_index++] = data;//将接收到的单个数据放入buff中
}
else{
Clear_RxBuff(buff,DATA_PACKAGE_LEN);//清空缓存
Rec_index = 0;
}
}
}
/**
*函 数:清空串口接收缓存
*参 数:rxBuff串口的缓存数组
*参 数:buffLen串口的缓存数组大小
*返回值:无
*/
void Clear_RxBuff(uint8_t * rxBuff,uint16_t buffLen)
{
int index;
for(index=0;index<buffLen;index++)
{
rxBuff[index]=NULL;//清空
}
}
/**
*函 数:串口1中断服务例程
*参 数:无
*返回值:无
*说 明:当串口1接收到数据时,触发中断,会执行该函数
*/
void USART1_IRQHandler(void)//串口1中断服务列程序
{
if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)//串口2触发接收中断
{
Rec = USART_ReceiveData(USART1); //接收一个数据
UART_Rx_BuffGetData(Rec,USART1_REC_BUFF);
if(Finish==1)
{
UART_SendString(USART1,USART1_REC_BUFF);//发送接收缓存数组的数据
Clear_RxBuff(USART1_REC_BUFF,100);
Finish=0;
}
USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清中断
}
}
main.c代码如下
#include "stm32f10x.h"
#include "usart.h"
int main(void)
{
UART1_Init(115200);//初始化串口1设置波特率为115200
UART_SendString(USART1,(unsigned char*)"我是RCT6");
while (1)
{
}
}
下面进行串口调试,你需要有一个串口调试助手,如果没有可以从下面的链接中获取
链接:https://pan.baidu.com/s/1hM6SVmsI2_5zHkti3jGcaw?pwd=meg3
提取码:meg3
选择端口(我的端口是COM4)
设置波特率为115200
打开串口
按下3次复位键后,在数据发送区输入"set led ON;"并且发送数据
其结果如下图所示
至此串口调试结束,可以收发数据。
2.两块单片机通过串口通信
如何实现两块单片机之间的通信?
首先要实现两块单片机的通信就需要有两块单片机,我使用的是STM32F103C8T6和STM32F103RCT6。
然后选择串口,由于串口1用来实现串口调试,因此选择串口2来实现单片机之间的通信。
于是需要初始化串口2和编写串口2中断服务例程。
于是上面的usart.h代码变成了下面这样
usart.h
#ifndef _USART_H
#define _USART_H
#include "stm32f10x.h"
#include "stdint.h"
#include "string.h"
void UART1_Init(u32 bound);
void UART2_Init(u32 bound);//*
void UART_SendString(USART_TypeDef* USARTx,uint8_t* str);
void UART_Rx_BuffGetData(uint8_t data,uint8_t * buff);
void Clear_RxBuff(uint8_t * rxBuff,uint16_t buffLen);
#endif
注释后面带 ”*“的表示是新加的代码。我们希望用户在串口助手发送命令,通过RCT6的串口2将用户命令发送给C8T6。因此需要在串口1的中断服务例程中添加通过串口2发送命令的代码,为什么这么做?这是因为当用户通过串口助手发送数据时,被发送的数据会在串口1的中断服务例程中得到处理,这时只需要在中断服务例程处理完数据后,退出中断服务例程之前,将处理好的数据(用户发送的数据)通过串口2发送给C8T6即可。
因此在RCT6的串口1中断服务例程中添加如下语句
/**
*函 数:串口1中断服务例程
*参 数:无
*返回值:无
*说 明:当串口1接收到数据时,触发中断,会执行该函数
*/
void USART1_IRQHandler(void)//串口1中断服务列程序
{
if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)//串口2触发接收中断
{
Rec = USART_ReceiveData(USART1); //接收一个数据
UART_Rx_BuffGetData(Rec,USART1_REC_BUFF);
if(Finish==1)
{
UART_SendString(USART1,USART1_REC_BUFF);//回显接收缓存数组的数据
//--------------------------------------添加语句--------------------------------------------
UART_SendString(USART2,USART1_REC_BUFF);//串口2发送命令
//--------------------------------------添加语句--------------------------------------------
Clear_RxBuff(USART1_REC_BUFF,100);
Finish=0;
}
USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清中断
}
}
RCT6完整的usart.c
usart.c
#include "usart.h" //数据包长度 #define DATA_PACKAGE_LEN 100 //串口接收缓存数组 uint8_t USART1_REC_BUFF[DATA_PACKAGE_LEN];//串口1 uint8_t USART2_REC_BUFF[DATA_PACKAGE_LEN];//*串口2 //接收完成标志 uint8_t Finish=0; //接收单个数据 uint8_t Rec; /** *函 数:串口发送函数 *参 数:USARTx哪个串口进行发送 *参 数:str需要发送的数据 *返回值:无 */ void UART_SendString(USART_TypeDef* USARTx,uint8_t* str) { uint8_t index; unsigned char len=strlen((const char*)str); for(index=0;index<len;index++) { while(USART_GetFlagStatus(USARTx,USART_FLAG_TC)!=SET);//串口发送完为SET 未发送完为RESET USART_SendData(USARTx,str[index]); } } /** *函 数:串口1初始化函数 *参 数:bound串口波特率 *返回值:无 */ void UART1_Init(uint32_t bound) { //GPIO端口设置 GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟以及复用功能时钟 //USART1_TX PA.9 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_Init(GPIOA, &GPIO_InitStructure); //USART1_RX PA.10 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入 GPIO_Init(GPIOA, &GPIO_InitStructure); //Usart1 NVIC 配置 USART1_IRQn NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;//抢占优先级2 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级2 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器 //USART 初始化设置 USART_InitStructure.USART_BaudRate = bound;//一般设置为9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式 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_Rx | USART_Mode_Tx; //收发模式 USART_Init(USART1, &USART_InitStructure); //初始化串口 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断 USART_Cmd(USART1, ENABLE); //使能串口 } /** *函 数:串口2初始化函数 *参 数:bound串口波特率 *返回值:无 */ //* void UART2_Init(uint32_t bound) { //GPIO端口设置 GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能GPIOA时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//使能USART2时钟 //USART1_TX PA.2 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA.2 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_Init(GPIOA, &GPIO_InitStructure); //USART1_RX PA.3 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //PA.3 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入 GPIO_Init(GPIOA, &GPIO_InitStructure); //Usart1 NVIC 配置 NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2 ;//抢占优先级2 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级2 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能 NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器 //USART 初始化设置 USART_InitStructure.USART_BaudRate = bound;//一般设置为9600; USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式 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_Rx | USART_Mode_Tx; //收发模式 USART_Init(USART2, &USART_InitStructure); //初始化串口 USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);//开启中断 USART_Cmd(USART2, ENABLE); //使能串口 } /** *函 数:指定缓存数组获取数据 *参 数:data串口获取到的单个数据 *参 数:buff串口缓存数组 *返回值:无 */ void UART_Rx_BuffGetData(uint8_t data,uint8_t * buff) { static uint8_t Rec_index = 0;//数据包下标 if(';' == data)//当接收到 ';' 时结束 { buff[Rec_index] = data; Finish = 1; //正确接收结束,Finish置为1 Rec_index = 0;//数据包下标重置 }else{ if(Rec_index<DATA_PACKAGE_LEN)//数据是否超出数据包大小 { buff[Rec_index++] = data;//将接收到的单个数据放入buff中 } else{ Clear_RxBuff(buff,DATA_PACKAGE_LEN);//清空缓存 Rec_index = 0; } } } /** *函 数:清空串口接收缓存 *参 数:rxBuff串口的缓存数组 *参 数:buffLen串口的缓存数组大小 *返回值:无 */ void Clear_RxBuff(uint8_t * rxBuff,uint16_t buffLen) { int index; for(index=0;index<buffLen;index++) { rxBuff[index]=NULL;//清空 } } /** *函 数:串口1中断服务例程 *参 数:无 *返回值:无 *说 明:当串口1接收到数据时,触发中断,会执行该函数 */ void USART1_IRQHandler(void)//串口1中断服务列程序 { if(USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET)//串口1触发接收中断 { Rec = USART_ReceiveData(USART1); //接收一个数据 UART_Rx_BuffGetData(Rec,USART1_REC_BUFF);//将接收到的数据放入到指定的缓存数组中 if(Finish==1) { UART_SendString(USART1,USART1_REC_BUFF);//回显示接收缓存数组的数据 //通过串口2将用户输入的命令发送出去 UART_SendString(USART2,USART1_REC_BUFF);//* Clear_RxBuff(USART1_REC_BUFF,100); Finish=0; } USART_ClearITPendingBit(USART1,USART_IT_RXNE); //清中断 } } /** *函 数:串口2中断服务例程 *参 数:无 *返回值:无 *说 明:当串口2接收到数据时,触发中断,会执行该函数 */ //* void USART2_IRQHandler(void)//串口2中断服务列程序 { if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET)//串口2触发接收中断 { Rec = USART_ReceiveData(USART2); //接收一个数据 UART_Rx_BuffGetData(Rec,USART2_REC_BUFF); //将接收到的数据放入到指定的缓存数组中 if(Finish==1) { //将串口2接收到的数据通过串口1回显到串口助手上 UART_SendString(USART1,USART2_REC_BUFF); Clear_RxBuff(USART2_REC_BUFF,100); Finish=0; } USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清中断 } }
RCT6和C8T6使用的usart.h和usart.c文件基本是一样的,这是因为两个单片机通过串口通信时,不仅要规定它们的波特率一样,还要规定接收数据的格式一样,比如上面规定当串口遇到";"时表示接收数据结束。
下面来看RCT6的main.c
main.c
#include "stm32f10x.h" #include "usart.h" int main(void) { UART1_Init(115200); UART2_Init(115200); UART_SendString(USART1,(unsigned char*)"我是RCT6"); while (1) { } }
接着是C8T6的main.c
main.c
#include "stm32f10x.h" #include "usart.h" int main(void) { UART2_Init(115200); UART_SendString(USART2,(unsigned char*)"我是C8T6;"); while (1) { } }
需要注意的是在C8T6的串口2中断服务例程中需要注释掉语句
UART_SendString(USART1,USART2_REC_BUFF);
并添加语句
if(0==strcmp(USART2_REC_BUFF,"C8T6;"))
UART_SendString(USART2,"C8T6已收到;");
添加语句的意思是当C8T6的串口2接收到“C8T6;”时会向RCT6发送一个“C8T6已收到;”
C8T6的串口2中断服务例程
/** *函 数:串口2中断服务例程 *参 数:无 *返回值:无 *说 明:当串口2接收到数据时,触发中断,会执行该函数 */ //* void USART2_IRQHandler(void)//串口2中断服务列程序 { if(USART_GetITStatus(USART2,USART_IT_RXNE)!=RESET)//串口2触发接收中断 { Rec = USART_ReceiveData(USART2); //接收一个数据 UART_Rx_BuffGetData(Rec,USART2_REC_BUFF); //将接收到的数据放入到指定的缓存数组中 if(Finish==1) { //将串口2接收到的数据通过串口1回显到串口助手上 //UART_SendString(USART1,USART2_REC_BUFF); if(0==strcmp(USART2_REC_BUFF,"C8T6;")) UART_SendString(USART2,"C8T6已收到;"); Clear_RxBuff(USART2_REC_BUFF,100); Finish=0; } USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清中断 } }
将上面程序分别烧录到RCT6和C8T6中,然后像下图一样将对应引脚用杜邦线链接起来
以上便完成了两块单片机通过串口通信。
当按下RCT6的复位键时,RCT6会通过串口1发送 “我是RCT6“ 到串口助手上。随后按下C8T6的复位键,C8T6会通过串口2发送 “我是C8T6;”,这时RCT6的串口2接收到来自C8T6串口2发送的数据,触发中断,经过处理检测到了“;”,于是又通过RCT6的串口1将“我是C8T6;”发送到串口助手。
串口助手中,用户输入数据,并以“;”结束,都会被RCT6的串口1正确接收。用户在串口助手上发送”C8T6;“ ,当RCT6的串口1接收到”C8T6;“时,会将数据回显,并且通过自己的串口2将用户输入的命令发送给C8T6,这时C8T6的串口2触发接收中断,如果正确接收“C8T6;”,C8T6的串口2会发送“C8T6已收到;”,随后RCT6的串口2接收并通过串口1将“C8T6已收到;”发送到串口助手上。演示视屏链接放下面
演示截图
总结
1.串口通信按通信方式可分为同步和异步,按数据传输方向可分为单工、半双工、全双工
2.在实现两个设备之间的串口通信时,我们一般不用调试串口(串口1),这会导致一些问题,可以自行尝试。还需要注意的是要约定好双方传输数据的格式,这样才能让设备之间更好的交互。
3.当使能了某个串口的中断后,如果这个串口的RX引脚接收到数据就会触发该串口中断服务例程,执行相应的操作