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;
} 
以上僅做記錄,望以後少走彎路!但總的來說,雖然很痛苦,可誰叫自己閱歷不豐富咧。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章