上一篇重点介绍了STM32 GPIO的输入输出模式,在整个框图中我们发现需要我们使用代码来控制GPIO的模式,本文的重点就是使用寄存器的编程方式,实现对于GPIO口的操作。
在这里首先需要做一个区分,我们常见的STM32的开发方式有两种,也就是寄存器开发与库函数开发。寄存器开发就是直接操作底层的寄存器来实现功能,而库函数,则是使用ST的官方库函数来实现功能,目前常用的库有基础库、HAL库、LL库。此系列都会采用直接操作寄存器的方式来实现功能。
既然是操作寄存器实现功能,就要知道与GPIO相关的寄存器有哪些,根据昨天的GPIO口模式以及框图我们可以大致猜出几个寄存器。
1.输入输出模式选择的寄存器;用来确定GPIO是输入模式还是输出模式;
2.输出类型选择的寄存器;用来选择是推挽还是开漏;
3.输出数据寄存器,暂存MCU输出的数字量;
4.输入数据寄存器,暂存外设输入的数字量;
5.上拉下拉寄存器,设置GPIO的上下拉模式;
没看手册之前,我们根据GPIO的功能以及结构猜测的有上面这五个寄存器,下面我们来看看编程手册关于GPIO的寄存器到底有哪些,又该怎么使用。
官方编程手册关于GPIO的寄存器介绍如下:
每个通用IO都有十个寄存器与之对应,在中文编程手册的第七章有关于这些寄存器的详细描述。
接下来我们来一一看一下这些寄存器的具体作用以及配置方式。
端口模式寄存器,就是控制整个端口的模式,通过写入不同的高低电平来区分对应管脚是输入还是输出,(GPIOx_MODER) 这个中间的x就代表从A—I的端口号(x = A…I),也就是说一块STM32最多只有9个端口模式寄存器。
它是一个32位的寄存器,前面我们提当过,STM32的一个端口有0-15共16个管脚,而这个端口模式寄存器又是32位的,这就说明每个管脚的模式控制有
32/16=2位二进制数来实现。如下图所示就是端口模式的寄存器(中文编程手册第七章7.4 节 GPIO 寄存器):
整个寄存器一共是0-31共32位,其中第0位与第1位下面写着MODER0[1:0],这个MODER0就代表是该端口对应的0号管脚模式控制,其中00表示输入模式(复位状态也是此模式),01表示通用输出模式,10表示复用功能,11表示模拟功能。依次类推,此寄存器的第2位第三位表示该端口的1号管脚的模式控制,剩下的也都是依次类推,直至第30位与31位代表第16号管脚的模式控制。细心的同学肯定已经发现了,这个寄存器实现的功能就是我们根据结构图分析出来的第一个寄存器所需的功能啊,它就解决了GPIO口的模式问题。
举个栗子:假设我们要将GPIOA的10号管脚配置为复用功能模式,这时候我们就需要找到MODER10[1:0],,然后将复用输出模式的10写进这个寄存器。
这里就会产生一个新问题,要怎么将数据写入寄存器呢,首先能够想到的肯定是需要赋值,那么怎么赋值呢,我们来分析一下。
首先这个寄存器的命名ST公司已经给我们做好了,就在我们之前搭建工程时使用的使用的stm32f4xx.h中,这里给大家截图在下面:
而且ST公司还利用宏定义给出了每个端口对应结构体的指针名。
可以看出所有的寄存器都是用的一个结构体给封装起来了,我们要调用赋值的话就可以按照下面的语句来实现操作,还是以GPIOA端口的10号管脚配置为复用输出为例:
GPIOA ->MODER |= (1<<21); //GPIOA_MODER寄存器配置为复用模式
//经过此步骤,MODER10[1:0]的数据就被覆盖成为了10,也就是手册中的复用模式。
//这里的1左移21位就是依据移位操作来实现的,由于MODER10对应的寄存器位数就是20位与21位,其中,第21位需要写1,所以我们就需要将1左移21位到此位置。
换做其他管脚也是类似的操作,再举个例子,我们将GPIOC的4号管脚配置为通用输出模式,此时需要的操作如下:
GPIOC->MODER |= (1<<8); //GPIOA_MODER寄存器配置为通用输出模式
//经过此步骤,MODER4[1:0]的数据就被覆盖成为了01,也就是手册中的通用输出模式。
//这里的1左移8位就是依据移位操作来实现的,由于MODER4对应的寄存器位数就是8位与9位,其中,第8位需要写1,所以我们就需要将1左移8位到此位置。
上面都是写1操作,用的是|,我们再举一个清零操作的例子,将GPIOA0配置为输入模式,此时就需要对MODER0进行清零操作,具体写法如下:
GPIOA ->MODER &=~(3<<0);//清0 GPIOA_MODER寄存器为00通用输入模式这里我们使用的是&=~的操作,3转换为二进制就是11,11左移0位,还是11,然后对11进行取反,变成00,再将00赋值给MODER,这样GPIOA_MODER0就被赋值成为了00,也就是将GPIOA0配置为了输入模式。
好了,到这里整个寄存器操作的赋值语句就学完了,就是一个赋值为一一个赋值为0的操作,两条语句,后面所有需要置一与清零的操作都是用的这个方法来实现的。
我们再来看看GPIO的第二个寄存器,端口输出类型寄存器,既然是输出类型寄存器,说明我们如果再上一个寄存器中将GPIO口配置为输入模式后,这个寄存器就可以不用操作了。
这个寄存器就是用来实现对输出模式的控制的,我们根据结构图分析出来的第二个寄存器的功能是不是与这个寄存器不谋而合呢,我们来看看手册中的描述,如下图所示:
有了上一个寄存器的经验,再看这个是不是就很清晰了,首先这个寄存器的高16位是保留的没有使用,后面的0-15共16位是不是刚好一位对应一个管脚号,也就是说OT0对应的就是0号管脚,OT15对应的就是15号管脚,然后注意下面的描述,配置为0的时候是推挽模式,而1的时候是开漏模式。
还是举个栗子吧,假设我们需要将GPIOC的4号脚配置为推挽模式,此时就该向OTC4写入0;参照上面的代码操作,就是如下代码:
GPIOC->OTC &=~(1<<4);//清0 GPIOA_OTC4寄存器为0推挽输出模式
然后是GPIO的第三个寄存器,GPIOx_OSPEEDR,端口输出速度寄存器,同理,由于是输出寄存器,所以如果在第一个寄存器中已经将端口配置为输入则再后续配置过程中不再需要操作本寄存器。
同样的,这个寄存器也是一个32位的寄存器,而且也是和第一个端口模式寄存器一样的,使用的是两个二进制位来控制一个管脚。这个寄存器是我们之前通过结构图所没能分析出来的,他主要作用就是控制IO的电平翻转速度。
根据手册的描述,我们还是举例将GPIOC端口的4号管脚配置为中速输出,首先在上图中找到OSPEEDR4[1:0],它对应的二进制位是第八位和第九位,配置为中速也就是25MHZ的输出速度,需要对这两位写入01,具体操作如下:
GPIOC->OSPEEDR |= (1<<8);//25MHZ中速
再然后就是第四个寄存器了,端口上拉下拉寄存器GPIOx_PUPDR,他的作用就是我们前面分析结构图提到的可以通过软件编程来实现IO内部上下拉功能的控制寄存器,通过配置它可以实现GPIO口的内部上下拉。此寄存器也是每两位二进制数控制一个管脚。
如上图所示,根据数据手册的描述,我们将GPIOA0设置为无上下拉模式,操作如下:找到对应0端口的控制位是第0位和第1位,要配置为无上下拉模式,就需要对这两位写入00。代码操作如下:
GPIOA ->PUPDR &=~(3<<0);//清0 GPIOA_PUPDR寄存器为00 浮空
然后再来看个稍微不一样一点的,输入数据寄存器,说它不一样主要是因为他是一个只读寄存器,也就是说,我们对它的操作只有读取,没有上面的赋值操作了,至于怎么读,我们放到明天的按键操作进行描述;根据编程手册可以看出,它也是一个一位代表一个端口号的寄存器。另外就是他是输入寄存器,说明我们在配置GPIO为输出模式时,是不需要操作此寄存器的,在这里我们只需要了解这么多,至于怎么编程控制我们后面再说。
可以看出这个寄存器的名字和上一个输入寄存器的名字刚好对应啊,它的作用就是将对应位上的值输出,同样的,它的命名是输出,说明配置输入时跟与它就没有关系了;它也是单个二进制位控制一个管脚。
这里举个栗子,假设我们需要GPIOC4输出高电平,我们就需要对ODR4写1;具体编程操作如下:
GPIOC->ODR |= (1<<4);