-->

GPIO

GPIO库函数

GPIO全称General Purpose Input Output,简称通用输入输出外设。在这个系列的芯片中,一共有144个引脚,其中除去一些电源、地、BOOT、晶振等引脚,剩下114个引脚可以作为GPIO口使用。

其实在使用这个芯片的时候ST公司为了让我们有良好的用户体验,每一个外设都给我们写了一个例子。也是在帮助手册中,寻找这个例子。如下在左侧的目录中。

image

需要注意的是,这些初始化之类的动作,一般而言只需要抄。但是步骤还是需要记一下。

点击去就可一看见代码。把代码关键赋值放在下面

#include "stm32f4xx.h"

#define LED1_PIN                         GPIO_Pin_6
#define LED2_PIN                         GPIO_Pin_8		//这两个宏是定义在头文件的,把他放在这里

int main()
{
    GPIO_InitTypeDef  GPIO_InitStructure;					//1.定义结构体
    
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG, ENABLE);	//2.配置时钟,置于配置什么时钟,在寄存器配置时会详细的说
 
   /* Configure PG6 and PG8 in output pushpull mode */
   GPIO_InitStructure.GPIO_Pin = LED1_PIN | LED2_PIN;		
   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
   GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
   GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;			//3.给结构体变量赋值
   GPIO_Init(GPIOG, &GPIO_InitStructure);					//4.初始化结构体
 
   while (1)
   {
     /* Set PG6 and PG8 */		//Set:置位,设置为高电平
     GPIOG->BSRRL = LED1_PIN | LED2_PIN;					//意思是把PG6和PG8设置为高电平
     /* Reset PG6 and PG8 */	//Reset:复位,设置为低电平
     GPIOG->BSRRH = LED1_PIN | LED2_PIN;					//意思是把PG6和PG8设置为低电平
    
    return 0;
}

  • 在创建工程是,我们加入了一个xx_gpio.c这个文件,注意每一个外设都有一个自己的.c源文件和头文件。并且每一个文件都会说明这个外设的使用下面就是gpio外设的说明
 ===============================================================================
                      ##### How to use this driver #####
 ===============================================================================       
 [..]             
   (#) Enable the GPIO AHB clock using the following function
       RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOx, ENABLE);
               
   (#) Configure the GPIO pin(s) using GPIO_Init()
       Four possible configuration are available for each pin:
       (++) Input: Floating, Pull-up, Pull-down.
       (++) Output: Push-Pull (Pull-up, Pull-down or no Pull)
            Open Drain (Pull-up, Pull-down or no Pull). In output mode, the speed 
            is configurable: 2 MHz, 25 MHz, 50 MHz or 100 MHz.
       (++) Alternate Function: Push-Pull (Pull-up, Pull-down or no Pull) Open 
            Drain (Pull-up, Pull-down or no Pull).
       (++) Analog: required mode when a pin is to be used as ADC channel or DAC 
            output.
          
   (#) To get the level of a pin configured in input mode use GPIO_ReadInputDataBit()
            
   (#) To set/reset the level of a pin configured in output mode use 
       GPIO_SetBits()/GPIO_ResetBits()

对于上面的部分解释,放到下面、复用功能之后再说。

image

  • 下面是这个GPIO_InitTypeDef结构体的成员
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;

置于每一个函数的参数,需要自己查看原函数的注释。由于太多,就不说了。

这里将一下这些成员的作用 ,每一个成员旁边都有一个这个参数,可以在这个文件全局搜索这个参数@ref GPIO_pins_define

  • 开漏:开漏不能输出高电平,只能输出低电平。
  • 推挽:推挽可以输出高电平,也可以输出低电平。

GPIO寄存器开发

  • 一般而言,想要控制硬件就要控制硬件的寄存器。

  • 寄存器:寄存器可以存储一个一组bit数据,寄存器是由触发器构成,了解过数电就知道,一组触发器可以构成一个寄存器,触发器存储数据是需要时钟信号的,所以想要把数据写入寄存器中,必须先打开时钟。还有就是每次的复位,为了降低功耗stm32会把所有的外设时钟给关闭,所以需要每一次复位都打开。如果没有打开时钟,那么数据写不进寄存器里面,那么就会导致控制不了硬件。寄存器相当于已经申请好的变量,直接进行赋值即可。

  •   //想对一个寄存器赋值,首先要知道它的物理地址
      
      /*
       * 这两个最长用的公式,必须要牢牢记住
       */
          
      //置位,将寄存器的第n位设置为1
      (*((volatile unsigned int *)(物理地址))) |= (1<<n);
      
      //复位,将寄存器的第n位设置为0
      (*((volatile unsigned int *)(物理地址))) &= ~(1<<n);
    
  • 需要注意的是,stm32内部是很多条总线的,外设都是挂载到总线上面的,根据总线可以知道外设的地址,想要更深入的了解这些总线,需要去参考Cortex-M4的权威指南。

下面是STM32的数据手册的内存分布(数据手册第四章),可以知道的地址,外设的物理地址 = 基地址 + 偏移地址。

image

后面考可以查阅stm32的参考手册,了解GPIO的寄存器这里只讲一个寄存器下面的寄存器作为参考

实际的物理地址就是

物理地址 = (0x4002_1800 + 0x00);

如下给一个IO口设置为输出模式GPIO-->9
//复位,将寄存器的第19位设置为0
(*((volatile unsigned int *)(0x4002_1800 + 0x00))) &= ~(1<<(2*9+1);

//置位,将寄存器的第18位设置为1
(*((volatile unsigned int *)(0x4002_1800 + 0x00))) |= (1<<(2*9);

image

以下的寄存器都是如此。

  • 注意:看完上面的内存映射,应该知道RCC这个外设是挂载到AHB1总线上面的,下面这个寄存器比较复杂一点,但是掌握了上面两个公式,也复杂不到哪里去。

例如想要使能GPIOG这个外设时钟

物理地址 = (0x4002_3800 + 0x30);

如下给一个GPIOG口设置时钟使能

//置位,将寄存器的第6位设置为1
(*((volatile unsigned int *)(0x4002_3800 + 0x30))) |= (1<<(6);

image

下面就是使用寄存器写一个例子

#define RCC_AHB1ENR 	( * (volatile unsigned int *)( 0x40023800 + 0x30 ) ) 	//外设使能时钟AHB1

#define GPIOF_MODER 	( * (volatile unsigned int *)( 0x40021400 + 0x00 ) ) 	//GPIOF模式寄存器
#define GPIOF_OTYPER 	( * (volatile unsigned int *)( 0x40021400 + 0x04 ) ) 	//GPIOF类型寄存器
#define GPIOF_OSPEEDR 	( * (volatile unsigned int *)( 0x40021400 + 0x08 ) ) 	//GPIOF速度寄存器
#define GPIOF_PUPDR 	( * (volatile unsigned int *)( 0x40021400 + 0x0C ) ) 	//GPIOF上下拉寄存器
#define GPIOF_ODR 		( * (volatile unsigned int *)( 0x40021400 + 0x14 ) ) 	//GPIOF输出寄存器


#define GPIOE_MODER 	( * (volatile unsigned int *)( 0x40021000 + 0x00 ) ) 	//GPIOE模式寄存器
#define GPIOE_OTYPER 	( * (volatile unsigned int *)( 0x40021000 + 0x04 ) ) 	//GPIOE类型寄存器
#define GPIOE_OSPEEDR 	( * (volatile unsigned int *)( 0x40021000 + 0x08 ) ) 	//GPIOE速度寄存器
#define GPIOE_PUPDR 	( * (volatile unsigned int *)( 0x40021000 + 0x0C ) ) 	//GPIOE上下拉寄存器
#define GPIOE_ODR 		( * (volatile unsigned int *)( 0x40021000 + 0x14 ) ) 	//GPIOE输出寄存器

int main()
{
	//1.使能GPIOF和GPIOE的时钟
	RCC_AHB1ENR |= (1<<5);
	RCC_AHB1ENR |= (1<<4);
	
	//2.设置GPIOF10的模式、速度、类型、上下拉
	GPIOF_MODER &= ~(1<<(2*10+1));
	GPIOF_MODER |= 	(1<<(2*10));			//设置为输出模式
	
	GPIOF_OTYPER &= ~(1<<10);				//设置为推挽模式
	
	GPIOF_OSPEEDR |= 	(1<<(2*10+1));		
	GPIOF_OSPEEDR |= 	(1<<(2*10));		//设置为速度高速
	
	GPIOF_PUPDR &= ~(1<<(2*10+1));
	GPIOF_PUPDR &= ~(1<<(2*10));			//设置不上下拉
	
	//3.设置GPIOE13的模式、速度、类型、上下拉
	GPIOE_MODER &= ~(1<<(2*13+1));
	GPIOE_MODER |= 	(1<<(2*13));			//设置为输出模式
		
	GPIOE_OTYPER &= ~(1<<10);				//设置为推挽模式
		
	GPIOE_OSPEEDR |= 	(1<<(2*13+1));		
	GPIOE_OSPEEDR |= 	(1<<(2*13));		//设置为速度高速
		
	GPIOE_PUPDR &= ~(1<<(2*13+1));
	GPIOE_PUPDR &= ~(1<<(2*13));			//设置不上下拉
	
	//输出低电平
	
	GPIOF_ODR &= ~(1<<10);
	GPIOE_ODR &= ~(1<<13);

	while(1);
}
  • 对于寄存器开发,其实也没必要怎么复杂,ST公司已经把所有的寄存器都放在一个头文件stm32f4xx.h里面,想要使用这个头文件必须要包含它。

image

随机参考一个GPIO端口的地址,可见这个代码的逻辑时非常清晰的,一步步都是有基地址加偏移地址得到的。

利用ST公司提供的寄存器开发,例程

#include "stm32f4xx.h"

int main()
{
    //1.使能GPIOF和GPIOE的时钟
	RCC->AHB1ENR |= 	(1<<5);
	RCC->AHB1ENR |= 	(1<<4);
	
	//2.设置GPIOF10的模式、速度、类型、上下拉
	GPIOF->MODER &= 	~(1<<(2*10+1));
	GPIOF->MODER |= 	(1<<(2*10));			//设置为输出模式
	
	GPIOF->OTYPER &= 	~(1<<10);				//设置为推挽模式
	
	GPIOF->OSPEEDR |= 	(1<<(2*10+1));		
	GPIOF->OSPEEDR |= 	(1<<(2*10));		//设置为速度高速
	
	GPIOF->PUPDR &= 	~(1<<(2*10+1));
	GPIOF->PUPDR &= 	~(1<<(2*10));			//设置不上下拉
	
	//3.设置GPIOE2的模式、速度、类型、上下拉
	GPIOE->MODER &= 	~(1<<(2*2+1));
	GPIOE->MODER &= 	~(1<<(2*2));			//设置为输入模式

	GPIOE->PUPDR &= 	~(1<<(2*2+1));
	GPIOE->PUPDR &= 	~(1<<(2*2));			//设置不上下拉
	
	//输出低电平
	GPIOF->ODR &= 		~(1<<10);
    
    while(1);
    
    return 0;
}
  • 注意比如有一些寄存器,比如一些只读寄存器,它一次读取只能把所有的比特位(32位读取出来),一次性读取32个比特位

image

  • 输入模式和输出模式差不读查看上面的gpio.c文件如何使用这个驱动,他就会说需要配置什么模式。
posted @ 2024-07-07 13:02  wuju  阅读(27)  评论(0)    收藏  举报