【STM32】USART串口协议

1 通信接口

通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统

通信协议:制定通信的规则,通信双方按照协议规则进行数据收发

【STM32】USART串口协议_第1张图片

USRT:TX是数据发送引脚,RX是数据接受引脚;

I2C:SCL是(Serial clock)时钟,SDA(Serial Data)是数据;

SPI:SCLK是时钟(Serial Clock),MOSI(Master output Slave Input)主机输出数据脚,MISO(Master intput Slave output)主机输入数据脚,CS(chip select) 片选:用于指定通信对象;

CAN通信:引脚CAN_H、CAN_L,用两个引脚表示差分数据;

USP通信:引脚是DP(Data positive)和DM(Data Minus)(D+/D-),也是差分数据脚。

全双工:同时双向通信,一般有两根通信线(一根发,一根收);

I2C和SPI有单独时钟线,所以它们是同步的,接收方可以在时钟信号的指引下进行采样。

USART、I2C、SPI都是单端电平,即它们引脚的高低电平都是对GND的电压差,还需要GND

CAN和USB是差分信号,它是靠两个差分引脚的电压差来传输信号的,不需要GND。提高抗干扰特性

1.1 串口通信

串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信

单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力

【STM32】USART串口协议_第2张图片

USB转串口模块:把串口协议转换成USB协议

陀螺仪传感器模块、蓝牙串口模块

1.2 硬件电路

