一,概念
I2C(IIC, Inter-Integrated Circuit,集成电路总线)总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。他是同步半双工通信
主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件。
(二)传输方向
在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。
如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送。
如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下,主机负责产生定时时钟和终止数据传送。
二,信过程
在时钟线低电平时,数据线进行数据翻转
I2C通信,存在几种信号
1)起始信号(条件):通知从机做好通信的准备。 //时钟线为高,数据线有高到低
2)应答信号:有应答和无应答。有应答是SDA为低电平,无应答是SDA为高电平。
3)停止信号(条件):告诉从机通信已经结束。 //时钟线为高,数据线有低变高
连续通信过程如图(AT24C02写入数据时序图为例)所示:主机先发送起始信号,然后发送寻址地址(寻址地址是由生产厂家和开发者共同决定的,本例子的设备地址是0xa0),等待从机响应,接着发送数据存储地址,等待AT24C02响应,接着每写入一个字节(8bit)从机应答一下。主机发送停止信号,通信结束。
三,代码实现
GPIO口配置
static GPIO_InitTypeDef GPIO_InitStructure;
void at24c02_Init(void)
{
// 输出模式
/* 1.打开GPIO时钟(根据需要开关,降低功耗) */
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
/* 2.配置(模拟IIC) */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9; // 指定引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; // 模式:输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // 输出类型:推挽
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; // 速率:输出(影响功耗)
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; // 无上下拉
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 3.初始化电平
SCL_W = 1;
SDA_W = 1;
}
修改数据线的输入,输出模式。
/ 修改SDA引脚为输入/输出模式
void sda_pin_mode(GPIOMode_TypeDef mode)
{
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Mode = mode;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
放在头文件中位带造作
#ifndef __I2C_H
#define __I2C_H
#include
#include
#include "sys.h"
#include "delay.h"
#define SDA_R PBin(9)
#define SDA_W PBout(9)
#define SCL_W PBout(8)
// 初始化AT24C02引脚(PB8/PB9)
void at24c02_Init(void);
// 连续写入数据(向地址addr写入数据pbuf,长度为len的)
uint8_t at24c02_write(uint8_t addr, uint8_t *pbuf, uint8_t len);
// 连续读取数据(从地址addr读取数据,存储到pbuf,长度为len)
uint8_t at24c02_read(uint8_t addr, uint8_t *pbuf, uint8_t len);
#endif
起始信号
/ 开始信号
void i2c_start(void)
{
// 1.确保SDA为输出模式
sda_pin_mode(GPIO_Mode_OUT);
// 2.确保SDA和SCL都为高电平
SDA_W = 1;
SCL_W = 1;
delay_us(5); // 时钟频率:100kHz -> 周期:10us (f = 1/T)
// 3.SDA跳变为低电平
SDA_W = 0;
delay_us(5); // 时钟频率:100kHz
// 4.让时钟线SCL跳变为低电平,使其不采样
SCL_W = 0;
delay_us(5); // 时钟频率:100kHz
}
结束信号
// 结束信号
void i2c_stop(void)
{
// 1.确保SDA为输出模式
sda_pin_mode(GPIO_Mode_OUT);
// 2.确保SCL为高电平,SDA为低电平
SCL_W = 1;
SDA_W = 0;
delay_us(5); // 时钟频率:100kHz
// 3.将SDA跳变为高电平
SDA_W = 1;
delay_us(5); // 时钟频率:100kHz
}
发送一个字节
void i2c_send_byte(uint8_t byte)
{
int i;
// 1.确保SDA为输出模式
sda_pin_mode(GPIO_Mode_OUT);
// 2.确保SCL和SDA都为低电平
SCL_W = 0;
SDA_W = 0;
delay_us(5); // 时钟频率:100kHz
// 3.开始发送数据 (0x10 1010)
for(i=7; i>=0; i--)
{
if(byte&(1<
接受一个字节
// 接收1个字节
uint8_t i2c_recv_byte(void)
{
int i;
uint8_t data=0;
// 1.确保SDA为输入模式
sda_pin_mode(GPIO_Mode_IN);
// 2.循环接收每个bit
for(i=7; i>=0; i--)
{
// 时钟线拉高,数据有效(对数据进行采样)
SCL_W = 1;
delay_us(5); // 时钟频率:100kHz
// 判断SDA数据线
if(SDA_R)
data |= 1<
等待从机应答
等待从机应答(有应答:0 无应答:1)
uint8_t i2c_wait_ack(void)
{
uint8_t ack;
// 1.确保SDA为输入模式
sda_pin_mode(GPIO_Mode_IN);
// 2.确保时钟线为高电平(第9周期)
SCL_W = 1;
delay_us(5); // 时钟频率:100kHz
// 3.判断SDA为高/低
//ack = SDA_R; // 一样的
if(SDA_R)
ack = 1;
else
ack = 0;
// 4.时钟线拉低,使其不采样
SCL_W = 0;
delay_us(5); // 时钟频率:100kHz
return ack;
}
应答从机
// 应答从机(有应答:0 无应答:1)
void i2c_ack(uint8_t ack)
{
// 1.确保SDA为输出模式
sda_pin_mode(GPIO_Mode_OUT);
// 2.确保SCL和SDA都为低电平(不采样)
SCL_W = 0;
SDA_W = 0;
delay_us(5); // 时钟频率:100kHz
// 3.控制SDA为高/低电平
SDA_W = ack;
delay_us(5); // 时钟频率:100kHz
// 4.时钟线拉高,数据有效(开始采样)
SCL_W = 1;
delay_us(5); // 时钟频率:100kHz
// 5.时钟线拉低,数据更改
SCL_W = 0;
delay_us(5); // 时钟频率:100kHz(保证时钟频率不变)
}
连续写入
// 连续写入数据(向地址addr写入数据pbuf,长度为len的)
uint8_t at24c02_write(uint8_t addr, uint8_t *pbuf, uint8_t len)
{
uint8_t ack;
uint8_t *p = pbuf;
// 1.发送开始信号
i2c_start();
// 2.发送寻址地址,检测是否有应答
i2c_send_byte(0xA0);
ack = i2c_wait_ack();
if(ack)
{
// 无应答
printf("device address error!\r\n");
return 2;
}
//printf("device address success!!!\r\n");
// 正常使用时,不要使用printf,可能导致时序出错
// 3.发送数据地址,检测是否有应答
i2c_send_byte(addr);
ack = i2c_wait_ack();
if(ack)
{
// 无应答
printf("word address error!\r\n");
return 3;
}
// 4.循环发送数据,每次都检测是否有应答
while(len--)
{
i2c_send_byte(*p);
p++;
ack = i2c_wait_ack();
if(ack)
{
// 无应答
printf("write error!\r\n");
return 4;
}
}
// 5.发送结束信号
i2c_stop();
return 0;
}
连续读
// 连续读取数据(从地址addr读取数据,存储到pbuf,长度为len)
uint8_t at24c02_read(uint8_t addr, uint8_t *pbuf, uint8_t len)
{
uint8_t ack;
// 1.发送开始信号
i2c_start();
// 2.发送寻址地址(写访问设备地址),检测应答
i2c_send_byte(0xA0);
ack = i2c_wait_ack();
if(ack)
{
// 无应答
printf("write device address error!\r\n");
return 2;
}
// 3.发送读取数据地址,检测应答
i2c_send_byte(addr);
ack = i2c_wait_ack();
if(ack)
{
// 无应答
printf("word address error!\r\n");
return 3;
}
// 4.再次发送开始信号
i2c_start();
// 5.再次发送寻址地址(读访问设备地址),检测应答
i2c_send_byte(0xA1);
ack = i2c_wait_ack();
if(ack)
{
// 无应答
printf("read device address error!\r\n");
return 5;
}
// 6.开始循环接收数据(len-1次),应答从设备
len = len-1;
while(len--)
{
*pbuf = i2c_recv_byte();
pbuf++;
i2c_ack(0);
}
// 7.接收最后1次数据,无应答从设备
*pbuf = i2c_recv_byte();
i2c_ack(1);
// 8.发送结束信号
i2c_stop();
}
如下图是AT24C02连续
读取逻辑图(AT24C02的读取地址是0XA1,写入地址是0xA0).
// 连续读取数据(从地址addr读取数据,存储到pbuf,长度为len)
uint8_t at24c02_read(uint8_t addr, uint8_t *pbuf, uint8_t len)
{
uint8_t ack;
// 1.发送开始信号
i2c_start();
// 2.发送寻址地址(写访问设备地址),检测应答
i2c_send_byte(0xA0);
ack = i2c_wait_ack();
if(ack)
{
// 无应答
printf("write device address error!\r\n");
return 2;
}
// 3.发送读取数据地址,检测应答
i2c_send_byte(addr);
ack = i2c_wait_ack();
if(ack)
{
// 无应答
printf("word address error!\r\n");
return 3;
}
// 4.再次发送开始信号
i2c_start();
// 5.再次发送寻址地址(读访问设备地址),检测应答
i2c_send_byte(0xA1);
ack = i2c_wait_ack();
if(ack)
{
// 无应答
printf("read device address error!\r\n");
return 5;
}
// 6.开始循环接收数据(len-1次),应答从设备
len = len-1;
while(len--)
{
*pbuf = i2c_recv_byte();
pbuf++;
i2c_ack(0);
}
// 7.接收最后1次数据,无应答从设备
*pbuf = i2c_recv_byte();
i2c_ack(1);
// 8.发送结束信号
i2c_stop();
}
主函数
#include
#include
#include
#include "sys.h"
#include "led.h"
#include "delay.h"
#include "usart.h"
#include "beep.h"
#include "i2c.h"
int main()
{
uint8_t pbuf[5] = {1, 2, 3, 4, 5};
uint8_t r_buf[5] = {0};
int i;
// 中断优先级选择第2组:拥有4种抢占、4种响应。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
LED_Init(); // 初始化LED
BEEP_Init(); // 初始化蜂鸣器
// USART1_Init(9600); // 初始化串口(9600bps:830us传输一个字节)
USART1_Init(115200); // 初始化串口(115200bps:8.6us传输一个字节)
at24c02_Init(); // 初始化EEPROM
// 保持程序循环执行
while(1)
{
at24c02_write(0, pbuf, sizeof(pbuf));
delay_ms(1000);
at24c02_read(0, r_buf, sizeof(r_buf));
for(i=0; i<5; i++)
printf("[%d] ", r_buf[i]);
printf("\r\n");
}
}
注意:本文章用 的是模拟IIC,一般不用硬件IIC因为中断有可能会打断时序导致死机。