STM32学习笔记(四)——STM32的USART和串口通信

STM32学习笔记(四)——STM32的USART和串口通信

STM32学习笔记(四)——STM32的USART和串口通信

一、串口通信

1.1 串口简介

Tx:Transmit-发送
Rx:Receive-接收
在这里插入图片描述

1.2 串口接线

在这里插入图片描述

1.3 串口的数据帧格式

1.3.1 数据帧的概念

通过01传输数据,为了防止一次性传很多数据时混乱,将数据分组传输,一组叫一个数据帧
一个数据帧率的构成:8个bit加上帧头和帧尾

在这里插入图片描述

1.3.2 数据帧的具体格式

起始位-帧头
数据位-传输的数据
停止位-帧尾
在这里插入图片描述

1.3.2.1 空闲

串口中数据传输最基本的单元为数据帧
数据帧1,停顿,数据帧2;3;4,停顿…
这些停顿就是空闲,用高电平表示
在这里插入图片描述

1.3.2.2 起始位

起始位:表示数据帧的开始,实际中用1位低电平表示起始位
在这里插入图片描述

1.3.2.3 数据位

数据位:用于数据传输
总是先传低位
可选用传8bit数据/9bit数据

在这里插入图片描述

1.3.2.4 校验位

校验位:为了检验数据传输是否出错而设立的比特位
校验位在数据位的内部

在这里插入图片描述

校验原理----奇偶校验
奇校验:数据位+校验位 有奇数个1

在这里插入图片描述

1.3.2.5 停止位

停止位:表示数据帧的结束,高电平,长度可以位0.5/1/1.5/2

在这里插入图片描述

1.3.3 单个数据发送

eg:发送0x5b 9位数据位,偶校验位,1位停止位
在这里插入图片描述

1.3.3 连续发送

前一个数据的停止位结束后紧接着下一个数据的起始位

1.4 异步通信和波特率

1.4.1 同步通信

同步通信:收发双方共享时钟
eg:IIC

在这里插入图片描述

1.4.2 异步通信

异步通信:收发双方不共享时钟
eg:串口通信

在这里插入图片描述

1.4.3 波特率
  • 异步通信才会考虑波特率
  • 波特率:每秒传输码元的个数
  • 每一个高低电平 称做一个码元

在这里插入图片描述

1.5 硬件流控

概念:

  • 若A一直发,不管B有没有收到,会发送数据错乱
  • 故A发送一句,B在接收完以后,回复一句收到,A再发下一句,这样有条不紊,不会出错

在这里插入图片描述

在这里插入图片描述

二、STM32的USART

2.1 USART简介

2.1.1 简介

USART这个片上外设 就是stm32内部的串口
在这里插入图片描述

2.1.2 USART和UART的区别

USART:有同步和异步功能
UART:仅有异步功能
在这里插入图片描述

2.1.3 USART的使用

通过CPU,将要发送数据的字节,写入发送数据寄存器(TDR),串口收到数据后,会将数据通过TX引脚发送出去

在这里插入图片描述

2.2 USART的工作原理

2.2.1 串并转换电路

串行通信:使用一根数据线,逐个发送
并行通信:多根数据线,一次发送多个bit位

在这里插入图片描述

串行转并行电路:输入是串行,输出是并行

在这里插入图片描述

为什么要进行串并转换?
CPU对数据是并行写入和读取,串口发送接收则是串行的

在这里插入图片描述
在这里插入图片描述

2.2.2 USART的完整框图

TDR:发送数据寄存器:
RDR:接收数据寄存器
CR:配置寄存器,配置传输数据的参数,如数据位采用9个bit还是8个bit,是否使用校验位,停止位长度等
SR:状态寄存器:读取USART的运行状态,如如何知道,写入的数据被发送完成了?查询SR寄存器中某位的值。
BRR:波特率寄存器
IER:中断使能寄存器

在这里插入图片描述

2.2.3 USART参数配置
2.2.3.1 数据帧格式设置

配置寄存器(CR):
在这里插入图片描述

  • M:数据位长度:M=0 8位;M=1 9位
  • PCE:奇偶校验使能,数据位的最后一位被使能位校验位 PCE=0 禁用;PCE=1 使能
  • PS:奇偶选择 PS=0 偶校验;PS=1 奇校验
  • STOP:停止位长度:
    • STOP=00 1位
    • STOP=01 0.5位
    • STOP=10 2位
    • STOP=11 1.5位