简单双向串口通信有两根通信线(发送端TX和接收端RX

TX与RX要交叉连接

当只需单向的数据传输时,可以只接一根通信线

当电平标准不一致时,需要加电平转换芯片

【STM32】USART串口协议_第3张图片

1.3 电平标准

电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种;

TTL电平:+3.3V+5V表示10V表示0

RS232电平:-3~-15V表示1+3~+15V表示0

RS485电平:两线压差+2~+6V表示1-2~-6V表示0(差分信号)

1.4 串口参数及时序

波特率:串口通信的速率(Band、比特率)

波特率是1000bps,表示1m发送1000位,每一位的时间就是1ms

起始位:标志一个数据帧的开始,固定为低电平

串口的空闲状态是高电平,打破高电平

数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行

校验位:用于数据验证,根据数据位计算得来(奇偶校验)

停止位:用于数据帧间隔,固定为高电平

发送0xf的电平

【STM32】USART串口协议_第4张图片

【STM32】USART串口协议_第5张图片

【STM32】USART串口协议_第6张图片

串口中,每一行字节都装在一个数据帧里,每个数据帧由起始位、数据位、停止位组成的;第二张图多了校验位。

1.5 串口时序

发送0x55,无校验,波特率是9600,一位的时间是1/9600 = 104us

低位先行(0101 0101)-》(1010 1010)

【STM32】USART串口协议_第7张图片

发送0xAA,无校验,波特率是9600,一位的时间是1/9600 = 104us

低位先行(1010 1010)-》(0101 0101)

【STM32】USART串口协议_第8张图片

下面同理

【STM32】USART串口协议_第9张图片

【STM32】USART串口协议_第10张图片

发送0x55,无校验,波特率是4800,一位的时间是1/4800 = 208us

低位先行(0101 0101)-》(1010 1010)

【STM32】USART串口协议_第11张图片

发送0x55,加了偶校验,波特率是4800,一位的时间是1/4800 = 208us

低位先行(0101 0101)-》(1010 1010),不用增加1

【STM32】USART串口协议_第12张图片

停止位也是可以设置的

【STM32】USART串口协议_第13张图片

上图的停止位是一位,下图的停止位是两位。

2 USART简介

USART(Universal Synchronous/Asynchronous Receiver/Transmitter通用同步/异步收发器

USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,拼接为一个字节数据,存放在数据寄存器里

自带波特率发生器,最高达4.5Mbits/s

可配置数据位长度(8/9)、停止位长度(0.5/1/1.5/2

可选校验位(无校验/奇校验/偶校验)

支持同步模式、硬件流控制、DMA、智能卡、IrDALIN

STM32F103C8T6 USART资源: USART1(APB2) USART2(APB1) USART3(APB1)

2.1 USART框图

【STM32】USART串口协议_第14张图片

先忽略寄存器,看引脚部分。左上角有TX和RX,就是发送和接收引脚,后面几个暂时不用管。

【STM32】USART串口协议_第15张图片

右上角是发送数据寄存器和接收数据寄存器,这两个寄存器占用同一个地址。

TDR是只写的,数据写到TDR中;RDR是只读的,数据从RDR读出。

发送移位寄存器的作用是:把一个字节的数据一位一位地移出去,正好对应串口协议的波形数据位。

工作方式:例如某时刻给TDR写入0x55这个数据,在寄存器里存储是0101 0101,此时硬件检测写入了数据,就会检查当前移位寄存器是不是有数据正在移位,如果没有,这个0101 0101就会立刻全部移动到发送移位寄存器,准备发送;当数据从TDR移动到移位寄存器时,会置一个标志位TXE,发送寄存器空,检查这个标志位如果置1了,就可以在TDR写入下一个数据了。当TXE置1时,数据其实没有发送出去,只要数据从TDR转移到发送移位寄存器了,TXE就会置1,就可以重新写入数据了。

然后发送移位寄存器就会在下面这里的发生控制器的驱动下,向右移位,然后一位一位的把数据输出到TX引脚,这里实现向右移位的,所以正好和串口通信规定的低位先行是一致的。当数据移位完成后,新的数据就会再次自动的从TDR转移到发送移位寄存器里来;如果当前移位还没有完成,TDR的数据就会进行等待,一旦移位完成,就会立刻转移过来。有了TDR和移位寄存器的双重缓存,可以保证连续发送数据的时候,数据帧之间不会有空闲。简单来说,数据一旦从TDR转移到移位寄存器了,不管有没有完成,就立刻把下一个数据放在TDR等着,一旦移完了,新的数据就会立刻跟上。

接收端类似的。数据从RX引脚通向接收移位寄存器,在接收器控制驱动下,一位一位的读取RX电平,先放在最高位,然后向右移,移位8次之后,就能接收一个字节了,低位先行,所以接收移位寄存器是从高位往低位这个方向移动的,之后当一个字节移位完成后,这一个字节的数据就会整体的一下子转移到接收数据移位寄存器RDR里,在转移的过程中也会置一个标志位RXNE(TX Not Empty,接收数据寄存器非空),当检测到RXNE置1后,就可以把数据读走了,同样,这里也是两个寄存器进行缓存,当数据从移位寄存器转移到RDR时,就可以直接接收下一位帧数据了,这就是USART的整个工作流程。

【STM32】USART串口协议_第16张图片

下面是发送器控制,用来控制发送移位寄存器工作的,接收器控制是控制接收移位寄存器工作的。

左边有硬件数据流控,如果发送设备发送的太快,接收设备来不及处理,就会出现数据覆盖丢失的现象,有了流控,可以避免这个问题。两个引脚,nRTS和nCTS,nRTS是请求发送,是输出脚(能不能接收);nCTS是清除发送,是输入脚(接收信号),n是低电平有效。(TX和CTS是一对,RX和RTX是一对)。

右边是用于产生同步的时钟信号,是配合发送移位寄存器输出的,发送移位寄存器每移位一次,同步时钟电平就跳转一个周期,时钟告诉对方,移出去了 一位数据,只支持输出,不支持输入,所以两个USART之间不能实现同步的串口通信。用途:(1)兼容别的协议;(2)可以做自适应波特率,测量时钟周期可以计算出波特率。

中间唤醒单元作用是实现串口挂载多设备(一条总线上挂多个设备)

下面是中断输出控制,中断申请位就是状态寄存器这里的各种标志位,TXE发送寄存器空RXNE接收寄存器非空这两个重要。

中断输出控制这里就是配置中断是不是通向NVIC。

【STM32】USART串口协议_第17张图片

波特率发生器部分,其实是分频器。

fPCLKx(x=1,2),USART1挂载在APB2上,所以就是PCLK2的时钟,一般是72M;其他的都挂载在APB1,所以是PCLK1的时钟,一般是36M。再除以16通向发送器时钟和接收器时钟;如果TE为1,就是发送器使能了,发送波特率有效;如果RE为1,就是接收器使能了,接收波特率有效。

引脚

2.2 USART基本结构

【STM32】USART串口协议_第18张图片

最左边是波特率发生器,用于产生约定的通信速率,时钟来源是PCLK1/2,经过波特率发生器分频后,产生的时钟通向发送控制器和接收控制器。之后发送数据寄存器和发送移位寄存器的配合将数据一位一位的移出去(低位先行),通过GPIO口的复用,输出到TX引脚。当数据由数据寄存器转移到移位寄存器的时候,会置一个TXE的标志位,判断这个标志位就可以知道是不是写一个数据了;接收部分是类似的,RX引脚的波形通过GPIO输入,在接收控制器控制下,一位一位的移入接收移位寄存器,也是右移的,移完一帧数据后,数据就会统一转运到接收数据寄存器,在转移的同时,置一个RXNE的标志位,检查这个标志位就可以知道是否接收到数据了;同时这个标志位也可以去申请中断,这样就可以在收到数据时,直接进入中断函数,然后快速的读取和保存数据。

有四个寄存器,但是只有一个DR寄存器供我们读写,写入时走上面,进行发送,读取时,走下面,进行接收。

2.3 数据帧

【STM32】USART串口协议_第19张图片

字长就是数据位长度,包含校验位。

下面的时钟就是同步时钟输出的功能,

【STM32】USART串口协议_第20张图片

不同停止位的波形变化,停止位的时长有0.5、1、1.5、2四种。看图。

2.3 输入数据的策略

输出比较简单,之直接输出高低电平就行;但是输入就很复杂,要保证采样频率和波特率一致,还要保证采样的位置处于每一位的正中间。对噪声有判断。

起始位侦测

【STM32】USART串口协议_第21张图片

当输入电路侦测到一个数据帧的起始位后,就会以波特率的频率连续采样一帧数据;同时从起始位开始,采样位置就要对齐到位的正中间,开始对齐了,后面都会对齐。输入的这部分对采样时钟进行了细分,以波特率的16倍频率进行采样,也就是在一位的时间里,可以进行16次采样。最开始空闲状态置高电平,那采样就一直是1,在某个位置突然采样到0,就说明两次采样之间出现了下降沿,如果没有任何噪声,那之后就应该是起始位了,在起始位会进行连续16次采样,没有噪声的话,16次采样都是0。但是实际电路肯定是有噪声的,所以即使出现下降沿了,后续还要再采样几次,接收电路还会在下降沿的3、5、7进行一次采样,在8、9、10进行一次采样,且这两批采样都要求每3位里至少有2个0,。通过了这个起始位侦测,接收状态就由空闲变为接收起始位。

数据采样

【STM32】USART串口协议_第22张图片

1-16是时间的长度,在一个数据位有16个采样时钟,直接在8、9、10次采样数据位,为了保证数据的可靠性,这里连续采样3次。2次为1就表示收到了1,2次为0就表示收到0。

波特率发生器

【STM32】USART串口协议_第23张图片

波特率发生器就是分频器。DIV分为整数部分和小数部分。

9600 = 72M / (16 * DIV)

DIV = 468.75,转成二进制1 1101 0100.11

2.4 数据模式

HEX模式/十六进制模式/二进制模式:以原始数据的形式显示

文本模式/字符模式:以原始数据编码后的形式显示

【STM32】USART串口协议_第24张图片

【STM32】USART串口协议_第25张图片

手册

【STM32】USART串口协议_第26张图片

3 串口发送

3.1 接线图

【STM32】USART串口协议_第27张图片

交叉接线

保证串口驱动没有问题

【STM32】USART串口协议_第28张图片

3.2 模块封装

按这个图初始化

【STM32】USART串口协议_第29张图片

库函数

void USART_DeInit(USART_TypeDef* USARTx);
void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);
void USART_StructInit(USART_InitTypeDef* USART_InitStruct);


// 配置同步时钟输出
void USART_ClockInit(USART_TypeDef* USARTx, USART_ClockInitTypeDef* USART_ClockInitStruct);
void USART_ClockStructInit(USART_ClockInitTypeDef* USART_ClockInitStruct);

void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState);
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState);
void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);

