锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

STM32基础---IIC通信(以AT24C02为例)

时间:2023-09-08 21:07:02 2402集成电路

一,概念

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因为中断有可能会打断时序导致死机。

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章