HT32F52352学习笔记之六

本文深入解析了HT32F5232微控制器通过I2C协议与AT24C02 EEPROM进行读写操作的实现过程。从I2C协议的基础配置到具体的数据传输流程,再到AT24C02的读写操作,提供了详细的代码示例和调试经验分享。

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

花了好多的时间算是把软件I2C协议的代码写出来了。然后进阶,把HT32F5232利用I2C读写AT24C02的程序写出来了。这个过程中很坑的地方就是数据线SDA要一直在输入和输出这里切换,因为这个HT32输入时需要输入使能,就很麻烦。鉴于I2C之前有学过,这里就不贴出来关于I2C的详细解释了。另外在调试时有用到串口,详见学习笔记之五https://blog.youkuaiyun.com/Unlimited_Bit/article/details/86704019

另外还使用了系统定时器SysTick,详情见https://blog.youkuaiyun.com/Unlimited_Bit/article/details/86683175

主要的代码:

(1)I2C

#include "iic.h"
#include "systick.h"
#include "usart.h"

static void I2C_CKCU_Config()
{
	CKCU_PeripClockConfig_TypeDef CCLOCK;
	
	CCLOCK.Bit.PA     = 1;
	CCLOCK.Bit.AFIO  = 1;
	
	CKCU_PeripClockConfig(CCLOCK, ENABLE);
}

static void I2C_GPIO_Config()
{
	AFIO_GPxConfig(I2C_SCL_GPIO_ID, I2C_SCL_PIN, I2C_SCL_AFIO_MODE);
	AFIO_GPxConfig(I2C_SDA_GPIO_ID, I2C_SDA_PIN, I2C_SDA_AFIO_MODE);
	
	/* Configure the GPIO pin                                                                                 */
	GPIO_PullResistorConfig(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PR_DISABLE);
	GPIO_DriveConfig(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_DV_8MA);
	GPIO_DirectionConfig(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_DIR_OUT);
	
	GPIO_PullResistorConfig(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_PR_DISABLE);
	GPIO_DriveConfig(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_DV_8MA);
	GPIO_DirectionConfig(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_DIR_OUT);
}

void I2C_Initia()
{
	I2C_CKCU_Config();
	I2C_GPIO_Config();
	
	/* 一定要先发一遍停止信号 */
	I2C_Stop();
}

void I2C_Start()
{
	I2C_SCL_1();
	I2C_SDA_1();
	Systick_Delay_us(DELAYTIME);
	
	I2C_SDA_0();
	Systick_Delay_us(DELAYTIME);
	
	I2C_SCL_0();
	Systick_Delay_us(DELAYTIME);
}

void I2C_Stop()
{
	I2C_SDA_0();
	I2C_SCL_1();
	Systick_Delay_us(DELAYTIME);
	I2C_SDA_1();
}

/* 返回0表示正确应答,1表示无器件响应 */
u8 I2C_WaitAck()
{
	u8 ack;
	
	I2C_SCL_1();
	Systick_Delay_us(DELAYTIME);
	
	I2C_SDA_1();
	Systick_Delay_us(DELAYTIME);
	I2C_SDA_Direction_Input();
	I2C_SDA_InputConfig();
	
	if( I2C_SDA_READ() ) {
		ack = 1;
	}
	else {
		ack = 0;
	}
	I2C_SCL_0();
	Systick_Delay_us(DELAYTIME);
	return ack;
}

void I2C_SendByte(u8 _ucByte)
{
	u8 i;

	/* 先发送字节的高位bit7 */
	for (i = 0; i < 8; i++)
	{		
		if (_ucByte & 0x80)
		{
			I2C_SDA_1();
		}
		else
		{
			I2C_SDA_0();
		}
		
		Systick_Delay_us(DELAYTIME);
		I2C_SCL_1();
		Systick_Delay_us(DELAYTIME);
		I2C_SCL_0();
		
		if (i == 7)
		{
			 I2C_SDA_1(); // 释放总线
		}
		_ucByte <<= 1;	/* 左移一个bit */
		Systick_Delay_us(DELAYTIME);
	}
}

u8 I2C_ReadByte()
{
	u8 i;
	u8 data;
	
	data = 0;
	
	for(i = 0;i < 8;i++)
	{
		data <<= 1;
		I2C_SCL_1();
		Systick_Delay_us(DELAYTIME);
		
		I2C_SDA_Direction_Input();
		I2C_SDA_InputConfig();
	
		if( I2C_SDA_READ() ) {
			data++;
		}
		I2C_SCL_0();
		Systick_Delay_us(DELAYTIME);
	}
	return data;
}

