【STM32F407学习笔记】串口外设USART


串口通讯是一种设备间非常常用的串行通讯方式,因为它便捷,大部分电子设备都支持该通讯方式。并且它也是一个很好的调试工具,我们可以利用它输出调试信息到电脑屏幕。
本节任务:利用USART外设实现STM32与电脑通信,重定向Printf() 函数
涉及外设:GPIO(复用输入输出) ,RCC (复位和时钟控制),USART(串口)

1. STM32 USART简介

1.1 串口通讯协议简介

  • 通信的方式分类:并行通信,串行通信
    1)并行通信:指数据的各位同时在多根数据线上发送或接收,如图中所示:
    在这里插入图片描述
    并行通讯特点:控制简单,传输速度快;由于传输线多,适用于距离近传输
    2)串行通信:指数据的各位在同一根数据线上逐位发送或接收,如图中所示:
    在这里插入图片描述
    串行通讯特点:控制复杂,传输速度慢;只需要一个线,适用于远距离传输。
  • 串行通信方式分类
    在串行通信中,根据对数据流的分界、定时及同步方法不同,可分为同步串行通信方式和异步串行通信方式。
    1)同步串行通信方式:把多个字符组成一个信息组(信息帧),每帧的开始用同步字符来指示、并且发送和接收的双方必须采用同一时钟,这样接收方就可以用时钟信号来确定每个信息位。同步通信如图所示:
    在这里插入图片描述同步通信:发送端和接收端必须使用统一时钟,它是一种连续传送数据的通信方式,一次通信传送多个字符数据,称为一帧信息。帧格式如图中所示:
    在这里插入图片描述
    同步串行通信帧:是将许多字符组成一个信息帧,这样,字符可以一个接一个的传输,但是在每帧信息的开始要加上同步字符,在没有信息要传输时,要填上空字符,因为同步传输不允许有间隙
    同步串行通信的特点:必须有同步时钟,传输信息量大,传输速度高,但是传输设备复杂,技术要求高。
    2)异步串行通信方式:指通信双方以一个字符(包括特定附加位)作为数据传输单位,且放松放传送字符的间隔时间不一定,具有不规则数据段传送特性的串行数据传输。异步串行通信如图中所示:
    在这里插入图片描述异步通信:指发送和接收端使用各自的时钟,并且它是一种不连续传输的通信方式,一次通信只传一个字符数据,成为字符帧。字符帧之间的间隙可以是任意间隙。帧格式如图中所示:
    在这里插入图片描述异步串行通信帧:是将一个字节数据加上起始位、校验位、停止位,构成的字符帧。由于异步通信没有同步时钟,所以接收端要时刻处于接收状态。
    • 起始位:在没有数据传送时即空闲状态,此时通信线上为逻辑“1”状态(高电平)。当发送端要发送1个字符数据时,首先发送一个逻辑“0”信号(低电平),这个低电平就是帧格式的起始位。
    • 数据位:在起始位之后,发送端发出的就是数据位,数据位的位数没有严格限制5 ~8 位都可以。低位在前,高位在后,由低位到高位逐位传送。
    • 校验位:数据发送完成后,可发送一位用来检验数据在传送过程中是否出错的校验位。(一般使用奇偶校验位)
    • 停止位:字符帧格式的最后部分时停止位,逻辑“1”电平有效,它可以是1/2位,1位或2位。
      异步串行通信特点:不需要时钟同步,通信实现简单,设备开销小。但是传输速率不高。
  • 串行通信数据传送方向
    根据串行数据的传输方向,我们可以将通信分为单工,半双工,全双工
    1)单工:指数据传输仅能沿一个方向,不能实现反向传输。
    2)半双工:指数据传输可以沿两个方向,但需要分时进行传输。
    3)全双工:指数据可以同时进行双向传输。
    在这里插入图片描述
  • 串行通信传输速率
    比特率:每秒钟传送的二进制位数。bps
    波特率:每秒钟调制信号变化的次数。Baud
    串行通信常用波特率表示数据传输速率。
    比特率=波特率 × \times ×单个调制状态对应的二进制位数
    串行通信双方识别位的时间间隔要相同,因此通信双方的波特率必须一致

1.2 STM32 USART简介

