stm32iic和at24c02

本文详细介绍了STM32通过I2C协议与AT24C02EEPROM进行通信的过程,包括I2C的基本概念、起始和停止信号、数据有效性、应答信号以及AT24C02的引脚、地址和读写时序。同时还提供了STM32端的I2C初始化和数据传输的C语言函数示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

STM32 第十天

  • IIC

1、IIC概述

I2C(IIC,Inter-Integrated Circuit),两线式串行总线,由PHILIPS公司开发用于连接微控制器及其外围设备。

它是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,高速IIC总线一般可达400kbps。

IIC是半双工通信方式,可实现一对多。

 

SDA:数据线,用于传输数据;可主机到从机,也可以从从机到主机。

SCL:时钟线,只能由主机发送,用于数据同步,一个脉冲发送/接收一位数据。

 

2、I2C通信原理-I2C协议

  1. 空闲状态
  2. 开始信号
  3. 停止信号
  4. 应答信号
  5. 数据的有效性

空闲状态

I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。

 

 

起始信号(主机发出,起始信号是针对所有的从机)

起始信号:当SCL为高期间,SDA由高到低的跳变;启动信号是一种电平跳变时序(上升沿或者下降沿)信号,而不是一个电平信号。

 

注意看启动信号的时序是由左到右进行查看。

//启动信号

void Iic_Start(void)

{

//设置数据线为输出模式

Iic_Sda_Mode(GPIO_Mode_OUT);

SCL = 1;

SDA_OUT = 1;

delay_us(5);

SDA_OUT = 0;

delay_us(5);

SCL = 0;

}

停止信号(主机发出)

停止信号:当SCL为高期间,SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。

 

注意看停止信号的时序是由左到右进行查看。

//停止信号

void Iic_Stop(void)

{

//设置数据线为输出模式

Iic_Sda_Mode(GPIO_Mode_OUT);

SCL = 0;

SDA_OUT = 0;

delay_us(5);

SCL = 1;

delay_us(5);

SDA_OUT = 1;

}

数据有效性

I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。

即:数据在SCL的上升沿到来之前就需准备好。并在在下降沿到来之前必须稳定。

 

注意:在主机接受数据时,使用是的上面相同的时序,只不过SCL由主机发出,SDA由从机发出,那么主机要做为输入,进行接受数据。同样的道理,在SCL为低电平区间,从机在这区间可改变数据,在SCL为高电平区间保持数据的稳定。所以主机在SCL为高电平区间判断引脚的电平从而得到对应的数据。

应答信号ACK

     发送器(STM32或者是从机)每发送一个字节,就在时钟脉冲9(前面8个脉冲周期用来发8位)期间释放数据线,由接收器反馈一个应答信号(反馈:发送一位数据,做为应答)。 应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。 

对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。 如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P。

结论:

  1. 每发送(主机与从机都适用)一个字节,则会(主机与从机都适用)接受一个应答信号(信号其实就是一位数据);每接收(主机与从机都适用)一个字节,则会(主机与从机都适用)发送一位应答信号(信号其实就是一位数据);
  2. 规定低电平为有效应答,表示成功接受字节;规定高电平为无效应答,表示没有成功接受字节。
  3. 假设主机接受数据,如果不想再接受,则发送一个无效应答(非应答)来通知从机不要再进行传输数据。

  • AT24C02

 

1、 24C02简介

  1. 24C02是一个2K位串行CMOS 的EEPROM,内部含有256个8位字节(大小:256字节)。
  2. 与 400KHz I2C 总线兼容
  3. 1.8 到 6.0 伏工作电压范围
  4. 低功耗 CMOS 技术
  5. 写保护功能 当 WP 为高电平时进入写保护状态
  6. 页写缓冲器
  7. 自定时擦写周期
  8. 1,000,000 编程/擦除周期
  9. 可保存数据 100 年
  10. 8 脚 DIP SOIC 或 TSSOP 封装
  11. 温度范围 商业级 工业级和汽车级

2、AT24C02引脚

 

3、 AT24C02地址查找

 

  1. LSB的意思是:全称为Least Significant Bit,在二进制数中意为最低有效位,一般来说,MSB位于二进制数的最左侧,LSB位于二进制数的最右侧。

