一、I2C基本知识
1、iic
iic的实质是串口通信的一种方式。IIC 即Inter-Integrated Circuit(集成电路总线) I2C总线是PHLIPS公司推出的一种串行总线,I2C总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。
2、物理接口
(1)SCL(serial clock):时钟线,传输CLK信号,一般是I2C主设备向从设备提供时钟的通道。
(2)SDA(serial data): 数据线,通信数据都通过SDA线传输
3、通讯特征
(1)串口通讯
数据传输以字节为单位
(2)主从设备
通讯过程中,收发分主从,究竟是主设备还是从设备,这个不是IIC通信协议规定的,而是通讯双方规定的(在应用而不在协议)
一般一个硬件芯片可以只作为从设备、也可以只做主设备、或者既能够做从设备也能够做主设备
(3)通讯方式:1对1、或者1对多
1对1:一个主设备、一个从设备
1对多:一个主设备、多个从设备
(4)主设备负责总线调度
主设备决定某一时间和哪个从设备通讯,同一时刻,IIC总线上只能有一个从设备,其他从设备处在休眠状态
(5)选择通讯从设备
每一个IIC从设备在通讯的时候都有一个从设备地址(这个设备号是在同一个电路板上唯一,全球不唯一),在通讯的时候,IIC主设备必须要知道将要通讯的从设备的地址,通过发送从设备地址选中。
(6)通讯过程中改变SDA数据传输方向时,要先结束通讯,在发送从设备+方向来改变SDA方向
4、用途
IIC一般用在SOC和SOC外部设备进行通信
二、I2C通信协议
1、通信信号
【起始信号】
实质就是一个信号变化,SCL处在高电平,SDA从高电平变成低电平(下降沿)
【结束信号】
实质也是一个信号变化:SCL保持高电平不变,SDA从低电平变为高电平(上升沿)
【数据传送】
I2C总线进行数据传递过程中,SCL处在高电平期间,SDA上数据必须保持稳定,SCL处在低电平期间,才允许SDA数据变化:
(1)发送数据
在SCL处在低电平期间,向SDA输出数据,等到SCL在高电平时,稳定不变
(2)接收数据
在SCL处在高电平期间,从SDA上读取数据
【应答信号】
iic总线规定,每发送一个字节数据,都要有一个应答信号来确定数据是否被接收方收到。应答信号由接受设备产生,在SCL为高电平期间,接受设备将SDA拉低为低电平,表示数据传输正确,产生应答(ACK)。
应答信号的实质是:在发送方发完一个字节之后的第9个时钟周期,接收方发送一个bit的0(第9bit)
【非应答信号】
实质就是在单个字节之后发送一个bit位的1(第9bit)
2.数据传输格式
1.写操作:
:: 白色背景:主→从
:: 灰色背景:从→主
2.读操作:
:: 白色背景:主→从
:: 灰色背景:从→主
三、GPIO模拟I2C通信
我们这里先给大家展示一下gpio模拟I2C的程序,这是之前学习的时候,用s5p6818的板子跟一个三轴加速器通信的例子,大家主要对其通信过程参考一下,具体带码实现可以不必深究:
两个文件:I2C.c mma8451.c
I2C.c 这里主要实现I2C通信的基本函数,包括:发送起始信号,结束信号,发送一个字节数据,接受一个字节数据
#include "s5p6818.h"
extern void DelayMs(unsigned int Time);
/************SDA设置--->GPIOD7*************/
#define SDA_OUT() (GPIOD.OUTENB |= (1<<7)) //将SDA方向设置为输出
#define SDA_IN() (GPIOD.OUTENB &= ~(1<<7)) //将SDA方向设置为输入
#define SDA_H() (GPIOD.OUT |= (1<<7))//将SDA引脚输出高电平
#define SDA_L() (GPIOD.OUT &= ~(1<<7)) //将SDA引脚输出低电平
#define SDA_DATA() (GPIOD.PAD & (1<<7) ? 1: 0) //获取SDA数据是0还是1
/************SCL设置--->GPIOD6*************/
#define SCL_H() (GPIOD.OUT |= (1<<6)) //设置SCL输出高电平
#define SCL_L() (GPIOD.OUT &= ~(1<<6)) //设置SCL输出低电平
/*
* iic初始化函数
* */
void iic_init(void)
{
//1、设置引脚功能为GPIO功能
GPIOD.ALTFN0 &= ~(0x3<<12); //GPIOD6
GPIOD.ALTFN0 &= ~(0x3<<14); //GPIOD7
//2、设置SCL和SDA为输出方向
GPIOD.OUTENB |= (0x3<<6); //设置GPIOD6和GPIOD7为输出
//3、确保SCL和SDA空闲时为高电平
GPIOD.OUT |= (0x3<<6);
DelayMs(1);
}
/*
* iic起始信号发送
* */
void iic_start(void)
{
//1、将SDA方向设置为输出
SDA_OUT();
//2、SDA、SCL输出高电平
SCL_H();
SDA_H();
//3、稳定电平信号
DelayMs(1);
//4、拉低SDA产生一个起始信号
SDA_L();
DelayMs(1);
}
/*
* iic发送停止信号
* */
void iic_stop(void)
{
//1、确保SDA处在输出方向
SDA_OUT();
//2、拉高SCL
SCL_H();
//3、拉低SDA
SDA_L();
DelayMs(1);//让信号稳定
//4、拉高SDA产生一个上升沿
SDA_H();
DelayMs(1);
}
/*
* iic发送一个字节数据
* */
char iic_send_byte(char data)
{
int i = 0;
char ack = 0;
//1、设置SDA为输出方向
SDA_OUT();
//2、发送一个字节(8个bit)
for(i=0; i<8; i++)
{
//拉低SCL,方便发送数据
SCL_L();
DelayMs(1);//稳定SCL的低电平
//写数据(发数据)
if(data & (1<<7)) //最高位为1
SDA_H(); //发送高电平
else
SDA_L(); //发送低电平
DelayMs(1);
//拉高SCL,让接收方开始接收数据
SCL_H();
DelayMs(1);//让高电平持续一段时间,方便接收方有时间去读取
data = data << 1;
}
//4、拉低SCL
SCL_L(); //拉低SCL给接收方一个发送ACK的信号
//3、让SDA输出一个高电平
SDA_H(); //消除最后一次发送的bit是0对于接收ACK掉的影响
//5、切换SDA方向为输入
SDA_IN();
DelayMs(1);
//6、拉高SCL
SCL_H();
DelayMs(1);
//7、读取数据
if(SDA_DATA()) //非ACK
ack = 1;
else //ACK应答
ack = 0;
//8、保持总线
SCL_L();
DelayMs(1);
}
/*
* iic接收字节
* */
char iic_read_byte(void)
{
char data = 0;
int i = 0;
//1、设置SDA方向为输入
SDA_IN();
//2、接收字节
for(i=7; i>=0; i--)
{
//1、拉低SCL,给发送方发送信号
SCL_L();
DelayMs(1); //稳定电平信号
//2、拉高SCL,准备接收数据
SCL_H();
DelayMs(1);
//3、读取数据
if(SDA_DATA()) //高电平->1
data = data | (1<<i);
}
//3、拉低SCL
SCL_L();
//4、SDA方向设置为输出
SDA_OUT();
DelayMs(1);
//5、发送ACK
SDA_L();
DelayMs(1);
//6、拉高SCL,让对方读取ACK
SCL_H();
DelayMs(1);
//7、拉低SCL保持总线使用权限
SCL_L();
DelayMs(1);
return data;
}
mma8451.c 实现对mma8451芯片的读写:
#include "iic.h"
#define MMA_ADDR (0x1c<<1) //从设备地址
#define IIC_WRITE (MMA_ADDR) //从设备地址 + 写标记
#define IIC_READ (MMA_ADDR | 1) //从设备地址 + 读标记
/*
* 写mma8451内部寄存器
* */
void mma8451_write_reg(char reg, char data)
{
//1、发送起始信号
iic_start();
//2、发送从设备地址 + 写标记(0)
iic_send_byte(IIC_WRITE);
//3、发送寄存器地址
iic_send_byte(reg);
//4、发送写入的数据
iic_send_byte(data);
//5、发送停止信号结束通信过程
iic_stop();
}
/*
* 读取mma8451寄存器值
* */
char mma8451_read_reg(char reg)
{
char data = 0;
//1、发送起始信号
iic_start();
//2、发送从设备地址 + 写标记:因为要告知mma8451将要读取的寄存器地址
iic_send_byte(IIC_WRITE);
//3、发送mma8451寄存器地址
iic_send_byte(reg);
//4、发送起始信号
iic_start();
//5、发送从设备地址 + 读标记
iic_send_byte(IIC_READ);
//6、读取数据
data = iic_read_byte();
//7、发送停止信号结束
iic_stop();
return data;
}
/*
* 初始化设置mma8451
* */
void mma8451_init(void)
{
//1、iic初始化
iic_init();
//2、设置mma8451工作模式为active模式:设置mma8451内部寄存器0x2a = 1
mma8451_write_reg(0x2a, 1);
// printf("0x2a = %d\n", mma8451_read_reg(0x2a));
//3、设置mma8451工作状态为高性能:设置mma8451内部寄存器0x2b = 2
mma8451_write_reg(0x2b, 2);
}
四、使用I2C控制器实现通信
从上面GPIO模拟I2C的通信我们可以看到,模拟的时候是很麻烦的,一般情况下主控芯片都会有I2C控制器,我们要发送数据时,可以把数据放到某个寄存器,它就会自动的发出时钟,并且把数据发送给从设备,同时会等待从设备返回回应信号。
4.1寄存器介绍
上图是我们s3c2440芯片手册中I2C控制器的逻辑图,根据上图,我们的时钟采用的是PCLK分频后的时钟,相关的寄存器功能设置下面介绍:
(1)IICCON寄存器(Multi-masterIIC-buscontrol)
使用IICCON寄存器时,有如下注意事项。
1.发送模式的时钟频率由位[6]、位[3:0]联合决定,另外,llCCON[6]=0,IICCON[3:0]不能取0或10
2.12c中断在以下3种情况下发生:当发出地址信息或接收到一个从机地址并且吻合时,当总线仲裁失败时,当发送/接收完一个字节的数据(包括响应位)时。
3.基于SDA、SCL线上时间特性的考虑,要发送数据时,先将数据写入IICDS寄存器,然后再清除中断。
4.如果IICCON[5]=0,IICCON将不能正常工作。所以,即使不使用12c中断,也要将IICCON[5]设为1。
(2)IICSTAT寄存器(Multi-masterIIC-buscontrol/status)
IICSTAT寄存器用于选择12c接口的工作模式,发出S信号、P信号,使能接收/发送功能,并标识各种状态,比如总线仲裁是否成功、作为从机时是否被寻址、是否接收到0地址、是否接收到ACK信号等。IICSTAT寄存器的各位如表:
(3)IICADD寄存器(Multi-masterIlC-busaddress)
用到IICADD寄存器的位[0:7],表示从机地址。IICADD寄存器在串行输出使能位
IICSTAT[4]为0时,才可以写入:在任何时间都可以读出。IICADD寄存器的各位如表:
(4)IICDS寄存器(Multi-masterIIC-busTx/Rxdatashift)
用到IICDS寄存器的位丨7:0],其中保存的是要发送或己经接收的数据。IICDS寄存器在串行输出使能位IICSTAT(1)为1时,才可以写入;在任何时间都可以读出。IICDS寄存器的各位如表:
4.2控制器读写流程图
4.3程序
首先我们这里把程序分为两部分,一部分是控制器代码,实现读、写的基本功能,另一部分对从设备的读写。
4.3.1 控制器部分
i2c_controller.h
#ifndef _I2C_CONTROLLER_H
#define _I2C_CONTROLLER_H
typedef struct i2c_msg {
unsigned int addr; /* 7bits */
int flags; /* 0 - write, 1 - read */
int len;
int cnt_transferred;
int err;
unsigned char *buf;
}i2c_msg, *p_i2c_msg;
typedef struct i2c_controller {
int (*init)(void);
int (*master_xfer)(p_i2c_msg msgs, int num);
char *name;
}i2c_controller, *p_i2c_controller;
#endif /* _I2C_CONTROLLER_H */
i2c_controller.c
#include "i2c_controller.h"
#include "../s3c2440_soc.h"
static p_i2c_msg p_cur_msg;
int isLastData(void)
{
if (p_cur_msg->cnt_transferred == p_cur_msg->len - 1)
return 1; /* 正要开始传输最后一个数据 */
else
return 0;
}
void resume_iic_with_ack(void)
{
unsigned int iiccon = IICCON;
iiccon |= (1<<7); /* 回应ACK */
iiccon &= ~(1<<4); /* 恢复IIC操作 */
IICCON = iiccon;
}
void resume_iic_without_ack(void)
{
unsigned int iiccon = IICCON;
iiccon &= ~((1<<7) | (1<<4)); /* 不回应ACK, 恢复IIC操作 */
IICCON = iiccon;
}
void i2c_interrupt_func(int irq)
{
int index;
unsigned int iicstat = IICSTAT;
unsigned int iiccon;
//printf("i2c_interrupt_func! flags = %d\n\r", p_cur_msg->flags);
p_cur_msg->cnt_transferred++;
/* 每传输完一个数据将产生一个中断 */
/* 对于每次传输, 第1个中断是"已经发出了设备地址" */
if (p_cur_msg->flags == 0) /* write */
{
/* 对于第1个中断, 它是发送出设备地址后产生的
* 需要判断是否有ACK
* 有ACK : 设备存在
* 无ACK : 无设备, 出错, 直接结束传输
*/
if (p_cur_msg->cnt_transferred == 0) /* 第1次中断 */
{
if (iicstat & (1<<0))
{ /* no ack */
/* 停止传输 */
IICSTAT = 0xd0;
IICCON &= ~(1<<4);
p_cur_msg->err = -1;
printf("tx err, no ack\n\r");
delay(1000);
return;
}
}
if (p_cur_msg->cnt_transferred < p_cur_msg->len)
{
/* 对于其他中断, 要继续发送下一个数据
*/
IICDS = p_cur_msg->buf[p_cur_msg->cnt_transferred];
IICCON &= ~(1<<4);
}
else
{
/* 停止传输 */
IICSTAT = 0xd0;
IICCON &= ~(1<<4);
delay(1000);
}
}
else /* read */
{
/* 对于第1个中断, 它是发送出设备地址后产生的
* 需要判断是否有ACK
* 有ACK : 设备存在, 恢复I2C传输, 这样在下一个中断才可以得到第1个数据
* 无ACK : 无设备, 出错, 直接结束传输
*/
if (p_cur_msg->cnt_transferred == 0) /* 第1次中断 */
{
if (iicstat & (1<<0))
{ /* no ack */
/* 停止传输 */
IICSTAT = 0x90;
IICCON &= ~(1<<4);
p_cur_msg->err = -1;
printf("rx err, no ack\n\r");
delay(1000);
return;
}
else /* ack */
{
/* 如果是最后一个数据, 启动传输时要设置为不回应ACK */
/* 恢复I2C传输 */
if (isLastData())
{
resume_iic_without_ack();
}
else
{
resume_iic_with_ack();
}
return;
}
}
/* 非第1个中断, 表示得到了一个新数据
* 从IICDS读出、保存
*/
if (p_cur_msg->cnt_transferred < p_cur_msg->len)
{
index = p_cur_msg->cnt_transferred - 1;
p_cur_msg->buf[index] = IICDS;
/* 如果是最后一个数据, 启动传输时要设置为不回应ACK */
/* 恢复I2C传输 */
if (isLastData())
{
resume_iic_without_ack();
}
else
{
resume_iic_with_ack();
}
}
else
{
/* 发出停止信号 */
IICSTAT = 0x90;
IICCON &= ~(1<<4);
delay(1000);
}
}
}
void s3c2440_i2c_con_init(void)
{
/* 配置引脚用于I2C*/
GPECON &= ~((3<<28) | (3<<30));
GPECON |= ((2<<28) | (2<<30));
/* 设置时钟 */
/* [7] : IIC-bus acknowledge enable bit, 1-enable in rx mode
* [6] : 时钟源, 0: IICCLK = fPCLK /16; 1: IICCLK = fPCLK /512
* [5] : 1-enable interrupt
* [4] : 读出为1时表示中断发生了, 写入0来清除并恢复I2C操作
* [3:0] : Tx clock = IICCLK/(IICCON[3:0]+1).
* Tx Clock = 100khz = 50Mhz/16/(IICCON[3:0]+1)
*/
IICCON = (1<<7) | (0<<6) | (1<<5) | (30<<0);
/* 注册中断处理函数 */
register_irq(27, i2c_interrupt_func);
}
int do_master_tx(p_i2c_msg msg)
{
p_cur_msg = msg;
msg->cnt_transferred = -1;
msg->err = 0;
/* 设置寄存器启动传输 */
/* 1. 配置为 master tx mode */
IICCON |= (1<<7); /* TX mode, 在ACK周期释放SDA */
IICSTAT = (1<<4);
/* 2. 把从设备地址写入IICDS */
IICDS = msg->addr<<1;
/* 3. IICSTAT = 0xf0 , 数据即被发送出去, 将导致中断产生 */
IICSTAT = 0xf0;
/* 后续的传输由中断驱动 */
/* 循环等待中断处理完毕 */
while (!msg->err && msg->cnt_transferred != msg->len);
if (msg->err)
return -1;
else
return 0;
}
int do_master_rx(p_i2c_msg msg)
{
p_cur_msg = msg;
msg->cnt_transferred = -1;
msg->err = 0;
/* 设置寄存器启动传输 */
/* 1. 配置为 Master Rx mode */
IICCON |= (1<<7); /* RX mode, 在ACK周期回应ACK */
IICSTAT = (1<<4);
/* 2. 把从设备地址写入IICDS */
IICDS = (msg->addr<<1)|(1<<0);
/* 3. IICSTAT = 0xb0 , 从设备地址即被发送出去, 将导致中断产生 */
IICSTAT = 0xb0;
/* 后续的传输由中断驱动 */
/* 循环等待中断处理完毕 */
while (!msg->err && msg->cnt_transferred != msg->len);
if (msg->err)
return -1;
else
return 0;
}
int s3c2440_master_xfer(p_i2c_msg msgs, int num)
{
int i;
int err;
for (i = 0; i < num; i++)
{
if (msgs[i].flags == 0)/* write */
err = do_master_tx(&msgs[i]);
else
err = do_master_rx(&msgs[i]);
if (err)
return err;
}
return 0;
}
4.3.2 设备部分
#include "i2c_controller.h"
#define AT24CXX_ADDR 0x50
int at24cxx_write(unsigned int addr, unsigned char *data, int len)
{
i2c_msg msg;
int i;
int err;
unsigned char buf[2];
for (i = 0; i < len; i++)
{
buf[0] = addr++;
buf[1] = data[i];
/* 构造i2c_msg */
msg.addr = AT24CXX_ADDR;
msg.flags = 0; /* write */
msg.len = 2;
msg.buf = buf;
msg.err = 0;
msg.cnt_transferred = -1;
/* 调用i2c_transfer */
err = s3c2440_master_xfer(&msg, 1);
if (err)
return err;
}
return 0;
}
int at24cxx_read(unsigned int addr, unsigned char *data, int len)
{
i2c_msg msg[2];
int err;
/* 构造i2c_msg */
msg[0].addr = AT24CXX_ADDR;
msg[0].flags = 0; /* write */
msg[0].len = 1;
msg[0].buf = &addr;
msg[0].err = 0;
msg[0].cnt_transferred = -1;
msg[1].addr = AT24CXX_ADDR;
msg[1].flags = 1; /* read */
msg[1].len = len;
msg[1].buf = data;
msg[1].err = 0;
msg[1].cnt_transferred = -1;
/* 调用i2c_transfer */
err = s3c2440_master_xfer(&msg, 2);
if (err)
return err;
return 0;
}