RK3568驱动指南|第十五篇 I2C-第181章使用GPIO模拟I2C驱动

news2024/7/4 17:30:21

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。


【公众号】迅为电子

【粉丝群】258811263(加群获取驱动文档+例程)

【视频观看】嵌入式学习之Linux驱动(第十五篇 I2C_全新升级)_基于RK3568

【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板


第181章使用GPIO模拟I2C驱动

I2C通信可以分为硬件I2C和软件I2C。在之前的章节中,我们使用的都是硬件I2C,这意味着无需自己编写相应的I2C时序代码。硬件I2C依赖于微控制器内部的专用硬件模块来处理通信时序,从而简化了开发过程,提高了通信效率和可靠性,而在本章节中将会对GPIO模拟I2C也就是软件I2C进行讲解。由于前面章节的实验中使用的都是I2C1 FT5X06触摸芯片,所以本章节继续使用I2C1进行软件I2C的实验。

181.1 设备树的修改

由于要使用软件I2C,所以要取消掉在设备树中硬件I2C1的使能,具体修改步骤如下:
首先在源码目录下使用以下命令对topeet_rk3568_lcds.dtsi文件进行修改,找到i2c1节点,将i2c1的status设置为disabled,设置完成如下图所示:

然后重新编译内核源码,得到boot.img镜像,烧写到开发板上,为了方便起见迅为已经将编译好的内核镜像放到了“iTOP-3568开发板\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动程序\111_soft_i2c\01_内核镜像”如下图所示:

将该镜像烧写到开发板之后没有I2C-1节点就证明修改成功了。

然后使用以下命令查看引脚复用

cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins

 

可以看到I2C1的两个复用引脚GPIO0 B3、GPIO0 B4已经是GPIO功能了。

181.2编写驱动程序

在本小节中将一步步编写模拟I2C驱动程序,最终编写完成的驱动程序存放路径为“iTOP-3568开发板\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动程序\111_soft_i2c\02_module”。

181.2.1 编写驱动框架

首先编写硬件I2C驱动程序框架,在驱动程序中申请GPIO0 B3和GPIO0 B4两个GPIO,并初始化为高电平,编写完成的驱动程序如下所示:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio.h>

// 定义 I2C 总线的时钟线和数据线对应的 GPIO 引脚编号
#define I2C_SCL 11
#define I2C_SDA 12

// 声明两个 GPIO 描述符变量,用于保存 SCL 和 SDA 引脚的描述符
struct gpio_desc *i2c_scl_desc;
struct gpio_desc *i2c_sda_desc;

// 驱动初始化函数
static int ft5x06_driver_init(void)
{
    // 将 GPIO 编号转换为 GPIO 描述符
    i2c_scl_desc = gpio_to_desc(I2C_SCL);
    if (i2c_scl_desc == NULL) {
        printk("gpio_to_desc error for SCL pin\n");
        return -1;
    }

    i2c_sda_desc = gpio_to_desc(I2C_SDA);
    if (i2c_sda_desc == NULL) {
        printk("gpio_to_desc error for SDA pin\n");
        return -1;
    }

    // 将 GPIO 引脚设置为输出模式,并初始化为高电平
    gpiod_direction_output(i2c_scl_desc, 1);
    gpiod_direction_output(i2c_sda_desc, 1);

    return 0;
}

// 驱动退出函数
static void ft5x06_driver_exit(void)
{
    // 释放 GPIO 描述符
    gpiod_put(i2c_scl_desc);
    gpiod_put(i2c_sda_desc);
}

// 注册驱动初始化和退出函数
module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);

MODULE_LICENSE("GPL");

本小节编写的驱动程序重点在ft5x06_driver_init驱动初始化函数函数,下面对ft5x06_driver_init函数进行讲解:

第17-28行使用gpio_to_desc函数将I2C_SCL、I2C_SDA两个GPIO编号转换为GPIO描述符。

第31-32行使用gpiod_direction_output函数将GPIO引脚设置为输出模式并初始化为高电平。

181.2.2 编写起始和终止信号代码

在上个小节中申请了GPIO0 B3和GPIO0 B4两个GPIO,并初始化为高电平,在本小节中继续完善硬件I2C驱动程序,添加起始信号和终止信号相关的代码,起始信号和终止信号通信时序图如下所示:

起始信号为SDA线从高电平到低电平的跳变,同时SCL线保持高电平,终止信号为为SDA线从低电平到高电平的跳变,同时SCL线保持高电平。然后根据上述时序图完善起始信号i2c_start和终止信号i2c_stop相关的代码,编写完成的驱动程序如下图所示:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/jiffies.h>

