1、IO擴展的作用
最近一個產品中主控制器MCU的IO接口不夠用,需要擴展出來更多的IO接口,那麼擴展IO接口的方式有兩種,(1)通過74HC595芯片可以擴展輸出IO接口,價格較低,需要4個IO來連接74HC595,擴展出來8個IO,擴展出來的IO不多,(2)選擇專用的IO擴展芯片,通過IIC接口來擴展成8路IO,16路IO,在一個IIC總線上連接多片IO擴展芯片可以擴展出來32路,64路等更多的IO接口。我這個產品中通過I2C接口來擴展IO口,芯片選擇的是TI公司的TCA9535, 擴展出來16路IO接口,8路IO用於連接LED指示燈,8路IO用於連接輸入按鍵。
2、TCA9535的硬件原理圖
TCA9535的電路比較簡單,I2C接口INT腳外拉上接電阻就可以,電源處增加濾波電容即可,我的應用中P0端口驅動3mm紅色發光二極管,P1端口用於連接按鍵輸入,具體請看下圖。
3、TCA9535的原理說明
TCA9535芯片內部一共8個寄存器,具體功能如下:
寄存器0,寄存器1 輸入寄存器:用於讀取P0,P1端口的輸入值,
寄存器2,寄存器3 輸出寄存器 :用於設置P0,P1端口的輸出值,
寄存器4,寄存器5 極性反轉寄存器:用於當P0,P1端口做爲輸入時,對輸入的電平進行反轉處理,即管腳爲高電平時,設置這個寄存器中相應的位爲1時,讀取到的輸入寄存器0,1的值就是低電平0了。
寄存器6,7 配置寄存器:用於配置P0,P1端口的做爲輸入或是輸出。
根據上面的原理圖可知,TCA9535需要設置P0端口爲輸出, P1端口爲輸入,在中斷程序中讀取P1端口的值,在程序的應用邏輯中調用寫P0端口來點亮不同的指示燈。
4、TCA9535的驅動程序
/******************************************************************************************
* 定義I2C管腳及通道
******************************************************************************************/
#define TCA9535_I2C_SDA PINNAME_DCD
#define TCA9535_I2C_SCL PINNAME_RI
#define TCA9535_I2C_INT PINNAME_CTS
#define TCA9535_SLAVE_ADDR 0x40
#define TCA9535_I2C_CHN 0
/******************************************************************************************
* 定義TCA9535寄存器
******************************************************************************************/
#define TCA9535_INPUT_PORT0_REG 0
#define TCA9535_INPUT_PORT1_REG 1
#define TCA9535_OUTPUT_PORT0_REG 2
#define TCA9535_OUTPUT_PORT1_REG 3
#define TCA9535_INVERSION_PORT0_REG 4
#define TCA9535_INVERSION_PORT1_REG 5
#define TCA9535_CONFIG_PORT0_REG 6
#define TCA9535_CONFIG_PORT1_REG 7
#define TCA9535_CONFIG_INPUT_VAL 0xFF
#define TCA9535_CONFIG_OUTPUT_VAL 0x00
/****************************************************************************************
** Function name: callback_eint_handle()
** Descriptions: 外部中斷回調函數
** input parameters:
** output parameters: 無
** Returned value: 無
****************************************************************************************/
static void callback_eint_handle(Enum_PinName eintPinName, Enum_PinLevel pinLevel, void* customParam)
{
STATUS ret = OK;
OTP_UINT8 key = 0;
OTP_UINT32 i = 0;
//mask the specified EINT pin.
Ql_EINT_Mask(TCA9535_I2C_INT);
/*低電平時讀取按鍵值 TCA9535在IO電平變化時產生中斷,按鍵按下和擡起時會產生2次中斷*/
if(PINLEVEL_LOW == pinLevel)
{
ret = tca9535_read_key(&key);
if(g_hdrc_vc8_4_status.switch_num == 4)
{
key |= 0xF0;
}
if((ret == OK) && (key != 0xFF))
{
for(i = 0; i < g_hdrc_vc8_4_status.switch_num; i++)
{
if((key & (1 << i)) == 0)
{
if(g_hdrc_vc8_4_status.val_switch & (1 << i))
{
g_hdrc_vc8_4_status.val_switch &= (~(1 << i));
}
else
{
g_hdrc_vc8_4_status.val_switch |= (1 << i);
}
}
}
/*操作相應的電磁閥打開*/
Ql_OS_SendMessage(server_cmd_id, MSG_ID_VAL_CONTROL, g_hdrc_vc8_4_status.val_switch, 0);
}
}
//unmask the specified EINT pin
Ql_EINT_Unmask(TCA9535_I2C_INT);
}
/****************************************************************************************
** Function name: tca9535_init()
** Descriptions: tca9535初始化函數
** input parameters: NONE
**
**
** output parameters: val:指示燈的亮的值,對應位爲1表示亮
** Returned value: OK ERROR
****************************************************************************************/
void tca9535_init(OTP_UINT8 *val)
{
OTP_INT32 ret = 0;
OTP_UINT8 tca9535_reg[] = {TCA9535_INPUT_PORT0_REG, TCA9535_CONFIG_OUTPUT_VAL, TCA9535_CONFIG_INPUT_VAL};
OTP_UINT8 tca9535_read[2] = {0};
OTP_UINT32 i = 0;
/*初始化I2C管腳 採用硬件I2C的方式*/
if( Ql_IIC_Init(TCA9535_I2C_CHN, TCA9535_I2C_SCL, TCA9535_I2C_SDA, TRUE) < 0)
{
cmd_out("IIC controller Ql_IIC_Init channel 0 fail"NEWLINE);
}
/*初始化I2C速度爲300Kbps*/
ret = Ql_IIC_Config(TCA9535_I2C_CHN, TRUE, TCA9535_SLAVE_ADDR, 300);// just for the IIC controller
if(ret < 0)
{
cmd_out("\r\n<--Failed !! IIC controller Ql_IIC_Config channel 0 fail ret=%d-->\r\n",ret);
}
/*先讀取一下P0, P1端口的輸入寄存器, 清除一下上電由於IO上接LED燈引起的中斷,否則INT一直接爲低電平*/
if(Ql_IIC_Write_Read(TCA9535_I2C_CHN, TCA9535_SLAVE_ADDR,
&tca9535_reg, 1, &tca9535_read, sizeof(tca9535_read)) < 0)
{
cmd_out("Read TCA9535_INPUT_PORT0_REG error!"NEWLINE);
}
/*讀取配置寄存器*/
tca9535_reg[0] = TCA9535_CONFIG_PORT0_REG;
ret = Ql_IIC_Write_Read(TCA9535_I2C_CHN, TCA9535_SLAVE_ADDR,
&tca9535_reg, 1, &tca9535_read, sizeof(tca9535_read));
if(ret == sizeof(tca9535_read))
{
if(tca9535_read[0] == TCA9535_CONFIG_OUTPUT_VAL)
{
/*軟件重新啓動 讀取P0端口輸出的數據值*/
tca9535_reg[0] = TCA9535_OUTPUT_PORT0_REG;
ret = Ql_IIC_Write_Read(TCA9535_I2C_CHN, TCA9535_SLAVE_ADDR,
&tca9535_reg, 1, &tca9535_read, sizeof(tca9535_read));
if(ret == sizeof(tca9535_read))
{
/*返回重新啓動前的端口輸出值.硬件排版問題所以轉換指示燈高位與低位*/
*val = 0;
for(i = 0; i < 8; i++)
{
if(tca9535_read[0] & (1 << i))
{
*val |= (1 << (7 - i));
}
}
*val = ~(*val);
}
else
{
cmd_out("Read TCA9535_OUTPUT_PORT0_REG error!"NEWLINE);
}
}
else
{
/*重新上電啓動*/
/*配置P0爲輸出 P1爲輸入*/
ret = Ql_IIC_Write(TCA9535_I2C_CHN, TCA9535_SLAVE_ADDR, tca9535_reg, sizeof(tca9535_reg));
if(ret < 0)
{
cmd_out("\r\n<--Failed Ql_IIC_Write channel 0 fail ret=%d-->\r\n",ret);
}
/*閥門爲全部關閉狀態*/
*val = 0;
}
}
else
{
cmd_out("tca9535 ret = %d"NEWLINE, ret);
/*閥門爲全部關閉狀態*/
*val = 0;
}
//Registers an EINT I/O, and specify the interrupt handler.
ret = Ql_EINT_Register(TCA9535_I2C_INT ,callback_eint_handle, NULL);
if(ret != 0)
{
cmd_out("<--OpenCPU: Ql_EINT_RegisterFast fail.-->\r\n");
}
/*************************************************************
*Initialize an external interrupt function.
*Parameters:
* eintPinName:
* EINT pin name, one value of Enum_PinName that has
* the interrupt function.
* eintType:
* Interrupt type, level-triggered or edge-triggered.
* Now, only level-triggered interrupt is supported.
* hwDebounce:
* Hardware debounce. Unit in 10ms.
* swDebounce:
* Software debounce. Unit in 10ms. The minimum value for
* this parameter is 5, which means the minimum software
* debounce time is 5*10ms=50ms.
* automask:
* mask the Eint after the interrupt happened.
**************************************************************/
ret = Ql_EINT_Init(TCA9535_I2C_INT, EINT_LEVEL_TRIGGERED, 1, 10, 0);
if(ret != 0)
{
cmd_out("<--OpenCPU: Ql_EINT_Init fail.-->\r\n");
}
}
/****************************************************************************************
** Function name: tca9535_read_key()
** Descriptions: tca9535讀取輸入鍵盤函數
** input parameters: key:讀取到的鍵值
**
**
** output parameters: 無
** Returned value: OK ERROR
****************************************************************************************/
STATUS tca9535_read_key(OTP_UINT8 *key)
{
OTP_UINT8 tca9535_wr_reg = TCA9535_INPUT_PORT1_REG;
OTP_UINT8 tca9535_read[1] = {0};
OTP_INT32 ret = 0;
ret = Ql_IIC_Write_Read(TCA9535_I2C_CHN, TCA9535_SLAVE_ADDR,
&tca9535_wr_reg, sizeof(tca9535_wr_reg), &tca9535_read, sizeof(tca9535_read));
if(ret != sizeof(tca9535_read))
{
cmd_out("tca9535 ret = %d"NEWLINE, ret);
return ERROR;
}
else
{
*key = tca9535_read[0];
return OK;
}
}
/****************************************************************************************
** Function name: tca9535_write_led()
** Descriptions: tca9535控制LED指示燈函數
** input parameters: led:指示燈的值,led對應位爲1,指示燈亮
**
**
** output parameters: 無
** Returned value: OK ERROR
****************************************************************************************/
STATUS tca9535_write_led(OTP_UINT8 led)
{
OTP_UINT8 tca9535_wr_reg[] = {TCA9535_OUTPUT_PORT0_REG, 0};
OTP_INT32 ret = 0;
OTP_UINT8 i = 0;
/*led爲1時點亮指示燈*/
led = ~led;
/*鍵值轉換成對應的輸出寄存器值*/
for(i = 0; i < 8; i++)
{
if(led & (1 << i))
{
tca9535_wr_reg[1] |= (1 << (7 - i));
}
}
ret = Ql_IIC_Write(TCA9535_I2C_CHN, TCA9535_SLAVE_ADDR,
&tca9535_wr_reg, sizeof(tca9535_wr_reg));
if(ret < 0)
{
cmd_out("tca9535_write_led ret = %d"NEWLINE, ret);
return ERROR;
}
else
{
return OK;
}
}
函數tca9535_init()實現初始化,主要設置P0口爲輸出,P1口爲輸入,設置MCU的中斷,還有一些其他的初始化程序是根據產品的實際需求增加上去的,從TC9535的輸出寄存器中讀取一下LED的狀態,做爲返回值返回。
函數tca9535_read_key(),實現讀取P1端口的8個按鍵值。
函數tca9535_write_led(),控制P0端口的8個LED指示燈點亮或熄滅。
到這裏你直接複製我的程序,如果I2C驅動正確的話,相信你的TCA9535已經可以正常的工作了。實際這個芯片是有一點中斷的bug的,你如果不用中斷或是隻用來擴展輸出IO的話是不會遇到問題,可以正常使用,但你像我這樣用就有問題了(我上面的代碼已經解決了這個中斷的bug)?
5、中斷異常的問題
TCA9535按上面的電路使用,一上電後中斷引腳就會一直輸出低電平。這是什麼原因呢?這麼明顯的bug,這樣的芯片怎麼才能使用呀。我懷疑買到了假芯片,或是使用PCA9535(型號就是PCA9535,這裏可沒有寫錯呀,TI先出的PCA9535這個芯片,芯片上電有問題,升級版本的芯片是TCA9535)這個芯片,這個芯片的上電時有問題,可能會引起中斷異常。這其間經過N次思考問題可能的原因,N次的測試,調整電路電阻,電容還是沒有找到問題的原因,懷疑爲芯片本身的問題。
三天過去了,問題沒有解決,還得繼續找,突發奇想,是不是外圍電路的問題呢?但是外圍電路和手冊上面畫一樣了,怎麼可能有問題呢。死馬當做活馬醫,把芯片外面連接的按鍵,LED全部去掉,一上電,中斷信號正常了,爲高電平。把按鍵接上,上電中斷信號正常,把LED燈接上,上電,中斷信號異常,爲低電平,測試連接LED的管腳電壓爲1.6V,1.6V也是屬於高電平,接到P0端口上引起了上電中斷異常,按鍵端口的3.3.V上拉電平就不會引起芯片中斷,還是芯片設計的不合理,芯片還得用,從軟件上看看能解決不?
6、中斷異常的解決
上面問題的原因已經查到,P0端口接LED燈時,芯片上電此端口默認爲輸入,讀取到了LED燈上拉產生的電平,產生了中斷。那麼試着在芯片上電後,讀取一下P0端口的輸入寄存器,來清除一箇中斷。修改tca9535_init()函數,在上電後,讀取一個P0,P1兩個端口的輸入寄存器,中斷信號在上電後恢復爲高電平,正常了。解決這個問題的關鍵代碼如下:
短短三行代碼,解決了困擾你幾天的問題,是不是要感謝一下我呀。如果這個博文對你有幫助,請在下面留言頂帖,讓更多的人看到了。