历史上的今天
返回首页

历史上的今天

今天是:2025年08月14日(星期四)

正在发生

2019年08月14日 | STM32CubeMX GPIO模拟I2C读写M24C64

2019-08-14 来源:eefocus

一、先了解一下硬件的连接,I2C_SDA和I2C_SCL分别接STM32的PB9、PB6
在这里插入图片描述
二、粗阅一下M24C64的数据手册,得知器件地址和存储器地址,器件地址是8bit,而存储器地址是16bit
在这里插入图片描述
三、下面是M24C64的写时序
在这里插入图片描述
四、下面是M24C64的读时序
在这里插入图片描述
五、下面是程序编写流程
在这里插入图片描述
六、看看时序参数在这里插入图片描述在这里插入图片描述
七、好啦!需要的知识点差不多都提到了开始搬砖
1、用STM32CubeMX配置生成工程,并打开工程。(具体怎么用这个软件这里不讲)
2、在我的工程里是这样配置的
《1》配置USART3,用打印读出来的数据与写入的是否一致
《2》配置PB6、PB9为开漏输出模式,配置如下:

void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct;

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOG_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOG, GPIO_PIN_7, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6|GPIO_PIN_9, GPIO_PIN_RESET);

  /*Configure GPIO pin : PG7 */
  GPIO_InitStruct.Pin = GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);

  /*Configure GPIO pins : PB6 PB9 */
  GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_9;						//PB6    PB9
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;				//开漏输出
  GPIO_InitStruct.Pull = GPIO_NOPULL;											//上下拉模式配置为既不上拉也不下拉
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//IO口速度配置
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);									//初始化
}
在这里插入代码片1234567891011121314151617181920212223242526272829303132

八、编写读程序,下面的代码是(安富莱电子 www.armfly.com)串行EEPROM 24xx驱动模块的代码,
代码如下:

/***********************************************************************************************
*
*
*
*
*
*
*
*
***************************************************************************************************/
#include "stm32f4xx_hal.h"


#define EE_MODEL_NAME		"AT24C64"
#define EE_DEV_ADDR			0xA0			/* 设备地址 */
#define EE_PAGE_SIZE		32				/* 页面大小(字节) */
#define EE_SIZE				(8*1024)		/* 总容量(字节) */
#define EE_ADDR_BYTES		2			 	/* 地址字节个数 */

// 定义I2C总线连接的GPIO端口, 用户只需要修改下面3行代码即可任意改变SCL和SDA的引脚 
#define GPIO_PORT_I2C	GPIOB			     // GPIO端口 
#define I2C_SCL_PIN		GPIO_PIN_6			 // 连接到SCL时钟线的GPIO 
#define I2C_SDA_PIN		GPIO_PIN_9			 // 连接到SDA数据线的GPIO 

/* 定义读写SCL和SDA的宏 */
#define I2C_SCL_1()  GPIO_PORT_I2C->BSRR = I2C_SCL_PIN							// SCL = 1 
#define I2C_SCL_0()  GPIO_PORT_I2C->BSRR = (uint32_t)I2C_SCL_PIN << 16U  		// SCL = 0 

#define I2C_SDA_1()  GPIO_PORT_I2C->BSRR = GPIO_PIN_9   						// SDA = 1 
#define I2C_SDA_0()  GPIO_PORT_I2C->BSRR = (uint32_t)GPIO_PIN_9 << 16U  		// SDA = 0 

#define I2C_SDA_READ()  (GPIO_PORT_I2C->IDR & GPIO_PIN_9)						// 读SDA口线状态 
#define I2C_SCL_READ()  (GPIO_PORT_I2C->IDR & I2C_SCL_PIN)						// 读SCL口线状态 


static void i2c_Delay(void)
{
	uint8_t i;
	for (i = 0; i < 40; i++);
}
void i2c_Start(void)
{
	// 当SCL高电平时,SDA出现一个下跳沿表示I2C总线启动信号 
	
	I2C_SDA_1();
	I2C_SCL_1();
	i2c_Delay();
	I2C_SDA_0();
	i2c_Delay();
	
	I2C_SCL_0();
	i2c_Delay();
}

