STM32 I2C

I2C也叫IIC,集成线路总线。由菲利普设计,主要用来连接整体线路,是一种多向控制总线。

I2C传输速率:100kbps~1Mbps
标准速率:100kbps,400kbps,1Mbps。

I2C串行总线一般有两根信号线,一个双向数据线SDA,另一根时钟线SCL。所有接到 I2C 总线的设备,SDA都连接到总线SDA上,SCL都连接到SCL上。

总线运行由主机(发出启动信号,发出时钟信号的设备,通常是微处理器)控制,被主机寻访的设备称为从机。

每个连接到I2C总线的从机都有一个唯一的地址(7bit地址),主机为寻找通信目标,所以有一个寻址的动作。

可以主机发送数据,也可以从机发送数据。凡是发数据到主机的叫发送器,接收主机数据的叫接收器。

类似手机连接到路由器,每个手机都有独立的IP地址,路由器要与指定手机通信,根据IP地址来分辨,手机和路由器可以互相收发数据。

从机地址:7位地址加一位读写控制位(1读,0写)。主要由芯片手册(地址输入引脚)、硬件设计决定。7位地址:由固定地址+硬件设计(查看开发板原理图,A0A1A2对应的引脚电平)

查看时序图,看图编程。
主机:起始信号触发通信开始 从机:
寻设备地址
等应答 应答
发送数据存储地址
等应答 应答
发送数据
等应答 应答
继续起始信号/停止信号

代码示例(PB8,PB9)

/*以下代码参考前面STM32的文章,主要实现延时和串口通信*/
#include "stm32f4xx.h"
#include "sys.h"
#include 
#include 

static GPIO_InitTypeDef  	GPIO_InitStructure;		//GPIO初始化的结构体

static NVIC_InitTypeDef   	NVIC_InitStructure;		//中断优先级初始化的结构体

static USART_InitTypeDef 	USART_InitStructure;	//串口初始化的结构体

void delay_us(uint32_t n)
{
	while(n--)
	{
		SysTick->CTRL =0;						//关闭系统定时器
		
		SysTick->LOAD = (21);					//设置定时时间为1us
		
		SysTick->VAL  = 0;						//清空标志位
		
		SysTick->CTRL = 1;   					//使能系统定时器工作,开始计数,同时使用的时钟源为系统时钟(21MHz)
		
		while((SysTick->CTRL & 0x00010000)==0);	//等待系统定时器计数完毕
		
		SysTick->CTRL =0;						//关闭系统定时器		
	}
}


void delay_ms(uint32_t n)
{
	while(n--)
	{
		SysTick->CTRL =0;						//关闭系统定时器
			
		SysTick->LOAD = (21000);				//设置定时时间为1ms
		
		SysTick->VAL  = 0;						//清空标志位
		
		SysTick->CTRL = 1;   					//使能系统定时器工作,开始计数,同时使用的时钟源为系统时钟(21MHz)
		
		while((SysTick->CTRL & 0x00010000)==0);	//等待系统定时器计数完毕
		
		SysTick->CTRL =0;						//关闭系统定时器		
	}

}

int fputc(int ch,FILE *f)
{
	USART_SendData(USART1,ch);

	//检测当前的串口是否发送数据完成
	while(RESET==USART_GetFlagStatus(USART1,USART_FLAG_TXE));
	
	return ch;

}

void usart1_init(uint32_t baud)
{

	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
	
	//使能串口1的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);


	/* 多功能引脚的配置,将PA9和PA10连接到串口1 */
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1);
	
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9|GPIO_Pin_10;	//第9 10号引脚
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;			//多功能模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;			//推挽输出,增强驱动能力,引脚的输出电流更大
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;		//引脚的速度最大为100MHz
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;		//没有使用内部上拉电阻
	GPIO_Init(GPIOA, &GPIO_InitStructure);	
		
	USART_InitStructure.USART_BaudRate = baud;										//波特率的配置
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;						//8位的数据位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;							//1个停止位
	USART_InitStructure.USART_Parity = USART_Parity_No;								//无校验
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;	//无流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;					//支持数据的发送与接收
	USART_Init(USART1, &USART_InitStructure);
	
	
	/* 使能串口1中断并且设置其优先级 */
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	
	/* 使能串口1工作 */
	USART_Cmd(USART1, ENABLE);
	
	//设置串口接收到数据后,就触发中断
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

}

