STM32之USART-串口通信(含串口实验详细解析)

2023-05-16

STM32之串口通信 - USART(含串口实验详细解析)

  • 开发环境:Window 10
  • 开发工具:Keil uVision5 MDK
  • 硬件:STM32F103

资料参考:

【正点原子】STM32F103开发板资料(A盘);

  • STM32F1开发指南(精英版)-库函数版本.pdf
  • STM32固件库使用手册的中文翻译版.pdf
  • STM32中文参考手册.pdf

注意:本文主要介绍 USART 异步串行模式下的收发器。

文章目录

  • STM32之串口通信 - USART(含串口实验详细解析)
    • STM32F103ZE 简介
    • USART简介
      • 1. 串口通信
      • 2. 串口通信的分类:
      • 3. 串行通信
      • 4.STM32的串口通信接口
    • USART的使用步骤
      • 1)串口时钟使能 GPIO 时钟使能
      • 2)串口复位
      • 3)GPIO 端口模式设置
      • 4)串口参数初始化
      • 5)开启中断 并且初始化 NVIC
      • 6)使能串口
      • 7)编写中断处理函数


STM32F103ZE 简介

https://www.keil.com/dd2/stmicroelectronics/stm32f103ze/

内核:ARM Cortex-M3,72MHz

内存:64kB RAM,512kB ROM

时钟和电源:2.00V—3.60V,72 MHz

通信 :SPI、I2C、UART、I2S、CAN、USART、USB、Device

定时器/计数器/PWM :8 × 16 位定时器

模拟:2通道 12位DAC,21通道 12位ADC

I/O 和封装: -40℃—85℃,144-QFP,144-BGA


USART简介

1. 串口通信

串口通信是指外设和计算机间,通过数据信号线 、地线、控制线等,按位进行传输数据的一种通讯方式,如SPI通信、USART通信、EEPROM通信等。简单讲,串口通信实现了上位机(PC)与下位机(如STM32)之间的信息交互。

上位机(PC)通过串口调试助手等实现数据的接收和发送;

下位机(STM32)通过printf()、getchar()等函数实现字符或字符串的接收和发送。

2. 串口通信的分类:

处理器与外部设备通信有两种方式:

2.1 串行通信:数据按位顺序依次传输,如8个数据位依次传输,速度慢,但占用引脚资源少

​ 按照数据传送方向,又分为:

  • 单工:数据传输只支持数据在一个方向上传输。(只收不发或者只发不收,模式固定)
  • 单双工:允许数据在两个方向上传输,但是在某一时刻,只允许数据在一个方向上传输。(能发能收,但不能同时进行)
  • 全双工:允许数据同时在两个方向上传输。(能发能收,且能同时进行)

2.2 并行通信:数据各个位同时传输,如8个数据位同时传输,占用引脚资源慢,但速度快。

3. 串行通信

串行通信按通信的方式可分为:

  • 同步通信:带时钟同步信号传输,如SPI、IIC通信等
  • 异步通信:不带时钟同步信号,如UART(通用异步收发器)、单总线等。

在这里插入图片描述

4.STM32的串口通信接口

  • UART:通用异步收发器
  • USART:通用同步/异步收发器(两种模式可切换)

STM32F103系列提供5路串口,包含3个 USART 和2个 UART 。

串口的引脚如下图所示:

在这里插入图片描述

数据传输的格式/通信协议

串行通信一定要有适合的通信协议

通信协议指通信双方之间为完成信息交互所必须遵守的一种规则和约定。比如两个人约定在何时交流、用中文还是英文交流、交流什么内容。

1.起始位

​ 当未有数据发送时,数据线处于逻辑“1”状态;先发出一个逻辑“0”信号,表示开始传输字符。

2.数据位

​ 紧随起始位之后,数据位表示真正要发送或接收的信息,位数一般有8位或9位

3.奇偶校验位

​ 数据位末尾可以选择是否添加奇偶校验位,用于检测数据传输是否正确

4.停止位

​ 代表信息传输结束的标志位,可以是1位,1.5位或2位。停止位的位数越多,数据传输的速率也越慢。

5.波特率设置

波特率表示每秒钟传输码元的个数,是衡量数据传输速率的指标,单位Baud。另外有个名词叫比特率,比特率表示每秒钟传输二进制位bit的个数,单位 bit/s。

​ 比特(bit)就是指一位信息,当用二进制表示数据时,0是一位,1也是一位信息,它是固定不变的,一个比特就代表二进制下的一位。

