电路设计论坛
直播中

jsqueh

8年用户 1135经验值
私信 关注
[资料]

设计一款MCP23017分线板

描述
面包板友好型 MCP23017 分线器

2C 端口扩展器或扩展器是非常有用的设备,我在我的项目中使用了很多。我的首选设备绝对是 PCF8574,主要是因为它有点“面包板友好”。MCP23017 没有本地可用的现有分接头。因此,我决定设计我自己的面包板友好型 MCP23017 分线板版本。

分线模块 PCB 及其特性

面包板友好型 MCP23017 分线板 - 正面


面包板友好型 MCP23017 分线板 – 返回

虽然这绝对是我比较容易的项目之一,但仍然需要一些时间才能让它恰到好处并将一些基本组件和功能直接添加到 PCB 上。

本次突破的主要特点:
– DIP12 布局 – 所有引脚都断开,地址引脚到跳线头……
– 适当的去耦电容,尽可能靠近 MCP23017 芯片。
我不得不利用 PCB 的背面层来做到这一点,这并不完全理想,但有适当的电源和接地层,以及漂亮的厚走线,我相信它们会很好。

– 地址选择器跳线 – 本地可用的分线器没有这些。
– 面包板友好布局 – 33.020 毫米 x 20.320 毫米 [电路板尺寸],各排引脚之间的垂直间距为 15.240 毫米,确保您可以轻松地将其安装到面包板上,同时仍有空间将跳线添加到引脚。水平引脚间距为标准 2.54mm。

原理图


原理图简单明了。不过有几点需要注意:
– 原理图上未显示地址选择头以及 io 引脚头。
– I2C 上拉电阻设置为 1k,但可以根据电路的需要替换为更合适的值

使用突破
让我们看看这款芯片的一些特点
16 位远程双向 I/O 端口:
I/O 引脚默认为输入
高速 I2C 接口 (MCP23017):
100kHz
400kHz
1.7兆赫
高速 SPI 接口 (MCP23S17):
10 兆赫(最大)
三个硬件地址引脚最多允许
总线上有八个设备
可配置中断输出引脚:
可配置为高电平有效、低电平有效或
开漏
INTA 和 INTB 可配置为运行
独立或一起
可配置中断源:
来自配置寄存器的变化中断
默认值或引脚更改
极性反转寄存器配置
输入端口数据的极性
外部复位输入
低待机电流:1 μA(最大值)
工作电压:
1.8V 至 5.5V @ -40°C 至 +85°C
2.7V 至 5.5V @ -40°C 至 +85°C
4.5V 至 5.5V @ -40°C 至 +125°C

16 个 I/O 端口分为两个“端口”——A(右侧)和 B(左侧)。引脚 9 连接到 5V,10 到 GND,11 未使用,12 是 I2C 总线时钟线(Arduino Uno/Duemailnove 模拟引脚 5,Mega 引脚 21)和 13 是 I2C 总线数据线(Arduino Uno/Duemailnove 模拟引脚 4,Mega 引脚 20)。

应在 I2C 总线上使用外部上拉电阻——在我们的示例中,我们使用 1k 欧姆值。引脚 14 未使用,我们不会查看中断,因此请忽略引脚 19 和 20。引脚 18 是复位引脚,通常为高电平 - 因此您将其接地以复位 IC。所以把它连接到5V!

最后我们得到了三个硬件地址引脚 15~17。这些用于确定芯片的 I2C 总线地址。如果将它们全部连接到 GND,则地址为 0x20。如果您有其他具有该地址的器件或需要使用多个 MCP23017,请参见数据表中的图 1-2。
您可以通过将引脚 15~17 的组合连接到 5V (1) 或 GND (0) 来更改地址。例如,如果将 15~17 全部连接到 5V,则控制字节变为二进制的 0100111,或十六进制的 0x27。

使用标准 Wire.h 库
23017 具有三个输入引脚,允许您为每个连接的 MCP23017 设置不同的地址。
以上对应于对应于 IC 输入引脚值的三行 A0、A1、A2 的硬件地址。您必须将这些硬件输入的值设置为 0V 或(高)伏特,不要让它们悬空,否则它们会从电噪声中获得随机值,芯片将无能为力!