// 发送数据和接收数据
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);

FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
void USART_ClearFlag(USART_TypeDef* USARTx, uint16_t USART_FLAG);
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT);
void USART_ClearITPendingBit(USART_TypeDef* USARTx, uint16_t USART_IT);

Serial.h

#include "stm32f10x.h"                  // Device header


// 串口初始化
void Serial_Init(void)
{
	// 1开启时钟,USART和GPIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 2GPIO初始化,TX配置成复用推挽输出,RX配置成输入
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 3配置USRT
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;											// 波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;		// 硬件流控制
	USART_InitStructure.USART_Mode = USART_Mode_Tx;										// 发送模式
	USART_InitStructure.USART_Parity = USART_Parity_No;									// 校验位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;								// 停止位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;							// 8位字长
	USART_Init(USART1, &USART_InitStructure);
	
	// 4开启(发送);中断(接收)
	USART_Cmd(USART1, ENABLE);
}

// 发送一个字节
void Serial_SendByte(uint8_t byte)
{
	USART_SendData(USART1, byte);
	// 等待标志位
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

// 发送一个数组
void Serial_SendArray(uint8_t* array, uint16_t len)
{
	uint16_t i;
	for (i = 0; i < len; i++) {
		Serial_SendByte(array[i]);
	}
}

// 发送字符串
void Serial_SendString(char* str) {
	uint8_t i;
	for (i = 0; str[i] != '\0'; i++) {
		Serial_SendByte(str[i]);
	}
}

// 次方函数
uint32_t Serial_Pow(uint32_t x, uint32_t y)
{
	uint32_t res = 1;
	while (y--) {
		res *= x;
	}
	return res;
}

// 发送数字,从高位到低位以此发送
void Serial_SendNumber(uint32_t number, uint8_t len) {
	uint8_t i;
	for (i = 0; i < len; i++)
	{
		Serial_SendByte(number / Serial_Pow(10, len - i - 1) % 10 + '0');
	}
}

3.3 主函数

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

int main()
{
	OLED_Init();								// 初始化OLED
	Serial_Init();
	
	Serial_SendByte('A');
	uint8_t array[] = {'B', 'C', 'D'};
	Serial_SendArray(array, 3);
	
	char* str = "Hello Wolrd\r\n";
	Serial_SendString(str);
	
	
	while (1)
	{
		
	}
}

现象:按复位键

【STM32】USART串口协议_第30张图片

3.4 移植printf函数

【STM32】USART串口协议_第31张图片

Micro LIB是Keil为嵌入式平台优化的一个精简库。

对printf重定向,将printf打印的东西输出到串口。Serial.c中多了如下代码,并在对应头文件加#include

// 重写fputc函数,fputc是printf函数的底层
int fputc(int ch, FILE* f)
{
	Serial_SendByte(ch);
	return ch;
}

主函数

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

int main()
{
	OLED_Init();								// 初始化OLED
	Serial_Init();
	
//	Serial_SendByte('A');
//	uint8_t array[] = {'B', 'C', 'D'};
//	Serial_SendArray(array, 3);
//	
//	char* str = "Hello Wolrd\r\n";
//	Serial_SendString(str);
//	Serial_SendNumber(456789, 6);
	
	printf("Num = %d\r\n", 666);
	
	while (1)
	{
		
	}
}

结果

【STM32】USART串口协议_第32张图片

多个串口都想用printf函数

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

int main()
{
	OLED_Init();								// 初始化OLED
	Serial_Init();
	
//	Serial_SendByte('A');
//	uint8_t array[] = {'B', 'C', 'D'};
//	Serial_SendArray(array, 3);
//	
//	char* str = "Hello Wolrd\r\n";
//	Serial_SendString(str);
//	Serial_SendNumber(456789, 6);
	
	printf("Num = %d\r\n", 666);
	
	char str[100];
	sprintf(str, "Num = %d\r\n", 777);
	Serial_SendString(str);
	
	while (1)
	{
		
	}
}

现象

【STM32】USART串口协议_第33张图片

封装sprintf函数,Serial.c中多了,头文件中加#include

// 封装sprintf函数
void Serial_Sprintf(char* format, ...)
{
	char str[100];
	va_list arg;					// 类型名 变量名
	va_start(arg, format);			// 从format位置开始接收参数表,放在arg中
	vsprintf(str, format, arg);
	va_end(arg);
	Serial_SendString(str);
}

主函数

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

int main()
{
	OLED_Init();								// 初始化OLED
	Serial_Init();
	
//	Serial_SendByte('A');
//	uint8_t array[] = {'B', 'C', 'D'};
//	Serial_SendArray(array, 3);
//	
//	char* str = "Hello Wolrd\r\n";
//	Serial_SendString(str);
//	Serial_SendNumber(456789, 6);
	
//	printf("Num = %d\r\n", 666);
//	
//	char str[100];
//	sprintf(str, "Num = %d\r\n", 777);
//	Serial_SendString(str);
	
	Serial_Sprintf("Num = %d\r\n", 666);
	
	while (1)
	{
		
	}
}

3.5 显示汉字

目前是utf8,接收端也得是utf8

【STM32】USART串口协议_第34张图片

汉字编码

--no-multibyte-chars

【STM32】USART串口协议_第35张图片

【STM32】USART串口协议_第36张图片

另一种方式,将Keil设置成GB2312(文件关闭重新打开),将串口助手设置成GBK

4 串口接收

4.1 接线图

同3.1

【STM32】USART串口协议_第37张图片

4.2 模块封装

有查询和中断两种模式

查询是在主函数中不断判断RXNE标志位

查询模式

Serial.c

#include "stm32f10x.h"                  // Device header
#include 
#include 

// 串口初始化
void Serial_Init(void)
{
	// 1开启时钟,USART和GPIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 2GPIO初始化,TX配置成复用推挽输出,RX配置成输入
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;		// 上拉输入
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 3配置USRT
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;											// 波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;		// 硬件流控制
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;;					// 收发模式
	USART_InitStructure.USART_Parity = USART_Parity_No;									// 校验位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;								// 停止位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;							// 8位字长
	USART_Init(USART1, &USART_InitStructure);
	
	// 4开启(发送);中断(接收)
	USART_Cmd(USART1, ENABLE);
}

// 发送一个字节
void Serial_SendByte(uint8_t byte)
{
	USART_SendData(USART1, byte);
	// 等待标志位
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

// 发送一个数组
void Serial_SendArray(uint8_t* array, uint16_t len)
{
	uint16_t i;
	for (i = 0; i < len; i++) {
		Serial_SendByte(array[i]);
	}
}

// 发送字符串
void Serial_SendString(char* str) {
	uint8_t i;
	for (i = 0; str[i] != '\0'; i++) {
		Serial_SendByte(str[i]);
	}
}

// 次方函数
uint32_t Serial_Pow(uint32_t x, uint32_t y)
{
	uint32_t res = 1;
	while (y--) {
		res *= x;
	}
	return res;
}

// 发送数字,从高位到低位以此发送
void Serial_SendNumber(uint32_t number, uint8_t len) {
	uint8_t i;
	for (i = 0; i < len; i++)
	{
		Serial_SendByte(number / Serial_Pow(10, len - i - 1) % 10 + '0');
	}
}

// 重写fputc函数。fputc是printf函数的底层
int fputc(int ch, FILE* f)
{
	Serial_SendByte(ch);
	return ch;
}

// 封装sprintf函数
void Serial_Sprintf(char* format, ...)
{
	char str[100];
	va_list arg;					// 类型名 变量名
	va_start(arg, format);			// 从format位置开始接收参数表,放在arg中
	vsprintf(str, format, arg);
	va_end(arg);
	Serial_SendString(str);
}

中断模式

Serial.c

#include "stm32f10x.h"                  // Device header
#include 
#include 

uint8_t Serial_rxData;
uint8_t Serial_rxFlag;

// 串口初始化
void Serial_Init(void)
{
	// 1开启时钟,USART和GPIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	// 2GPIO初始化,TX配置成复用推挽输出,RX配置成输入
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;		// 上拉输入
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct);
	
	// 3配置USRT
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;											// 波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;		// 硬件流控制
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;;					// 收发模式
	USART_InitStructure.USART_Parity = USART_Parity_No;									// 校验位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;								// 停止位
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;							// 8位字长
	USART_Init(USART1, &USART_InitStructure);
	
	
	// 开启中断
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	// 配置NVIC
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);				// 分组
	NVIC_InitTypeDef NVIC_InitStruct;
	NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;				// 中断通道
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStruct);
	
	// 4开启(发送);中断(接收)
	USART_Cmd(USART1, ENABLE);
}