2、MSB的意思是:全称为Most Significant Bit,在二进制数中属于最高有效位,MSB是最高加权位,与十进制数字中最左边的一位类似。

4、 AT24CO2读写时序

写时序

 

读时序

 iic

#include "iic.h"

/****************************************
引脚说明
SCL -- PB8
SDA -- PB9

*****************************************/

void Iic_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStruct;
	
	//打开GPIOB组时钟 
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);	

	
	GPIO_InitStruct.GPIO_Pin	= GPIO_Pin_9|GPIO_Pin_8;		//引脚8
	GPIO_InitStruct.GPIO_Mode	= GPIO_Mode_OUT;				//输出模式
	GPIO_InitStruct.GPIO_OType	= GPIO_OType_PP;				//推挽输出
	GPIO_InitStruct.GPIO_PuPd	= GPIO_PuPd_UP;					//上拉
	GPIO_InitStruct.GPIO_Speed	= GPIO_Speed_50MHz;				//速度
	
	GPIO_Init(GPIOB, &GPIO_InitStruct);	
	
	//空闲状态
	SCL = 1;
	SDA_OUT = 1;
}

//引脚模式变更--SDA
void Iic_Sda_Mode(GPIOMode_TypeDef mode)
{
	GPIO_InitTypeDef  	GPIO_InitStructure;
	
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;				//第9号引脚
	GPIO_InitStructure.GPIO_Mode  = mode;					//输入/输出模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;			//推挽输出,增强驱动能力,引脚的输出电流更大
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		//引脚的速度最大为100MHz
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;			//没有使用内部上拉电阻
	GPIO_Init(GPIOB, &GPIO_InitStructure);	
}

//启动信号
void Iic_Start(void)
{
	//设置数据线为输出模式 
	Iic_Sda_Mode(GPIO_Mode_OUT);
	
	
	SCL = 1;
	SDA_OUT = 1;
	
	delay_us(5);
	SDA_OUT = 0;
	delay_us(5);
	SCL = 0;
}


//停止信号
void Iic_Stop(void)
{
	//设置数据线为输出模式 
	Iic_Sda_Mode(GPIO_Mode_OUT);
	
	SCL = 0;
	SDA_OUT = 0;
	delay_us(5);
	
	SCL = 1;
	delay_us(5);
	SDA_OUT = 1;
	
}

//发送一位数据 发1引脚置1 发0引脚置0
void Iic_Send_Ack(u8 ack)
{
	//设置数据线为输出模式 
	Iic_Sda_Mode(GPIO_Mode_OUT);

	
	//一个脉冲周期
	SCL = 0;
	
	//时钟为低电平时准备数据
	if(ack == 1)
	{
		SDA_OUT = 1;
	}
	else
	{
		SDA_OUT = 0;
	}	
	
	delay_us(5);
	SCL = 1;
	delay_us(5);
	SCL = 0;

}

//发送一个字节 先发高位
void Iic_Send_Byte(u8 data)
{
	u8 i;
	
	//设置数据线为输出模式 
	Iic_Sda_Mode(GPIO_Mode_OUT);
	
	SCL = 0;
	for(i=0; i<8; i++)
	{
		//时钟为低电平时准备数据
		if(data & (0x01<<(7-i)))
		{
			SDA_OUT = 1;
		}
		else
		{
			SDA_OUT = 0;
		}	
		
		delay_us(5);
		SCL = 1;
		delay_us(5);
		SCL = 0;	
	}

}
//接受一位数据
u8 Iic_Recv_Ack(void)
{
	u8 ack = 0;
	
	//设置数据线为输出模式 
	Iic_Sda_Mode(GPIO_Mode_IN);

	
	//一个脉冲周期
	SCL = 0;
	delay_us(5);
	SCL = 1;
	delay_us(5);
	
	//时钟为低电平时准备数据
	if(SDA_IN == 1)
	{
		ack = 1;
	}
	else
	{
		ack = 0;
	}	
	
	SCL = 0;

	return ack;
}