最左边的四位固定为 0100(由向制造商分配地址范围的联盟指定)。
因此 MCP23017 I2C 地址范围是十进制 32 到十进制 37 或 MCP23017 的 0x20 到 0x27。
请注意:地址与 PCF8475 相同。因此,如果您在同一 i2c 总线上使用这两个设备,则必须小心!

MCP23017 非中断寄存器
IODIR I/O 方向寄存器
为了控制每个引脚的 I/O 方向,寄存器 IODIR (A/B) 允许您在写入零时将引脚设置为输出,在向寄存器位写入“1”时将引脚设置为输入。这与大多数微控制器的方案相同——关键是要记住零(“0”)等于输出中的“O”。
GPPU上拉寄存器
设置一个高位设置相应 I/O 引脚的上拉有效。
OLAT 输出锁存器
这与 18F 系列 PIC 芯片中的 I/O 端口完全相同,无论是否达到该引脚的实际状态,您都可以读回端口引脚的“所需”输出。即考虑连接到引脚的强电流 LED - 很容易将引脚上的输出电压拉低到逻辑阈值以下,即如果从引脚本身读取,您会读回零,而实际上它应该是一. 从软件工程的角度来看,读取 OLAT 寄存器位会返回“一”。
IPOL 引脚反转寄存器
IPOL(A/B) 寄存器允许您选择性地反转任何输入引脚。这减少了将其他设备连接到 MCP23017 所需的胶合逻辑,因为您无需添加反相逻辑芯片即可将正确的信号极性输入 MCP23017。
以正确的方式获取信号也非常方便,例如通常使用上拉电阻进行输入,因此当用户按下输入键时,电压输入为零,因此在软件中您必须记住测试零。
使用 MCP23017,您可以反转该输入并测试 1(在我看来,按键更等同于开启状态,即“1”)但是我一直使用上拉(并且 uC 通常在启用时使用内部上拉) 所以必须忍受零作为“按下”。使用此设备可以轻松纠正此问题。注意:到处使用低电平有效信号的原因是历史原因:TTL(晶体管晶体管逻辑)设备由于内部电路而在低电平有效状态下消耗更多功率,并且它对于减少不必要的功耗非常重要——因此,大多数时间处于非活动状态的信号(例如芯片选择信号)被定义为高电平。对于 CMOS 设备,任何一种状态都会导致相同的功耗,因此现在无关紧要 - 但是使用低电平有效,因为每个人现在都使用它并且过去使用它。
SEQOP 轮询模式:寄存器位:(在 IOCON 寄存器内)
如果您的设计具有关键中断代码,例如用于执行时序关键测量,您可能不希望非关键输入产生中断,即您为最重要的输入数据保留中断。
在这种情况下,允许轮询某些设备输入可能更有意义。为了便于实现这一点,提供了“字节模式”。在此模式下,您可以使用时钟读取同一组 GPIO,而无需提供其他控制信息。即它保持在同一组 GPIO 位上,您可以连续读取它而无需更新寄存器地址。在非字节模式下,您要么必须将从(A 或 B 组)读取的地址设置为控制输入数据。
现在来检查如何在我们的草图中使用 IC。
正如您现在应该知道的,大多数 I2C 设备都有几个可以寻址的寄存器。每个地址包含一个字节的数据,用于确定各种选项。所以在使用之前我们需要设置每个端口是输入还是输出。首先,我们将检查将它们设置为输出。

因此,要将端口 A 设置为输出,我们使用:
Wire.beginTransmission(0x20);
Wire.write(0x00); // IODIRA 寄存器
Wire.write(0x00); // 将所有端口 A 设置为输出
Wire.endTransmission();
然后将端口 B 设置为输出,我们使用:
Wire.beginTransmission(0x20);
Wire.write(0x01); // IODIRB 寄存器
Wire.write(0x00); // 将所有端口 B 设置为输出
Wire.endTransmission();
所以现在我们处于 void loop() 或您自己创建的函数中,并且想要控制一些输出引脚。要控制端口 A,我们使用:
Wire.beginTransmission(0x20);
Wire.write(0x12); //地址端口A
Wire.write(??); //要发送的值
Wire.endTransmission();
要控制端口 B,我们使用:
Wire.beginTransmission(0x20);
Wire.write(0x13); //地址端口B
Wire.write(??); //要发送的值
Wire.endTransmission();