// 定义 I2C 总线的时钟线和数据线对应的 GPIO 引脚编号
#define I2C_SCL 11
#define I2C_SDA 12

// 声明两个 GPIO 描述符变量,用于保存 SCL 和 SDA 引脚的描述符
struct gpio_desc *i2c_scl_desc;
struct gpio_desc *i2c_sda_desc;

// I2C 起始条件函数
void i2c_start(void)
{
    // 将 SCL 和 SDA 引脚设置为输出模式,并初始化为高电平
    // 这是 I2C 总线的空闲状态
    gpiod_direction_output(i2c_scl_desc, 1);
    gpiod_direction_output(i2c_sda_desc, 1);
    mdelay(1); // 延时 1 毫秒

    // 将 SDA 引脚设置为低电平,保持 SCL 为高电平
    // 这将产生 I2C 总线的起始条件
    gpiod_direction_output(i2c_sda_desc, 0);
    mdelay(1); // 延时 1 毫秒

    // 将 SCL 引脚设置为低电平
    // 起始条件建立完成
    gpiod_direction_output(i2c_scl_desc, 0);
    mdelay(1); // 延时 1 毫秒
}

// I2C 停止条件函数
void i2c_stop(void)
{
    // 将 SCL 和 SDA 引脚设置为低电平
    gpiod_direction_output(i2c_scl_desc, 0);
    gpiod_direction_output(i2c_sda_desc, 0);
    mdelay(1); // 延时 1 毫秒

    // 将 SCL 引脚设置为高电平
    gpiod_direction_output(i2c_scl_desc, 1);
    mdelay(1); // 延时 1 毫秒

    // 将 SDA 引脚设置为高电平
    // 这将产生 I2C 总线的停止条件
    gpiod_direction_output(i2c_sda_desc, 1);
    mdelay(1); // 延时 1 毫秒
}

// 驱动初始化函数
static int ft5x06_driver_init(void)
{
    // 将 GPIO 编号转换为 GPIO 描述符
    i2c_scl_desc = gpio_to_desc(I2C_SCL);
    if (i2c_scl_desc == NULL) {
        printk("gpio_to_desc error for SCL pin\n");
        return -1;
    }

    i2c_sda_desc = gpio_to_desc(I2C_SDA);
    if (i2c_sda_desc == NULL) {
        printk("gpio_to_desc error for SDA pin\n");
        return -1;
    }

    // 将 GPIO 引脚设置为输出模式,并初始化为高电平
    // 这是 I2C 总线的空闲状态
    gpiod_direction_output(i2c_scl_desc, 1);
    gpiod_direction_output(i2c_sda_desc, 1);

	i2c_start();
	i2c_stop();
    return 0;
}

// 驱动退出函数
static void ft5x06_driver_exit(void)
{
    // 释放 GPIO 描述符
    gpiod_put(i2c_scl_desc);
    gpiod_put(i2c_sda_desc);
}

// 注册驱动初始化和退出函数
module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);

MODULE_LICENSE("GPL");

181.2.3 编写发送和接收应答信号代码

在上个小节中添加了起始信号和终止信号两个函数,本小节继续对硬件I2C驱动进行填充,添加发送和接收应答信号相关的代码,关于应答信号相关的具体时序图如下所示:

当发送设备在第9个时钟脉冲期间释放SDA线时,接收设备可以拉低SDA线并在此时钟高电平期间保持稳定低电平,这就定义了应答信号。如果在第9个时钟脉冲期间SDA线保持高电平,则定义为非应答信号。

编写完成的驱动程序如下图所示:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/jiffies.h>

// 定义 I2C 总线的时钟线和数据线对应的 GPIO 引脚编号
#define I2C_SCL 11
#define I2C_SDA 12

// 声明两个 GPIO 描述符变量,用于保存 SCL 和 SDA 引脚的描述符
struct gpio_desc *i2c_scl_desc;
struct gpio_desc *i2c_sda_desc;


// I2C 起始条件函数
void i2c_start(void)
{
    // 将 SCL 和 SDA 引脚设置为输出模式,并初始化为高电平
    // 这是 I2C 总线的空闲状态
    gpiod_direction_output(i2c_scl_desc, 1);
    gpiod_direction_output(i2c_sda_desc, 1);
    mdelay(1); // 延时 1 毫秒

    // 将 SDA 引脚设置为低电平,保持 SCL 为高电平
    // 这将产生 I2C 总线的起始条件
    gpiod_direction_output(i2c_sda_desc, 0);
    mdelay(1); // 延时 1 毫秒

    // 将 SCL 引脚设置为低电平
    // 起始条件建立完成
    gpiod_direction_output(i2c_scl_desc, 0);
    mdelay(1); // 延时 1 毫秒
}

