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

瑞芯微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.mfbz.cn/a/766119.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

阿里云再次突发故障,高可用形同虚设?

作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、MySQL、PG、高斯及Greenplum备份恢复&#xff0c; 安装迁移&#xff0c;性能优化、故障…

轻松拯救手机数据,数据恢复软件推荐这8款!

在现代生活中&#xff0c;手机已成为我们不可或缺的工具&#xff0c;承载着大量重要的个人和工作数据。然而&#xff0c;意外删除、系统崩溃、设备损坏等情况可能导致数据丢失&#xff0c;给我们带来极大的困扰。幸运的是&#xff0c;随着科技的发展&#xff0c;各种手机数据恢…

文生图功能介绍

Stable Diffusion WebUI&#xff08;SD WebUI&#xff09;及文生图功能介绍 一、引言 随着人工智能技术的飞速发展&#xff0c;AI绘画作为一种新兴的艺术形式&#xff0c;逐渐走入人们的视野。Stable Diffusion WebUI&#xff08;简称SD WebUI&#xff09;作为AI绘画领域的重…

[附源码]最新springboot线上电商|前后端分离|界面简洁

一. 前言 今天小编给大家带来了一款可学习&#xff0c;可商用的&#xff0c;线上电商的网站源码&#xff0c;支持二开&#xff0c;无加密。代码的后端是SpringBoot技术栈&#xff08;非jsp&#xff09;&#xff0c;前端是Angular。如果您需要定制需求请找小编。 文章第六小节…

英灵神殿mac能玩吗 英灵神殿对电脑配置要求《英灵神殿》新手攻略查询 PD虚拟机能玩英灵神殿吗

近年来&#xff0c;随着《英灵神殿》&#xff08;Valheim&#xff09;游戏的火热&#xff0c;越来越多的玩家被其独特的北欧神话题材和丰富的生存挑战所吸引。然而&#xff0c;对于Mac用户来说&#xff0c;如何在Mac平台上运行这款游戏可能是一个问题。此外&#xff0c;作为一名…

编译原理3-自底向上的语法分析

自底向上分析 &#xff0c;就是自左至右扫描输入串&#xff0c;自底向上进 行分析&#xff1b;通过反复查找当前句型的 句柄&#xff0c; 并使 用产生式规则 将找到的句柄归约为相应的非终结符 。逐步进行“ 归约 ”&#xff0c;直到至文法的开始符号&#xff1b; 对于规范推导…

【unity实战】在Unity中使用有限状态机制作一个敌人AI

最终效果 文章目录 最终效果前言有限状态机的主要作用和意义素材下载逻辑图敌人动画配置优雅的代码文件目录状态机代码定义敌人不同状态切换创建敌人效果更多的敌人参考源码完结 前言 有限状态机以前的我嗤之以鼻&#xff0c;现在的我逐帧分析。其实之前我就了解过有限状态机&…

day03-主页模块-修改密码

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.获取用户资料在Vuex中共享登录成功跳转到主页之后&#xff0c;可以获取用户资料&#xff0c;获取的资料在Vuex中共享&#xff0c;这样用户就可以很方便的获取该信…

Leetcode刷题笔记 | 二叉树基本性质 | 一天的题量 | 5道题目 | 深度优先搜索 | 广度优先搜索 | 递归 | 遍历

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 &#x1f4cc;本期毛毛张分享的是LeetCode关于二叉树&#x1f332;的性质的一些基础题&#xff0c;做这些题目的本质还是遍历二叉树&#x1f3c3;‍➡️的过程&#…

计算机组成原理 | 储存子系统(1)概述

三级储存体系 物理与虚拟存储器 &#xff08;抽象逻辑模型&#xff09; 存储器类型 存储器的速度指标

中国民间网络外交组织(CCND)

中国民间网络外交组织Chinese Civil Network Diplomacy简称(CCDN) 是由中国网民建立起来的一个网络外交组织&#xff0c;深度贯彻党的主张和网民意志的统一&#xff0c;为保护中国中华优秀传统文化&#xff0c;民族自信&#xff0c;国家安全&#xff0c;民族利益&#xff0c;社…

