0
点赞
收藏
分享

微信扫一扫

stm32软件模拟I2C

祈澈菇凉 2022-02-27 阅读 73

文章目录


前言

本文基于野火STM32F103VET6开发板,实现软件模拟i2c


一、芯片手册和原理图

由原理图可以知道
I2C的时钟线和数据线在GIOPB端口
在这里插入图片描述

芯片手册
GPIOB挂载在APB2总线上,I2C1挂载在APB1总线上
在这里插入图片描述
将BSRR寄存器某位置1,相当于ODR寄存器的相应位置1
在这里插入图片描述

将BRR寄存器某位置1,相当于ODR寄存器的相应位置0
在这里插入图片描述
读SDA_IO口的数据
在这里插入图片描述

二、编程要点

起始条件和停止条件
在这里插入图片描述
数据的有效性:
在这里插入图片描述
在这里插入图片描述

数据格式:数据8位,高位先行
在这里插入图片描述
应答信号:
在这里插入图片描述

三、代码

只附上有关I2C的代码

代码如下(示例):

bsp_i2c_gpio.c

#include "bsp_i2c_gpio.h"

static void i2c_CfgGpio(void);

static void i2c_Delay(void)
{
	uint8_t i;

	/* 
	 	下面的时间是通过逻辑分析仪测试得到的。
    工作条件:CPU主频72MHz ,MDK编译环境,1级优化
  
		循环次数为10时,SCL频率 = 205KHz 
		循环次数为7时,SCL频率 = 347KHz, SCL高电平时间1.5us,SCL低电平时间2.87us 
	 	循环次数为5时,SCL频率 = 421KHz, SCL高电平时间1.25us,SCL低电平时间2.375us 
	*/
	for (i = 0; i < 10; i++);
}



/* I2C起始信号 */
void i2c_Start(void) {
	EEPROM_I2C_SDA_1();		//一开始SDA是高电平
	EEPROM_I2C_SCL_1();		//一开始SCL是高电平
	i2c_Delay();
	EEPROM_I2C_SDA_0();		//将SDA数据线拉低
	i2c_Delay();
	EEPROM_I2C_SCL_0();		//将SCL时钟线拉低
	i2c_Delay();

	return ;
	
}

/* I2C停止信号 */
void i2c_Stop(void) {
	/* 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 */
	EEPROM_I2C_SDA_0();		//将SDA数据线拉低
	EEPROM_I2C_SCL_1();
	i2c_Delay();
	EEPROM_I2C_SDA_1();		//将SDA数据线拉高

	return ;
}


/* 功能说明: CPU向I2C总线设备发送8bit数据 */
void i2c_SendByte(uint8_t _ucByte) {
	uint8_t i;
	EEPROM_I2C_SCL_0();	
	i2c_Delay();
	/* 先发送字节的高位bit7 */
	for (i = 0; i < 8; i++)
	{	
		if (_ucByte & 0x80) {
			EEPROM_I2C_SDA_1();
		} else {
			EEPROM_I2C_SDA_0();
		}
		_ucByte <<= 1;	/* 左移一个bit */
		
		EEPROM_I2C_SCL_1();
		i2c_Delay();	
		
		EEPROM_I2C_SCL_0();		
		i2c_Delay();	
		
	}
	
	EEPROM_I2C_SDA_1(); // 释放总线	(方便应答信号)
	i2c_Delay();
	
	return;
}



/* 功能说明: CPU从I2C总线设备读取8bit数据 */
uint8_t i2c_ReadByte(void) {
	//数据是串行的
	uint8_t i;
	uint8_t value;
	
	/* 读到第1个bit为数据的bit7 */
	value = 0;
	
	/* SCL不断拉高拉低,拉高读取数据,拉低允许数据变化 */
	for(i = 0; i < 8; i++) {
		value <<= 1;
		EEPROM_I2C_SCL_1();
		i2c_Delay();
		if (EEPROM_I2C_SDA_READ()) {	//如果读到的bit为1,那么就置1
			value++;
		}
		EEPROM_I2C_SCL_0();
		i2c_Delay();
	}
	
	return value;
}

