使用GPIO模拟I2C控制器需要按照以下步骤进行:
1、模拟出I2C控制器需要2个GPIO引脚,即SCL和SDA;
2、首先需要确定GPIO是否可用;
1、cat /sys/kernel/debug/gpio //查看当前gpio信息,无法查看到gpio10的信息
2、echo 10 > /sys/class/gpio/export //export需要设置的gpio10
3、cat /sys/kernel/debug/gpio //查看当前gpio信息,可以查看到gpio10的信息
4、echo out > /sys/class/gpio/gpio10/direction //设置gpio位输出模式
5、cat /sys/class/gpio/gpio10/value //查看gpio10的值
6、echo 1 > /sys/class/gpio/gpio10/value //设置gpio10输出值位高电平
7、cat /sys/class/gpio/gpio10/value //查看gpio10的值
8、cat /sys/kernel/debug/gpio //查看当前gpio信息
9、echo 10 > /sys/class/gpio/unexport //unexport需要设置的gpio10
10、cat /sys/kernel/debug/gpio //查看当前gpio信息
3、配置GPIO引脚:将GPIO引脚配置为输出模式,用于产生I2C控制器的SCL(串行时钟)和SDA(串行数据)信号;
4、产生起始条件:在需要开始数据传输时,将SDA引脚设置为高电平,然后在SCL引脚上产生一个高电平脉冲,以产生起始条件;
5、发送数据:在SCL信号的上升沿将数据写入SDA引脚,以发送一个字节的数据。在每个字节发送完毕后,需要在SCL信号的下降沿产生一个停止条件;
6、接收数据:在SCL信号的上升沿读取SDA引脚上的数据,以接收一个字节的数据。在每个字节接收完毕后,需要在SCL信号的下降沿产生一个停止条件;
7、产生停止条件:在数据传输完成后,将SDA引脚设置为低电平,然后在SCL引脚上产生一个高电平脉冲,以产生停止条件。
需要注意的是,使用GPIO模拟I2C控制器可能会受到硬件噪声和其他因素的影响,导致数据传输的可靠性降低。因此,在实际应用中,建议使用专门的I2C控制器芯片来保证数据传输的稳定性和可靠性。
下图为I2C控制器波形图:
怎么解析DTS文件中的gpio引脚
旧GPIO接口写法:
struct device *dev,
struct device_node *np = dev->of_node;
int gpio_pin = of_get_named_gpio_flags(np, "dts-name", 0, &flag); //解析设备树节点的属性信息,函数可以获取到GPIO引脚的配置信息,并将其存储在标志flag中
lcd_en_gpio =of_get_named_gpio(np, "dts-name", 0); //解析设备树节点的属性信息,函数可以获取到GPIO引脚的编号,并将其返回给调用者。
gpio_request(gpio_pin, "gpio_name"); //通过gpio号申请gpio
gpio_direction_output(gpio_pin, 1); //设置gpio_pin为输出引脚,同时初始值为1
gpio_set_value(gpio_pin, 0); //设置gpio_pin值为0
gpio_free(gpio_pin); //gpio_pin不再使用后应当释放
of_gpio_count(np); //多个gpio引脚解析
新GPIO接口写法:
struct i2c_client *i2c;
devm_gpiod_get(&i2c->dev,
"spk-con",
GPIOD_OUT_LOW)); //通过gpio号申请gpio
gpiod_direction_output(gpio_pin, 1); //设置gpio_pin为输出引脚,同时初始值为1
gpiod_direction_input(gpio_pin, 1); //设置gpio_pin为输出引脚,同时初始值为1
gpiod_set_value(gpio_pin, 0); //设置gpio_pin值为0
gpiod_free(gpio_pin); //gpio_pin不再使用后应当释放
gpiod_put(gpio_pin); //gpio_pin不再使用后应当释放
获取IRQ:
在probe函数中获取中断,调用中断处理函数
1、request_irq是Linux内核中用于注册中断服务函数的函数。当设备产生中断时,系统会调用这个函数来处理中断。
函数的原型如下:
int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *), unsigned long irqflags, const char *devname, void *dev_id);//函数原型
struct i2c_client *client = sd1513->client;
ret = request_irq(client->irq , sd1513_rx_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED, "sd1513_det_gpio", client);
printk("%s >>> sd1513 >>> %s >>> %d ret >>> %d >>> client->irq %d",__FILE__,__FUNCTION__,__LINE__,ret,client->irq);
if (ret < 0) {
free_irq(client->irq, sd1513);
}
参数说明:
irq:中断号,即设备的中断向量。
handler:中断处理程序,系统会在中断发生时调用这个函数来处理中断。
irqflags:中断标志位,用于指定中断的属性、触发方式等。
devname:设备名称,用于在内核日志中标识中断来源。
dev_id:设备标识符,用于在多个设备共享同一个中断号时区分不同的设备。
函数返回值为0表示注册成功,否则表示注册失败。
需要注意的是,request_irq函数需要在设备驱动程序中调用,而且需要在设备初始化之前完成中断的注册。
2、devm_request_irq是内核设备模型中用于申请中断的函数,它的功能与request_irq类似,但申请的是内核“managed”的资源。它与request_irq的区别在于,devm_request_irq申请的中断号不需要在出错的地方处理,也不需要在remove()接口里面显式释放。
static inline int devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char *devname, void *dev_id) //函数原型
struct i2c_client *i2c
ret = devm_request_irq(&i2c->dev, rt5651->irq, rt5651_irq,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
| IRQF_ONESHOT, "rt5651", rt5651);
if (ret == 0) {
/* Gets re-enabled by rt5651_set_jack() */
disable_irq(rt5651->irq);
} else {
dev_warn(&i2c->dev, "Failed to reguest IRQ %d: %d\n",
rt5651->irq, ret);
rt5651->irq = -ENXIO;
}
参数说明:
dev:指向设备对象的指针。
irq:中断号。
handler:中断处理函数。
irqflags:中断标志,用于指定中断触发模式。
devname:中断名称,用于方便调试。
dev_id:设备标识符,通常是设备结构体或设备私有数据的指针。
其中,irqflags参数可以是以下值的按位或:
IRQF_SHARED:允许多个设备共享同一个中断号。
IRQF_TRIGGER_NONE:禁用中断触发。
IRQF_TRIGGER_RISING:上升沿触发。
需要注意的是,在使用devm_request_irq函数时,需要确保已经正确初始化了设备对象和中断处理函数。同时,当不再需要使用中断时,需要使用free_irq函数显式释放中断号。