详解GPIO:了解树莓派的通用输入输出引脚

以STM32F4系列的单片机做例子

一.引入 

    单片机最小系统的组成: 
        
        芯片 + 供电电路 + 复位电路 + 时钟(晶振)电路
        
    一个完成的系统的组成 
    
        最小系统 + 项目所需要的其他硬件(外设)
    
    芯片: 
        
        整个系统的核心 相当于人类的大脑  会提供引脚与外部电路相连接 
        引脚(俗称 官方称呼“GPIO”)

二. GPIO 

    GPIO是什么? 
    General Purpose Input Output 通用功能输出输出 
    GPIO就是从芯片内部引出来一根功能复用的口线(电线)
    功能复用是指:GPIO的引脚可以由CPU配置成不同的功能 
    比如:输入功能 输出功能 模拟功能 复用功能等等
    
    分析GPIO内部结构图如picture/STM32F4XX_GPIO内部结构.PNG 
    通过图我们可以得知 每个GPIO可以独立地被配置成不同的功能。 
    GPIO配置功能如下: 
    (1)输入功能 
    
        CPU可以通过该GPIO的来获取外部电路输入的一个电平状态 
        输入功能又可以分为几种模式: 
        a.带上拉的输入(input pull-up)
            默认接一个上拉电阻  
            此时就算IO引脚没有外部输入信号时  CPU也能读到一个高电平 
            只有在外部电路输入低电平的时候 CPU读取到的才是低电平


        b.带下拉的输入 
            默认接一个下拉电阻 
            此时就算IO引脚没有外部输入信号时 CPU也能读到一个低电平
            只有在外部电路输入高电平的时候 CPU读取到的才是高电平  

         
        c.输入悬空
            既不接上拉电阻 也不接下拉电阻  
            这种情况下 IO引脚的电平状态完全由外部输入所绝对 此时CPU可以通过读取数据的
            操作来获取外部电路的工作状态


        d.模拟输入
            该引脚被设置为模拟输入的时候 能够获取到模拟信号 
            通过ADC转换为数字量

 


    
    (2)输出功能 
    
        CPU可以通过该GPIO口往外部输出一个电平状态(相当于可以控制外部电路工作)
        输出功能也可以分为以下两种模式 
        a.输出推挽 (PP: push-pull)
            CPU往外写高电平(1)时,此时引脚输出一个高电平 
            CPU往外写低电平(0)时,此时引脚输出一个低电平  

 
            
        b.输出开漏  (OD: open drain)
            不输出电压 
            CPU往外写低电平(0)时  此时引脚接VSS(GND)相当于接地
            CPU外外写高电平(1)时  此时引脚的电平状态由上下拉电阻决定

 

 

 

 

 

    (3)复用功能 
        
        复用功能是指GPIO口用作其他的外设的功能口线 
        比如:
                I2C USART SPI等等 
        
        每个GPIO口都可以被配置成多达16中复用功能 
        具体哪个引脚可以被复用成哪种功能 需要看原理图
    
    
    STM32F4xx共有144个GPIO引脚
    分为九组 记为GPIOA , GPIOB … GPIOI
             简写PA PB … PI 
    
    每组有16根引脚 编号从0~15 
    也就是说: 
            比如GPIOA这一组就有  
            GPIOA0                     PA0
            GPIOA1                     PA1 
            GPIOA2                     PA2
            …
            GPIOA15                 PA15
    
    
    而这些GPIO的功能 都有独立的寄存器组(不同的GPIO硬件控制器)来配置他们  
    也就是说我们如果要使用比如GPIO口的输入功能的话 我们首先需要把对应寄存器组配置好。
    那么如果我们要去配置(访问)寄存器的话 就必须知道寄存器的地址  
    
    每组GPIO的地址分布如下:     参考:第 192 页的第 7.4.11 节:GPIO 寄存器映射
        
        
                边界地址               外设            总线: 
        0x4002 2000 – 0x4002 23FF     GPIOI    
        0x4002 1C00 – 0x4002 1FFF     GPIOH
        0x4002 1800 – 0x4002 1BFF     GPIOG        
        0x4002 1400 – 0x4002 17FF     GPIOF
        0x4002 1000 – 0x4002 13FF     GPIOE            AHB1
        0x4002 0C00 – 0x4002 0FFF     GPIOD
        0x4002 0800 – 0x4002 0BFF     GPIOC
        0x4002 0400 – 0x4002 07FF     GPIOB
        0x4002 0000 – 0x4002 03FF     GPIOA
    
    
    边界地址:指对应的寄存器组的起始地址(基址)和结束地址
    外设:     该寄存器组对应的硬件控制器
    总线:  该硬件控制器所处的系统时钟总线 
            请注意:任何一个硬件控制器想要去正常工作 都必须开启(使能)时钟 
                    而总线 就是给硬件控制器提供时钟的 
    

