• 树莓派按键控制LED(中断方式)


    GPIO

    GPIO(通用输入/输出口)是一个灵活的由软件控制的数字信号。
    每个 GPIO 都代表一个连接到特定引脚或球珠(BGA 封装)的一个位。
    电路板原理图显示了 GPIO 与外部硬件的连接关系。
    在某些情况下,每个非专用引脚都可以配置为 GPIO,且大多数芯片都最少有一些 GPIO。
    同时还有通过 I2C 或 SPI 串行总线连接的“GPIO 扩展器”芯片。
    GPIO 的实际功能因系统而异,通常用法有:

    • 输出;
    • 输入;GPIO 控制器有输入去毛刺/消抖逻辑,有时也需要软件控制
    • 输入通常可作为 IRQ 信号;一般为边沿触发,但有时是电平触发。
      • IRQ 可能配置为系统唤醒事件,以将系统从低功耗状态下唤醒。

    对于给定的电路板,每个 GPIO 都用于某个特定的目的,如监控 MMC/SD 卡的插入/移除、检测卡的写保护状态、驱动 LED、配置收发器、模拟串行总线、复位硬件看门狗、感知开关状态等等。

    使用

    某个平台可能通过一个简单地访问芯片寄存器的内联函数来实现它,其它平台可能通过委托一系列不同的 GPIO 控制器的抽象函数来实现它。
    想使用 GPIO 函数,驱动需要包含头文件

    #include 
    
    • 1

    GPIO 是通过无符号整型来标识的,范围是 0 到 MAX_INT。
    平台会定义这些整数的用法,且通常使用 #define 来定义 GPIO,这样板级特定的启动代码可以直接关联相应的原理图。

    ① 对于一个 GPIO,系统应该做的第一件事情就是通过 gpio_request() 函数分配它

    gpio_request()
    • 1

    将无效的 GPIO 编码传递给 gpio_request() 会导致失败,申请一个已使用这个函数声明过的 GPIO 也会失败
    gpio_request() 的返回值必须检查。你应该在进程上下文中调用这些函数。
    如两个或更多驱动错误地认为他们已经独占了某个信号线,或是错误地认为移除一个管理着某个已激活信号的驱动是安全的。也就是说,申请 GPIO 的作用类似一种锁机制。

    ② 接下来是设置 I/O 方向,这通常是在板级启动代码中为所使用的 GPIO 设置 platform_device 时完成。
    测试一个 GPIO 编号是否可用

    /* 设置为输入或输出, 返回 0 或负的错误代码 */
    int gpio_direction_input(unsigned gpio);
    int gpio_direction_output(unsigned gpio, int value);
    
    • 1
    • 2
    • 3

    返回值为零代表成功,否则返回一个负的错误代码。
    如果这个 GPIO 编码不存在,或者特定的 GPIO 不能用于那种模式,则方向设置可能失败。

    大多数 GPIO 控制器可以通过内存读/写指令来访问。这些指令不会休眠,可以安全地在硬(非线程)中断例程和类似的上下文中完成。

    /* GPIO 输入:返回零或非零 */
    int gpio_get_value(unsigned gpio);
    
    /* GPIO 输出 */
    void gpio_set_value(unsigned gpio, int value);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    当读取一个输出引脚的值时,返回值应该是引脚上的值。
    要注意的是并不是所有平台都可以从输出引脚中读取数据,对于不能读取的引脚应总返回零。

    /* 释放之前声明的 GPIO */
    void gpio_free(unsigned gpio);
    
    • 1
    • 2

    int gpio_is_valid(int number);
    
    • 1

    ⑥ 申请一个 GPIO 并没有以任何方式配置它,只不过标识那个 GPIO 处于使用状态。必须有另外的代码来处理引脚配置(如控制 GPIO 使用的引脚、上拉/下拉)。
    考虑到大多数情况下声明 GPIO 之后就会立即配置它们,所以定义了以下三个辅助函数:

    /* 申请一个 GPIO 信号, 同时通过特定的'flags'初始化配置,
     * 其他和 gpio_request()的参数和返回值相同 */
    int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);
    
    /* 在单个函数中申请多个 GPIO */
    int gpio_request_array(struct gpio *array, size_t num);
    
    /* 在单个函数中释放多个 GPIO */
    void gpio_free_array(struct gpio *array, size_t num);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    这里 'flags' 当前定义可指定以下属性:
    	* GPIOF_DIR_IN		- 配置方向为输入
    	* GPIOF_DIR_OUT		- 配置方向为输出
    	
    	* GPIOF_INIT_LOW	- 在作为输出时,初始值为低电平
    	* GPIOF_INIT_HIGH	- 在作为输出时,初始值为高电平
    	* GPIOF_OPEN_DRAIN	- gpio引脚为开漏信号
    	* GPIOF_OPEN_SOURCE	- gpio引脚为源极开路信号
    	
    	* GPIOF_EXPORT_DIR_FIXED		- 将 gpio 导出到 sysfs,并保持方向
    	* GPIOF_EXPORT_DIR_CHANGEABLE	- 同样是导出, 但允许改变方向
    
    因为 GPIOF_INIT_* 仅有在配置为输出的时候才存在,所以有效的组合为:
    	* GPIOF_IN		- 配置为输入
    	* GPIOF_OUT_INIT_LOW	- 配置为输出,并初始化为低电平
    	* GPIOF_OUT_INIT_HIGH	- 配置为输出,并初始化为高电平
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    注意:
    当设置 flag 为 GPIOF_OPEN_DRAIN 时,则假设引脚是开漏信号。这样的引脚将不会在输出模式下置1。这样的引脚需要连接上拉电阻。通过使能这个标志,gpio库将会在被要求输出模式下置 1 时将引脚变为输入状态来使引脚置高。引脚在输出模式下通过置 0 使其输出低电平。
    当设置 flag 为 GPIOF_OPEN_SOURCE 时,则假设引脚为源极开路信号。这样的引脚将不会在输出模式下置 0。这样的引脚需要连接下拉电阻。通过使能这个标志,gpio 库将会在被要求输出模式下置 0 时将引脚变为输入状态来使引脚置低。引脚在输出模式下通过置1使其输出高电平。

    ⑦ 更进一步,为了更简单地声明/释放多个 GPIO,‘struct gpio’ 被引进来封装所有这三个字段:

    struct gpio {
    	unsigned		gpio;
    	unsigned long	flags;
    	const char		*label;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    一个典型的用例:

    static struct gpio leds_gpios[] = {
    	{ 32, GPIOF_OUT_INIT_HIGH, "Power LED" }, /* 默认开启 */
    	{ 33, GPIOF_OUT_INIT_LOW,  "Green LED" }, /* 默认关闭 */
    	{ 34, GPIOF_OUT_INIT_LOW,  "Red LED"   }, /* 默认关闭 */
    	{ 35, GPIOF_OUT_INIT_LOW,  "Blue LED"  }, /* 默认关闭 */
    	{ ... },
    };
    
    err = gpio_request_one(31, GPIOF_IN, "Reset Button");
    if (err)
    	...
    
    err = gpio_request_array(leds_gpios, ARRAY_SIZE(leds_gpios));
    if (err)
    	...
    
    gpio_free_array(leds_gpios, ARRAY_SIZE(leds_gpios));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    ⑧ GPIO 映射到 IRQ
    GPIO 编号是无符号整数;IRQ 编号也是。
    这些构成了两个逻辑上不同的命名空间(GPIO 0 不一定使用 IRQ 0)。你可以通过以下函数在它们之间实现映射:

    /* 映射 GPIO 编号到 IRQ 编号 */
    int gpio_to_irq(unsigned gpio);
    
    /* 映射 IRQ 编号到 GPIO 编号 (尽量避免使用) */
    int irq_to_gpio(unsigned irq);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    返回值为负数表示出错,例如,某些 GPIO 无法做为 IRQ 使用。

    ⑨ irq_to_gpio()返回的非错误值大多数通常可以被 gpio_get_value() 所使用

    irq_to_gpio()
    
    • 1

    ⑩ 开漏
    有时在只有低电平信号作为实际驱动结果的时候,共享的信号线需要使用“开漏”信号。
    一个开漏信号的常见例子是共享的低电平使能 IRQ 信号线。
    某些 GPIO 控制器直接支持开漏输出,还有许多不支持。当你需要开漏信号,但硬件又不直接支持的时候,一个常用的方法是用任何即可作输入也可作输出的 GPIO 引脚来模拟:(骚操作)

     LOW:	gpio_direction_output(gpio, 0) ... 这代码驱动信号并覆盖
    	上拉配置。
    
     HIGH:	gpio_direction_input(gpio) ... 这代码关闭输出,所以上拉电阻
    	(或其他的一些器件)控制了信号。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    ⑪ 特殊用途
    例如,通过适当的系统硬件文档,用户空间可以知道 GIOP #23 控制 Flash 存储器的写保护(用于保护其中 Bootloader 分区)。产品的系统升级可能需要临时解除这个保护:首先导入一个 GPIO,改变其输出状态,然后在重新使能写保护前升级代码。通常情况下,GPIO #23 是不会被触及的,并且内核也不需要知道它。

    ⑫ 标准驱动
    注意:标准内核驱动中已经存在通用的“LED 和按键”GPIO 任务,分别是:“leds-gpio” 和 “gpio_keys”。请使用这些来替代直接访问 GPIO,因为集成在内核框架中的这类驱动比你在用户空间的代码更好。

    ⑬ sysfs

       /sys/class/gpio/
    
       	"export" ... 用户空间可以通过写其编号到这个文件,要求内核导出
    	一个 GPIO 的控制到用户空间。
    
    	例如: 如果内核代码没有申请 GPIO #19,"echo 19 > export"
    	将会为 GPIO #19 创建一个 "gpio19" 节点。
    
       	"unexport" ... 导出到用户空间的逆操作。
    
    	例如: "echo 19 > unexport" 将会移除使用"export"文件导出的
    	"gpio19" 节点。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    ⑭ 从内核代码中导出

    内核代码可以明确地管理那些已通过 gpio_request()申请的 GPIO 的导出:
    
    /* 导出 GPIO 到用户空间 */
    int gpio_export(unsigned gpio, bool direction_may_change);
    
    /* gpio_export()的逆操作 */
    void gpio_unexport();
    
    /* 创建一个 sysfs 连接到已导出的 GPIO 节点 */
    int gpio_export_link(struct device *dev, const char *name,
    	unsigned gpio)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在一个内核驱动申请一个 GPIO 之后,它可以通过 gpio_export() 使其在 sysfs 接口中可见。该驱动可以控制信号方向是否可修改。这有助于防止用户空间代码无意间破坏重要的系统状态。
    这个明确的导出有助于(通过使某些实验更容易来)调试,也可以提供一个始终存在的接口,与文档配合作为板级支持包的一部分。

    实例

    硬件:树莓派
    开发环境:Ubuntu、buildroot

    interrupt.c

    #include 
    #include 
    #include 
    #include 
    
    #define BUTTON_PIN 12 /* GPIO 12 */
    #define LED_PIN 21	  /* GPIO 21 */
    
    int flag = 0;
    
    static irqreturn_t irq_handler(int irq, void *dev)
    {
    	int curr_val;
    
    	printk("%s()\n", __FUNCTION__);
    
    	curr_val = gpio_get_value(LED_PIN);
    	gpio_set_value(LED_PIN, !curr_val);
    
    	return IRQ_HANDLED;
    }
    
    static int led_init(void)
    {
    	int err;
    	int irq;
    
    	printk("%s()\n", __FUNCTION__);
    
    	err = gpio_request_one(BUTTON_PIN, GPIOF_IN, "Button");
    	if (err)
    		return err;
    
    	err = gpio_request_one(LED_PIN, GPIOF_OUT_INIT_LOW, "LED");
    	if (err)
    		return err;
    
    	irq = gpio_to_irq(BUTTON_PIN);
    	// enable_irq(irq); // why crash ?
    	err = request_irq(irq, irq_handler, IRQ_TYPE_EDGE_BOTH, "LED Test", NULL);
    	if (err < 0) {
    		printk("request irq (%d) failed!", irq);
    		return err;
    	}
    
    	printk("request irq (%d) success!", irq);
    
    	flag = 1;
    
    	return 0;
    }
    
    static void led_exit(void)
    {
    	printk("%s()\n", __FUNCTION__);
    
    	if (flag)
    		free_irq(gpio_to_irq(BUTTON_PIN), NULL);
    
    	gpio_free(BUTTON_PIN);
    	gpio_free(LED_PIN);
    
    	return;
    }
    
    module_init(led_init);
    module_exit(led_exit);
    
    MODULE_LICENSE("Dual BSD/GPL");
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    Makefile

    obj-m = interrupt.o
    
    KDIR=/home/liyongjun/project/board/buildroot/RPi3/build/linux-custom
    CROSS_COMPILE=/home/liyongjun/project/board/buildroot/RPi3/host/bin/arm-buildroot-linux-gnueabihf-
    
    all:
    	make -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) modules
    
    clean:
    	make -C $(KDIR) M=$(PWD) ARCH=arm CROSS_COMPILE=$(CROSS_COMPILE) clean
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    测试

    # tftp -gr interrupt.ko 192.168.31.224
    # insmod interrupt.ko 
    [  126.841440] led_init()
    [  126.844522] request irq (200) success!
    # 
    # [  131.515371] irq_handler()
    [  131.518698] irq_handler()
    [  131.542489] irq_handler()
    [  131.545789] irq_handler()
    [  131.549085] irq_handler()
    [  131.552373] irq_handler()
    [  131.693361] irq_handler()
    [  131.696647] irq_handler()
    [  131.726628] irq_handler()
    [  131.729905] irq_handler()
    [  131.733188] irq_handler()
    [  131.736740] irq_handler()
    [  131.740021] irq_handler()
    # 
    # rmmod  interrupt.ko 
    [  138.352981] led_exit()
    # 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
  • 相关阅读:
    【数据结构】交换排序之冒泡排序与快速排序
    商业智能BI
    RGB转MIPI,GM8828C,国产视频接口转换芯片
    web期末大作业 用HTML+CSS做一个漂亮简单的节日网页【传日文化节日中秋节】
    ZJUBCA研报分享 | 《BTC/USDT周内效应研究》
    java计算机毕业设计高校学生资助管理信息系统源码+mysql数据库+系统+lw文档+部署
    5G定位技术原理与应用场景
    基于ssm的图书商城
    混迹互联网,怎么才能写出好简历?
    Android Frgment中onActivityResult无效的问题
  • 原文地址:https://blog.csdn.net/lyndon_li/article/details/127914774