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

一,概念

I2C(IIC, Inter-Integrated Circuit,集成电路总线)总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。他是同步半双工通信

主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件。

(二)传输方向

在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。

如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送。

如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下,主机负责产生定时时钟和终止数据传送。

二,信过程

在时钟线低电平时,数据线进行数据翻转

I2C通信,存在几种信号

1)起始信号(条件):通知从机做好通信的准备。  //时钟线为高,数据线有高到低

2)应答信号:有应答和无应答。有应答是SDA为低电平,无应答是SDA为高电平。

3)停止信号(条件):告诉从机通信已经结束。  //时钟线为高,数据线有低变高

STM32基础---IIC通信(以AT24C02为例)_第1张图片

连续通信过程如图(AT24C02写入数据时序图为例)所示:主机先发送起始信号,然后发送寻址地址(寻址地址是由生产厂家和开发者共同决定的,本例子的设备地址是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);
	
	// 3.初始化电平
	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).

STM32基础---IIC通信(以AT24C02为例)_第2张图片

// 连续读取数据(从地址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因为中断有可能会打断时序导致死机。

你可能感兴趣的:(嵌入式/STM32,c语言)