历史上的今天
返回首页

历史上的今天

今天是:2025年08月12日(星期二)

正在发生

2018年08月12日 | 关于stm32中的GPIO引脚初始化的程序讲解及相关知识拓展介绍

2018-08-12 来源:eefocus

以下是我要讲解的GPIO初始化程序段,尽量讲解小白学习过程中不解的每一个方面。

代码讲解时我是根据一个程序边讲边跳入它的声明或是定义中讲解的。

voidLED_GPIO_Config(void)

{     

       /*定义一个GPIO_InitTypeDef类型的结构体*/

1.    GPIO_InitTypeDef  GPIO_InitStructure;

       /*开启GPIOF的外设时钟*/

2. RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);

       /*选择要控制的GPIOF引脚*/           

3. GPIO_InitStructure.GPIO_Pin =GPIO_Pin_9 | GPIO_Pin_10;

       /*设置引脚模式为通用推挽输出*/

4.    GPIO_InitStructure.GPIO_Mode =GPIO_Mode_OUT;

5.    GPIO_InitStructure.GPIO_OType =GPIO_OType_PP;

       /*设置引脚速率为100MHz */  

6.    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;

7.    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;

       /*调用库函数,初始化GPIOF*/

8.    GPIO_Init(GPIOF, &GPIO_InitStructure);

}

解读:

1:对于1来讲,正如注解所说,先定义一个结构体,结构体中有需要初始化的一些结构体变量,而这些变量又有什么意义呢?这是我们要思考的问题。

typedef struct
{
  uint32_t GPIO_Pin;              /*!< Specifies the GPIO pins to be configured.
                                       This parameter can be any value of @ref GPIO_pins_define */

  GPIOMode_TypeDef GPIO_Mode;     /*!< Specifies the operating mode for the selected pins.
                                       This parameter can be a value of @ref GPIOMode_TypeDef */

  GPIOSpeed_TypeDef GPIO_Speed;   /*!< Specifies the speed for the selected pins.
                                       This parameter can be a value of @ref GPIOSpeed_TypeDef */

  GPIOOType_TypeDef GPIO_OType;   /*!< Specifies the operating output type for the selected pins.
                                       This parameter can be a value of @ref GPIOOType_TypeDef */

  GPIOPuPd_TypeDef GPIO_PuPd;     /*!< Specifies the operating Pull-up/Pull down for the selected pins.
                                       This parameter can be a value of @ref GPIOPuPd_TypeDef */
}GPIO_InitTypeDef;

这是结构体中定义的变量

         再看下GPIOMode_TypeDef,这是一个枚举变量。而几个枚举元素的值其实代表了对相应的寄存器的赋值(相应的赋值对应相应的模式)。例如将GPIO模式寄存器赋值0x00,则表示引脚配置为输入模式。

typedef enum

  GPIO_Mode_IN   = 0x00, /*!< GPIO Input Mode */
  GPIO_Mode_OUT  = 0x01, /*!< GPIO Output Mode */
  GPIO_Mode_AF   = 0x02, /*!< GPIO Alternate function Mode */
  GPIO_Mode_AN   = 0x03  /*!< GPIO Analog Mode */
}GPIOMode_TypeDef;

现在解读《2》句,RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);

 

void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState)
{
  /* Check the parameters */
  assert_param(IS_RCC_AHB1_CLOCK_PERIPH(RCC_AHB1Periph));

  assert_param(IS_FUNCTIONAL_STATE(NewState));
  if (NewState != DISABLE)
  {
    RCC->AHB1ENR |= RCC_AHB1Periph;
  }
  else
  {
    RCC->AHB1ENR &= ~RCC_AHB1Periph;
  }
}


这是一个配置时钟的函数,有两个参数,第一个参数的专业介绍:

RCC_AHBPeriph:specifies the AHB1 peripheral to gates its clock.

This parameter canbe any combination of the following values:

现在我来解释一下,大体意思是:指定AHB1外设去给它的时钟装个门,很形象,第二句的意思是说这个参数可以为一下的一些值,当然这里我没给出来,篇幅所限。