void i2c_Stop(void)
{
	// 当SCL高电平时,SDA出现一个上跳沿表示I2C总线停止信号 
	I2C_SDA_0();
	I2C_SCL_1();
	i2c_Delay();
	I2C_SDA_1();
	i2c_Delay();
}
void i2c_SendByte(uint8_t _ucByte)
{
	uint8_t i;
	
	// 先发送字节的高位bit7 
	for (i = 0; i < 8; i++)
	{
		if (_ucByte & 0x80)
		{
			I2C_SDA_1();
		}
		else
		{
			I2C_SDA_0();
		}
		i2c_Delay();
		I2C_SCL_1();
		i2c_Delay();
		I2C_SCL_0();
		if (i == 7)
		{
			 I2C_SDA_1(); 	// 释放总线
		}
		_ucByte <<= 1;		// 左移一个bit 
		i2c_Delay();
	}
}
uint8_t i2c_ReadByte(void)
{
	uint8_t i;
	uint8_t value;

	/* 读到第1个bit为数据的bit7 */
	value = 0;
	for (i = 0; i < 8; i++)
	{
		value <<= 1;
		I2C_SCL_1();
		i2c_Delay();
		if (I2C_SDA_READ())
		{
			value++;
		}
		I2C_SCL_0();
		i2c_Delay();
	}
	return value;
}


uint8_t i2c_WaitAck(void)
{
	uint8_t re;

	I2C_SDA_1();	/* CPU释放SDA总线 */
//	i2c_Delay();
	I2C_SCL_1();	/* CPU驱动SCL = 1, 此时器件会返回ACK应答 */
	i2c_Delay();

	if (I2C_SDA_READ())	/* CPU读取SDA口线状态 */
	{
		re = 1;
	}
	else
	{
		re = 0;
	}

	I2C_SCL_0();
	i2c_Delay();
	return re;
}

void i2c_Ack(void)
{
	I2C_SDA_0();	/* CPU驱动SDA = 0 */
	i2c_Delay();
	I2C_SCL_1();	/* CPU产生1个时钟 */
	i2c_Delay();
	I2C_SCL_0();
	i2c_Delay();
	I2C_SDA_1();	/* CPU释放SDA总线 */
}

void i2c_NAck(void)
{
	I2C_SDA_1();	/* CPU驱动SDA = 1 */
	i2c_Delay();
	I2C_SCL_1();	/* CPU产生1个时钟 */
	i2c_Delay();
	I2C_SCL_0();
	i2c_Delay();
}

uint8_t i2c_CheckDevice(uint8_t _Address)
{
	uint8_t ucAck;

	if (I2C_SDA_READ() && I2C_SCL_READ())
	{
		i2c_Start();		/* 发送启动信号 */

		/* 发送设备地址+读写控制bit(0 = w, 1 = r) bit7 先传 */
		i2c_SendByte(_Address | I2C_WR);
		ucAck = i2c_WaitAck();	/* 检测设备的ACK应答 */

		i2c_Stop();			/* 发送停止信号 */

		return ucAck;
	}
	return 1;	/* I2C总线异常 */
}

