0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

如何根据时序图编写高效IO模拟I2C程序

电子Online 来源:21ic电子网 2024-04-06 03:05 次阅读

很多人不知道怎么看着时序图写程序,下面结合一个非标准的I2C器件,教大家如何写一个高效的IO模拟I2C时序。

8de2b62c-ee2d-11ee-a297-92fbcf53809c.png

观察该时序,具备I2C的开始信号,I2C的结束信号,I2C的应答、非应答、响应应答,以及写字节和读字节的基本操作时序。

下面,我们一步一步分析。

1、I2C开始信号

观察时序图,在SCLK高电平的状态下,在SDIO产生一个下降沿是为开始信号。

void I2C_Start()
{
  //设置I2C使用的两个引脚为输出模式
  pinMode(SCLK_PIN, OUTPUT);
  pinMode(SDIO_PIN, OUTPUT);


  //在SCL为高电平的时候让SDA产生一个下降沿是为开始信号
  digitalWrite(SDIO_PIN, 1);
  digitalWrite(SCLK_PIN, 1);
  digitalWrite(SDIO_PIN, 0);
}

上述代码即先将两个引脚设置为输出模式,然后在SCLK为高电平的时候在SDIO引脚输出一个下降沿。

2、I2C停止信号

观察时序图,在SCLK为高电平的时候在SDIO引脚产生一个上升沿是为停止信号。

void I2C_Stop()
{
  pinMode(SDIO_PIN, OUTPUT);
  //在SCL为高电平的时候让SDA产生一个上升沿是为停止信号
  digitalWrite(SDIO_PIN, 0);
  digitalWrite(SCLK_PIN, 1);
  digitalWrite(SDIO_PIN, 1);
}

这里采用的是Arduino编写的IO基本操作,你可以替换成任意单片机的IO操作。

由于整个过程SCLK引脚一直是输出状态,所以仅在开始信号中对SCLK初始化为输出模式,而过程中可能会修改SDIO的输入输出模式,所以其他的函数开头都要根据情况对SDIO引脚的模式进行设置。

通过三行代码实现在SCLK为高电平的时候在SDIO产生一个上升沿,实现停止信号。

3、写字节操作

接下来,按照时序的顺序编写方便认读

I2C的读写字节是这么定义的:当时钟线为低电平的时候,允许修改数据线的电平状态,在时钟线为高电平的时候读取数据线的状态。

因为是写操作,因此我们要先将时钟线SCLK拉低,再修改SDIO的值,然后拉高时钟。拉高后,从机就会从总线上读取SDIO的状态,接着一位一位的这么发送。

void I2C_Write(uint8_t dat)
{
  pinMode(SDIO_PIN, OUTPUT);
  //拉低时钟线后可修改数据线的状态
  digitalWrite(SCLK_PIN, 0); 
  for(int i=0;i<8;i++)
  {
    digitalWrite(SDIO_PIN, (bool)(dat&0x80)); 
    digitalWrite(SCLK_PIN, 1);//在高电平时候送出数据
    dat=dat<<1;
    digitalWrite(SCLK_PIN, 0);//拉低准备下一个位的数据发送
  }
}

上述代码正描述了这一情况:为了保证最后是低电平,这里将SCLK的第一次拉低放到循环外面,这样可以用最少的执行次数完成一个字节的写任务;同时,结束完一个字节写入后时钟线是低电平状态(时序图中写入的第一个字节为DeviceID,第二个字节为寄存器地址+读写位)。

写完一个字节后,从机会对写入事件进行应答,这个时候主级可以从总线上读取应答信号。

4、读取从机应答引号

应答信号在写入完一个字节后的低电平后由从机送出,在时钟为高电平的时候可以读取出来,我们注意到写入自己操作后时钟线已经是低电平了,因此这个时候

只要拉高时钟线,接下来就可以读取应答信号,读取完应答信号根据时序图应该拉低时钟准备下一个字节的写入。

