linux gpio模拟I2C - 以rk3588为列

linux GPIO模拟I2C - 以rk3588为列

一、使用内核模块i2c-gpio.c实现

该文件已经实现gpio模拟i2c的所有流程和功能,对于应用层来说操作都是一样的,无需关心其他问题,个人比较推荐该方式,使用该方式需要配置以下选项:

1.内核开启i2c_gpio支持

Device Drivers->
    I2C support  --->
        I2C Hardware Bus support  --->
            <*> GPIO-based bitbanging I2C

在这里插入图片描述

开启后defconfig中会增加 CONFIG_I2C_GPIO=y
对应指令:

make menuconfig
make savedefconfig

2.设备树配置对应IO

在配置IO之前,请确认该IO没有被使用,并且是GPIO功能,可以使用以下指令查看(以下是我使用的IO)

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

在这里插入图片描述
配置设备树

	aliases {
   
		i2c15 = &i2c15;
	};
	i2c15:i2c15_gpio{
   
		#address-cells = <1>;
		#size-cells = <0>;
		status = "okay";
		compatible = "i2c-gpio";
		gpios = <&gpio2 RK_PB4 GPIO_ACTIVE_HIGH>,//sda
				<&gpio2 RK_PB5 GPIO_ACTIVE_HIGH>;//scl
		i2c-gpio,delay-us = <2>; /* ~100 kHz */
	};

有几个注意点:

  • 新增的I2C节点,需要配置到 /{ }设备数的根节点下
  • compatible 属性需配置正确,与i2c-gpio.c内属性匹配
  • 配置引脚时先SDA再SCL
  • aliases属性下增加对应名字,在匹配完成后,会在/dev目录下生成指定的设备节点(/dev/i2c-15)

3.以上配置完成后,重新编译内核并烧录

烧录完成后可以在/dev/目录下找到新加的i2c-15,此时可以使用i2ctool进行调试,与硬件I2C操作一致

二、应用层直接模拟I2C

1.该方式就只需两个普通IO即可,i2c读写时序都由应用层自己控制,下面简单说明时序部分:

I2C的数据格式:
无数据:SCL=1,SDA=1
开始位(Start):当SCL=1时,SDA由1向0跳变;
停止位(Stop):当SCL=1时,SDA由0向1跳变;
数据位:当SCL由0向1跳变时,由发送方控制SDA,此时SDA为有效数 据,不可随意改变SDA;当SCL保持为0时,SDA上的数据可随意改变;

地址位:定义同数据位,但只由Master发给Slave;
应答位(ACK):当发送方传送完8位时,发送方释放SDA,由接收方控制SDA,且SDA=0;
否应答位(NACK):当发送方传送完8位时,发送方释放SDA,由接收方控制SDA,且SDA=1。
数据为单字节传送时,格式为
开始位,8位地址位(含1位读写位),应答,8位数据,应答,停止位。
数据为一串字节传送时,格式为:
开始位,8位地址位(含1位读写位),应答,8位数据,应答,8位数据,应答,……,8位数据,应答,停止位。

2.I2C读写时ACK和NACK尤为重要(以下做说明):

在I2C通信中,**ACK(Acknowledge)NAK(Not Acknowledge)**是通过数据线(SDA线)上的信号来实现的。这两个信号用于确认或拒绝接收数据,确保数据传输的正确性和可靠性。

2.1 ACK(确认信号)

  • ACK信号表示接收方成功接收了发送方发送的数据。
  • 发送方在发送完数据字节后,期望接收方会拉低SDA线(SDA=0),表示接收成功。
  • 在I2C协议中,ACK通常发生在每个字节传输后,除了最后一个字节之外。发送完数据字节后,接收方在下一个时钟周期(SCL的高电平)将SDA拉低,以表示接收了该字节,继续接收后续的数据。

2.2 NAK(拒绝信号)

  • NAK信号表示接收方没有成功接收数据,或者表示这是最后一个字节的传输。
  • 在I2C协议中,NAK通常出现在最后一个数据字节的读取过程中。接收方发送NAK,告诉发送方已经接收完所有需要的数据。
  • 在读取数据时,当接收方不希望再接收更多字节时,它会在最后一个字节后发送NAK。此时,SDA保持为高电平(SDA=1),并且发送方会停止传输。

