STM32F429入门(十七):USART/UART

一、理解通信

在计算机设备与设备之间或集成电路之间常常需要进行数据传输,所以需要通讯。

(一)串行通讯与并行通讯

按数据传送的方式,通讯可分为串行通讯并行通讯串行通讯是指设备之间通过少量数据信号线(一般是 8 根以下),地线以及控制信号线,按数据位形式一位一位地传输数据的通讯方式。并行通讯一般是指使用 8、16、32 及 64 根或更多的数据线进行传输的通讯方式,并行通讯就像多个车道的公路,可以同时传输多个数据位的数据

 很明显,因为一次可传输多个数据位的数据 ,在数据传输速率相同的情况下,并行通 讯传输的数据量要大得多,而串行通讯则可以节省数据线的硬件成本(特别是远距离时)以 及 PCB 的布线面积。不过由于并行传输对同步要求较高,且随着通讯速率的提高,信号干扰的问题会显著影响通讯性能,现在随着技术的发展,越来越多的应用场合采用高速率的串行差分传输。

(二)按数据通信方向:全双工、半双工和单工

(三)按数据的同步方式:同步与异步

区分同步与异步,关键在于它有没有存在时钟信号。如果有时钟信号,则为同步。在同步通讯中,收发设备双方会使用一根信号线表示时钟信号,在时钟信号的驱动下双方进行协调,同步数据,通讯中通常双方会统一规定在时钟信号的上升沿或下降沿对数据线进行采样。

同步:

 异步:

在异步通讯中不使用时钟信号进行数据同步,它们直接在数据信号中穿插一些同步用 的信号位,或者把主体数据进行打包,以数据帧的格式传输数据,某些通讯中还需要双方约定数据的传输速率,以便更好地同步。

同步通讯中,数据信号所传输的内容绝大部分就是有效数据,而异步通讯中会包含有帧的各种标识符,所以同步通讯的效率更高,但是同步通讯双方的时钟允许误差较小, 而异步通讯双方的时钟允许误差较大

(四)通信的速率

在确保稳定性的前提下,我们会加大通信的速率。

Bitrate——比特率:每秒钟传输的二进制位数,单位为比特每秒(bit/s)。

Baudrate——波特率:表示每秒钟传输的码元个数。

而码元是通讯信号调制的概念,通讯中常用 时间间隔相同的符号来表示一个二进制数字,这样的信号称为码元。如常见的通讯传输中, 用 0V 表示数字 0,5V 表示数字 1,那么一个码元可以表示两种状态 0 和 1,所以一个码元等于一个二进制比特位,此时波特率的大小与比特率一致;如果在通讯传输中,有 0V、 2V、4V 以及 6V 分别表示二进制数 00、01、10、11,那么每个码元可以表示四种状态, 即两个二进制比特位,所以码元数是二进制比特位数的一半,这个时候的波特率为比特率 的一半。

二、串口

(一)串口通信协议简介

物理层:规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输。其实就是硬件部分。

协议层:协议层主要规定通讯逻辑,统一收发双方的数据打包、解包标准。其实就是软件部分。

串口的标准有:

  • RS232标准

  • USB转串口

  • 原生的串口到串口

  • ...

1.物理层

(二)RS232标准串口通讯结构图

 

  • RS232标准主要用于工业设备直接通信,抗干扰,长距离。

  • 从控制器出来都是TTL电平。

  • 电平转换芯片一般有MAX3232、SP3232

在上面的通讯方式中,两个通讯设备的“DB9 接口”之间通过串口信号线建立起连接, 串口信号线中使用“RS-232 标准”传输数据信号。由于 RS-232 电平标准的信号不能直接 被控制器直接识别,所以这些信号会经过一个“电平转换芯片”转换成控制器能识别的 “TTL 校准”的电平信号,才能实现通讯。

为什么需要一个电平转换芯片,是因为他们之间的电平转换不一样:

 

 

(三)RS-232信号线

在旧式的台式计算机中一般会有 RS-232 标准的 COM 口(也称 DB9 接口),在最初的应用中,RS-232 串口标准常用于计算机、路由与调制调解器(MODEN,俗称 “猫”)之间的通讯 ,在这种通讯系统中,设备被分为数据终端设备 DTE(计算机、路由)和 数据通讯设备 DCE(调制调解器)。

 

 

 当两个设备进行连接时,需要使用“直通型”的串口线进行连接:

串口线中的 RTS、CTS、DSR、DTR 及 DCD 信号,使用逻辑 1 表示信号有效,逻辑 0 表示信号无效。例如,当计算机端控制 DTR 信号线表示为逻辑 1 时,它是为了告知远端的 调制调解器,本机已准备好接收数据,0 则表示还没准备就绪。 在目前的其它工业控制使用的串口通讯中,一般只使用 RXD、TXD 以及 GND 三条信 号线,直接传输数据信号。而 RTS、CTS、DSR、DTR 及 DCD 信号都被裁剪掉了。

(四)USB转串口通讯结构图

  • USB转串口主要用于设备跟电脑通信

  • 电平转换芯片一般有CH340、PL2303、CP2102、FT232(稳定)

  • 使用的时候电脑端需要安装电平转换芯片的驱动

(五)原生的串口到串口

  • 原生的串口通信主要是控制器跟串口的设备或者传感器通信。不需要经过电平转换芯片来转换电平,直接就用TTL电平通信。

  • 使用这个的有:GPS模块、GSM模块、串口转WIFI模块、HC04蓝牙模块

2.协议层

数据包的基本组成

串口通讯的数据包由发送设备通过自身的 TXD 接口传输到接收设备的 RXD 接口。在 串口通讯的协议层中,规定了数据包的内容,它由启始位、主体数据、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据。

(一)波特率

我们主要学习的是串口异步通讯,异步通讯中由于没有时钟信号,所以两个通讯设备之间需要约定好波特率,即每个码元的长度,以便对信号进行解码,如上图中用虚线分开的每一格就是代表一个码元。常见有4800、9600、115200等。

(二)通讯的起始和停止信号

串口通讯的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由一 个逻辑 0 的数据位表示,而数据包的停止信号可由 0.5、1、1.5 或 2 个逻辑 1 的数据位表示, 只要双方约定一致即可。

(三)有效数据

在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度常被约定为 5、6、7 或 8 位长

(四)数据校验

在有效数据之后,有一个可选的数据校验位。由于数据通信相对更容易受到外部干扰 导致传输数据出现偏差,可以在传输过程加上校验位来解决这个问题。校验方法有奇校验 (odd)、偶校验(even)、0 校验(space)、1 校验(mark)以及无校验(noparity)

  • 奇校验

    有效数据和校验位中“1”的个数为奇数。

    比如一个 8 位长的有效数据 为:01101001,此时总共有 4 个“1”,为达到奇校验效果,校验位为“1”,最后传输的数据将是 8 位的有效数据加上 1 位的校验位总共 9 位

  • 偶校验

    要求帧数据和校验位中“1”的个数为偶数。

    比如 数据帧:11001010,此时数据帧“1”的个数为 4 个,所以偶校验位为“0”。

  • 0 校验

    是不管有效数据中的内容是什么,校验位总为“0”。

  • 1校验

    是校验位总为 “1”。

  • 无校验位

    数据包中不包含校验位。

三、STM32串口功能详解

 

我们将功能图分为四个部分,分别为1-引脚,2-数据寄存器,3-控制器,4-波特率

(一)第一部分——引脚

TX:数据发送。

RX:数据接收。

SW_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚。

nRTS:请求以发送(Request To Send),n 表示低电平有效。如果使能 RTS 流控制,当 USART 接收器准备好接收新数据时就会将 nRTS 变成低电平;当接收寄存器已满时, nRTS 将被设置为高电平。该引脚只适用于硬件流控制。

nCTS:清除以发送(Clear To Send),n 表示低电平有效。如果使能 CTS 流控制,发送 器在发送下一帧数据之前会检测 nCTS 引脚,如果为低电平,表示可以发送数据,如果为高电平则在发送完当前数据帧之后停止发送。该引脚只适用于硬件流控制。

SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式

UART 只是异步传输功能,所以没有 SCLK、nCTS 和 nRTS 功能引脚。具体引脚的详细功能可去数据手册上了解。

(二)第二部分——数据寄存器(USART_DR)

9位有效,包含一个发送数据寄存器TDR和一个接收数据寄存器RDR。一个地址对应了两个物理内存。