bool I2C_RACK()
{
  bool ack;
  pinMode(SDIO_PIN, INPUT);


  digitalWrite(SCLK_PIN, 1);//接收应答信号,当时钟拉高时候,从机送出应答信号
  ack = digitalRead(SDIO_PIN);
  digitalWrite(SCLK_PIN, 0);//读取完应答信号后拉低时钟。
  return ack;
}

如上代码所示,即为接收从机应答,拉高时钟,读取应答,再拉低,返回应答。如果从机应答了,这里会读取到一个低电平。

后面就是再写入一个寄存器+读写位的地址,参靠上面的写入操作。

写入寄存器地址后,紧跟着又一个接收从机应答信号,然后从机就会送出数据,送出的数据分高字节和低字节,高低字节间要有一个主机发送给从机的应答信号,这样从机酒知道主机收到了数据,就会送出后面的低字节数据。

5、读字节操作

注意,前面说过,读写都是总线在时钟低电平时候修改数据线,在高电平送出。

因此,主机读取从机送来的数据仍然是在高电平时候读取。

uint8_t I2C_Read()
{
  uint8_t dat=0;
  pinMode(SDIO_PIN, INPUT);
  for(int i=0;i<8;i++)
  {
    digitalWrite(SCLK_PIN, 1);//读取数据时候是在时钟的高电平状态读取
    dat=dat<<1;
    if(digitalRead(SDIO_PIN))
    {
      dat=dat|1;
    }
    digitalWrite(SCLK_PIN, 0);//拉低时钟线准备下一个位的读取
  }
  return dat;
}

操作过程是将SDIO数据线的IO设置为输入模式,准备读取,然后拉高时钟,读取数据,移位,拉低循环读取8位数据。

注意,操作完一个字节读取任务后,时钟线还是低电平。

读取完一个字节后,主机要给从机发送一个应答信号,这样从机会接着发低字节数据。

6、主机发送应答信号给从机

void I2C_ACK()
{
pinMode(SDIO_PIN, OUTPUT);
digitalWrite(SDIO_PIN, 0);//给从机发送应答信号,即拉低数据线,然后拉高时钟让从机读取该应答
digitalWrite(SCLK_PIN, 1);
digitalWrite(SCLK_PIN, 0);//执行完应答后拉低时钟线,准备下一步动作。
}

拉低数据线,然后在高电平的时候让从机去读取,之后拉低时钟线准备下一步接收动作。

当再接收一个字节后,就读取完成了,这个时候就是产生一个非应答信号,然后发给总线结束信号,告诉从机一个读写周期结束了。

7、主机非应答信号

什么是非应答信号呢?

就是接收完了数据,释放数据线,不去拉低数据线。

void I2C_NACK()
{
  //非应答信号:即主机不再对从机进行应答,主机释放数据线,即拉高数据线,然后给时钟一个周期信号(拉高再拉低)
  pinMode(SDIO_PIN, OUTPUT);
  digitalWrite(SDIO_PIN, 1);
  digitalWrite(SCLK_PIN, 1);
  digitalWrite(SCLK_PIN, 0);
}

将SDIO引脚设置为输出,拉高数据线,即为释放数据线,然后拉高拉低时钟,即在时钟线产生一个时钟周期信号。

然后发送结束信号。结束信号在开头已经讲明,即在时钟线为高电平的状态下,在数据线产生一个上升沿。

观察以上代码没一个多余重复的操作动作,即完美的视线了时序图上的所有操作。

接下来就是利用上述的I2C成分进行对寄存器的读写操作了。

8、读寄存器

由于图中设备的DeviceID 为0x80,即直接写进来,从机判断是读还是写的字节在寄存器地址。

因此,将寄存器的地址左移一位,在末尾补上是读(1)还是写(0)。

uint16_t read_reg(uint8_t reg)
{
  uint16_t dat=0;
  reg=(reg<<1)|1;
  I2C_Start();
  I2C_Write(0x80);
  I2C_RACK();
  I2C_Write(reg);
  I2C_RACK();
  dat=I2C_Read();
  dat=dat<<8;
  I2C_ACK();
  dat=dat|I2C_Read();
  I2C_NACK(); 
  I2C_Stop();
  return dat;
}

