【STM32学习】——串口通信协议&STM32-USART外设&数据帧/输入数据策略/波特率发生器&串口发送/接受实操


声明:学习笔记根据b站江科大自化协stm32入门教程编辑,仅供学习交流使用!

前言

本节学习两个代码,第一个为串口发送,第二个为串口发送+接收。


一、串口通信

1.通信接口

(1)通信的目的:将一个设备的数据传送到另一个设备,扩展硬件系统。比如STM32芯片里面集成了很多功能模块,如定时器计数、PWM输出、AD采集等等,这些都是芯片内部的电路,它们的配置寄存器、数据寄存器都在芯片里面,操作简单,直接读写就行。但是有些功能STM32内部没有,如想要蓝牙无线遥控的功能、想要陀螺仪加速度计测量姿态的功能,只能外挂芯片完成,数据都在STM32外面,这就需要通信线路发送或者接收数据完成数据交换。


(2)通信协议:制定通信的规则,通信双方按照协议规则进行数据收发。STM32里的通信协议有如下表:(引脚列出的只是最简单最常用的值得注意的)
在这里插入图片描述
全双工为输入输出可同时进行,半双工为既可以输入也可以输出但不能同时进行,单工指只能输出或输入。

I2C和SPI有时钟线SCL为同步通信,没时钟线的为异步通信,需要双方约定一个采样频率,且需要加一些帧头帧尾等进行采样位置的对齐。

电平特性,单端就是它们引脚的高低电平都是对GND的电压差,所以单端信号通信的双方必须要共地,就是把GND接在一起。后两种CAN和USB为差分数据通信,靠两个差分引脚的电压差来传输信号的,在通信时可以不用GND,不过USB协议里有些地方要单端信号,它还是需要共地的。差分信号有好的抗干扰特性,所以一般它的传输速度和距离都会比较高性能也不错。

USART和USB属于点对点通信,中间三个可挂载多个设备,需要一个寻址过程(设备编号)。


2.串口通信

(1)串口简介

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

单片机的串口可以使单片机与单片机单片机与电脑单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力。主要特点就是用在单片机和电脑之间,其他如I2C、SPI等较复杂的通信协议多用在芯片与芯片之间,陀螺仪加速度计测量姿态芯片与STM32、MPU6050与STM32。
在这里插入图片描述
左图为一个USB转串口模块,上面有个CH340芯片,可以把串口协议转换为USB协议,因为电脑的接口现在流行的都是USB口。
中间图为一个陀螺仪传感器模块,可测量角速度、加速度等姿态参数,左右各有四个引脚,一边是串口的引脚,另一边为I2C的引脚。
右图为一个蓝牙串口模块,下面4个脚是串口通信的引脚,上面的芯片可以和手机互联,实现手机遥控单片机的功能。


(2)串口硬件电路

简单双向串口通信有两根通信线(发送端TX和接收端RX),TX与RX要交叉连接。当只需单向的数据传输时,可以只接一根通信线。
在这里插入图片描述
电平标准不一致时,需要加电平转换芯片。串口有很多电平标准,像我们这种直接从控制器里面出来的信号一般都是TTL电平,相同的电平才能互相通信,几种点评标准如下介绍。

电平标准是数据1和数据0的表达方式,是传输线缆中人为规定的电压与数据的对应关系,串口常用的电平标准有如下三种:
(1)TTL电平:+3.3V或+5V表示1,0V表示0。低压小型设备,如单片机。
(2)RS232电平:-3到-15V表示1,+3到+15V表示0。一般在大型机器使用,由于环境比较恶劣静电干扰比较大,所以电压较大且允许波动的范围较广。
(3)RS485电平:两线压差+2到+6V表示1,-2到-6V表示0(差分信号)。通信距离可达上千米,上面两种最远几十米。


(3)串口软件部分