// 发送一个字节
void Serial_SendByte(uint8_t byte)
{
	USART_SendData(USART1, byte);
	// 等待标志位
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}

// 发送一个数组
void Serial_SendArray(uint8_t* array, uint16_t len)
{
	uint16_t i;
	for (i = 0; i < len; i++) {
		Serial_SendByte(array[i]);
	}
}

// 发送字符串
void Serial_SendString(char* str) {
	uint8_t i;
	for (i = 0; str[i] != '\0'; i++) {
		Serial_SendByte(str[i]);
	}
}

// 次方函数
uint32_t Serial_Pow(uint32_t x, uint32_t y)
{
	uint32_t res = 1;
	while (y--) {
		res *= x;
	}
	return res;
}

// 发送数字,从高位到低位以此发送
void Serial_SendNumber(uint32_t number, uint8_t len) {
	uint8_t i;
	for (i = 0; i < len; i++)
	{
		Serial_SendByte(number / Serial_Pow(10, len - i - 1) % 10 + '0');
	}
}

// 重写fputc函数。fputc是printf函数的底层
int fputc(int ch, FILE* f)
{
	Serial_SendByte(ch);
	return ch;
}

// 封装sprintf函数
void Serial_Sprintf(char* format, ...)
{
	char str[100];
	va_list arg;					// 类型名 变量名
	va_start(arg, format);			// 从format位置开始接收参数表,放在arg中
	vsprintf(str, format, arg);
	va_end(arg);
	Serial_SendString(str);
}