​ 通常描述码元,我们会说M进制的码元。比如八进制,我们知道八进制包含0~7共八种数据,而计算机是只识别0,1两种的,我们若是想将这八种数据发送给计算机,可以用3个比特为一组的形式来表示,即000,001,…,111共八组,因而一个八进制的码元就表示携带了3个比特,这时的比特率也就是波特率的3倍。那么,一个M进制的码元,就携带log2 M个比特。

如果还不明白可以看一下这篇文章:波特、码元与比特的关系

6.波特率的计算

波特率是由USART_BRR波特率寄存器控制的,TR/Tx的波特率 = 串口时钟 / BRR的值。

一般我们先确定波特率,通过计算得到BRR的值,并将其赋给USART_BRR寄存器。如串口时钟72M,选用波特率115200,BRR的值为72M÷115200=625(16进制0x0271),BRR寄存器的值就为0x0271。

USART的使用步骤

串口设置的一般步骤可以总结为如下几个步骤:

1)串口时钟使能 GPIO 时钟使能

2)串口复位

3)GPIO 端口模式设置

4)串口参数初始化

5)开启中断 并且初始化 NVIC(如果需要开启中断才需要这个步骤)

6)使能串口

7)编写中断处理函数

以正点原子的实例-串口实验对串口时钟的使用步骤进行详细分析。

程序功能是STM32通过 串口1 和上位机(PC)对话,STM32收到上位机发送的数据(字符串)后,将数据原原本本的返回给上位机。采用USART1的全双工异步串行收发模式

在例程的usart.c中,引入了一段使程序支持printf函数的代码,直接复制使用即可。

#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 

}; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
void _sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{      
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    USART1->DR = (u8) ch;      
	return ch;
}
#endif 

1)串口时钟使能 GPIO 时钟使能

GPIO口引脚复用,需要开启复用时钟;使用串口外设也要开启时钟

串口挂载、复用时钟在APB2下面的外设

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);

2)串口复位

库函数中,串口的复位通过USART_DeInit()实现,选择待复位的串口USART1 进行设置即可。

USART_DeInit(USART1); // 复位串口 1

3)GPIO 端口模式设置

PA9是发送引脚,需要发送数字量上表示为0,1的高低电平,若使用开漏输出要建立外部电路,接上拉电阻。故采用推挽输出模式,直接输出0V、3.3V的电压。

PA10是接收引脚,需要接收高低电平,设置上拉会将引脚电平限制在高电平,下拉会限制在低电平,模拟输入则将信号传输到其他外设,只有浮空输入能实现高低电平的接收。

同时,STM32复用功能对端口的配置也有以下三条标准(详见参考手册P107 8.1.4 复用功能(AF)):

  • 对于复用的输入功能,端口必须配置成输入模式(浮空、上拉或下拉)且输入引脚必须由外部驱动;
  • 对于复用输出功能,端口必须配置成复用功能输出模式(推挽或开漏);
  • 对于双向复用功能,端口位必须配置复用功能输出模式(推挽或开漏),这时,输入驱动器被配置成浮空输入模式。
	//USART1_TX   GPIOA.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);//初始化GPIOA.9
   
  //USART1_RX	  GPIOA.10初始化
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10 

4)串口参数初始化

库函数中,使用USART_Init()对串口进行初始化

void USART_Init(USART_TypeDef* USARTx, USART_I nitTypeDef* USART_InitStruct)

第一个参数:待初始化的串口标号

第二个参数:配置串口参数的结构体指针

typedef struct
{
u32 USART_BaudRate;
u16 USART_WordLength;
u16 USART_StopBits;
u16 USART_Parity;
u16 USART_HardwareFlowControl;
u16 USART_Mode;
u16 USART_Clock;
u16 USART_CPOL;
u16 USART_CPHA;
u16 USART_LastBit;
} USART_InitTypeDef;
  1. USART_BaudRate:波特率,常用9600,115200等,不用换算成BRR寄存器的值
  2. USART_WordLength:数据位的长度,取值8位或9位,USART_WordLength_8b,USART_WordLength_9b
  3. USART_StopBits:停止位的位数,取值为0.5,1,1.5,2位,常用1位USART_StopBits_1
  4. USART_Parity:奇偶校验位使能,取值有奇偶失能USART_Parity_No,偶校验USART_Parity_Even,奇校验USART_Parity_Odd
  5. USART_HardwareFlowControl:硬件流控制模式使能,用于处理数据丢失,这里选失能不启用就好
  6. USART_Mode:发送接收模式使能,取值有接收使能USART_Mode_Rx,发送使能USART_Mode_Tx

