I2C器件之PCF8574TS调试记录

这两天要写一下板子上这个芯片的驱动,虽然现在写好了,但是感觉还是有必要把整个挣扎痛苦的过程记录下来,积累经验。

首先由于之前并没用过配置寄存器的方式实现主机模式的I2C通信,故想吃一次“螃蟹”。然后从网上找了一些资料做准备工作,初始化IO、配置I2C、实现读写函数,感觉还挺顺手的。接着马上下载到板子上开始调试,然后辨出现了意料之中的事:程序挂了。。。然后开始了各种怀疑。参考网上的例程和其他资料,好不靠谱啊。比对了好久,还是不能确定我写的程序到底对不对。然后小小地纠结了一下,唉,还是用模拟I2C写驱动吧,起码以前还成功用过。

随后一狠心将之前研究了半天的代码(突然感觉好悲催)给删除了 。好吧,重新整理下思路,调整下心情,开始模拟I2C。

配IO、找相关控制寄存器做上宏定义、写函数(
start、stop、read、write四个函数 ),测试函数写完,带着略有兴奋的心情,又一次将 程序下载到板子里。这一次的结果可是出乎意料的:虽然程序没挂,但是读写程序都报错了。。。好失败啊。。可是尽管这样我还是比较相信我的代码的(因为之前这可是好用着呢)。于是去问度娘了,看看,有悲惨遭遇的小伙伴还不少(多少有点安慰)。

找了半天罗列了一些:地址不对?延时不对?然后一个一个尝试,先找地址,手册上是这样描述的:


额,我的器件是PCF8574TS,没有。。。 虽然原理图上写的是0x40,但还是谨慎一点为好(看到网上好多骂奸商坑人的文章)。


可是。。。这两种地址都做了尝试,都不行啊。。都快崩溃了。周日晚上加班到八点半,没搞定,灰溜溜地回去了。。

第二天,就是今天啦,虚心请教了大神同事。也是各种怀疑(我都快怀疑人生了),然后检查呗,都准备亮出必杀技(量波形)了。唉,上午就这么快过了,下午大神同事说,看看供电吧。赶紧拿个万用表,看准原理图比对元件,一戳,0V,额没供电。。。。

突然感觉上边那么多话都成废话了。。。

请教了硬件工程师之后,飞了根线,供电供上了。马上下载程序(
当时用了地址0x40),调试了一下,哟,进去了,成功了!哎呀,万事开头难,这下好了,可以正常一点了。接下来就简单了,先贴两个图吧:



看了半天,感觉不对劲,①写时序中没有stop信号。。。②读时序中stop信号之前有个1。。。
第一个问题又去请教大神同事(不耻下问嘛),说stop信号必须有。
第二个问题额,1不就是NACK么(好蠢,还是经同事提醒想起来的)。
贴上I2C标准时序:


想通之后,重新修改整理代码,调试通过,哎呀,完美了。。好了,贴代码了。

i2c.h 文件

#ifndef _I2C_H_
#define _I2C_H_
#include "config.h"

#define	I2C_NOT_LAST	0x00
#define	I2C_LAST_BYTE	0x01

#define	I2C_READ	0x01
#define	I2C_WRITE	0x00

#define	PCF8574TS	0x40



/*! 
 * 定义I2C1的IO访问操作
 * 配置I2C1引脚:SCL:PB6(92) SDA:PB7(93)
 */
#define	I2C1_SDA_IDR	(GPIOB_BASE+0x10 - PERIPH_BASE)		//!<输入数据寄存器(选择B端口)
#define	I2C1_SDA_ODR	(GPIOB_BASE+0x14 - PERIPH_BASE)		//!<输出数据寄存器(选择B端口)
#define	I2C1_SCL_ODR	(GPIOB_BASE+0x14 - PERIPH_BASE)		//!<输出数据寄存器(选择B端口)

/*定义I2C1 SDA地址*/
#define	I2C1_SDA_NUM	7
#define	I2C1_SDA_OUA	(PERIPH_BB_BASE + (I2C1_SDA_ODR * 32) + (I2C1_SDA_NUM * 4))    
#define	I2C1_SDA_ODA	 *(__IO uint32_t *)I2C1_SDA_OUA		//!<数据寄存器输出地址
#define	I2C1_SDA_INA	(PERIPH_BB_BASE + (I2C1_SDA_IDR * 32) + (I2C1_SDA_NUM * 4))    
#define	I2C1_SDA_IDA	*(__IO uint32_t *)I2C1_SDA_INA		//!<数据寄存器输入地址(时钟线不需要读输入寄存器数据)
#define	I2C1_SDA_MCA	*(__IO uint32_t *)(GPIOB_BASE+0)	//!<控制寄存器
/*定义I2C1 SCL地址*/
#define	I2C1_SCL_NUM	6
#define	I2C1_SCL_OUA	(PERIPH_BB_BASE + (I2C1_SCL_ODR * 32) + (I2C1_SCL_NUM * 4))    
#define	I2C1_SCL_ODA	*(__IO uint32_t *)I2C1_SCL_OUA
#define	I2C1_SCL_MCA	*(__IO uint32_t *)(GPIOB_BASE+0)	//!<控制寄存器