2.2.3.2 数据传输方向的选择

通过配置寄存器,再软件层面决定数据传输的方向,是A传到B、B传到A、还是A和B互相都传输
TXE:0断开;1闭合
RXE:0断开;1闭合

在这里插入图片描述

2.2.3.3 波特率设置

在这里插入图片描述

USART是一个片上外设,需要使能时钟,以片上外设的时钟为基础,通过不同的分配,产生不同的时钟频率,从而得到不同的波特率

在这里插入图片描述

假设片上时钟为72000000,我们想得到9600,只需要让灰色的部分(分频系数)为7500即可,所以分频器上的值为468.75,这样72000000➗468.75➗16=9600
怎么让分频器上值为468.75呢?

  • 用到波特率寄存器BRR:一共16位 (上图),再对应位置写1即可
2.2.3.4 USART的总开关

利用UE bit位
在这里插入图片描述

2.2.4 数据的发送过程
2.2.4.1 双缓冲与连续发送

拥有两级缓冲,解决了连续发送的问题, 但是会有两个问题

  • 数据发送过快,导致数据覆盖问题?
  • 数据发送什么完成的问题?
  • 通过读取SR状态寄存器的值来解决,查询当前数据是否发送完成等状态

在这里插入图片描述

2.2.4.2 TXE标志位

为了避免数据覆盖,每次发送数据前,都查询TXE的寄存器是否为0
TXE(Transmit Data Register Empty)发送数据寄存器空 空为1;非空为0

在这里插入图片描述

2.2.4.3 TC标志位

作用同上
红色部分为空 TC=1;红色为非空 TC=0

在这里插入图片描述

2.2.4.4 单个数据的发送
  • 等待TDR的清空
  • 向TDR写入数据
  • 等待数据发送完成

在这里插入图片描述

2.2.4.5 数据的连续发送

注意!右侧的代码才正确

在这里插入图片描述

2.2.5 数据的接收过程

读数据不能太早也不能太晚,我们应该选择恰当的实际读取数据

2.2.5.1 RXNE标志位

状态寄存器中的RXEN标志位
RXEN=0 RDR寄存器中无数据
RXEN=1 RDR寄存器中有数据
有数据时应该立马将RDR中数据读出来

在这里插入图片描述

2.2.5.2 接收单个数据
  • 等待RDR中有数据
  • 将数据提取出来

在这里插入图片描述

2.2.5.3 接收多个数据

固定的代码 记住!

在这里插入图片描述

2.3 错误标志位

如何判断数据的传输是否有错误呢

在这里插入图片描述

检验错误的程序,只会发生再接收程序里,故如何使用呢?

在这里插入图片描述

三、USART的标准库编程

3.1 实验简介

使用STM32的USART外设,与电脑进行通信
在这里插入图片描述

  • 1.定位使用STM32上的哪个引脚
    • 法1:查看最小系统板引脚分布图,找复用功能中的USART
    • 法2: 查找复用功能重映射表

在这里插入图片描述
在这里插入图片描述

  • 2.完整的接线图
    在这里插入图片描述

3.2 标准库编程接口

介绍五大常用编程接口:
在这里插入图片描述

3.2.1 USART_Init()

USART_Init():设置配置寄存器,和波特率寄存器

  • 参数1:串口几
  • 参数2:初始化调查问卷:
    • USART_WordLength:数据位个数(8位/9位)
    • USART_Parity:奇偶校验选择(odd奇、Even偶、No不校验)
    • USART_StopBits:停止位长度(0.5/1/1.5/2)
    • USART_Mode:通信方向(收/发/双向通信)
    • USART_BaudRate:波特率(9600/115200…)
    • USART_HardwareFlowControl:硬件流控

在这里插入图片描述

在这里插入图片描述

3.2.2 USART_Cmd()

USART_Init():设置总开关

  • 参数1:串口几
  • 参数2:开/关

在这里插入图片描述

3.2.3 USART_SendData()

USART_SendData():设置总开关

  • 参数1:串口几
  • 参数2:发送的数据(8个bit)

在这里插入图片描述

3.2.4 USART_ReceiveData()

USART_ReceiveData():设置总开关

  • 参数1:串口几

在这里插入图片描述

3.2.5 USART_GetFlagStatus()

