I2C总线协议的软件模拟实现方法

在上一篇博客中已经讲过I2C总线通信协议,本文讲述I2C总线协议的软件模拟实现方法。

1. 简述

所谓的I2C总线协议的软件模拟实现方法,就是用软件控制GPIO的输入、输出和高低电平变化,来模拟I2C总线通讯过程中SCL、SDA的电平变化来实现的。

2. I2C总线的封装

每个处理器对应的GPIO操作都有差异,即使是同一款处理器,不同的人也会有不同的GPIO封装风格,就以我个人习惯用的GPIO方法为例来进行讲解。我习惯上将GPIO的组和位封装为一个结构体,这样使用方便,看起来也更直观。

typedef struct {
unsigned char group;
unsigned char bit;
} gpio_t;

将I2C总线中使用的SCL和SDA的GPIO进一步进行封装。

typedef struct {
gpio_t scl;
gpio_t sda;
} i2c_gpio_t;

将I2C总线软件模拟部分当做驱动程序中的一个模块来使用,定义一个结构体来封装I2C模块中的一些全局变量,如:GPIO、锁等等。本文中的锁只是为了保证I2C的一个操作步骤是原子的,所有锁的使用可以忽略,如果想要了解更过关于锁的使用方法,请关注另外一篇博客(还没来得及写,以后会补充)。

typedef struct {
i2c_gpio_t gpio;
spinlock_t lock;
struct mutex i2c_mutex;
} i2c_info_t;

3. 软件模拟实现

3.1 I2C总线的初始化

1)先初始化I2C总线,具体要做的内容是,先把外部调用I2C模块时要使用的GPIO引脚,作为参数传递到I2C模块,用来进行一系列的操作。在这里将GPIO作为参数传递到I2C模块后,保存在全局变量的结构体中。

2)再初始化I2C总线的GPIO引脚,即将用来代替模拟I2C总线中SCL、SDA引脚的GPIO设置为输出,并输出高电平,因为两条线上都接有上拉电阻,I2C总线空闲时默认SCL、SDA都处于高电平,也就是空闲状态。

3)如果要使用锁机制,需要在这一步中将锁初始化。

// I2C模块初始化
int i2c_init(i2c_gpio_t *gpio)
{
i2c_debug("i2c_init"); // 初始化锁
spin_lock_init(&i2c_info.lock);
mutex_init(&i2c_info.i2c_mutex); // 初始化全局变量中I2C的GPIO
i2c_info.gpio.scl = gpio->scl;
i2c_info.gpio.sda = gpio->sda; i2c_gpio_init(); return 0;
}
// I2C的GPIO初始化
static void i2c_gpio_init(void)
{
i2c_debug("i2c_gpio_init");
i2c_sda_init();
i2c_scl_init();
}
// I2C的SCL初始化
static void i2c_scl_init(void)
{
i2c_debug("scl init");
SET_SCL_OUT;
SET_SCL_HIGH;
}
// I2C的SDA初始化
static void i2c_sda_init(void)
{
i2c_debug("sda init");
SET_SDA_OUT;
SET_SDA_HIGH;
}

3.2 I2C总线的起始位

I2C总线在开始通信时要先发送一个起始位标志,起始位是在SCL为高电平时,SDA由高电平变为低电平。

// I2C总线的起始位
int i2c_start(void)
{
mutex_lock(&i2c_info.i2c_mutex);
SET_SDA_OUT;
udelay(I2C_DELAY); SET_SDA_HIGH;
udelay(I2C_DELAY); SET_SCL_HIGH;
udelay(I2C_DELAY); SET_SDA_LOW;
udelay(I2C_DELAY); SET_SCL_LOW;
udelay(I2C_DELAY);
mutex_unlock(&i2c_info.i2c_mutex); return 0;
}

3.3 I2C总线的结束位

I2C总线在数据传输完成后,需要发送一个结束位,来结束I2C通讯,并释放I2C总线,结束位是在SCL为高电平时,SDA由低电平变为高电平

// I2C总线的结束位
int i2c_stop(void)
{
mutex_lock(&i2c_info.i2c_mutex);
SET_SDA_OUT;
udelay(I2C_DELAY); SET_SCL_LOW;
udelay(I2C_DELAY); SET_SDA_LOW;
udelay(I2C_DELAY); SET_SCL_HIGH;
udelay(I2C_DELAY); SET_SDA_HIGH;
udelay(I2C_DELAY);
mutex_unlock(&i2c_info.i2c_mutex); return 0;
}

3.4 I2C总线的应答

为了统一管理和使用方便,将I2C总线的等待应答、发送应答信号、发送非应答信号封装在一起进行管理。

1)I2C总线的等待应答