// I2C 停止条件函数
void i2c_stop(void)
{
    // 将 SCL 和 SDA 引脚设置为低电平
    gpiod_direction_output(i2c_scl_desc, 0);
    gpiod_direction_output(i2c_sda_desc, 0);
    mdelay(1); // 延时 1 毫秒

    // 将 SCL 引脚设置为高电平
    gpiod_direction_output(i2c_scl_desc, 1);
    mdelay(1); // 延时 1 毫秒

    // 将 SDA 引脚设置为高电平
    // 这将产生 I2C 总线的停止条件
    gpiod_direction_output(i2c_sda_desc, 1);
    mdelay(1); // 延时 1 毫秒
}

// 发送ACK信号
void i2c_send_ack(int ack) {
    // 设置SDA线为输出模式
    gpiod_direction_output(i2c_sda_desc, 0);
    
    if (ack) {
        // 发送ACK信号, SDA线拉低
        gpiod_direction_output(i2c_sda_desc, 0);
    } else {
        // 发送NACK信号, SDA线拉高
        gpiod_direction_output(i2c_sda_desc, 1);
    }
    
    // 拉高SCL线1ms,然后拉低
    gpiod_direction_output(i2c_scl_desc, 1);
    mdelay(1);
    gpiod_direction_output(i2c_scl_desc, 0);
}

// 接收ACK信号
int i2c_recv_ack(void) {
    int value = 0;
    
    // 设置SDA线为输入模式
    gpiod_direction_input(i2c_sda_desc);
    
    // 拉高SCL线1ms
    gpiod_direction_output(i2c_scl_desc, 1);
    mdelay(1);
    
    // 读取SDA线的电平状态
    if (gpiod_get_value(i2c_sda_desc)) {
        value = 1; // 接收到NACK信号
    } else {
        value = 0; // 接收到ACK信号
    }
    
    // 拉低SCL线
    gpiod_direction_output(i2c_scl_desc, 0);
    
    // 设置SDA线为输出模式并拉高
    gpiod_direction_output(i2c_sda_desc, 1);
    
    return value;
}

// 驱动初始化函数
static int ft5x06_driver_init(void)
{
    // 将 GPIO 编号转换为 GPIO 描述符
    i2c_scl_desc = gpio_to_desc(I2C_SCL);
    if (i2c_scl_desc == NULL) {
        printk("gpio_to_desc error for SCL pin\n");
        return -1;
    }

    i2c_sda_desc = gpio_to_desc(I2C_SDA);
    if (i2c_sda_desc == NULL) {
        printk("gpio_to_desc error for SDA pin\n");
        return -1;
    }

    // 将 GPIO 引脚设置为输出模式,并初始化为高电平
    // 这是 I2C 总线的空闲状态
    gpiod_direction_output(i2c_scl_desc, 1);
    gpiod_direction_output(i2c_sda_desc, 1);

	i2c_start();
	i2c_stop();
    return 0;
}

// 驱动退出函数
static void ft5x06_driver_exit(void)
{
    // 释放 GPIO 描述符
    gpiod_put(i2c_scl_desc);
    gpiod_put(i2c_sda_desc);
}

// 注册驱动初始化和退出函数
module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);

MODULE_LICENSE("GPL");

181.2.4 编写发送和接收数据函数

在上个小节中添加了接收和发送应答信号两个函数,本小节继续对硬件I2C驱动进行填充,添加发送和接收数据相关的代码,接收和发送相关的时序图如下所示:

1.首先发送一个7位的目标地址,后跟一个读/写方向位(R/W位)。

2.读/写方向位是第8位,0表示写操作(WRITE),1表示读操作(READ)。

编写完成的驱动程序如下图所示:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/jiffies.h>

// 定义 I2C 总线的时钟线和数据线对应的 GPIO 引脚编号
#define I2C_SCL 11
#define I2C_SDA 12

// 声明两个 GPIO 描述符变量,用于保存 SCL 和 SDA 引脚的描述符
struct gpio_desc *i2c_scl_desc;
struct gpio_desc *i2c_sda_desc;