//接受一个字节数据
u8 Iic_Recv_Byte(void)
{
	u8 i;
	u8 data = 0x00; //0x00
	
	//设置数据线为输出模式 
	Iic_Sda_Mode(GPIO_Mode_IN);

	SCL = 0;
	for(i=0; i<8; i++)
	{

		delay_us(5);
		SCL = 1;
		
		delay_us(5);	
		//时钟为低电平时准备数据
		if(SDA_IN == 1)
		{
			data |= 0x01<<(7-i);
		}
	
		SCL = 0;
	}
		
	return data;
}
#ifndef __IIC_H
#define __IIC_H

#include "stm32f4xx.h"
#include "sys.h"
#include "delay.h"
/*******************************
引脚说明
SCL -- PB8
SDA -- PB9
********************************/


#define SCL  	PBout(8)
#define SDA_IN	PBin(9)
#define SDA_OUT	PBout(9)

void Iic_Init(void);

#endif

iic_at24c02

#include "iic.h"

/****************************************
引脚说明
SCL -- PB8
SDA -- PB9

*****************************************/

void Iic_Init(void)
{
	GPIO_InitTypeDef  GPIO_InitStruct;
	
	//打开GPIOB组时钟 
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);	

	
	GPIO_InitStruct.GPIO_Pin	= GPIO_Pin_9|GPIO_Pin_8;		//引脚8
	GPIO_InitStruct.GPIO_Mode	= GPIO_Mode_OUT;				//输出模式
	GPIO_InitStruct.GPIO_OType	= GPIO_OType_PP;				//推挽输出
	GPIO_InitStruct.GPIO_PuPd	= GPIO_PuPd_UP;					//上拉
	GPIO_InitStruct.GPIO_Speed	= GPIO_Speed_50MHz;				//速度
	
	GPIO_Init(GPIOB, &GPIO_InitStruct);	
	
	//空闲状态
	SCL = 1;
	SDA_OUT = 1;
}

//引脚模式变更--SDA
void Iic_Sda_Mode(GPIOMode_TypeDef mode)
{
	GPIO_InitTypeDef  	GPIO_InitStructure;
	
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;				//第9号引脚
	GPIO_InitStructure.GPIO_Mode  = mode;					//输入/输出模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;			//推挽输出,增强驱动能力,引脚的输出电流更大
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		//引脚的速度最大为100MHz
	GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;			//没有使用内部上拉电阻
	GPIO_Init(GPIOB, &GPIO_InitStructure);	
}

//启动信号
void Iic_Start(void)
{
	//设置数据线为输出模式 
	Iic_Sda_Mode(GPIO_Mode_OUT);
	
	
	SCL = 1;
	SDA_OUT = 1;
	
	delay_us(5);
	SDA_OUT = 0;
	delay_us(5);
	SCL = 0;
}


//停止信号
void Iic_Stop(void)
{
	//设置数据线为输出模式 
	Iic_Sda_Mode(GPIO_Mode_OUT);
	
	SCL = 0;
	SDA_OUT = 0;
	delay_us(5);
	
	SCL = 1;
	delay_us(5);
	SDA_OUT = 1;
	
}

//发送一位数据 发1引脚置1 发0引脚置0
void Iic_Send_Ack(u8 ack)
{
	//设置数据线为输出模式 
	Iic_Sda_Mode(GPIO_Mode_OUT);

	
	//一个脉冲周期
	SCL = 0;
	
	//时钟为低电平时准备数据
	if(ack == 1)
	{
		SDA_OUT = 1;
	}
	else
	{
		SDA_OUT = 0;
	}	
	
	delay_us(5);
	SCL = 1;
	delay_us(5);
	SCL = 0;

}

//发送一个字节 先发高位
void Iic_Send_Byte(u8 data)
{
	u8 i;
	
	//设置数据线为输出模式 
	Iic_Sda_Mode(GPIO_Mode_OUT);
	
	SCL = 0;
	for(i=0; i<8; i++)
	{
		//时钟为低电平时准备数据
		if(data & (0x01<<(7-i)))
		{
			SDA_OUT = 1;
		}
		else
		{
			SDA_OUT = 0;
		}	
		
		delay_us(5);
		SCL = 1;
		delay_us(5);
		SCL = 0;	
	}

}
//接受一位数据
u8 Iic_Recv_Ack(void)
{
	u8 ack = 0;
	
	//设置数据线为输出模式 
	Iic_Sda_Mode(GPIO_Mode_IN);

	
	//一个脉冲周期
	SCL = 0;
	delay_us(5);
	SCL = 1;
	delay_us(5);
	
	//时钟为低电平时准备数据
	if(SDA_IN == 1)
	{
		ack = 1;
	}
	else
	{
		ack = 0;
	}	
	
	SCL = 0;

	return ack;
}