9、写寄存器操作

void write_reg(uint8_t reg, uint16_t dat)
{
  reg=(reg<<1);
  I2C_Start();
  I2C_Write(0x80);
  I2C_RACK();
  I2C_Write(reg);
  I2C_RACK();
  I2C_Write(dat>>8);
  I2C_RACK();
  I2C_Write(dat&0xFF);
  I2C_NACK();
  I2C_Stop();
}

最后,对寄存器读写函数测试。

void setup() 
{
  Serial.begin(115200);
  Serial.println("Hello I2C");
  write_reg(0x02,0x2250);
  Serial.println(read_reg(0x02),HEX);
  write_reg(0x02,0x2281);
  Serial.println(read_reg(0x02),HEX);
}


void loop() 
{


}

8df3bc56-ee2d-11ee-a297-92fbcf53809c.jpg

读取的数值与写入的是一样的。

最后晒出完整的测试代码:

#define SCLK_PIN 8
#define SDIO_PIN 9




void I2C_Start()
{
  //设置I2C使用的两个引脚为输出模式
  pinMode(SCLK_PIN, OUTPUT);
  pinMode(SDIO_PIN, OUTPUT);


  //在SCL为高电平的时候让SDA产生一个下降沿是为开始信号
  digitalWrite(SDIO_PIN, 1);
  digitalWrite(SCLK_PIN, 1);
  digitalWrite(SDIO_PIN, 0);
}


void I2C_Stop()
{
  pinMode(SDIO_PIN, OUTPUT);
  //在SCL为高电平的时候让SDA产生一个上升沿是为停止信号
  digitalWrite(SDIO_PIN, 0);
  digitalWrite(SCLK_PIN, 1);
  digitalWrite(SDIO_PIN, 1);
}


void I2C_Write(uint8_t dat)
{
  pinMode(SDIO_PIN, OUTPUT);
  //拉低时钟线后可修改数据线的状态
  digitalWrite(SCLK_PIN, 0); 
  for(int i=0;i<8;i++)
  {
    digitalWrite(SDIO_PIN, (bool)(dat&0x80)); 
    digitalWrite(SCLK_PIN, 1);//在高电平时候送出数据
    dat=dat<<1;
    digitalWrite(SCLK_PIN, 0);//拉低准备下一个位的数据发送
  }
}


uint8_t I2C_Read()
{
  uint8_t dat=0;
  pinMode(SDIO_PIN, INPUT);
  for(int i=0;i<8;i++)
  {
    digitalWrite(SCLK_PIN, 1);//读取数据时候是在时钟的高电平状态读取
    dat=dat<<1;
    if(digitalRead(SDIO_PIN))
    {
      dat=dat|1;
    }
    digitalWrite(SCLK_PIN, 0);//拉低时钟线准备下一个位的读取
  }
  return dat;
}




bool I2C_RACK()
{
  bool ack;
  pinMode(SDIO_PIN, INPUT);


  digitalWrite(SCLK_PIN, 1);//接收应答信号,当时钟拉高时候,从机送出应答信号
  ack = digitalRead(SDIO_PIN);
  digitalWrite(SCLK_PIN, 0);//读取完应答信号后拉低时钟。
  return ack;
}


void I2C_ACK()
{
  pinMode(SDIO_PIN, OUTPUT);
  digitalWrite(SDIO_PIN, 0);//给从机发送应答信号,即拉低数据线,然后拉高时钟让从机读取该应答
  digitalWrite(SCLK_PIN, 1);
  digitalWrite(SCLK_PIN, 0);//执行完应答后拉低时钟线,准备下一步动作。
}