USART_GetFlagStatus():设置总开关

  • 参数1:串口几
  • 参数2:要读取的标志位名称(如USART_FLAG_TXE…)

在这里插入图片描述

3.3 USART的初始化

  1. 初始化IO引脚:用到了TX和RX引脚,来和外部设备的TX和RX引脚交叉连接,或其它用到的引脚,如时钟,硬件流控引脚…
  2. 使能USART时钟:USART是STM32的一个片上外设,为了节能,大部分片上外设默认都是关闭的,所以要先使能时钟
  3. 配置USART参数:配置CR寄存器(选择数据帧格式)、配置BRR寄存器(波特率)
  4. 闭合总开关

在这里插入图片描述

3.3.1 IO引脚初始化

1.猜IO引脚参数

  • TX:USART间接控制的引脚,选复用;且可输出高、低,选复用推挽;输出速度根据手册10MHZ
  • RX:选上拉模式,因为串口数据帧格式的空闲状态默认为高电平

在这里插入图片描述

2.查看权威手册,验证猜想
查看中文参考手册的----8.1.11 外设的GPIO配置

表格解析:

  • 双工:可发送数据也可接收数据
    • 全双工:数据的接收和发送可同时进行
    • 半双工:接收数据时,不能发送数据 (很少用到,如上图)

在这里插入图片描述

  1. 完整参数表格

在这里插入图片描述

  1. 复用功能的重映射
    重映射用到了AFIO外设
  • 开启AFIO时钟
  • 重映射代码

在这里插入图片描述

#include "stm32f10x.h"
#include "stm32f10x_pal.h"

int main(void)
{
    
    
	PAL_Init();	
	//1.初始化TX PB6 AF__PP 10MHZ
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
	GPIO_InitTypeDef GPIOInitStruct;
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
	GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//1.初始化RX PB7 输入IPU
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//1.重映射USART的TX和RX引脚
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
	
	while(1){
    
    }
}

3.3.2 使能USART的时钟

在这里插入图片描述

#include "stm32f10x.h"
#include "stm32f10x_pal.h"

int main(void)
{
    
    
	PAL_Init();
	
	//1.初始化TX PB6 AF__PP 10MHZ
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
	GPIO_InitTypeDef GPIOInitStruct;
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
	GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//1.初始化RX PB7 输入IPU
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//1.重映射USART的TX和RX引脚
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
	
	//2.使能USART时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);

	while(1){
    
    }
}

3.3.3 配置USART参数

在这里插入图片描述

#include "stm32f10x.h"
#include "stm32f10x_pal.h"

int main(void)
{
    
    
	PAL_Init();	
	//1.初始化TX PB6 AF__PP 10MHZ
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
	GPIO_InitTypeDef GPIOInitStruct;
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
	GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//1.初始化RX PB7 输入IPU
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//1.重映射USART的TX和RX引脚
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
	
	//2.使能USART1时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	//3.设置UASRT1参数
	USART_InitTypeDef USARTInitStruct;
	USARTInitStruct.USART_BaudRate = 9600;
	USARTInitStruct.USART_WordLength = USART_WordLength_8b;
	USARTInitStruct.USART_StopBits = USART_StopBits_1;
	USARTInitStruct.USART_Parity = USART_Parity_No;
	USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_Init(USART1,&USARTInitStruct);
	
	while(1){
    
    }
}
3.3.4 闭合总开关

在这里插入图片描述

#include "stm32f10x.h"
#include "stm32f10x_pal.h"

int main(void)
{
    
    
	PAL_Init();
	
	//1.初始化TX PB6 AF__PP 10MHZ
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
	GPIO_InitTypeDef GPIOInitStruct;
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
	GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//1.初始化RX PB7 输入IPU
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//1.重映射USART的TX和RX引脚
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
	
	//2.使能USART1时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	//3.设置UASRT1参数
	USART_InitTypeDef USARTInitStruct;
	USARTInitStruct.USART_BaudRate = 9600;
	USARTInitStruct.USART_WordLength = USART_WordLength_8b;
	USARTInitStruct.USART_StopBits = USART_StopBits_1;
	USARTInitStruct.USART_Parity = USART_Parity_No;
	USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_Init(USART1,&USARTInitStruct);
	
	//4.闭合UASRT1总开关
	USART_Cmd(USART1,ENABLE);
	
	while(1){
    
    }
}

3.4 发送数据