/*定义I2C1 SDA IO操作*/
#define I2C1_SDA_OUT()  I2C1_SDA_MCA = (((I2C1_SDA_MCA) & 0xFFFF3FFF) | 0x00004000)  //!<数据线配置为输出(PB7)
#define I2C1_SDA_H()	I2C1_SDA_ODA = 1	//!<数据线拉高
#define I2C1_SDA_L()	I2C1_SDA_ODA = 0	//!<数据线拉低
#define I2C1_SDA_IN()   I2C1_SDA_MCA = (((I2C1_SDA_MCA) & 0xFFFF3FFF) | 0x00000000)  //!<数据线配置为输入(PB7)
#define I2C1_SDA_READ() I2C1_SDA_IDA		//!<读取数据线上电平高低值
/*定义I2C1 SCL IO操作*/
#define	I2C1_SCL_OUT()  I2C1_SCL_MCA = (((I2C1_SCL_MCA) & 0xFFFFCFFF) | 0x00001000)  //!<时钟线配置为输出(PB6)
#define	I2C1_SCL_H()	I2C1_SCL_ODA = 1	//!<时钟线拉高
#define	I2C1_SCL_L()	I2C1_SCL_ODA = 0	//!<时钟线拉低

void I2CSysCtlDelay(unsigned long ulCount);
void I2C1_Start(void);
void I2C1_Stop(void);
uint8 I2C1_Receive_byte(uint8 flag);
uint8 I2C1_Send_byte(uint8 data);

#endif
i2c.c 文件

#include "i2c.h"

#define I2C_DELAY()  I2CSysCtlDelay(30)


/*
 * @brief  SysCtlDelay
 * @param  ulCount 延时值,必须大于0
 * @retval None
 */
void I2CSysCtlDelay(unsigned long ulCount)
{
	
   __asm("    subs    r0, #1\n"
         "    bne.n   I2CSysCtlDelay\n"
         "    bx      lr");
   
}
/*!
 * @brief   I2C1 GPIO初始化
 * @param	none
 * @return	NONE
 * @note	PB6-SCL,PB7-SDA
 */
void I2C1_IO_init(void)
{
	GPIO_InitTypeDef  GPIO_Init_s;
    
	GPIO_Init_s.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 ;
	GPIO_Init_s.GPIO_Mode = GPIO_Mode_OUT; 		//!<输出模式
	GPIO_Init_s.GPIO_OType = GPIO_OType_OD;		//!<开漏
	GPIO_Init_s.GPIO_Speed = GPIO_Speed_50MHz;	//!<快速
	GPIO_Init_s.GPIO_PuPd = GPIO_PuPd_UP;//GPIO_PuPd_NOPULL;	//无上拉
	
	GPIO_Init(GPIOB, &GPIO_Init_s);
}

/*!
 *  @brief	I2C1起始信号
 *  @param	none
 *  @return	none
 *	@note	数据:D  时钟:C  高:H  低:L  输出:O  输入:I  延时:_
 *	@note	DOCO_DHCH_DL__CL
 */
void I2C1_Start(void)
{
  I2C1_SDA_OUT(); 
  I2C1_SCL_OUT(); 
  I2C_DELAY();
  I2C1_SDA_H();  
  I2C1_SCL_H(); 
  I2C_DELAY();  
  I2C1_SDA_L();
  I2C_DELAY();
  I2C_DELAY();
  I2C1_SCL_L();
}

/*!
 *  @brief	I2C1结束信号
 *  @param	none
 *  @return	none
 *	@note	数据:D  时钟:C  高:H  低:L  输出:O  输入:I  延时:_
 *	@note	DOCO_DLCL_CH___DH__
 */
void I2C1_Stop(void)
{
  I2C1_SDA_OUT(); 
  I2C1_SCL_OUT();
  I2C1_SDA_L();  
  I2C1_SCL_L(); 
  I2C_DELAY();
  I2C1_SCL_H();
  I2C_DELAY();  
  I2C_DELAY();
  I2C_DELAY();
  I2C1_SDA_H();
  I2C_DELAY();
  I2C_DELAY();
}

