(1)IIC总线硬件是由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。
(2)IIC总线通信原理是通过对SCL和SDA线高低电平时序的控制,来产生IIC总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线会被上拉电阻拉高,保持着高电平。
IIC总线是具有多主多从的系统总线可裁决功能的高性能串行总线。(多主机用的比较少,基本上都是一个主机挂载多个从机设备)
主设备通过寻址来确定与哪个器件进行通信。通常情况下,我们把CPU带IIC总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。
主机(主设备):主动发起接收和发送数据(CPU)
从机(从设备):被动的接收和发送数据为从机
IIC总线上可挂接的设备总数量受总线的最大电容400pF所限制。如果所挂接的是相同型号的器件,则还受器件地址位个数的限制。
IIC总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。一般通过IIC总线接口可编程时钟来实现传输速率的调整,同时也跟所接的上拉电阻的阻值有关。
怎么区分主机和哪一个从机通信?
IIC总线上的每一个设备都可以作为主设备或从设备,而且每一个接到IIC总线上的器件都会分配一个唯一的地址。主机和其他从机器件在数据传输时通过对地址进行匹配后再确认通信,不区分主机、从机,发送数据的设备是发送器,接收数据的设备是接收器。
IIC总线上的主设备与从设备间以字节(8位 = 一帧)为单位进行双向的数据传输,主、从设备间的数据传输是建立在地址的基础上。也就是说:主设备在传输有效数据之前要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位。然后协议规定再给地址添加一个最低位:用来表示接下来数据传输的方向
0:主机向从机写(发送)数据
1:主机向从机读(接收)数据,从机写数据
设备地址的产生?
通过芯片引脚的物理接地或上拉电阻拉高。
eg:从IIC器件的数据手册得知,如TVP5158芯片,总共有7位地址依次为bit6~bit0:101 1xxx, 低三位总共有8种地址类型选配。如果第三位引脚全部物理接地,则该设备地址为0x58。
注:此类芯片在该主、从总线上最多可以挂载8个。
(1)IIC协议规定,总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生,即意味着从设备不可以主动通信,所有的通信都是从主设备发起的,主机可以发出询问的command,然后等待从机设备的通信。
(2)起始和结束信号的产生条件:总线在空闲状态时,SCL和SDA默认都是保持着高电平状态。
(3)在起始条件产生后,总线处于忙状态:即本次数据传输的总线由主、从设备独占,其他IIC器件此时无法访问总线;而在终止条件产生后,本次数据传输的主、从设备将总线释放,总线再次处于空闲状态。
SCL在高电平期间,SDA数据线必须保持数据的稳定,此时才可以读取数据。
SCL在低电平时,SDA数据线上高低电平状态才允许变化,此时才可以写入数据。
注意:无论主机还是从机,发送或接收数据都是把数据先放入自己的移位寄存器里保存的,之后再进行数据的发送到SDA/写入到自己的内存中。
以stm32F105VCT为例:
/* --------------------- IIC 初始化 -------------------------*/
/* IIC1 初始化 */
void IIC_Dev_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能GPIOB端口时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
/* 配置SCL,SDA */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* SCL、SDA初始默认状态值 */
IIC_SCL = 1;
IIC_SDA = 1;
}
/* --------------------- IIC 初始化 -------------------------*/
/* IIC1 初始化 */
void IIC_Dev_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* 使能GPIOB端口时钟 */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
/* 配置SCL,SDA */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
/* SCL、SDA初始默认状态值 */
IIC_SCL = 1;
IIC_SDA = 1;
}
/*---------------------------- 软件模拟 IIC时序 ---------------------------*/
/* 产生IIC 起始信号 */
void IIC_Start(void)
{
SDA_OUT(); // 设置SDA线为输出
IIC_SDA = 1;
IIC_SCL = 1;
delay_us(4);
IIC_SDA = 0;
delay_us(4);
IIC_SCL = 0;
}
/* 产生IIC 停止信号 */
void IIC_Stop(void)
{
SDA_OUT();
IIC_SCL = 0;
IIC_SDA = 0;
delay_us(4);
IIC_SCL = 1;
IIC_SDA = 1; // 发送I2C总线结束信号
delay_us(4);
}
/* 等待应答信号到来 */
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;
}
/*------------- 指令周期 约1.05us ----------------*/
void delay_us(int i)
{
while(i--);
}
可以适用于任何的stm32F系列平台的IIC时序,delay_us()使用MDK(keil5.0)可以调试仿真:一条指令的大概执行时间。
/*---------------- IIC发送/接收一个 Byte数据 -------------------*/
/**
* @brief IIC发送一个字节
* @param
* @retval 返回从机有无应答
* 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;
txd <<= 1;
delay_us(2); //对TEA5767这三个延时都是必须的
IIC_SCL = 1;
delay_us(2);
IIC_SCL = 0;
delay_us(2);
}
}
/* 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;
}
#define I2C_CLK_SPEED 100000 //100KHZ < 400KHZ
/* ------------------------ IIC的SCL、SDA 位带操作 ----------------------- */
//外设地址
#define PERIPH_BASE ((uint32_t)0x40000000)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
#define GPIOB_BASE (APB2PERIPH_BASE + 0x0C00)
#define GPIOB_ODR_Addr (GPIOB_BASE + 12)
#define GPIOB_IDR_Addr (GPIOB_BASE + 8)
//位带映射操作
#define BITBAND(addr, bitnum) \
((addr & 0xF0000000) + 0x2000000 + ((addr & 0xFFFFF) << 5) + (bitnum << 2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr, n)
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr, n)
#define IIC_SCL PBout(8) //SCL
#define IIC_SDA PBout(9) //SDA
#define READ_SDA PBin(9) //read SDA
/* ------------------- 设置SDA_IO数据流传输方向--------------------*/
/* SDA数据流输入 */
#define SDA_IN() \
do \
{ \
GPIOB->CRH &= 0XFFFFFF0F; \
GPIOB->CRH |= (u32)8 << 4; \
} while (0) //上拉输入
/* SDA数据流输出 */
#define SDA_OUT() \
do \
{ \
GPIOB->CRH &= 0XFFFFFF0F; \
GPIOB->CRH |= (u32)3 << 4; \
} while (0) //50MHZ推挽输出