配置串口调试助手和单片机参数保持一致
在这里插入图片描述
HEX 十六进制显示模式
ASCII 字符显示模式
如:发送65
HEX模式:0x41
ASCII模式:A

3.4.1 发送单个字节
  1. 等待TDR数据清空(查询SR寄存器中TXE标准位 1为空)
  2. 将要发送的数据写入TDR寄存器
  3. 等待数据发送完成(查询SR寄存器中TC标准位)

在这里插入图片描述

#include "stm32f10x.h"
#include "stm32f10x_pal.h"

int main(void)
{
    
    
	PAL_Init();
	
	//初始化TX PB6 AF__PP 10MHZ
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
	GPIO_InitTypeDef GPIOInitStruct;
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
	GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//初始化RX PB7 输入IPU
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//重映射USART的TX和RX引脚
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
	
	//使能USART1时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	//设置UASRT1参数
	USART_InitTypeDef USARTInitStruct;
	USARTInitStruct.USART_BaudRate = 9600;
	USARTInitStruct.USART_WordLength = USART_WordLength_8b;
	USARTInitStruct.USART_StopBits = USART_StopBits_1;
	USARTInitStruct.USART_Parity = USART_Parity_No;
	USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_Init(USART1,&USARTInitStruct);
	
	//闭合UASRT1总开关
	USART_Cmd(USART1,ENABLE);
	
	//发送单个字节
	//1.等待TDR寄存器清空
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET){
    
    }//等于0就一直循环,不等于0执行下一部操作
	//2.将要发送的数据写入TDR寄存器
	USART_SendData(USART1,0x5a);
	//3.等待数据发送完成
	while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET){
    
    }
	
	while(1){
    
    }
}
3.4.2 发送字节数组
  1. 声明要发送的数组变量

在这里插入图片描述

#include "stm32f10x.h"
#include "stm32f10x_pal.h"

int main(void)
{
    
    
	PAL_Init();
	
	//初始化TX PB6 AF__PP 10MHZ
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
	GPIO_InitTypeDef GPIOInitStruct;
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
	GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//初始化RX PB7 输入IPU
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//重映射USART的TX和RX引脚
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
	
	//使能USART1时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	//设置UASRT1参数
	USART_InitTypeDef USARTInitStruct;
	USARTInitStruct.USART_BaudRate = 9600;
	USARTInitStruct.USART_WordLength = USART_WordLength_8b;
	USARTInitStruct.USART_StopBits = USART_StopBits_1;
	USARTInitStruct.USART_Parity = USART_Parity_No;
	USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_Init(USART1,&USARTInitStruct);
	
	//闭合UASRT1总开关
	USART_Cmd(USART1,ENABLE);
	
	//发送字节数组
	//1.声明发送的数组变量
	uint8_t a[] = {
    
    0,1,2,3,4,5};
	uint32_t i;
	for(i=0;i< sizeof(a)/sizeof(uint8_t);i++)
	{
    
    
		//1.等待TDR寄存器清空
		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET){
    
    }
		//2.将要发送的数据写入TDR寄存器
		USART_SendData(USART1,a[i]);
	}
		//3.等待数据发送完成//写在while外面 保证数据是连续发送的
	while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET){
    
    }
	
	
	while(1){
    
    }
}
3.4.3 发送字符和字符串

在这里插入图片描述

字符在计算机中以ASCLL码存储

  • 可以直接发送对应ASLL码
  • 记不住的话可以这样发送 ‘a’
#include "stm32f10x.h"
#include "stm32f10x_pal.h"

int main(void)
{
    
    
	PAL_Init();
	
	//初始化TX PB6 AF__PP 10MHZ
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
	GPIO_InitTypeDef GPIOInitStruct;
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
	GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//初始化RX PB7 输入IPU
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//重映射USART的TX和RX引脚
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
	
	//使能USART1时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	//设置UASRT1参数
	USART_InitTypeDef USARTInitStruct;
	USARTInitStruct.USART_BaudRate = 9600;
	USARTInitStruct.USART_WordLength = USART_WordLength_8b;
	USARTInitStruct.USART_StopBits = USART_StopBits_1;
	USARTInitStruct.USART_Parity = USART_Parity_No;
	USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_Init(USART1,&USARTInitStruct);
	
	//闭合UASRT1总开关
	USART_Cmd(USART1,ENABLE);
	
	//发送单个字符
	//1.发送字符
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET){
    
    }
	//2.将要发送的数据写入TDR寄存器
	USART_SendData(USART1,'a');		
	//3.等待数据发送完成
	while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET){
    
    }
	
	while(1){
    
    }
}