// 返回值为0表示正确,返回1表示未探测到
u8 I2C_CheckDevice(u8 _Address)
{
//	u8 time = 0;
	u8 ucAck = 0;
	
	I2C_Start();
	I2C_SendByte(_Address | EEPROM_I2C_WR);
	ucAck =I2C_WaitAck();
	I2C_SDA_Direction_Output();
	I2C_Stop();

	return ucAck;
}

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

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

(2)AT24C02

//1表示正常,0表示不正常
u8 EE_CheckOK()
{
	if( I2C_CheckDevice(EEPROM_DEV_ADDR) == 0 ){
		printf("\r\n发送地址完成,AT24C02在线\n");
		return 1;
	}
	else{
		printf("\r\n发送地址完成,AT24C02离线\n");
		I2C_Stop();
		return 0;
	}
}

/**********************************************
*	_usAddress : 起始地址					  *
*	_usSize : 数据长度,单位为字节			  *
*	_pWriteBuf : 存放读到的数据的缓冲区指针	  *
***********************************************/

/* 为了提高连续写的效率: 本函数采用page wirte操作。*/
/* _usSize大于8会在第四步卡住 */
u8 EE_WriteBytes(u8 *_pWriteBuf, u16 _usAddress, u16 _usSize)
{
	u16 i,m;
	u16 usAddr;

	usAddr = _usAddress;	
	for (i = 0; i < _usSize; i++)
	{
		/* 当发送第1个字节或是页面首地址时,需要重新发起启动信号和地址 */
		if ((i == 0) || (usAddr & (EEPROM_PAGE_SIZE - 1)) == 0)
		{
			/* 启动内部写操作 */
			I2C_Stop();
			
			for (m = 0; m < 1000; m++)
			{				
				I2C_Start();
				
				/* 第2步:高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
				I2C_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR);	
				
				if (I2C_WaitAck() == 0)
				{
					break;
				}
			}
			I2C_SDA_Direction_Output();
			/* 第4步:发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */
			I2C_SendByte((uint8_t)usAddr);
			
			if(I2C_WaitAck() != 0)
			{
				time++;
				
				if( time ==WAITMAX ){
					printf("写入数据第四步无应答");
					return 0;
				}
			}
			time = 0;
		}
		I2C_SDA_Direction_Output();
		/* 第6步:开始写入数据 */
		I2C_SendByte(_pWriteBuf[i]);
	
		/* 第7步:发送ACK */
		if(I2C_WaitAck() != 0)
		{
			time++;
				
			if( time ==WAITMAX ){
				printf("写入数据第七步无应答");
				return 0;
			}
		}
		time = 0;
		I2C_SDA_Direction_Output();
		usAddr++;	/* 地址增1 */		
	}
	I2C_SDA_Direction_Output();
	/* 命令执行成功,发送I2C总线停止信号 */
	I2C_Stop();
	return 1;
}

u8 EE_ReadBytes(u8 *_pReadBuf, u16 _usAddress, u16 _usSize)
{
	u16 i;
	u32 time = 0;
	
	I2C_Initia();
	I2C_Start();
	
	/* 第2步:发起控制字节,高7bit是地址,bit0是读写控制位,0表示写,1表示读 */
	I2C_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_WR);
	
	if ( I2C_WaitAck() != 0 )
	{
		time++;
		
		if(time == WAITMAX){
			printf("\r\n读取数据第二步无应答\r\n");
			return 0;
		}
	}
	I2C_SDA_Direction_Output();
	time = 0;

	/* 发送字节地址,24C02只有256字节,因此1个字节就够了,如果是24C04以上,那么此处需要连发多个地址 */
	I2C_SendByte((u8)_usAddress);
	
	if ( I2C_WaitAck() != 0 )
	{
		time++;
		
		if(time == WAITMAX){
			printf("\r\n读取数据第四步EEPROM无应答\r\n");	/* EEPROM器件无应答 */
			return 0;
		}
	}
	I2C_SDA_Direction_Output();
	time = 0;
	
	/* 重新启动I2C总线。前面的代码的目的向EEPROM传送地址,下面开始读取数据 */
	I2C_Start();
	
	/* 发起控制字节,高7bit是地址,bit0是读写控制位 */
	I2C_SendByte(EEPROM_DEV_ADDR | EEPROM_I2C_RD);
	
	if ( I2C_WaitAck() != 0 )
	{
		time++;
		
		if(time == WAITMAX){
			printf("\r\n读取数据第七步EEPROM无应答\r\n");	/* EEPROM器件无应答 */
			return 0;
		}
	}
	
	time = 0;	
	I2C_SDA_Direction_Input();
	I2C_SDA_InputConfig();
	/* 第9步:循环读取数据 */
	for (i = 0; i < _usSize; i++)
	{
		_pReadBuf[i] = I2C_ReadByte();	/* 读1个字节 */
		
		/* 每读完1个字节后,需要发送Ack, 最后一个字节不需要Ack,发Nack */
		if (i != _usSize - 1)
		{
			I2C_SDA_Direction_Output();
			I2C_Ack();	/* CPU产生ACK信号(驱动SDA = 0) */
			I2C_SDA_Direction_Input();
			I2C_SDA_InputConfig();
		}
		else
		{
			I2C_SDA_Direction_Output();
			I2C_NAck();	/* CPU产生NACK信号(驱动SDA = 1) */
		}
	}
	
	I2C_Stop();
	printf("\r\n读取数据成功\r\n");
	return 1;	/* 执行成功 */
}

