stm32—串口 ✔

 1. 串口

什么是串口?

        UART:Universal Asynchronous Receiver / Transmitter   

                通用异步收发器

        USART:Universal Synchronous Asynchronous Receiver / Transmitter

                通用同步异步收发器

串口是单片机中属于最常见,最简单的串行数据传输协议

串口是用来实现 模块与模块 之间的通信问题

通信?

        (1) 物理媒介(硬件层面)

        (2) 协议(软件层面)


模块通信:上位机            下位机

        模块通信一般来说,有多方(多个模块),人为把这些通信模块分为"上位机"  "下位机"

        上位机:把处理性能强的机子称为上位机,数据大部分处理都在上位机完成

        下位机:把数据采集的终端,处理性能一般,功能单一的机子称为下位机


那什么是同步?什么是异步?

        通信双方有没有共同的时钟线用来作为同步时钟使用

        如果有那么就是同步通信,如果没有那么就是异步通信

时钟信号可以作为通信时的同步信号,比如:

  1. 在时钟信号为低跳变的时候,改变信号 ---> 发送方发送数据
  2. 在时钟信号为高跳变的时候,采样信号 ---> 接收方接收数据

举个例子:

        同步就相当于A叫B吃饭,B听到了就和A一起去吃饭,但是如果没有听到的话,A就不停的叫,直到B告诉A听到了,并和A一起去吃饭。此乃一对好基友

        异步就是A叫B吃饭,叫完B一声之后就不管了,不管B有没有听到,也不管B到底去不去,A自己就去吃饭了,有点像心机Boy

        所以要是你想请我吃饭,你就用同步的方式,要是我请你吃饭,我就用异步的方式

总结:

        同步:发送方发送数据之后,等接收方发回响应以后,才发下一个数据包的通讯方式

        异步:发送方发送数据之后,不等接收方发回响应,接着发送下一个数据包的通讯方式

串口:串行数据传输协议

        只需要两根数据线,就可以实现全双工串行通信

两根数据线分别为:

        Tx:发送数据端,用于向对方发送数据

        Rx:接收数据端,用于从对方那接收数据

            比如两个设备之间要通讯的话:

                设备A                设备B

                   Tx   ---------->   Rx

                   Rx   <----------   Tx

        全双工通信:两个设备的接收端和发送端是相互独立的,互不干扰的,接收数据不会干扰到发送数据,所以两个设备可以同时收/发

        还有其他的通信方式:如单工(设备只能收或者发,只有一根数据线)、半双工(设备有两根数据线,但是不能够同时收/发)

        串行:数据的传输只有一根”电线”,每次就只能传输 1bits 数据,当多个数据发送时,也只能 1bit 1bit 的发送

                比如:发送 0x5C

                        0101 1100

        就是把这个字节的每个 bit 一个 bit 接一个 bit 的发送出去。那么我们发送一个 bit 是怎么发送的呢?比如要发送一个0,我们就只需要将Tx 这根线的电平状态拉低一段时间就可以了,要发送1,就需要将Tx这根线的电平状态拉高一段时间就可以了
 

但是大家想一下,我的Tx这根线,就算什么都不往外发,是不是也会有一个电平状态,那么这个时候对面怎么知道你这个是不是要发送过来的数据呢?
        这个时候我们就需要一个协议了,协议就是双发约定好的传输方式,串口也有协议

2. UART协议

UART  Protocol  串口协议  ---> 链路层

        作用:规定了串口发送和接收数据的方式,必须以 帧(Frame) 为单位


1帧(Frame) = 1 start bit(起始位) + 5~9bits数据位 + 0/1校验位 + (0.5/1/1.5/2)bits停止位

起始位:一个周期的低电平信号

        所以串口的数据线Tx在空闲的时候应该要永远保持高电平

5~9数据位:通信的正文,具体是发送多少bits,由双方协商
        并且
最先发送最低位(LSB),最后传送最高位(MSB)