STM32具有多个USART外设用于串口通信,USART(通用同步异步收发器)能够灵活的与外部设备进行全双工数据通信。
在这里插入图片描述
此图可以分为四部分:引脚、波特率发生器、USART控制单元、数据交换相关寄存器。
1. USART功能引脚
Tx:发送数据输出引脚
Rx:接收数据输入引脚
SW_Rx:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚。
nRTS:请求以发送(Request To Send),n表示低电平有效。该引脚仅适用于硬件流控制。
nCTS:清除以发送(Clear To Send),n表示低电平有效。该引脚仅适用于硬件流控制。
SCLK:发送时钟输出引脚.这个引脚仅适用于同步模式
2. 波特率发生器
USART的发送器速率控制和接收器速率控制共用一个波特率寄存器(USART_BRR),波特率寄存器里存放的是时钟分频值,它一共是16位,分为整数部分DIV_Mantissa[16:5]和小数部分DIV_Fraction[4:0]两部分。USART通信所需波特率是对相应总线时钟分频,然后一系列计算所得到的。USART波特率计算公式如下:
Tx/Rx波特率 = f c k 8 × ( 2 − OVER8 ) × USARTDIV \text{Tx/Rx波特率}=\frac{f_{ck}}{8\times(2-\text{OVER8})\times\text{USARTDIV}} Tx/Rx波特率=8×(2OVER8)×USARTDIVfck
f c k f_{ck} fck:系统总线时钟。USART1和USART6在APB2总线下,USART2在APB1总线下。
OVER8:是由USART_CR1的第15位设置。
USARTDIV:波特率分频系数,USART_BRR配置得到。USARTDIV的计算公式:
USARTDIV=DIV_Mantissa+(DIV_Fraction/8*(2-OVER8))
3. USART控制单元
整个USART控制单元包括:发送控制器、唤醒单元、接受控制器。我们通过配置寄存器相应位来设置这些控制器的工作模式。
发送控制器:工作在发送模式时,它将按照程序设置的波特率,帧格式将CPU的数据或者DMA总线上的数据一位一位送到Tx引脚。
接收控制器:工作在接收模式时,它将按照程序设置的波特率,帧格式将数据从Rx引脚一位一位的接收外部发来的数据并上传给CPU或DMA
4. 数据寄存器和移位寄存器
图中部分4一共有四个寄存器,发送模式用的“发送数据寄存器(TDR)”和“发送移位寄存器”,接收模式用的“接收数据寄存器(RDR)”和“接收移位寄存器”。其实TDR和RDR都属于数据寄存器(USART_DR)

USART发送过程:

  • 使能发送,即USART_CR1的TE位置1
  • 内部总线的数据的一个字节写入“发送数据寄存器(TDR)”(该操作将清零TXE位(发送数据寄存器非空),其他数据不可以写入)
  • “TDR”中的数据一次性复制进入“发送移位寄存器”(将TXE置位(发送数据寄存器为空),后续数据可以接着写入)
  • “发送移位寄存器”将刚才“TDR”复制的数据一位一位的送到Tx引脚
  • 循环执行上述操作,直到总线将最后一个数据写入“发送数据寄存器*(TDR)”后,等待TC=1。表明最后一帧的传送已完成。

USART接收过程:
Rx引脚有数据输入时

  • 首先使能接收USART_CR1的RE位置1
  • Rx引脚移入数据的最低有效位,到“接收移位寄存器”
  • 当接收移位寄存器8位满时,将数据一次性写入“接收数据寄存器(RDR)”(该操作将RXNE置1,即接收数据寄存器非空,总线可读取)
  • 总线发现RXNE=1时,立即读取数据并将RXNE置0(接收期间每接收一个字节,RXNE都置1)
  • 循环执行上面操作,直到Rx引脚将最后一字节数据传送入”接收数据寄存器(RDR)“后,等待总线读取完成。

2. 硬件连接

Rx ⇒ \Rightarrow Tx
Tx ⇒ \Rightarrow Rx

3. 软件设计

我们需要将PA9和PA10当作USART1的Tx和Rx引脚来使用,即IO不再是通用功能了,而是用到了IO引脚的复用功能。STM32将这种应用叫做“IO引脚复用”,也就是说PA9(Tx)将被配置为复用推挽输出模式,PA10(Rx)将被配置为复用输入模式。

3.1 IO引脚复用功能初始化

  • IO引脚的复用功能
    STM32F4有很多片内外设,这些外设的外部引脚都是由GPIO复用得来的。片内外设的功能引脚不是随意复用的,也就是说片内外设的功能引脚是特定在某个或者多个GPIO引脚上的。例如USART1的Tx引脚就固定在PA9/PB6上,Rx引脚固定在PA10/PB7上。其他外设的复用也类似。