对硬件方面(如引脚、模式选择等的封装):

#ifndef _IIC_H
#define _IIC_H

#include "ht32f5xxxx_01.h"

#define EEPROM_I2C_WR				0		/* 写控制bit */
#define EEPROM_I2C_RD				1		/* 读控制bit */
#define EEPROM_DEV_ADDR				0xA0	/* 24xx02的设备地址 */
#define EEPROM_PAGE_SIZE		  	8		/* 24xx02的页面大小 */
#define EEPROM_SIZE				  	256		/* 24xx02总容量 */

/* 定义I2C的SCL连接的GPIO端口 */
#define I2C_SCL_GPIO_ID					GPIO_PA					/* GPIO端口 */
#define I2C_SCL_PIN						GPIO_PIN_0				/* 连接到SCL时钟线的GPIO */
#define I2C_SCL_AFIO_MODE				AFIO_FUN_GPIO
#define I2C_SCL_PORT					HT_GPIOA

/* 定义I2C的SDA连接的GPIO端口 */
#define I2C_SDA_GPIO_ID					GPIO_PA					/* GPIO端口 */
#define I2C_SDA_PIN						GPIO_PIN_1				/* 连接到SCL时钟线的GPIO */
#define I2C_SDA_AFIO_MODE				AFIO_FUN_GPIO
#define I2C_SDA_PORT					HT_GPIOA

#define I2C_SCL_1()						GPIO_SetOutBits(I2C_SCL_PORT, I2C_SCL_PIN)
#define I2C_SCL_0()						GPIO_ClearOutBits(I2C_SCL_PORT, I2C_SCL_PIN)
	
#define I2C_SDA_1()						GPIO_SetOutBits(I2C_SDA_PORT, I2C_SDA_PIN)
#define I2C_SDA_0()						GPIO_ClearOutBits(I2C_SDA_PORT, I2C_SDA_PIN)
#define I2C_SDA_Direction_Input()		GPIO_DirectionConfig(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_DIR_IN)
#define I2C_SDA_InputConfig()			GPIO_InputConfig(I2C_SDA_PORT, I2C_SDA_PIN, ENABLE)
#define I2C_SDA_InputDisable()			GPIO_InputConfig(I2C_SDA_PORT, I2C_SDA_PIN, DISABLE)
#define I2C_SDA_Direction_Output()		GPIO_DirectionConfig(I2C_SDA_PORT, I2C_SDA_PIN, GPIO_DIR_OUT)
#define I2C_SDA_READ()					GPIO_ReadInBit(I2C_SDA_PORT, I2C_SDA_PIN)

#define WAITMAX							2000	
#define DELAYTIME 						2

void I2C_Initia(void);
void I2C_Start(void);
void I2C_Stop(void);
u8 I2C_WaitAck(void);
void I2C_SendByte(u8 _ucByte);
u8 I2C_ReadByte(void);
void I2C_Ack(void);
void I2C_NAck(void);
//FlagStatus I2C_CheckOK(void);
u8 I2C_CheckDevice(u8 _Address);

u8 EE_CheckOK(void);
u8 EE_WriteBytes(u8 *_pWriteBuf, u16 _usAddress, u16 _usSize);
u8 EE_ReadBytes(u8 *_pReadBuf, u16 _usAddress, u16 _usSize);

 

评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值