IIC总线协议
IIC总线协议介绍
IIC:Inter Integrated Circuit,集成电路总线,是一种同步 串行 半双工通信总线。
总线就是传输数据通道
协议就是传输数据的规则
IIC总线结构图
① 由时钟线SCL和数据线SDA组成,并且都接上拉电阻,确保总线空闲状态为高电平
② 总线支持多设备连接,允许多主机存在,每个设备都有一个唯一的地址
③ 连接到总线上的数目受总线的最大电容400pf限制
④ 数据传输速率:标准模式100k bit/s 快速模式400k bit/s 高速模式3.4Mbit/s
起始信号
void iic_start(void)
{ /* SCL为高电平期间, SDA从高电平往低电平跳变*/
IIC_SDA ( 1 );
IIC_SCL ( 1 );
iic_delay( );
IIC_SDA ( 0 );
iic_delay( );
IIC_SCL ( 0 );
iic_delay( ); /* 钳住总线, 准备发送/接收数据 */
}
停止信号
void iic_stop(void)
{ /* SCL为高电平期间, SDA从低电平往高电平跳变*/
IIC_SDA ( 0 );
iic_delay( );
IIC_SCL ( 1 );
iic_delay( );
IIC_SDA ( 1 ); /* 发送总线停止信号*/
iic_delay( );
}
检测应答信号
uint8_t iic_wait_ack (void) /* return 1:fail 0:succeed*/
{
IIC_SDA (1); /* 主机释放SDA线 */
iic_delay( );
IIC_SCL (1); /* 从机返回ACK*/
iic_delay( );
if ( IIC_READ_SDA ) /* SCL高电平读取SDA状态*/
{
iic_stop(); /* SDA高电平表示从机nack */
return 1;
}
IIC_SCL(0); /* SCL低电平表示结束ACK检查 */
iic_delay( );
return 0;
}
发送应答信号
void iic_ack(void)
{
IIC_SCL (0);
iic_delay( );
IIC_SDA (0); /* 数据线为低电平,表示应答 */
iic_delay( );
IIC_SCL (1);
iic_delay( );
}
发送非应答信号
void iic_nack(void)
{
IIC_SCL (0);
iic_delay( );
IIC_SDA (1); /* 数据线为高电平,表示非应答 */
iic_delay( );
IIC_SCL (1);
iic_delay( );
}
发送1字节数据
void iic_send_byte(uint8_t data)
{
for (uint8_t t = 0; t < 8; t++)
{ /* 高位先发 */
IIC_SDA((data & 0x80) >> 7);
iic_delay( );
IIC_SCL ( 1 );
iic_delay( );
IIC_SCL ( 0 );
data <<= 1; /* 左移1位, 用于下一次发送 */
}
IIC_SDA ( 1 ); /* 发送完成,主机释放SDA线 */
}
读取1字节数据
uint8_t iic_read_byte (uint8_t ack) /* 1:ack 0:nack*/
{
uint8_t receive = 0 ;
for (uint8_t t = 0; t < 8; t++)
{ /* 高位先输出,先收到的数据位要左移 */
receive <<= 1;
IIC_SCL ( 1 );
iic_delay( );
if ( IIC_READ_SDA ) receive++;
IIC_SCL ( 0 );
iic_delay( );
}
if ( !ack ) iic_nack();
else iic_ack();
return receive;
}
AT24C02
原理图
EEPROM是一种掉电后数据不丢失的储存器,常用来存储一些配置信息,在系统重新上电时就可以加载。
AT24C02是一个2K bit的EEPROM存储器,使用IIC通信方式。
AT24C02:写操作地址(0xA0) 读操作地址(0xA1)
AT24C02写时序
AT24C02读时序
IIC配置步骤
1、使能SCL和SDA对应时钟
__HAL_RCC_GPIOB_CLK_ENABLE()
2、设置GPIO工作模式
SDA开漏/SCL推挽输出模式,使用HAL_GPIO_Init初始化
3、编写基本信号
起始信号 停止信号 应答信号
4、编写读和写函数
iic_read_byte、iic_send_byte
为什么IIC总线SDA建议用开漏模式?
开漏输出的特点:不能输出高电平, 必须有外部(或内部)上拉才能输出高电平
IIC的SDA脚即要作为输出,又要作为输入,用开漏输出模式,很好实现输出输入共用,避免IO模式频繁切换带来的麻烦。
输出时:主机(MCU)输出0,可以拉低信号,来实现低电平发送,主机输出1(实际不起作用),由外部上拉电阻上拉,实现高电平发送。
输入时:主机(MCU)设置输出1状态,此时因为MCU无法输出1,相当于释放了SDA脚,此时外部器件可以主动拉低SDA脚/释放SDA脚(同样由上拉电阻提供“输出1的功能”),实现SDA脚的高低电平变化。
由于开漏输出模式下,MCU还是可以读取IDR状态寄存器,来获取引脚高低电平,因此MCU读取IDR,即可获得SDA脚的高低电平状态,从而实现输入检测。
AT24C02配置步骤
1、初始化IIC接口
2、编写写入/读取一个字节数据函数 遵循读写时序流程编写
3、编写连续读和连续写函数 在2的基础上进行实现
实验:驱动AT24C02实现读和写1字节数据
myiic.c
#include "./BSP/IIC/myiic.h"
#include "./SYSTEM/delay/delay.h"
void iic_init(void)
{
GPIO_InitTypeDef gpio_init_struct;
IIC_SCL_GPIO_CLK_ENABLE(); /* SCL引脚时钟使能 */
IIC_SDA_GPIO_CLK_ENABLE(); /* SDA引脚时钟使能 */
gpio_init_struct.Pin = IIC_SCL_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP; /* 推挽输出 */
gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH; /* 高速 */
HAL_GPIO_Init(IIC_SCL_GPIO_PORT, &gpio_init_struct); /* SCL */
gpio_init_struct.Pin = IIC_SDA_GPIO_PIN;
gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD; /* 开漏输出 */
HAL_GPIO_Init(IIC_SDA_GPIO_PORT, &gpio_init_struct); /* SDA */
/* SDA引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了, 开漏输出的时候(=1), 也可以读取外部信号的高低电平 */
}
static void iic_delay(void)
{
delay_us(2);
}
/* 起始信号 */
void iic_start(void)
{
/* SCL为高电平期间, SDA从高电平往低电平跳变*/
IIC_SDA ( 1 );
IIC_SCL ( 1 );
iic_delay( );
IIC_SDA ( 0 );
iic_delay( );
IIC_SCL ( 0 );
iic_delay( ); /* 钳住总线, 准备发送/接收数据 */
}
/* 停止信号 */
void iic_stop(void)
{
/* SCL为高电平期间, SDA从低电平往高电平跳变*/
IIC_SDA ( 0 );
iic_delay( );
IIC_SCL ( 1 );
iic_delay( );
IIC_SDA ( 1 ); /* 发送总线停止信号*/
iic_delay( );
}
/* 等待应答信号 */
uint8_t iic_wait_ack (void) /* return 1:fail 0:succeed*/
{
IIC_SDA (1); /* 主机释放SDA线 */
iic_delay( );
IIC_SCL (1); /* 从机返回ACK*/
iic_delay( );
if ( IIC_READ_SDA ) /* SCL高电平读取SDA状态*/
{
iic_stop(); /* SDA高电平表示从机nack */
return 1;
}
IIC_SCL(0); /* SCL低电平表示结束ACK检查 */
iic_delay( );
return 0;
}
/* 应答信号 */
void iic_ack(void)
{
IIC_SCL (0);
iic_delay( );
IIC_SDA (0); /* 数据线为低电平,表示应答 */
iic_delay( );
IIC_SCL (1);
iic_delay( );
}
/* 非应答信号 */
void iic_nack(void)
{
IIC_SCL (0);
iic_delay( );
IIC_SDA (1); /* 数据线为低电平,表示应答 */
iic_delay( );
IIC_SCL (1);
iic_delay( );
}
/* 发送一个字节数据 */
void iic_send_byte(uint8_t data)
{
for (uint8_t t = 0; t < 8; t++)
{
/* 高位先发 */
IIC_SDA((data & 0x80) >> 7);
iic_delay( );
IIC_SCL ( 1 );
iic_delay( );
IIC_SCL ( 0 );
data <<= 1; /* 左移1位, 用于下一次发送 */
}
IIC_SDA ( 1 ); /* 发送完成,主机释放SDA线 */
}
/* 读取1字节数据 */
uint8_t iic_read_byte (uint8_t ack)
{
uint8_t receive = 0 ;
for (uint8_t t = 0; t < 8; t++)
{
/* 高位先输出,先收到的数据位要左移 */
receive <<= 1;
IIC_SCL ( 1 );
iic_delay( );
if ( IIC_READ_SDA ) receive++;
IIC_SCL ( 0 );
iic_delay( );
}
if ( !ack ) iic_nack();
else iic_nack();
return receive;
}
myiic.h
#ifndef __MYIIC_H
#define __MYIIC_H
#include "./SYSTEM/sys/sys.h"
/******************************************************************************************/
/* 引脚 定义 */
#define IIC_SCL_GPIO_PORT GPIOB
#define IIC_SCL_GPIO_PIN GPIO_PIN_6
#define IIC_SCL_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
#define IIC_SDA_GPIO_PORT GPIOB
#define IIC_SDA_GPIO_PIN GPIO_PIN_7
#define IIC_SDA_GPIO_CLK_ENABLE() do{ __HAL_RCC_GPIOB_CLK_ENABLE(); }while(0) /* PB口时钟使能 */
/******************************************************************************************/
/* IO操作 */
#define IIC_SCL(x) do{ x ? \
HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(IIC_SCL_GPIO_PORT, IIC_SCL_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* SCL */
#define IIC_SDA(x) do{ x ? \
HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_SET) : \
HAL_GPIO_WritePin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN, GPIO_PIN_RESET); \
}while(0) /* SDA */
#define IIC_READ_SDA HAL_GPIO_ReadPin(IIC_SDA_GPIO_PORT, IIC_SDA_GPIO_PIN) /* 读取SDA */
void iic_init(void);
void iic_start(void);
void iic_stop(void);
uint8_t iic_wait_ack(void);
void iic_ack(void);
void iic_nack(void);
void iic_send_byte(uint8_t data);
uint8_t iic_read_byte (uint8_t ack);
#endif
24cxx.c
#include "./BSP/IIC/myiic.h"
#include "./BSP/24CXX/24cxx.h"
#include "./SYSTEM/delay/delay.h"
void at24c02_init(void)
{
iic_init();
}
void at24c02_write_one_byte(uint8_t addr, uint8_t data)
{
/* 1、发送起始信号 */
iic_start();
/* 2、发送通讯地址(写操作地址) */
iic_send_byte(0xA0);
/* 3、等待应答信号 */
iic_wait_ack();
/* 4、发送内存地址 */
iic_send_byte(addr);
/* 5、等待应答信号 */
iic_wait_ack();
/* 6、发送写入数据 */
iic_send_byte(data);
/* 7、等待应答信号 */
iic_wait_ack();
/* 8、发送停止信号 */
iic_stop();
/* 等待EEPROM写入完成 */
delay_ms(10);
}
uint8_t at24c02_read_one_byte(uint8_t addr)
{
uint8_t rec = 0;
/* 1、发送起始信号 */
iic_start();
/* 2、发送通讯地址(写操作地址) */
iic_send_byte(0xA0);
/* 3、等待应答信号 */
iic_wait_ack();
/* 4、发送内存地址 */
iic_send_byte(addr);
/* 5、等待应答信号 */
iic_wait_ack();
/* 6、发送起始信号 */
iic_start();
/* 7、发送通讯地址(读操作地址) */
iic_send_byte(0xA1);
/* 8、等待应答信号 */
iic_wait_ack();
/* 9、等待接收数据 */
rec = iic_read_byte(0);
/* 10、发送非应答(获取该地址即可) */
/* 11、发送停止信号 */
iic_stop();
return rec;
}
24cxx.h
#ifndef __24CXX_H
#define __24CXX_H
#include "./SYSTEM/sys/sys.h"
void at24c02_init(void);
void at24c02_write_one_byte(uint8_t addr, uint8_t data);
uint8_t at24c02_read_one_byte(uint8_t addr);
#endif
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/24CXX/24cxx.h"
int main(void)
{
uint8_t key;
uint8_t i = 0;
uint8_t data = 0;
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
key_init(); /* 初始化按键 */
at24c02_init();
while (1)
{
key = key_scan(0);
if (key == KEY1_PRES)
{
at24c02_write_one_byte(100, 66);
printf("write data \r\n");
}
if (key == KEY0_PRES)
{
data = at24c02_read_one_byte(100);
printf("read data:%d \r\n", data);
}
i++;
if (i % 20 == 0)
{
LED0_TOGGLE(); /* 红灯闪烁 */
i = 0;
}
delay_ms(10);
}
}