嵌入式学习笔记——使用寄存器编程操作GPIO

使用寄存器编程操作GPIO

  • 前言
  • GPIO相关的寄存器
  • GPIO 端口模式寄存器 (GPIOx_MODER) (x = A..I)
  • 位操作
  • GPIO 端口输出类型寄存器 (GPIOx_OTYPER) (x = A..I)
  • GPIO 端口输出速度寄存器 (GPIOx_OSPEEDR) (x = A..I/)
  • GPIO 端口上拉/下拉寄存器 (GPIOx_PUPDR) (x = A..I/)
  • GPIO 端口输入数据寄存器 (GPIOx_IDR) (x = A..I)
  • GPIO 端口输出数据寄存器 (GPIOx_ODR) (x = A..I)
  • GPIO 端口置位/复位寄存器 (GPIOx_BSRR) (x = A..I)
  • GPIO 端口配置锁定寄存器 (GPIOx_LCKR) (x = A..I)
  • GPIO 复用功能低位寄存器 (GPIOx_AFRL) (x = A..I)
  • GPIO 复用功能高位寄存器 (GPIOx_AFRH) (x = A..I)
  • 小结
  • 使用寄存器编程控制LED灯的亮灭
  • 原理图分析
  • MDK代码编程
  • 总结
  • M4系列目录
  • 前言

    上一篇重点介绍了STM32 GPIO的输入输出模式,在整个框图中我们发现需要我们使用代码来控制GPIO的模式,本文的重点就是使用寄存器的编程方式,实现对于GPIO口的操作。
    在这里首先需要做一个区分,我们常见的STM32的开发方式有两种,也就是寄存器开发与库函数开发。寄存器开发就是直接操作底层的寄存器来实现功能,而库函数,则是使用ST的官方库函数来实现功能,目前常用的库有基础库、HAL库、LL库。此系列都会采用直接操作寄存器的方式来实现功能。

    GPIO相关的寄存器

    既然是操作寄存器实现功能,就要知道与GPIO相关的寄存器有哪些,根据昨天的GPIO口模式以及框图我们可以大致猜出几个寄存器。
    1.输入输出模式选择的寄存器;用来确定GPIO是输入模式还是输出模式;
    2.输出类型选择的寄存器;用来选择是推挽还是开漏;
    3.输出数据寄存器,暂存MCU输出的数字量;
    4.输入数据寄存器,暂存外设输入的数字量;
    5.上拉下拉寄存器,设置GPIO的上下拉模式;

    没看手册之前,我们根据GPIO的功能以及结构猜测的有上面这五个寄存器,下面我们来看看编程手册关于GPIO的寄存器到底有哪些,又该怎么使用。
    官方编程手册关于GPIO的寄存器介绍如下:

    每个通用IO都有十个寄存器与之对应,在中文编程手册的第七章有关于这些寄存器的详细描述。

    接下来我们来一一看一下这些寄存器的具体作用以及配置方式。

    GPIO 端口模式寄存器 (GPIOx_MODER) (x = A…I)

    端口模式寄存器,就是控制整个端口的模式,通过写入不同的高低电平来区分对应管脚是输入还是输出,(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 端口输出类型寄存器 (GPIOx_OTYPER) (x = A…I)

    我们再来看看GPIO的第二个寄存器,端口输出类型寄存器,既然是输出类型寄存器,说明我们如果再上一个寄存器中将GPIO口配置为输入模式后,这个寄存器就可以不用操作了。
    这个寄存器就是用来实现对输出模式的控制的,我们根据结构图分析出来的第二个寄存器的功能是不是与这个寄存器不谋而合呢,我们来看看手册中的描述,如下图所示:


    有了上一个寄存器的经验,再看这个是不是就很清晰了,首先这个寄存器的高16位是保留的没有使用,后面的0-15共16位是不是刚好一位对应一个管脚号,也就是说OT0对应的就是0号管脚,OT15对应的就是15号管脚,然后注意下面的描述,配置为0的时候是推挽模式,而1的时候是开漏模式。
    还是举个栗子吧,假设我们需要将GPIOC的4号脚配置为推挽模式,此时就该向OTC4写入0;需要注意的是,虽然命名的时候是按照管脚号命名为OTCx.但是实际操作的都是整个寄存器OTYPER,根据管脚,操作对应位即可,参照上面的代码操作,就是如下代码:

    	GPIOC->OTYPER &=~(1<<4);//清0  GPIOA_OTC4寄存器为0推挽输出模式,GPIOA_OTC4对应OTYPER的第四位。
    

    GPIO 端口输出速度寄存器 (GPIOx_OSPEEDR) (x = A…I/)

    然后是GPIO的第三个寄存器,GPIOx_OSPEEDR,端口输出速度寄存器,同理,由于是输出寄存器,所以如果在第一个寄存器中已经将端口配置为输入则再后续配置过程中不再需要操作本寄存器。
    同样的,这个寄存器也是一个32位的寄存器,而且也是和第一个端口模式寄存器一样的,使用的是两个二进制位来控制一个管脚。这个寄存器是我们之前通过结构图所没能分析出来的,他主要作用就是控制IO的电平翻转速度。

    根据手册的描述,我们还是举例将GPIOC端口的4号管脚配置为中速输出,首先在上图中找到OSPEEDR4[1:0],它对应的二进制位是第八位和第九位,配置为中速也就是25MHZ的输出速度,需要对这两位写入01,具体操作如下:

    	GPIOC->OSPEEDR |= (1<<8);//25MHZ中速
    

    GPIO 端口上拉/下拉寄存器 (GPIOx_PUPDR) (x = A…I/)

    再然后就是第四个寄存器了,端口上拉下拉寄存器GPIOx_PUPDR,他的作用就是我们前面分析结构图提到的可以通过软件编程来实现IO内部上下拉功能的控制寄存器,通过配置它可以实现GPIO口的内部上下拉。此寄存器也是每两位二进制数控制一个管脚。


    如上图所示,根据数据手册的描述,我们将GPIOA0设置为无上下拉模式,操作如下:找到对应0端口的控制位是第0位和第1位,要配置为无上下拉模式,就需要对这两位写入00。代码操作如下:

    GPIOA ->PUPDR &=~(3<<0);//清0  GPIOA_PUPDR寄存器为00 浮空
    

    GPIO 端口输入数据寄存器 (GPIOx_IDR) (x = A…I)

    然后再来看个稍微不一样一点的,输入数据寄存器,说它不一样主要是因为他是一个只读寄存器,也就是说,我们对它的操作只有读取,没有上面的赋值操作了,至于怎么读,我们放到明天的按键操作进行描述;根据编程手册可以看出,它也是一个一位代表一个端口号的寄存器。另外就是他是输入寄存器,说明我们在配置GPIO为输出模式时,是不需要操作此寄存器的,在这里我们只需要了解这么多,至于怎么编程控制我们后面再说。

    GPIO 端口输出数据寄存器 (GPIOx_ODR) (x = A…I)

    可以看出这个寄存器的名字和上一个输入寄存器的名字刚好对应,它的作用就是将对应位上的值输出到GPIO口上,直白说就是对它写1对应管脚就是输出高电平,对它写0对应管脚就会输出低电平,同样的,它的命名是输出,说明配置输入时跟与它就没有关系了;它也是单个二进制位控制一个管脚。此寄存器在昨天的框图中也有提到过,属于输出框图里面的,他对应的有两个输入,一个是直接和内核通信,另外一个输入方式内核需要经过置位复位控制器来间接控制端口输出寄存器。

    这里举个栗子,假设我们需要GPIOC4输出高电平,我们就需要对GPIO->ODR的第 4 位写1;具体编程操作如下:

    GPIOC->ODR |= (1<<4);
    

    GPIO 端口置位/复位寄存器 (GPIOx_BSRR) (x = A…I)

    再上一个寄存器中我们提到了这个端口置位复位寄存器,从名称中我们可以看出他的作用是对对应的管脚的ODR寄存器进行置零和置一操作。
    手册中关于这个寄存器的描述如下,它也是一个32位寄存器,但是区分了高16位和低16位,其中高16位表示BR0—–BR15也就是管脚0-15的复位操作,对对应管脚写1,其ODR寄存器就会进行复位,写0就不操作。
    而第十六位是BS0—–BS15控制的是0–15号管脚的置位功能,同样写入1就对对应的ODR寄存器置1,写0就不操作。
    可以发现这个寄存器的作用与上面的ODR寄存器其实功能一致,只是内核通过操作此寄存器间接操作ODR,所以在正常使用过程中很少用到这个寄存器,一般都是使用内核直接操作ODR寄存器来实现。

    GPIO 端口配置锁定寄存器 (GPIOx_LCKR) (x = A…I)

    关于此寄存器,其作用是配置后锁定后,我们后面所有的对对应GPIO口的操作都无效了,但是实际使用过程中,关于GPIO口的配置经常是需要操作的,所以这个寄存器也很少使用到。想要了解的同学去手册自己瞄一眼就可以了。

    然后是最后两个寄存器,他们是配置GPIO口的复用功能的,由于GPIO口的复用功能非常丰富,为了实现更多的功能,ST公司设计了两个复用功能寄存器,一个是复用功能低位寄存器另一个是复用功能高位寄存器它们两个寄存器都是32位寄存器,每个寄存器分管8个IO口的复用模式,复用功能低位寄存器 (GPIOx_AFRL) 控制0—–7管脚的复用功能;复用功能高位寄存器 (GPIOx_AFRH) 控制8—-15管脚的复用功能;也就是说在复用功能配置这里,每个管脚有四个二进制位进行控制,一共有AF0—–AF15共16中复用模式,在配置具体的复用模式时,需要根据引脚映射表来查询具体的是AFX,然后根据下面的AFX对应的数据写入即可,具体的使用我们在后面的片上外设复用使用中再做具体介绍。由于是复用功能,所以在通用模式时我们用不上这个寄存器,只有需要使用复用功能时才需要用到。

    GPIO 复用功能低位寄存器 (GPIOx_AFRL) (x = A…I)

    GPIO 复用功能高位寄存器 (GPIOx_AFRH) (x = A…I)

    小结

    至此,我们已经看完了所有的GPIO寄存器,一共是10个,但是在实际使用中,我们需要用到寄存器并没有那么多,需要对应模式进行选择,最终关于对应模式的GPIO寄存器配置可以总结为下表:


    表中打X的表示不用配置,举个栗子,配置通用推挽输出(不带上下拉),这时候的配置步骤:

    伪代码:
    配置某一个IO口为通用推挽输出模式所需要的GPIO寄存器
    {
    	1.对应位的MODER配置为01——————————通用输出
    	2.对应位的OTYPER配置为0——————————推挽输出
    	3.对应位的OSPEEDR配置为(01)—————25Mhz中速(这个不一定,按照自己的需求)
    	4.对应位的PUPDR配置为00——————————无上下拉
    	5.对应位的ODR    配置为0——————————输出低电平; 配置为1——————————输出高电平
    }
    

    使用寄存器编程控制LED灯的亮灭

    看完上面的寄存器介绍,我们对于整个配置流程已经有了一个了解,接下里就是使用MDK编写代码实现我们想要的功能,今天我们先控制一个LED灯。

    原理图分析

    首先笔者这里用的板子的LED小灯电路图如下图所示,根据原理图可以得出,我们给PA6低电平LED1就点亮,给PA6高电平,LED1就熄灭。

    MDK代码编程

    打开我们在嵌入式学习笔记——STM32单片机开发前的准备http://t.csdn.cn/PF8Y9中搭建好的工程,(没有搭好的可以私信获取);
    1.打开后新建一个Led.c文件到USER下的src的文件夹下;
    2.新建一个Led.h文件到USER下的inc文件夹下;

    3.添加Led.c进入工程,双击1所示的USER,在弹出的框内2的位置打开src文件夹,选中Led.c电机Add即可。

    4.编写Led.h头文件,注意头文件的分区,保持一个好的格式风格。

    #ifndef _LED_H__
    #define _LED_H__
    //包含头文件区
    #include "stm32f4xx.h"//此头文件每个子模块的.h都必须包含
    /*----------------------------------------------------------*/
    //宏定义区
    /*----------------------------------------------------------*/
    //函数声明区
    /*----------------------------------------------------------*/
    #endif
    

    5.编写Led.c文件,首先第一步添加"Led.h",使头文件与源文件联系起来,然后就是编写初始化函数,具体的初始化过程我们参考上面的伪代码来进行配置。
    在这里还需要特别注意的一步是,在编写上面的伪代码之前还需要开启对应GPIOA挂接的时钟使能。
    查询步骤:
    1.看片内外设的结构框图,找GPIOA对应的时钟线,数据手册第二章,如下图所示,GPIOA对应的就是AHB1总线;

    2.在编程手册查找AHB1对应的使能寄存器,如下图所示:
    在编程手册第六章,最后一节,可以看见这个寄存器的第0位就是GPIOA的时钟,将他配置为1即可打开GPIOA的时钟,具体代码实现如下:

    //打开GPIOA端口对应的AHB1时钟
    RCC->AHB1ENR |= (1<<0);
    

    #include "Led.h"
    
    /*******************************************
    *函数名    :Led_Init
    *函数功能  :LED灯所用的管脚的初始化配置
    *函数参数  :无
    *函数返回值:无
    *函数描述  :
    LED1--------PA6--------低电平亮,通用输出模式
    *********************************************/
    void Led_Init(void)
    {
    	//打开GPIOA端口对应的AHB1时钟
    	RCC->AHB1ENR |= (1<<0);
    	/*端口模式清零*/
    	GPIOA->MODER &= ~(3<<12);
    	/*端口通用输出模式*/
    	GPIOA->MODER |= (1<<12);
    	/*端口输出推挽模式*/
    	GPIOA->OTYPER &= ~(1<<6);
    	/*端口输出速度2MHz*/
    	GPIOA->OSPEEDR &= ~(3<<12);//清零OSPEEDR
    	GPIOA->OSPEEDR |= (1<<12);//25MHZ中速
    	GPIOA->PUPDR   &= ~(3<<12);//默认无上下拉
    	/* 端口输出寄存器*/
    //	GPIOA->ODR|=(0xf<<6);//置1拉高对应端口,灯灭
    }
    


    6.然后在main.c首先包含Led.h到main.h中:添加一句#include"Led.h";

    再在main.c中调用Led_Init()初始化函数,并对ODR寄存器写0点亮LED1;

    代码:

    #include "main.h"
    
    int main(void)
    {
    /*------------------变量定义区--------------------------*/
    	
    /*------------------初始化外设区------------------------*/
    	Led_Init();
    	GPIOA->ODR&=~(0xf<<6);//置零拉低对应端口,灯亮
    /*------------------单次运行区--------------------------*/	
    	
    	while(1)//防止程序跑飞
    	{
    /*------------------主循环区--------------------------*/			
    	}
     
    }
    
    
    

    7.点击1全编译,如果出现2所示的0errors0warings,表示代码没问题了,
    8.选择下载器:笔者使用的是ST-LINK,操作如下:选择3所示的魔法棒,选择4Debug,选择ST-LINK,然后点击确定。

    9.点击烧录,等待下方进度条为百分百,表示烧录完毕。

    10.点击板子上的复位或者重新上电,小灯已经被点亮。

    总结

    至此,关于GPIO的寄存器配置以及一个简单的代码编程已经完成了,后面一篇我们再来尝试配置输入模式的编程过程,本文篇幅也很长,如有不足,希望大家指出,寄存器的详细介绍,大家可以去编程手册查看。

    M4系列目录

    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通信

    物联沃分享整理
    物联沃-IOTWORD物联网 » 嵌入式学习笔记——使用寄存器编程操作GPIO

    发表评论