/*****************开始I2C的配置**************/
//一、引脚配置,宏定义引脚名,方便使用
#define SCL_W PBout(8);		//宏定义时钟线
#define SDA_W PBout(9);		//宏定义数据线,写权限
#define SDA_R PBin(9);		//宏定义数据线,读权限

void at24c02_Init(void)
{
	//1、使能时钟,相当于打开对应端口的开关,让其开始工作
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);

	//2、给GPIO初始化结构体内成员赋值,并使配置生效
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9;	//第8 9号引脚,相同端口引脚可以使用‘|’一起初始化
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;			//输出模式,另外有输入模式,多功能模式,模拟模式
	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;			//推挽输出,增强驱动能力,引脚的输出电流更大,另外有开漏输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;		//引脚的速度最大为100MHz,根据实际调节
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;		//没有使用内部上拉电阻
	GPIO_Init(GPIOB, &GPIO_InitStructure);					//使初始化好的结构体生效

	//3、设定时钟线和数据线的初始状态
	SCL_W = 1;		//根据芯片时序图,设定时钟线初始状态为高电平
	SDA_W = 1;		//根据芯片时序图,设定数据线初始状态为高电平
}

/*引脚输入输出模式的切换,需要重新配置引脚。SDA引脚发送数据时为输出模式,等待应答信号时为输入模式,时钟引脚不变*/
void i2c_sda_mode(GPIOMode_TypeDef mode)
{

	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_100MHz;		//引脚的速度最大为100MHz
	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;		//没有使用内部上拉电阻
	GPIO_Init(GPIOB, &GPIO_InitStructure);	
}


/**************以下为数据通信功能***********/
/*起始信号,根据时序图编写*/
void i2c_start(void)
{
	//保证SDA引脚为输出模式
	i2c_sda_mode(GPIO_Mode_OUT);

	SCL_W=1;
	SDA_W=1;
	
	//查看芯片手册,得到芯片的最大运行频率
	//延时5us(一般100KHz通信,10us就1Hz,一个时钟周期由一半高电平,一半低电平组成,所以高电平为5us)
	delay_us(5);
	
	SDA_W=0;
	
	//延时5us
	delay_us(5);

	//拉低时钟线,占用时钟线,告诉其他从机已经被占用了(可以查看http://blog.sina.com.cn/s/blog_6582c5f30102v9ic.html)
	SCL_W=0;
	
	//延时5us
	delay_us(5);	
}


/*结束信号*/
void i2c_stop(void)
{
	//保证SDA引脚为输出模式
	i2c_sda_mode(GPIO_Mode_OUT);

	SCL_W=0;
	SDA_W=0;
	
	//延时5us
	delay_us(5);
	
	SCL_W=1;

	//延时5us
	delay_us(5);

	SDA_W=1;

	//延时5us
	delay_us(5);	
}


/*发送1个字节,一个字节有8位的数据*/
void i2c_send_byte(uint8_t byte)
{
	uint32_t i=0;
	//保证SDA引脚为输出模式
	i2c_sda_mode(GPIO_Mode_OUT);

	SCL_W=0;
	SDA_W=0;
	
	//延时5us
	delay_us(5);

	for(i=0; i<8; i++)
	{
	
		//MSB,最高有效位优先数据发送
		if(byte & (1<<(7-i)))
			SDA_W=1;
		else
			SDA_W=0;
		
		//延时5us
		delay_us(5);
	
		//设置时钟线为高电平,这个时候,从机会读取该电平的状态
		SCL_W=1;
		delay_us(5);
		
	
		//设置时钟线为低电平,这个时候,从机不需要读取该电平的状态,主机要更改该引脚电平
		SCL_W=0;
		delay_us(5);		
	}
}

/*等待从机应答*/
uint8_t i2c_wait_ack(void)
{
	uint8_t ack=0;
	
	//保证SDA引脚为输入模式
	i2c_sda_mode(GPIO_Mode_IN);

	SCL_W=1;
	
	//延时5us
	delay_us(5);
	
	//判断SDA引脚的电平
	if(SDA_R)
		ack=1;
	else
		ack=0;
	
	SCL_W=0;
	
	//延时5us
	delay_us(5);

	return ack;
}

