IO扩展芯片TCA9535的驱动程序和中断bug问题的解决

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fhqlongteng/article/details/83542773

 

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两个端口的输入寄存器,中断信号在上电后恢复为高电平,正常了。解决这个问题的关键代码如下:

      短短三行代码,解决了困扰你几天的问题,是不是要感谢一下我呀。如果这个博文对你有帮助,请在下面留言顶帖,让更多的人看到了。

7、读取输入寄存器的bug

       长时间多次按按键测试,有个别情况发现从输入寄存器中读取到的按键值不正确,读取到多个按键同时按下的情况,问题也待解决中。

      

      

 

 

 

 

 

展开阅读全文

没有更多推荐了,返回首页