IO擴展芯片TCA9535的驅動程序和中斷bug問題的解決

 

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兩個端口的輸入寄存器,中斷信號在上電後恢復爲高電平,正常了。解決這個問題的關鍵代碼如下:

      短短三行代碼,解決了困擾你幾天的問題,是不是要感謝一下我呀。如果這個博文對你有幫助,請在下面留言頂帖,讓更多的人看到了。

      

      

 

 

 

 

 

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