这里使用FT24C128A 解释IIC通讯。
文章开始前了解设备地址和寄存器地址。
设备地址:是一台设备的地址 ,相当于是一个硬盘的名字
寄存器地址:是个存储位置,相当于是一个硬盘的位置,如D:/location/location/location,是个存储位置地址。
寄存器:register,而一个设备,就相当是存储器。
128Kb内存(kb的b为bit),其内部有128k个bit位,注意这个是16bit的EEPROM,原因是其要用上2个字14个Bit来定义地址,这14Bit如何来的?
定址:128kb/8bit=16k=24x 1024=24 x 210= 16384 , 由于0也可以用来表示一个栈存器地址,从0到16383=0011 1111 1111 1111=0x3f 0xff个定址,如果每8个字比作为一间房(内住8个bit位),则其房间号可以达到16384个房号。
总言之,就是容量16KB(B:byte,字节),128Kb(b:bit,位)
如果要定址字只能为1个字节(1byte),则由于在I2C传输中,最后1个bit用来判断是读还是写,故存地址只有7个bit位,111 1111【r/w】=0x7f=7x161+15x160 =127,127 x 8bit=1016,内存要是少于1kb(1kb=1024bit),其只能为8bit定址(1个字节),在读写数据时为1个字节地址位的(只能用7个Bit,最后一个用来判断读写(r/w) )。
同时其有256页的,每页有64byte内存。
上图大意:
A0,A1,A2:硬件地址引脚
WP:写保护引脚,接高电平只读,接地允许读和写
SCL和SDA:IIC总线
- 128A引脚定义
我们使用I2C协议来读写Eeprom;
先了解I2C如何通讯的
1.概述
IIC = Inter Integrated-Circuit 总线是PHLIPS公司推出的一种串行总线,用于连接微控制器及其外围设备。是微电子通信控制领域广泛采用的一种总线标准。是具备多主机系统所需的包括总线裁决和高低速器件同步功能的高性能串行总线,它支持多主控(multimastering),其中任何能够进行发送和接收的设备都可以成为主总线,为了方便把IIC设备分为主设备和从设备,基本上谁控制时钟线(即控制SCL的电平高低变换)谁就是主设备。串行的8 位双向数据传输位速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s;I2C总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。注意IIC是为了与低速设备通信而发明的,所以IIC的传输速率比不上SPI
(1)开始信号与停止信号
开始信号:
SCL保持高电平,SDA由高电平变为低电平,即下降沿,延时(>4.7us),SCL变为低电平。
停止信号:SCL保持高电平。SDA由低电平变为高电平。即下降沿。
在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。
(2)应答信号
每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,故应答在第9个周期。
应答信号:主机SCL拉高,读取从机SDA的电平,为低电平表示产生应答
- 应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
- 应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
应答是0,非应答是1,如上ACK。
(3)数据读写位
图中开始信号后,传设备地址,这个地址会有一个读写位,这个位表达通讯是读还是写。
-
0表示主设备向从设备(write)写数
-
1表示主设备向从设备(read)读数据
主机要向从机写数据时:
IC的每一帧数据由9bit组成:
- 如果是发送数据,则包含 8bit数据+1bit ACK,
- 如果是设备地址数据,则8bit包含7bit设备地址+1bit方向+1bit ACK
(4)信号标准
当一个IIC通讯如发现不能通讯了,硬件正常时,都会首先看看波形,以是以IIC通讯的信号标准;
一般有问题有可能会有vcc太小,造成电压不匹配问题。
如上是个开始信号的维持时间,如果找遍了还是找不到问题,还是可以看看这些的差异。
(5)数据有效性
IIC信号在数据传输过程中,当SCL=1高电平时,数据线SDA必须保持稳定状态,不允许有电平跳变,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。当SCL=1时 数据线SDA的任何电平变换会看做是总线的起始信号或者停止信号。
也就是在IIC传输数据的过程中,SCL时钟线会频繁的转换电平,以保证数据的传输
(6)设备寻址(启动信号的后一步)
128K/256K EEPROM 在启动条件后需要一个 8 位器件地址
字,以使芯片能够进行读或写操作(参见下图 )。这是所有 2 线 EEPROM器件的共性。
128K/256K 使用两个设备地址位 A1、A0,允许同一总线上
多达四个设备。这些位必须与其对应的硬连线输入引脚进行
比较。A1和 A0 引脚使用内部专有电路,如果引脚允许浮动,
该电路会将它们偏置到逻辑低电平状态。
设备地址的第八位是读/写操作选择位。如果该位为高电平,
则启动读操作;如果该位为低电平,则启动写操作
=======================
顺便插入解释下FT24C128A的WP引脚
数据安全 数据安全:AT24C128/256 有一个硬件数据保护方案,允许 有一个硬件数据保护方案,允许用户在 用户在 WP 引脚位于 VCC 时对整个内存进行写保护。
=======================
(7)IIC数据传送(写)
输出到SDA线上的每个字节必须是8位,数据传送时,先传送最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)。
字节写
字节写:1个写操作要求在设备地址字和确认之后有两个 8 位数位数据字地址(因为这个EEPROM是16Bit定址的芯片,1 个字节无法直接将栈存器的地址存完)。
在Linux中可以使用I2ctransfer直接写,但要安装第三方工具。如要方法请看文末
sudo i2ctransfer -f -y 1 w3@0x51 0x50 0x81 0x01
w3 是写,0x51是从设备地址,0x50 0x81是寄存器地址,0x01是要写的数据
当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位ACK, 此时才认为一个字节真正的被传输完成 ,如果一段时间内没有收到从机的应答信号,则自动认为从机已正确接收到数据。
以下为写一个数据要经过的过程
- 主机首先产生START信号
- 然后紧跟着发送一个从机地址,这个地址共有7位,紧接着的第8位是数据方 向位(R/W),0表示主机发送数据(写),1表示主机接收数据(读)
- 主机发送地址时,总线上的每个从机都将这7位地址码与自己的地址进行比较,若相同,则认为自己正在被主机寻址,根据R/T位将自己确定为发送器或接收器
- 这时候主机等待从机的应答信号(A)
- 当主机收到应答信号时,发送要访问从机的那个地址, 继续等待从机的应答信号
- 当主机收到应答信号时,发送N个字节的数据,继续等待从机的N次应答信号
- 主机产生停止信号,结束传送过程。
byte write
(* =不关心的位)
(† =128K不关心 的位)
页面写入
页面写入:128K/256K 可编程只读存储器能够进行64字节的页面写入,的启动方式与字节写入相同,但微控制器不会在第一个数据字输入后发送停止条件。相反,在 EEPROM 确认收到第一个数据字后,微控制器最多可以传输 63 个数据字。在接收到每个数据字后,电可擦可编程只读存储器将以零响应。微控制器必须以停止条件终止页面写入序列(参见下图 )。收到每个数据字后,数据字地址低 6 位在内部递增。较高的数据字地址位不递增,保留存储器页行位置。当内部生成的字地址到达页面边界时,下一个字节被放在同一页面的开头。如果超过 64 个数据字被传输到电可擦可编程只读存储器,数据字地址将“翻转”,先前的数据将被覆盖。写期间的地址“翻转”是从当前页的最后一个字节到同一页的第一个字节。
(* =不关心位)
(† =128K不关心的位)
(8)读取操作
除了器件地址字中的读/写选择位设为1之外,读操作的启动方式与写操作相同。有三种读取操作:当前地址读取、随机地址读取和顺序读取。
在Linux中可以使用I2ctransfer直接写,但要安装第三方工具。如要方法请看文末
sudo i2ctransfer -f -y 1 w2@0x51 0x50 0x81 r3
w2 是读,0x51是从设备地址,0x50 0x81是寄存存器地址,r3是要连续读的3byte数据
【1】当前地址读取
当前地址读取:内部数据字地址计数器保持在最后一次读取 内部数字地址计数器保持在最后一次读取或写入操作期间访问的最后一个地址,递增 或写入操作期间访问的最后一个地址,递增 1。 。只要苾片功率保持不变,该地址在两次操作之间保持有效。读取期间的
地址“翻转”是从最后一个内存页的最后一个字节到第一个页
面的第一个字节。一旦读/写选择位设为 1 的器件地址被输入,并被 EEPROM确认,当前地址数据字被串行输出。微控制器不响应输入零,但会产生以下停止条件
- 当前地址读取
【2】随机读取
随机读取: 随机读取需要一个“ 虚拟”字节写入序列来载 字节写入序列来载 入数据字地址。一旦器件地址字和数据字地址被输入,并由EEPROM 确认,微控制器必须产生另一个启动条件。微控制器现在通过发送读/写选择位为高电平的器件地址来启动当前地址读取。电可擦可编程只读存储器确认设备地址,并连续输出数据字。微控制器不会以零响应,但会产生以下停止条件
- 随机读取
” *“=不要在意的位
(128K 不要在意的位)
【3】顺序读取
顺序读取:顺序读取由当前地址读取或随机地址读取启动。 顺序读取由当前地址读取或随机地址读取启动。微控制器接收到数据字后,会做出应答。只要电可擦可编程只读存储器收到确认,它将继续增加数据字地址,并连续时钟输出连续的数据字。当达到存储器地址限制时,数据字地址将“翻转”,顺序读取将继续。当微控制器不以零响应,但会产生以下停止条件时,顺序读取操作终止
- 顺序读
2. 硬件结构
I2C总线上的主设备使用7位地址对从设备进行寻址,可以使用128个从机地址(设备地址只有7个bit来定义,111 1111 =127,加一个0号,128个设备地址)。硬件连接很简单。2个4.7K的电阻是作电平匹配用的。
linux系统下i2c
此处内容引自路人 假的分享及Raina 感谢原作者的分享。
linux系统快速测试i2c设备,可以使用第三方的工具i2c-tools;
下载地址: https://www.kernel.org/pub/software/utils/i2c-tools/
解压后, 进入解压后的目录, 然后编译:
cd i2c-tools-4.2
make
安装:
sudo make install
使用
终端输入i2c按两次Tab键会出现可选命令:
使用如果报错:
Error: Could not open file `/dev/XXX`: Permission denied
Run as root?
请使用root权限运行:
sudo i2cdetect -y 1
如果报错:
error while loading shared libraries: libi2c.so.0: cannot open shared object file:
No such file or directory
将i2c-tools-4.0/lib/libi2c.so.0.1.1复制到/usr/lib/下, 重命名为libi2c.so.0, 并修改权限为777:
sudo cp lib/libi2c.so.0.1.1 /usr/lib/libi2c.so.0
sudo chmod 777 /usr/lib/libi2c.so.0
i2cdetect – 列出I2C bus和上面所有的设备
i2cdump – 显示所有寄存器的值
双字节的定址设备的读写:
i2ctransfer
sudo i2ctransfer -f -y 1 w3@0x51 0x50 0x81 0x01
w3 是写,0x51是从设备地址,0x50 0x81是寄存器地址,0x01是要写的数据
sudo i2ctransfer -f -y 1 w2@0x51 0x50 0x81 r3
w2 是读,0x51是从设备地址,0x50 0x81是寄存存器地址,r3是要连续读的3byte数据
单字节的定址设备的读写:
i2cget – 读取设备上寄存器register的值
i2cset – 写入设备上某个寄存器内
具体用法如下:
1、安装i2c-tools
sudo apt-get install i2c-tools
2、列出所有的i2c总线
i2cdetect -l
3、查看总线上挂载的所有i2c设备,命令以总线4为例
sudo i2cdetect -r -y 4
4、查看总线上地址为0x6a的设备的所有寄存器内部值。
sudo i2cdump -f -y 4 0x6a
5、写入栈存器0x65的值为0x01
sudo i2cset -f -y 4 0x6a 0x65 0x01
6、读取栈存器0xa9的值
sudo i2cget -f -y 4 0x6a 0xa9
注意这里读写是适用于只有1 个字节的定址的I2c设备(即8Bit地址的I2c设备的EEprom),如果是2 字节的就要I2ctranfer命令了。
在有些特定功能的IIC芯片,它们有的特定的栈存器是有特定作用的,不可以随便更改。比如中断,通讯。如以下的音频解码芯片:
其0x4A是使能SPDIF及其速度的。如果要使用此类芯片某些寄存器进行测试使用,先检查有功能已经使用上了这些寄存器。
通讯代码
代码是引用文章基于stm32的I2C总线通讯简介及使用操作(附代码),感谢原作者分享
#include "myi2c.h"
void I2C_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = SCL_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
void I2C_delay(void)
{
u8 i=10; //这里可以优化速度 ,经测试最低到5还能写入
while(i)
{
i--;
}
}
void delay5ms(void)
{
int i=5000;
while(i)
{
i--;
}
}
uint16_t I2C_Start(void)
{
SDA_H;
SCL_H;
I2C_delay();
if(!SDA_read)return FALSE; //SDA线为低电平则总线忙,退出
SDA_L;
I2C_delay();
if(SDA_read) return FALSE; //SDA线为高电平则总线出错,退出
SDA_L;
I2C_delay();
return TRUE;
}
void I2C_Stop(void)
{
SCL_L;
I2C_delay();
SDA_L;
I2C_delay();
SCL_H;
I2C_delay();
SDA_H;
I2C_delay();
}
void I2C_Ack(void)
{
SCL_L;
I2C_delay();
SDA_L;
I2C_delay();
SCL_H;
I2C_delay();
SCL_L;
I2C_delay();
}
void I2C_NoAck(void)
{
SCL_L;
I2C_delay();
SDA_H;
I2C_delay();
SCL_H;
I2C_delay();
SCL_L;
I2C_delay();
}
uint16_t I2C_WaitAck(void) //返回为:=1有ACK,=0无ACK
{
SCL_L;
I2C_delay();
SDA_H;
I2C_delay();
SCL_H;
I2C_delay();
if(SDA_read)
{
SCL_L;
I2C_delay();
return FALSE;
}
SCL_L;
I2C_delay();
return TRUE;
}
void I2C_SendByte(unsigned char SendByte) //数据从高位到低位//
{
u8 i=8;
while(i--)
{
SCL_L;
I2C_delay();
if(SendByte&0x80)
SDA_H;
else
SDA_L;
SendByte<<=1;
I2C_delay();
SCL_H;
I2C_delay();
}
SCL_L;
}
unsigned char I2C_RadeByte(void) //数据从高位到低位//
{
u8 i=8;
u8 ReceiveByte=0;
SDA_H;
while(i--)
{
ReceiveByte<<=1;
SCL_L;
I2C_delay();
SCL_H;
I2C_delay();
if(SDA_read)
{
ReceiveByte|=0x01;
}
}
SCL_L;
return ReceiveByte;
}
//ZRX
//单字节写入*******************************************
uint16_t Single_Write(unsigned char SlaveAddress,unsigned char REG_Address,unsigned char REG_data) //void
{
if(!I2C_Start())return FALSE;
I2C_SendByte(SlaveAddress); //发送设备地址+写信号//I2C_SendByte(((REG_Address & 0x0700) >>7) | SlaveAddress & 0xFFFE);//设置高起始地址+器件地址
if(!I2C_WaitAck()){I2C_Stop(); return FALSE;}
I2C_SendByte(REG_Address ); //设置低起始地址
I2C_WaitAck();
I2C_SendByte(REG_data);
I2C_WaitAck();
I2C_Stop();
delay5ms();
return TRUE;
}
//单字节读取*****************************************
unsigned char Single_Read(unsigned char SlaveAddress,unsigned char REG_Address)
{ unsigned char REG_data;
if(!I2C_Start())return FALSE;
I2C_SendByte(SlaveAddress); //I2C_SendByte(((REG_Address & 0x0700) >>7) | REG_Address & 0xFFFE);//设置高起始地址+器件地址
if(!I2C_WaitAck()){I2C_Stop();return FALSE;}
I2C_SendByte((u8) REG_Address); //设置低起始地址
I2C_WaitAck();
I2C_Start();
I2C_SendByte(SlaveAddress+1);
I2C_WaitAck();
REG_data= I2C_RadeByte();
I2C_NoAck();
I2C_Stop();
//return TRUE;
return REG_data;
}
头文件 iic.h
#ifndef __MYI2C_H__
#define __MYI2C_H__
#include "stm32f10x.h"
#define FALSE 0
#define TRUE 1
#define SCL_PIN GPIO_Pin_3
#define SDA_PIN GPIO_Pin_2
#define SCL_H GPIOB->BSRR = SCL_PIN
#define SCL_L GPIOB->BRR = SCL_PIN
#define SDA_H GPIOB->BSRR = SDA_PIN
#define SDA_L GPIOB->BRR = SDA_PIN
#define SCL_read GPIOB->IDR & SCL_PIN
#define SDA_read GPIOB->IDR & SDA_PIN
void I2C_GPIO_Config(void);
void I2C_delay(void);
void delay5ms(void);
uint16_t I2C_Start(void);
void I2C_Stop(void);
void I2C_Ack(void);
void I2C_NoAck(void);
uint16_t I2C_WaitAck(void);
void I2C_SendByte(unsigned char SendByte);
unsigned char I2C_RadeByte(void);
uint16_t Single_Write(unsigned char SlaveAddress,unsigned char REG_Address,unsigned char REG_data);
unsigned char Single_Read(unsigned char SlaveAddress,unsigned char REG_Address);
#endif // __MYI2C_H__