// 中断处理函数
void USART1_IRQHandler(void)
{
	// 判断标志位
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		// 读数据
		Serial_rxData = USART_ReceiveData(USART1);
		Serial_rxFlag = 1;
		
		// 清除标志位
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

// 读后清除标志位函数
uint8_t Serial_GetRxFlag(void)
{
	if (Serial_rxFlag == 1)
	{
		Serial_rxFlag = 0;
		return 1;
	}
	return 0;
}

// 获取接收数据
uint8_t Serial_GetRxData(void)
{
	return Serial_rxData;
}

4.3 主函数

​​​​​​​查询模式

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

uint8_t RxData;

int main()
{
	OLED_Init();       						// 初始化
	Serial_Init();
	
	// 有查询和中断两种模式
	// 查询是在主函数中不断判断RXNE标志位
	
	
//	OLED_Clear();                           // 清屏
	while (1)
	{
		if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET)
		{
			RxData = USART_ReceiveData(USART1);
			OLED_ShowHexNum(1, 1, RxData, 2);
			// 不需要再清除标志位了
		}
	}
}

现象是:STM32的OLED上显示发送的数据

【STM32】USART串口协议_第38张图片

中断模式

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

uint8_t RxData;

int main()
{
	OLED_Init();								// 初始化OLED
	Serial_Init();
	OLED_ShowString(1, 1, "RxData:");
	
	while (1)
	{
		if (Serial_GetRxFlag() == 1)
		{
			RxData = Serial_GetRxData();
			Serial_SendByte(RxData);                     // 回传
 			OLED_ShowHexNum(1, 8, RxData, 2);
			// 不需要再清除标志位了
		}
	}
}

现象是:STM32的OLED上显示发送的数据,回传到电脑上

【STM32】USART串口协议_第39张图片

你可能感兴趣的:(STM32,stm32,嵌入式硬件,单片机,串口通信,USART)