结合2的实例,RCC_AHB1Periph_GPIOF,它的意思是(先透露一下是对AHB1外设时钟使能寄存器的置位值,即将Bit7置位1,使能GPIOF端口时钟)

#defineRCC_AHB1Periph_GPIOF   ((uint32_t)0x00000020)。

再看这个函数中的第5句,理解好这句的执行十分重要。

RCC->AHB1ENR |=RCC_AHB1Periph;

#define RCC   ((RCC_TypeDef *) RCC_BASE)(RCC是个宏,先透露,猜也猜得到是个地址)

RCC表示一个地址,同时通过强制转换将该地址指向了(或者说赋值给了)RCC_TypeDef这个结构体,而这个结构体中,是许多的寄存器,前面已经讲了,这个结构体的初始地址已经有了,具体是什么意思后面再讲。而结构体中的成员按照顺序依照C语言的语法规则,每个成员依次占用4个字节(一个寄存器32位,占4个字节),刚好是每个寄存器的偏移地址。所以说这个结构体变量RCC与实际芯片中的每个寄存器(我们已经按顺序取好了名字)的物理地址顺序是一致的。我们通过寄存器操作就可以达到相应的目的,将相应的寄存器位置位来实现。而且每个寄存器都是见名知义。

所以至此我应该是解释了RCC的内容以及作用,下面我还要深扒这个地址的根源是谁,究竟代表什么意思,前面我虽然讲了该地址是指向RCC-TypeDef,但为什么指向它,指向它又是什么意思我还没讲。

#define RCC_BASE      (AHB1PERIPH_BASE+ 0x3800)(这是挂载在AHB1总线上的外设首地址了)

#define AHB1PERIPH_BASE      (PERIPH_BASE + 0x00020000)(外设基地址加偏移地址指向AHB1总线基地址)

#define PERIPH_BASE          ((uint32_t)0x40000000) /*!< Peripheral base address in the aliasregion(注释的意思我猜是:这个别名区域代表外设基地址)

{

参考:零死角玩转STM32—F407霸天虎.pdf

第6.5.1节中有详细介绍。(这个资料自己百度也行,或者)

片上外设区分为四条总线,根据外设速度的不同,不同总线挂载着不同的外设, APB

挂载低速外设, AHB 挂载高速外设。相应总线的最低地址我们称为该总线的基地址,总线

基地址也是挂载在该总线上的首个外设的地址。其中 APB1总线的地址最低,片上外设从

这里开始,也叫外设基地址。

}

所以,RCC_TypeDef指定地址后,就代表了芯片上实实在在的一片物理地址空间,这片地址上有各种寄存器,通过对不同寄存器进行置位,就能实现不同的功能(具体不同寄存器的不同位有什么不同功能需参考技术手册)。 

具体有哪些寄存器见下:见名知义,我们用到的是其中的时钟使能寄存器。

__IO uint32_t AHB1ENR;       /*!< RCC AHB1 peripheral clock register,                          Address offset: 0x30 */

