stm32深入浅出——由GPIO谈谈寄存器配置

来源:互联网 发布:android 应用启动优化 编辑:程序博客网 时间:2024/05/22 03:42

相信大家对GPIO的配置并不陌生,只需简单的几个库函数就能完成。而本菜今天要讲的不是怎么用这些库函数,而是要讲讲这些库函数是怎么工作的。本菜留意了下,无论是网上还是书籍,涉及这方面的知识很少,直接抄了使用手册就上了。那么本菜在这里就详细讲一讲,做些补充,希望能帮助到大家。


GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;

GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;

GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;

GPIO_Init(GPIOD, &GPIO_InitStructure);


 这几行代码是司空见惯了。我们先从它们入手。

GPIO_InitStructure  看名字,就知道是个结构体,在主文件里是这样声明的:

 

GPIO_InitTypeDef  GPIO_InitStructure;

 

于是乎,我们再看看GPIO_InitTypeDef 是什么东西吧。

 

typedef struct

{

  u16 GPIO_Pin;

  GPIOSpeed_TypeDef GPIO_Speed;

  GPIOMode_TypeDef GPIO_Mode;

}GPIO_InitTypeDef;

 

其中各有声明为:

typedef enum

  GPIO_Speed_10MHz = 1,

  GPIO_Speed_2MHz, 

  GPIO_Speed_50MHz

}GPIOSpeed_TypeDef;

 

//================================================================//

 

typedef enum

{ GPIO_Mode_AIN = 0x0,

  GPIO_Mode_IN_FLOATING = 0x04,

  GPIO_Mode_IPD = 0x28,

  GPIO_Mode_IPU = 0x48,

  GPIO_Mode_Out_OD = 0x14,

  GPIO_Mode_Out_PP = 0x10,

  GPIO_Mode_AF_OD = 0x1C,

  GPIO_Mode_AF_PP = 0x18

}GPIOMode_TypeDef;

    我们可以发现,这些配置信息都有它们的固定数值。这些数值,代表什么意义呢?我们接下去看看GPIO_Init(GPIOD, &GPIO_InitStructure)这个函数吧。

先看参数的类型GPIO_TypeDef* GPIO_InitTypeDef之前已经有讲解) 

GPIO_TypeDef  

typedef struct

{

  vu32 CRL;

  vu32 CRH;

  vu32 IDR;

  vu32 ODR;

  vu32 BSRR;

  vu32 BRR;

  vu32 LCKR;

} GPIO_TypeDef;

 

    这里有几个寄存器需要简单讲解的:CRL是低位配置寄存器,是用来存储低位数据(低8位)的配置情况。CRH是高位寄存器。ODR是写入输出寄存器,在配置为输出时,该寄存器的值就输出到I/O引脚。IDR是输入数据寄存器,在每个APB2时钟周期读取并捕获对应I/O口数据。BSRR寄存器是32位置/复位寄存器,高16位可以对2字节(16位)的ODR上对应位进行位操作,低16位则进行复位操作,一般都用这个寄存器对I/O口的输出进行操作,当然也可以通过修改ODR寄存器来实现,像在这个库函数里就是通过BSRR来实现的。BRR16位复位寄存器,只能进行复位操作。LCKR是端口锁定寄存器,开锁定以后,对该端口的某位的配置修改不可行。具体请参照STM32F10X使用手册。


然后另一个实参(举例):GPIOD  查找其声明,就可以发现:  #define GPIOD         ((GPIO_TypeDef *) GPIOD_BASE)

就和之前的例子一样,是以GPIOD_BASE为基地址的结构体。那么当你再查找GPIOD_BASE的定义时,就会发现其值为0x40011800,也即:


这是什么意思呢?就是指管理GPIOD的寄存器的初始地址就是0x40011800。那么我们再来看看第一个寄存器CRL的信息:



我们发现CRL的偏移地址是00h,也即其是基地址上的第一个寄存器组。仔细一看便知,这个寄存器组包含32个寄存器,也即占了32位空间。我们再看CRL在结构体的类型是vu32,就能明白,这些空间分配,是遵循着线性规律的。当我们再看CRH,也就是第二个寄存器组时,不难发现其偏移地址是04h,也即4*8=32位的偏移地址。这和我们之前的分析是完全符合的。

    再回到程序中,可以发现I/O口的配置是分高低8位的,原因在于GPIO的配置寄存器分为高位配置寄存器CRH和低位配置CRL。在STM32中通常一个寄存器有32位,在这里却用了2个寄存器来配置GPIO,说明一个I/O口需要4个位来配置。就仅仅以CRL为例,来看继续看上图。

    我们再看GPIO_PIN_x的定义:


从中不难发现一些规律。看官心中有些明了之后,我们再回到代码中,只看这几句:

if (((u32)GPIO_InitStruct->GPIO_Pin & ((u32)0x00FF)) != 0x00)

  {

    tmpreg = GPIOx->CRL;

    for (pinpos = 0x00; pinpos < 0x08; pinpos++)

    {

      pos = ((u32)0x01) << pinpos;

      

      currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;

    我们大致揣摩,就能得出这几句语句是用来选择当前的引脚的。这也就是为何上面GPIO_PIN_x的定义是以2的倍数递增。当然,CRL这个寄存器的位的排列也是遵循这个规律的:


 这是PIN_0脚上的4个配置位,可见这是与它们名称的尾号是一致的,MODExCNFx控制PIN_x。掌握这个规律,后面几条语句自然容易推敲出来是什么意思。

    不过,有些不同寻常的是:

// Reset the corresponding ODR bit 

        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPD)

        {

          GPIOx->BRR = (((u32)0x01) << (pinpos + 0x08));

        }

        //Set the corresponding ODR bit 

        if (GPIO_InitStruct->GPIO_Mode == GPIO_Mode_IPU)

        {

          GPIOx->BSRR = (((u32)0x01) << (pinpos + 0x08));

        }

很匪夷所思,根据判断的条件,显然是要选择下拉输入还是上拉输入。但它居然需要对输出数据复位置位寄存器BRRBSRR来操作。这是为何?不管那么多,先看手册:


在CNFX[1:0]中,显然不能选定上拉和下拉(MODEX[1:0]同样也没有),再看输入口配置电路图:


    必须有一个控制器控制着这个ON/OFF。那么在下图中,我们就可以找到答案:


这下了然了吧!ODR寄存器是参与选择上拉和下拉模式的。

    现在大家可以通过写寄存器来做一些像点LED灯的简单操作了。虽说直接使用库函数绝对是很快捷的方法,但是了解了寄存器的功能,目的是为了未来要使用这些库函数时能得心应手。我们学习这些寄存器不是为了摆脱库函数,而是为了更好地利用库函数。

原创粉丝点击