在这里插入图片描述
串口中,每个字节都装载在一个数据帧(10或11位)里,每个数据帧都由起始位数据位和停止位加粗样式组成,数据位有8个代表一个字节的8位。右图数据位9位多了一个奇偶校验位,跟在有效载荷(字节)后面。参数如下
波特率:串口通信的速率。波特率本来的意思是每秒传输码元的个数,单位是码元/s,或者直接叫波特(Baud),还有个速率叫比特率,每秒传输的比特数,单位是bit/s,或者是bps。在二进制调制下,一个码元就是一个bit,此时波特率等于比特率,单片机的串口通信基本都是二进制调制(高电平表示1,低电平表示0,一位就是1bit),所以串口的波特率经常会和比特率混用。
起始位:标志一个数据帧的开始,固定为低电平。空闲状态为高电平,起始位产生下降沿,来告诉设备要开始发送数据了
数据位:数据帧的有效载荷,1为高电平,0为低电平,低位先行
校验位:用于数据验证,根据数据位计算得来。这里串口用的是奇偶校验的数据验证方法,可以判断数据传输是否出错,如果出错可选择丢弃或者重传。可选择三种方式,无校验、奇校验、偶校验。奇校验,包括校验位在内的9个数据位会出现奇数个1,根据8位数据情况奇校验位补0或1,保证1的个数位奇数,接收方接收数据时,会验证数据位和校验位,检出率不高比如有两位同时出错,只校验奇偶特性是检验不出的。偶校验同理,只能保证一定检出率。如果要更高检出率可以使用CRC校验
停止位:用于数据帧间隔,固定为高电平。也是为下一个起始位做准备(切换到高电平空闲状态)
在这里插入图片描述
上面为用示波器测出的串口通信时的实际波形!


二、STM32的USART外设

1.USART简介

USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通用同步/异步收发器。同步模式只是多了个时钟输出,只支持时钟输出不支持时钟输入,一般很少用,所以USART和UART基本相同。同步模式更多的是为了兼容别的协议或者特殊用途而设计的


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


自带波特率发生器,最高达4.5Mbits/s,常用9600或115200。用来配置波特率的,其实就是个分频器,比如APB2总线给一个72MHz的频率,波特率发生器进行一个分频得到想要的波特率时钟,在这个时钟下进行收发,就是指定的通信波特率。


支持配置参数
可配置数据位长度(8或9,一般选8无校验)、停止位长度(0.5或1或1.5或2,一般选1位)。
可选校验位(无校验/奇校验/偶校验)。
支持同步模式、硬件流控制、DMA、智能卡、IrDA、LIN同步模式多了个时钟CLK输出。硬件流控制,比如A设备有个TX向B设备RX发送数据,A设备发的太快导致B处理不过来,如果没有硬件流控制,B就只能抛弃新数据或者覆盖原数据了,如果有硬件流控制,在硬件电路上会多出一根线,如果B没准备好接收就置于
高电平,准备好了就置低电平,A只会在B准备好的时候发送数据。DMA,是指串口支持DMA进行数据转运,收发数据量大时可借助DMA,减轻CPU负担。智能卡、IrDA、LIN,这些是其他的一些协议,这些协议与串口非常相似,所以STM32对USART加了些改动,可兼容这些协议。IrDA用于红外通信的,一边红外发光管,另一边红外接收管,靠闪烁红外光通信,与遥控器的红外不同。LIN是局域网的通信协议。


STM32F103C8T6 USART资源: USART1、 USART2、 USART3。USART1位APB2总线上的设备,USART2和USART3位APB1总线上的设备,开启时钟RCC时注意下。


2.图示详解

在这里插入图片描述
左上角TX、RX为发送和接收引脚,SW_RX、IRDA_OUT/IN是智能卡和IrDA通信的引脚(暂时不用)。TX由发送移位寄存器发出,RX通向接收移位寄存器。
灰色部分,TDR和RDR就是串口的数据寄存器了,发送和接收的字节存在这里,两个寄存器占用同一个地址(与51单片机串口的SBUF寄存器一样),所以在程序上只表现为一个寄存器DR。发送数据寄存器只写,接收数据寄存器只读。
比如某时刻在TXD里写入0x55,二进制为01010101,此时硬件检测到写入数据了,就会检查当前发送移位寄存器是否有数据在移位,如果没有那么01010101就会全部移动到寄存器准备发送,这时会置一个标志位TXE(TX Empty)发送寄存器空,检查这个标志位如果置1了,就可在TDR写入下一个数据(注意TXE置1时数据其实还没发送出去),然后发送移位寄存器在下面发送器控制的驱动下开始向右移位,一位一位地把数据送到TX引脚(向右移位与串口规定的低位先行是一致的)。


