美文网首页我爱编程
《GPIO那点事》

《GPIO那点事》

作者: AdeSimonire | 来源:发表于2018-08-04 18:20 被阅读0次

    GPIO

    - 基于8086指令集的51单片机

    51单片机有4个双向并行IO口:P0、P1、P2、P3

    • P0为三态双向口,其驱动能力比较大,可驱动高达8个TTL电路
    • P1、P2、P3为准双向口,其负载能力比较低,只能驱动4个TTL电路

    PS:作为输入时,IO总线需要被拉成高电平,故称准双向口

    - 基于ARM cortex M3 的STM32单片机

    当单片机类型从8位升级到32位之后,51的io口寄存器驱动方法就不再适合32系列的单片机,ARM便采用GPIO的形式驱动io口。

    General Purpose Input Output (通用输入/输出接口)简称为GPIO,或总线扩展器,人们利用工业标准I2C、SMBus或SPI接口简化了I/O口的扩展。当微控制器或芯片组没有足够的I/O端口,或当系统需要采用串行通信或控制时,GPIO产品能够提供额外的控制和监视功能。

    STM32 的 IO 口相比 51 而言要复杂得多,所以使用起来也困难很多。首先 STM32 的 IO 口 可以由软件配置成如下 8 种模式:

    1. 输入浮空 IN_FLOATING
    2. 输入上拉 IPU
    3. 输入下拉 IPD
    4. 模拟输入 AIN
    5. 开漏输出 OD
    6. 推挽输出 PP
    7. 推挽式复用功能 AF_PP
    8. 开漏复用功能AF_OD

    一般常用的就是PP、IPU和IPD,AF_PP用在复用gpio的功能上,stm32的每个io口都是可以自由编程的,但每一个io口寄存器都是要32位访问,这里就带来了和51完全不同的操作方式,因为八个位是一个一个,对于16进制来说,就是四位。也就是没操作一次io口,用寄存器的方法配置输出模式:


    IO寄存器.png

    但是对于嵌入式开发来说,如此庞大的寄存器配置,无疑对工程师来说是负担。所以st专门开发了标准库SPL、硬件抽象层库HAL和底层库LL。
    其中的LL是兼容SPL和HAL的,所以最新推出,原因是HAL库对于大部分32芯片是完全兼容的,但是是面向底层的,所以相比于SPL来说,还是比较难读懂的。不像SPL是通俗易懂的,完全可以通过函数名来打包自己的硬件库函数进行操作。

    例如:
    使用寄存器对stm32的PB5和PE5进行io配置高电平:

    void LED_Init(void) {
    GPIOB->BRR=GPIO_Pin_5;//LED0亮
    
    GPIOE->BRR=GPIO_Pin_5;//LED1亮
     }
    

    使用SPL库对stm32的PB5和PE5进行io配置高电平:

    void LED_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    RCC_APB2PeriphClockCmd
    (
    RCC_APB2Periph_GPIOB| RCC_APB2Periph_GPIOE, 
    ENABLE
    );//使能 PB,PE 端口时钟 
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; 
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
    GPIO_Init(GPIOB, &GPIO_InitStructure); 
    GPIO_SetBits(GPIOB,GPIO_Pin_5);//PB.5 输出高
    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_Init(GPIOE, &GPIO_InitStructure); 
    GPIO_SetBits(GPIOE,GPIO_Pin_5);//PE.5 输出高
    }
    

    使用HAL库对stm32的PB0和PB1进行io配置高电平:

    void LED_Init(void)
    {
    GPIO_InitTypeDef GPIO_Initure;
    __HAL_RCC_GPIOB_CLK_ENABLE();           //开启GPIOB时钟
        
    GPIO_Initure.Pin=GPIO_PIN_0|GPIO_PIN_1; //PB1,0
    GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出
    GPIO_Initure.Pull=GPIO_PULLUP;          //上拉
    GPIO_Initure.Speed=GPIO_SPEED_HIGH;     //高速
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);
        
    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_0,GPIO_PIN_SET);   
    //PB0置1,默认初始化
    HAL_GPIO_WritePin(GPIOB,GPIO_PIN_1,GPIO_PIN_SET);   
    //PB1置1,默认初始化
    }
    

    对于操作io口的配置方式,st给出了三种方法来编程io口,各有各的优点和缺点,在使用过程中要考虑实际情况来使用。

    GPIO位操作

    位操作代码在 sys.h 文件中,实现对 STM32 各个 IO 口的位操作,包括读入和输出。当然 在这些函数调用之前,必须先进行 IO 口时钟的使能和 IO 口功能定义。此部分仅仅对 IO 口进 行输入输出读取和控制。
    位带操作简单的说,就是把每个比特膨胀为一个 32 位的字,当访问这些字的时候就达到了 访问比特的目的,比如说 BSRR 寄存器有 32 个位,那么可以映射到 32 个地址上,我们去访问 这 32 个地址就达到访问 32 个比特的目的。这样我们往某个地址写 1 就达到往对应比特位写 1 的目的,同样往某个地址写 0 就达到往对应的比特位写 0 的目的。位带操作在实际开发中可能只是用来 IO 口的输入输出还比较方便,其他操作在日常开发中也基本很少用。

    //位带操作,实现51类似的GPIO控制功能
    //具体实现思想,参考<<CM3权威指南>>第五章(87页~92页).
    //IO口操作宏定义
    #define BITBAND(addr, bitnum)   ((addr & 0xF0000000)
    +0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2)) 
    #define MEM_ADDR(addr)     *((volatile unsigned long  *)(addr)) 
    #define BIT_ADDR(addr, bitnum)     MEM_ADDR(BITBAND(addr, bitnum)) 
    //IO口地址映射
    #define GPIOA_ODR_Addr    (GPIOA_BASE+12) //0x4001080C 
    #define GPIOB_ODR_Addr    (GPIOB_BASE+12) //0x40010C0C 
    #define GPIOC_ODR_Addr    (GPIOC_BASE+12) //0x4001100C 
    #define GPIOD_ODR_Addr    (GPIOD_BASE+12) //0x4001140C 
    #define GPIOE_ODR_Addr    (GPIOE_BASE+12) //0x4001180C 
    #define GPIOF_ODR_Addr    (GPIOF_BASE+12) //0x40011A0C    
    #define GPIOG_ODR_Addr    (GPIOG_BASE+12) //0x40011E0C    
    
    #define GPIOA_IDR_Addr    (GPIOA_BASE+8) //0x40010808 
    #define GPIOB_IDR_Addr    (GPIOB_BASE+8) //0x40010C08 
    #define GPIOC_IDR_Addr    (GPIOC_BASE+8) //0x40011008 
    #define GPIOD_IDR_Addr    (GPIOD_BASE+8) //0x40011408 
    #define GPIOE_IDR_Addr    (GPIOE_BASE+8) //0x40011808 
    #define GPIOF_IDR_Addr    (GPIOF_BASE+8) //0x40011A08 
    #define GPIOG_IDR_Addr    (GPIOG_BASE+8) //0x40011E08 
     
    //IO口操作,只对单一的IO口!
    //确保n的值小于16!
    #define PAout(n)   BIT_ADDR(GPIOA_ODR_Addr,n)  //输出 
    #define PAin(n)    BIT_ADDR(GPIOA_IDR_Addr,n)  //输入 
    
    #define PBout(n)   BIT_ADDR(GPIOB_ODR_Addr,n)  //输出 
    #define PBin(n)    BIT_ADDR(GPIOB_IDR_Addr,n)  //输入 
    
    #define PCout(n)   BIT_ADDR(GPIOC_ODR_Addr,n)  //输出 
    #define PCin(n)    BIT_ADDR(GPIOC_IDR_Addr,n)  //输入 
    
    #define PDout(n)   BIT_ADDR(GPIOD_ODR_Addr,n)  //输出 
    #define PDin(n)    BIT_ADDR(GPIOD_IDR_Addr,n)  //输入 
    
    #define PEout(n)   BIT_ADDR(GPIOE_ODR_Addr,n)  //输出 
    #define PEin(n)    BIT_ADDR(GPIOE_IDR_Addr,n)  //输入
    
    #define PFout(n)   BIT_ADDR(GPIOF_ODR_Addr,n)  //输出 
    #define PFin(n)    BIT_ADDR(GPIOF_IDR_Addr,n)  //输入
    
    #define PGout(n)   BIT_ADDR(GPIOG_ODR_Addr,n)  //输出 
    #define PGin(n)    BIT_ADDR(GPIOG_IDR_Addr,n)  //输入
    

    GPIO的小tips

    1. stm32的io口虽然很多支持ft格式的电平,但是电流比较小,难以带的动大功率的电机,例如步进和空心杯等等
    2. stm的io口和其他类型的单片机一样,对于SPI和IIC都可以进行模拟电平时间进行模拟通信协议进行通信,对于初学者来说,最好照着时序图操作一遍,对于io口和通信协议的配置的记忆有着很大的帮助。

    相关文章

      网友评论

        本文标题:《GPIO那点事》

        本文链接:https://www.haomeiwen.com/subject/vtemvftx.html