前言
写文章的目的是想通过记录自己的学习过程,以便以后使用到相关的知识点可以回顾和参考。
I2C简介
I2C 是很常见的一种总线协议,I2C 是 NXP 公司设计的,I2C 使用两条线在主控制器和从
机之间进行数据通信。一条是 SCL(串行时钟线),另外一条是 SDA(串行数据线),这两条数据
线需要接上拉电阻,总线空闲的时候 SCL 和 SDA 处于高电平。I2C 总线标准模式下速度可以
达到 100Kb/S,快速模式下可以达到 400Kb/S。
I2C 是支持多从机的,也就是一个 I2C 控制器下可以挂多个 I2C 从设备,这些不同的 I2C
从设备有不同的器件地址,这样 I2C 主控制器就可以通过 I2C 设备的器件地址访问指定的 I2C
设备了,一个 I2C 总线连接多个 I2C 设备如图所示:
实现流程
实现i2c传输功能的步骤:
1、初始化i2c,包括通过I2CCLKENB启动i2c的时钟源PCLK,通过I2CCON设置时钟分频得到i2c_clk,通过I2CSTAT开启i2c功能。
2、建立i2c传输的数据结构
3、编写产生读/写开始信号的函数,产生读/写停止信号的函数,检查ack应答信号的函数
4、编写主机模式下的读/写函数
具体实现
1、初始化i2c
其中i2c_clk的时钟源——PCLK的开启需要到手册的时钟体系板块那里找到相应的寄存器,而实现通过分频得到i2c_clk就到手册的i2c板块那里找相应的寄存器,一般i2c_clk的频率是100KHz或者400KHz。
#define I2C0_Clk (*(volatile unsigned int*)0xC00AE000)
#define I2C1_Clk (*(volatile unsigned int*)0xC00AF000)
#define I2C2_Clk (*(volatile unsigned int*)0xC00B0000)
/* 初始化i2c */
void i2c_init(I2C_Type *Base)
{
Base->I2CSTAT &= ~(1<<4); /* 关闭i2c */
I2C0_Clk |=(1<<3); I2C1_Clk |=(1<<3); I2C2_Clk |=(1<<3); /* 使能i2c0,1,2 的时钟源PCLK */
Base->I2CCON |= (1<<6); /* PCLK/256 = 200/256 = 781KHz */
Base->I2CCON &= (0xF<<0);
Base->I2CCON |= (7<<0); /* Tx clock = 781/7 = 111KHz */
Base->I2CCON |= (1<<7); /* 1:发送允许ACK信号 ,0:发出NO ACK信号*/
Base->I2CSTAT |= (1<<4); /* 使能i2c */
}
2、建立i2c传输的数据结构
/* 相关宏定义 */
#define I2C_STATUS_OK (0) /* 正常 */
#define I2C_STATUS_BUSY (1) /* 在忙 */
#define I2C_STATUS_IDLE (2) /* 空闲 */
#define I2C_STATUS_NOACK (3) /* 发送NOACK信号 */
#define I2C_STATUS_ARBITRATIONLOST (4) /* 仲裁丢失错误 */
#define I2C_STATUS_TIMEOUT (5) /* 超时 */
#define I2C_STATUS_ADDRNAK (6) /* 没收到ACK信号 */
enum i2c_direction
{
i2c_write = 0, /* 写数据 */
i2c_read = 1, /* 读数据 */
};
/* 主机传输结构体 */
struct i2c_transfer
{
unsigned char slaveAddress; /* 7位从机地址 */
unsigned int subaddress; /* 寄存器地址 */
volatile char *data; /* 数据缓冲区 */
volatile unsigned int dataSize; /* 数据缓冲区长度 */
};
3、编写产生读/写开始信号的函数,产生读/写停止信号的函数,检查ack应答信号的函数,参考数据手册需要使用的寄存器有I2CSTAT,I2CDS,I2CCON
/* 发送read的开始信号 */
unsigned char i2c_master_start_r(I2C_Type *Base, unsigned char address, enum i2c_direction direction)
{
//if(Base->I2CSTAT & (1<<5)) /* 检查i2c是否在忙 */
// return I2C_STATUS_BUSY;
Base->I2CDS = (((unsigned char)address<<1) | ((direction == i2c_read)? 1:0)); /* 发送从设备地址及读写位 */
Base->I2CSTAT = 0xB0; /* 根据参考手册 read 0xB0(M/T Start),即产生一个start信号 */
return 0;
}
/* 发送write的开始信号 */
unsigned char i2c_master_start_w(I2C_Type *Base, unsigned char address, enum i2c_direction direction)
{
if(Base->I2CSTAT & (1<<5)) /* 检查i2c是否在忙 */
return I2C_STATUS_BUSY;
Base->I2CDS = (((unsigned char)address<<1) | ((direction == i2c_read)? 1:0)); /* 发送从设备地址及读写位 */
Base->I2CSTAT = 0xF0; /* 根据参考手册 write 0xF0(M/T Start),即产生一个start信号 */
return 0;
}
/* 错误检查 */
unsigned char i2c_check_and_clear_error(I2C_Type *Base)
{
//unsigned short timeout = 0xffff;
/* 检查仲裁丢失错误 */
if((Base->I2CSTAT & (1<<3)))
{
Base->I2CCON &= ~(1<<4); /* 清标志位 */
Base->I2CSTAT &= ~(1<<4); /* 关闭i2c */
Base->I2CSTAT |= (1<<4); /* 重新打开i2c */
return I2C_STATUS_ARBITRATIONLOST;
}
/* 检查是否接收到ack信号 */
else if(Base->I2CSTAT & (1<<0))
{
return I2C_STATUS_NOACK;
}
return I2C_STATUS_OK;
}
/* 发送read停止信号 */
unsigned char i2c_master_stop_r(I2C_Type *Base)
{
unsigned short timeout = 0xffff;
Base->I2CSTAT = 0x90; /* 根据参考手册 write 0xD0(M/T Start),即产生一个stop信号 */
/* 等待忙完 */
while(Base->I2CSTAT & (1<<5))
{
timeout--;
if(timeout == 0)
return I2C_STATUS_TIMEOUT;
}
return I2C_STATUS_OK;
}
/* 发送write停止信号 */
unsigned char i2c_master_stop_w(I2C_Type *Base)
{
unsigned short timeout = 0xffff;
Base->I2CSTAT = 0xD0; /* 根据参考手册 write 0xD0(M/T Start),即产生一个stop信号 */
/* 等待忙完 */
while(Base->I2CSTAT & (1<<5))
{
timeout--;
if(timeout == 0)
return I2C_STATUS_TIMEOUT;
}
return I2C_STATUS_OK;
}
4、编写主机模式下的读/写函数
在参考手册中的截图:主机模式下的发送流程
/* 主机模式下的写操作 */
unsigned char i2c_master_write(I2C_Type *Base, struct i2c_transfer *vfer)
{
//unsigned char error = 0;
//Base->I2CCON &= ~(1<<4); /* 清标志位 */
i2c_master_start_w(Base, vfer->slaveAddress, i2c_write); /* 发送start信号 */
Base->I2CCON &= ~(1<<4); /* 清标志位 */
if(i2c_check_and_clear_error(Base)) /* 检查ACK */
return i2c_check_and_clear_error(Base);
Base->I2CDS = vfer->subaddress; /* 要写入的寄存器地址 */
Base->I2CCON &= ~(1<<4); /* 清标志位 */
if(i2c_check_and_clear_error(Base)) /* 检查ACK */
return i2c_check_and_clear_error(Base);
while(vfer->dataSize--)
{
Base->I2CDS = *(vfer->data)++;
Base->I2CCON &= ~(1<<4); /* 清标志位 */
if(i2c_check_and_clear_error(Base)) /* 检查ACK */
{
i2c_master_stop_w(Base); /* 出错时发送write停止信号 */
Base->I2CCON &= ~(1<<4); /* 清标志位 */
return i2c_check_and_clear_error(Base);
}
}
i2c_master_stop_w(Base); /* 发送write停止信号 */
Base->I2CCON &= ~(1<<4); /* 清标志位 */
return I2C_STATUS_OK;
}
在参考手册中的截图:主机模式下的接收流程
/* 主机模式下的读操作 */
unsigned char i2c_master_read(I2C_Type *Base, struct i2c_transfer *vfer)
{
volatile char unusedata = 0;
unusedata++;
i2c_master_start_w(Base, vfer->slaveAddress, i2c_write); /* 发送write的start信号 */
Base->I2CCON &= ~(1<<4); /* 清标志位 */
if(i2c_check_and_clear_error(Base)) /* 检查ACK */
return i2c_check_and_clear_error(Base);
Base->I2CDS = vfer->subaddress; /* 要读取的寄存器地址 */
Base->I2CCON &= ~(1<<4); /* 清标志位 */
if(i2c_check_and_clear_error(Base)) /* 检查ACK */
return i2c_check_and_clear_error(Base);
i2c_master_start_r(Base, vfer->slaveAddress, i2c_read); /* 发送read的start信号 */
Base->I2CCON &= ~(1<<4); /* 清标志位 */
if(i2c_check_and_clear_error(Base)) /* 检查ACK */
return i2c_check_and_clear_error(Base);
if(vfer->dataSize == 1)
Base->I2CCON &= ~(1<<7); /* 发送NO ACK信号 */
unusedata = Base->I2CDS; /* 假读,第一个数据没用,不要 */
Base->I2CCON &= ~(1<<4); /* 清标志位 */
if(i2c_check_and_clear_error(Base)) /* 检查ACK */
return i2c_check_and_clear_error(Base);
while(vfer->dataSize--)
{
if(vfer->dataSize == 1)
{
Base->I2CCON &= ~(1<<7); /* 在接收倒数第二个数据时发送NO ACK信号 */
}
*(vfer->data)++ = Base->I2CDS; /* 接收数据 */
if(i2c_check_and_clear_error(Base)) /* 检查ACK */
{
i2c_master_stop_r(Base); /* 出错时发送read停止信号 */
Base->I2CCON &= ~(1<<4); /* 清标志位 */
return i2c_check_and_clear_error(Base);
}
}
i2c_master_stop_r(Base); /* 发送read停止信号 */
Base->I2CCON &= ~(1<<4); /* 清标志位 */
return I2C_STATUS_OK;
}