// I2C 起始条件函数
void i2c_start(void)
{
    // 将 SCL 和 SDA 引脚设置为输出模式,并初始化为高电平
    // 这是 I2C 总线的空闲状态
    gpiod_direction_output(i2c_scl_desc, 1);
    gpiod_direction_output(i2c_sda_desc, 1);
    mdelay(1); // 延时 1 毫秒

    // 将 SDA 引脚设置为低电平,保持 SCL 为高电平
    // 这将产生 I2C 总线的起始条件
    gpiod_direction_output(i2c_sda_desc, 0);
    mdelay(1); // 延时 1 毫秒

    // 将 SCL 引脚设置为低电平
    // 起始条件建立完成
    gpiod_direction_output(i2c_scl_desc, 0);
    mdelay(1); // 延时 1 毫秒
}

// I2C 停止条件函数
void i2c_stop(void)
{
    // 将 SCL 和 SDA 引脚设置为低电平
    gpiod_direction_output(i2c_scl_desc, 0);
    gpiod_direction_output(i2c_sda_desc, 0);
    mdelay(1); // 延时 1 毫秒

    // 将 SCL 引脚设置为高电平
    gpiod_direction_output(i2c_scl_desc, 1);
    mdelay(1); // 延时 1 毫秒

    // 将 SDA 引脚设置为高电平
    // 这将产生 I2C 总线的停止条件
    gpiod_direction_output(i2c_sda_desc, 1);
    mdelay(1); // 延时 1 毫秒
}

// 发送ACK信号
void i2c_send_ack(int ack) {
    // 设置SDA线为输出模式
    gpiod_direction_output(i2c_sda_desc, 0);
    
    if (ack) {
        // 发送ACK信号, SDA线拉低
        gpiod_direction_output(i2c_sda_desc, 0);
    } else {
        // 发送NACK信号, SDA线拉高
        gpiod_direction_output(i2c_sda_desc, 1);
    }
    
    // 拉高SCL线1ms,然后拉低
    gpiod_direction_output(i2c_scl_desc, 1);
    mdelay(1);
    gpiod_direction_output(i2c_scl_desc, 0);
}

// 接收ACK信号
int i2c_recv_ack(void) {
    int value = 0;
    
    // 设置SDA线为输入模式
    gpiod_direction_input(i2c_sda_desc);
    
    // 拉高SCL线1ms
    gpiod_direction_output(i2c_scl_desc, 1);
    mdelay(1);
    
    // 读取SDA线的电平状态
    if (gpiod_get_value(i2c_sda_desc)) {
        value = 1; // 接收到NACK信号
    } else {
        value = 0; // 接收到ACK信号
    }
    
    // 拉低SCL线
    gpiod_direction_output(i2c_scl_desc, 0);
    
    // 设置SDA线为输出模式并拉高
    gpiod_direction_output(i2c_sda_desc, 1);
    
    return value;
}

void i2c_send_data(int data) {
    int i;
    int value;

    // 设置SCL线为输出模式并拉低
    gpiod_direction_output(i2c_scl_desc, 0);

    // 发送8位数据
    for (i = 0; i < 8; i++) {
        // 获取当前位的值
        value = (data << i) & 0x80;

        // 根据当前位的值设置SDA线
        if (value) {
            gpiod_direction_output(i2c_sda_desc, 1);
        } else {
            gpiod_direction_output(i2c_sda_desc, 0);
        }

        // 拉高SCL线1ms,然后拉低
        gpiod_direction_output(i2c_scl_desc, 1);
        mdelay(1);
        gpiod_direction_output(i2c_scl_desc, 0);
        mdelay(1);
    }
}

int i2c_recv_data(void) {
    int i;
    int temp = 0;
    int data = 0;

    // 设置SDA线为输入模式
    gpiod_direction_input(i2c_sda_desc);
    mdelay(1);

    // 接收8位数据
    for (i = 0; i < 8; i++) {
        // 拉低SCL线1ms
        gpiod_direction_output(i2c_scl_desc, 0);
        mdelay(1);

        // 拉高SCL线1ms
        gpiod_direction_output(i2c_scl_desc, 1);
        mdelay(1);

        // 读取SDA线的电平状态
        data = gpiod_get_value(i2c_sda_desc);

        // 根据当前位的值更新接收数据
        if (data) {
            temp = (temp << 1) | data;
        } else {
            temp = (temp << 1) & ~data;
        }
    }

    // 拉低SCL线
    gpiod_direction_output(i2c_scl_desc, 0);
    mdelay(1);

    // 设置SDA线为输出模式并拉高
    gpiod_direction_output(i2c_sda_desc, 1);

    return temp;
}