在I2C总线通讯时,主设备给从设备发送一个字节的数据后,要等待从设备的一个应答信号,这时候主设备处于等待应答状态,需要检测从设备的应答信号是否到来,如果从设备的应答信号到来,主设备就继续给从设备发送下一个字节的数据,或者发送停止位结束I2C通讯;如果在主设备等待超时后,从设备的应答信号时钟不到来,就说明I2C总线通讯中出现问题,主设备跳出等待,直接发送结束位,以结束I2C总线通讯。

// I2C总线的等待应答
static int i2c_wait_ack(void)
{
int ack_times = 0;
int ret = 0; mutex_lock(&i2c_info.i2c_mutex);
SET_SDA_OUT;
udelay(I2C_DELAY); SET_SDA_HIGH;
udelay(I2C_DELAY); SET_SDA_IN;
udelay(I2C_DELAY); SET_SCL_LOW;
udelay(I2C_DELAY); SET_SCL_HIGH;
udelay(I2C_DELAY); ack_times = 0;
// 检测从设备应答信号
while (GET_SDA_VAL) {
ack_times++;
// 判断等待是否超时
if (ack_times == 10) {
ret = 1;
i2c_error("i2c ack error, no ack");
break;
}
} SET_SCL_LOW;
mutex_unlock(&i2c_info.i2c_mutex); return ret;
}

2)I2C总线的发送应答

在I2C总线通信的时候,主设备每次接收到从设备发送的一个字节数据后,要给从设备发送应答信号(ACK)以继续接收从设备的数据,或者给从设备发送非应答信号(NOACK)以结束接收从设备的数据。

应答信号(ACK)就是先拉低SDA线,并在SCL为高电平期间保持SDA线为低电平

// I2C总线发送应答信号
static int i2c_send_ack(void)
{
i2c_debug("i2c_send_ack"); mutex_lock(&i2c_info.i2c_mutex);
SET_SDA_OUT;
udelay(I2C_DELAY); SET_SCL_LOW;
udelay(I2C_DELAY); SET_SDA_LOW;
udelay(I2C_DELAY); SET_SCL_HIGH;
udelay(I2C_DELAY); SET_SCL_LOW;
udelay(I2C_DELAY);
mutex_unlock(&i2c_info.i2c_mutex); return 0;
}

非应答信号(NOACK)就是不要拉低SDA线(此时SDA线为高电平),并在SCL为高电平期间保持SDA线为高电平。

// I2C总线发送非应答信号
static int i2c_send_noack(void)
{
i2c_debug("i2c_send_noack"); mutex_lock(&i2c_info.i2c_mutex);
SET_SDA_OUT;
udelay(I2C_DELAY); SET_SCL_LOW;
udelay(I2C_DELAY); SET_SDA_HIGH;
udelay(I2C_DELAY); SET_SCL_HIGH;
udelay(I2C_DELAY); SET_SCL_LOW;
udelay(I2C_DELAY);
mutex_unlock(&i2c_info.i2c_mutex); return 0;
}

3.5 I2C总线的写操作

// I2C总线的写操作
int i2c_write_byte(u8 data)
{
unsigned long flag = 0;
u8 i = 0; local_irq_save(flag);
preempt_disable();
mutex_lock(&i2c_info.i2c_mutex);
SET_SDA_OUT;
udelay(I2C_DELAY); for (i = 0; i < 8; i++) {
if (data & 0x80) {
SET_SDA_HIGH;
} else {
SET_SDA_LOW;
}
udelay(I2C_DELAY); SET_SCL_HIGH;
udelay(I2C_DELAY); SET_SCL_LOW;
udelay(I2C_DELAY); data <<= 0x1;
}
mutex_unlock(&i2c_info.i2c_mutex);
preempt_enable();
local_irq_restore(flag); return 0;
}
int i2c_write_byte_with_ack(u8 data)
{
i2c_write_byte(data);
if (i2c_ack(I2C_WAIT_ACK)) {
i2c_error("wait ack failed, no ack");
i2c_stop();
return -1;
} return 0;
}

3.6 I2C总线的读操作

// I2C总线的读操作
int i2c_read_byte(u8 *data)
{
unsigned long flag = 0;
u8 ret = 0;
u8 i = 0; local_irq_save(flag);
preempt_disable();
mutex_lock(&i2c_info.i2c_mutex); SET_SDA_IN;
udelay(I2C_DELAY); for (i = 0; i < 8; i++) {
SET_SCL_HIGH;
udelay(I2C_DELAY); ret <<= 1; if (GET_SDA_VAL) {
ret |= 0x01;
} SET_SCL_LOW;
udelay(I2C_DELAY);
} mutex_unlock(&i2c_info.i2c_mutex);
preempt_enable();
local_irq_restore(flag); *data = ret; return 0;
}