/*写数据*/
int32_t at24c02_write(uint8_t addr,uint8_t *pbuf,uint32_t len)
{
	uint8_t ack=0;
	
	//发送起始信号
	i2c_start();
	
	//发送设备地址,进行寻址,前7位为A,最后一位为写访问操作
	i2c_send_byte(0xA0);
	
	//等待从机应答
	ack=i2c_wait_ack();
	
	if(ack)
	{
		printf("i2c ack device address fail\r\n");
		
		return -1;
	}

	printf("i2c ack device address ok\r\n");


	//发送数据存储的地址,写访问操作
	i2c_send_byte(addr);
	
	//等待从机应答
	ack=i2c_wait_ack();
	
	if(ack)
	{
		printf("i2c ack word address fail\r\n");
		
		return -2;
	}

	printf("i2c ack word address ok\r\n");
	
	//发送数据内容
	while(len--)
	{
		
		//发送数据内容,写访问操作
		i2c_send_byte(*pbuf++);
		
		//等待从机应答
		ack=i2c_wait_ack();
		
		if(ack)
		{
			printf("i2c ack data fail\r\n");
			
			return -3;
		}
	}
	
	printf("i2c write data ok\r\n");
	
	//发送停止信号,结束这个I2C的通信
	i2c_stop();
	
	return 0;
}

/*读数据*/
int32_t at24c02_read(uint8_t addr,uint8_t *pbuf,uint32_t len)		//设备地址(读写权限),内容,长度
{
	uint8_t ack=0;
	
	//发送起始信号
	i2c_start();
	
	//发送设备地址,进行寻址,写访问操作
	i2c_send_byte(0xA0);
	
	//等待从机应答
	ack=i2c_wait_ack();
	
	if(ack)
	{
		printf("i2c ack device address fail \r\n");
		
		return -1;
	}

	printf("i2c ack device address  1 ok \r\n");
	
	

	//发送数据存储的地址,写访问操作
	i2c_send_byte(addr);
	
	//等待从机应答
	ack=i2c_wait_ack();
	
	if(ack)
	{
		printf("i2c ack word address fail\r\n");
		
		return -2;
	}

	printf("i2c ack word address ok\r\n");
	
	
	//发送起始信号
	i2c_start();
	
	//发送设备地址,进行寻址,读访问操作
	i2c_send_byte(0xA1);
	
	//等待从机应答
	ack=i2c_wait_ack();
	
	if(ack)
	{
		printf("i2c ack device address fail \r\n");
		
		return -3;
	}

	printf("i2c ack device address  1 ok \r\n");	
	
	
	//读取数据内容
	len=len-1;
	while(len--)
	{
		
		//接收数据内容
		*pbuf++=i2c_recv_byte();
		
		//向从机发送应答信号
		i2c_ack(0);
	}
	*pbuf = i2c_recv_byte();
	
	//向从机发送无应答信号
	i2c_ack(1);	
	
	//printf("i2c read data ok\r\n");
	
	//发送停止信号,结束这个I2C的通信
	i2c_stop();
	
	return 0;
}

/*主函数,逻辑实现*/
int main(void)
{
	uint8_t buf[32]={0};
	uint8_t i=0;

	//配置中断优先级分组选择第二组
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	//串口1初始化波特率为115200bps,参考(https://mp.csdn.net/postedit/86110869)
	usart1_init(115200);
	
	//at24c02初始化
	at24c02_init();
	
	
	//向0地址发送8个字节,全都是0xAC
	memset(buf,0xAC,8);
	
	at24c02_write(0,buf,8);
	
	printf("at24c02 write data all is 0xAC\r\n");
	
	//24c02这个芯片,写完之后必须要一定的延时,至少2ms左右,完成从机内部的数据刷新,才能进行其他操作
	delay_ms(2);
	
	memset(buf,0,8);
	at24c02_read(0,buf,16);
	
	printf("at24c02 read data at addr 0:");
	
	for(i=0; i<16; i++)
	{
		printf("%02X ",buf[i]);
	
	}
	printf("\r\n");
	
	while(1)
	{

	}

}

void USART1_IRQHandler(void)
{
	uint8_t d=0;
	
	//检测是否有接收到数据
	if(USART_GetITStatus(USART1,USART_IT_RXNE)==SET)
	{
		d= USART_ReceiveData(USART1);
				
		
		//清空标志位,告诉CPU,我已经处理完毕,可以接收新的一次中断请求
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
	}
}



你可能感兴趣的:(STM32)