GPIO 驱动
更新时间:2017-08-08 阅读:3786
前言
- QN9021 有 15 个可自由配置的 GPIO 。由于片载资源包括有两个串口、两路 PWM 输出、一个 SPI 接口、一个 I2C 接口、一个 RTCI 接口、一个 SWD 仿真入口、一个比较器输出和一个定时器等许多资源。众多资源难免会出现 IO 口的复用,所以 GPIO 必须经过配置之后才能正常使用。
- 本例作为 GPIO 实验,主要是介绍如何配置 GPIO 口的引脚功能以及怎么使用配置 GPIO 为普通 IO 口及其使用。
- 需要注意的是,在深度睡眠下,只有P0和P1组的IO口才能唤醒睡眠。
初始化
一般裸驱开发过程为系统初始化-->GPIO配置-->各驱动模块初始化-->主循环实现功能。第一步系统初始化是每一个例程所必须的,初始化的过程也是相同的,所以只需调用系统初始化接口即可。
下面的三个部分都是需要我们根据自己的需求自行实现,在上一小节我们已经配置好了GPIO,接下来就是各个模块的初始化。
初始化函数
SPI初始化的函数原型为:
void gpio_init(gpio_callback_t p_callback) { #if GPIO_CALLBACK_EN==TRUE // Initialize environment gpio_env.callback = p_callback; #endif /* Enable AHB clock to the GPIO domain. */ gpio_clock_on(); /* Set up NVIC when I/O pins are configured as external interrupts. */ #if CONFIG_GPIO_ENABLE_INTERRUPT==TRUE NVIC_EnableIRQ(GPIO_IRQn); #endif }
传入参数:GPIO发生中断时的回调函数。
初始化函数进行了三件事
- 若开启回调函数,则设置回调函数,回调函数的地址有参数指针传入。
- 开启GPIO的时钟。
- 开启GPIO的中断。
回调函数实现了对LED_PIN输出电平的取反操作。
void cb_gpio(enum gpio_pin pin) { if (pin == BUTTON_PIN) /* toggle the LED_PIN GPIO_LEVEL*/ gpio_toggle_pin(LED_PIN); }
GPIO实验
GPIO输入实验
GPIO实验主要是展示了设置某一GPIO和设置所有GPIO方向的方法、读取某一GPIO和读取所有GPIO的输入状态。进行GPIO输入实验,需要把宏定义GPIO_INPUT_EXAMPLE
设置为true
,并把其他两项设置为FALSE
。
程序中设置有两个全局变量,分别用来存储当前所有GPIO的输入状态和P00口的输入状态的。
uint32_t result; enum gpio_level result_level;
单独设置某一IO口方向的函数为
void gpio_set_direction(enum gpio_pin pin, enum gpio_direction direction) { if(direction == GPIO_INPUT) { gpio_gpio_ClrOutEnable(QN_GPIO, pin); } else /*if(direction == GPIO_OUTPUT)*/ { gpio_gpio_SetOutEnable(QN_GPIO, pin); } }
这两个传入参数都为枚举型变量,这样可以保证参数传递的正确性,增强代码可读逻辑。查找gpio_pin
的定义与gpio_direction
的定义即可知道,第一个传入参数是需要配置的具体GPIO,第二个参数设置GPIO状态是输入还是输出。
设置所有GPIO口方向的函数为
void gpio_set_direction_field(uint32_t pin_mask, uint32_t direction_value) { direction_value &= pin_mask; gpio_gpio_SetOutEnable(QN_GPIO, direction_value); direction_value ^= pin_mask; gpio_gpio_ClrOutEnable(QN_GPIO, direction_value); }
由上面查到的资料可知,芯片最多引出了30个GPIO口,QN9021与QN9020兼容,只有15个GPIO口,但是位置定义都没有改变。所以一个32位的uint32_t
类型变量完全可以用掩码形式表示出所有的IO口。而由于IO口只有输入和输出两个方向,所以恰好我们也可以使用一个uint32_t
类型的掩码数来表示所有IO口的方向。所以只需以上两个参数传递即可实现同时设置多个IO口方向。其中direction_value &= pin_mask;
与 direction_value ^= pin_mask;
是为了不影响其他几位的操作。
实验中首先把所有的GPIO口都设置为输入方向
/* Set all pin to input */ gpio_set_direction_field(GPIO_PIN_ALL, (uint32_t)GPIO_INPUT);
然后不停的读取IO口的状态
读取所有GPIO口输入状态
/* read all pins input level */ result = gpio_read_pin_field(GPIO_PIN_ALL);
读取GPIO_P00口输入状态
/* read P0.0 input level*/ result_level = gpio_read_pin(GPIO_P00);
函数gpio_read_pin_field
和gpio_read_pin
的传入参数亦是如此。用户可以通过仿真器调试看到当前GPIO的输入状态,也可以通过设置GPIO的上下拉状态来查看不同状态下引线悬空时电平是否一样。
GPIO输出实验
设置所有GPIO为输出
/* Set all pin to output */ gpio_set_direction_field(GPIO_PIN_ALL, (uint32_t)GPIO_OUTPUT);
从P00到P36,依次设置为高电平。
int i; for(i=0; i<31; ++i) { /* Polling all pins to output low level, and the order is from P0.0 to P3.6 */ gpio_write_pin_field(GPIO_PIN_ALL, ~(1<<i)); delay(10000); }
类似的,如果需要单独设定某一GPIO的输出电平,可使用 gpio_write_pin
函数实现。以下例程中对此函数有具体应用。
GPIO中断实验
GPIO中断实验主要实现了通过按键触发GPIO口的中断,进入到中断处理中,中断处理函数会把板上的LED进行亮灭的切换,从而实现按键对LED的控制。进行GPIO中断实验,需要把宏定义GPIO_INTERRUPT_EXAMPLE
设置为true
,并把其他两项设置为FALSE
。
#define GPIO_INTERRUPT_EXAMPLE TRUE #define GPIO_INPUT_EXAMPLE FALSE #define GPIO_OUTPUT_EXAMPLE FALSE
首先,定义按键和LED在开发板上对应的引脚。
#define BUTTON_PIN (GPIO_P03) #define LED_PIN (GPIO_P26)
开发板上有两个按键,一个是五向按键,一个是普通按键。普通按键是复位开发板用的,实验中指的按键是五向按键。五向按键由两个IO配合工作实现按键状态的判断,其中连接P03口的引脚在按键朝任意一个方向按下时都会产生一个低电平,所以如果只考虑P03的电平状态,五向按键其实相当于一个有五种按法但是结果一样的普通按键。
依次配置BUTTON_PIN为上拉状态、输入模式、上升沿触发中断并开启中断。
/* set BUTTON_PIN to pull up */ gpio_pull_set(BUTTON_PIN, GPIO_PULL_UP); /* set BUTTON_PIN direction to input*/ gpio_set_direction(BUTTON_PIN, (uint32_t)GPIO_INPUT); /* set BUTTON_PIN interrupt to rising edge*/ gpio_set_interrupt(BUTTON_PIN, GPIO_INT_RISING_EDGE); /* enable BUTTON_PIN interrupt and set the callback function*/ gpio_enable_interrupt(BUTTON_PIN);
为什么要配置BUTTON_PIN为上拉呢?因为作为输入口,IO口必须要能察觉外界电平的变化。实际上按键相当于一个开关,按下的时候是导通的,释放的时候是断开的。硬件设计中按键的两端一段直接连接到IO口,一端直接到地。在按键按下的时候,IO口相当于直接接地,此时CPU能获取到外界输入了一个低电平,于是在按键释放的时候,此时应该是高电平。而按键释放时为开路状态,所以需要开启上拉电阻把电平给拉高,这样才能实现按键状态的判断。依次类推,如果按键另一端接高电平,此时就应该配置IO口为下拉状态了。
配置完BUTTON_PIN还不能实现对LED的控制,因为LED_PIN还没进行初始化。
设置LED_PIN为输出状态
/* set LED_PIN direction to output*/ gpio_set_direction(LED_PIN, (uint32_t)GPIO_OUTPUT);
设置LED_PIN的初始电平,根据硬件设计,LED_PIN为低电平时按键是点亮的,反之则是熄灭的。
/* set LED_PIN to output low level*/ gpio_write_pin(LED_PIN, GPIO_LOW);
至此所有的配置工作都已经完成了,只要按下按键就可以改变LED的亮和灭了。
实验总结
在使用 GPIO 之前,确保系统初始化和 GPIO 引脚功能配置已经完成。
- 调用
gpio_init
函数初始化 GPIO ,设置回调函数及开启 GPIO 时钟和中断。(注意不要以为不需要回调函数就不调用此函数,如果没有调用此函数将不会开启 GPIO 时钟,后面的配置和使用都将毫无效果。) - 设置 GPIO 口方向,如果是输入方向需要设置 IO 口上下拉状态,输出方向则需要初始化 IO 口初始电平。
- 根据需要去使用 GPIO 了。