USART_DR 实际是包含了两个寄存器,一个专门用于发送的可写 TDR,一个专门用于接收的可读 RDR。当进行发送操作时,往 USART_DR 写入数据会自动存储在 TDR 内;当进行读取操作时,向 USART_DR 读取数据会自动提取 RDR 数据。

TDR 和 RDR 都是介于系统总线和移位寄存器之间。串行通信是一个位一个位传输的, 发送时把 TDR 内容转移到发送移位寄存器,然后把移位寄存器数据每一位发送出去,接收 时把接收到的每一位顺序保存在接收移位寄存器内然后才转移到 RDR

USART 支持 DMA 传输,可以实现高速数据传输。

 (三)控制器

 

  • 发送器

当 USART_CR1 寄存器的发送使能位 TE 置 1 时,启动数据发送,发送移位寄存器的 数据会在 TX 引脚输出,如果是同步模式 SCLK 也输出时钟信号。

一个字符帧发送需要三个部分:起始位+数据帧+停止位。起始位是一个位周期的低电平,位周期就是每一位占用的时间;数据帧就是我们要发送的 8 位或 9 位数据,数据是从 最低位开始传输的;停止位是一定时间周期的高电平

停止位时间长短是可以通过 USART 控制寄存器 2(USART_CR2)的 STOP[1:0]位控制, 可选 0.5 个、1 个、1.5 个和 2 个停止位。默认使用 1 个停止位。2 个停止位适用于正常 USART 模式、单线模式和调制解调器模式。0.5 个和 1.5 个停止位用于智能卡模式。

当选择 8 位字长,使用 1 个停止位时,如图所示:

当发送使能位 TE 置 1 之后,发送器开始会先发送一个空闲帧(一个数据帧长度的高电平),接下来就可以往 USART_DR 寄存器写入要发送的数据。在写入最后一个数据后,需要等待 USART 状态寄存器(USART_SR)的 TC 位为 1,表示数据传输完成,如果 USART_CR1 寄存器的 TCIE 位置 1,将产生中断。

在发送数据时,有几个重要的标志位如下:

  • 接收器

如果将 USART_CR1 寄存器的 RE 位置 1,使能 USART 接收,使得接收器在 RX 线开 始搜索起始位。在确定到起始位后就根据 RX 线电平状态把数据存放在接收移位寄存器内。接收完成后就把接收移位寄存器数据移到 RDR 内,并把 USART_SR 寄存器的 RXNE 位置 1,同时如果 USART_CR2 寄存器的 RXNEIE 置 1 的话可以产生中断。

(四)波特率——每秒钟要发送多少数据

为得到一个信号真实情况,需要用一个比这个信号频率高的采样信号去检测,称为过采样,这个采样信号的频率大小决定最后得到源信号准确度,一般频率越高得到的准确度越高,但为了得到越高频率采样信号越也困难,运算和功耗等等也会增加,所以一般选择合适就好。

接收器可配置为不同过采样技术,以实现从噪声中提取有效的数据。USART_CR1 寄 存器的 OVER8 位用来选择不同的采样采样方法,如果 OVER8 位设置为 1 采用 8 倍过采样, 即用 8 个采样信号采样一位数据;如果 OVER8 位设置为 0 采用 16 倍过采样,即用 16 个采样信号采样一位数据

USART 的起始位检测需要用到特定序列。如果在 RX 线识别到该特定序列就认为是检测到了起始位。起始位检测对使用 16 倍或 8 倍过采样的序列都是一样的。该特定序列为: 1110X0X0X0000,其中 X 表示电平任意,1 或 0 皆可。

波特率指数据信号对载波的调制速率,它用单位时间内载波调制状态改变次数来表示, 单位为波特。比特率指单位时间内传输的比特数,单位 bit/s(bps)。

USART 的发送器和接收器使用相同的波特率

其中,fPLCK为 USART 时钟,有90M与45M。

OVER8 为 USART_CR1 寄存器的 OVER8 位对应的值,USARTDIV 是一个存放在波特率寄存器(USART_BRR)的一个无符号定点数。 其中 DIV_Mantissa[11:0]位定义 USARTDIV 的整数部分,DIV_Fraction[3:0]位定义 USARTDIV 的小数部分,DIV_Fraction[3]位只有在 OVER8 位为 0 时有效,否则必须清零。我们普遍使用OVER8为0的情况,比如我们的波特率115200:

这个寄存器只有四位存放小数点后的数,所以只有2的四次方等于16。最高精度为1/16。当波特率为115200bps以及OVER8=0,选择USART1,则fPLCK=90Mhz。由以上条件可以算得USARTDIV=48.825125,可以算的DIV_Fraction=0xD,DIV_Mantissa=0x30,即应 该设置 USART_BRR 的值为 0x30D。所以我们将0x30D传入寄存器USART_BRR中。由 USART_BRR 的值为 0x30D,可得 DIV_Fraction=13,DIV_Mantissa=48,所以 USARTDIV=48+16*0.13=48.8125,所以实际波特率为:115237;这个值跟我们的目标波特 率误差为 0.03%,这么小的误差在正常通信的允许范围内。

四、中断控制

由以上分析,我们可以得到如下的总结:

中断事件事件标志使能控制位
发送数据寄存器为空TXETXEIE
CTS标志CTSCTSIE
发送完成TCTCIE
准备好读取接收到的数据RXNERXNEIE
检测到上溢错误ORERXNEIE
检测到空闲线路IDLEIDLEIE
奇偶校验错误PEPEIE
断路标志LBDLBDIE
多缓冲通信中的噪声标志、上溢错误和帧错误NF/ORE/FEEIE

 五、USART初始化结构体

typedef struct
{
 	uint32_t USART_BaudRate; 				//波特率 BRR
 	uint16_t USART_WordLength; 				//字长 CR1_M
 	uint16_t USART_StopBits;				//停止位 CR2_STOP
 	uint16_t USART_Parity; 					//校验控制 CR1_PCE、CR1_PS
 	uint16_t USART_Mode; 					//模式选择CR1_TE、CR1_RE					
 	uint16_t USART_HardwareFlowControl;		// 硬件流选择 CR3_CTSE、CR3_RTSE
} USART_InitTypeDef;
  • USART_BaudRate:波特率设置。一般设置为 2400、9600、19200、115200。标准库函数会根据设定值计算得到 USARTDIV 值,并 设置 USART_BRR 寄存器值。

  • USART_WordLength:数据帧字长,可选 8 位或 9 位。它设定 USART_CR1 寄存 器的 M 位的值。如果没有使能奇偶校验控制,一般使用 8 数据位;如果使能了奇 偶校验则一般设置为 9 数据位。

  • USART_StopBits:停止位设置,可选 0.5 个、1 个、1.5 个和 2 个停止位,它设定 USART_CR2 寄存器的 STOP[1:0]位的值,一般我们选择 1 个停止位。

  • USART_Parity :奇偶校验控制选择 , 可 选 USART_Parity_No( 无校验 ) 、 USART_Parity_Even( 偶校验 ) 以 及 USART_Parity_Odd( 奇 校 验 ) , 它 设 定 USART_CR1 寄存器的 PCE 位和 PS 位的值。

  • USART_Mode:USART 模式选择,有 USART_Mode_Rx 和 USART_Mode_Tx, 允许使用逻辑或运算选择两个,它设定 USART_CR1 寄存器的 RE 位和 TE 位。

  • USART_HardwareFlowControl:硬件流控制选择,只有在硬件流控制模式才有效, 可选有⑴使能 RTS、⑵使能 CTS、⑶同时使能 RTS 和 CTS、⑷不使能硬件流。

当使用同步模式时需要配置 SCLK 引脚输出脉冲的属性,标准库使用一个时钟初始化结构体 USART_ClockInitTypeDef 来设置,因此该结构体内容也只有在同步模式才需要设 置。