字符串在计算中以字符数组形式存储

  • 如Hello world 有11个元素,将对应ASCLL码依次发送即可
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
#include "string.h"

int main(void)
{
    
    
	PAL_Init();
	
	//初始化TX PB6 AF__PP 10MHZ
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
	GPIO_InitTypeDef GPIOInitStruct;
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
	GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//初始化RX PB7 输入IPU
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//重映射USART的TX和RX引脚
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
	
	//使能USART1时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	//设置UASRT1参数
	USART_InitTypeDef USARTInitStruct;
	USARTInitStruct.USART_BaudRate = 9600;
	USARTInitStruct.USART_WordLength = USART_WordLength_8b;
	USARTInitStruct.USART_StopBits = USART_StopBits_1;
	USARTInitStruct.USART_Parity = USART_Parity_No;
	USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_Init(USART1,&USARTInitStruct);
	
	//闭合UASRT1总开关
	USART_Cmd(USART1,ENABLE);
	//发送字符串
	const char *str ="Hello word"; //在使用字符串时,我们通常使用char*类型的变量来表示一个字符串
	uint32_t i;
	for(i=0;i< strlen(str);i++)
	{
    
    
		//1.等待TDR寄存器清空
		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET){
    
    }
		//2.将要发送的数据写入TDR寄存器
		USART_SendData(USART1,str[i]);
	}
		//3.等待数据发送完成//写在while外面 保证数据是连续发送的
	while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET){
    
    }
	
	while(1){
    
    }
}

3.5 接收数据

接收原理:
查看RXNE标志位,当EXNE标志位为1时,将RDR寄存器里的值读取出来

在这里插入图片描述

实验测试:

  • 若接收到0 熄灭LED;
  • 若接收到1 点亮LED;
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
#include "string.h"

int main(void)
{
    
    
	PAL_Init();
	
	//初始化TX PB6 AF__PP 10MHZ
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//开启GPIOB时钟
	GPIO_InitTypeDef GPIOInitStruct;
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_AF_PP;
	GPIOInitStruct.GPIO_Speed =GPIO_Speed_10MHz;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//初始化RX PB7 输入IPU
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIOInitStruct.GPIO_Mode =GPIO_Mode_IPU;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//重映射USART的TX和RX引脚
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
	
	//使能USART1时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	//设置UASRT1参数
	USART_InitTypeDef USARTInitStruct;
	USARTInitStruct.USART_BaudRate = 9600;
	USARTInitStruct.USART_WordLength = USART_WordLength_8b;
	USARTInitStruct.USART_StopBits = USART_StopBits_1;
	USARTInitStruct.USART_Parity = USART_Parity_No;
	USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USART_Init(USART1,&USARTInitStruct);
	
	
	//初始化PC13
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_13;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_Out_OD;//电路有外接的上拉电阻
	GPIOInitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOC,&GPIOInitStruct);
	
	GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);//最开始熄灭
	
	uint8_t c;
	while(1)
	{
    
    
		//1.等待RXNE标志位为1 表示RDR寄存器中有数据
		while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == RESET){
    
    }
		//2.存储到变量C中
		c = USART_ReceiveData(USART1);
			
		if(c == '0')
		{
    
    
			//熄灭LED
			GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
		}
		else if(c == '1')
		{
    
    
			//点亮LED
			GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
		}
		else
		{
    
    
		}
	}
}

四、USART的标准库编程—中断方式数据接收和发送

4.1 中断方式接收简介

串口数据的接收有两种方式

  • 轮询方式:不符合裸机多任务模型,做不到多任务并行执行的要求
  • 中断方式:当RX引脚接收到数据时,就触发一次中断

在这里插入图片描述

4.2 配置中断源

状态寄存器也能中的每一位都可以产生一路中断,通过或门将多路合并成一路中断,作为串口的中断,为了能做到分别屏蔽每个标志位的中断,要加一个开关,采用中断使能位与上中断标志位的电路实现

在这里插入图片描述

USART_ITConfig():

  • 使能/禁止中断
  • 通过配置中断使能寄存器实现(IER)
    在这里插入图片描述

USART_GetITStatus()

  • 查询SR寄存器中的状态在这里插入图片描述