图下半部分主要是控制部分和增强功能。硬件数据流控,也叫硬件流控制,简称流控,之前介绍过,引脚nRTS(request to send)是请求发送,为输出脚,接到外部设备的CTS用于告诉外部设备现在自己是否可以接收;引脚nCTS(clear to send)是清除发送,是输入脚,接到外部设备的RTS用于接收别人nRTS的信号,查看别人是否可以接收(也就是要求对方也要有流控引脚)。”n“的意思是低电平有效。(一般不用,了解一下)
SCLK控制和SCLK这部分用于产生同步的时钟信号,配合发送移位寄存器输出的,发送寄存器每移位一次,同步时钟电平就跳变一个周期,时钟告诉对方我移出一位数据了,只支持输出不支持输入,所以两个USART之间不能实现同步串口通信。它的第一个用途是,兼容别的协议,比如串口加上时钟后跟SPI协议很像,有了时钟输出的串口就可兼容SPI;这个时钟也可做自适应波特率,比如接收设备不确定发送设备给的什么波特率,就可测量下此时钟周期计算得到波特率(一般不用,了解一下)
唤醒单元,作用是实现串口挂载多设备。之前说过串口一般为点对点通信(只支持两个设备,直接发收数据就行),而多设备通信在一条总线可接多个从设备,每个设备分配一个地址,通信要先进行寻址确定通信对象,再进行数据收发。唤醒单元可使其获得这种功能,在USART地址处给串口分配一个地址,当发送指定地址时此设备唤醒开始工作,当发送别的设备地址时别的设备工作,没收到地址的设备不唤醒保持沉默。(一般不用,了解一下)


中断(输出)控制,申请中断位就是状态寄存器SR的各种标志位,其中TXE发送寄存器空和RXNE接收寄存器非空比较重要,是必要的。中断控制就是配置中断是否可以通向NVIC。
最下面为波特率发生器部分,波特率发生器其实就是分频器,APB时钟进行分频,得到发送和接收移位的时钟。时钟输入是fPCLKx(x=1或2),USART1挂载在APB2上所以为PCLK2时钟,一般72MHz;其他的USART都挂载在APB1,为PCLK1的时钟,一般为36MHz。时钟除以一个USARTDIV分频系数进行分频,USARTDIV为虚线框内为一个数值,分为DIV_Mantissa整数部分和DIV_Fractior小数部分,分频更加精准。
分频完还要再除以16,得到发送器时钟和接收器时钟,通向发送器和接收器控制部分。如果TE(TX Enable)为1,即发送器使能,发送部分的波特率有效,如果RE(RX Enable)为1,就是接收器使能,接收部分的波特率有效。


剩下的是一些寄存器指示,比如各个CR控制寄存器的哪一位控制哪一部分电路,SR状态寄存器有哪些标志位等,可参考芯片手册寄存器的介绍。
江科大自化协博主将框图简化后得到常用基本结构如下
在这里插入图片描述


三、细节问题

1.数据帧

在这里插入图片描述
数据帧下的时钟波就是之前说过的同步时钟输出的功能,在每个数据位的中间都有一个时钟上升沿,时钟频率与数据速率一样,接收端可以在时钟上升沿处进行采样,这样可以精准定位每一位数据,时钟的最后一位(虚线)可通过LBCL位控制要不要输出,时钟的极性、相位等也可通过配置寄存器配置。


空闲帧从头到尾都是1,断开帧从头到尾都是0,它们用于局域网协议,串口用不到,可了解下。


8位字长也可以设置有无校验位,一般为了发送完整字节都选择无校验。9位字长常选择有校验。


在这里插入图片描述
以上为不同停止位的波形变化。STM32的串口可配置停止位长度为0.5,1,1.5,2四种,本质是时长不同。一般选择1位。


2.输入数据策略

串口的输出TX比输入RX简单很多,输出就定时翻转TX引脚高低电平;但是输入要保证采样频率和波特率一致,还要保证每次输入采样的位置,要正好处于每一位正中间,这样读进来的电平最可靠,另外输入要对噪声有一定判断能力,如果是噪声最好能置个标志位提醒一下。

(1)起始位侦测

在这里插入图片描述
当输入电路侦测到一个数据帧的起始位后,就会以波特率的频率连续采样一帧数据,同时从起始位开始,采样位置就要对齐到位的正中间,只要第一位对齐后面就肯定对齐了。


