最近一个产品中主控制器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用于连接输入按键。
TCA9535的电路比较简单,I2C接口INT脚外拉上接电阻就可以,电源处增加滤波电容即可,我的应用中P0端口驱动3mm红色发光二极管,P1端口用于连接按键输入,具体请看下图。
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端口来点亮不同的指示灯。
/******************************************************************************************
* 定义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)?
TCA9535按上面的电路使用,一上电后中断引脚就会一直输出低电平。这是什么原因呢?这么明显的bug,这样的芯片怎么才能使用呀。我怀疑买到了假芯片,或是使用PCA9535(型号就是PCA9535,这里可没有写错呀,TI先出的PCA9535这个芯片,芯片上电有问题,升级版本的芯片是TCA9535)这个芯片,这个芯片的上电时有问题,可能会引起中断异常。这其间经过N次思考问题可能的原因,N次的测试,调整电路电阻,电容还是没有找到问题的原因,怀疑为芯片本身的问题。
三天过去了,问题没有解决,还得继续找,突发奇想,是不是外围电路的问题呢?但是外围电路和手册上面画一样了,怎么可能有问题呢。死马当做活马医,把芯片外面连接的按键,LED全部去掉,一上电,中断信号正常了,为高电平。把按键接上,上电中断信号正常,把LED灯接上,上电,中断信号异常,为低电平,测试连接LED的管脚电压为1.6V,1.6V也是属于高电平,接到P0端口上引起了上电中断异常,按键端口的3.3.V上拉电平就不会引起芯片中断,还是芯片设计的不合理,芯片还得用,从软件上看看能解决不?
上面问题的原因已经查到,P0端口接LED灯时,芯片上电此端口默认为输入,读取到了LED灯上拉产生的电平,产生了中断。那么试着在芯片上电后,读取一下P0端口的输入寄存器,来清除一个中断。修改tca9535_init()函数,在上电后,读取一个P0,P1两个端口的输入寄存器,中断信号在上电后恢复为高电平,正常了。解决这个问题的关键代码如下:
长时间多次按按键测试,有个别情况发现从输入寄存器中读取到的按键值不正确,读取到多个按键同时按下的情况,问题也待解决中。