/*!
 *  @brief	主机向I2C1总线发送一个字节
 *  @param	data:数据
 *  @return	0:失败  1:成功
 *	@note	数据:D  时钟:C  高:H  低:L  输出:O  输入:I  延时:_  读取:R
 *	@note	(_DH/L_CH__CL)*8_DHDI_CH_DR_CL_DO
 */
uint8 I2C1_Send_byte(uint8 data)
{
	uint8 k;
	for(k=0;k<8;k++){//!<发送8bit数据
		I2C_DELAY();
		if(data&0x80){
			I2C1_SDA_H();
		}else{
			I2C1_SDA_L();
		}
		data=data<<1;
		I2C_DELAY();
		I2C1_SCL_H();
		I2C_DELAY();
		I2C_DELAY();
		I2C1_SCL_L();
	}
	I2C_DELAY();//!<延时读取ACK响应
	I2C1_SDA_H();
  
	I2C1_SDA_IN();//!<设为输入
	I2C_DELAY();
	I2C1_SCL_H();   
	I2C_DELAY();
  
	k=I2C1_SDA_READ();//读取ACK/NACK
	I2C_DELAY();
	I2C1_SCL_L();
	I2C_DELAY();
	I2C1_SDA_OUT();
	if(k){ NACK响应
		return 0;
	}
	return 1;
}

/*!
 *  @brief	主机从I2C1总线上接收一个字节
 *  @param	flag:是否为读取的最后一个字节
 *  @return	data:读取的数值
 *	@note	数据:D  时钟:C  高:H  低:L  输出:O  输入:I  延时:_  读取:R
 *	@note	DI(_CH_DR_CL_)*8DODL_CH__CL_DO
 */
uint8 I2C1_Receive_byte(uint8 flag)
{
	uint32 k,data;

	I2C1_SDA_IN();//!<置为输入线
	data=0;
	for(k=0;k<8;k++){	//!<接收8bit数据
		I2C_DELAY();
		I2C1_SCL_H();
		I2C_DELAY();
      
		data=data|I2C1_SDA_READ();//!<读数据
		data=data<<1;
		I2C_DELAY();
		I2C1_SCL_L();
		I2C_DELAY(); 
	}
	data=data>>1;	//!<往回移动1次
	I2C1_SDA_OUT();	//!<置为输出线
	if(flag){
		I2C1_SDA_H();	//!<拉高为NACK
	}else{
		I2C1_SDA_L();	//!<拉低为ACK
	}
	I2C_DELAY();
	I2C1_SCL_H();
	I2C_DELAY();
	I2C_DELAY();
	I2C1_SCL_L();
	I2C_DELAY();
	I2C1_SDA_OUT();

	return (uint8)data;	//!<返回读取的数据
}

i2c_PCF8574TS.c 文件

#include "i2c.h"


/*!
 *  @brief	主机从I2C1总线上的PCF8574TS设备中读取一个字节数据
 *  @param	data:数据回传指针
 *  @return	0:失败  1:成功
 *	@note	获取的数据为8个IO口状态
 */
uint8 i2c_EIO_RD(uint8* data)
{
	uint8 state,ret;
	I2C1_Start();									//!<启动I2C总线
	state = I2C1_Send_byte(PCF8574TS|I2C_READ);		//!<发送器件地址,主机读
	if(state == 1){
		*data = I2C1_Receive_byte(I2C_LAST_BYTE);	//!<读取数据
		ret = 1;
	}else{
		ret = 0;
	}
	I2C1_Stop();
	return ret;
}

/*!
 *  @brief	主机向I2C1总线上的PCF8574TS设备中写入一个字节数据
 *  @param	data:写入的数据
 *  @return	0:失败  1:成功
 *	@note	none
 */
uint8 i2c_EIO_WD(uint8 data)
{
	uint8 state,ret;
	I2C1_Start();									//!<启动I2C总线
	state = I2C1_Send_byte(PCF8574TS|I2C_WRITE);	//!<发送器件地址,主机写
	if(state==1){
	  	state = I2C1_Send_byte(data);
		if(state==1){
			ret = 1;
		}else{
			ret = 0;
		}
	}else{
		ret = 0;
	}
	I2C1_Stop();
	return ret;
} 
以上仅做记录,望以后少走弯路!但总的来说,虽然很痛苦,可谁叫自己阅历不丰富咧。


  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值