// 驱动初始化函数
static int ft5x06_driver_init(void)
{
    // 将 GPIO 编号转换为 GPIO 描述符
    i2c_scl_desc = gpio_to_desc(I2C_SCL);
    if (i2c_scl_desc == NULL) {
        printk("gpio_to_desc error for SCL pin\n");
        return -1;
    }

    i2c_sda_desc = gpio_to_desc(I2C_SDA);
    if (i2c_sda_desc == NULL) {
        printk("gpio_to_desc error for SDA pin\n");
        return -1;
    }

    // 将 GPIO 引脚设置为输出模式,并初始化为高电平
    // 这是 I2C 总线的空闲状态
    gpiod_direction_output(i2c_scl_desc, 1);
    gpiod_direction_output(i2c_sda_desc, 1);

    return 0;
}

// 驱动退出函数
static void ft5x06_driver_exit(void)
{
    // 释放 GPIO 描述符
    gpiod_put(i2c_scl_desc);
    gpiod_put(i2c_sda_desc);
}

// 注册驱动初始化和退出函数
module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);

MODULE_LICENSE("GPL");

181.2.5 编写FT5X06寄存器读写函数

在上个小节中添加了数据发送和接收两个函数,本小节继续对硬件I2C驱动进行填充,添加FT5X06寄存器读写函数,编写完成的驱动程序如下图所示:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/jiffies.h>

// 定义 I2C 总线的时钟线和数据线对应的 GPIO 引脚编号
#define I2C_SCL 11
#define I2C_SDA 12

// 声明两个 GPIO 描述符变量,用于保存 SCL 和 SDA 引脚的描述符
struct gpio_desc *i2c_scl_desc;
struct gpio_desc *i2c_sda_desc;


// I2C 起始条件函数
void i2c_start(void)
{
    // 将 SCL 和 SDA 引脚设置为输出模式,并初始化为高电平
    // 这是 I2C 总线的空闲状态
    gpiod_direction_output(i2c_scl_desc, 1);
    gpiod_direction_output(i2c_sda_desc, 1);
    mdelay(1); // 延时 1 毫秒

    // 将 SDA 引脚设置为低电平,保持 SCL 为高电平
    // 这将产生 I2C 总线的起始条件
    gpiod_direction_output(i2c_sda_desc, 0);
    mdelay(1); // 延时 1 毫秒

    // 将 SCL 引脚设置为低电平
    // 起始条件建立完成
    gpiod_direction_output(i2c_scl_desc, 0);
    mdelay(1); // 延时 1 毫秒
}

// I2C 停止条件函数
void i2c_stop(void)
{
    // 将 SCL 和 SDA 引脚设置为低电平
    gpiod_direction_output(i2c_scl_desc, 0);
    gpiod_direction_output(i2c_sda_desc, 0);
    mdelay(1); // 延时 1 毫秒

    // 将 SCL 引脚设置为高电平
    gpiod_direction_output(i2c_scl_desc, 1);
    mdelay(1); // 延时 1 毫秒

    // 将 SDA 引脚设置为高电平
    // 这将产生 I2C 总线的停止条件
    gpiod_direction_output(i2c_sda_desc, 1);
    mdelay(1); // 延时 1 毫秒
}

// 发送ACK信号
void i2c_send_ack(int ack) {
    // 设置SDA线为输出模式
    gpiod_direction_output(i2c_sda_desc, 0);
    
    if (ack) {
        // 发送ACK信号, SDA线拉低
        gpiod_direction_output(i2c_sda_desc, 0);
    } else {
        // 发送NACK信号, SDA线拉高
        gpiod_direction_output(i2c_sda_desc, 1);
    }
    
    // 拉高SCL线1ms,然后拉低
    gpiod_direction_output(i2c_scl_desc, 1);
    mdelay(1);
    gpiod_direction_output(i2c_scl_desc, 0);
}

// 接收ACK信号
int i2c_recv_ack(void) {
    int value = 0;
    
    // 设置SDA线为输入模式
    gpiod_direction_input(i2c_sda_desc);
    
    // 拉高SCL线1ms
    gpiod_direction_output(i2c_scl_desc, 1);
    mdelay(1);
    
    // 读取SDA线的电平状态
    if (gpiod_get_value(i2c_sda_desc)) {
        value = 1; // 接收到NACK信号
    } else {
        value = 0; // 接收到ACK信号
    }
    
    // 拉低SCL线
    gpiod_direction_output(i2c_scl_desc, 0);
    
    // 设置SDA线为输出模式并拉高
    gpiod_direction_output(i2c_sda_desc, 1);
    
    return value;
}

