Extra digitale pinnen voor je Arduino

Wat te doen als je te weinig pinnen hebt voor een project? Dan gebruik je een zogenaamde bus expander (ook wel bus extender genoemd). Bij de Arduino gebruikt men doorgaans het woord "pin" in plaats van "bus". Op deze pagina ontkom ik er niet aan om ook het woord "bus" te gebruiken (in de betekenis van een aansluitmogelijkheid voor een elektrisch apparaat).

In plaats van bus expanders worden vaak schuifregisters gebruikt. Schuifregisters zijn erg goedkoop, maar hebben veel beperkingen. Omdat bus expanders inmiddels tamelijk goedkoop zijn en veel voordelen hebben, begin ik met deze oplossing te beschrijven. Als je zowel extra INPUT als OUTPUT pinnen nodig hebt of interrupt pinnen, dan zijn schuifregisters sowieso niet geschikt.

Extra digitale pinnen via bus expanders

Er zijn vier soorten veel gebruikte bus expanders: voor acht extra invoer/uitvoer pinnen en voor 16 extra invoer/uitvoer pinnen; beide in de uitvoeringen met een I²C of SPI interface. Het is niet moeilijk ze te programmeren of aan te sluiten en ze werken fantastisch. Bij de meeste expanders is het basis I²C adres 0x20, maar dat kan vaak verhoogd worden tot maximaal 0x27 (in decimale getallen is dat van 32 tot 29). Je kunt dus meerdere bus expanders gelijk aansluiten als je het adres steeds iets anders instelt. Ik testte een aantal veel gebruikte uitvoeringen.

MCP23017 en MCP23S17

Deze twee chips zijn identiek, behalve dat de een I²C gebruikt en de ander SPI. De chips geven je 16 extra pinnen. De pinnen van deze chip werken vrijwel hetzelfde als de pinnen van een Arduino. Je kunt maximaal 8 chips tegelijk aansluiten, waarbij je het adres verschillend moet maken. Dat doe je door een of meer van de drie pootjes A0, A1 en A2 aan VCC te leggen. Je kunt dus maximaal 8 × 16 = 128 extra poorten realiseren. Waarschijnlijk kun je SPI en I²C combineren zodat je tot 256 kunt komen, maar dat heb ik niet getest. De chip is erg makkelijk in gebruik en biedt veel functionaliteit. Zo hebben alle pinnen een interne pull-up weerstand, die je programmatisch kunt kiezen. Alle pinnen kunnen gebruikt worden voor interrupts. De pinnen zijn georganisserd in twee blokken (A en B) van acht pinnen. Je kunt ze echter ook als één bank beschouwen waarbij de pinnen doorlopend genummerd zijn. Je kunt de pinnen desgewenst per blok tegelijk programmeren. De chip kan ook goed gebruikt worden met de snelle nodeMCU met een esp2866 of esp32 processor. Bijkomend voordeel is dan dat alle extra pinnen gelijk zijn (de standaard nodeMCU pinnen hebben some een vast pull-up en soms een vaste pull-down weerstand, dat kan verwarring geven).

MCP23017  (I²C)

pinout
Om de chip te kunnen programmeren moet je ofwel je eigen routines schrijven of Adafruit_MCP23017.h gebruiken. Deze bibliotheek is te vinden in bibliotheekbeheer. Als je hem hebt geïstalleerd krijg je een paar voorbeeldprogramma's die je via het menu Bestand→Voorbeelden in de IDE kunt vinden. Ik heb het hieronder staande voorbeeld eruit afgeleid.
#include "Adafruit_MCP23017.h"
/*
  Pin 12 van de expander gaat naar A5 (Arduino Uno) of 21 (Arduino Mega) (I2C clock: SCL)
  Pin 13 van de expander gaat naar A4 (Arduino Uno) of 20 (Arduino Mega) (I2C data:  SDA)
  Pinnen 15, 16 en 17 van de expander gaan naar GND (standaard I2C adres)
  Pin 9 van de expander gaat naar VCC
  Pin 10 van de expander gaat naar GND
  Pin 18 van de expander gaat via een weerstand van ca 10 kOhm naar VCC (reset pin, laag = actief)
*/

Adafruit_MCP23017 mcp;

void setup() {
  mcp.begin();
  for (byte i = 0; i < 16; i++) mcp.pinMode(i, OUTPUT);
}