void I2C_NACK()
{
  //非应答信号:即主机不再对从机进行应答,主机释放数据线,即拉高数据线,然后给时钟一个周期信号(拉高再拉低)
  pinMode(SDIO_PIN, OUTPUT);
  digitalWrite(SDIO_PIN, 1);
  digitalWrite(SCLK_PIN, 1);
  digitalWrite(SCLK_PIN, 0);
}


uint16_t read_reg(uint8_t reg)
{
  uint16_t dat=0;
  reg=(reg<<1)|1;
  I2C_Start();
  I2C_Write(0x80);
  I2C_RACK();
  I2C_Write(reg);
  I2C_RACK();
  dat=I2C_Read();
  dat=dat<<8;
  I2C_ACK();
  dat=dat|I2C_Read();
  I2C_NACK(); 
  I2C_Stop();
  return dat;
}


void write_reg(uint8_t reg, uint16_t dat)
{
  reg=(reg<<1);
  I2C_Start();
  I2C_Write(0x80);
  I2C_RACK();
  I2C_Write(reg);
  I2C_RACK();
  I2C_Write(dat>>8);
  I2C_RACK();
  I2C_Write(dat&0xFF);
  I2C_NACK();
  I2C_Stop();
}




void setup() 
{
  Serial.begin(115200);
  Serial.println("Hello I2C");
  write_reg(0x02,0x2250);
  Serial.println(read_reg(0x02),HEX);
  write_reg(0x02,0x2281);
  Serial.println(read_reg(0x02),HEX);
}


void loop() 
{


}

看完这篇文章,你学会纯手工撸IO模拟I2C时序的代码了吗?

审核编辑:黄飞

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 总线
    +关注

    关注

    10

    文章

    2856

    浏览量

    87879
  • 函数
    +关注

    关注

    3

    文章

    4273

    浏览量

    62292
  • 高电平
    +关注

    关注

    6

    文章

    143

    浏览量

    21289
  • I2C驱动
    +关注

    关注

    0

    文章

    9

    浏览量

    7027
  • 时钟线
    +关注

    关注

    0

    文章

    6

    浏览量

    3607

原文标题:看时序图写I2C驱动,教你如何自己手撸非标I2C驱动函数

