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

2023-11-18


声明:学习笔记根据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精讲

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • RT-Thread 内核基础(六)

    RT Thread内核配置示例 RT Thread的一个重要特性是高度可裁剪性 支持对内核进行精细调整 对组件进行灵活拆卸 配置主要是通过修改工程目录下的rtconfig h文件来进行 用户可以通过打开 关闭该文件中的宏定义来对代码进行条件
  • 普冉32位单片机 PY32C642,M0+内核,1.7 V ~ 5.5 V宽工作电压

    PY32C642 单片机采用高性能的 32 位 ARM Cortex M0 内核 宽电压工作范围 嵌入 24Kbytes Flash 和 3 Kbytes SRAM 存储器 最高工作频率 24 MHz 包含多种不同封装类型产品 工作温度范围
  • 小学二三年级入门信奥赛,如何从Scratch进入C++的学习

    小学生几年级适宜开始学习C 这是讨论的比较热烈 也是比较热门的话题 小学生适宜几年级开始学C 小学生适宜几年级开始学C CSDN博客 simple happiness 信息学规划 北京二年级学生图形化过二级想往信奥靠拢如何准备 信息学规划
  • 1.常用单词学习

    1 1 听力练习 第一课 Av264771740 P1 Av736460000 P1 哔哩哔哩 bilibili 有推荐的吗 这个和这个都很推荐 这个多少钱 请给我这个 全部 这些一共多少钱 卫生间在哪呢 一度 願 麻烦再来一次 英語話 会
  • 从外卖员到程序员,自学3年终于转行成功,三面“拿下”拼多多

    前言 先来自我介绍 老家农村 家里好不容易把我送到大城市读书 大学非985 211 但在我们老家 能出一个本科大学生也是非常不容易的 因为农村信息的相对闭塞 我对大学专业一无所知 加上分数并非前茅 最后被调剂一个我并不喜欢的专业 这里就不透
  • 【OpenCV学习笔记02】- 图像入门

    内容 这里介绍了图像处理的入门操作 你将学习如何读取图像 如何显示图像以及如何将其保存回去 你将学习以下功能 cv imread cv imshow cv imwrite 简单使用OpenCV 读取图像 使用 cv imread 函数读取图
  • 太阳诱电 | 电容器为什么会发热?什么是纹波电流

    电容器中存在寄生于电极和电介质的电阻成分 当纹波电流等交流电流通过电容器时 电阻的成分会产生热量 为了抑制发热 选择ESR较低的电容器非常重要 陶瓷电容器在电容器中ESR较低 非常适合抑制发热 电容器中的纹波电流主要是指电源电路中由于IC的
  • Arm:objcopy 如何知道 elf 中的哪些部分要包含在二进制或 ihex 中?

    我正在开发一个项目 其中涉及解析arm elf 文件并从中提取部分 显然 elf 文件中有很多部分没有加载到闪存中 但我想知道 objcopy 到底如何知道要在二进制文件中包含哪些部分以直接闪存到闪存中 以arm elf文件的以下reade
  • 小白也能学会的创建Git仓库实操

    2024软件测试面试刷题 这个小程序 永久刷题 靠它快速找到工作了 刷题APP的天花板 CSDN博客 文章浏览阅读2 2k次 点赞85次 收藏11次 你知不知道有这么一个软件测试面试的刷题小程序 里面包含了面试常问的软件测试基础题 web自
  • 2024年金三银四网络安全考试试题

    2023年金三银四网络安全考试试题 1 关于数据使用说法错误的是 A 在知识分享 案例中如涉及客户网络数据 应取敏感化 不得直接使用 B 在公开场合 公共媒体等谈论 传播或发布客户网络中的数据 需获得客户书面授权或取敏感化 公开渠道获得的除
  • 用CHAT写一份标题为职业教育教师教学能力提升培训总结

    CHAT回复 标题 职业教育教师教学能力提升培训总结 一 活动概述 本次由学校组织的职业教育教师教学能力提升培训于8月15日至8月20日顺利进行 来自全校的60位职业教育教师参与了此次培训 主讲人为享有盛名的教育专家马丁先生 二 培训内容与
  • msyql 异常,别干着急,70%的问题都在这里!

    2024软件测试面试刷题 这个小程序 永久刷题 靠它快速找到工作了 刷题APP的天花板 CSDN博客 文章浏览阅读2 3k次 点赞85次 收藏11次 你知不知道有这么一个软件测试面试的刷题小程序 里面包含了面试常问的软件测试基础题 web自
  • 【计算机毕业设计】OA公文发文管理系统_xtv98

    近年来 人们的生活方式以网络为主题不断进化 OA公文发文管理就是其中的一部分 现在 无论是大型的还是小型的网站 都随处可见 不知不觉中已经成为我们生活中不可或缺的存在 随着社会的发展 除了对系统的需求外 我们还要促进经济发展 提高工作效率
  • 【GRNN-RBFNN-ILC算法】【轨迹跟踪】基于神经网络的迭代学习控制用于未知SISO非线性系统的轨迹跟踪(Matlab代码实现)

    欢迎来到本博客 博主优势 博客内容尽量做到思维缜密 逻辑清晰 为了方便读者 座右铭 行百里者 半于九十 本文目录如下 目录 1 概述 2 运行结果 2 1 第1部分 2 2 第2部分
  • Cortex-M3与M4权威指南

    处理器类型 所有的ARM Cortex M 处理器是32位的精简指令集处理器 它们有 32位寄存器 32位内部数据路径 32位总线接口 除了32位数据 Cortex M处理器也可以有效地处理器8位和16位数据以及支持许多涉及64位数据的操作
  • 核心耦合内存在 STM32F4xx 上可执行吗?

    尝试从 STM32F429s CCM 运行代码 但每当我命中 CCM 中的第一条指令时 我总是会遇到硬故障 并且 IBUSERR 标志被设置 该指令有效且一致 STM32F4xx 是否可能不允许从 CCM 执行 数据访问效果良好 alios
  • 使用 STM32F0 ADC 单独读取不同的输入

    STM32F072CBU 微控制器 我有多个 ADC 输入 并且希望单独读取它们 STMcubeMX 生成样板代码 假设我希望按顺序读取所有输入 但我无法弄清楚如何纠正这个问题 这篇博文 http blog koepi info 2015
  • 哪些变量类型/大小在 STM32 微控制器上是原子的?

    以下是 STM32 微控制器上的数据类型 http www keil com support man docs armcc armcc chr1359125009502 htm http www keil com support man d
  • 移动数组中的元素

    我需要一点帮助 我想将数组中的元素向上移动一个元素 以便新位置 1 包含位置 1 中的旧值 new 2 包含 old 1 依此类推 旧的最后一个值被丢弃 第一个位置的新值是我每秒给出的新值 我使用大小为 10 的数组 uint32 t TE
  • stm32l0: 执行MI命令失败。使用 vFlashErase 数据包擦除闪存时出错

    我正在使用 Nucleo STM32L031 和 AC6 STM32 工作台 eclipse 我编写应用程序并进入调试模式 一切正常 直到我在应用程序中添加另一个功能 我注意到当我删除 评论 新函数 软件可以再次进入调试模式 但是当我添加

