在嵌入式系统开发中,我们经常会遇到一个经典矛盾:MCU的引脚资源有限,但外设需求却不断增长。以PIC10F202为例,这款6引脚微控制器在空间受限场景中表现出色,但GPIO数量往往成为瓶颈。这时,MCP23X08系列GPIO扩展器就像给MCU装上了"外挂装备",通过I2C或SPI总线将8个GPIO引脚"变"出来。
我曾在智能家居传感器节点项目中深有体会:当需要同时驱动LED指示灯、读取多个按键状态并连接环境传感器时,PIC10F202的原始引脚根本不够分配。采用MCP23008扩展后,不仅解决了引脚短缺问题,还通过中断功能实现了低功耗唤醒——这正是GPIO扩展器的核心价值所在。
MCP23X08系列包含两个孪生兄弟:
两者都具备以下核心特性:
关键细节:硬件地址引脚允许在同一总线上挂载多达8个MCP23008(I2C)或4个MCP23S08(SPI)设备,这在多节点系统中非常实用。
MCP23X08通过寄存器控制所有功能,主要寄存器包括:
| 寄存器地址 | 名称 | 功能描述 |
|---|---|---|
| 0x00 | IODIR | 方向控制(1=输入,0=输出) |
| 0x01 | IPOL | 输入极性反转(1=反转) |
| 0x02 | GPINTEN | 中断使能控制 |
| 0x03 | DEFVAL | 中断默认比较值 |
| 0x04 | INTCON | 中断控制(1=比较DEFVAL) |
| 0x05 | IOCON | 配置寄存器 |
| 0x06 | GPPU | 上拉电阻使能(1=启用) |
| 0x07 | INTF | 中断标志位 |
| 0x08 | INTCAP | 中断捕获的端口值 |
| 0x09 | GPIO | 端口寄存器 |
| 0x0A | OLAT | 输出锁存器 |
典型连接电路如下:
code复制PIC10F202 MCP23008
GP0 (SDA) ---- SDA
GP1 (SCL) ---- SCL
GP2 ---- A0 (地址位0)
GP3 ---- A1 (地址位1)
GP4 ---- A2 (地址位2)
GP5 ---- RESET (可选)
注意事项:
SPI模式下的连接方式:
code复制PIC10F202 MCP23S08
GP0 (SI) ---- SI
GP1 (SO) ---- SO
GP2 (SCK) ---- SCK
GP3 ---- CS (片选)
GP4 ---- A0 (地址位0)
GP5 ---- A1 (地址位1)
模式选择:
MCP23S08支持SPI模式0和1,通过IOCON寄存器配置。实测发现模式0(CPOL=0, CPHA=0)兼容性最好。
assembly复制; 写入单个字节到指定寄存器
I2C_WriteByte:
call I2C_Start ; 发送起始条件
movlw OPCODE ; 准备设备操作码
iorlw AnPINS ; 合并地址引脚状态
movwf TempData
movlw WRITECMD ; 设置写命令位
iorf TempData, 1
call I2CClockByte ; 发送控制字节
call IsACK? ; 检查ACK
movf Addr, w ; 发送寄存器地址
movwf TempData
call I2CClockByte
call IsACK?
movf DataByte, w ; 发送数据
movwf TempData
call I2CClockByte
call IsACK?
call I2C_Stop ; 发送停止条件
return
assembly复制; 从指定寄存器读取单个字节
I2C_ReadByte:
call I2C_Start ; 第一阶段:发送寄存器地址
movlw OPCODE
iorlw AnPINS
movwf TempData
movlw WRITECMD
iorf TempData, 1
call I2CClockByte
call IsACK?
movf Addr, w ; 发送要读取的寄存器地址
movwf TempData
call I2CClockByte
call IsACK?
call I2C_Start ; 重复起始条件
movlw OPCODE ; 第二阶段:读取数据
iorlw AnPINS
movwf TempData
movlw READCMD
iorf TempData, 1
call I2CClockByte
call IsACK?
movlw 0xFF ; 准备接收数据
movwf TempData
call I2CClockByte ; 读取数据字节
call NoACK ; 发送NACK
call I2C_Stop ; 停止条件
return
assembly复制SPI_WriteByte:
call ClockMode00 ; 设置SPI模式0
movf RAMTRIS, w
iorlw CS_L ; 拉低CS片选
movwf RAMTRIS
tris GPIO
movlw OPCODE ; 发送操作码
iorlw AnPINS
movwf TempData
movlw WRITECMD
iorf TempData, 1
call SPIClockByte
movf Addr, w ; 发送寄存器地址
movwf TempData
call SPIClockByte
movf DataByte, w ; 发送数据
movwf TempData
call SPIClockByte
movf RAMTRIS, w ; 释放CS片选
andlw CS_H
movwf RAMTRIS
tris GPIO
return
assembly复制SPI_ReadByte:
call ClockMode00 ; 设置SPI模式0
movf RAMTRIS, w
iorlw CS_L ; 拉低CS片选
movwf RAMTRIS
tris GPIO
movlw OPCODE ; 发送操作码
iorlw AnPINS
movwf TempData
movlw READCMD
iorf TempData, 1
call SPIClockByte
movf Addr, w ; 发送寄存器地址
movwf TempData
call SPIClockByte
movlw 0xFF ; 准备接收数据
movf TempData, f ; 空操作
call SPIClockByte ; 读取数据
movf RAMTRIS, w ; 释放CS片选
andlw CS_H
movwf RAMTRIS
tris GPIO
return
当GPIO配置为输入时,机械开关会产生抖动。建议在固件中实现软件防抖:
assembly复制; 输入防抖检测子程序
Debounce_Check:
movlw .10 ; 设置采样次数
movwf Counter
clrf TempStore
Debounce_Loop:
call Read_GPIO ; 读取GPIO状态
andlw 0x0F ; 只保留低4位
iorwf TempStore, f ; 累积结果
decfsz Counter, f
goto Debounce_Loop
movf TempStore, w ; 如果所有位都为1
xorlw 0xFF ; 则表示稳定按下
btfsc STATUS, Z
retlw 0xFF
movf TempStore, w ; 如果所有位都为0
btfsc STATUS, Z ; 则表示稳定释放
retlw 0x00
retlw 0x80 ; 抖动状态
利用MCP23X08的中断功能可以大幅降低MCU功耗:
典型配置流程:
assembly复制 movlw b'00001111' ; 使能GP0-GP3中断
movwf GPINTEN
movlw b'00000000' ; 设置为变化触发
movwf INTCON
movlw b'00001111' ; 启用内部上拉
movwf GPPU
检查物理连接:
验证设备地址:
检查ACK响应:
时钟极性验证:
片选信号检查:
数据对齐问题:
虽然本文示例使用单字节读写,但MCP23X08支持连续读写操作。通过顺序访问寄存器可以显著提高传输效率:
I2C连续写入示例:
assembly复制 call I2C_Start
; 发送设备地址+写命令
; 发送起始寄存器地址
; 连续发送多个数据字节
call I2C_Stop
在最近的一个电池供电项目中,通过合理配置MCP23008的中断功能,使系统待机电流从1.2mA降至35μA,续航时间延长了30倍。
通过本指南介绍的硬件连接方法和固件实现技巧,开发者可以充分发挥MCP23X08在资源受限系统中的扩展能力。在实际项目中,建议根据具体需求选择I2C或SPI版本——I2C适合引脚资源紧张的场景,而SPI则能提供更高的通信速率。