STM32基础---IIC通信(以AT24C02为例)
时间:2023-09-08 21:07:02
一,概念
I2C(IIC, Inter-Integrated Circuit,集成电路总线)总线是由Philips公司开发的一种简单的双向二线系统同步串行总线。它只需要两根线就可以在连接到总线的设备之间传输信息。他是同步半双工通信
主要设备用于启动总线传输数据,并生成开启传输时钟的设备,此时,任何被搜索的设备都被视为从设备。
(二)传输方向
在总线上,主与从、发、收的关系不是恒定的,而是取决于此时数据传输的方向。
如果主机想向设备发送数据,主机首先从设备找到地址,然后主动向设备发送数据,最后由主机终止数据传输。
如果主机想从设备接收数据,首先,主机从设备中找到位置。然后主机接收从设备中发送的数据,最后主机终止接收过程。在这种情况下,主机负责生成定时钟和终止数据传输。
二,信过程
当时钟线低电时,数据线翻转数据
I2C通信,有几个信号
1)初始信号(条件):通知从机准备通信。 //时钟线高,数据线高到低
2)响应信号:有响应和无响应。有响应。SDA对于低电平,没有回应SDA为高电平。
3)停止信号(条件):告诉从机通信已经结束。 //时钟线高,数据线低变高
如图所示(AT24C以02写入数据时序图为例:主机首先发送起始信号,然后发送搜索地址(搜索地址由制造商和开发商共同决定,设备地址为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); // 三、初始化电平 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因为中断有可能会打断时序导致死机。