void i2c_send_data(int data) {
    int i;
    int value;

    // 设置SCL线为输出模式并拉低
    gpiod_direction_output(i2c_scl_desc, 0);

    // 发送8位数据
    for (i = 0; i < 8; i++) {
        // 获取当前位的值
        value = (data << i) & 0x80;

        // 根据当前位的值设置SDA线
        if (value) {
            gpiod_direction_output(i2c_sda_desc, 1);
        } else {
            gpiod_direction_output(i2c_sda_desc, 0);
        }

        // 拉高SCL线1ms,然后拉低
        gpiod_direction_output(i2c_scl_desc, 1);
        mdelay(1);
        gpiod_direction_output(i2c_scl_desc, 0);
        mdelay(1);
    }
}

int i2c_recv_data(void) {
    int i;
    int temp = 0;
    int data = 0;

    // 设置SDA线为输入模式
    gpiod_direction_input(i2c_sda_desc);
    mdelay(1);

    // 接收8位数据
    for (i = 0; i < 8; i++) {
        // 拉低SCL线1ms
        gpiod_direction_output(i2c_scl_desc, 0);
        mdelay(1);

        // 拉高SCL线1ms
        gpiod_direction_output(i2c_scl_desc, 1);
        mdelay(1);

        // 读取SDA线的电平状态
        data = gpiod_get_value(i2c_sda_desc);

        // 根据当前位的值更新接收数据
        if (data) {
            temp = (temp << 1) | data;
        } else {
            temp = (temp << 1) & ~data;
        }
    }

    // 拉低SCL线
    gpiod_direction_output(i2c_scl_desc, 0);
    mdelay(1);

    // 设置SDA线为输出模式并拉高
    gpiod_direction_output(i2c_sda_desc, 1);

    return temp;
}

// ft5x06 触摸屏写寄存器函数
void ft5x06_write_reg(int addr, int reg, int value) {
    int ack;

    // 开始 I2C 通信
    i2c_start();

    // 发送触摸屏设备地址(写操作)
    i2c_send_data(addr << 1 | 0x00);
    ack = i2c_recv_ack();
    if (ack) {
        printk("send write + addr error\n");
        goto end;
    }

    // 发送寄存器地址
    i2c_send_data(reg);
    ack = i2c_recv_ack();
    if (ack) {
        printk("send reg error\n");
        goto end;
    }

    // 发送要写入的值
    i2c_send_data(value);
    ack = i2c_recv_ack();
    if (ack) {
        printk("send value error\n");
    }

end:
    // 结束 I2C 通信
    i2c_stop();
}

//  ft5x06 触摸屏读寄存器函数
int ft5x06_read_reg(int addr, int reg) {
    int ack;
    int data;

    // 开始 I2C 通信
    i2c_start();

    // 发送触摸屏设备地址(写操作)
    i2c_send_data(addr << 1 | 0x00);
    ack = i2c_recv_ack();
    if (ack) {
        printk("send write + addr error\n");
        goto end;
    }

    // 发送要读取的寄存器地址
    i2c_send_data(reg);
    ack = i2c_recv_ack();
    if (ack) {
        printk("send reg error\n");
        goto end;
    }

    // 重新开始 I2C 通信,发送读操作地址
    i2c_start();
    i2c_send_data(addr << 1 | 0x01);
    ack = i2c_recv_ack();
    if (ack) {
        printk("send read + addr error\n");
        goto end;
    }

    // 读取寄存器值
    data = i2c_recv_data();
    printk("data is %d\n", data);

    // 发送 ACK 以结束读操作
    i2c_send_ack(0);

end:
    // 结束 I2C 通信
    i2c_stop();

    return data;
}

// 驱动初始化函数
static int ft5x06_driver_init(void)
{
    // 将 GPIO 编号转换为 GPIO 描述符
    i2c_scl_desc = gpio_to_desc(I2C_SCL);
    if (i2c_scl_desc == NULL) {
        printk("gpio_to_desc error for SCL pin\n");
        return -1;
    }

    i2c_sda_desc = gpio_to_desc(I2C_SDA);
    if (i2c_sda_desc == NULL) {
        printk("gpio_to_desc error for SDA pin\n");
        return -1;
    }

    // 将 GPIO 引脚设置为输出模式,并初始化为高电平
    // 这是 I2C 总线的空闲状态
    gpiod_direction_output(i2c_scl_desc, 1);
    gpiod_direction_output(i2c_sda_desc, 1);

	ft5x06_write_reg(0x38,0x80,0x33);
	ft5x06_read_reg(0x38,0x80);
    return 0;
}