那么有哪些寄存器呢?分别有什么用呢? 
    

三.STM32F4XX GPIO寄存器 

    每个通用 I/O 端口包括 
    4 个 32 位配置寄存器(GPIOx_MODER、GPIOx_OTYPER、GPIOx_OSPEEDR 和 GPIOx_PUPDR)
    2 个 32 位数据寄存器(GPIOx_IDR 和GPIOx_ODR)
    1 个 32 位置位/复位寄存器 (GPIOx_BSRR)
    1 个 32 位锁定寄存器(GPIOx_LCKR) 
    2 个 32 位复用功能选择寄存器(GPIOx_AFRH 和 GPIOx_AFRL)。
        
    (1)GPIOx_MODER :功能模式选择寄存器
        
        偏移地址:0x00  (寄存器地址 = 基址 + 偏移地址)
        比如 :GPIOA_MODER的地址 0x40020000 + 0x00 = 0x40020000
        
        该寄存器用来控制GPIOx(x=A,B,C…I)组的16个引脚的模式(4种:输入、输出,模拟,复用)
        一个寄存器是32bits 一组GPIOx共有16个引脚
        每个GPIO引脚占2bits
        2bits正好可以表示4种状态
        编号为y(y=0,1…15)的GPIO引脚在寄存器中的比特位为GPIOx_MODER[2y+1,2y]
        具体配置如下: 
            GPIOx_MODER[2y,2y+1]                模式 
                00                                输入模式 
                01                                输出模式 
                10                                复用模式 
                11                                模拟模式
    
    
        例子: 用c代码将PF9配置称为输出模式
        
        分析: 
            GPIOF组寄存器的起始地址(基址):0x4002 1400  
            GPIOx_MODER的偏移地址是0x00 
            so:
                GPIOF_MODER的寄存器地址:0x4002 1400 + 0x00 = 0x4002 1400
                
            如果要将PF9设置为输出模式 就需要将 
                GPIOF_MODER[2*9+1:2*9]  
                GPIOF_MODER[19:18] ==> 01
            
            把地址为0x40021400的寄存器中的bit19置为0 bit18置为1 怎么做到这两点呢?    
            通过地址我们就可以将寄存器中的bit置位 
            在STM32中 用unsigned long来表示地址的值
            
            unsigned long * p = (unsigned long *)0x4002 1400
            
            但是一般情况下我们会在地址的前面加上volatile 变成如下: 
            
            volatile unsigned long * p = (volatile unsigned long *)0x4002 1400
            
            volatile的作用是作为指令关键字 禁止编译器优化 访问的就是实际地址
            而不会被编译器优化成别的地址 一般用于多线程的全局变量 中断处理函数访问
            的全局变量 状态寄存器。
            
            那么我们就可以通过指针p将地址0x40021400的寄存器中的bit19置为0 bit18置为1 
            操作如下: 
                
                    xxxxxxxxxxxx yy xxxxxxxxxxxxxxxxxx
                    
                    <—-12—->    <——  18 ——>
                    
                &    111111111111 01 111111111111111111        <=先把bit19置为0    
            
            ===>    xxxxxxxxxxxx 0y xxxxxxxxxxxxxxxxxx
            
            
            1<<19    000000000000 10 000000000000000000
    
          ~(1<<19)    111111111111 01 111111111111111111
    
    
                    *p = *p & ~(1<<19)
    
    
            再把bit18置为1 
                
                类似与上面操作 为: 
                    
                    *p = *p | (1<< 18)
            
            所以我们分两步完成这个操作: 
                
                *p = *p & ~(1<<19)    
                *p = *p | (1<< 18)
    
            但是实际上面的操作对寄存器进行了两次操作 效率太低 
            有点耗费硬件资源  我们对寄存器的修改必须一步到位 
            
            所以我们会先定义一个中间变量 用来记录寄存器的值 
            然后再通过中间变量 一步到位去修改寄存器的值 如下操作 
            
                unsigned long r = 0 ;
                r = *p ;  //先用r保存寄存器中的值 并按照需求修改r值 
                
                r &= ~(1<<19);
                r |= (1<<18);
                
                *p = r; //通过中间变量 一步到位修改寄存器的值
    
    
        例子: 将PA0配置为输入模式 
               用C语言配置一次  用汇编配置一次
    
                C语言: 
                
                volatile unsigned long * p = (volatile unsigned long * )0x40020000 
                unsigned long r = 0 ;
                r = *p;
                
                r &= ~3 ; //把bit1和bit0都设置为0   00表示输入模式 
                
                *p = r;
                
                汇编: 
                
                LDR R0,=0X40020000 
                LDR    ,R1,[R0]         ;R1就相当于r     此指令相当于r = *p
                BIC    R1,R1,#0X03        ; r &= ~3  
                STR R1,[R0]
                
                
    (2)GPIOx_OTYPER    :Output Type Register 输出类型选择寄存器

        偏移地址:0x04
        该寄存器用来选择GPIOx(x=A,B…I)这组的16个GPIO引脚的输出类型 
        寄存器有32bits 
        低16个bit用于保存对于编号引脚的输出类型 高16bit保留
        一个bit保存一个引脚 
        一个bit有两种状态 分别对应开漏输出和推挽输出
        
        每个GPIO引脚占1bit 编号为y(y=0,1,2…15)的引脚在该寄存器中对于的bit为GPIOx_OTYPER[y]
        具体配置如下: 
            
            GPIOx_OTYPER[y]            输出类型 
                1                    输出开漏(OD)
                0                    输出推挽(PP)
                
        
                
    (3)GPIOx_OSPEEDR:Output Speed Register 输出速率寄存器 

        偏移地址:0x08
        用于控制GPIOx组的16个GPIO引脚的输出速率 
        每个引脚占2bit 
        编号为y的引脚在该寄存器中的bit位是GPIOx_OSPEEDR[2y+1:2y]
        具体配置如下: 
        
            GPIOx_OSPEEDR                速率 
            
                00                        2MHZ 
                01                        25MHZ 
                10                        50MHZ 
                11                        30pf则为100MHZ 
                                        15pf则为80MHZ
        
                
                
    (4)GPIOx_PUPDR:Pull Up Pull Down Register 端口上拉/下拉寄存器
    
        偏移地址:0x0c 
        该寄存器用来控制GPIOx组的16个引脚的上拉/下拉选择
        每个GPIO引脚占2bits 
        编号为y的GPIO引脚在该寄存器中所在的bit为GPIOx_PUPDR[2y+1:2y]
        具体配置如下: 
        
            GPIOx_PUPDR                上下拉选择 
                00                    无上拉、无下拉 
                01                    上拉 
                10                    下拉 
                11                    保留
        
                
    (5)GPIOx_IDR: Input Data Register 输入数据寄存器 

        偏移地址:0x10 
        该寄存器用来表示GPIOx这组的16个GPIO引脚的输入的电平状态值 
        每个GPIO引脚占1bits  该寄存器中高16bit保留没有使用 
        低16bit表示x组的16个引脚的电平状态
        比如:GPIOx_IDR[0] ==>表示的就是该组的第0个引脚GPIOx0的输入电平状态
        具体配置如下: 
        
            GPIOx_IDR[y]            编号为y的引脚的输入电平状态 
                1                        高电平 
                0                        低电平
        
        比如: 
            CPU想要知道GPIOA0这个引脚输入的是高电平还是低电平? 
            思路: 
                    if(GPIOA_IDR & 0X01  == 0X01)
                    {
                        PA0为高电平
                    }
                    else 
                    {
                        PA0为低电平
                    }
            
            ===> 
                    
                volatile unsigned long * p =(volatile unsigned long *)(0x40020000+0x10)
                if(*p & 0x01)
                {
                    PA0为高电平
                }
                
    (6)GPIOx_ODR:Output Data Register 端口输出数据寄存器 
    
        偏移地址: 0x14
        该寄存器保存了该组16个GPIO引脚的输出电平状态 
        高16bit保留的 低16个bit就是对于编号的引脚的输出电平状态 
        具体配置如下: 
        
            GPIOx_ODR[y]            编号为y的引脚的输出电平状态 
                1                        高电平 
                0                        低电平
    
            
                
                
                
    (7)GPIOx_BSRR:Bit Set Reset Register 端口置位/复位寄存器 
            
        偏移地址:0x18
        置位:set  把bit位置为1 
        复位:reset 把bit位置为0
        该寄存器用来表示GPIOx组的16个GPIO引脚的输出状态 
        其中:
            高16bits为端口复位寄存器 
            低16bits为端口置位寄存器
        
        这个寄存器有点特殊  写1有效 写0无效
        将GPIOx_BSRR[31:16]置为1 表示将GPIOx15~GPIOx0设置为0 
        将GPIOx_BSRR[15:0]置为1 表示将GPIOx15~GPIOx0设置为1
        实现效果跟GPIOx_ODR一样 用来设置GPIO引脚的输出状态
        
        
    (8)GPIOx_LCKR    :锁定寄存器  

    (9)GPIOx_AFRL:复用功能低位寄存器     偏移地址:0x20
    (10)GPIOx_AFRH:复用功能高位寄存器    偏移地址:0x24
        
        GPIOx_AFRL和GPIOx_AFRH这两个寄存器是放在一起使用的
        
        AFR:Alternate Function Register 复用功能选择寄存器 
        
        因为一个GPIO引脚最多有16个复用功能 那么1个GPIO引脚需要4个bit 
        所以16个引脚就需要16*4 = 64bits 也就是2个寄存器的空间 
        
        GPIO引脚编号为0-7由GPIOx_AFRL进行配置 
                      8-15由GPIOx_AFRH进行配置
        
        
        具体的值表示哪个复用功能或者引脚有哪些复用功能 
        需要结合电路原理图和功能手册来看
        
        
        