/* 功能说明: CPU产生一个时钟,并读取器件的ACK应答信号 */
uint8_t i2c_WaitAck(void) {
	uint8_t re;
	
	EEPROM_I2C_SDA_1();	/* CPU释放SDA总线 */
	i2c_Delay();
	
	EEPROM_I2C_SCL_1();	/* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
	i2c_Delay();
	
	//读取SDA数据线的第9bit
	if( EEPROM_I2C_SDA_READ() ) {
		re = 1;
	} else {
		re = 0;
	}
	
	EEPROM_I2C_SCL_0();
	i2c_Delay();
	
	return re;
	
}


/* 功能说明: CPU产生一个ACK信号 */
void i2c_Ack(void) {
	EEPROM_I2C_SDA_0();	/* CPU驱动SDA = 0 */
	i2c_Delay();
	EEPROM_I2C_SCL_1();	/* CPU产生1个时钟 */
	i2c_Delay();
	EEPROM_I2C_SCL_0();
	i2c_Delay();
	EEPROM_I2C_SDA_1();	/* CPU释放SDA总线 */
}

/* 功能说明: CPU产生1个NACK信号 */
void i2c_NAck(void) {
	EEPROM_I2C_SDA_1();	/* CPU驱动SDA = 1 */
	i2c_Delay();
	EEPROM_I2C_SCL_1();	/* CPU产生1个时钟 */
	i2c_Delay();
	EEPROM_I2C_SCL_0();
	i2c_Delay();	
}

/* 功能说明: 配置I2C总线的GPIO,采用模拟IO的方式实现 */
static void i2c_CfgGpio(void) {
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(EEPROM_I2C_GPIO_PORT_RCC, ENABLE);	/* 打开GPIOB时钟 */
	
	GPIO_InitStructure.GPIO_Pin = EEPROM_I2C_SCL_PIN | EEPROM_I2C_SDA_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;  	/* 开漏输出 */
	GPIO_Init(EEPROM_I2C_GPIO_PORT, &GPIO_InitStructure);	

	/* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */
	i2c_Stop();
	
	return ;
}

/*
*********************************************************************************************************
*	函 数 名: i2c_CheckDevice
*	功能说明: 检测I2C总线设备,CPU向发送设备地址,然后读取设备应答来判断该设备是否存在
*	形    参:_Address:设备的I2C总线地址
*	返 回 值: 返回值 0 表示正确, 返回1表示未探测到
*********************************************************************************************************
*/
uint8_t i2c_CheckDevice(uint8_t _Address) {
	uint8_t ucAck;
	
	i2c_CfgGpio();		/* 配置GPIO */
	
	i2c_Start();		/* 发送启动信号 */
	
	/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
	i2c_SendByte(_Address | EEPROM_I2C_WR);
	ucAck = i2c_WaitAck();	/* 检测设备的ACK应答 */
	//i2c_Stop();			/* 发送停止信号 */
	
	return ucAck;

}

bsp_i2c_gpio.h

#ifndef __BSP_I2C_GPIO_H__
#define __BSP_I2C_GPIO_H__

#include "stm32f10x.h"

/* i2c挂载在APB1总线上,有I2C1和I2C2。本程序用的是I2C1 */
/* SCL在PB6, SDA在PB7 ,   主机:stm32			从机:EEPROM*/

#define EEPROM_I2C_WR	0		/* 主机 写 数据到从机    控制bit */
#define EEPROM_I2C_RD	1		/* 主机 从 从机中读数据  控制bit */

#define EEPROM_I2C_GPIO_PORT			GPIOB			/* GPIO端口 */
#define EEPROM_I2C_GPIO_PORT_RCC	RCC_APB2Periph_GPIOB			/* GPIO端口时钟 */
#define EEPROM_I2C_SCL_PIN				GPIO_Pin_6			/* 连接到SCL时钟线的GPIO */
#define EEPROM_I2C_SDA_PIN				GPIO_Pin_7			/* 连接到SDA数据线的GPIO */


/* 直接操作寄存器实现IO读写 */
#define EEPROM_I2C_SCL_1()				EEPROM_I2C_GPIO_PORT->BSRR |= EEPROM_I2C_SCL_PIN	/* SCL = 1 */
#define EEPROM_I2C_SCL_0()				EEPROM_I2C_GPIO_PORT->BRR	 |= EEPROM_I2C_SCL_PIN  /* SCL = 0 */

#define EEPROM_I2C_SDA_1()				EEPROM_I2C_GPIO_PORT->BSRR |= EEPROM_I2C_SDA_PIN	/* SDA = 1 */
#define EEPROM_I2C_SDA_0()				EEPROM_I2C_GPIO_PORT->BRR  |= EEPROM_I2C_SDA_PIN	/* SDA = 0 */

#define EEPROM_I2C_SDA_READ()			((EEPROM_I2C_GPIO_PORT->IDR & EEPROM_I2C_SDA_PIN) != 0)


void i2c_Start(void);
void i2c_Stop(void);
void i2c_SendByte(uint8_t _ucByte);
uint8_t i2c_ReadByte(void);
uint8_t i2c_WaitAck(void);
void i2c_Ack(void);
void i2c_NAck(void);
uint8_t i2c_CheckDevice(uint8_t _Address);

#endif /* __BSP_I2C_GPIO_H__ */



bsp_i2c_ee.c

#include "bsp_i2c_ee.h"
#include "bsp_i2c_gpio.h"
#include "bsp_usart.h" 

/*
*********************************************************************************************************
*	函 数 名: ee_CheckOk
*	功能说明: 判断串行EERPOM是否正常
*	形    参:无
*	返 回 值: 1 表示正常, 0 表示不正常
*********************************************************************************************************
*/
uint8_t ee_CheckOk(void)
{
	if (i2c_CheckDevice(EEPROM_DEV_ADDR) == 0) {
		return 1;
	} else {
		/* 失败后,切记发送I2C总线停止信号 */
		i2c_Stop();		
		return 0;
	}
}

/*
*********************************************************************************************************
*	函 数 名: ee_WriteBytes
*	功能说明: 向串行EEPROM指定地址写入若干数据,采用页写操作提高写入效率
*	形    参:_usAddress : 起始地址
*			 			_usSize : 数据长度,单位为字节
*			 			_pWriteBuf : 存放读到的数据的缓冲区指针
*	返 回 值: 1表示成功
*********************************************************************************************************
*/
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize) {
	uint16_t i, m, usAddr;
	
	usAddr = _usAddress;
	for(i = 0; i < _usSize; i++) {
		/* 当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址 */
		if(0 == i || (usAddr & (EEPROM_PAGE_SIZE - 1)) == 0 ) {
			i2c_Stop();
			for(m = 0; m < 1000; m++) {
				i2c_Start();
				i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR);	/* 发送设备地址+写方向 */
				if (i2c_WaitAck() == 0) {
					break;
				}				
			}
			
			if (m  == 1000) {
				goto cmd_fail;	/* EEPROM器件写超时 */
			}			
			
			
			i2c_SendByte((uint8_t)usAddr);
			
			if (i2c_WaitAck() != 0) {
				goto cmd_fail;	/* EEPROM器件无应答 */
			}
		}
		/* 开始写入数据 */
		i2c_SendByte(_pWriteBuf[i]);
		if (i2c_WaitAck() != 0) {
				goto cmd_fail;	/* EEPROM器件无应答 */
		}
		
		usAddr++;	/* 地址增1 */
		
		
	}
	/* 命令执行成功,发送I2C总线停止信号 */
	i2c_Stop();
	return 1;

cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	i2c_Stop();
	return 0;

}

/*
*********************************************************************************************************
*	函 数 名: ee_ReadBytes
*	功能说明: 从串行EEPROM指定地址处开始读取若干数据
*	形    参:_usAddress : 起始地址
*			 _usSize : 数据长度,单位为字节
*			 _pReadBuf : 存放读到的数据的缓冲区指针
*	返 回 值: 0 表示失败,1表示成功
*********************************************************************************************************
*/
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize) {
	uint16_t i;
	i2c_Start();
	i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR);	/* 此处是写指令 */
	if (i2c_WaitAck() != 0) {
		goto cmd_fail;	/* EEPROM器件无应答 */
	}
	
	i2c_SendByte((uint8_t)_usAddress);//发送在哪读的地址
	if (i2c_WaitAck() != 0) {
		goto cmd_fail;	/* EEPROM器件无应答 */
	}
	
	i2c_Start();
	i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_RD);	/* 此处是读指令 */
	if (i2c_WaitAck() != 0) {
		goto cmd_fail;	/* EEPROM器件无应答 */
	}
	
	for (i = 0; i < _usSize; i++) {
		_pReadBuf[i] = i2c_ReadByte();	/* 读1个字节 */
		if(i != _usSize-1 ) {
			i2c_Ack();	//主机发送应答信号
		} else {
			i2c_NAck();	/* 最后1个字节读完后,主机发送非应答信号*/
		}
	}
	/* 发送I2C总线停止信号 */
	i2c_Stop();
	return 1;	
	
cmd_fail: /* 命令执行失败后,切记发送停止信号,避免影响I2C总线上其他设备 */
	/* 发送I2C总线停止信号 */
	i2c_Stop();
	return 0;	
}

void ee_Erase(void)
{
	uint16_t i;
	uint8_t buf[EEPROM_SIZE];
	
	/* 填充缓冲区 */
	for (i = 0; i < EEPROM_SIZE; i++)
	{
		buf[i] = 0xFF;
	}
	
	/* 写EEPROM, 起始地址 = 0,数据长度为 256 */
	if (ee_WriteBytes(buf, 0, EEPROM_SIZE) == 0)
	{
		printf("擦除eeprom出错!\r\n");
		return;
	}
	else
	{
		printf("擦除eeprom成功!\r\n");
	}
}

