前面几篇文章,从最基础的寄存器点灯,到设备树点灯,再到GPIO子系统点灯,一步步了解嵌入式Linux开发的各种点灯原理。
点灯用到的都是GPIO的输出功能,这篇,通过按键的使用,来学习GPIO输入功能的使用。
先来看原理图,我板子上有4个按键sw1~sw4:
SW1是板子的系统复位按键,不可编程使用
SW2:SNVS_TAMPER1,GPIO5_1
平时是低电平,按下去是高电平。
SW3:ONOFF
它也是系统级的按键,用于长按进行开关机。
SW4是BOOT_MODE1脚,用来进行串行烧录模式切换,需要再与复位键配合使用。
本篇仅测试按键功能,因此可以该按键。
板子上这4个按键的功能特性如下表:
本实验使用SW2和SW4这两个按键来进行实验。
修改imx6ull-myboard.dts,在iomuxc节点的imx6ull-evk字节点下创建一个名为pinctrl_key的子节点,节点内容如下:
pinctrl_key: keygrp {
? ?fsl,pins = <
? ? ? ?MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 ? ?0x3080 /* SW2 */
? ? ? ?MX6ULL_PAD_BOOT_MODE1__GPIO5_IO11 ? ? ?0xF080 /* SW4 */
? ?>;
};
这部分是对引脚进行配置,这两个引脚的定义是在imx6ull-pinfunc-snvs.h文件中:
引脚宏定义后面的值,是对引脚功能的配置:
SW2:0x3080,即0011 0000 1000 0000
SW4:0xF080,即1000 0000 1000 0000
对照之前讲解GPIO的PAD寄存器的配置,根据两个按键的实际电路配置上拉或下拉。
/*
*bit 16:0 HYS关闭
*bit [15:14]: [00]下拉 [01]47k上拉 [10]100k上拉 [11]22k上拉 <---
*bit [13]: [0]kepper功能 [1]pull功能
*bit [12]: [0]pull/keeper-disable [1]pull/keeper-enable
*bit [11]: 0 关闭开路输出
*bit [10:8]: 00 保留值
*bit [7:6]: 10 速度100Mhz
*bit [5:3]: 000 输出disable <---
*bit [2:1]: 00 保留值
*bit [0]: 0 低转换率
*/
注:SW4 (MX6ULL_PAD_BOOT_MODE1__GPIO5_IO11)这个GPIO,在设备中实际已经被其它设备(spi4)使用了。
在imx6ull-myboard.dts的300多行处,有:
pinctrl_spi4: spi4grp { fsl,pins = < ? ? MX6ULL_PAD_BOOT_MODE0__GPIO5_IO10 ? ? ? ?0x70a1 ? ? MX6ULL_PAD_BOOT_MODE1__GPIO5_IO11 ? ? ? ?0x70a1 ? ? MX6ULL_PAD_SNVS_TAMPER7__GPIO5_IO07 ? ? ?0x70a1 ? ? MX6ULL_PAD_SNVS_TAMPER8__GPIO5_IO08 ? ? ?0x80000000 ? ? >; };
理论上我们应该把这里的配置给注释掉,因为1个IO是不能同时进行2种功能的。由于本次实验不使用spi4,暂且也先不管它,看看会有什么影响,如果影响了本实验,再给把这里的配置给注掉。
在根节点下创建名为key的按键节点,内容如下:
key {
? ?#address-cells = <1>;
? ?#size-cells = <1>;
? ?compatible = "myboard-key";
? ?pinctrl-names = "default";
? ?pinctrl-0 = <&pinctrl_key>;
? ?key1-gpio = <&gpio5 1 GPIO_ACTIVE_HIGH>; ? /* SW2 */
? ?key2-gpio = <&gpio5 11 GPIO_ACTIVE_LOW>; ? /* SW4 */
? ?status = "okay";
};
按键驱动,也属于字符设备驱动,和之前的字符设备驱动的框架一样,主要的修改点在按键的硬件初始化配置已经按键的读取。
新建一个key-Bsp.c
初始化的流程,就是使用OF函数来从设备树中获取key节点,然后使用GPIO子系统的API函数,将GPIO配置为输入。
static int keyio_init(void)
{
? ?keydev.nd = of_find_node_by_path("/key");
? ?if (keydev.nd== NULL)
? {
? ? ? ?return -EINVAL;
? }
?
? ?keydev.key1_gpio = of_get_named_gpio(keydev.nd ,"key1-gpio", 0);
? ?keydev.key2_gpio = of_get_named_gpio(keydev.nd ,"key2-gpio", 0);
? ?if ((keydev.key1_gpio < 0)||(keydev.key2_gpio < 0))
? {
? ? ? ?printk("can't get key\r\n");
? ? ? ?return -EINVAL;
? }
? ?printk("key1_gpio=%d, key2_gpio=%d\r\n", keydev.key1_gpio, keydev.key2_gpio);
?
? ?/* 初始化key所使用的IO */
? ?gpio_request(keydev.key1_gpio, "key1"); ? ?/* 请求IO */
? ?gpio_request(keydev.key2_gpio, "key2"); ? ?/* 请求IO */
? ?gpio_direction_input(keydev.key1_gpio); ? ?/* 设置为输入 */
? ?gpio_direction_input(keydev.key2_gpio); ? ?/* 设置为输入 */
? ?return 0;
}
读取按键的值,也是GPIO子系统的API函数来读取。读取到按键的值后,将该值传递出来给应用层使用,注意这里使用了原子操作的方式atomic_set和atomic_read实现数据的写入和读取。
/* 定义按键值 */
#define KEY1VALUE ? ? 0X01 ? ?/* 按键值 ? ? ? */
#define KEY2VALUE ? ? 0X02 ? ?/* 按键值 ? ? ? */
#define INVAKEY ? ? ? 0X00 ? ?/* 无效的按键值 */
?
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
? ?int ret = 0;
? ?int value;
? ?struct key_dev *dev = filp->private_data;
?
? ?if (gpio_get_value(dev->key1_gpio) == 1) ? ? ? ? /* key1按下 */
? {
? ? ? ?printk("get key1: high\r\n");
? ? ? ?while(gpio_get_value(dev->key1_gpio)); ? ? ? /* 等待按键释放 */
? ? ? ?atomic_set(&dev->keyvalue, KEY1VALUE);
? }
? ?else if (gpio_get_value(dev->key2_gpio) == 0) ? ?/* key2按下 */
? {
? ? ? ?printk("get key2: low\r\n");
? ? ? ?while(!gpio_get_value(dev->key2_gpio)); ? ? ?/* 等待按键释放 */
? ? ? ?atomic_set(&dev->keyvalue, KEY2VALUE);
? }
? ?else
? {
? ? ? ?atomic_set(&dev->keyvalue, INVAKEY); ? ? ? ?/* 无效的按键值 */
? }
?
? ?value = atomic_read(&dev->keyvalue);
? ?ret = copy_to_user(buf, &value, sizeof(value));
? ?return ret;
}
新建一个key-App.c
按键的应用层程序,主要就通过驱动程序提供的按键读取接口,来循环读取按键的值,并在按键按下时,将按键的值打印出来。
/* 定义按键值 */
#define KEY1VALUE ? 0X01
#define KEY2VALUE ? 0X02
#define INVAKEY ? ? 0X00
?
int main(int argc, char *argv[])
{
? ?int fd, ret;
? ?char *filename;
? ?int keyvalue;
?
? ?if(argc != 2)
? {
? ? ? ?printf("Error Usage!\r\n");
? ? ? ?return -1;
? }
?
? ?filename = argv[1];
?
? ?/* 打开key驱动 */
? ?fd = open(filename, O_RDWR);
? ?if(fd < 0)
? {
? ? ? ?printf("file %s open failed!\r\n", argv[1]);
? ? ? ?return -1;
? }
?
? ?/* 循环读取按键值数据! */
? ?while(1)
? {
? ? ? ?read(fd, &keyvalue, sizeof(keyvalue));
? ? ? ?if (keyvalue == KEY1VALUE)
? ? ? {
? ? ? ? ? ?printf("KEY1 Press, value = %#X\r\n", keyvalue);
? ? ? }
? ? ? ?else if (keyvalue == KEY2VALUE)
? ? ? {
? ? ? ? ? ?printf("KEY2 Press, value = %#X\r\n", keyvalue);
? ? ? }
? }
?
? ?ret= close(fd); /* 关闭文件 */
? ?if(ret < 0)
? {
? ? ? ?printf("file %s close failed!\r\n", argv[1]);
? ? ? ?return -1;
? }
? ?return 0;
}
编译设备树文件,并将编译出的dtb文件复制到启动文件夹:
网络方式启动开发板,查看key节点:
先Ctrl+C结束掉此按键进程,然后使用如下指令来后台运行按键程序:
./key-App /dev/key &
然后再使用指令:
top
来查看CPU是使用情况。从下图可以看出,此时CPU的使用率是99.8%,全被按键检查程序占用了,因为按键程序中有个while循环在一直读取按键的值。
使用指令:
【i.MX6ULL】驱动开发系列文章汇总入口:
【i.MX6ULL】驱动开发——by DDZZ669 - ARM技术 - 电子工程世界-论坛 (eeworld.com.cn)
分目录:
【i.MX6ULL】驱动开发6——Pinctrl子系统与GPIO子系统点亮LED
引用: Jacktang 发表于 2021-11-10 07:16 Linux下的按键操作好像比较复杂 kill命令后, 再top指令查看,可以看到CPU的使用率又变回了0,kill命 ...
linux下的按键也可以用中断的方式,这个程序CPU占用高的原因主要是因为应用层程序在一直读取按键的值。kill命令就是用来强行结束某个进程。