1个校验位:表示是否需要校验
        0bit  没有校验位
        1bit  有校验

                D0  D1  D2  D3 ...... Dn  x
                奇校验: 要保证前面的数据位加上校验位的1的个数是奇数个

                偶校验: 要保证前面的数据位加上校验位的1的个数是偶数个
        比如:

                9bit发送0x5c (0101 1100)

                奇校验:0 0 1 1 1 0 1 0 x=1

                偶校验:0 0 1 1 1 0 1 0 x=0

0.5~2个停止位:高电平
        具体持续多久高电平,由双方约定
                0.5个周期 / 1个周期 / 1.5个周期 / 2个周期

但是由于UART异步串口,没有时钟进行同步,因此光靠帧格式传输数据还是不能准确收发,因此我们有另外一个东西来确保准确收发

        Baudrate波特率:单位时间内传送二进制数据的位数,单位:bps(位/秒),UART 的传输速率,决定 1 Frame 的传输周期

                常见的波特率有:9600bps(Bits per second) ,115200bps ......

                        ---> 9600bps:每传输一个 bit 花费时间为 1 / 9600s

                        ---> 9600bps:每传输一个 bit 花费时间为 1 / 115200s
        波特率约定了双方的采样周期/频率,所以收发双方必须保持一致

        比特率:比特率来衡量异步串行通信的数据传输速率,即单位时间内传送二进制有效数据的位数,单位用bps表示

        比特率 = 波特率 x 单个调制状态对应的二进制位数

        比特率表示有效数据的传输速率



UART协议: 帧格式 + 波特率

3. 物理层标准

串口有不同的分类:
        TTL level UART : TTL电平串口

                Tx          数据发送端口

                Rx          数据接收端口

                VCC       电源端口(+),给外部模块供电,如果外部模块有电,可以不需要

                GND      接地端口(-),通信双方的GND必须连接在一起,必须共地

        注意:先接GND,再接其他端口,防止接错从而导致模块烧坏

还有常见的分类比如:RS-232、RS-422、RS-485

        RS-485    +   <转换电路>     <===相连===>      TTL
        不同电气标准的串口,引脚的个数也不一样,但是数据线Tx / Rx是一定存在的

        不同电气标准的串口的区别如下:

 

        单端信号是指用一根线传输的信号,一根线没有参考点怎么会有信号呢?

                参考点就是地。也就是说单端信号是在一根导线上传输的与地之间的电平差

        差分信号指的是用两根线传输的信号,传输的是两根信号之间的电平差

 4. STM32F4xx 串口控制器

参考<STM32F4xx参考手册.pdf>678页<USART 框图>

从图来看,串口不仅有Tx和Rx两根线,还有两根用于硬件流控的RTS和CTS线,这两根线存在的意义在于,有时候发送方通过Tx往外发送数据时,如果对方还没有准备好,发送的数据将会被丢弃,所以在硬件上加两根硬件流控的信号 

        RTS:Request To Send 请求发送信号

               终端告诉对方我已经准备好了,你可以向我传输数据了

        CTS:Clear To Send 清除发送信号

                对方告诉终端,我要向你发送数据了
                

        接线情况如下:

                  A                  B

                RTS --------> CTS

                CTS <-------- RTS
        如果采用硬件流控的话,B已经准备好接收数据啦,B往A的CTS传输一个电平(请求发送信号),在A往B发送数据前,先清掉CTS表示我要向你发送数据啦


        当然,并不一定需要使用RTS/CTS

RDR:Receiver Data Register   接收数据寄存器

        对方Tx  ---> Rx -----> 接收移位寄存器 ----> RDR
        CPU从RDR把接收到的数据读走(要及时读走,否则会被下一次接收的数据给覆盖掉)

 

TDR:Transmiterr Data Register   发送数据寄存器

        CPU把要发送的数据写到 TDR ----> 发送移位寄存器 ----> Tx -----> Rx(对方)
        

在图的下方有一个USART_BRR(波特率发生器),主要是用来控制数据的收发速率

另外在图的中部有两个寄存器:

        CR(Control Reigster):控制寄存器,用来控制串口的一些行为

        SR(Status Register):状态寄存器,用来指示串口控制器的一些状态