I2C总线协议的软件模拟实现方法的更多相关文章

  1. I2C总线协议的简要说明

    为了快速的了解I2C总线协议,此处采用另类的方式进行说明. 倘若你和另外一个人只能通过一个开关加灯泡的装置在不同的两个房间进行交流,以下是很简单能说明的一个模型: 你的房间有一个开关,另外一间房间有一 ...

  2. [I2C]I2C总线协议图解

    转自:http://blog.csdn.net/w89436838/article/details/38660631 1  I2C总线物理拓扑结构      I2C 总线在物理连接上非常简单,分别由S ...

  3. I2C总线协议的总结介绍

    在看天翔哥的视频之后,他强调要把I2C协议好好研究一下,那么就对一些基本的通信手段是十分有帮助的..那么就来了解一下I2C总线协议的一些知识吧. I2C(Inter-Integrated Circui ...

  4. I2C总线协议图解(转载)

    转自:http://blog.csdn.net/w89436838/article/details/38660631 另外,https://blog.csdn.net/qq_38410730/arti ...

  5. 【转】I2C总线协议

    I2C总线(Inter Integrated-Circuit)是由PHILIPS公司在上世纪80年代发明的一种电路板级串行总线标准,通过两根信号线——时钟线SCL和数据线SDA——即可完成主从机的单工 ...

  6. I2C总线协议详解

    I2C总线定义     I2C(Inter-Integrated Circuit)总线是一种由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备.I2C总线产生于在80年代,最初为音 ...

  7. I2C 总线协议

    1.I2C协议     2条双向串行线,一条数据线SDA,一条时钟线SCL.   SDA传输数据是大端传输,每次传输8bit,即一字节.   支持多主控(multimastering),任何时间点只能 ...

  8. I2C总线协议

     1.I2C协议   2条双向串行线,一条数据线SDA,一条时钟线SCL.   SDA传输数据是大端传输,每次传输8bit,即一字节.   支持多主控(multimastering),任何时间点只能有 ...

  9. I2C总线协议学习笔记 (转载)

    1.I2C协议   2条双向串行线,一条数据线SDA,一条时钟线SCL.   SDA传输数据是大端传输,每次传输8bit,即一字节.   支持多主控(multimastering),任何时间点只能有一 ...

随机推荐

  1. element ui 1.4 升级到 2.0.11

    公司的框架 选取的是 花裤衩大神开源的 基于 element ui + Vue 的后台管理项目, 项目源码就不公开了,记录 分享下 步骤 1. 卸载 element ui 1.4的依赖包 2. 卸载完 ...

  2. python+opencv选出视频中一帧再利用鼠标回调实现图像上画矩形框

    最近因为要实现模板匹配,需要在视频中选中一个目标,然后框出(即作为模板),对其利用模板匹配的方法进行检测.于是需要首先选出视频中的一帧,但是在利用摄像头读视频的过程中我唯一能想到的方法就是: 1.在视 ...

  3. 【算法】哈希表的诞生(Java)

    参考资料 <算法(java)>                           — — Robert Sedgewick, Kevin Wayne <数据结构>       ...

  4. 【linux之设备,分区,文件系统】

    一.设备 IDE磁盘的设备文件采用/dev/hdx来命名,分区则采用/dev/hdxy来命名,其中x表示磁盘(a是第一块磁盘,b是第二块磁盘,以此类推), y代表分区的号码(由1开始,..3以此类推) ...

  5. linux 内核参数优化

    Sysctl命令及linux内核参数调整   一.Sysctl命令用来配置与显示在/proc/sys目录中的内核参数.如果想使参数长期保存,可以通过编辑/etc/sysctl.conf文件来实现.   ...

  6. html拨打电话、发送短信、发送邮件的链接写法

    拨打电话 <a href="tel:88888888">呼叫</a> 发送短信 <a href="sms:88888888"> ...

  7. mysql中的范式与范式——读<<高性能mysql>>笔记一

    对于任何给定的数据库通常都有很多表示方法,从完全的范式化到完全的反范式化,以及两者的折中.在范式化的数据库中,每个事实数据会出现并且只出现一次.相反,在反范式化的数据库中,可能会存储在多个地方. 那什 ...

  8. iOS isa指针

    在Objective-C中,任何类的定义都是对象.类和类的实例没有任何本质上的区别.任何对象都有isa指针. isa:是一个Class 类型的指针. 每个实例对象有个isa的指针,它指向对象的类,而C ...

  9. ACdream 1068

    我没有用二分法,直接构造最小数,既然题目保证答案一定存在那么与上界无关. 如给定S=16,它能构成的最小数为79,尽量用9补位,最高位为S%9.如果构造的数大于下界A,那么直接输出,因为这是S能构成的 ...

  10. hive:框架理解

    1. 什么是hive  •Hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表,并提供类SQL查询功能. •本质是将HQL转换为MapReduce程序  2. 为什么 ...