上一篇中,对串口做了个概述,主要是介绍了串口通信的特征,异步串行全双工通信,然后就是结合串口的框图梳理了一下STM32中USART的配置流程以及发送接收数据的流程,本文将接着上篇的内容,对串口的寄存器做个介绍,然后实现一个简单的收发实验。
根据之前GPIO的经验,咱们可以打开中文编程手册去找到对应的章节,然后依次看一下寄存器的每个位的功能。具体的位置在手册的第20章。
首先第一个寄存器,就是状态寄存器,其作用就是用于描述USART的工作状态,为编程者提供一个串口的实时状态,前面分析框图的时候,有提到过,发送时需要判断上一帧有没有发送完毕;接收时需要判断一帧数据有没有接收完毕,当时说的是有内部的标志,这其中的标志就在此寄存器中。
状态寄存器是只读。
寄存器的访问方式,还是结构体指针指向成员的写法:
**USARTx->SR(x代表1、2、3、6)
或者
UARTx->SR(x代表4、5)**
位 7 TXE:发送数据寄存器为空 (Transmit data register empty)
用于判断发送数据寄存器
当发送数据寄存器为空 1
当发送数据寄存器不为空 0
从发送数据寄存器发送到移位寄存器 这一位置1
位 6 TC:发送完成 (Transmission complete)
用于判断上一帧数据是否发送完成
发送完成 1
发送未完成 0
移位寄存器将数据发送完了,会将这一位置1;用于判断上一帧有么有发送完毕,没有就等待。
参照之前GPIO等待松手的写法,等待发送完成的代码应该是下面这个样子:
while( !(USART1->SR & 1<<6) ); /如果不为1 则一直等待
到这里关于发送和接收的代码编写思路其实已经有了,用串口1为例,代码如下:
//注意注释
串口的发送字符数据函数
{
等待上一帧数据发送完成
发送数据
}
/*******************************************
*函数名 :Usart1_Send_Byte
*函数功能 :串口1发送一个字节函数
*函数参数 :u8 data
*函数返回值:无
*函数描述 :
发送一个U8类型的字符
*********************************************/
void Usart1_Send_Byte(u8 data)
{
//等待之前的发送完成
while(!(USART1->SR & (1<<6)));
//将要发送的数据给数据寄存器
USART1->DR = data;
}
接收过程
{
等待接收移位寄存器为满
接收数据
}
/*******************************************
*函数名 :Usart1_Receive_Byte
*函数功能 :串口1接收一个字节函数
*函数参数 :void
*函数返回值:u8 str
*函数描述 :
发送一个U8类型的字符
*********************************************/
u8 Usart1_Receive_Byte(void)
{
u8 str;
//等待接收完成
while(!(USART1->SR & (1<<5)));
//将数据寄存器的数据读取到
str = USART1->DR;
return str;
}
这个寄存器最大作用就是解决了上一篇中发送完成和接收完成的两个标志位的问题,完善了框图中发送和接收过程的标志判断问题。
注意上面的代码中,无论是写数据还是读数据都用到了一个USART1->DR的
寄存器,那么它的作用又是什么呢,接下里就对它来做个分析。
注意手册中红框的表述,前面的框图中,发送和接收是两个数据寄存器,但实际在单片机内部是一个,这两个寄存器的唯一区别方法就是,执行写操作就是发送数据寄存器(TDR),执行读操作的时候就是接受数据寄存器(RDR)。这也就解释了为什么上面的代码中,读和写都是使用的DR寄存器。
然后就是波特率寄存器,根据昨天的框图,在波特率的配置过程中,只用将计算的DIV结构写入一个寄存器即可。如下图:该寄存器的4-15位就是写入DIV的整数部分,0-3位就是写入DIV的小数部分。
至于怎么写,参照前面的经验,直接使用赋值语句即可。还是借用昨天的
使用串口1,最后一句就是写入过程。
波特率:115200 时钟大小:84000000 过采样:16
float USARTDIV;
unsigned int DIV_M;
unsigned int DIV_F;
USARTDIV=84000000/16/115200; // 45.57291666666667
DIV_M =(u32) USARTDIV;//读取整数部分
DIV_F = (USARTDIV- DIV_M)*16+0.5 f //考虑四舍五入
USART1->BRR = DIV_M<<4 | DIV_F;//写入BRR寄存器
前面三个寄存器已经解决了前面框图分析提到的发送接收以及波特率的配置,剩下的USART_CR1、USART_CR2、USART_CR3就是用来配置串口控制器的,可以预见的是,这三个寄存器绝对是与串口的剩下三要素紧密相光的。
如上图,控制寄存器1的高十六位是做保留的,只有低十六位用来做配置,这里还是先挑出今天需要使用的位,想要全面了解的可以自己去看数据手册哈。
位 15 OVER8:过采样模式 (Oversampling mode),过采样,很熟悉吧,上面一个寄存器中计算公式的8倍过采样还是16倍过采样就是通过这个寄存器来进行配置的,寄存器写1时是8倍过采样,写0时是16倍过采样。
位 13 UE:USART 使能 (USART enable)
片上外设使能----模块级使能
打开时钟 ----内核级使能
“注!!!:具备配置写保护,进行USART配置的时候要先关闭使能,其他的配置完才能打开使能,要放在串口配置的最后一个”
位 12 M:字长 (Word length)
配置数据位,此位就是用来配置四要素中的数据位的,一般配置为8位数据位,也就是对其写0。
位 10 PCE:奇偶校验控制使能 (Parity control enable)
一般没有使用,所以直接配置为0,禁止奇偶校验。
位 2 RE:接收器使能 (Receiver enable)使能接收。
在控制寄存器1中我们需要操作的就是这些位,这里需要注意写法,控制寄存器1(USART_CR1),在代码中的写法是
USART1->CR[0]
可以发现,上面的控制寄存器1配置完后,四要素还有一个没有配置完毕,那就是停止位的配置,所以关于控制寄存器2,目前唯一用的上的就是第12
和13位。
一般配置为1个停止位,也就是写入00。
可以发现,到此,关于框图中的分析出来的,使能、四要素,发送,接收都齐活了,可是还有好几个寄存器没有露脸啊,这个后面遇到相关功能了再回来看,今天的功能确实只需要使用到上面的这些寄存器就够了。
关于控制寄存器的具体代码如下:
/*-----------------------------------------------------------------------*/
//Usart1初始化(四要素)
RCC->APB2ENR |= (1<<4);//打开AHB2上的Usart_1时钟使能。
/* //CR1
// USART1->CR1 &= ~(1<<15); //16倍过采样
// USART1->CR1 &= ~(1<<12); //8位字长
// USART1->CR1 &= ~(1<<10); //无奇偶校验
// USART1->CR1 |= (1<<3); //发送使能
// USART1->CR1 |= (1<<2); //接收使能*/
USART1->CR1 &=~ (0XB<<12);//清零(16倍过采样,8位字长)
USART1->CR1 |= (3<<2); //配置CR1寄存器,无奇偶校验、发送使能接收使能
//CR2
USART1->CR2 &= ~(3<<12); //1个停止位
/*-----------------------------------------------------------------------*/
//串口使能(最后开启使能,不然会锁住寄存器导致配置失败)
USART1->CR1 |= (1<<13);
可以发现,到这里关于昨天框图的流程已经走完了,那么是不是将上面的这些代码放进工程就可以了呢,事实上是不行的,还漏了一个点,前面介绍GPIO的时候说过,任何需要CPU与外界进行数据交换的时间,都需要使用到GPIO,很明显,上面的步骤中,还没有对GPIO进行操作。根据GPIO那篇的介绍,现在这种情况属于GPIO的复用模式,那么该怎么配置呢,分析如下:
既然GPIO是唯一通道,那么串口的通道具体对应那几个GPIO口呢,根据前面的通信特征可以知道应该会用到两个GPIO口,一个是TX一个是RX那么具体的对应要怎么查询呢,在数数据手册的第三章最后一个表格中有具体的映射,这里还是以USART1来说,首先在上方找到USART1,然后向下找到USART1_TX和USART1_RX然后再向左寻找即可看见对应管脚
,细心的同学肯定会发现一个问题,在后面的续表中还有一组GPIO口对应值USART1的TX和RX,这时候到底要配置哪一组呢,解决方法就是看原理图的物理连接用的是什么。通过原理图可以发现使用的是PA9和PA10这一组,所以说,在配置过程中就要使用PA9和PA10管脚。
前面的按键输入和控制LED做了通用输入模式和通用输出模式的配置,这是第一次使用到复用模式的配置,那么关于复用模式具体怎么配置呢,之前在介绍GPIO的寄存的时候,有两个寄存器当时给略过了,现在就需要用到他们了,GPIO的复用功能寄存器,其中GPIO复用功能低寄存器对应控制的是0-7八个管脚的复用功能,GPIO复用功能高寄存器对应控制的是8-15这八个管脚的复用功能,这里使用的是PA9和PA10属于复用功能高位寄存器,这里和上面的串口控制寄存器一样需要注意寄存器具体的写法:
GPIO复用功能低位寄存器: GPIOx->AFR[0]
GPIO复用功能高位寄存器: GPIOx->AFR[1]
然后在上面的引脚映射表中可以看见有个AF7,这个AF7就可以让PA9PA10对应到USART1的TX和RX。注意到编程手册的描述,对应AF7需要写入的是0111,也就是说,需要对AFR9和AFR10中写入0111即可。
具体的配置代码:
RCC->AHB1ENR |= (1<<0); //打开AHB1上GPIOA端口
GPIOA ->MODER &= ~(0xf<<18);//清0 GPIOA_MODER寄存器
GPIOA ->MODER |= (0xA<<18); //GPIOA_MODER寄存器配置为复用模式
GPIOA->AFR[1] &= ~((15<<4) | (15<<8)); //清零
GPIOA ->AFR[1]|= (0X77<<4); //A9,A10配置为AF7(USART1的TX、RX)
根据上面的介绍,我们将函数进行封装,串口配置部分就使用初始化函数,接收和发送就是用功能函数,同样的,这是一个新模块,还是需要新建两个文件用来存放源文件和头文件。
具体的代码:
#include "Usart1.h"
/*******************************************
*函数名 :Usart1_Init
*函数功能 :串口1初始化函数
*函数参数 :u32 bps波特率
*函数返回值:无
*函数描述 :
PA9--------USART1_TX
PA10-------USART1_RX
*********************************************/
void Usart1_Init(u32 bps)
{
float USARTDIV;
unsigned int DIV_M;
unsigned int DIV_F;
//GPIOA9、GPIOA10的初始化
RCC->AHB1ENR |= (1<<0); //打开AHB1上GPIOA端口
GPIOA ->MODER &= ~(0xf<<18);//清0 GPIOA_MODER寄存器
GPIOA ->MODER |= (0xA<<18); //GPIOA_MODER寄存器配置为复用模式
GPIOA->AFR[1] &= ~((15<<4) | (15<<8)); //清零
GPIOA ->AFR[1]|= (0X77<<4); //A9,A10配置为AF7(USART1的TX、RX)
/*-----------------------------------------------------------------------*/
//Usart1初始化(四要素)
RCC->APB2ENR |= (1<<4);//打开AHB2上的Usart_1时钟使能。
//以下是两种配置方式,注释部分是一位一位的配置,未注释的是一起配置的部分。
/* //CR1
// USART1->CR1 &= ~(1<<15); //16倍过采样
// USART1->CR1 &= ~(1<<12); //8位字长
// USART1->CR1 &= ~(1<<10); //无奇偶校验
// USART1->CR1 |= (1<<3); //发送使能
// USART1->CR1 |= (1<<2); //接收使能*/
USART1->CR1 &=~ (0XB<<12);//清零(16倍过采样,8位字长)
USART1->CR1 |= (3<<2); //配置CR1寄存器,无奇偶校验、发送使能接收使能
//CR2
USART1->CR2 &= ~(3<<12); //1个停止位
/*-----------------------------------------------------------------------*/
//BRR波特率计算
USARTDIV = 84000000/16/bps; //
DIV_M =(u32)USARTDIV;
DIV_F = (USARTDIV - DIV_M)*16+0.5f; //考虑四舍五入
USART1->BRR = DIV_M<<4 | DIV_F;
/*-----------------------------------------------------------------------*/
//串口使能(最后开启使能,不然会锁住寄存器导致配置失败)
USART1->CR1 |= (1<<13);
}
//!!!还需要将上面的发送一个字节和接收一个字节函数添加进来!!!!!
#ifndef _USART1_H__
#define _USART1_H_
#include "stm32f4xx.h"
void Usart1_Init(u32 bps);
void Usart1_Send_Byte(u8 data);
u8 Usart1_Receive_Byte(void);
#endif
编译下载后发现,并没有按照我们的代码写的发送出‘A’到串口调试助手上,而是问号,仔细检查发现配置也没有问题,而且再林外一块板子上可以使用,那这是为什么呢。
后来,经过检查,是因为官方的时钟配置文件的分配是以25MHZ的晶振为基础来写的,而我的板子是8M晶振,造成了时序混乱,所以打印乱码了,修改为8后就可以了。
修改后,可以正常打印字符A了。
需求1:使用PC机控制板子的LED灯
接收字符 ’O’ 打开全部灯
接收字符‘F’关闭全部灯
答:直接调用前面的接收函数,然后判断接受到的值,执行操作即可。
1.嵌入式学习笔记——概述
2.嵌入式学习笔记——基于Cortex-M的单片机介绍
3.嵌入式学习笔记——STM32单片机开发前的准备
4.嵌入式学习笔记——STM32硬件基础知识
5.嵌入式学习笔记——认识STM32的 GPIO口
6.嵌入式学习笔记——使用寄存器编程操作GPIO
7.嵌入式学习笔记——寄存器实现控制LED小灯
8.嵌入式学习笔记——使用寄存器编程实现按键输入功能
9.嵌入式学习笔记——STM32的USART通信概述
10.嵌入式学习笔记——STM32的USART相关寄存器介绍及其配置
11.嵌入式学习笔记——STM32的USART收发字符串及串口中断
12.嵌入式学习笔记——STM32的中断控制体系
13.嵌入式学习笔记——STM32寄存器编程实现外部中断
14.嵌入式学习笔记——STM32的时钟树
15.嵌入式学习笔记——SysTick(系统滴答)
16.嵌入式学习笔记——M4的基本定时器
17.嵌入式学习笔记——通用定时器
18.嵌入式学习笔记——PWM与输入捕获(上)
19.嵌入式学习笔记——PWM与输入捕获(下)
20.嵌入式学习笔记——ADC模数转换器
21.嵌入式学习笔记——DMA
22.嵌入式学习笔记——SPI通信
23.嵌入式学习笔记——SPI通信的应用
24嵌入式学习笔记——IIC通信