void loop() {
  for (byte i = 0; i < 16; i++) {
    mcp.digitalWrite(i, HIGH);
    delay(100);
  }
  for (byte i = 0; i < 16; i++) {
    mcp.digitalWrite(i, LOW);
    delay(100);
  }
  delay(1000);
}
Helaas ondersteunt de bibliotheek lang niet alle mogelijkheden van de chip. Je kunt daarom ook overwegen om hem direct te programmeren. Je hebt dan de bibliotheek Wire.h nodig, maar die is standaard aanwezig. Hieronder staat een voorbeeldprogramma dat beide blokken in een keer instelt op OUTPUT en vervolgens 16 ledjes om en om aanzet. Houdt er rekening mee dat ik er altijd vanuit ga dat je de laatste versie van de IDE gebruikt. Als je een heel oude versie hebt, dan moet je dit programma aanpassen (write vervangen door send). Omdat de chip het adres automatisch verhoogt kun je heel efficiënt beide blokken programmeren.
#include <Wire.h>

void setup() {
  Wire.begin();
  Wire.beginTransmission(0x20); // Het standaard I2C adres
  Wire.write(0); // 0 is blok A bij keuze van INPUT of OUTPUT
  Wire.write(0); // 0 zet alle pinnen van blok A in INPUT mode
  Wire.write(0); // 0 zet alle pinnen van blok B in INPUT mode
  Wire.endTransmission();
  delay(100);
}

byte patroon = B10101010;
void loop() {
  Wire.beginTransmission(0x20);
  Wire.write(0x12);             // 0x12 is blok "A" bij uitvoer
  Wire.write(patroon);          // Zet er het patroon in A
  Wire.write(patroon);          // Zet er het patroon in B
  Wire.endTransmission();
  delay(500);
  patroon = 0xFF - patroon;     // Keer bit patroon om
}

MCP23S17  (SPI)

pinout
Deze chip is identiek aan de MCP23017, behalve dat deze SPI gebruikt i.p.v. I²C. Je kunt je afvragen waarom je de SPI versie zou willen gebruiken, want voor SPI heb je vier draadjes (en dus pinnen van je Arduino) nodig. Een reden kan zijn dat je I²C al gebruikt met acht bus expanders. Een andere reden is dat SPI veel sneller is dan I²C. Voor deze chip kun je het best de bibliotheek MCP23S17.h gebruiken (te downloaden via github). Deze bibliotheek wordt ook beschreven in een van de officiële Arduino pagina's. Na installeren hen je een drietal voorbeeldprogramma's die laten zien hoe je acht of 16 pinnen direct aanstuurt. Helaas zijn deze programma's niet erg goed. Ik laat daarom hieronder mijn eigen variant zien. Deze zet in een klap alle invoer van de acht A pinnen op OUTPUT en laat vervolgens acht ledjes tegelijkertijd knipperen. Het kan ook met alle 16 pinnen! Om de acht pinnen van blok A op OUTPUT te zetten, schrijf je een 0 in de registers via byteWrite(IODIRA, 0); . Om alle pinnen van blok A op INPUT te zetten gebruik je byteWrite(IODIRA, 0xFF); . Door een andere waarde te kiezen kun je een deel op INPUT zetten en een ander deel op INPUT. Voor het aan en uitzetten van de ledjes moet je ook een waarde zetten voor alle leds. Eventueel kun je daarvoor de binaire notatie gebruiken, zoals in B10101010 (zet de ledjes om en om aan).
#include <MCP23S17.h>
/*
  Pin 11 van de expander gaat naar 10 (meeste Arduino's) of 53 (Arduino Mega) (CS/SS)
  Pin 12 van de expander gaat naar 13 (meeste Arduino's) of 52 (Arduino Mega) (SCK)
  Pin 13 van de expander gaat naar 11 (meeste Arduino's) of 51 (Arduino Mega) (MOSI)
  Pin 14 van de expander gaat naar 12 (meeste Arduino's) of 50 (Arduino Mega) (MISO)
  Pinnen 15, 16 en 17 van de expander gaan naar GND (standaard adres 0)
  Pin 9 van de expander gaat naar VCC
  Pin 10 van de expander gaat naar GND
  Pin 18 van de expander gaat via een weerstand van ca 10 kOhm naar VCC (reset pin, laag = actief)
*/
MCP iochip(0, 10); // Adres = 0, CS gaat naar pin 10

void setup() {
  iochip.begin();
  iochip.byteWrite(IODIRA, 0); // Zet alle 8 pinnen van blok A in een keer op OUTPUT
}

byte patroon = B10101010;
void loop() {
  iochip.byteWrite(GPIOA, patroon); // Zet alle pinnen in een keer op AAN of UIT
  patroon = 0xFF - patroon;         // Keer aan en uit om
  delay(400);
}

PCF8574 (I²C)