3.2 串口配置步骤

  1. 使能串口时钟:使用外设USART1和USART6,因为它们挂载在APB2总线上,则用函数RCC_APB2PeriphClockCmd() 使能;使用外设USART2~USART5,则用函数RCC_APB1PeriphClockCmd() 使能。

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);// 使能 USART1 时钟
    
  2. 配置串口使用的GPIO

    • 使能GPIO时钟,USART1,使用PA9和PA10,则使能GPIOA的时钟。
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); // 使能 GPIOA 时钟
    
    • 引脚复用功能映射,用函数GPIO_PinAFConfig() 设置引脚复用。
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); // PA9
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); // PA10
    
    • 配置引脚工作模式
      • PA9 ⇒ \Rightarrow Tx(复用推挽输出)
      • PA10 ⇒ \Rightarrow Rx(复用上拉输入)
    	//Tx Rx
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;  //PA9与PA10
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;	//速度100MHz
    	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;  // 推挽复用输出
    	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
    	GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PA9,PA10
    
  3. 配置串口
    通过USART_init() 函数,配置串口1的工作模式等具体参数。

    USART_InitStructure.USART_BaudRate = 115200;// 波特率设置为115200
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;// 字长为8位数据格式
    USART_InitStructure.USART_StopBits = USART_StopBits_1;// 一个停止位
    USART_InitStructure.USART_Parity = USART_Parity_No;// 无奇偶校验位
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;// 无硬件数据流控制
    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	// 收发模式
    USART_Init(USART1, &USART_InitStructure); // 初始化串口1
    
  4. 使能串口
    使用USART_Cmd() 函数,使能配置好的串口

    USART_Cmd(USART1,ENABLE);
    
  5. 重定向printf()函数
    首先我们需要知道printf()是C语言的标准输入输出stdio.h 中的函数,因此需要在工程中包含该头文件,并且在Keil中勾选上Use Micro Lib的选项
    在这里插入图片描述
    然后对printf()函数重定向,而这个函数是基于fputc()来写的,因此只需要对这个函数进行修改

    int fputc(int ch,FILE *f)
    {
    	// 发送数据
    	USART_SendData(USART1,(uint_8t)ch);
    	// 等待发送完毕
    	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);// 查询发送数据寄存器为空标志位,SET了即为发送完成一帧
    	return ch;
    }
    
  6. 串口通信
    然后我们就可以愉快的在工程中使用printf()进行串口输出了。

3.3 完整USART.c实现

下面给出一个完整的usart.c的实现,在这个程序中串口1用到的引脚是PB6和PB7,串口2用到的引脚是PA2和PA3。

#include "usart.h"
	
	/// @brief 初始化串口1(PB6->Tx  PB7->Rx)
	/// @param baud 波特率 
	void USART_init(uint32_t baud)
	{
	    GPIO_InitTypeDef GPIO_InitStructure;
	    USART_InitTypeDef USART_InitStructure;
	    // 使能USART1时钟
	    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
	    // 使能GPIO时钟
	    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
	    // 引脚复用功能
	    GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_USART1);
	    GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_USART1);
	    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
	    GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	    USART_InitStructure.USART_BaudRate = baud;
	    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	    USART_InitStructure.USART_StopBits = USART_StopBits_1;
	    USART_InitStructure.USART_Parity = USART_Parity_No;
	    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	    USART_Init(USART1, &USART_InitStructure);
	    USART_Cmd(USART1, ENABLE);
	}
	
	void USART2_init(void)
	{
	    GPIO_InitTypeDef GPIO_InitStructure;
	    USART_InitTypeDef USART_InitStructure;
	    // 使能USART2时钟
	    RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
	    // 使能GPIO时钟
	    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
	    // 引脚复用功能
	    GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2);
	    GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2);
	    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
	    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
	    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3;
	    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	    GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	    USART_InitStructure.USART_BaudRate = 9600;
	    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
	    USART_InitStructure.USART_StopBits = USART_StopBits_1;
	    USART_InitStructure.USART_Parity = USART_Parity_No;
	    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	    USART_Init(USART2, &USART_InitStructure);
	    USART_Cmd(USART2, ENABLE);
	}
	
	// 重定向printf
	int fputc(int ch, FILE *f)
	{
	    USART_SendData(USART1,(uint8_t)ch);// 串口1发送数据
	    while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);// 等待发送完成
	    return ch;
	}

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值