……更换??将二进制或等效的十六进制或十进制值发送到寄存器。
要计算所需的数字,请考虑从 7 到 0 的每个 I/O 引脚匹配二进制数的一位 - 1 表示开启,0 表示关闭。因此,您可以插入一个表示每个输出引脚状态的二进制数。或者,如果您使用二进制,请将其转换为十六进制。或十进制数。
例如,您希望引脚 7 和 1 打开。二进制表示 10000010,十六进制表示 0x82,或 130 十进制。(如果要显示递增值或函数结果中的值,使用小数会很方便)。
例如,我们希望端口 A 为 11001100,端口 B 为 10001000 - 所以我们发送以下内容(注意我们将二进制值转换为十进制):

Wire.beginTransmission(0x20);
Wire.write(0x12); //地址端口A
Wire.write(204); //要发送的值
Wire.endTransmission();
Wire.beginTransmission(0x20);
Wire.write(0x13); //地址端口B
Wire.write(136); //要发送的值
Wire.endTransmission();

一个完整的例子

// 引脚 15~17 到 GND,I2C 总线地址为 0x20
#include "Wire.h"
无效设置()
{
Wire.begin(); // 唤醒 I2C 总线
// 将 I/O 引脚设置为输出
Wire.beginTransmission(0x20);
Wire.write(0x00); // IODIRA 寄存器
Wire.write(0x00); // 将所有端口 A 设置为输出
Wire.endTransmission();
Wire.beginTransmission(0x20);
Wire.write(0x01); // IODIRB 寄存器
Wire.write(0x00); // 将所有端口 B 设置为输出
Wire.endTransmission();
}
无效二进制计数()
{
对于(字节 a=0;a<256;a++)
{
Wire.beginTransmission(0x20);
Wire.write(0x12); // GPIOA
Wire.write(a); // 端口 A
Wire.endTransmission();
Wire.beginTransmission(0x20);
Wire.write(0x13); // 通用输入输出接口
Wire.write(a); // 端口 B
Wire.endTransmission();
}
}
无效循环()
{
二进制计数();
延迟(500);
}

使用引脚作为输入
尽管这可能看起来像是一个简单的演示,但它的创建展示了如何使用输出。所以现在您知道如何控制设置为输出的 I/O 引脚了。请注意,您不能从每个引脚提供超过 25 mA 的电流,因此如果切换更高的电流负载,请使用晶体管和外部电源等。
现在让我们扭转局面,继续使用 I/O 引脚作为数字输入。MCP23017 I/O 引脚默认为输入模式,因此我们只需启动 I2C 总线。然后在 void loop() 或其他函数中,我们所做的就是设置寄存器的地址来读取和接收一个字节的数据。


// 引脚 15~17 到 GND,I2C 总线地址为 0x20
#include "Wire.h"
字节输入=0;
无效设置()
{
序列号.开始(9600);
Wire.begin(); // 唤醒 I2C 总线
}
无效循环()
{
Wire.beginTransmission(0x20);
Wire.write(0x13); // 将 MCP23017 内存指针设置为 GPIOB 地址
Wire.endTransmission();
Wire.requestFrom(0x20, 1); // 从 MCP20317 请求一个字节的数据
输入=Wire.read(); // 将传入的字节存储到“输入”中
if (inputs>0) // 如果按钮被按下
{
Serial.println(输入,BIN);// 以二进制显示GPIOB寄存器的内容
延迟(200);// 去抖动
}
}

使用 Adafruit 库
1. 按钮示例
#include
#include “Adafruit_MCP23017.h”


// MCP23017 I/O 扩展器的基本引脚读取和上拉测试
// 公共区域!


// 将扩展器的引脚 #12 连接到模拟 5(i2c 时钟)
// 将扩展器的引脚 #13 连接到模拟 4(i2c 数据)
// 将扩展器的引脚#15、16 和 17 接地(地址选择)
// 将扩展器的引脚 #9 连接到 5V(电源)
// 将扩展器的引脚 #10 接地(公共接地)
//通过〜10kohm电阻将引脚#18连接到5V(复位引脚,低电平有效)


// 输入#0 在引脚 21 上,因此连接一个按钮或从那里切换到地


Adafruit_MCP23017 mcp;


无效设置(){
mcp.begin(); // 使用默认地址 0


mcp.pinMode(0,输入);
mcp.pullUp(0, HIGH); // 在内部开启 100K 上拉


pinMode(13,输出);// 使用 p13 LED 作为调试
}