文章出处:【微信号:电子Online,微信公众号:电子Online】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    利用IO模拟I2C时序进而实现I2C通讯的步骤

    模拟I2C时序进而实现I2C通讯的。给很多想学习硬件I2C通讯的小伙伴带来了困难。下面先介绍一下配置步骤1.E
    发表于 01-24 08:22

    I2C总线驱动程序

    1 /**————————————————————2 〖说明〗I2C总线驱动程序(用两个普通IO模拟I2
    发表于 08-13 17:04 64次下载

    I2C总线驱动程序的实现

    I2C总线驱动程序的实现 I2C 驱动程序的简介本驱动程序为标准的51 系列CPU 编写,让C
    发表于 09-26 17:25 5694次阅读
    <b class='flag-5'>I2C</b>总线驱动<b class='flag-5'>程序</b>的实现

    基于GAL的I2C总线时序模拟

    本文给出了用可编程逻辑器件GAL配合ISA总线模拟I2C总线时序来对FI1256 MK2进行控制的方法。该方法与PCI总线进行模拟的方法相类
    发表于 03-27 11:33 2856次阅读
    基于GAL的<b class='flag-5'>I2C</b>总线<b class='flag-5'>时序模拟</b>

    I2C总线的结构、工作时序模拟编程

    I2C总线的结构、工作时序模拟编程
    发表于 10-24 14:34 13次下载
    <b class='flag-5'>I2C</b>总线的结构、工作<b class='flag-5'>时序</b>和<b class='flag-5'>模拟</b>编程

    软件模拟i2c实现io时序电路的技巧

    关于 Bit Bang 的解释:Use software to control serial communication at general-purpose I/O pins,简单来讲就是使用软件通过 IO 脚去实现 I2C
    的头像 发表于 11-06 09:32 1.5w次阅读
    软件<b class='flag-5'>模拟</b><b class='flag-5'>i2c</b>实现<b class='flag-5'>io</b>脚<b class='flag-5'>时序</b>电路的技巧

    深度解析IO模拟时序(SPI)的注意事项

    有硬件I2C、SPI时尽量用硬件操作,省去IO模拟繁琐的时序调试。但在内部资源不够时就要用IO模拟
    的头像 发表于 01-17 09:37 1.6w次阅读
    深度解析<b class='flag-5'>IO</b><b class='flag-5'>模拟</b><b class='flag-5'>时序</b>(SPI)的注意事项

    80C51单片机模拟I2C总线的主机程序分享

    I2C总线协议程序 在使用的过程中一定要注意时序、时间的问题。 i2c.c /* I2C.c 标准80
    发表于 12-05 15:39 3348次阅读
    80<b class='flag-5'>C</b>51单片机<b class='flag-5'>模拟</b><b class='flag-5'>I2C</b>总线的主机<b class='flag-5'>程序</b>分享

    使用51单片机IO模拟I2C程序免费下载

    本文档的主要内容详细介绍的是使用51单片机IO模拟I2C程序免费下载。
    发表于 08-02 17:34 5次下载
    使用51单片机<b class='flag-5'>IO</b><b class='flag-5'>模拟</b><b class='flag-5'>I2C</b>的<b class='flag-5'>程序</b>免费下载

    DSP配置I2C通讯(非IO口软件模拟时序

    TMS320F28377D为例,使用ti公司dsp开发工具ccs10.1配置i2c模块。写这篇文章的缘由,因为市面上很多DSP其实内部是具有硬件I2C接口的,但由于网络上大部分资料和例程都是利用IO
    发表于 11-29 14:06 16次下载
    DSP配置<b class='flag-5'>I2C</b>通讯(非<b class='flag-5'>IO</b>口软件<b class='flag-5'>模拟</b><b class='flag-5'>时序</b>)

    嵌入式内核及驱动开发-09IIC子系统框架使用(I2C协议和时序I2C驱动框架,I2C从设备驱动开发,MPU6050硬件连接

    从设备adapter i2c 控制器对象数据包对象读写数据设备树中添加MPU6050信息内核选配添加设备树节点编写驱动程序 mpu6050_i2c_drv.cI2c协议和
    发表于 12-06 14:06 17次下载
    嵌入式内核及驱动开发-09IIC子系统框架使用(<b class='flag-5'>I2C</b>协议和<b class='flag-5'>时序</b>,<b class='flag-5'>I2C</b>驱动框架,<b class='flag-5'>I2C</b>从设备驱动开发,MPU6050硬件连接

    硬件I2C模拟I2C

    硬件I2C对应芯片上的I2C外设,有相应I2C驱动电路,其所使用的I2C管脚也是专用的,因而效率要远高于软件模拟
    发表于 12-28 19:14 81次下载
    硬件<b class='flag-5'>I2C</b>与<b class='flag-5'>模拟</b><b class='flag-5'>I2C</b>

    经过验证的GPIO模拟I2C时序代码

    使用STM32的GPIO模拟I2C总线时序,GPIO设置为开漏模式,SDA和SCK外部必须使用上拉电阻,一般是4.7K。开漏模式的好处是,可以同时读取输入电平,而无需切换输入/输出模式。注意事项:在
    发表于 12-28 19:36 13次下载
    经过验证的GPIO<b class='flag-5'>模拟</b><b class='flag-5'>I2C</b><b class='flag-5'>时序</b>代码

    ESP 12E I2c基卡的I2C IO卡设计

    电子发烧友网站提供《ESP 12E I2c基卡的I2C IO卡设计.zip》资料免费下载
    发表于 08-15 09:27 3次下载
    ESP 12E <b class='flag-5'>I2c</b>基卡的<b class='flag-5'>I2C</b> <b class='flag-5'>IO</b>卡设计

    STC单片机IO模拟I2C(主从)文件资料

    STC单片机IO模拟I2C(主从)文件资料免费下载。
    发表于 08-29 10:13 12次下载