Linux学习笔记4-GPIO(2)

书接上回,先回顾下STM32时的GPIO初始化过程,随便找个之前写的工程代码gpio.c文件

void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LED0_GPIO_Port, LED0_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pins : PEPin PEPin */
  GPIO_InitStruct.Pin = Key1_Pin|Key0_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

  /*Configure GPIO pin : PtPin */
  GPIO_InitStruct.Pin = LED1_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(LED1_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : PtPin */
  GPIO_InitStruct.Pin = Key_up_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLDOWN;
  HAL_GPIO_Init(Key_up_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : PtPin */
  GPIO_InitStruct.Pin = LED0_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(LED0_GPIO_Port, &GPIO_InitStruct);

}

可以看到,单片机GPIO的初始化分为四个步骤

  1. 使能指定GPIO的时钟
  2. 初始化GPIO,比如输入还是输出,上下拉,速度
  3. 设置复用功能(上面这个例子好像没有)
  4. 如果是输出,要初始化输出高电平还是低电平,即使用HAL_GPIO_WritePin()函数

上一章其实把复用、速度设置功能弄清楚了,接下去要看时钟、设置输入输出这几个怎么做。
通过阅读手册,当IO用作GPIO时需要设置的寄存器一共有8个DR, GDIR, PSR, ICR1, ICR2, EDGE_SEL, IMRISR

I.MX6UL一共有5组GPIO,GPIO1-GPIO5,每组GPIO都有这8个寄存器。m每组GPIO最大有32个口,所以这几个寄存器基本上都是32位,每一位对应一个GPIO口,具体来看:

  • GDIR寄存器:用来设置某个GPIO口是输入还是输出,输入则设置相应的位为0,输出的话则设置为1。比如要设置GPIO1_IO00为输入,则GPIO1.GDIR = 0,设置GPIO1_IO00输入,GPIO1_IO01为输出,则GPIO1.GDIR = 2(即0x0010)
  • DR寄存器:设置相应的IO口输出的高低电平。1为高电平,0为低电平。例如要设置GPIO1_IO01为低电平,IO00为高电平,则GPIO1.DR = 0x0001,当然GDIR寄存器要设置清楚。
  • PSR寄存器,也是32位,读取相应的位可获取该端口的高低电平值
  • ICR1和2,分别用于IO0-IO15(ICR1)和IO16-IO31(ICR2)配置中断触发方式,每个口用两位来配置:00-低电平触发;01-高电平触发;10-上升沿触发;11-下降沿触发。比如要设置GPIO1_IO15为上升沿触发,那么GPIO1.ICR1 = 2<<30(不是左移15位哦)。GPIO中断我后面会重点学习,还会打交道
  • IMR和ISR是成对的,IMR是写,即控制GPIO中断是否使能,而ISR是读,某个GPIO中断使能时则这个寄存器相应的位是1
  • 最后的EDGE_SEL寄存器用来设置边沿中断,它可以覆盖ICR1和2的设置。如果相应的位被置1,则对应的GPIO是上升沿和下降沿双触发,不用去看ICR1和2的设置。

以上就相当于单片机的GPIO属性设置,接下来看时钟。I.MX6U和STM32是类似的,每个外设的时钟都可以独立使能或禁止,这样可以关掉无关外设的时钟,省电。需要使能GPIO的时钟时,需要设置CCM_CCGR0~CCM_CCGR6这7个寄存器中的一个,在手册里找到GPIO对应的寄存器。例如CCM_CCGR2寄存器的27-26位,对应的是GPIO3的时钟,如果要使能,将这两个位置1,可以使用CCM_CCGR2 = 3 << 26这句。(不需要解释了吧?)

好了,基本上GPIO的操作就是这些了,STM32的那4个步骤也完成了。我们来看看Linux编程实际是怎么做的。
首先会在头文件中指定这些寄存器的地址,这些地址设置可以在所有项目中使用

...
#define CCM_CCGR2 *((volatile unsigned int *)0X020C4070)  //时钟寄存器地址,一共有7个
...
#define SW_MUX_GPIO1_IO03 *((volatile unsigned int *)0X020E0068)  //IO复用寄存器地址
#define SW_PAD_GPIO1_IO03 *((volatile unsigned int *)0X020E02F4)  //IO属性寄存器地址

#define GPIO1_DR *((volatile unsigned int *)0X0209C000)  //GPIO1相关寄存器设置的地址,一共有8个
...

接着根据需要来设置这些寄存器的值,例如

 CCM_CCGR0 = 0xFFFFFFFF; //使能GR0管理的所有外设时钟
 SW_MUX_GPIO1_IO03 = 0x5; //把这个IO口复用为GPIO
 SW_PAD_GPIO1_IO03 = 0x10B0; //配置这个口的IO属性
 GPIO1_GDIR = 0x0000008;  //将GPIO1_IO03设置为输出
 GPIO1_DR = 0x0; //GPIO1_IO03设置为低电平输出

也可以更简便一些,自己创建一些函数来定义GPIO的输入输出,以及读取或写入GPIO的高低电平值。
首先定义GPIO的输入输出枚举,

typedef enum _gpio_pin_direction
{
    kGPIO_DigitalInput = 0U,  		/* 输入 */
    kGPIO_DigitalOutput 		/* 输出 */
} gpio_pin_direction_t;

接着定义一个结构体,来初始化GPIO的输入输出,和输出的默认电平

typedef struct _gpio_pin_config
{
    gpio_pin_direction_t direction; /* GPIO方向:输入还是输出 */
    uint8_t outputLogic;            /* 如果是输出的话,默认输出电平 */
} gpio_pin_config_t;

接着定义几个函数来方便操作GPIO。首先是读取GPIO电平值

int gpio_pinread(GPIO_Type *base, int pin)  //base是GPIO组号,pin是组内的IO口号
 {
	 return (((base->DR) >> pin) & 0x1);
 }

写GPIO电平值

void gpio_pinwrite(GPIO_Type *base, int pin, int value)  //写0低电平
{
	 if (value == 0U)
	 {
		 base->DR &= ~(1U << pin); /* 输出低电平 */
	 }
	 else
	 {
		 base->DR |= (1U << pin); /* 输出高电平 */
	 }
}

GPIO初始化函数

void gpio_init(GPIO_Type *base, int pin, gpio_pin_config_t *config)
{
	if(config->direction == kGPIO_DigitalInput) /* 输入 */
	{
		base->GDIR &= ~( 1 << pin);
	}
	else										/* 输出 */
	{
		base->GDIR |= 1 << pin;
		gpio_pinwrite(base,pin, config->outputLogic);/* 设置默认输出电平 */
	}
}

这样在工程里可以比较方便的使用GPIO了。

作为总结,我们可以看到I.MX6UL的GPIO使用远远比STM32不便,首先是没有更方便的库函数,需要去操作和了解寄存器,其次是IO口的复用太多,要深入了解每个复用,没有图形化的界面(怀念CubeMX)来简单操作。没办法,只能等后期厂商来做完善吧(但估计也没有)。

接下去可以利用这些来做一个比较综合的应用。

(未完待续)

你可能感兴趣的:(linux,学习,笔记,arm,c语言)