typedef struct
{
	 uint16_t USART_Clock; 		// 同步时钟 CR2_CLKEN
 	 uint16_t USART_CPOL; 		// 极性 CR2_CPOL
 	 uint16_t USART_CPHA; 		// 相位 CR2_CPHA
	 uint16_t USART_LastBit; 	//最后一个位的时钟脉冲 CR2_LBC
} USART_ClockInitTypeDef;
  • USART_Clock:同步模式下 SCLK 引脚上时钟输出使能控制,可选禁止时钟输出 (USART_Clock_Disable)或开启时钟输出(USART_Clock_Enable);如果使用同步模 式发送,一般都需要开启时钟。它设定 USART_CR2 寄存器的 CLKEN 位的值。

  • USART_CPOL:同步模式下 SCLK 引脚上输出时钟极性设置,可设置在空闲时 SCLK 引脚为低电平(USART_CPOL_Low)或高电平(USART_CPOL_High)。它设定 USART_CR2 寄存器的 CPOL 位的值。

  • USART_CPHA:同步模式下 SCLK 引脚上输出时钟相位设置,可设置在时钟第一个变化沿捕获数据(USART_CPHA_1Edge)或在时钟第二个变化沿捕获数据。它设定 USART_CR2 寄存器的 CPHA 位的值。USART_CPHA 与 USART_CPOL 配合 使用可以获得多种模式时钟关系。(上升沿或者是下降沿)

  • USART_LastBit:选择在发送最后一个数据位的时候时钟脉冲是否在 SCLK 引脚输 出 ,可以是不输出脉冲 (USART_LastBit_Disable) 、输出脉冲 (USART_LastBit_Enable)。它设定 USART_CR2 寄存器的 LBCL 位的值。

六、使用串口编程时所需要的固件库函数

(1)配置GPIO为具体的第二功能(复用功能需要指定)

void GPIO_PinAFConfig (GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF)

(2)中断配置函数

void USART_ITConfig (USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState)

(3)串口使能函数

void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState)

(4)数据发送函数

void USART_SendData (USART_TypeDef* USARTx, uint16_t Data)

(5)数据接收函数

uint16_t USART_ReceiveData(USART_TypeDef* USARTx)

(6)中断状态位获取函数

ITStatus USART_GetITStatus (USART_TypeDef* USARTx, uint16_t USART_IT)

七、实验

实验1:编写一个程序实现开发板与电脑通信,在开发板上电时通过USART发送一串字符串给电脑,然后开发板进入中断接受等待状态,如果电脑有发送数据过来,开发板就会产生中断,我们在中断服务函数接收数据,并且马上把数据返回发送给电脑。

1-初始化串口需要用到的GPIO,GPIO_InitTypeDef, GPIO_PinAFConfig();

2-初始化串口,USART_InitTypeDef

3-中断配置

4-使能串口

5-编写发送和接收函数

6-编写中断服务函数

实现1到3步的程序如下:

//配置嵌套向量中断控制器NVIC
static void NVIC_Configuration(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
  
  /* 嵌套向量中断控制器组选择 */
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  
  /* 配置USART为中断源 */
  NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
  /* 抢断优先级为1 */
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  /* 子优先级为1 */
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  /* 使能中断 */
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  /* 初始化配置NVIC */
  NVIC_Init(&NVIC_InitStructure);
}

//配置串口
void Debug_USART_Config(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  USART_InitTypeDef USART_InitStructure;
		
  RCC_AHB1PeriphClockCmd(DEBUG_USART_RX_GPIO_CLK|DEBUG_USART_TX_GPIO_CLK,ENABLE);

  /* 使能 USART 时钟 */
  RCC_APB2PeriphClockCmd(DEBUG_USART_CLK, ENABLE);
  
  /* GPIO初始化 */
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  
  /* 配置Tx引脚为复用功能  */
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_PIN  ;  
  GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);

  /* 配置Rx引脚为复用功能 */
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
  GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_PIN;
  GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
  
 /* 连接 PXx 到 USARTx_Tx*/
  GPIO_PinAFConfig(DEBUG_USART_RX_GPIO_PORT,DEBUG_USART_RX_SOURCE,DEBUG_USART_RX_AF);

  /*  连接 PXx 到 USARTx__Rx*/
  GPIO_PinAFConfig(DEBUG_USART_TX_GPIO_PORT,DEBUG_USART_TX_SOURCE,DEBUG_USART_TX_AF);
  
  /* 配置串DEBUG_USART 模式 */
  /* 波特率设置:DEBUG_USART_BAUDRATE */
  USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
  /* 字长(数据位+校验位):8 */
  USART_InitStructure.USART_WordLength = USART_WordLength_8b;
  /* 停止位:1个停止位 */
  USART_InitStructure.USART_StopBits = USART_StopBits_1;
  /* 校验位选择:不使用校验 */
  USART_InitStructure.USART_Parity = USART_Parity_No;
  /* 硬件流控制:不使用硬件流 */
  USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
  /* USART模式控制:同时使能接收和发送 */
  USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
  /* 完成USART初始化配置 */
  USART_Init(DEBUG_USART, &USART_InitStructure); 
	
  /* 嵌套向量中断控制器NVIC配置 */
	NVIC_Configuration();
  
	/* 使能串口接收中断 */
	USART_ITConfig(DEBUG_USART, USART_IT_RXNE, ENABLE);
	
  /* 使能串口 */
  USART_Cmd(DEBUG_USART, ENABLE);
}