随机推荐

  • 网页按钮点击动画

    要求 一个按钮 每点击一次在大小可随时变化的按钮表面生成一个实心圆形 对每个圆形配置的时间 T T T 单位 毫秒 内有如下过程 第 i i i次点击生成一个圆形
  • 如何快速构建一个SpringBoot项目

    我们主要介绍如何快速构建一个SpringBoot项目 以此来提升日常开发效率 SpringBoot是搭建应用的手脚架 由Spring公司的核心团队在2013年开始研发 2014年4月发布第一个版本的全新开源的轻量级框架 它基于Spring4
  • Windows10系统自动登录

    1 打开注册表 在搜索框内 输入regedit 或者 注册表 2 找到 HKEY LOCAL MACHINE SOFTWARE Microsoft Windows NT CurrentVersion Winlogon 3 添加新键 类型是字
  • openGL之API学习(二零三)GL_TEXTURE_WRAP_S GL_TEXTURE_WRAP_T

    设置纹理坐标超出0 1范围时的处理方式 使用函数glTexParameteri 设置纹理参数 设置纹理参数 GL TEXTURE WRAP S 为 GL REPEAT 表示纹理X方向循环使用纹理 glTexParameteri GL TEX
  • 实际工作中的高级技术(训练加速、推理加速、深度学习自适应、对抗神经网络)

    目录 一 训练加速 1 基于数据的并行 Model Average 模型平均 SSGD 同步随机梯度下降
  • 大学生选课抢课如何提高选中概率

    作者位于哈尔滨某高校 选课总是激动人心的一件大事 但是明明与同学一起进的系统 他就能顺利选课 而我却被强退出来 无数辛酸让我知道了一些道理 写下这篇文章给学弟学妹们作为参考 原理 问 为什么大多数学校教务系统选课时都会卡 答 学校教务系统平
  • 热敏电阻测温

    热敏电阻器主要分为 PTC 和 NTC 正温度系数热敏电阻器 PTC 在温度越高时电阻值越大 负温度系数热敏电阻器 NTC 在温度越高时电阻值越低 它们同属于半导体器件 测温的热敏电阻一般为NTC 其主要参数有以下几个 标称阻值 标称阻值是
  • 期货有哪些(正规期货公司排名)

    期货有哪些 期货暂时重要分为两大版块 辨别是商品期货和金融期货 与此同声这两大版块又不妨辨别细化出各别的品种 商品期货又可细分为非金属商品 动力商品 农产物等 金融期货重要指保守的金融商品或东西 如一手一足 内债 税率 汇率等 商品期货农产
  • 58同城面经

    文章目录 58一面 58二面 58同城通过了技术面试 但迟迟没有hr面 可能表现的不是很好 58一面 自我介绍 数据结构大概有哪些分类 关于项目 为什么会考虑做商城项目 商城首页的优化 操作系统为什么会有线程这个操作吗 Java创建线程的方
  • Golang基础 流程控制 循环控制

    循环控制 01 基础循环 for 02 键值循环 for range 参考资料 循环控制通常用于程序中需要重复执行的逻辑模块 循环结构通常由循环变量 循环终止条件和循环体三个部分构成 01 基础循环 for Golang 中所有的循环控制都
  • PCL 最小点数约束的改进半径滤波(C++详细过程版)

    目录 一 概述 1 不足 2 改进 二 代码实现 三 结果展示 一 概述 1 不足 传统半径滤波算法在点云数据量巨大的情况下 算法效率会大幅度降低 而对于稠密点云数据 一个影响效率的重要因素就是搜索半径的大小 当搜索半径较大时 需要计算邻域
  • @vue/cli 创建项目报Cannot find module ‘inquirer‘错

    解决 这可能是因为cli版本问题 1 第一步 2 第二步 npm uninstall g vue cli 3 第三步 npm install g vue cli
  • 由PyRetri浅谈基于深度学习的图像检索

    前言 最近发现face 开源了一个图像检索和行人重识别的基于深度学习的软件包 最近一段时间也一直在接触图像检索相关的东西 故借此机会 对里面涉及的一些常用的方法模块进行一个简单的介绍总结 便于日后回顾 PyRetri是什么 PyRetri是
  • 如何查看linux服务器字符集,Linux字符集查看与设置

    查看字符集 Linux 中字符集在系统中的体现是一个环境变量 以 CentOS 6 5 为例 查看当前终端使用的字符集的方式有 1 root jerry echo LANG zh CN GB18030 2 root jerry env gr
  • 对 React Hook的闭包陷阱的理解,有哪些解决方案?

    hooks中 奇怪 其实符合逻辑 的 闭包陷阱 的场景 同时 在许多 react hooks 的文章里 也能看到 useRef 的身影 那么为什么使用 useRef 又能摆脱 这个 闭包陷阱 搞清楚这些问题 将能较大的提升对 react h
  • vue 全局组件注册_如何注册vue3全局组件

    vue 全局组件注册 With the new versions of Vue3 out now it s useful to start learning how the new updates will change the way w
  • unity playerprefs android,Unity持久化存储之PlayerPrefs的使用

    一 PlayerPrefs类支持3中数据类型的保存和读取 浮点型 整形 和字符串型 分别对应的函数为 php SetInt 保存整型数据 GetInt 读取整形数据 SetFloat 保存浮点型数据 GetFlost 读取浮点型数据 Set
  • pygame之五子棋的实现

    先上代码 调用pygame库 import pygame import sys 调用常用关键字常量 from pygame locals import QUIT KEYDOWN import numpy as np 初始化pygame py
  • laravel-vue后端返回数据的字符串中(<br/> \n)换行无效

    laravel 做后端 vue做前端 后端返回数据的字符串中含有 br 或 n r n 等换行符 在前端页面无法正常渲染出换行效果 尝试用str replace方法无效 最终找到解决办法 解决办法 给包含换行符的字符串元素增加css whi
  • 【STM32学习】——串口通信协议&STM32-USART外设&数据帧/输入数据策略/波特率发生器&串口发送/接受实操

    文章目录 前言 一 串口通信 1 通信接口 2 串口通信 1 串口简介 2 串口硬件电路 3 串口软件部分 二 STM32的USART外设 1 USART简介 2 图示详解 三 细节问题 1 数据帧 2 输入数据策略 1 起始位侦测 2 数