美文网首页我爱编程
《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