FireBLE

FireBLE 是一个面向于打造智能生活的开源平台,以BLE(Bluetooth Low Energy)技术为核心,拥有超低的功耗、不俗的处理能力和广泛的应用场合,专注于更智能、高效率的工作模式,让生活在科技中更安全、方便、快捷。也许您一个不经意的想法与FireBLE擦出的火花,会在这片原野上燎出火焰,甚至燃烧整个世界。

GPIO 驱动

更新时间:2017-08-08 阅读:3752

前言

  • 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发生中断时的回调函数。


初始化函数进行了三件事

  1. 若开启回调函数,则设置回调函数,回调函数的地址有参数指针传入。
  2. 开启GPIO的时钟。
  3. 开启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_fieldgpio_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 引脚功能配置已经完成。


  1. 调用gpio_init函数初始化 GPIO ,设置回调函数及开启 GPIO 时钟和中断。(注意不要以为不需要回调函数就不调用此函数,如果没有调用此函数将不会开启 GPIO 时钟,后面的配置和使用都将毫无效果。)
  2. 设置 GPIO 口方向,如果是输入方向需要设置 IO 口上下拉状态,输出方向则需要初始化 IO 口初始电平。
  3. 根据需要去使用 GPIO 了。