USART_ClearITPendingBit()

  • 清除中断标志位
  • 向SR寄存器中的标志位写0在这里插入图片描述

4.3 中断接收的编程思路

若触发了中断,表明有数据来了,则接收数据
接收数据后,还可能进行数据的处理,为了满足裸机多任务模型,应注重数据处理的时间,从而形成了两套思路

  • 数据处理能瞬时完成
  • 数据处理耗时较长

在这里插入图片描述

4.3.1 数据处理能瞬时完成

在中断响应函数中处理
在这里插入图片描述

4.3.2 数据处理耗时较长
  1. 初始化函数
  2. 中断响应函数
    • 执行数据的接收
    • 执行数据的转存
  3. 进程函数
    • 从缓冲区中一个字节一个字节拿数据,并处理

在这里插入图片描述

4.4 中断法的串口接收实验

  1. 编写USART_Recv_Init()函数

在这里插入图片描述

  1. 编写USART1_IRQHandler()中断响应函数

在这里插入图片描述

#include "stm32f10x.h"
#include "stm32f10x_pal.h"

static void USART_Recv_Init(void);

int main(void)
{
    
    
	GPIO_InitTypeDef GPIOInitStruct;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//4.2.1 中断优先级分组
	PAL_Init();
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_13;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
	GPIOInitStruct.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOC,&GPIOInitStruct);
	
	USART_Recv_Init();
	
	while(1){
    
    }
}

static void USART_Recv_Init(void)
{
    
    
	//1.初始化IO引脚
	//PB6 TX PB7 RX
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitTypeDef GPIOInitStruct;
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIOInitStruct.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//使用AFIO外设,将USART1引脚重映射到PB6 PB7
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
	
	//2.使能USART1时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	
	//3.配置UASRT1的参数 
	//9600 数据位8 无奇偶校验 不硬件流控 停止位1 TX|RX
	
	USART_InitTypeDef USARTInitStruct;
	USARTInitStruct.USART_BaudRate = 9600;
	USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USARTInitStruct.USART_Parity = USART_Parity_No;
	USARTInitStruct.USART_StopBits = USART_StopBits_1;
	USARTInitStruct.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1,&USARTInitStruct);
	
	//4.配置中断
	//4.1 配置中断源
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
	//4.2 配置NVIC中断管理器
	//4.2.1 中断优先级分组
	//4.2.2 初始化参数设置
	NVIC_InitTypeDef NVICInitStruct;
	
	NVICInitStruct.NVIC_IRQChannel = USART1_IRQn;
	NVICInitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVICInitStruct.NVIC_IRQChannelPreemptionPriority = 0;
	NVICInitStruct.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&NVICInitStruct);
	//5.闭合USART1的总开关
	USART_Cmd(USART1,ENABLE);
}

void USART1_IRQHandler(void)
{
    
    
	uint8_t c;
	//1.判断中断是谁产生的
	if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)
	{
    
    
		c = USART_ReceiveData(USART1);//清除中断;也读取了数据
		
		if(c == '0')
		{
    
    
			//熄灯
			GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_SET);
		}
		else if(c == '1')
		{
    
    
			//亮灯
			GPIO_WriteBit(GPIOC,GPIO_Pin_13,Bit_RESET);
		}
	}
}

4.5 串口的回声实验(Echo)

在这里插入图片描述

4.5.1 编程思路
  1. 就地处理:不符合

在这里插入图片描述

  1. 延时处理:
    延时处理需要将中断中收到的数据接收到缓冲区,什么来做缓冲区呢?----队列这种数据结构

预备知识—队列:
队列(Queue):一种常用的数据结构,经常作为缓冲区来使用,由于具有"先入先出"的特点,也叫FIFO(First In Frist Out)
在这里插入图片描述

队列的C语言实现:
在这里插入图片描述

//queue.c文件
#include "queue.h"

void Queue_Init(Queue_HandleTypeDef *hQueue)
{
    
    
	hQueue->Tail = 0;
}

void Queue_Enqueue(Queue_HandleTypeDef *hQueue,uint8_t Element)
{
    
    
	hQueue->Data[hQueue->Tail++] = Element;
}

