PCA9555是一款基于I2C的GPIO扩展芯片,可以通过I2C接口扩展出16个I/O口。每个I/O口都可以单独作为输入口或者输出口,并且在linux驱动程序里面可以像普通GPIO口那样操作这些扩展出来的GPIO。使用gpiod_get_value_cansleep函数读取输入电平,使用gpiod_set_value_cansleep函数设置输出电平,甚至还可以用request_threaded_irq函数申请中断,而且每一个GPIO端口都可以单独申请中断。
【电路图】
接了6个LED灯和3个按键。
按键不需要接上/下拉电阻,直接一端接I/O口,另一端接地就行了。因为I2C通信速度较慢,所以程序里面不需要消抖。

【电路板】

【设备树:rk3308-pca9555.dtsi】
/*
* ./build.sh kernel-config里面需要勾选:
* Device Drivers --->
-*- GPIO Support --->
I2C GPIO expanders --->
<*> PCA95[357]x, PCA9698, TCA64xx, and MAX7310 I/O ports
[*] Interrupt controller support for PCA953x
*
* 参考文档:Luckfox_Nova_SDK_250430/kernel/Documentation/devicetree/bindings/gpio/gpio-pca95xx.yaml
*/
&i2c3 {
status = "okay";
pca9555: pca9555@20 {
compatible = "nxp,pca9555";
reg = <0x20>;
interrupt-parent = <&gpio0>;
interrupts = <RK_PB6 IRQ_TYPE_LEVEL_LOW>;
interrupt-controller;
#interrupt-cells = <2>;
gpio-controller;
#gpio-cells = <2>;
};
};
/ {
pca9555_test1 {
compatible = "testboard,pca9555_test";
led-gpios = <&pca9555 15 GPIO_ACTIVE_HIGH>, <&pca9555 14 GPIO_ACTIVE_HIGH>;
button-gpio = <&pca9555 6 GPIO_ACTIVE_LOW>;
interrupt-parent = <&pca9555>;
interrupts = <6 IRQ_TYPE_EDGE_FALLING>;
};
pca9555_test2 {
compatible = "testboard,pca9555_test";
led-gpios = <&pca9555 12 GPIO_ACTIVE_HIGH>, <&pca9555 13 GPIO_ACTIVE_HIGH>;
button-gpio = <&pca9555 7 GPIO_ACTIVE_LOW>;
interrupt-parent = <&pca9555>;
interrupts = <7 IRQ_TYPE_EDGE_FALLING>;
};
pca9555_test3 {
compatible = "testboard,pca9555_test";
led-gpios = <&pca9555 10 GPIO_ACTIVE_HIGH>, <&pca9555 11 GPIO_ACTIVE_HIGH>;
button-gpio = <&pca9555 9 GPIO_ACTIVE_LOW>;
interrupt-parent = <&pca9555>;
interrupts = <9 IRQ_TYPE_EDGE_FALLING>;
};
};
【C语言驱动程序:pca9555_test.c】
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
struct pca9555_data {
struct platform_device *pdev;
struct gpio_descs *led_gpios;
struct gpio_desc *button_gpio;
unsigned long value;
};
static irqreturn_t pca9555_test_irq_handler(int irq, void *dev_id)
{
int value;
struct platform_device *pdev = dev_id;
struct pca9555_data *drvdata = platform_get_drvdata(pdev);
// 实测发现这里不需要对按键进行消抖
// 只需要判断一下value==1,按一次按键,LED灯的状态就只会变化一次
value = gpiod_get_value_cansleep(drvdata->button_gpio);
dev_info(&pdev->dev, "irq=%d, gpio_value=%d\n", irq, value);
if (value == 1) {
drvdata->value = (drvdata->value + 1) & 3;
gpiod_set_array_value_cansleep(drvdata->led_gpios->ndescs, drvdata->led_gpios->desc, drvdata->led_gpios->info, &drvdata->value);
}
return IRQ_HANDLED;
}
static int pca9555_test_probe(struct platform_device *pdev)
{
int irq, ret;
struct pca9555_data *drvdata;
dev_info(&pdev->dev, "probe\n");
drvdata = devm_kzalloc(&pdev->dev, sizeof(struct pca9555_data), GFP_KERNEL);
if (drvdata == NULL)
return -ENOMEM;
drvdata->pdev = pdev;
platform_set_drvdata(pdev, drvdata);
// 获取GPIO引脚
drvdata->led_gpios = devm_gpiod_get_array(&pdev->dev, "led", GPIOD_OUT_LOW);
if (IS_ERR(drvdata->led_gpios)) {
dev_err(&pdev->dev, "failed to get led gpios\n");
return PTR_ERR(drvdata->led_gpios);
}
drvdata->button_gpio = devm_gpiod_get(&pdev->dev, "button", GPIOD_IN);
if (IS_ERR(drvdata->button_gpio)) {
dev_err(&pdev->dev, "failed to get button gpio\n");
return PTR_ERR(drvdata->button_gpio);
}
// 申请GPIO中断
#if 0
// 方法1: 申请button-gpio属性对应的GPIO中断
// 需要在代码里面设置触发方式(电平或边沿触发)
irq = gpiod_to_irq(drvdata->button_gpio);
if (irq < 0) {
dev_err(&pdev->dev, "gpiod_to_irq() failed\n");
return irq;
}
dev_info(&pdev->dev, "gpiod_to_irq(): irq=%d\n", irq);
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, pca9555_test_irq_handler, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, pdev->dev.of_node->name, pdev);
if (ret != 0) {
dev_err(&pdev->dev, "failed to request irq\n");
return ret;
}
#else
// 方法2: 申请interrupt-parent和interrupts属性指定的中断
// 触发方式是在设备树里面设置
irq = of_irq_get(pdev->dev.of_node, 0);
if (irq <= 0) {
dev_err(&pdev->dev, "of_irq_get() failed, ret=%d\n", irq);
return -ENODEV;
}
dev_info(&pdev->dev, "of_irq_get(): irq=%d\n", irq);
ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, pca9555_test_irq_handler, IRQF_ONESHOT, pdev->dev.of_node->name, pdev);
if (ret != 0) {
dev_err(&pdev->dev, "failed to request irq\n");
return ret;
}
#endif
return 0;
}
static int pca9555_test_remove(struct platform_device *pdev)
{
struct pca9555_data *drvdata = platform_get_drvdata(pdev);
dev_info(&pdev->dev, "remove\n");
drvdata->value = 0;
gpiod_set_array_value_cansleep(drvdata->led_gpios->ndescs, drvdata->led_gpios->desc, drvdata->led_gpios->info, &drvdata->value);
return 0;
}
static const struct of_device_id pca9555_test_match_ids[] = {
{.compatible = "testboard,pca9555_test"},
{}
};
MODULE_DEVICE_TABLE(of, pca9555_test_match_ids);
static struct platform_driver pca9555_test_driver = {
.driver = {
.name = "pca9555_test",
.of_match_table = pca9555_test_match_ids
},
.probe = pca9555_test_probe,
.remove = pca9555_test_remove
};
module_platform_driver(pca9555_test_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Oct1158");
【Makefile】
KDIR ?= ../../Luckfox_Nova_SDK_250430/kernel
CROSS_COMPILE ?= ../../Luckfox_Nova_SDK_250430/prebuilts/gcc/linux-x86/aarch64/gcc-arm-10.3-2021.07-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-
obj-m = pca9555_test.o
.PHONY: build clean
build:
$(MAKE) -C $(KDIR) M=$(shell pwd) modules ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE)
clean:
$(MAKE) -C $(KDIR) M=$(shell pwd) clean
【程序运行结果】
注意:如果按键按得比较快,有时触发中断时gpio_value会等于0。
root@rk3308b-buildroot:/root# lsmod
Module Size Used by Tainted: G
aic8800_bsp 73728 0
root@rk3308b-buildroot:/root#
root@rk3308b-buildroot:/root# insmod pca9555_test.ko
[ 1181.061097] pca9555_test pca9555_test1: probe
[ 1181.067957] pca9555_test pca9555_test1: of_irq_get(): irq=60
[ 1181.074173] pca9555_test pca9555_test2: probe
[ 1181.080873] pca9555_test pca9555_test2: of_irq_get(): irq=61
[ 1181.087108] pca9555_test pca9555_test3: probe
[ 1181.093700] pca9555_test pca9555_test3: of_irq_get(): irq=62
root@rk3308b-buildroot:/root# [ 1188.605249] pca9555_test pca9555_test1: irq=60, gpio_value=1
[ 1189.327816] pca9555_test pca9555_test1: irq=60, gpio_value=1
[ 1189.637688] pca9555_test pca9555_test1: irq=60, gpio_value=1
[ 1190.707084] pca9555_test pca9555_test1: irq=60, gpio_value=1
[ 1191.065025] pca9555_test pca9555_test2: irq=61, gpio_value=1
[ 1191.304011] pca9555_test pca9555_test2: irq=61, gpio_value=1
[ 1191.521441] pca9555_test pca9555_test2: irq=61, gpio_value=1
[ 1191.733588] pca9555_test pca9555_test2: irq=61, gpio_value=1
[ 1192.353480] pca9555_test pca9555_test3: irq=62, gpio_value=1
[ 1192.598903] pca9555_test pca9555_test3: irq=62, gpio_value=1
[ 1193.090824] pca9555_test pca9555_test3: irq=62, gpio_value=1
[ 1193.434381] pca9555_test pca9555_test3: irq=62, gpio_value=1
root@rk3308b-buildroot:/root# rmmod pca9555_test.ko
[ 1196.898846] pca9555_test pca9555_test3: remove
[ 1196.904631] pca9555_test pca9555_test2: remove
[ 1196.910470] pca9555_test pca9555_test1: remove
root@rk3308b-buildroot:/root#
1万+

被折叠的 条评论
为什么被折叠?