Pinout van de PCF8574
Deze chip is heel populair omdat hij goedkoop is en goed werkt. Je krijgt per chip 8 extra pinnen (P0 t/m P7). Evenals bij de hiervoor besproken chips moet je de drie adrespootjes A0, A1 en A2 aan aarde (GND) leggen, anders gaat het adres springen. Als je een ander adres dan de standaard 0x20 nodig hebt, dan kun je een of meer van deze adrespootjes aan VCC leggen. Als je geavanceerde dingen wilt doen, dan kun je een bibliotheek gebruiken, maar voor simpele toepassingen kun je de module ook direct aansturen. Elke pin is een bit van een binair getal zoals B11100011 (lees van rechts naar links, dus zet hiermee bijvoorbeeld de twee eerste en de drie laatste ledjes aan, de rest uit). In het plaatje hieronder zit de meest rechtse led aan P0 en de meest linkse aan P7.
Uitvoer
De ledjes aangezet volgens het patroon B11100011

In de sketch hieronder zie je hoe je acht ledjes in een keer kunt aan- en uitzetten.
#include <Wire.h>
#define adres 0x20
void setup()
{
  Wire.begin();
}

void loop()
{
  Wire.beginTransmission(0x20); // Verander als A0 t/m A2 niet aan GND liggen
  Wire.write(B10101010); // Zet om en om aan
  Wire.endTransmission();
  delay(500);
  Wire.beginTransmission(adres);
  Wire.write(B01010101); // Zet om en om aan, één verschoven t.o.v. vorige keer
  Wire.endTransmission();
  delay(500);
}

Extra uitvoerpinnen, bijvoorbeeld voor led lampjes

Naast het gebruik van een bus-expander zijn er nog andere mogelijkheden:
  1. Gebruik een truc.
  2. Gebruik van een schuifregister.
  3. Gebruik van een speciaal IC voor multiplexing.
  4. Gebruik van meerdere Arduino's.

Ik bespreek deze mogelijkheden hier in het kort.
  1. Als je veel ledjes hebben, maar er is er steeds maar een tegelijk aan, dan kun je goed gebruik maken van het feit dat je een ledje gerust verkeerd-om kunt aansluiten. Hij trekt dan vrijwel geen stroom. Je kunt dus van een serie ledjes de minpool in plaats van op GND alemaal op een pin aansluiten. Met die pin bepaal je dan of de ledjes aan of uit kunnen zijn. Een andere serie ledjes zet je dan op een andere pin. Ik gebruik een dergelijke truc bij mijn roulettewiel (37 ledjes, een zoemer en een drukknop op een standaard Arduino). Ook als je meerdere ledjes tegelijk aan wilt hebben dan zijn er mogelijkheden.
  2. Met de meeste shiftregisters (bijvoorbeeld de 74HC595) kun je acht ledjes onafhankelijk van elkaar aan- of uitzetten via drie pinnen van de Arduino. Als je de intensiteit wilt regelen dan heb je nog een pin nodig. Door meerdere shift registers aan elkaar te koppelen kun je nog veel meer ledjes aansturen. De 74HC595 chip is super goedkoop en wordt daarom veel gebruikt. Hij heeft wel enkele nadelen. Zo is een schema met meer dan één shift register al gauw erg onoverzichtelijk. Hoe je een shift register moet aansluiten, met voorbeelden kun je overal op internet vinden, alsmede op de officiële Arduino site ( zoek op de functie shiftOut() ).
  3. Er zijn diverse chips die bedoeld zijn om via multiplexing meerdere leds met slechts enkele pinnen te kunnen aansturen. De bekendste is waarschijnlijk wel de MAX7219 serie. Hiermee kunnen maximaal 64 ledjes worden aangestuurd via drie pinnen van de Arduino. In bijna elk goedkoop 8 bij 8 led display worden deze chips gebruikt, eveneens bij numerieke displays met acht-segmenten cijfers. Ik heb zelf deze chip nog niet los getest. Op de Maxim site is te zien hoe je de Max7219 aansluit. In je Arduino sketch kun je het best een speciale bibliotheek gebruiken.
  4. Gebruik van meerdere Arduino's is niet eens zo'n slechte oplossing als je bijvoorbeeld een aantal goedkope Arduino nano's hebt liggen. Je kunt Arduino's op heel veel manieren met elkaar laten communiceren: via I²C , via TX en RX of met gewoon een draadje waar je zelf een protocol voor bedenkt. In alle gevallen moet je GND van alle Arduino's met elkaar verbinden. Hoewel het erg meevalt om dit te realisren, zou het te ver voeren dit alles hier te beschrijven. Een voordeel van het gebruik van meerdere Arduino's is uiteraard dat je veel meer flexibiliteit hebt dan bij de eerder genoemde opties.