以上是异步模式的配置参数,同步模式还需要配置7~10。

  1. USART_Clock:时钟使能

  2. USART_CPOL:时钟输出极性

  3. USART_CPHA:时钟输出相位

  4. USART_LastBit:最后一位数据的时钟脉冲输出方式

详见固件库使用手册P346 Table707

在这里插入图片描述

对串口1 初始化范例:

 //USART 初始化设置
	USART_InitStructure.USART_BaudRate = bound;//串口波特率
	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); //初始化串口1

5)开启中断 并且初始化 NVIC

如果要使用中断服务,便要对中断优先级进行配置,如果只有一个中断服务,随意配置顺序即可。

 //Usart1 NVIC 配置
 	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断

6)使能串口

在移植代码或自己编写的过程中,都不能忘记开启串口功能

USART_Cmd(USART1, ENABLE);         //使能串口1 

7)编写中断处理函数

其中,SYSTEM_SUPPORT_OS 的作用是实现STM32和操作系统共用STM32内部SysTick定时器,使二者拥有相同的时钟周期。

中断服务过程:

  • USART_GetITStatus():用于获取接收状态,即是否开启接收

  • USART_ReceiveData():获得当前接收到的数据,数据长度8位

  • USART_RX_STA:接收的状态标志,数据长度16位,第14位、15位对接收结束的判断,第0~13位是接收数据的长度。

  • USART_RX_BUF[]:数据存储栈,将接受到的数据依次存储

当 USART_GetITStatus读取到起始位时触发更新请求,代表数据开始接收。用USART_ReceiveData()读取当前接收的数据,如果不是0x0d,则将数据保存到BUF中,如果连续接收到了0x0d(回车),0x0a(换行)表示数据接收结束,STA的第15位置1,在主程序main()中执行相关操作并等待开启下一次的数据接收(当STA=0时表示数据可以被接收存储),详细流程图如下。

在这里插入图片描述

扩展:回车、换行的区别

回车 \r 本义是光标重新回到本行开头,

换行 \n 本义是光标往下一行(不一定到下一行行首)