接下来是编写发送8位数据、16位数据以及字符串:

//发送8位数据
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
	/* 发送一个字节数据到USART */
	USART_SendData(pUSARTx,ch);
		
	/* 等待发送数据寄存器为空,当寄存器中值为空时,TXE=1,表示传输完成 */
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
}

//发送16位数据
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch)
{
	uint8_t temp_h, temp_l;
	
	/* 取出高八位 */
	temp_h = (ch&0XFF00)>>8;
	/* 取出低八位 */
	temp_l = ch&0XFF;
	
	/* 发送高八位 */
	USART_SendData(pUSARTx,temp_h);	
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
	
	/* 发送低八位 */
	USART_SendData(pUSARTx,temp_l);	
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
}

//发送一个字符串
void Usart_SendString( USART_TypeDef * pUSARTx, char *str)
{
	unsigned int k=0;
  do 
  {
      Usart_SendByte( pUSARTx, *(str + k) );
      k++;
  } while(*(str + k)!='\0');
  
  /* 等待发送完成 */
	//单字节检查TXE 多字节检查TC
  while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET)
  {}
}

由于每次调用以上函数都十分麻烦,不如使用printf函数,而正常printf函数是在软件上实现打印的,而我们只需要在fputc中重定向它到串口,之后就可以直接输出了!getchar函数同理。

//重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
		/* 发送一个字节数据到串口 */
		USART_SendData(DEBUG_USART, (uint8_t) ch);
		
		/* 等待发送完毕 */
		while (USART_GetFlagStatus(DEBUG_USART, USART_FLAG_TXE) == RESET);		
	
		return (ch);
}

//重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
		/* 等待串口输入数据 */
		while (USART_GetFlagStatus(DEBUG_USART, USART_FLAG_RXNE) == RESET);

		return (int)USART_ReceiveData(DEBUG_USART);
}

编写完以上程序后,我们需要在中断添加:

void DEBUG_USART_IRQHandler(void)
{
  uint8_t ucTemp;
	//当串口接收到数据时,RXNE=1
	if(USART_GetITStatus(DEBUG_USART,USART_IT_RXNE)!=RESET)
	{		
		ucTemp = USART_ReceiveData( DEBUG_USART );
    USART_SendData(DEBUG_USART,ucTemp);    
	}	 
}	

最后实现的主函数为:

int main(void)
{
		LED_GPIO_Config();
	
		Debug_USART_Config();
	
		while(1)
		{
		}
}

实验2:电脑给单片机发命令,用于控制开发板上的RGB灯。

当我们想要实现以上功能之前,我们需要把中断关掉,就是屏蔽函数中的这两句:

 

之后将中断中的接收处理函数屏蔽。

之后在主函数中:

int main(void)
{
		char ch;
		
		LED_GPIO_Config();
	
		Debug_USART_Config();
	
		Show_Message();
	
		while(1)
		{
			ch = getchar();
			
			printf("Your number is %c\n",ch);
			
			switch(ch)
			{
			case '1':
        LED_RED;
      break;
      case '2':
        LED_GREEN;
      break;
      case '3':
        LED_BLUE;
      break;
      case '4':
        LED_YELLOW;
      break;
      case '5':
        LED_PURPLE;
      break;
      case '6':
        LED_CYAN;
      break;
      case '7':
        LED_WHITE;
      break;
      case '8':
        LED_RGBOFF;
      break;
      default:
        /* 如果不是指定指令字符,打印提示信息 */
        Show_Message();
        break;     
			}
				
		}
}

 就可以实现以上功能。终于把USART学完啦!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郑烯烃快去学习

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值