ErrorStatus Queue_Dequeue(Queue_HandleTypeDef *hQueue,uint8_t *pElement)
{
    
    
	uint32_t i;
	if(hQueue->Tail == 0) return ERROR;//队列空,操作无效
	*pElement = hQueue->Data[0];
	for(i=0;i<hQueue->Tail-1;i++)
	{
    
    
		hQueue->Data[i] = hQueue->Data[i+1];
	}	
	hQueue->Tail--;
	return SUCCESS;
}
//queue.h文件
#ifndef _QUEUE_H_
#define _QUEUE_H_

#include "stm32f10x.h"
typedef struct
{
    
    
	//存储队列里的元素
	uint8_t Data[100];
	//队尾指针
	uint16_t Tail;
}Queue_HandleTypeDef;

void Queue_Init(Queue_HandleTypeDef *hQueue);
void Queue_Enqueue(Queue_HandleTypeDef *hQueue,uint8_t Element);
ErrorStatus Queue_Dequeue(Queue_HandleTypeDef *hQueue,uint8_t *pElement);

#endif //防止头文件被重复引用
//应用代码
#include "stm32f10x.h"
#include "stm32f10x_pal.h"

#include "queue.h"

static Queue_HandleTypeDef hQueue;

int main(void)
{
    
    
	uint8_t elementOut;
	PAL_Init();
	Queue_Init(&hQueue);
	Queue_Enqueue(&hQueue,'H');
	if(Queue_Dequeue(&hQueue,&elementOut) == ERROR)
	{
    
    
		//错误时的处理代码
	}	
	else 
	{
    
    
		//此时 elementOut == 'H'
	}
	
	while(1){
    
    }
}

延时处理函数:
在中断响应函数中不直接进行数据处理,而是把收到的数据,存储到缓冲区里,由进程函数将缓冲区中的数据提取出来,进行处理

在这里插入图片描述

//延时处理代码
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
#include "queue.h"
#include "string.h"
static Queue_HandleTypeDef hQueue;

static void USART_Echo_Init(void);//串口初始化函数
static void USART_Echo_Proc(void);//串口进程函数
static void USART1_SendString(const char *str);//串口发送数据函数

int main(void)
{
    
    
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//miss.c中
	PAL_Init();
	USART_Echo_Init();
	
	while(1)
	{
    
    	
		USART_Echo_Proc();
	}
}

void USART1_IRQHandler(void)
{
    
    
	uint8_t c;
	if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)
	{
    
    
		c = USART_ReceiveData(USART1);		
		Queue_Enqueue(&hQueue,c);
	}
}


static void USART_Echo_Init(void)
{
    
    	
	Queue_Init(&hQueue);
	
	//1.初始化IO引脚
	//PB6-TX PB7-RX
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	GPIO_InitTypeDef GPIOInitStruct;
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIOInitStruct.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIOB,&GPIOInitStruct);
	
	//利用AFIO外设进行重映射
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_USART1,ENABLE);
	
	//2.初始化USART1外设
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	USART_InitTypeDef USARTInitStruct;
	USARTInitStruct.USART_BaudRate = 9600;
	USARTInitStruct.USART_HardwareFlowControl =  USART_HardwareFlowControl_None ;
	USARTInitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	USARTInitStruct.USART_Parity =  USART_Parity_No;
	USARTInitStruct.USART_StopBits = USART_StopBits_1; 
	USARTInitStruct.USART_WordLength = USART_WordLength_8b;
	USART_Init(USART1,&USARTInitStruct);
	//3.配置USART1中断
	//3.1 开启USART1的中断源
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);
	//3.2配置NVIC
	//3.2.1中断优先级分组
	//3.2.2 初始化NVIC
	NVIC_InitTypeDef NVICInitStruct;
	
	NVICInitStruct.NVIC_IRQChannel = USART1_IRQn;
	NVICInitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVICInitStruct.NVIC_IRQChannelPreemptionPriority = 0;
	NVICInitStruct.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&NVICInitStruct);
	//4.闭合USART1的总开关
	USART_Cmd(USART1,ENABLE);
}
static uint8_t a[100];//数组
static uint16_t cursor = 0;//游标
static void USART_Echo_Proc(void)
{
    
    
	uint8_t c;
	if(Queue_Dequeue(&hQueue,&c) == SUCCESS)
	{
    
    
		a[cursor++] = c;
		
		if(cursor>2 && a[cursor-2] == '\r'&& a[cursor-1] == '\n')//收到新行
		{
    
    
			//发送出去		
			a[cursor] = 0;
			USART1_SendString((const char *)a);			
			cursor = 0;				
		}
	}
}