昇思MindSpore学习笔记2-04 LLM原理和实践--文本解码原理--以MindNLP为例

摘要&#xff1a; 介绍了昇思MindSpore AI框架采用贪心搜索、集束搜索计算高概率词生成文本的方法、步骤&#xff0c;并为解决重复等问题所作的多种尝试。 这一节完全看不懂&#xff0c;猜测是如何用一定范围的词造句。 一、概念 自回归语言模型 文本序列概率分布 分解为每…

76. UE5 RPG 实现场景阻挡剔除功能

在俯视角游戏中&#xff0c;我们总会碰到一个问题就是&#xff0c;建筑会遮挡住角色的问题。遇到这种问题有多种解决方案&#xff0c;厂商经常使用的一种方案是&#xff0c;如果角色被遮挡&#xff0c;则使用一种纯色或者增加一些菲涅尔的效果来实现 这种效果我之前在unity内实…

SpringBoot 项目整合 MyBatis 框架,附带测试示例

文章目录 一、创建 SpringBoot 项目二、添加 MyBatis 依赖三、项目结构和数据库表结构四、项目代码1、application.yml2、TestController3、TbUser4、TbUserMapper5、TestServiceImpl6、TestService7、TestApplication8、TbUserMapper.xml9、MyBatisTest 五、浏览器测试结果六、…

一文了解什么是车载Tbox

目录 前言一、Tbox是什么呢?二、Tbox架构三、App——TSP——Tbox交互时序图四、汽车混合网关拓扑结构示例五、Tbox功能 前言 强烈建议提前阅读一下这篇文章&#xff1a;车机Tbox介绍 一、Tbox是什么呢? Tbox是汽车上的一个盒子&#xff0c;指的是Telematics BOX&#xff0c…

Michael.W基于Foundry精读Openzeppelin第61期——ERC1967Upgrade.sol

Michael.W基于Foundry精读Openzeppelin第61期——ERC1967Upgrade.sol 0. 版本0.1 ERC1967Upgrade.sol 1. 目标合约2. 代码精读2.1 _getImplementation() internal && _upgradeTo(address newImplementation) internal2.2 _upgradeToAndCall(address newImplementation,…

常见反爬及应对

一&#xff0c;特殊混淆的还原 1.1 还原 AAEncode 与 JJEncode AAEncode是一种JavaScript代码混淆算法&#xff0c;利用它&#xff0c;可以将代码转换成 颜文字 表示的JavaScript代码。 去掉代码最后的 (‘‘)&#xff0c;这是函数的自调用&#xff0c;去除后就是函数的声明…

【计算机网络仿真】b站湖科大教书匠思科Packet Tracer——实验13 静态路由配置错误导致的路由环路问题

一、实验目的 1.验证静态路由配置错误导致的路由环路问题&#xff1b; 二、实验要求 1.使用Cisco Packet Tracer仿真平台&#xff1b; 2.观看B站湖科大教书匠仿真实验视频&#xff0c;完成对应实验。 三、实验内容 1.构建网络拓扑&#xff1b; 2.验证路由环路。 四、实验…

探囊取物之多形式登录页面(基于BootStrap4)

基于BootStrap4的登录页面&#xff0c;支持手机验证码登录、账号密码登录、二维码登录、其它统一登录 低配置云服务器&#xff0c;首次加载速度较慢&#xff0c;请耐心等候&#xff1b;演练页面可点击查看源码 预览页面&#xff1a;http://www.daelui.com/#/tigerlair/saas/pr…

【AI提升】如何使用大模型:本机离线和FastAPI服务调用

大模型本身提供的功能&#xff0c;类似于windows中的一个exe小工具&#xff0c;我们可以本机离线调用然后完成具体的功能&#xff0c;但是别的机器需要访问这个exe是不可行的。常见的做法就是用web容器封装起来&#xff0c;提供一个http接口&#xff0c;然后接口在后端调用这个…