个人笔记
前几天写了一篇关于 I2C 的文章,发现 I2C 其实还挺简单的。(前提是不考虑多主机,10位寻址我也没去研究)
那篇文章的 I2C 代码是我按照 AT24C02 芯片手册里的时序图编写的(同时还参考了许多代码模板),虽然功能实现了,但总觉得自己写的代码可能不够规范。
所以今天我想使用逻辑分析仪(淘宝买的杂牌便宜货),对 I2C 波形一探究竟,我将拿我的代码和普中51单片机教程给出的 I2C 范例程序及正点原子STM32教程提供的 I2C 范例程序做一个简单的对比。(对比时序波形)。
【本人水平有限,本文的内容仅供参考,不能保证100%靠谱。】
这里只分析时序图,对应 I2C 代码见文章最后一章
总览:
主机先向从机写入数据0x00,然后读取从机返回的数据0x08,从机地址(设备地址+读写位)为0xA0。
起始信号:
起始信号:时钟SCL高电平时,数据SDA由高变低,由于51单片机时钟频率低,空语句延时达到了30us,不过 I2C 周期长短只会影响它的传输速率,并不会导致传输失败。
时钟低电平修改SDA:
在时钟SCL低电平期间,改变SDA状态,下图中,SDA电平变化至时钟上升沿的时间间距为3.25us
,这是由于我代码中发送数据(改变SDA)和拉高时钟电平写在一起,中间没有任何延时代码。(总感觉这中间应该加一个延时代码,其他人的代码这里都加了延时)
传输8位地址数据:
主设备向从设备发送了1个字节的数据,这里的数据是地址数据,时钟SCL高电平时对应的数据SDA即为每位传输的数据,SDA高电平表示1,SDA为低表示0。一共8个周期,传输8位二机制数据。
等待从机应答:
在第9个周期前,需要将SDA拉高(下图SDA并没有出现高电平,说明在我们手动拉高瞬间,从机就把SDA拉低了。?),如果在第9个周期的高电平期间,从机已经将SDA拉低,说明从机发出了应答信号ACK,双方继续通信。
发送1个字节的数据:
和上面的发送地址数据相同,这里继续发送一个字节的数据。可以看到SDA一直是低,说明传输的数据是0x00。
重新起始信号:
由于接下来需要切换成读操作,所以主机需要再发送一个起始信号(又称重新起始信号)。
发送设备地址(读):
和之前的第一个发送地址数据(设备地址+读写位)的时序相同,唯一的区别是上次发的是写地址,这次发送的位读地址,为0xA1(最后1位为1,表示主机读数据)。
读取1个字节的数据:
和写操作时序类似,只是传输方向由主机->从机
变成主机<-从机
。
从机发送非应答信号+主机发送停止信号:
数据读取完成后,从机发出非应答信号(不把SDA拉低),第9个周期过后,主机发送停止信号,结束本次通信。
停止信号:SCL为高电平时,数据SDA由低变高。
这里只分析时序图,对应 I2C 代码见文章最后一章
总览
和上文一样的操作,写三个字节(2个地址数据+1个普通数据),读取一个字节。
这份时序中,在修改SDA电平的前后都加了延时,也就是低电平的持续时间是我的两倍,也是高电平时间的两倍。
应答信号的处理方式也和我不同,这份时序的第9个周期的高电平时间不是固定的,而是只要检测到SDA被从机拉低,就算应答信号完成。所以周期信号重要的不是维持时间(但也有最小时间限制),而是上升沿信号。
其他地方和我的时序没太大区别。
这里只分析时序图,对应 I2C 代码见文章最后一章
由于我没接 I2C 从机,所以没有应答信号。这份时序和普中51的很类似,比如低电平时长为高低平的两倍(在中间更改SDA电平),第9个周期采用循环读取应答信号,如果检测到应答信号,就将时钟拉低,否则一直检测,直到超时(循环内有计数值限制循环时间)。
另外,由于这款32位芯片的时钟频率较高,所以一个周期时长只有9.542us,而普中51的时序中周期时长为57.5us。
内核的 I2C 源码我不太了解,平时只用过内核自带的 I2C 接口函数
总览
LInux 内核的 I2C 时序有个特点,起始信号、结束信号和中间数据传输直接隔得很开,读和写也分开了,但时序规则没变。
发送地址数据
一个周期的时长为4.99us,高电平2us,低电平3us,9个周期长度相同。
读取数据
下面仅给出 I2C 的功能代码和简单的外设读写代码(组成完整 I2C 时序),不涉及主函数和其他无关功能代码
代码仅用来参考 I2C 具体实现过程,不能直接运行
/******************************************************************************
* @ 函数名 : Delay_10us
* @ 功 能 : 10us粗略延时
* @ 参 数 : 延时时间--单位10us
* @ 返回值 : 无
******************************************************************************/
void Delay_10us(unsigned int time)
{
while(time--);
}
/******************************************************************************
* @ 函数名 : I2c_Delay
* @ 功 能 : I2C延时函数
* @ 参 数 : 无
* @ 返回值 : 无
******************************************************************************/
void I2c_Delay()
{
Delay_10us(1);
}
/******************************************************************************
* @ 函数名 : I2c_Start
* @ 功 能 : I2C起始信号
* @ 参 数 : 无
* @ 返回值 : 无
******************************************************************************/
void I2c_Start()
{
sda = 1;
scl = 1;
I2c_Delay(); //起始信号建立时间
sda = 0; //SDA拉低,下降沿
I2c_Delay(); //起始信号保持时间
scl = 0;
}
/******************************************************************************
* @ 函数名 : I2c_Stop
* @ 功 能 : I2C停止信号
* @ 参 数 : 无
* @ 返回值 : 无
******************************************************************************/
void I2c_Stop()
{
scl = 0;
I2c_Delay(); //上一个时钟周期的低电平
sda = 0;
scl = 1;
I2c_Delay(); //停止信号建立时间
sda = 1; //SDA拉高,上升沿
I2c_Delay(); //总线空闲时间保持
}
/******************************************************************************
* @ 函数名 : I2c_Ack
* @ 功 能 : I2C应答信号
* @ 参 数 : 无
* @ 返回值 : 无
******************************************************************************/
void I2c_Ack()
{
scl = 0;
sda = 0; //SDA拉低,发出应答信号
I2c_Delay();
scl = 1;
I2c_Delay();
scl = 0;
}
/******************************************************************************
* @ 函数名 : I2c_No_Ack
* @ 功 能 : I2C非应答信号
* @ 参 数 : 无
* @ 返回值 : 无
******************************************************************************/
void I2c_No_Ack()
{
scl = 0;
sda = 1; //SDA拉高,发出非应答信号
I2c_Delay();
scl = 1;
I2c_Delay();
scl = 0;
}
/******************************************************************************
* @ 函数名 : I2c_Wait_Ack
* @ 功 能 : I2C等待应答信号
* @ 参 数 : 无
* @ 返回值 : 1:接收到应答信号,0:接收到非应答信号
******************************************************************************/
unsigned char I2c_Wait_Ack()
{
unsigned char ack = 0;
sda = 1;
scl = 0;
I2c_Delay();
scl = 1;
I2c_Delay();
if(sda == 0) //检测数据线SDA是否被拉低
ack = 1;
else
ack = 0;
scl = 0;
return ack;
}
/******************************************************************************
* @ 函数名 : I2c_Write_Byte
* @ 功 能 : I2C写字节
* @ 参 数 : dat 要写入的字节数据
* @ 返回值 : 无
******************************************************************************/
void I2c_Write_Byte(unsigned char dat)
{
unsigned char i = 0;
for(i = 0; i < 8; i++) //读取8位
{
scl = 0;
I2c_Delay();
if(dat & 0x80) //发送最高位
sda = 1;
else
sda = 0;
scl = 1;
I2c_Delay();
dat <<= 1; //左移1位
}
scl = 0;
}
/******************************************************************************
* @ 函数名 : I2c_Read_Byte
* @ 功 能 : I2C读字节
* @ 参 数 : 无
* @ 返回值 : 读取的字节数据
******************************************************************************/
unsigned char I2c_Read_Byte()
{
unsigned char dat = 0, i = 0;
for(i = 0; i < 8; i++) //读取8位
{
dat <<= 1; //左移1位
scl = 0;
I2c_Delay();
scl = 1; //SCL高电平
I2c_Delay();
if(sda) //读取SDA状态
dat |= 0x1;
}
scl = 0;
return dat;
}
/******************************************************************************
* @ 函数名 : At24c02_Write
* @ 功 能 : AT24C02写字节
* @ 参 数 :
* addr 要写数据的地址(存储空间)
* dat 要写入的字节数据
* @ 返回值 : 无
******************************************************************************/
void At24c02_Write(unsigned char addr, unsigned char dat)
{
I2c_Start();
I2c_Write_Byte(0xA0); //发送I2C设备地址
I2c_Wait_Ack(); //等待从机响应
I2c_Write_Byte(addr); //发送要写入的内存地址
I2c_Wait_Ack();
I2c_Write_Byte(dat); //写入数据
I2c_Wait_Ack();
I2c_Stop();
Delay_10us(1000); //写周期
}
/******************************************************************************
* @ 函数名 : At24c02_Read
* @ 功 能 : AT24C02读字节
* @ 参 数 : addr 要读数据的地址(存储空间)
* @ 返回值 : 读取的字节数据
******************************************************************************/
unsigned char At24c02_Read(unsigned char addr)
{
unsigned char dat = 0, i = 0;
I2c_Start();
I2c_Write_Byte(0xA0); //发送I2C设备地址
I2c_Wait_Ack(); //等待从机响应
I2c_Write_Byte(addr); //发送要写入的内存地址
I2c_Wait_Ack();
I2c_Start();
I2c_Write_Byte(0xA1); //发送I2C设备地址(读数据)
I2c_Wait_Ack(); //等待从机响应
dat = I2c_Read_Byte(); //读取数据
I2c_Wait_Ack();
I2c_Stop();
return dat;
}
————————————————
版权声明:本文为CSDN博主「小辉_Super」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_43772810/article/details/122149151
代码仅用来参考 I2C 具体实现过程,不能直接运行
#include"i2c.h"
/*******************************************************************************
* 函数名 : Delay10us()
* 函数功能 : 延时10us
* 输入 : 无
* 输出 : 无
*******************************************************************************/
void Delay10us()
{
unsigned char a,b;
for(b=1;b>0;b--)
for(a=2;a>0;a--);
}
/*******************************************************************************
* 函数名 : I2cStart()
* 函数功能 : 起始信号:在SCL时钟信号在高电平期间SDA信号产生一个下降沿
* 输入 : 无
* 输出 : 无
* 备注 : 起始之后SDA和SCL都为0
*******************************************************************************/
void I2cStart()
{
SDA=1;
Delay10us();
SCL=1;
Delay10us();//建立时间是SDA保持时间>4.7us
SDA=0;
Delay10us();//保持时间是>4us
SCL=0;
Delay10us();
}
/*******************************************************************************
* 函数名 : I2cStop()
* 函数功能 : 终止信号:在SCL时钟信号高电平期间SDA信号产生一个上升沿
* 输入 : 无
* 输出 : 无
* 备注 : 结束之后保持SDA和SCL都为1;表示总线空闲
*******************************************************************************/
void I2cStop()
{
SDA=0;
Delay10us();
SCL=1;
Delay10us();//建立时间大于4.7us
SDA=1;
Delay10us();
}
/*******************************************************************************
* 函数名 : I2cSendByte(unsigned char dat)
* 函数功能 : 通过I2C发送一个字节。在SCL时钟信号高电平期间,保持发送信号SDA保持稳定
* 输入 : num
* 输出 : 0或1。发送成功返回1,发送失败返回0
* 备注 : 发送完一个字节SCL=0,SDA=1
*******************************************************************************/
unsigned char I2cSendByte(unsigned char dat)
{
unsigned char a=0,b=0;//最大255,一个机器周期为1us,最大延时255us。
for(a=0;a<8;a++)//要发送8位,从最高位开始
{
SDA=dat>>7; //起始信号之后SCL=0,所以可以直接改变SDA信号
dat=dat<<1;
Delay10us();
SCL=1;
Delay10us();//建立时间>4.7us
SCL=0;
Delay10us();//时间大于4us
}
SDA=1;
Delay10us();
SCL=1;
while(SDA)//等待应答,也就是等待从设备把SDA拉低
{
b++;
if(b>200) //如果超过2000us没有应答发送失败,或者为非应答,表示接收结束
{
SCL=0;
Delay10us();
return 0;
}
}
SCL=0;
Delay10us();
return 1;
}
/*******************************************************************************
* 函数名 : I2cReadByte()
* 函数功能 : 使用I2c读取一个字节
* 输入 : 无
* 输出 : dat
* 备注 : 接收完一个字节SCL=0,SDA=1.
*******************************************************************************/
unsigned char I2cReadByte()
{
unsigned char a=0,dat=0;
SDA=1; //起始和发送一个字节之后SCL都是0
Delay10us();
for(a=0;a<8;a++)//接收8个字节
{
SCL=1;
Delay10us();
dat<<=1;
dat|=SDA;
Delay10us();
SCL=0;
Delay10us();
}
return dat;
}
/*******************************************************************************
* 函数名 : void At24c02Write(unsigned char addr,unsigned char dat)
* 函数功能 : 往24c02的一个地址写入一个数据
* 输入 : 无
* 输出 : 无
*******************************************************************************/
void At24c02Write(unsigned char addr,unsigned char dat)
{
I2cStart();
I2cSendByte(0xa0);//发送写器件地址
I2cSendByte(addr);//发送要写入内存地址
I2cSendByte(dat); //发送数据
I2cStop();
}
/*******************************************************************************
* 函数名 : unsigned char At24c02Read(unsigned char addr)
* 函数功能 : 读取24c02的一个地址的一个数据
* 输入 : 无
* 输出 : 无
*******************************************************************************/
unsigned char At24c02Read(unsigned char addr)
{
unsigned char num;
I2cStart();
I2cSendByte(0xa0); //发送写器件地址
I2cSendByte(addr); //发送要读取的地址
I2cStart();
I2cSendByte(0xa1); //发送读器件地址
num=I2cReadByte(); //读取数据
I2cStop();
return num;
}
代码仅用来参考 I2C 具体实现过程,不能直接运行
//
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK战舰STM32开发板
//IIC驱动 代码
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/9/9
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//
//初始化IIC
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE ); //使能GPIOB时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_SetBits(GPIOA,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7 输出高
}
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;//发送I2C总线结束信号
delay_us(4);
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1;delay_us(1);
IIC_SCL=1;delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出0
return 0;
}
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//不产生ACK应答
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
//IIC_SDA=(txd&0x80)>>7;
if((txd&0x80)>>7)
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
//在AT24CXX指定地址读出一个数据
//ReadAddr:开始读数的地址
//返回值 :读到的数据
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{
u8 temp=0;
IIC_Start();
if(EE_TYPE>AT24C16)
{
IIC_Send_Byte(0XA0); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddr>>8);//发送高地址
IIC_Wait_Ack();
}else IIC_Send_Byte(0XA0+((ReadAddr/256)<<1)); //发送器件地址0XA0,写数据
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddr%256); //发送低地址
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(0XA1); //进入接收模式
IIC_Wait_Ack();
temp=IIC_Read_Byte(0);
IIC_Stop();//产生一个停止条件
return temp;
}
//在AT24CXX指定地址写入一个数据
//WriteAddr :写入数据的目的地址
//DataToWrite:要写入的数据
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{
IIC_Start();
if(EE_TYPE>AT24C16)
{
IIC_Send_Byte(0XA0); //发送写命令
IIC_Wait_Ack();
IIC_Send_Byte(WriteAddr>>8);//发送高地址
}else
{
IIC_Send_Byte(0XA0+((WriteAddr/256)<<1)); //发送器件地址0XA0,写数据
}
IIC_Wait_Ack();
IIC_Send_Byte(WriteAddr%256); //发送低地址
IIC_Wait_Ack();
IIC_Send_Byte(DataToWrite); //发送字节
IIC_Wait_Ack();
IIC_Stop();//产生一个停止条件
delay_ms(10);
}
前言http://www.jianshu.com/p/78b4b4b9d041因为 实战项目系列 涉及到数据持久化,这边就来补充一下。如本文有错或理解偏差欢迎联系我,会尽快改正更新!如有什么问题,也可直接通过邮箱 [email protected] 联系我。demo链接: https://pan.baidu.com/s/1hsspiio 密码: dk3h数
频繁模式挖掘(Frequent Pattern Mining)
// 解密 public function decrypt($input, $key) { $size = mcrypt_get_block_size('des', 'ecb'); $td = mcrypt_module_open('des', '', 'ecb', ''); $iv = '00000000';...
__gcd-最大公约数最大公约数(greatest common divisor,简写为gcd;或highest common factor,简写为hcf)__gcd(x,y)是algorithm库中的函数#include#includeusing namespace std;int n,m;int main(){scanf(&amp;amp;amp;amp;quot;%d %d&amp;amp;amp;amp;quot;,&amp;amp;amp;amp;am
113、开发中都用到了那些设计模式?用在什么场合? 每个模式都描述了一个在我们的环境中不断出现的问题,然后描述了该问题的解决方案的核心。通过这种方式,你可以无数次地使用那些已有的解决方案,无需在重复相同的工作。主要用到了MVC的设计模式。用来开发JSP/Servlet或者J2EE的相关应用。简单工厂模式等。 114、jsp有哪些动作?作用分别是什么? JSP共有以下6种基本动作 jsp:inclu...
鼓捣了几个小时,终于能够对最小系统烧录了
看到一篇文章中提到“最近几年国内的初级Android程序员已经很多了,但是中高级的Android技术人才仍然稀缺“,这的确不假,从我在百度所进行的一些面试来看,找一个适合的高级Android工程师的确不容易,一般需要进行大量的面试才能挑选出一个比较满意的。为什么中高级Android程序员不多呢?这是一个问题,我不好回答,但是我想写一篇文章来描述下Android的学习路线,期望可以帮助更多的Andr
思路:在win10安全中心中设置文件夹或文件排除项,避免被误杀。1、打开Windows Defender参考:https://blog.csdn.net/u014162133/article/details/90515085https://blog.csdn.net/qq_38276669/article/details/85046615#commentsedit...
Failed to download metadata for repo 'AppStream'Error: Failed to download metadata for repo 'AppStream'_1671465600
阅读实验手册第16章《 LED 驱动开发实验 》P275下: fs4412_led.c, fs4412_led.h, test.c 文件 1.系统调用函数test.c的main函数中,while(1) { ioctl(fd, LED_ON, &i); usleep(500000); ioctl(fd, LED_OFF, &i);
MongoDB数据库0x00 介绍MongoDB是一个面向文档存储的非关系型数据库,是用C++编写的。MongoDB将数据存储为一个文档,数据结构由“键值对”组成,字段值可以包含其他文档、数组及文档数组,类似于JSON对象,如下格式:{ "name": "Scrapy爬虫网络", "description": "做一个Scrapy爬虫达人", "author": "["张三","李四"]", "price": "59",}MongoDB与SQL对应的术语:
首先,我很肯定我已经正确地遵循了Attaching MonoDevelop Debugger To An Android Device指南中的所有步骤(最重要的是:在构建设置中启用了“开发构建”和“脚本调试”).建设时然而,运行Unity Android应用程序时,我的设备(Samsung Galaxy S5,Android 6.0.1)在MonoDevelop和Visual Studio中都不会...