无效循环(){
// LED 将“回显”按钮
数字写入(13,mcp.digitalRead(0));
}
2. 中断示例
// 安装 LowPower 库以获得可选的睡眠支持。
// 有关用法的详细信息,请参阅 loop() 函数注释。
//#include


#include
#include


Adafruit_MCP23017 mcp;


字节 LEDPin=13;


// 来自 MCP 的中断将由该 PIN 处理
字节arduinoIntPin=3;


// ... 还有这个中断向量
字节 arduinoInterrupt=1;


volatile boolean wakeByInterrupt = false;


// MCP 上的两个引脚(端口 A/B,其中一些按钮已设置。)
// 按钮将引脚连接到地,引脚被拉起。
字节 mcpPinA=7;
字节 mcpPinB=15;


无效设置(){


序列号.开始(9600);
Serial.println("MCP23007 中断测试");


pinMode(arduinoIntPin,输入);


mcp.begin(); // 使用默认地址 0


// 我们镜像 INTA 和 INTB,因此在 MCP 和 Arduino 之间只需要一条线用于 int 报告
// INTA/B 不会浮动
// INT 将发出 LOW 信号
mcp.setupInterrupts(true,false,LOW);


// 端口 A 上的按钮配置
// 当引脚被按钮接地时将触发中断
mcp.pinMode(mcpPinA,输入);
mcp.pullUp(mcpPinA,高);// 在内部开启 100K 上拉
mcp.setupInterruptPin(mcpPinA,FALLING);


// 类似,但在端口 B。
mcp.pinMode(mcpPinB,输入);
mcp.pullUp(mcpPinB, HIGH); // 在内部打开 100K 上拉电阻
mcp.setupInterruptPin(mcpPinB,FALLING);


// 我们将从 int 例程中设置一个用于闪烁的引脚
pinMode(ledPin,输出);// 使用 p13 LED 作为调试


}


// int 处理程序只会发出 int 已经发生的信号
// 我们将从主循环中完成工作。
无效intCallBack(){
唤醒中断=真;
}


无效句柄中断(){


// 从 INT 获取 MCP 的更多信息
uint8_t pin=mcp.getLastInterruptPin();
uint8_t val=mcp.getLastInterruptPinValue();


// 我们将根据触发中断的 PIN 使 LED 闪烁 1 或 2 次
// 3 和 4 个 flases 应该是不可能的条件......只是为了调试。
uint8_t 闪烁=4;
如果(pin==mcpPinA)闪烁=1;
如果(pin==mcpPinB)闪烁=2;
if(val!=LOW) 闪烁=3;


// 模拟一些与此相关的输出
for(int i=0;i<闪烁;i++){
延迟(100);
数字写入(ledPin,HIGH);
延迟(100);
数字写入(ledPin,低);
}


// 我们必须等待中断条件完成
// 否则我们可能会在持续状态下进入睡眠状态,并且再也不会醒来。
// 因为,需要一个动作来清除 INT 标志,并允许它再次触发。
// 请参阅数据表了解数据。
而(!(mcp.digitalRead(mcpPinB)&& mcp.digitalRead(mcpPinA)));
// 并清除排队的 INT 信号
清洁中断();
}


// 方便按钮触发的中断
// 由于弹跳问题,通常会发出一些信号
无效的清洁中断(){
EIFR=0x01;
唤醒中断=假;
}


/**
*主要程序:让arduino睡觉,并在中断时醒来。
* 睡眠需要 LowPower 库或类似库,但此处模拟睡眠。
* 如数据表所述,实际上可以让 MCP 在待机时仅消耗 1uA,
* 但是没有待机模式。这一切都归结为以电流不流动的方式设置每个引脚。
* 并且您可以在等待时等待中断。
*/
无效循环(){


// 在进入睡眠/等待之前启用中断
// 我们为 arduino INT 处理程序设置了一个回调。
attachInterrupt(arduinoInterrupt,intCallBack,FALLING);


//模拟深度睡眠
while(!awakenByInterrupt);
// 或者让 arduino 睡觉,这个库很棒,如果你有的话。
//LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF);


// 在处理中断时禁用中断。
分离中断(arduinoInterrupt);


如果(唤醒ByInterrupt)句柄中断();
}

代码
https://github.com/adafruit/Adafruit-MCP23017-Arduino-Library

更多回帖

发帖
×
20
完善资料,
赚取积分