void USART1_IRQHandler(void)//串口1中断服务程序
	{
	u8 Res;
#if SYSTEM_SUPPORT_OS 		//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntEnter();	//进入中断服务函数
#endif
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
		{
		Res =USART_ReceiveData(USART1);	//读取接收到的数据
		
		if((USART_RX_STA&0x8000)==0)//接收未完成
			{
			if(USART_RX_STA&0x4000)//接收到了0x0d
				{
				if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
				else USART_RX_STA|=0x8000;	//接收完成了 
				}
			else //还没收到0X0D
				{	
				if(Res==0x0d)USART_RX_STA|=0x4000;
				else
					{
					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
					USART_RX_STA++;
					if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
					}		 
				}
			}   		 
     } 
#if SYSTEM_SUPPORT_OS 	//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntExit();  											 
#endif
} 
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32之USART-串口通信(含串口实验详细解析) 的相关文章

  • STM32串口环形缓冲区实现

    stm32串口环形缓冲区 一 ringbuff h span class token macro property span class token directive hash span span class token directiv
  • C++系列8:常用库

    1 cmath 数学计算 include lt iostream gt include lt cmath gt using namespace std int main 数字定义 short s 61 10 int i 61 1000 lo
  • C语言 字符串函数

    C语言 字符串函数 一 字符串长度函数 strlen二 不受限制的字符串函数2 1 复制字符串函数 strcpy2 2 字符串连接函数 strcat2 3 字符串比较函数strcmp 三 长度受限制的字符串函数3 1 复制字符串函数 str
  • HTTP基本认证(Basic Authentication)的JAVA示例

    大家在登录网站的时候 xff0c 大部分时候是通过一个表单提交登录信息 但是有时候浏览器会弹出一个登录验证的对话框 xff0c 如下图 xff0c 这就是使用HTTP基本认证 下面来看看一看这个认证的工作过程 第一步 客户端发送http r
  • c++各种常用库

    cmath xff1a 开根号 xff1a include lt iostream gt include lt cmath gt using namespace std int main double x 61 8 cout lt lt p
  • 安装cv2遇到的小问题解决

    前面文章中也用到了cv2这个包 安装cv2只需要输入下面指令 xff0c pip install opencv python 我在给我的树莓派安装时 xff0c 后面还遇到了一些问题 先是出现 ImportError libcblas so
  • C语言<string.h>常用函数整理

    整理了string h中常用的几个函数 xff0c 仅供参考 xff01 1 strcpy 2 memcpy 3 strcat 4 strchr 5 strstr 6 strcmp 7 memcmp 8 strset 9 memset 声明
  • POCO C++库学习和分析 -- 日志 (一)

    POCO C 43 43 库学习和分析 日志 一 日志对于程序来说是非常重要的 xff0c 特别是对一些大型程序而言 一旦程序被发布 xff0c 在现场日志几乎是程序员唯一可以获取程序信息的手段 Poco作为一个框架类库 xff0c 提供了
  • 字节存储排序:大端和小端

    当前的存储器 xff0c 多以byte为访问的最小单元 xff0c 当一个逻辑上的地址必须分割为物理上的若干单元时就存在了先放谁后放谁的问题 于是端 endian 的问题应运而生了 对于不同的存储方法 就有大端 big endian 和小端
  • CRC算法原理及C语言实现

    CRC算法原理及C语言实现 摘 要 本文从理论上推导出CRC算法实现原理 xff0c 给出三种分别适应不同计算机或微控制器硬件环境的C语言程序 读者更能根据本算法原理 xff0c 用不同的语言编写出独特风格更加实用的CRC计算程序 关键词
  • 动态分配的内存释放之后指针要置空

    define CRT SECURE NO WARNINGS include lt stdio h gt include lt stdlib h gt void main2 int num scanf 34 d 34 amp num prin
  • yolomark的相关操作

    1 文件组成 主要组成部分如图上 改yolo mark脚本中指令 主要是改左边的图片路径 2 使用步骤 进入cmd 输入yolo mark exe的文件路径 开始标注 image num 是对应图像的数 xff0c object id是对应
  • c++:3.7迭代器(1)

    迭代器 迭代器的基本概念 xff1a 1 迭代器是一种遍历容器元素的数据类型 C 43 43 迭代器Interator就是一个指向某种STL对象的泛型指针 通过该指针可以简单方便地遍历所有元素 2 迭代器 xff08 iterator xf
  • 51单片机定时器的查询和进入中断处理

    51单片机定时器可用两种方式处理 xff1a 查询和进入中断处理 1 查询方式 void Init T0 void 定时器初始化 TMOD 61 0x01 计时器模式 TH0 61 65535 65000 256 初装值为65536 650
  • DIY 无人机

    这是一个为满足个人兴趣的实践性项目 这个项目起源于我买的一架航模级的遥控电动直升机 xff0c 想要飞好它还是相当困难的 xff0c 需要敏捷的反应 轻柔的控制 xff0c 不幸的是我的反射弧很长 xff0c 所以练习起来格外困难 在对我的
  • 自定义的CircleProgressBar,支持自定义宽度,颜色等等。

    基于Anbase框架优化了了一个自定义的CircleProgressBar xff0c onDraw方法如下 64 Override protected void onDraw Canvas canvas super onDraw canv
  • 3D打印技术

    3D打印技术 什么是3D打印技术3D打印机的发展和运用领域3D打印过程 什么是3D打印技术 3D打印 xff08 3D printing xff09 是快速成型的一种技术 xff0c 自1986年美国科学家查克 赫尔开发第一台商业3D印刷机
  • 改变世界的17个方程式

    数学是一种美妙而优雅的东西 xff0c 它隐藏在我们生活的方方面面 xff0c 却又难以察觉 xff0c 而这需要一双慧眼才能看到 2013年 xff0c 科普作家伊恩 斯图尔特 Ian Stewart 就专门出了一本书 xff0c 名叫
  • ROS控制多台机器人实现多机协同

    ROS控制多台机器人的思路与实现 1 实现思路1 1 两台机器人跟随1 2 多台机器人编队 2 TF工具的使用2 1 什么是TF2 2 TF的构成2 3 向TF工具广播发送自己位置2 4 向TF工具收听获取坐标关系 3 通过turtlesi
  • 适合小白入门Arduino UNO的介绍

    编者按 xff1a 本文转载于酷耍平台 xff08 kooshua com xff09 Arduino是什么 xff1f Arduino是一款便捷灵活 方便上手的开源电子原型平台 包含硬件 xff08 各种型号的Arduino板 xff09