//接受一个字节数据
u8 Iic_Recv_Byte(void)
{
	u8 i;
	u8 data = 0x00; //0x00
	
	//设置数据线为输出模式 
	Iic_Sda_Mode(GPIO_Mode_IN);

	SCL = 0;
	for(i=0; i<8; i++)
	{

		delay_us(5);
		SCL = 1;
		
		delay_us(5);	
		//时钟为低电平时准备数据
		if(SDA_IN == 1)
		{
			data |= 0x01<<(7-i);
		}
	
		SCL = 0;
	}
		
	return data;
}




//u8 addr:写在AT24C02时的起始地址
void AT24c02_Write(u8 addr, u8 *data, u8 len)
{
	u8 ack = 0;
	
	//启动信号
	Iic_Start();
	
	//发送从机设备地址,并执行写操作
	Iic_Send_Byte(0xA0);
	ack = Iic_Recv_Ack();
	if(ack == 1)
	{
		printf("ack failure\r\n");
		Iic_Stop();
		return;
	}
	
	//发要在AT24C02内写数据的起始地址
	Iic_Send_Byte(addr);
	ack = Iic_Recv_Ack();
	if(ack == 1)
	{
		printf("ack failure\r\n");
		Iic_Stop();
		return;
	}
		
	while(len--)
	{
		//写数据
		Iic_Send_Byte(*data);
		ack = Iic_Recv_Ack();
		if(ack == 1)
		{
			printf("ack failure\r\n");
			Iic_Stop();
			return;
		}		
		//地址指向下一个数据空间
		data++;
		
	}
	
	Iic_Stop();
	printf("write finish\r\n");

}

void AT24c02_Read(u8 addr, u8 *data, u8 len)
{
	u8 ack = 0;
	
	//启动信号
	Iic_Start();
	
	//发送从机设备地址,并执行写操作
	Iic_Send_Byte(0xA0);
	ack = Iic_Recv_Ack();
	if(ack == 1)
	{
		printf("ack failure\r\n");
		Iic_Stop();
		return;
	}	
	
	//发要在AT24C02内读数据的起始地址
	Iic_Send_Byte(addr);
	ack = Iic_Recv_Ack();
	if(ack == 1)
	{
		printf("ack failure\r\n");
		Iic_Stop();
		return;
	}	

	//启动信号
	Iic_Start();

	//发送从机设备地址,并执行读操作
	Iic_Send_Byte(0xA1);
	ack = Iic_Recv_Ack();
	if(ack == 1)
	{
		printf("ack failure\r\n");
		Iic_Stop();
		return;
	}

	while(len--) 
	{
		*data = Iic_Recv_Byte();
		if(len > 0)
			Iic_Send_Ack(0);
		
		//地址指向下一个数据空间
		data++;
	}
	
	Iic_Send_Ack(1);
	Iic_Stop();

}


//u8 addr:页起始地址
void At24c02_Data(u8 addr, u8 *write_buff, u8 len)
{
	u8 i, page, data; 
	
	//计算有多少页及剩余多少个字节
	page = len/8;
	data = len%8;
	
	for(i=0; i<page; i++)
	{
		AT24c02_Write(addr, write_buff, 8);
		write_buff+=8;	//取一个8个字节
		addr+=8;		//下一页
		delay_ms(10);
	}
	
	AT24c02_Write(addr, write_buff, data);

}
#ifndef __IIC_H
#define __IIC_H

#include "stm32f4xx.h"
#include "sys.h"
#include "delay.h"
/*******************************
引脚说明
SCL -- PB8
SDA -- PB9
********************************/


#define SCL  	PBout(8)
#define SDA_IN	PBin(9)
#define SDA_OUT	PBout(9)