a. 从读的角度来说

        外部设备将数据往Rx引脚上发送,因为串口协议规定数据是一个bit一个bit 发送的,所以每来一个bit,需要进行位移位或操作后先将其保存在接收移位寄存器中,当数据到齐之后,再将其挪入接收数据寄存器(Receiver Data Register),此时CPU就应该及时将RDR寄存器中接收到的数据及时读走,否则会被下一次发过来的数据给覆盖掉

        那么我们怎么知道数据什么时候到齐该去读取了呢?

                就需要用到SR寄存器中的标志位RXNE了

                        RXNE:Rx data Register Not Empty  接收数据寄存器非空标志

                              如果RXNE被设置,说明RDR中已经存入数据(从对方接收来的), CPU就可以去读取RDR以获取接收到的数据                                        

b. 从发的角度来说

        我们只需将需要发送出去的数据存放至发送数据寄存器(TDR)中,发送控制器将要发送的数据自动填充到发送移位寄存器中去,在设定好的波特率影响下,将数据一个 bit 一个 bit 的通过Tx线发送出去

        需要注意的是只有当一个数据发送完成后,才能接着发送下一个数据

        那么我们怎么知道数据已经发送完了?

                通过以下标志位来识别:

                TXE:Tx data Register Empty 发送数据寄存器为空标志

                       如果TXE被设置(为1),说明TDR中的数据被发送出去了,此时CPU可以往TDR发送数据了

                         但是,并不表示数据已经发送完成,因为TDR的数据可能只是被转移到了发送移位寄存器中

                TC:Transmit Complete  发送完成标志

                          表示发送移位寄存器中的数据,都已经从Tx引脚发送出去了

                          TDR(发送数据寄存器)  ---> 发送移位寄存器 ---> Rx(对方)

                          TC == 1

                                   表示 TXE == 1

                                           并且 发送移位寄存器 也为空

注意:发送数据之前,必须确保TDR寄存器为空(TXE被设置),否则,上一个发送的数据可能没有发送完成,那么就会被覆盖。接收数据需要等 RXNE标志被设置,才能去接收。因此,一般使用串口中断来完成串口数据接收

另外还有一些在CR寄存器中的中断使能位: 

        TXEIE:TXE中断使能位,这个标志位当串口TDR为空时会触发的中断

                如果TXEIE被设置为1,即当TXE=1(发送数据寄存器为空)时,就会产生一个串口中断

                 所以 IE -->Interrupt Enable 中断使能,控制是否产生一个串口中断

       TCIE:发送完成,中断使能位

                如果TCIE 被设置,当串口数据都发送完成(经过Tx引脚),就会触发串口中断

        

        RXNEIE:串口接收数据寄存器不为空  中断使能位

                如果RXNEIE == 1,当RDR寄存器不为空时,就会触发串口中断

        一个串口控制器,只对应一个中断,但是串口的多个标志,都可以引起串口的中断,因此,在你的串口中断处理函数中,要加以区分,以作不同的处理

5. STM32F4xx 串口代码流程

串口配置步骤:

        根据原理图了解GEC_M4串口

        -----> USART的Tx和Rx引脚是GPIO的功能复用而来