随机推荐

  • 记海康摄像头获取保存截图的一个方法(c#)

    记海康摄像头获取保存截图的一个方法 xff08 c xff09 先获取海康摄像头接口信息 引入模块 span class token keyword using span span class token namespace System
  • Linux设备树语法详解

    概念 Linux内核从3 x开始引入设备树的概念 xff0c 用于实现驱动代码与设备信息相分离 在设备树出现以前 xff0c 所有关于设备的具体信息都要写在驱动里 xff0c 一旦外围设备变化 xff0c 驱动代码就要重写 引入了设备树之后
  • Arduino智能越野小车AIR ROVER

    摘自 xff1a https goldelec com product detail 507 精心打造智能小车AIR ROVER xff0c 结实 稳固 大小适中 xff0c 越野爬坡能强 xff0c 采用麦克纳姆轮 xff0c 通过手机A
  • SSR的原理及好处

    什么是SSR SSR是Server Side Render简称 xff0c 叫服务端渲染 在客户端请求服务器的时候 xff0c 服务器到数据库中获取到相关的数据 xff0c 并且在服务器内部将Vue组件渲染成HTML xff0c 并且将数据
  • 区位码、国标码与机内码

    区位码 国标码与机内码 为了适应计算机处理汉字信息的需要 xff0c 在 1980 年 xff0c 我国国家标准总局发布 信息交换用汉字编码字符集 基本集 1981 年 5 月 1 日 开始实施的这套国家标准 这套国家标准的标准号是 GB2
  • 获取NSString子字符串

    NSString类中提供了这样三个方法用于获取子字符串 xff1a substringFromIndex substringWithRange substringToIndex xff1a 它们该怎么使用呢 xff1f 见下面代码即可知道
  • NSData和UIImage之间的转换

    源自 xff1a http stackoverflow com questions 2240765 nsdata to uiimage Try this code This worked for me create path to save
  • 获取UIImage的图像MD5

    问 xff1a I 39 m trying to compare two UIImages from the file system to see if they are the same Obviously I can 39 t use
  • 最全面的shsh备份及恢复教程,已更新Win版小雨伞4.33.00

    转自 xff1a http bbs weiphone com read htm tid 2017752 html 很多小白不知道如何备份shsh xff0c 论坛有很多关于备份shsh的帖子 xff0c 但大多都不全面 xff0c 本贴把s
  • C++查看大端序小端序的一些思考

    首先明确一个概念 xff0c 无论是大段序还是小端序 类型指针指向的地址都是该类型所占内存的低地址 明确了这个 xff0c 就好判断大端序和小端序了 这是大端序小端序的介绍链接 下边的是验证系统是大端序还是小端序的程序 span class
  • 设计模式书籍推荐

    1 名称 xff1a Head First Design Patterns 语言 xff1a java 评论 xff1a Jolt大奖得主 xff0c 介绍了常见的十几种模式 我认为最好的模式入门书籍 xff0c 内容生动 xff0c 风格
  • C#如何调用linux so库

    testlib c中的内容 xff1a include lt stdio h gt int sum int a int b return a 43 b int minus int a int b return a b main cs中的内容
  • 电机KV值

    电机 KV 值 xff1a 电机的转速 xff08 空载 xff09 61 KV 值 X 电压 xff1b 例如 KV1000 的电机在 10V 电压下它的转速 xff08 空载 xff09 就是 10000 转 分钟 电机的 KV 值越高
  • 用DD命令制作硬盘镜像

    用DD命令制作硬盘镜像 本文参考http serverfault com questions 4906 using dd for disk cloning写出 xff0c 转载时请说明出处 以下的说明都是针对备份整个硬盘 xff0c 而不是
  • 什么是PID 算法

    PID是工业控制上的一种控制算法 xff0c 其中P表示比例 xff0c I表示积分 xff0c D表示微分 以温度控制的PID程序为例 xff1a P xff08 比例 xff09 表示在温度设定值上下多少度的范围内做比例动作 xff0c
  • html代码向左居右对齐

    刚才居然忘了 现在写出来 lt div align 61 right gt 居右对齐 lt div gt left左 xff0c center中 xff0c right右
  • 出现java.lang.IllegalArgumentException: No configs match configSpec

    模拟器不支持OpenGL ES 2 0 是因为android模拟器不支持OpenGL ES 2 0 xff0c 解决办法 xff1a 创建模拟器时 xff1a GPU emulation选yes xff0c 需要android 4 0以上的
  • [005] [ARM-Cortex-M3/4] 大小端存储

  • Android使用java调用C的方法

    Android使用java调用C的方法 xff1a 要将C语言写的算法需要集成到安卓端 xff0c 因此学习如何从安卓java调用C JNI 1 JNI 提示 Android NDK Android Developers google cn
  • STM32之USART-串口通信(含串口实验详细解析)

    STM32之串口通信 USART xff08 含串口实验详细解析 xff09 开发环境 xff1a Window 10开发工具 xff1a Keil uVision5 MDK硬件 xff1a STM32F103 资料参考 xff1a 正点原