void Iic_Init(void);
void AT24c02_Write(u8 addr, u8 *data, u8 len);
void AT24c02_Read(u8 addr, u8 *data, u8 len);
void At24c02_Data(u8 addr, u8 *write_buff, u8 len);

#endif
#include "stm32f4xx.h"
#include "led.h"
#include "key.h"
#include "exti.h"
#include "delay.h"
#include "tim.h"
#include "pwm.h"
#include "usart.h"
#include "sys.h"
#include "dht11.h"
#include "infrared.h"
#include "iwdg.h"
#include "iic.h"


u8  buffer[64] = {0};
u8  rx_buffer[64] = {0};
u8  count = 0, rx_i = 0;
u8  rx_flag = 0; //接受标志位,rx_flag = 表示数据帧完毕


void USART1_IRQHandler(void)
{
	


	//判断接收标志位是否为1
	if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
		//清空接受标志位
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);	
		//接受数据 
		buffer[count++] = USART_ReceiveData(USART1);
		//判断数据是否为':',如果是':'数据帧结束
		if(buffer[count-1] == ':')
		{
			//数据赋值到rx_buffer,并过滤帧尾
			for(rx_i=0; rx_i<(count-1); rx_i++)
			{
				rx_buffer[rx_i] = buffer[rx_i];
			}
		
			//清空数组
			memset(buffer, 0, sizeof(buffer));
			
			//标志位置1
			rx_flag = 1;
			//下一帧数据从buffer[0]开始接受
			count = 0; 
					
		
		}
		
	}
}




void USART2_IRQHandler(void)
{
	
	u8 data;

	//判断接收标志位是否为1
	if(USART_GetITStatus(USART2, USART_IT_RXNE) == SET)
	{
		//清空接受标志位
		USART_ClearITPendingBit(USART2, USART_IT_RXNE);	
		//接受数据 
		buffer[count++] = USART_ReceiveData(USART2);
		//判断数据是否为':',如果是':'数据帧结束
		if(buffer[count-1] == ':')
		{
			//数据赋值到rx_buffer,并过滤帧尾
			for(rx_i=0; rx_i<(count-1); rx_i++)
			{
				rx_buffer[rx_i] = buffer[rx_i];
			}
		
			//清空数组
			memset(buffer, 0, sizeof(buffer));
			
			//标志位置1
			rx_flag = 1;
			//下一帧数据从buffer[0]开始接受
			count = 0; 
					
		}
		
	}


}


void USART3_IRQHandler(void)
{
	
	u8 data;

	//判断接收标志位是否为1
	if(USART_GetITStatus(USART3, USART_IT_RXNE) == SET)
	{
		//清空接受标志位
		USART_ClearITPendingBit(USART3, USART_IT_RXNE);	
		//接受数据 
		buffer[count++] = USART_ReceiveData(USART3);
		//判断数据是否为':',如果是':'数据帧结束
		if(buffer[count-1] == ':')
		{
			//数据赋值到rx_buffer,并过滤帧尾
			for(rx_i=0; rx_i<(count-1); rx_i++)
			{
				rx_buffer[rx_i] = buffer[rx_i];
			}
		
			//清空数组
			memset(buffer, 0, sizeof(buffer));
			
			//标志位置1
			rx_flag = 1;
			//下一帧数据从buffer[0]开始接受
			count = 0; 
					
		}
		
	}


}




int main(void)
{

	u8 write_buff[13] = "helloworld";
	u8 read_buff[13] = {0};
	//设置NVIC分组(一个工程只能设置一个分组) 
	//第二分组;抢占优先组取值范围:0~3 响应先组取值范围:0~3
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	Led_Init();
	Delay_Init();
	Usart1_Init(115200);

	Iic_Init();
	
	//AT24c02_Write(0x00, write_buff, 8);
	At24c02_Data(0x00, write_buff, 10);
	delay_ms(50);
	AT24c02_Read(0x00, read_buff, 10);
	
	printf("read_buff:%s\r\n", read_buff);
	
	
	
	while(1)
	{
		

		//这里的循环相当应用程序的时间
		delay_s(4);


	}
}

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hqb_newfarmer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值