首先输入电路对采样时钟进行了细分,会以波特率的16倍频率进行采样,即在1位的时间里进行16次采样策略是最开始空闲状态高电平,那采样就一直是1,在某个位置突然采到0就说明在两次采样之间出现了下降沿,如果没有任何噪声那么之后应该就是起始位了,在起始位会进行连续16次采样,没有噪声的话16次都是0。
实际上会有噪声,所以即使出现下降沿后续也要再采样几次,以防万一,根据手册接收电路还会在下降沿出现之后的第3次、5次、7次进行一批采样,在第8次、9次、10次再进行一批采样,且这两批采样都要要求每三位里面至少有2个0(如图),才算检测到起始位。如果三位都是0那就无噪声,检测到起始位;如果有两个0,也算检测到起始位但会在状态寄存器里会置一个NE(Noise Error)噪声标志位,用于提醒数据是收到了但是有噪声,用的时候注意下;如果三位只有一个0,就不算检测到起始位,重新捕捉下降沿。
如果通过了起始位侦测,接收状态就由空闲变为接收起始位,同时第8、9、10次采样的位置正好是起始位的正中间,之后接收数据位时就都在第8、9、10次进行采样,保证再正中间。


(2)数据采样

在这里插入图片描述
从1到16是一个数据位的时间长度,即一个数据位有16个采样时钟,由于起始位侦测已经对齐了采样时钟,所以这里就直接在第8、9、10次采样数据位。为了保证数据可靠性连续采样3次(8、9、10),如果没有噪声时,3次都为1或0,那么就认为采样的为1或0;如果有噪声造成3次中既有0也有1,按照2:1规则来,按出现2次的,这种情况NE噪声标志位也会置1。


3.波特率发生器

上面框图详解是也学到过。
在这里插入图片描述

发送器和接收器的波特率由波特率寄存器BRR里的DIV确定(分整数和小数部分),计算公式:波特率 = fPCLK2/1 / (16 * DIV),不除以16的话为上面提到的采样时钟,它是波特率的16倍,所以要除以16。
比如要配置USART为9600的波特率,那么BRR寄存器的配置为:代入公式9600=72M/(16xDIV),解得DIV=468.75,二进制数为111010100.11,所以写入DIV整数部分的是111010100(前面未满的位补零),DIV小数部分写入的是11(后面未满的位补零)。不过用库函数就很方便。


4.拓展——USB转串口模块电路图

在这里插入图片描述
最左边是USB的端口,USB有四个线GND、D+、D-、VCC,标准供电为5V,D+、D-是通信线走的是USB协议,通过CH340芯片进行转换,转换之后输出的就是TXD和RXD串口协议,通过排针引出来到CON6第2、3脚。
供电策略,所有的电都是从VCC+5V(USB的一个脚,一般插在电脑)来的,然后VCC+5V通过稳压电路进行降压得到VCC+3.3V,之后VCC+5V和VCC+3.3V都通过排针引出来到第4、6脚为电压输出脚,第5脚引到了CH340G_VCC,是CH340电源输入脚,一般这个模块的排针会有一些跳线帽,跳线帽需要插在4、5脚,或者5、6脚上(根据单片机供电需求选5V或3.3V,如右上角文字说明)。实物接线表现为:
在这里插入图片描述
更多关于USART见芯片手册第25章!


四、实操案例

1.串口发送

在这里插入图片描述
注意首先在电脑设备管理器里会有如下标识,如果出现了COM号并且图标前没有感叹号,就说明串口驱动没问题,否则需要安装下串口驱动:
在这里插入图片描述
由引脚定义表,PA9为USART1_TX复用,PA10为USART1_RX复用,与USB的TX和RX交叉连接。程序里GPIO初始化时,把TX配置成复用推挽输出,RX配置为输入模式。输入并不分什么普通输入、复用输入,一根线只能一个输出,但可以有多个输入,所以输入脚外设和GPIO都可以同时使用,一般RX配置为浮空输入或者上拉输入。