四. STM32F4XX GPIO时钟使能 

 

    根据上述的寄存器 就可以去实现所有基于GPIO能够完成的功能配置了 
    比如: 
        点亮led灯
        获取key按键的状态(按下/松开)
        控制蜂鸣器等等 
        
    但是我们在之前就提到过 任何一个硬件控制器想要工作 都必须去实现时钟使能
    (GPIO所有分组全部属于AHB1时钟总线)
    那么时钟的相关配置 请参考RCC部分(数据手册的第六章)    
    RCC: Reset Clock Control 复位时钟控制 基址:0x40023800
    那么现在我们的目的是使能GPIO分组时钟: 
        RCC AHB1外设时钟使能寄存器(RCC_AHB1ENR)
        偏移地址:0x30 
        该寄存器的第0位到第8位分别控制GPIOA到GPIOI组时钟的使能: 
            1        使能对于GPIO分组的时钟 
            0        禁止对应GPIO分组的时钟 
            
    比如:使能GPIOF组时钟  
          ==》RCC_AHB1ENR[5] -> 1 
          
        C语言实现: 
        
        volatile unsigned long * p = (volatile unsigned long *)(0x40023800 + 0x30);
        unsigned long r = 0; 
        r = *p;
        r |= 1<<5; 
        *p = r;
        
        
    GPIOF组的时钟使能后 就可以去配置GPIOF组的GPIO引脚了 
    配置后这些GPIO引脚可以与连接硬件电路正常工作了
    
    
    总结: 
        利用寄存器来实现GPIO功能配置的步骤 
        
        1)配置GPIO分组时钟(RCC_AHB1ENR)
        2)配置GPIO功能模式(GPIOx_MODER)
        3)配置输出类型(输出功能)
        4)配置输出速率 
        5)配置上拉/下拉
        6)如果是输入模式 则通过GPIOx_IDR可以获取外部电路工作状态 
          如果是输出模式 则通过GPIOx_ODR或这GPIOx_BSRR来向外部输出一个电平信息 
          如果是复用模式 则后面再讲


          
        
 
 

 

    

物联沃分享整理
物联沃-IOTWORD物联网 » 详解GPIO:了解树莓派的通用输入输出引脚

发表评论