uint8_t ee_CheckOk(void)
{
	if (i2c_CheckDevice(EE_DEV_ADDR) == 0)
	{
		return 1;
	}
	else
	{
		/* 失败后,切记发送I2C总线停止信号 */
		i2c_Stop();
		return 0;
	}
}
uint8_t ee_WriteBytes(uint8_t *_pWriteBuf, uint16_t _usAddress, uint16_t _usSize)
{
	uint16_t i,m;
	uint16_t usAddr;
	usAddr = _usAddress;
	for (i = 0; i < _usSize; i++)
	{
		/* 当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址 */
		if ((i == 0) || (usAddr & (EE_PAGE_SIZE - 1)) == 0)
		{
			/* 第0步:发停止信号,启动内部写操作 */
			i2c_Stop();

			/* 通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms
				CLK频率为200KHz时,查询次数为30次左右
			*/
			for (m = 0; m < 1000; m++)
			{
				/* 第1步:发起I2C总线启动信号 */
				i2c_Start();

				/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
				
//				#if EE_ADDR_A8 == 1
//					i2c_SendByte(EE_DEV_ADDR | I2C_WR | ((_usAddress >> 7) & 0x0E));	/* 此处是写指令 */
//				#else				
					i2c_SendByte(EE_DEV_ADDR | I2C_WR);
//				#endif
        
				/* 第3步:发送一个时钟,判断器件是否正确应答 */
				if (i2c_WaitAck() == 0)
				{
					break;
				}
			}
			if (m  == 1000)
			{
				goto cmd_fail;	/* EEPROM器件写超时 */
			}
			/* 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */
			if (EE_ADDR_BYTES == 1)
			{
				i2c_SendByte((uint8_t)usAddr);
				if (i2c_WaitAck() != 0)
				{
					goto cmd_fail;	/* EEPROM器件无应答 */
				}
			}
			else
			{
				i2c_SendByte(usAddr >> 8);
				if (i2c_WaitAck()!= 0)
				{
					goto cmd_fail;	/* EEPROM器件无应答 */
				}

				i2c_SendByte(usAddr);
				if (i2c_WaitAck() != 0)
				{
					goto cmd_fail;	/* EEPROM器件无应答 */
				}
			}
		}

		/* 第6步:开始写入数据 */
		i2c_SendByte(_pWriteBuf[i]);

		/* 第7步:发送ACK */
		if (i2c_WaitAck() != 0)
		{
			goto cmd_fail;	/* EEPROM器件无应答 */
		}
		usAddr++;	/* 地址增1 */
	}

	/* 命令执行成功,发送I2C总线停止信号 */
	i2c_Stop();

	/* 通过检查器件应答的方式,判断内部写操作是否完成, 一般小于 10ms
		CLK频率为200KHz时,查询次数为30次左右
	*/
	for (m = 0; m < 1000; m++)
	{
		/* 第1步:发起I2C总线启动信号 */
		i2c_Start();

		/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */	
		#if EE_ADDR_A8 == 1
			i2c_SendByte(EE_DEV_ADDR | I2C_WR | ((_usAddress >> 7) & 0x0E));	/* 此处是写指令 */
		#else		
			i2c_SendByte(EE_DEV_ADDR | I2C_WR);	/* 此处是写指令 */
		#endif

		/* 第3步:发送一个时钟,判断器件是否正确应答 */
		if (i2c_WaitAck() == 0)
		{
			break;
		}
	}
	if (m  == 1000)
	{
		goto cmd_fail;	/* EEPROM器件写超时 */
	}

	/* 命令执行成功,发送I2C总线停止信号 */
	i2c_Stop();	

	return 1;

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

	/* 采用串行EEPROM随即读取指令序列,连续读取若干字节 */

	/* 第1步:发起I2C总线启动信号 */
	i2c_Start();

	/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	
	i2c_SendByte(EE_DEV_ADDR | I2C_WR);	/* 此处是写指令 */

	/* 第3步:发送ACK */
	if (i2c_WaitAck() != 0)
	{	
		goto cmd_fail;	/* EEPROM器件无应答 */
	}
	/* 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */
	if (EE_ADDR_BYTES == 1)
	{
		i2c_SendByte((uint8_t)_usAddress);
		if (i2c_WaitAck() != 0)
		{
			goto cmd_fail;	/* EEPROM器件无应答 */
		}
	}
	else
	{
		i2c_SendByte(_usAddress >> 8);
		if (i2c_WaitAck() != 0)
		{
			goto cmd_fail;	/* EEPROM器件无应答 */
		}

		i2c_SendByte(_usAddress);
		if (i2c_WaitAck() != 0)
		{
			goto cmd_fail;	/* EEPROM器件无应答 */
		}
	}

	/* 第6步:重新启动I2C总线。下面开始读取数据 */
	i2c_Start();

	/* 第7步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */	
	
	i2c_SendByte(EE_DEV_ADDR | I2C_RD);	/* 此处是写指令 */

	/* 第8步:发送ACK */
	if (i2c_WaitAck() != 0)
	{
		goto cmd_fail;	/* EEPROM器件无应答 */
	}

	/* 第9步:循环读取数据 */
	for (i = 0; i < _usSize; i++)
	{
		_pReadBuf[i] = i2c_ReadByte();	/* 读1个字节 */

		/* 每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack */
		if (i != _usSize - 1)
		{
			i2c_Ack();	/* 中间字节读完后,CPU产生ACK信号(驱动SDA = 0) */
		}
		else
		{
			i2c_NAck();	/* 最后1个字节读完后,CPU产生NACK信号(驱动SDA = 1) */
		}
	}
	/* 发送I2C总线停止信号 */
	i2c_Stop();

	return 1;	/* 执行成功 */

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

九、main函数

推荐阅读

史海拾趣

Amphenol RF公司的发展小趣事

Amphenol RF 是 Amphenol 公司的一个子公司,专注于设计、制造和销售射频连接器和组件,在电子行业中占据重要地位。以下是关于 Amphenol RF 公司发展的五个相关故事:

  1. 创立与早期发展:Amphenol RF 公司的历史可以追溯到20世纪40年代,当时被称为RF Connector Division。最初,公司主要致力于生产射频连接器和组件,为通信、航空航天、军事和工业市场提供射频连接解决方案。随着射频技术的发展和市场需求的增长,Amphenol RF 逐渐壮大成为射频连接器领域的重要参与者。

  2. 技术创新与产品多样化:Amphenol RF 公司在射频领域持续进行技术创新,并不断推出新型的射频连接器和组件产品。公司致力于提升产品的性能、可靠性和适应性,满足客户对高频射频应用的需求。除了传统的射频连接器,Amphenol RF 还推出了一系列新型产品,如微波连接器、同轴连接器、板对板连接器等,拓展了产品线的多样性。

  3. 客户合作与定制化解决方案:Amphenol RF 公司与客户建立了紧密的合作关系,共同开发定制化的射频连接器和组件解决方案。公司的工程团队与客户密切合作,根据客户的需求和应用场景,设计和制造符合特定要求的产品。这种定制化解决方案能够满足客户个性化的需求,为客户提供更加专业和完善的服务。

  4. 质量控制与认证标准:Amphenol RF 公司高度重视产品质量控制,并严格遵循国际质量管理体系标准。公司的生产工艺和质量管理体系符合ISO 9001质量管理体系认证标准,以确保产品质量的稳定性和可靠性。此外,Amphenol RF 公司的产品还通过了各种行业和应用领域的认证标准,如航空航天领域的MIL-SPEC标准等,确保产品符合行业标准和规定。

  5. 全球市场拓展与合作伙伴关系:除了在美国的生产基地外,Amphenol RF 公司还在全球范围内设有多个销售办事处和代理商网络,拓展了国际市场份额。公司与全球各种行业领先企业建立了长期稳定的合作关系,共同推动产品的创新和市场拓展。通过全球市场拓展和合作伙伴关系,Amphenol RF 公司不断扩大业务规模,增强了在射频连接器领域的市场竞争力。

这些故事展示了 Amphenol RF 公司从成立初期到如今在技术创新、产品多样化、客户合作与定制化解决方案、质量控制与认证标准以及全球市场拓展与合作伙伴关系等方面取得的重要进展。

Design Gateway公司的发展小趣事

随着Gateway业务的不断发展,公司开始寻求更多的市场曝光。1987年,Gateway在《Computer Shopper》杂志上投放了一版独特的广告,吸引了众多消费者的目光。1991年,公司推出了彰显其牧场起家背景的别具一格的奶牛花斑盒状商标,这一创新举措获得了全国消费者的认可,进一步提升了Gateway的品牌知名度和市场地位。

(请注意,由于篇幅限制,以上两个故事为简化版。在实际写作中,可以进一步扩展每个故事,包括更详细的背景信息、人物对话、市场反应等。)

由于篇幅所限,这里只提供了两个故事概要。如果需要更多关于Gateway或其他电子公司的发展故事,可以进一步研究和撰写。

DEMMELPRODUCTS公司的发展小趣事

在追求技术创新和市场拓展的同时,DEMMELPRODUCTS公司始终将品质管理放在首位。公司建立了严格的质量管理体系,从原材料采购到产品生产的每一个环节都进行严格的质量控制。此外,公司还积极引入先进的生产设备和技术,不断提高生产效率和产品质量。通过持续改进和优化,DEMMELPRODUCTS公司的产品品质得到了客户和行业的广泛认可。

川晶科技公司的发展小趣事

川晶科技注重团队建设和人才培养。公司建立了完善的培训体系和发展机制,为员工提供丰富的培训机会和广阔的发展空间。通过引进高素质的人才和内部培养相结合的方式,川晶科技打造了一支高素质、专业化的团队。团队成员之间团结协作、互相支持,共同推动公司的持续成长和发展。同时,公司还建立了良好的激励机制和企业文化氛围,激发员工的工作积极性和创造力。

固得沃克(GOODWORK)公司的发展小趣事

随着业务的不断拓展,固得沃克意识到技术升级和产能扩张的重要性。2019年,公司在江苏盐城成立了江苏固得沃克微电子科技有限公司,作为新的生产基地。该基地拥有2万平米的厂房,引进了具有国际水准的专业生产设备及先进检测仪器,并通过了ISO9001、ISO14001等国际管理体系认证。这一举措不仅提升了公司的生产能力和产品质量,还进一步巩固了其在半导体行业的地位。

Hengstler GmbH公司的发展小趣事

固得沃克始终关注市场需求的变化,不断拓展产品线以满足不同客户的需求。公司从最初的二极管生产,逐步扩展到三极管、整流桥堆、TVS管、MOS管等多个领域,并实现了全面自主研发、生产和销售。此外,固得沃克还提供定制化服务,根据客户的具体需求提供个性化的解决方案。这种灵活多变的产品策略和服务模式,使公司在激烈的市场竞争中保持了竞争优势。

问答坊 | AI 解惑

電腦com口燒壞?不知是和原因

問題:電子產品測試時用到電腦的 DB9串口與電子產品的console口進行通訊,            有時電腦的com口會燒壞,有時產品的console口的Tx/Rx PCB走線都燒壞,很是奇怪? 以往也有這些現象發生,檢查電腦 ...…

查看全部问答>

中断无法正常使用,改后系统都无法加载了,请帮忙!!

您好,我现在的问题也是这样的,我用的是2440,原来打算用timer0或者是timer1来做一个定时,在display驱动里面创建了个线程并动态分配映射了一个sysintr,配置好寄存器后,一切都ok,但是就是无法启动IST线程,后来怀疑是事件没有触发事件,单步调 ...…

查看全部问答>

BOOT.NB0存放目录

请问重新编译是改变C:\\WINCE500\\PBWorkspaces下面的BOOT.NBO文件还是C:\\WINCE500\\PLATFORM\\Draco\\Bin\\Usbboot下面的文件啊…

查看全部问答>

寻找导师--现在自学嵌入式中

     大家好,我是个新人,但想借此论坛寻找一个嵌入式开发方面的导师,请大家谅解及支持。       目前我是做软体测试工程师,主要从事笔记本的测试,但一直想从事嵌入式开发,目前在学习操作系统。 &nb ...…

查看全部问答>

我想破解打印机不知如何做?

我想破解打印机不知如何做?…

查看全部问答>

www.kingofcoder.com --- 编程文章网

www.kingofcoder.com 编程文章网 …

查看全部问答>

小灯控制问题

各位大哥,小弟遇到一问题,要求控制400个小灯,并且要求小灯亮暗可以为任意组合,请问硬件怎么做比较好,小灯为0603的贴片的,是不是有什么芯片可以用来可以跟主控芯片通信控制小灯?谢谢各位帮忙。…

查看全部问答>

请教版主,关于备份电池掉电和模拟电源关系

                                 版主,一直以来受到模拟电源精度和电池掉电困扰,是否可以这样解决,把VDDA和VDD连接,而Vref用独立的电源系统,这 ...…

查看全部问答>

求助:不同型号MSP430单片机对24LSXX存储器的操作问题

在使用MSP430单片机对24LSXX存储器读写时出现问题,描述如下: 采用IIC协议,分别用MSP430F149和MSP430F1121,使用基本相同的程序(只修改了头文件和管脚定义),对同一个24LSXX存储器进行操作(该存储器电路验证无误,10k上拉)。但出 ...…

查看全部问答>

TI Sitara AM335x系统之Linux下AM335X芯片 MUX 配置分析

[url=]Linux下AM335X芯片 MUX 配置分析[/url](转载自http://blog.chinaunix.net/uid-20543672-id-3067021.html) 在移植内核的时候,通常会遇到引脚复用(MUX)的配置问题。在现在的Linux内核中,对于TI的ARM芯片,早已经有了比较通用的MUX配置框 ...…

查看全部问答>