//Serial.c
#include "stm32f10x.h"                  // Device header
//老样子根据基本结构图打通:
//1、开启时钟,打开USART和GPIO时钟都打开
//2、GPIO初始化,把TX配置成复用输出,RX配置为输入(本代码没用到接收功能)
//3、配置USART,使用一个结构体即可配置所有相关参数
//4、如果只需要发送功能,直接开启USART初始化就结束了
//(5、如果需要接收功能,可能需要配置中断,在开启USART之前加上ITConfig和NVIC的代码就行)
//初始化完成后,如果需要发送数据调用一个发送函数就行,接收数据同理,如果要获取发送和接收标志位也是调用一个函数
void Serial_Init(void){
    
    
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//USART1为APB2总线上的外设
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP ;//复用推挽输出模式
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;//供串口外设TX脚使用
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate= 9600;//Init函数内部会自动计算需要的分频系数写入BRR寄存器
	USART_InitStructure.USART_HardwareFlowControl= USART_HardwareFlowControl_None;//无流控
	USART_InitStructure.USART_Mode= USART_Mode_Tx;//输出发送
	USART_InitStructure.USART_Parity= USART_Parity_No;//Odd奇、Even偶、No无校验
	USART_InitStructure.USART_StopBits= USART_StopBits_1;//停止位长度
	USART_InitStructure.USART_WordLength= USART_WordLength_8b;//字长8位
	USART_Init(USART1,&USART_InitStructure);
	
	USART_Cmd(USART1,ENABLE);
}

void Serial_SendByte(uint8_t Byte){
    
    
	USART_SendData(USART1,Byte);
	while (USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);//等待置1
	//该标志位不需要手动清零,下一次SendData这个标志位会自动清零
}

void Serial_SendArray(uint8_t* Array,uint16_t Length){
    
    
	//第一个参数为uint8_t*类型指向数组首地址,传递数组需要用到指针
	//第二个参数由于数组无法判断是否结束,用Length
	uint16_t i;
	for(i=0;i<Length;i++){
    
    
		Serial_SendByte(Array[i]);
	}
}

void Serial_SendString(char* String){
    
    //uint8_t也可以,由于字符串自带结束标志位0,不用Length了
	uint8_t i;
    for(i=0;String[i] != '\0';i++){
    
    //可以把字符串当作一个数组
		Serial_SendByte(String[i]);
	}
	
}

uint32_t Serial_Pow(uint32_t X,uint32_t Y){
    
    //次方函数X**Y
	uint32_t Result = 1;
	while(Y--){
    
    
		Result *= X;
	}
	return Result;
}
void Serial_SendNumer(uint32_t Number,uint8_t Length){
    
    
	//首先要把需要发送的Number的个位、十位、百位、千位等每一位以十进制拆分开
	//然后转换为字符数字对应的数据,依次发送出去
	//以十进制拆分:比如一个数字为1234,取千位1:1234/10**3=1.234;1.234%10=1
	uint8_t i;
	for(i=0;i<Length;i++){
    
    
		Serial_SendByte(Number/Serial_Pow(10,Length-i-1)%10+0x30);
		//之所以是Length-i-1是因为从高位开始取的(如1234依次取千位、百位、十位、个位依次发送)
		//之所以+0x30是因为要用字符(文本)显示,ASCII码表里字符0对应0x30。其实也可写成+'0'
	}
	
}

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

int main(void){
    
    
	OLED_Init();
	Serial_Init();
	Serial_SendByte(0x22);
	//程序的逻辑是上电后,初始化串口,再利用STM32串口发送一个0x22。
	//调用Serial_SendByte(0x22)后TX引脚会产生一个0x22对应的波形,这个波形可以发送给其他支持串口的模块
	//这里使用USB转串口模块发送给电脑端,需要一个串口助手软件查看
	
	uint8_t MyArray[]={
    
    0x42,0x43,0x44,0x45};
	Serial_SendArray(MyArray,4);
	
	Serial_SendString("Hello world!\n");
	
	Serial_SendNumer(1234 , 4);
	
	while(1){
    
    
		
	}
}
//拓:可深入学习下指针、字符数组、可变参数等内容
//sprintf是少有的含有可变参数的函数,有兴趣可搜索C语言可变参数

串口助手软件里接收模式选择HEX模式,就是以原始数据的形式显示,如发送0x41,显示为41;如果想显示字符串可选择文本模式,以字符形式显示,如发送0x41显示为A。
HEX模式也叫做十六进制模式或二进制模式,以原始数据显示,只能显示一个个十六进制数;
文本模式也叫字符模式,通过原始数据查找字符集编码成一个字符,如ASCII码表是最简单的字符集,如果想显示和存储汉字的话需要汉字字符集,如GB2312、GBK、GB18030等。为了标准化不同国家的字符集,出现了Unicode字符集,Unicode最常用的传输形式是UTF8
0x41到A叫做译码,A到0x41叫做编码。