2.3 ACK和NAK的实现机制

2.3.1 写操作时的ACK和NAK
  • 在I2C写操作中,发送方向接收方传送字节后,接收方会回应一个ACK或NAK。
  • 发送方每发送完一个字节后,都需要等待接收方的响应(ACK或NAK)。接收方如果成功接收到字节,它会通过将SDA拉低(ACK)回应发送方。如果接收方没有成功接收字节,或者它不想接收更多数据,它会通过将SDA保持为高电平(NAK)来回应。
2.3.2 读操作时的ACK和NAK
  • 在I2C读操作中,接收方每读一个字节后,也需要回应ACK或NAK。
  • 在I2C读取数据的过程中,接收方每读取完一个字节后,会发送一个ACK,告诉发送方它已经成功接收了该字节。如果接收方已经不再需要更多的数据,它会在最后一个字节读取后发送NAK,表示不再接收数据。

2.4 图示解释:

假设我们有一个I2C读操作,操作顺序如下:

Master (发送方)        Slave (接收方)
     SCL ↑                  SCL ↑
     SDA ↓  ------------>     SDA ↓   <- ACK (接收方确认数据)
     SDA ↑                  SDA ↑   <- 发送字节
     SCL ↓                  SCL ↓
  ...

ACK/NAK的时序:
每传输一个字节(8位数据)后,接收方必须给出ACK
最后一个字节传输后,接收方发送NAK(SDA保持为1),表示数据传输结束

2.5 代码中实现ACK和NAK

  • 读取字节时:
    如果你正在读取多个字节,通常每个字节都需要发送ACK,除了最后一个字节外,最后一个字节应该发送NAK。

  • 写入字节时:
    在每个字节传输后,接收方会向发送方发出ACK或NAK。发送方需要等待读取该确认信号后,才能继续下一步操作。

  • ACK/NAK的具体实现:
    在每次读取完字节后,如果需要继续读取下一个字节,ack参数会传递1,表示发送ACK。
    如果是最后一个字节,ack会传递0,表示发送NAK,告诉发送方这次读操作结束。

2.6 模拟i2c时钟的选择和计算(100k为列)

  • I2C协议要求SCL信号在每个比特的高电平和低电平之间交换,通常我们在每次操作中,会将SCL的高电平和低电平分为均等的部分,这意味着每个状态(高电平或低电平)的时间为5微秒。因此设置I2C scl 半周期为5us来保证SCL的周期为10us,以实现100kHz的I2C时钟

3.以下为个人应用层模拟使用的程序(测试正常)

#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <unistd.h>

// 定义 I2C 总线的时钟线和数据线对应的 GPIO 引脚编号
#define I2C_SCL 21     // GPIO0_PC5
#define I2C_SDA 20     // GPIO0_PC4
#define I2C_DELAY_US 5 // I2C bit period for 100kHz I2C clock

static int gpio_export(int gpio_num);
static int gpio_unexport(int gpio_num);
static int gpio_set_direction(int gpio_num, const char *in_out);
static int gpio_read_direction(int gpio_num);
static int gpio_set_val(int gpio_num, int val);
static int gpio_read_val(int gpio_num);
static void gpiod_direction_output(int gpio_num, int val);
static void gpiod_direction_input(int gpio_num);

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

    // 将 SDA 引脚设置为低电平,保持 SCL 为高电平
    // 这将产生 I2C 总线的起始条件
    gpiod_direction_output(I2C_SDA, 0);
    usleep(I2C_DELAY_US);

    // 将 SCL 引脚设置为低电平
    // 起始条件建立完成
    gpiod_direction_output(I2C_SCL, 0);
    usleep(I2C_DELAY_US);
}

// I2C 停止条件函数
void i2c_stop(void)
{
   
    // 将 SCL 和 SDA 引脚设置为低电平
    gpiod_direction_output(I2C_SCL, 0);
    gpiod_direction_output(I2C_SDA, 0);
    usleep(I2C_DELAY_US
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kylan~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值