GEC_M4 引出的 3 个串口,都可以由跳线帽选择不同的用途

        USART1:

                1-3 和 2-4 作为调试/烧写串口(经过CH340转USB后可以与PC相连)

                3-5 和 4-6 作为P4排针的外接串口使用
                所以,在使用串口的时候,先根据用途,对跳线帽进行短接

                然后再通过程序代码配置

 ===============================================================================
                        ##### How to use this driver #####
 ===============================================================================
    [..]
      (#) Enable peripheral clock using the following functions
          RCC_APB2PeriphClockCmd(RCC_APB2Periph_USARTx, ENABLE) for USART1 and USART6 
          RCC_APB1PeriphClockCmd(RCC_APB1Periph_USARTx, ENABLE) for USART2, USART3, 
          UART4 or UART5.
  
      (#) According to the USART mode, enable the GPIO clocks using 
          RCC_AHB1PeriphClockCmd() function. (The I/O can be TX, RX, CTS, 
          or/and SCLK). 
  
      (#) Peripheral's alternate function: 
        (++) Connect the pin to the desired peripherals' Alternate 
            Function (AF) using GPIO_PinAFConfig() function
        (++) Configure the desired pin in alternate function by:
            GPIO_InitStruct->GPIO_Mode = GPIO_Mode_AF
        (++) Select the type, pull-up/pull-down and output speed via 
            GPIO_PuPd, GPIO_OType and GPIO_Speed members
        (++) Call GPIO_Init() function
          
      (#) Program the Baud Rate, Word Length , Stop Bit, Parity, Hardware 
          flow control and Mode(Receiver/Transmitter) using the USART_Init()
          function.
  
      (#) For synchronous mode, enable the clock and program the polarity,
          phase and last bit using the USART_ClockInit() function.
  
      (#) Enable the NVIC and the corresponding interrupt using the function 
         USART_ITConfig() if you need to use interrupt mode. 
  
      (#) When using the DMA mode 
        (++) Configure the DMA using DMA_Init() function
        (++) Active the needed channel Request using USART_DMACmd() function
   
      (#) Enable the USART using the USART_Cmd() function.
   
      (#) Enable the DMA using the DMA_Cmd() function, when using DMA mode. 

串口编程流程:
(1) 串口 GPIO 配置

        Tx / Rx 引脚都是由GPIO引脚复用的

        a. 使能GPIO分组的时钟

                RCC_AHB1PeriphClockCmd();

        b. 初始化GPIO  

                GPIO_Init();===> AF 模式

        c. 配置GPIO复用功能

                GPIO_PinAFConfig();

        比如:PA9 作为 USART1_TX
                GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);


(2) USART 配置
         a. 使能USART时钟
               RCC_APB2PeriphClockCmd(RCC_APB1Periph_USART1, ENABLE);

                       or

               RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

               根据具体配置哪个串口,不同的串口位于不同的总线上

         b. 初始化配置USART

void USART_Init(USART_TypeDef *USARTx, USART_InitTypeDef *USART_InitStruct);
@USARTx:指定串口编号
    USART1、USART2、USART3...

@USART_InitStruct:指向串口初始化信息结构体
	typedef struct
	{
		uint32_t USART_BaudRate;
		    指定串口的通信波特率,单位为bps:bits per second              
            是一个整数(通信双方必须一样),常见的有9600/115200等
			
        uint16_t USART_WordLength;
			指定数据帧数据位,也就是传输字长
            在STM32中传输字长 = 数据位数 + 校验位数
				USART_WordLength_8b        一般无校验用8bit数据位
                    8bits数据位 + 0bit校验位
                    7bits数据位 + 1bit校验位(奇/偶校验)
				USART_WordLength_9b        有校验用9bit数据位
                    8bits数据位 + 1bit校验位(奇/偶校验)
                    9bits数据位 + 0bit校验位
			
        uint16_t USART_StopBits;
			指定停止位长度
				USART_StopBits_0_5
				USART_StopBits_1	1个周期的停止位
				USART_StopBits_1_5
				USART_StopBits_2
			
        uint16_t USART_Parity;
			指定校验方式
				USART_Parity_No		 不要校验
				USART_Parity_Even    偶校验
				USART_Parity_Odd   	 奇校验
		    
        uint16_t USART_Mode;
			指定串口模式
				USART_Mode_Rx  	接收模式 只接收数据
				USART_Mode_Tx  	发送模式 只发送数据
				USART_Mode_Rx | USART_Mode_Tx 收发模式(全双工)
			 
        uint16_t USART_HardwareFlowControl; 
			指定硬件控制流
				USART_HardwareFlowControl_None  不要硬件流控
				USART_HardwareFlowControl_RTS	请求发送
                    当你准备好接收数据	接收流控制
				USART_HardwareFlowControl_CTS	CTS
                    当你要发送数据时通知对方		发送流控制
				USART_HardwareFlowControl_RTS_CTS
                    发送和接收都用流控制

    } USART_InitTypeDef;

(3) 中断配置

        a. 中断源的控制(中断控制位使能)

                产生串口中断的事件或标志有很多,如:

                        TxE ----> 串口中断

                        TC  ----> 串口中断

                        RXNE ----> 串口中断

                        ...

                这些事件需要”中断控制位使能”才能产生中断

                USART_ITConfig:用来设置哪些状态会触发串口中断

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

@USARTx:指定具体的串口编号
	USART1、USART2...

@USART_IT:指定触发中断的标志
       
    USART_IT_RXNE: RDR中存入数据后会触发中断---> CPU可以接收1字节数据   
    USART_IT_CTS
    USART_IT_LBD
    USART_IT_TXE
    USART_IT_TC
    USART_IT_IDLE
    USART_IT_PE
    USART_IT_ERR

@NewState:
    ENALBE   相应的事件产生串口中断
	DISABLE  相应的事件不产生串口中断

        b. NVIC控制

串口中断使能(需要配置NVIC)

配置NVIC:void NVIC_Init(NVIC_InitTypeDef *NVIC_InitStruct);

(4) 使能串口

void USART_Cmd(USART_TypeDef *USARTx, FunctionalState NewState);
			
@USARTx:指定串口编号
	USART1、USART2、USART3...
			
@NewState:
	ENABLE		使能,开启串口
	DISABLE

(5) 串口数据收发
        一般的,在串口通信中,接收部分几乎都是由中断来完成
        那么串口中断服务函数名经查中断向量表可知:

void USART1_IRQHandler(void) { // 一般串口中断服务函数的实现如下

    // 有多个事件(TXE,TC,RXNE...)可以引起串口中断
    // 所以,在串口中断处理函数中一般要判断是什么事件引起的串口中断
    if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) { // RXNE事件产生
        // 去读取数据 USART_ReceiveData
        // 清除中断标志 USART_ClearITPendingBit
    }
}

获取串口中断标志:
FlagStatus USART_GetFlagStatus(USART_TypeDef *USARTx, uint16_t USART_FLAG);
ITStatus USART_GetITStatus(USART_TypeDef *USARTx, uint16_t USART_IT);
		
@USARTx:指定串口,如:USART1...
		
@USART_IT:指定串口事件,如:
	USART_IT_TXE、USART_IT_TC、USART_IT_RXNE...
		
@返回值:
	SET	:1		指定的事件产生
	RESET :0	指定的事件没产生

清除串口中断标志:
void USART_ClearITPendingBit(USART_TypeDef *USARTx, uint16_t USART_IT);
void USART_ClearFlag(USART_TypeDef *USARTx, uint16_t USART_FLAG);

串口接收数据:从指定的串口接收1个字节数据

uint16_t USART_ReceiveData(USART_TypeDef *USARTx);
				
@USARTx:指定串口编号
	USART1、USART2、USART3...
				
返回值:
	从串口接收到的数据

串口发送数据:

void USART_SendData(USART_TypeDef *USARTx, uint16_t Data);
				
@USARTx:指定串口编号
	USART1、USART2、USART3...
				
@Data: 要发送的数据,1个字节
			
注意:
    发送数据之前,必须确保TDR为空(TXE被设置),如果没有,则不能发送下一个字节    
    接收是被动的,我们并不知道什么时候有数据过来需要接收,所以在中断服务函数中接收
而发送是主动的,由我们自己决定什么时候发送数据出去,所以不用中断实现数据发送,
而需要自定义串口发送函数:

// 发送一个字节
void USART1_SendByte(uint16_t data) {
	// 发送
	USART_SendData(USART1, data);

	// 等待发送寄存器为空事件产生
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) != SET);

	// 清除发送寄存器为空事件标志
	USART_ClearFlag(USART1, USART_FLAG_TXE);
}

// 发送一个字符串
void USART1_SendDatas(const char *str) {
    const char *s = str;
	while (*s) {
		USART1_SendByte(*s);
		s++;
	}
}

6. 在stm32中使用 printf 函数

在STM32中可以将 printf 函数重定向到串口:

1. 初始化 USART1
2. 在调用的文件处添加 stdio.h
    同时需要勾选 <Options> ---> <Target> ---> <USE MicroLIB>

3. printf 实际上是调用 fputc 来实现的
    所以我们需要重新定义 fputc 这个函数

int fputc(int c, FILE *stream) {

	USART_SendData(USART1, c & 0xFF);

	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    
    // 清除发送寄存器为空事件标志
	USART_ClearFlag(USART1, USART_FLAG_TXE);
   
	return 0;
}

注意:

        (1) 程序的运行速度很快,如果串口调试助手打开太慢了,程序前面的打印数据不会显示在串口调试助手上。从打开串口调试助手的那一刻,此时程序运行到了某个地方,在这个地方的后面打印函数开始显示在串口调试助手上
        解决这个问题的方法是:加一个延时函数,让我们有足够的时间打开串口调试助手

        (2) windows 中换行符为 \r\n

7. 配置串口1 (USART1 跳线帽接1-3和2-4),实现与PC机双向通信

usart.h

#ifndef __USART_H__
#define __USART_H__

#include "stm32f4xx.h"
#include "stdio.h"

/*
	串口1的初始化函数
		@baudrate:串口传输的波特率
*/
void USART1_Init(int baudrate);

// 发送一个字节
void USART1_SendByte(uint16_t data);

// 发送一个字符串
void USART1_SendDatas(const char *str);

// 将printf函数重定向到串口  注意:windows 中换行符为 \r\n
int fputc(int c, FILE *stream);

#endif

usart.c 

#include "usart.h"

// 串口初始化 baudrate:波特率
void USART1_Init(int baudrate) {
	
	// (1) 串口 GPIO 配置
	// a. 使能 GPIO 分组时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
	
	// b. 初始化 GPIO
	// PA9 Tx   PA10 Rx
	GPIO_InitTypeDef g;

	g.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
	g.GPIO_Mode = GPIO_Mode_AF;
	g.GPIO_Speed = GPIO_Speed_2MHz;
	g.GPIO_OType = GPIO_OType_PP; // 输出推挽
	g.GPIO_PuPd = GPIO_PuPd_NOPULL;
	
	GPIO_Init(GPIOA, &g);

	// c. 配置GPIO复用功能
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
	
	// (2) USART配置
	// a. 使能 USART 分组时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	
	// b. 初始化配置USART
	USART_InitTypeDef u;

	// 波特率
	u.USART_BaudRate = baudrate;
	// 般无校验8bit数据位
	u.USART_WordLength = USART_WordLength_8b;
	// 指定停止位长度
	u.USART_StopBits = USART_StopBits_1;
	// 指定校验方式 不要校验
	u.USART_Parity = USART_Parity_No;
	// 指定串口模式  全双工
	u.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	// 指定硬件控制流   不要硬件流控
	u.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	
	USART_Init(USART1, &u);
		
	// (3) 中断配置
	
	// a. 中断源的控制(中断控制位使能)
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

	// b. 配置NVIC
	NVIC_InitTypeDef v;

	v.NVIC_IRQChannel = USART1_IRQn;
	v.NVIC_IRQChannelPreemptionPriority = 2;
	v.NVIC_IRQChannelSubPriority = 2;
	v.NVIC_IRQChannelCmd = ENABLE;

	NVIC_Init(&v);
	
	// (4) 使能串口
	USART_Cmd(USART1, ENABLE);
}

// 发送一个字节
void USART1_SendByte(uint16_t data) {
	// 发送
	USART_SendData(USART1, data);

	// 等待发送寄存器为空事件产生
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) != SET);

	// 清除发送寄存器为空事件标志
	USART_ClearFlag(USART1, USART_FLAG_TXE);
}

// 发送一个字符串
void USART1_SendDatas(const char *str) {
    const char *s = str;
	while (*s) {
		USART1_SendByte(*s);
		s++;
	}
}

/*
	串口 1 的中断服务函数
*/
void USART1_IRQHandler(void) {
	
	// 如果产生了接收中断  RXNE 事件产生
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) {
		
		// 写回给PC
		uint8_t RecvData = USART_ReceiveData(USART1);
		USART1_SendByte(RecvData);
		// 清除中断标志
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

// 将printf函数重定向到串口  注意:windows 中换行符为 \r\n
int fputc(int c, FILE *stream) {

	USART_SendData(USART1, c & 0xFF);
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
	
	// 清除发送寄存器为空事件标志
	USART_ClearFlag(USART1, USART_FLAG_TXE);
	
   	return 0;
}
  • 12
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值