2.串口发送+接收

硬件电路接线图与上一个一样,代码中着重加入了接收部分,有注释的为不同点(此代码目前只支持发送一个字节):

//Serial.c
#include "stm32f10x.h"   

uint8_t Serial_RxData;//多
uint8_t Serial_RxFlag;//多

void Serial_Init(void){
    
    
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP ;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ;//上拉输入
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//多
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//多
	GPIO_Init(GPIOA,&GPIO_InitStructure);//多
	
	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;
	USART_Init(USART1,&USART_InitStructure);
	
	//对于串口接收可使用查询和中断两种方法,如果使用查询到此初始化就结束
	//查询流程:在主函数里不断判断RXNE标志位,置1说明收到数据了,
	          //再调用USART_ReceiveData读取DR(与上面的Serial_SendByte类似)
	//下面我们程序中实现下中断的方法:
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);//开启RXNE标志位到NVIC的输出
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//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_Init(&NVIC_InitStructure);//初始化NVIC的USART通道
	
	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 Length){
    
    
	uint16_t i;
	for(i=0;i<Length;i++){
    
    
		Serial_SendByte(Array[i]);
	}
}

void Serial_SendString(char* String){
    
    
	uint8_t i;
    for(i=0;String[i] != '\0';i++){
    
    
		Serial_SendByte(String[i]);
	}
	
}

uint32_t Serial_Pow(uint32_t X,uint32_t Y){
    
    
	uint32_t Result = 1;
	while(Y--){
    
    
		Result *= X;
	}
	return Result;
}
void Serial_SendNumer(uint32_t Number,uint8_t Length){
    
    
	uint8_t i;
	for(i=0;i<Length;i++){
    
    
		Serial_SendByte(Number/Serial_Pow(10,Length-i-1)%10+0x30);
	}
	
}


//RXNE标志位一但置1,就会向NVIC申请中断,之后就会在中断函数里接收数据
//其实就是在中断里面对数据进行了一次转存,最终还要扫描查询RxFlag来接收数据
//放在这里转运一个字节意义看着不大,但是为下节多字节数据包接收作铺垫
void USART1_IRQHandler(void){
    
    //中断函数名是固定的
	if (USART_GetFlagStatus(USART1,USART_IT_RXNE)==SET){
    
    
		Serial_RxData= USART_ReceiveData(USART1);
		Serial_RxFlag= 1;
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
		//标志位,如果读取了DR就会自动清零,如果没读取DR,就需要手动清零。这里清零下也不影响
	}
}

uint8_t Serial_GetRxFlag(void){
    
    //实现读后自动清除功能
	if(Serial_RxFlag == 1){
    
    
		Serial_RxFlag=0;
		return 1;
	}
	return 0;
}
uint8_t Serial_GetRxData(void){
    
    //封装接收数据函数
	return Serial_RxData;
}

//main.c
#include "stm32f10x.h"   
#include "Delay.h"   
#include "OLED.h"
#include "Serial.h"

uint8_t RxData;

int main(void){
    
    
	OLED_Init();
	OLED_ShowString(1,1,"RxData:");
	
	Serial_Init();
	
	while(1){
    
    
		if (Serial_GetRxFlag()==1){
    
    
			RxData =Serial_GetRxData();//接收数据
			Serial_SendByte(RxData);//把接受到的这一数据回传到电脑
			OLED_ShowHexNum(1,8,RxData,2);
		}
		
	}
}


总结

遇到挫折,要有勇往直前的信念,马上行动,坚持到底,决不放弃,成功者决不放弃,放弃者绝不会成功。成功的道路上,肯定会有失败;对于失败,我们要正确地看待和对待,不怕失败者,则必成功;怕失败者,则一无是处,会更失败。
今天的学习分享到此就结束了,我们下次再见!!
在这里插入图片描述往期精彩:
STM32定时器输入捕获(IC)
STM32定时器输出比较(PWM波)
STM32定时中断
STM32外部中断
STM32GPIO精讲

猜你喜欢

转载自blog.csdn.net/weixin_51658186/article/details/129770790