typedef struct
{
  
  __IO uint32_t AHB1ENR;       /*!< RCC AHB1 peripheral clock register,                          Address offset: 0x30 */
  __IO uint32_t AHB2ENR;       /*!< RCC AHB2 peripheral clock register,                          Address offset: 0x34 */
  __IO uint32_t AHB3ENR;       /*!< RCC AHB3 peripheral clock register,                          Address offset: 0x38 */
  uint32_t      RESERVED2;     /*!< Reserved, 0x3C                                                                    */
  __IO uint32_t APB1ENR;       /*!< RCC APB1 peripheral clock enable register,                   Address offset: 0x40 */
  __IO uint32_t APB2ENR;       /*!< RCC APB2 peripheral clock enable register,                   Address offset: 0x44 */
  uint32_t      RESERVED3[2];  /*!< Reserved, 0x48-0x4C                                                               */
  __IO uint32_t AHB1LPENR;     /*!< RCC AHB1 peripheral clock enable in low power mode register, Address offset: 0x50 */
  __IO uint32_t AHB2LPENR;     /*!< RCC AHB2 peripheral clock enable in low power mode register, Address offset: 0x54 */
  __IO uint32_t AHB3LPENR;     /*!< RCC AHB3 peripheral clock enable in low power mode register, Address offset: 0x58 */
  uint32_t      RESERVED4;     /*!< Reserved, 0x5C                                                                    */
  __IO uint32_t APB1LPENR;     /*!< RCC APB1 peripheral clock enable in low power mode register, Address offset: 0x60 */
  __IO uint32_t APB2LPENR;     /*!< RCC APB2 peripheral clock enable in low power mode register, Address offset: 0x64 */
  uint32_t      RESERVED5[2];  /*!< Reserved, 0x68-0x6C                                                               */
  __IO uint32_t BDCR;          /*!< RCC Backup domain control register,                          Address offset: 0x70 */
  __IO uint32_t CSR;           /*!< RCC clock control & status register,                         Address offset: 0x74 */
  uint32_t      RESERVED6[2];  /*!< Reserved, 0x78-0x7C                                                               */
  __IO uint32_t SSCGR;         /*!< RCC spread spectrum clock generation register,               Address offset: 0x80 */
  __IO uint32_t PLLI2SCFGR;    /*!< RCC PLLI2S configuration register,                           Address offset: 0x84 */
  __IO uint32_t PLLSAICFGR;    /*!< RCC PLLSAI configuration register,                           Address offset: 0x88 */
  __IO uint32_t DCKCFGR;       /*!< RCC Dedicated Clocks configuration register,                 Address offset: 0x8C */

} RCC_TypeDef;

这里再多说一句:__IO是什么意思,#define     __IO    volatile             /*!< Defines 'read / write' permissions              */

volatile又是什么意思呢,简单的说,就是不让编译器进行优化,即每次读取或者修改值的时候,都必须重新从内存或者寄存器中读取或者修改。

现在再回到RCC->AHB1ENR |= RCC_AHB1Periph;前半句的意思是访问RCC上的AHB1ENR寄存器,见名知义这是AHB1外设时钟使能寄存器。后半句的意思是将RCC_AHB1Periph的值与AHB1ENR的值先或运算再赋给AHB1ENR,因为AHB1ENR初始化为0,实际上是直接复制给AHB1ENR的。(这里注意一个地方就是我一下子说AHB1ENR寄存器,一下子又把AHB1ENR当做一个变量值对待赋值,实际上是指AHB1ENR代表的那个地址区域,它被称作寄存器,对那个区域进行赋值操作,反映在C语言语法上就是对一个变量进行操作,我们软件设计使得这个变量地址落在该寄存器物理地址上)

         现在解释第《3》句话,GPIO_InitStructure.GPIO_Pin= GPIO_Pin_9 | GPIO_Pin_10;