// 驱动退出函数
static void ft5x06_driver_exit(void)
{
    // 释放 GPIO 描述符
    gpiod_put(i2c_scl_desc);
    gpiod_put(i2c_sda_desc);
}

// 注册驱动初始化和退出函数
module_init(ft5x06_driver_init);
module_exit(ft5x06_driver_exit);

MODULE_LICENSE("GPL");

181.3运行测试

181.3.1 编译驱动程序

首先在上一小节中的ft5x06_driver.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:

export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m += ft5x06_driver.o    #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel    #这里是你的内核目录                                                                                                                            
PWD ?= $(shell pwd)
all:
    make -C $(KDIR) M=$(PWD) modules    #make操作
clean:
    make -C $(KDIR) M=$(PWD) clean    #make clean操作

对于Makefile的内容注释已在上图添加,保存退出之后,来到存放platform_driver.c和Makefile文件目录下,如下图所示:

然后使用命令“make”进行驱动的编译,编译完成如下图所示:

编译完生成ft5x06_driver.ko目标文件,如下图所示:

181.3.2 运行测试

首先启动开发板,开发板启动进入系统之后如下图所示:

然后将上一个小节编译完成的ko文件拷贝到开发板上,拷贝完成如下图所示:

然后使用以下命令加载驱动,加载完成如下图所示:

insmod ft5x06_driver.ko

可以看到这里打印的值为51,换算成16进制为0x33,与驱动程序中写入的值是相同的,这就证明在上个小节中编写的驱动程序是没有问题的。

至此,使用GPIO模拟I2C的驱动代码就测试完成。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1886308.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

一个能让渲染性能提高100倍的办法

GPU 光线追踪是当今的热门话题&#xff0c;所以让我们来谈谈它&#xff01;今天我们将光线追踪一个单个球体。 使用片段着色器。 是的&#xff0c;我知道。并不特别花哨。你可以在 Shadertoy 上搜索并获得数百个示例(https://www.shadertoy.com/results?querysphere)。甚至已…

速通RK3568开发板多网口网线直连测试

开源鸿蒙硬件方案领跑者 触觉智能 本文适用于在EVB3568开发板上进行多网口网线直连。触觉智能的EVB3568主板基于瑞芯微RK3568处理器&#xff0c;采用22nm先进工艺制程&#xff0c;四核A55 CPU&#xff0c;主频高达2.0GHz&#xff0c;支持高达8GB高速LPDDR4&#xff0c;1T算力N…

Linux miniconda 安装tensorflow-gpu遇到找不到GPU问题

背景&#xff1a; Linux Miniconda python3.9 安装步骤 1、 pip install tensorflow-gpu2.8.0 -i https://pypi.tuna.tsinghua.edu.cn/simple 2、报错如下&#xff1a; 更换镜像源&#xff0c;单独安装 pip install tf-estimator-nightly2.8.0.dev2021122109 -i https:/…

【C语言】typedef 关键字

在C语言中&#xff0c;typedef关键字用于给现有的数据类型起一个新的名字。它在提高代码可读性、简化复杂类型声明、增强可维护性方面非常有用。typedef通常用于定义结构体、指针、函数指针以及其他复杂类型。 基本用法 typedef int MyInt; MyInt x 10;在这个例子中&#xf…

42、nginx之nginx.conf

nginx----web服务器 一、nginx http就是apache&#xff0c;在国内很少。 nginx是开源的&#xff0c;是一款高性能&#xff0c;轻量级的web服务软件。 稳定性高&#xff0c;而且版本迭代比较快&#xff08;修复bug速度比较快&#xff0c;安全性快&#xff09; 消耗系统资源…

中日区块链“大比拼”!中国蚂蚁加大区块链押注资本!日本索尼进军加密货币市场!

科技巨头在区块链和加密货币领域的动作越来越频繁。近期&#xff0c;中国金融科技巨头蚂蚁集团进一步加大了在区块链业务上的投资&#xff0c;而日本电子科技巨头索尼集团则正式进军加密货币交易领域。这些举措反映了两国对于区块链和加密资产领域的不同态度和布局。 蚂蚁集团加…

Load Tensor to local Nvidia GPU

0. 安装Nvidia驱动 ubuntu24.04的安装非常简单&#xff0c;在安装界面&#xff0c;选择为"图形化和其他硬件安装驱动"&#xff0c;重启后即有原版Nvidia驱动(如图Nvidia X xxx) 1.确定电脑上是否有NvidiaGPU且安装好Nvidia驱动 import torch print(torch.version…

