MCP23017芯片是通过IIC来扩展IO口用的,看了datasheet后,了解到其功能之强大,能作为输入也可以作为输出。作为输入时,还可以通过中断来通知输入引脚电平变化。
本文主要描述MCP23017扩展IO时作为输出来驱动LED的过程、实现、及注意事项。
电路接线示意图:
A0/A/A2器件地址选择引脚都接入到GND,MCP23017器件的地址为0x40。
MCP23017器件有很多寄存器地址,首先要关注的是BANK的值默认是多少。
从datasheet给出的表格可以看出,BANK的值默认是0。(先关注BANK值的原因是,BANK=0和BANK=1的各个寄存器地址是不同的,寄存器的值设置的不对,就无法得到预期的效果)
我的目标是让MCP23017作为IO输出,从电路连接示意图可以看出,输出低电平时,对应的LED指示灯亮,输出高电平时,对应的LED指示灯灭。
IODIRA和IODIRB这两个寄存器默认值都是0xFF,查阅datasheet,可以知道默认为输入。
要驱动LED亮灭,剩下的就是给寄存器配置高低电平了,找到对应的寄存器GPIOA和GPIOB。
测试的代码:
bsp_mcp23017.c
#include "bsp_mcp23017.h"
void init_iic_mcp23017(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(MCP_RCC_I2C_PORT, ENABLE);
GPIO_InitStructure.GPIO_Pin = MCP_I2C_SCL_PIN | MCP_I2C_SDA_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_Init(MCP_GPIO_PORT_I2C, &GPIO_InitStructure);
i2c_stop();
}
void i2c_delay(void)
{
uint8_t i;
for (i = 0; i < 10; i++);
}
void i2c_start(void)
{
MCP_I2C_SDA_1();
MCP_I2C_SCL_1();
i2c_delay();
MCP_I2C_SDA_0();
i2c_delay();
MCP_I2C_SCL_0();
i2c_delay();
}
void i2c_stop(void)
{
MCP_I2C_SDA_0();
MCP_I2C_SCL_1();
i2c_delay();
MCP_I2C_SDA_1();
}
void i2c_sendbyte(uint8_t _ucByte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
if (_ucByte & 0x80)
{
MCP_I2C_SDA_1();
}
else
{
MCP_I2C_SDA_0();
}
i2c_delay();
MCP_I2C_SCL_1();
i2c_delay();
MCP_I2C_SCL_0();
if (i == 7)
{
MCP_I2C_SDA_1();
}
_ucByte <<= 1;
i2c_delay();
}
}
uint8_t i2c_readbyte(void)
{
uint8_t i;
uint8_t value;
value = 0;
for (i = 0; i < 8; i++)
{
value <<= 1;
MCP_I2C_SCL_1();
i2c_delay();
if (MCP_I2C_SDA_READ())
{
value++;
}
MCP_I2C_SCL_0();
i2c_delay();
}
return value;
}
uint8_t i2c_waitack(void)
{
uint8_t re;
MCP_I2C_SDA_1();
i2c_delay();
MCP_I2C_SCL_1();
i2c_delay();
if (MCP_I2C_SDA_READ())
{
re = 1;
}
else
{
re = 0;
}
MCP_I2C_SCL_0();
i2c_delay();
return re;
}
void i2c_ack(void)
{
MCP_I2C_SDA_0();
i2c_delay();
MCP_I2C_SCL_1();
i2c_delay();
MCP_I2C_SCL_0();
i2c_delay();
MCP_I2C_SDA_1();
}
void i2c_nack(void)
{
MCP_I2C_SDA_1();
i2c_delay();
MCP_I2C_SCL_1();
i2c_delay();
MCP_I2C_SCL_0();
i2c_delay();
}
u8 fn_mcp23017_iodir(uint16_t ports)
{
u8 ADDR_MCP23017 = 0x40;
i2c_start();
i2c_sendbyte(ADDR_MCP23017);
if (i2c_waitack() != 0)
{
goto cmd_fail;
}
i2c_sendbyte(0x00);
if (i2c_waitack() != 0)
{
goto cmd_fail;
}
i2c_sendbyte(ports >> 8);
if (i2c_waitack() != 0)
{
goto cmd_fail;
}
// i2c_sendbyte(0x01);
//
// if (i2c_waitack() != 0)
// {
// goto cmd_fail;
// }
i2c_sendbyte((uint8_t)ports);
if (i2c_waitack() != 0)
{
goto cmd_fail;
}
i2c_stop();
return 0;
cmd_fail:
i2c_stop();
return 1;
}
u8 fn_mcp23017_setports(uint16_t ports)//A high 8 bits; B low 8 bits
{
u8 ADDR_MCP23017 = 0x40;
i2c_start();
i2c_sendbyte(ADDR_MCP23017);
if (i2c_waitack() != 0)
{
goto cmd_fail;
}
i2c_sendbyte(0x12);
if (i2c_waitack() != 0)
{
goto cmd_fail;
}
i2c_sendbyte(ports >> 8);
if (i2c_waitack() != 0)
{
goto cmd_fail;
}
// i2c_sendbyte(0x13);
//
// if (i2c_waitack() != 0)
// {
// goto cmd_fail;
// }
i2c_sendbyte(ports);
if (i2c_waitack() != 0)
{
goto cmd_fail;
}
i2c_stop();
return 0;
cmd_fail:
i2c_stop();
return 1;
}
bsp_mcp23017.h
#ifndef __BSP_MCP23017_H
#define __BSP_MCP23017_H
#include "stm32f10x.h"
#define MCP_GPIO_PORT_I2C GPIOB
#define MCP_RCC_I2C_PORT RCC_APB2Periph_GPIOB
#define MCP_I2C_SCL_PIN GPIO_Pin_10
#define MCP_I2C_SDA_PIN GPIO_Pin_11
#define MCP_I2C_SCL_1() GPIO_SetBits(MCP_GPIO_PORT_I2C, MCP_I2C_SCL_PIN) /* SCL = 1 */
#define MCP_I2C_SCL_0() GPIO_ResetBits(MCP_GPIO_PORT_I2C, MCP_I2C_SCL_PIN) /* SCL = 0 */
#define MCP_I2C_SDA_1() GPIO_SetBits(MCP_GPIO_PORT_I2C, MCP_I2C_SDA_PIN) /* SDA = 1 */
#define MCP_I2C_SDA_0() GPIO_ResetBits(MCP_GPIO_PORT_I2C, MCP_I2C_SDA_PIN) /* SDA = 0 */
#define MCP_I2C_SDA_READ() GPIO_ReadInputDataBit(MCP_GPIO_PORT_I2C, MCP_I2C_SDA_PIN)
void init_iic_mcp23017(void);
void i2c_delay(void);
void i2c_start(void);
void i2c_stop(void);
void i2c_sendbyte(uint8_t _ucByte);
uint8_t i2c_readbyte(void);
uint8_t i2c_waitack(void);
void i2c_ack(void);
void i2c_nack(void);
u8 fn_mcp23017_iodir(uint16_t ports);
u8 fn_mcp23017_setports(uint16_t ports);
#endif
main.c
#ifndef __STM32F10X_H
#define __STM32F10X_H
#include "stm32f10x.h"
#endif
#ifndef __BSP_BEEP_H
#define __BSP_BEEP_H
#include "bsp_beep.h"
#endif
#ifndef __BSP_LED_H
#define __BSP_LED_H
#include "bsp_led.h"
#endif
#ifndef __Z_UTIL_TIME_H
#define __Z_UTIL_TIME_H
#include "z_util_time.h"
#endif
#include "bsp_mcp23017.h"
int main()
{
init_beep();
init_led();
init_iic_mcp23017();
fn_mcp23017_iodir(0);//0-output/1-input
func_led2_on();
func_led3_on();
func_led4_on();
func_beep_play();
delay_ms(100);
func_beep_stop();
delay_ms(1000);
for(;;)
{
func_led4_on();
fn_mcp23017_setports(0xffff);
delay_ms(1000);
func_led4_off();
fn_mcp23017_setports(0x0000);
delay_ms(1000);
}
}
测试的效果图(我只接了一个LED,然后依次将LED的杜邦线接入GPA0~GPA7然后 GPB0~GPB7)
LED一秒闪烁一次
最后,还有一个要注意的问题,datasheet描述了寄存器自增的情况
在BANK=0时适用,如果不喜欢这种方式,可以通过代码调整,通过i2c_stop()和i2c_start()再发一次器件地址,寄存器地址,和寄存器设置值。但是这个方法比较笨拙。