static void USART1_SendString(const char *str)
{
    
    
	
	uint32_t i;
	for(i=0;i<strlen(str);i++)
	{
    
    
		while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET ){
    
    }//等待发送数据寄存器清空
		USART_SendData(USART1,str[i]);
	}
	while(USART_GetFlagStatus(USART1,USART_FLAG_TC) == RESET ){
    
    }
}

4.6 中断方式的数据发送

在这里插入图片描述

让发送数据(TXE)标志位产生中断,当要发送数据时,产生中断,在中断响应函数中编写,向串口发送数据的代码。
为满足裸机多任务模型:

  • TXE原本关闭的
  • 产生发送数据中断时,先将数据写入缓存
  • 打开TXE中断
  • 这时中断响应函数被调用
  • 在中断响应函数中,将字符重缓存中取出,依次写入TDR寄存器发送

调用static void USART_SendString(const char *Str)发送字符串

  • 将TXE中断关闭
  • 数据入队
  • 开启中断(触发了中断响应函数)

void USART1_IRQHandler(void)执行

  • 进行数据出队 并发送
  • 数据发完关闭TXE中断
#include "stm32f10x.h"
#include "stm32f10x_pal.h"
#include "queue.h"
#include <string.h>

static Queue_HandleTypeDef hQueue;

static void USART_Tx_Init(void);
static void USART_SendString(const char *Str);

int main(void)
{
    
    
	PAL_Init();
	
	USART_Tx_Init();
	
	USART_SendString("Hello world\r\n");
	
	USART_SendString("I love stm32\r\n");
	
	while(1){
    
    }
}

void USART1_IRQHandler(void)//串口1的中断响应函数
{
    
    
	uint8_t c;
	
	if(USART_GetITStatus(USART1, USART_IT_TXE) == SET)//从队列取元素 不空就放到TDR中发送
	{
    
    
		if(Queue_Dequeue(&hQueue, &c) == SUCCESS)
		{
    
    
			USART_SendData(USART1, c);
		}
		else
		{
    
    
			USART_ITConfig(USART1, USART_IT_TXE, DISABLE);
		}
	}
}

static void USART_SendString(const char *Str)//发送一个字符串
{
    
    
	
	uint32_t i;
	USART_ITConfig(USART1, USART_IT_TXE, DISABLE);//关中断
	for(i=0;i<strlen(Str);i++)//入队
	{
    
    
		Queue_Enqueue(&hQueue, Str[i]);
	}
	USART_ITConfig(USART1, USART_IT_TXE, ENABLE);//开中断
}

static void USART_Tx_Init(void)//串口的初始化
{
    
    
	GPIO_InitTypeDef GPIOInitStruct;
	
	Queue_Init(&hQueue);
	
	// 1. 初始化IO引脚
	// PB6 PB7
	// Tx
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIOInitStruct.GPIO_Speed = GPIO_Speed_10MHz;
	GPIO_Init(GPIOB, &GPIOInitStruct);
	// Rx
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	GPIOInitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIOInitStruct.GPIO_Mode = GPIO_Mode_IPU;
	GPIO_Init(GPIOB, &GPIOInitStruct);
	// AFIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);
	
	// 2. 使能USART1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	
	// 3. 配置USART1的参数
	USART_InitTypeDef USARTInitStruct;
	USARTInitStruct.USART_BaudRate = 9600;
	USARTInitStruct.USART_WordLength = USART_WordLength_8b;
	USARTInitStruct.USART_Parity = USART_Parity_No;
	USARTInitStruct.USART_StopBits = USART_StopBits_1;
	USARTInitStruct.USART_Mode = USART_Mode_Tx;
	USARTInitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_Init(USART1, &USARTInitStruct);
	
	// 4. 配置中断
	// USART_ITConfig(USART1, USART_IT_TXE, ENABLE);
	
	NVIC_InitTypeDef NVICInitStruct;
	NVICInitStruct.NVIC_IRQChannel = USART1_IRQn;
	NVICInitStruct.NVIC_IRQChannelPreemptionPriority = 0;
	NVICInitStruct.NVIC_IRQChannelSubPriority = 0;
	NVICInitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVICInitStruct);
	
	// 5. 闭合USART1的总开关
	USART_Cmd(USART1, ENABLE);
}

猜你喜欢

转载自blog.csdn.net/m0_58326153/article/details/135566944