这句话很好理解了,就是选定这两个引脚,哦对了,刚刚想起有个知识点需要提一下,一个寄存器分高16位和低16位,控制的是GPIOx端口的0-15个引脚,相邻两个引脚的地址相差2倍。上面这个语句其实是选定了Pin9和Pin10这两个引脚(他们的宏定义的意思都是指偏移地址,相对于GPIOx基地址的)。那么程序上如何实现选定到这两个引脚的呢?我们已经知道了,要挂载GPIOF外设,并且上一条程序已经使能了GPIOF时钟,而且最终我们是一起初始化GPIOF,包括其他相关设定列输入输出模式,速度等。那么如何将GPIOF基地址与我们所选定的两个引脚的偏移地址链接(通俗的讲是联系)起来呢?

         这就有点复杂了,请耐心,休息一下…。首先我们来讲一下这个FOR方法体。

 for (pinpos = 0x00; pinpos < 0x10; pinpos++)
  {
    pos = ((uint32_t)0x01) << pinpos;
    /* Get the port pins position */
    currentpin = (GPIO_InitStruct->GPIO_Pin) & pos;

    if (currentpin == pos)
    {
      GPIOx->MODER  &= ~(GPIO_MODER_MODER0 << (pinpos * 2));
      GPIOx->MODER |= (((uint32_t)GPIO_InitStruct->GPIO_Mode) << (pinpos * 2));

1.      FOR循环中的循环条件告诉我们是依次检索16个引脚。

2.      方法体中的第一条语句表示循环左移得到我们要检索的引脚或是要操作的引脚,语句2表示该待检索的引脚与我们预先设定的引脚相与,如果依然得到待检索的引脚值,那么表明就是我们选定的引脚。

3.      按照2的思路如果条件成立,执行IF方法体,现在先介绍一下GPIOX->MODER,两层意思,GPIOX实质上代表的是某GPIO的端口地址,经过GPIO_TypeDef强制转换成指针,再宏定义得来的。所以现在GPIOX指向的是GPIO_TypeDef结构体的首地址。即这句话表示指向GPIOX的端口模式寄存器,通过对该寄存器赋值控制端口模式。

4.      现在需要了解GPIO_MODER_MODER0什么意思,这条程序的具体含义是什么,否则,下一条更是完全不懂。

                              
                             
 5.      其实这个名字就是表示该寄存器的端口配置位,占用寄存器(前面已经说过它有32位,16个引脚,全部配置共占用32位)两个位。这样就好理解了,那句程序的意思就可以理解为对寄存器操作了。

6.      这里还需解释一个东西就是为什么程序里要PINS*2,我已经讲了,每个端口配置位占用寄存器两个位,即你要控制(或者说配置)那个引脚,在配置寄存器的位时,位数为引脚号乘以2.。

7.      GPIOx->MODER  &= ~(GPIO_MODER_MODER0 << (pinpos *2));

#define GPIO_MODER_MODER0                    ((uint32_t)0x00000003)

所以这句程序的意思是先移位得到(前面我们要配置引脚9和11)0000 0000 0000 1100 0000 0000 0000 0000取反得到1111 11111111 0011 1111 1111…,相与等于1111 1111 1111 0011 …,即将除要配置的引脚之外的引脚在寄存器内的配置位全置为11,

8.  GPIOx->MODER |= (((uint32_t)GPIO_InitStruct->GPIO_Mode)<< (pinpos * 2));

这里的话自己根据C语言的运算语法算一遍很容易发现是将我们选定的引脚配置成我们初始化时设定的GPIO口模式。具体实现是通过将模式寄存器的相应引脚配置位配置成相应的代码模式(即我们初始化时设定的GPIO_MODE).这样一来未选定的引脚被配置为模拟模式,选定的引脚则配置成想配置的模式了。

9. 

至此我已经解释完了第<3>句,实际上附带介绍了第四句,即怎么将控制的引脚配置成相应的模式。第<5>句的配置道理其实同第四句,也是通过控制相应的寄存器,将寄存器相应的引脚配置位置为对应的模式代码。


具体程序见最后两句。(参照相应寄存器即模式代码如下)

#define GPIO_PUPDR_PUPDR0                    ((uint32_t)0x00000003)

#define GPIO_PUPDR_PUPDR0_0                  ((uint32_t)0x00000001)

#define GPIO_PUPDR_PUPDR0_1                  ((uint32_t)0x00000002)

10.             然后相信各位对于速度的配置也能了然于胸了。

11.             相应的GPIO_Init();这个函数就自然知道是啥意思了。


推荐阅读

史海拾趣

Gamma Microelectronics ( APM )公司的发展小趣事

2007年10月,G24i在英国建立了DSSC的30MW试产线,这是全球范围内的重要里程碑。这条生产线具备高效的生产能力,能够在不到3小时的时间内制造出长达约800米的太阳能电池,生产速度高达每分钟4米以上。这一成就不仅展示了G24i在规模化生产方面的实力,也为后续的商业化应用奠定了产能基础。尽管初期商业化模组产品的转换效率仅为3%,但G24i通过不断优化生产工艺和降低成本,逐步提升了产品的市场竞争力。

Digital Core Design公司的发展小趣事

随着《古墓丽影》系列游戏的成功,Core Design的团队迅速扩张。当PS2主机即将推出时,索尼和Core Design都对这款新主机充满期待。为了抓住这一机遇,Core Design将“古墓丽影”的制作团队从最初的12人增加到60人,随后又增加到了100人。这一举措展示了Core Design对市场和技术的敏锐洞察力和决心。

Alpha (Taiwan)公司的发展小趣事

面对全球环保意识的日益增强,Alpha (Taiwan)公司积极响应号召,致力于绿色电子产品的研发和生产。公司投入大量资金研发环保材料和技术,成功推出了一系列绿色环保电子产品。这些产品不仅具有优异的性能,而且在使用过程中对环境的影响较小,符合现代社会的环保理念。此外,公司还积极参与环保公益活动,宣传环保知识,推动电子行业的绿色发展。

力芯微(ETEK)公司的发展小趣事

在初创期,力芯微公司专注于DVD、音响、机顶盒及遥控器等传统电子市场的芯片研发及销售。公司凭借对市场的敏锐洞察,与步步高、TCL、Sony、飞利浦、富士康等知名品牌建立了稳固的合作关系。这些合作不仅为公司带来了稳定的收入,也为力芯微积累了与品牌客户合作的宝贵经验。

ddm hopt + schuler GmbH & Co KG公司的发展小趣事

随着全球对环境保护意识的增强,ddm hopt + schuler公司开始关注绿色制造和可持续发展。公司投入大量资源进行绿色生产技术的研发,成功开发出一系列节能、环保的电子生产设备。这些设备在保障生产效率和产品质量的同时,显著降低了能源消耗和废弃物排放。这一举措不仅提升了公司的品牌形象,也赢得了客户和市场的广泛赞誉。

BLT Circuit Services公司的发展小趣事

随着公司实力的不断增强,BLT Circuit Services开始积极拓展国内外市场。公司积极参加各类行业展会,与国内外同行进行深入交流与合作,不断提升公司的知名度和影响力。同时,公司还根据市场需求调整产品策略,推出了一系列符合市场需求的新产品,进一步扩大了市场份额。

问答坊 | AI 解惑

请问谁有LTH1550-01的实用电路图?

本帖最后由 paulhyde 于 2014-9-15 09:11 编辑 请问谁有LTH1550-01的实用电路图?或中文资料?  …

查看全部问答>

发点CH451芯片控制的代码,详细讲解,正规代码!!!!

FPGA应用于很多场合。比如通信,验证,接口控制。 接口比较多,相对多个PLD来说,成本和速度节省。 CH451资料比较多,上网可以下载。 具体分析下怎么用FPGA写这些控制。 :D :D 第一个阅读器件,了解器件初始化过程,以及显示数据具体过程。也 ...…

查看全部问答>

大家第一个项目都做了多久啊?

最近调试项目都快吐了,想看看牛人们都用了多少时间完成自己第一个项目的啊,大家交流交流~…

查看全部问答>

民用监控误区:PC/嵌入式DVR如何选择?

◆选购安防监控录像系统的误区     目前民用级安防监控主要是来源一些小规模的商业需求,相比一些大规模的安防工程,对于监控的线路基本维持在8路以内,自己购买设备便可以进行调试安装。     目前这类民用级监控系统的选 ...…

查看全部问答>

VS2005里面,我自己创建的类(没有通过类向导创建)怎么突然不能增加成员函数了(昨天还是可以的),增加成员变量是可以的,怎么回事啊?!

VS2005里面,我自己创建的类(没有通过类向导创建)怎么突然不能增加成员函数了(昨天还是可以的),增加成员变量是可以的,怎么回事啊?!…

查看全部问答>

请问在ARM中如何用汇编语言实现冒泡排序算法

请问在ARM中如何用汇编语言实现冒泡排序算法?我想定义一组数字,以此来用冒泡法来排序,不知道如何定义这组数,又该如何引用啊?…

查看全部问答>

请问一个usb的问题

问一下 pdiusbd12中检查它的型号的时候,为什么要读取两次? U32 tmp;                 D12Cmd = 0xfd;         tmp = D12Dat;         tmp = tmp…

查看全部问答>

HP的待遇以及文化!!

帮我拿个主意!!我最近去了一家培训公司面试,面试的是HP的电话技术支持工程师 400多个人面试,最后通过的就六个人,其中有我一个!!! 通知我明天去复试,是HP亲自面试,各位说我去不去??? 最主要的是这家公司要岗前培训,有费用4800多元 ...…

查看全部问答>