LInux SSH Server远程代码执行漏洞 (CVE-2024-6387)处理

一、漏洞描述 2024年7月1日&#xff0c;OpenSSH Server中存在的一个RCE远程代码执行漏洞&#xff08;CVE-2024-6387&#xff0c;又被称为regreSSHion&#xff09;细节被公开&#xff0c;该漏洞影响基于glibc的Linux系统上的OpenSSH Server (sshd)。 默认配置下的OpenSSH Serve…

MIX OTP——依赖项和总体项目

在本章中&#xff0c;我们将讨论如何管理 Mix 中的依赖项。 我们的 kv 应用程序已经完成&#xff0c;现在是时候实现处理我们在第一章中定义的请求的服务器了&#xff1a; 但是&#xff0c;我们不会向 kv 应用程序添加更多代码&#xff0c;而是将 TCP 服务器构建为另一个应用程…

Linux系统之安装Firefox浏览器

Linux系统之安装Firefox浏览器 一、Firefox浏览器介绍1.1 Firefox浏览器介绍1.2 Firefox浏览器特点 二、环境介绍二、本次实践环境介绍2.1 环境规划2.2 本次实践介绍 三、安装firefox浏览器3.1 安装epel3.2 检查yum仓库状态3.3 安装Firefox浏览器3.4 查看Firefox版本 四、在命令…

win11电源设置

把钩子去掉以后 win11的电脑关机才有用 否则&#xff0c;关机了&#xff0c;电脑也实际上一直在运行

partition()方法——分割字符串为元组

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法参考 partition()方法根据指定的分隔符将字符串进行分割。如果字符串中包含指定的分隔符&#xff0c;则返回一个3元的元组&#xff0c;第一个为…

HarmonyOS(38) UIAbility里icon和label的作用

UIAbility里icon和label的作用 icon和label实际效果测试代码传送门参考资料 icon和label 为使应用能够正常使用UIAbility&#xff0c;需要在module.json5配置文件的abilities标签中声明UIAbility的名称、入口、标签等相关信息&#xff1a; {"module": {..."ab…

3.3prometheus命令行参数讲解

本节重点介绍 : target页面flags页面status页面tsdb-status页面 访问地址 $ip:9090 target页面 flags页面 展示命令行参数的&#xff0c;没设置的取默认值 status页面 描述运行信息和编译的信息 tsdb-status页面 打印存储的运行状态信息帮我们定位重查询的 服务发现页面…

怎么把录音转文字?推荐几个简单易操作的方法

在小暑这个节气里&#xff0c;炎热的天气让人分外渴望效率up&#xff01;Up&#xff01;Up&#xff01; 对于那些在会议或课堂中急需记录信息的朋友们&#xff0c;手写笔记的速度往往难以跟上讲话的节奏。此时&#xff0c;电脑录音转文字软件就像一阵及时雨&#xff0c;让记录…

深度学习原理与Pytorch实战

深度学习原理与Pytorch实战 第2版 强化学习人工智能神经网络书籍 python动手学深度学习框架书 TransformerBERT图神经网络&#xff1a; 技术讲解 编辑推荐 1.基于PyTorch新版本&#xff0c;涵盖深度学习基础知识和前沿技术&#xff0c;由浅入深&#xff0c;通俗易懂&#xf…

动态顺序表实现通讯录

系列文章目录 【数据结构】顺序表 文章目录 系列文章目录前言一、通讯录的功能要求二、通讯录的代码实现1. 新建文件2. 创建通讯录的结构体3. 对顺序表文件进行修改4. 通讯录具体功能实现4.1. 通讯录的初始化和销毁4.2. 增加联系人信息&#xff08;尾插&#xff09;4.3. 查找指…

无污染的独立海域-第13届蓝桥杯省赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第90讲。 无污染的独立海域…

余承东在母校西工大毕业典礼演讲:定位决定地位,眼界决定境界。

添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 【6月29日&#xff0c;西北工业大学2024届本科生毕业典礼暨学位授予仪式隆重举行。典礼上&#xff0c;华为常务董事、终端BG 董事长、智能汽车解决方案BU 董事长余承东作为校友代表致辞&#xff0c;为毕业生…

项目实战--Spring Boot实现三次登录容错功能

一、功能描述 项目设计要求输入三次错误密码后&#xff0c;要求隔段时间才能继续进行登录操作&#xff0c;这里简单记录一下实现思路 二、设计方案 有几个问题需要考虑一下&#xff1a; 1.是只有输错密码才锁定&#xff0c;还是账户名和密码任何一个输错就锁定&#xff1f;2…