This post was last edited by DDZZ669 on 2021-11-9 00:04
In the previous articles, we have gradually learned about the various lighting principles of embedded Linux development, from the most basic register lighting, to device tree lighting, and then to GPIO subsystem lighting.
The output function of GPIO is used to turn on the lights. In this article, we will learn how to use the GPIO input function by using the buttons .
1 Hardware Introduction
1.1 Schematic diagram of buttons on the board
Let's look at the schematic diagram first . There are 4 buttons sw1~sw4 on my board:
1.1.1 SW1
SW1 is the system reset button of the board and cannot be used programmably
1.1.2 SW2, SW3
-
SW2 : SNVS_TAMPER1, GPIO5_1
It is usually at a low level, and when pressed it is at a high level.
-
SW3 : ONOFF
It is also a system-level button used to turn the device on and off by long pressing.
1.1.3 SW4
SW4 is the BOOT_MODE1 pin, which is used to switch the serial burning mode and needs to be used in conjunction with the reset button.
This article only tests the button function, so you can use this button.
1.1.4 Use two of the buttons
The functional characteristics of the four buttons on the board are as follows:
This experiment uses the two buttons SW2 and SW4 to carry out the experiment.
2 Software Writing
2.1 Modify the device tree file
2.1.1 Modify the iomuxc node
Modify imx6ull-myboard.dts and create a child node named pinctrl_key under the imx6ull-evk child node of the iomuxc node. The node content is as follows:
pinctrl_key: keygrp {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x3080 /* SW2 */
MX6ULL_PAD_BOOT_MODE1__GPIO5_IO11 0xF080 /* SW4 */
>;
};
This part configures the pins. The definitions of these two pins are in the imx6ull-pinfunc-snvs.h file:
The value following the pin macro definition is the configuration of the pin function:
SW2: 0x3080, i.e. 0011 0000 1000 0000
SW4: 0xF080, i.e. 1000 0000 1000 0000
Refer to the configuration of the GPIO PAD register explained previously , and configure the pull-up or pull-down according to the actual circuit configuration of the two buttons.
/*
*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 低转换率
*/
Note: The GPIO SW4 (MX6ULL_PAD_BOOT_MODE1__GPIO5_IO11) is actually used by another device (spi4) in the device.
At more than 300 lines of imx6ull-myboard.dts, there is:
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
>;
};
In theory, we should comment out the configuration here, because one IO cannot perform two functions at the same time. Since spi4 is not used in this experiment, ignore it for now and see what impact it will have. If it affects this experiment, comment out the configuration here.
2.1.2 Add key node
Create a key node named key under the root node with the following content:
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";
};
2.2 Writing a Key Driver
The key driver also belongs to the character device driver. Like the previous character device driver framework, the main modification points are the hardware initialization configuration of the key and the reading of the key.
Create a new key-Bsp.c
2.2.1 Hardware initialization of buttons
The initialization process is to use the OF function to obtain the key node from the device tree, and then use the API function of the GPIO subsystem to configure the GPIO as input.
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;
}
2.2.2 Read the value of a button
The key value is read by the API function of the GPIO subsystem. After reading the key value, the value is passed to the application layer for use. Note that atomic operations atomic_set and atomic_read are used here to implement data writing and reading.
/* 定义按键值 */
#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;
}
2.3 Writing a Keystroke Application
Create a new key-App.c
The application layer program of the key mainly uses the key reading interface provided by the driver to cyclically read the value of the key and print out the value of the key when the key is pressed.
/* 定义按键值 */
#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;
}
3 Experimental tests
3.1 Compiler
3.1.1 Compile device tree
Compile the device tree file and copy the compiled dtb file to the startup folder:
Start the development board via the network and view the key node:
3.1.2 Compile the key driver
3.1.3 Compile the button application
3.2 Testing
3.3 Check CPU usage
First, press Ctrl+C to end the key process, and then use the following command to run the key program in the background:
./key-App /dev/key &
Then use the command:
top
To check the CPU usage. As can be seen from the figure below, the CPU usage is 99.8% at this time, which is all occupied by the key check program, because there is a while loop in the key program that keeps reading the key value.
Use the command:
ps
Check the process number of the key, which is 149 in the following figure, and then use:
kill -9 149
To kill the key process, and then use the top command to view, you can see that the CPU usage has returned to 0.
In actual key usage, the method of continuous detection that causes CPU occupancy is generally not used. This article only introduces the use of GPIO input function. Later, a more efficient key detection mechanism will be used to implement the key detection function.
4 Conclusion
This article mainly introduces the use of key detection of i.MX6ULL. The main knowledge points are the modification of device tree, as well as the input configuration of GPIO and the reading of high and low levels.