/*--------------------------------------------------------------------------------------------------*/
static void ee_Delay(__IO uint32_t nCount)	 //简单的延时函数
{
	for(; nCount != 0; nCount--);
}

/*
 * eeprom AT24C02 读写测试
 * 正常返回1,异常返回0
 */
uint8_t ee_Test(void) {
  uint16_t i;
	uint8_t write_buf[EEPROM_SIZE] = {0};
  uint8_t read_buf[EEPROM_SIZE] = {0};
	
	if (ee_CheckOk() == 0) {
		printf("没有检测到串行EEPROM!\r\n");
		return 0;
	}
	/* 填充测试缓冲区 */
	for(i = 0; i < EEPROM_SIZE; i++) {
		write_buf[i] = i;
	}
	
	if( ee_WriteBytes(write_buf, 0, EEPROM_SIZE) == 0 ) {
		printf("写eeprom出错!\r\n");
		return 0;
	} else {
		printf("写eeprom成功!\r\n");
	}
	
  /*写完之后需要适当的延时再去读,不然会出错*/
  ee_Delay(0x0FFFFF);
/*-----------------------------------------------------------------------------------*/
  if (ee_ReadBytes(read_buf, 0, EEPROM_SIZE) == 0) {
		printf("读eeprom出错!\r\n");
		return 0;
	} else {		
		printf("读eeprom成功,数据如下:\r\n");
	}
	
	for(i = 0; i < EEPROM_SIZE; i++) {
		if(read_buf[i] != write_buf[i]) {
			printf("0x%02X ", read_buf[i]);
			printf("错误:EEPROM读出与写入的数据不一致");
			return 0;
		}
		printf(" %02X", read_buf[i]);
		
		if ((i & 15) == 15) {
			printf("\r\n");	
		}
	}
	printf("eeprom读写测试成功\r\n");
	return 1;
}

/*********************************************END OF FILE**********************/

bsp_i2c_gpio.h

#ifndef __BSP_I2C_EE_H__
#define __BSP_I2C_EE_H__

#include "stm32f10x.h"

#define EEPROM_DEV_ADDR			0xA0		/* 24xx02的设备地址:10100000 */
#define EEPROM_PAGE_SIZE		  8			  /* 24xx02的页面大小 */
#define EEPROM_SIZE				  256			  /* 24xx02总容量 */

uint8_t ee_CheckOk(void);
uint8_t ee_ReadBytes(uint8_t *_pReadBuf, uint16_t _usAddress, uint16_t _usSize);
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize);
void ee_Erase(void);
uint8_t ee_Test(void);


#endif /* __BSP_I2C_EE_H__ */

main.c

#include "stm32f10x.h"   // 相当于51单片机中的  #include <reg51.h>
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_i2c_gpio.h"
#include "bsp_i2c_ee.h"




extern uint8_t ucTemp;

void Delay(unsigned int n) {
    unsigned int i,j;
    for(j = 0; j<5000; j++)
    {
        for(i = 0;i<n;i++);
    }
		return ;
}

uint8_t Test(void) {
	//uint8_t a = 0xfe;
	uint8_t val;
  if (ee_CheckOk() == 0)
	{
		/* 没有检测到EEPROM */
		printf("没有检测到串行EEPROM!\r\n");
				
		return 0;
	} else {
		printf("测试成功!\n");
	}
	/*  写 */
	i2c_Stop();
	i2c_Start();
	
	i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR);	/* 此处是写设备地址 */
	while(i2c_WaitAck() == 1);
	
	i2c_SendByte(0);
	while(i2c_WaitAck() == 1);
	
	i2c_SendByte(0xff);
	while(i2c_WaitAck() == 1);
	i2c_Stop();	
	Delay(10000);

	
	/* 读 */
	i2c_Stop();
	i2c_Start();
	
	i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR);	/* 发送设备地址 */
	while(i2c_WaitAck() == 1);											//等待应答
	
	i2c_SendByte(0);
	while(i2c_WaitAck() == 1);	
	
	i2c_Start();
	i2c_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_RD);	/* 发送读方向地址 */
	while(i2c_WaitAck() == 1);
	
	val = i2c_ReadByte();
	i2c_Ack();
	i2c_Stop();	
	Delay(1000);
	return val;
}

int main(void) {
	// 来到这里的时候,系统的时钟已经被配置成72M。
	LED_GPIO_Config();//LED的GPIO初始化配置
	USART_Config();		//初始化串口配置
	LED_BLUE;
	printf("开发板是stm32f103\n");
	printf("eeprom 软件模拟i2c测试例程 \r\n");		
	if( ee_Test() == 1 ) {
		LED_GREEN;
	} else {
		LED_RED;
	}
	while(1);
	
}

2.结果

结果如图所示

在这里插入图片描述

举报

相关推荐

0 条评论