Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion lib/mcp23009e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,38 @@ exercise the driver against the `TwoWire` mock from
make test-native
```

## Examples

| Example | What it does |
|---------|--------------|
| [`simon_game`](examples/simon_game/) | Memory game inspired by *Simon Says*. The board flashes an increasingly long sequence using the on-board LEDs and the player must reproduce it using the D-PAD. Score is printed to the serial monitor. |
| [`reaction_timer`](examples/reaction_timer/) | Reaction-time trainer. After a random delay, the red LED lights up and the player must press the UP button as quickly as possible. The measured reaction time is printed in milliseconds. |
| [`music_player`](examples/music_player/) | Turn the D-PAD into a mini jukebox. Each direction plays a different 8-bit melody on the on-board speaker (Tetris, Mario, Zelda, or Pokémon). |
| [`morse_code`](examples/morse_code/) | Enter Morse code using the D-PAD. Short and long presses on the UP button generate dots and dashes, while the DOWN button decodes the current sequence and prints the matching letter. |

### Building an example

List available examples (each line is a runnable Make target):

```bash
make list-examples
```

Then flash one — copy a line from the listing:

```bash
make flash-mcp23009e/morse_code
```

This builds, uploads, and opens the serial monitor at 115200 baud.

To reliably capture the first lines printed at boot (which the interactive monitor often misses), swap `flash-` for `capture-`:

```bash
make capture-mcp23009e/morse_code # 10 seconds, OpenOCD reset, stdout
make capture-mcp23009e/morse_code DURATION=30 # longer window
```

## License

GPL-3.0-or-later — see [LICENSE](../../LICENSE).
GPL-3.0-or-later — see [LICENSE](../../LICENSE).
105 changes: 105 additions & 0 deletions lib/mcp23009e/examples/MorseCode/morse_code.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// SPDX-License-Identifier: GPL-3.0-or-later
Comment thread
Charly-sketch marked this conversation as resolved.
//
// MorseDecoder — enter Morse code using the UP button on the D-PAD.
// A short UP press (< 300 ms) inputs a dot.
// A long UP press (>= 300 ms) inputs a dash.
// Press DOWN to decode the current Morse sequence.

#include <Arduino.h>
#include <MCP23009E.h>
#include <Wire.h>

#include <map>

constexpr uint32_t kDotThresholdMs = 300;
constexpr uint32_t kDebounceDelayMs = 50;

static const std::map<String, char> kMorseTable = {
{".-", 'A'}, {"-...", 'B'}, {"-.-.", 'C'}, {"-..", 'D'}, {".", 'E'}, {"..-.", 'F'},
{"--.", 'G'}, {"....", 'H'}, {"..", 'I'}, {".---", 'J'}, {"-.-", 'K'}, {".-..", 'L'},
{"--", 'M'}, {"-.", 'N'}, {"---", 'O'}, {".--.", 'P'}, {"--.-", 'Q'}, {".-.", 'R'},
{"...", 'S'}, {"-", 'T'}, {"..-", 'U'}, {"...-", 'V'}, {".--", 'W'}, {"-..-", 'X'},
{"-.--", 'Y'}, {"--..", 'Z'},
};

String code = "";

bool upWasPressed = false;
uint32_t upPressStart = 0;

TwoWire internalI2C(I2C_INT_SDA, I2C_INT_SCL);
MCP23009E expander(internalI2C, RST_EXPANDER, MCP23009_I2C_ADDR, INT_EXPANDER);

void decodeMorse() {
if (code == "") {
Serial.println("No Morse code to decode.");
return;
}

auto it = kMorseTable.find(code);
if (it != kMorseTable.end()) {
Serial.print("Decoded: ");
Serial.println(it->second);
} else {
Serial.print("Unknown code: ");
Serial.println(code);
}

code = "";
}

void setup() {
Serial.begin(115200);
while (!Serial && millis() < 2000) {
// Wait up to 2 s for the serial monitor.
}

internalI2C.begin();
expander.begin();

expander.setup(MCP23009_BTN_UP, MCP23009_DIR_INPUT, MCP23009_PULLUP);
expander.setup(MCP23009_BTN_DOWN, MCP23009_DIR_INPUT, MCP23009_PULLUP);

Serial.println("MorseDecoder");
Serial.println("UP short press: dot");
Serial.println("UP long press: dash");
Serial.println("DOWN: decode current code");
}

void loop() {
bool upPressed = expander.getLevel(MCP23009_BTN_UP) == MCP23009_LOGIC_LOW;
bool downPressed = expander.getLevel(MCP23009_BTN_DOWN) == MCP23009_LOGIC_LOW;

if (upPressed && !upWasPressed) {
upPressStart = millis();
upWasPressed = true;
}

if (!upPressed && upWasPressed) {
uint32_t pressDuration = millis() - upPressStart;

if (pressDuration < kDotThresholdMs) {
code += ".";
Serial.println("Dot");
} else {
code += "-";
Serial.println("Dash");
}

Serial.print("Current code: ");
Serial.println(code);

upWasPressed = false;
delay(kDebounceDelayMs);
}

if (downPressed) {
decodeMorse();

while (expander.getLevel(MCP23009_BTN_DOWN) == MCP23009_LOGIC_LOW) {
delay(10);
}

delay(kDebounceDelayMs);
}
}
119 changes: 119 additions & 0 deletions lib/mcp23009e/examples/MusicPlayer/music_player.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: GPL-3.0-or-later
Comment thread
Charly-sketch marked this conversation as resolved.
//
// MelodyPlayer — play a different 8-bit melody on the on-board buzzer
// depending on which D-PAD button is pressed: UP plays Tetris, DOWN plays
// Mario, LEFT plays Zelda and RIGHT plays Pokemon.
//
// The MCP23009E sits on the STeaMi internal I2C bus, so spin up a
// dedicated TwoWire pointed at the variant pin macros and hand it to the
// driver. Open the serial monitor at 115200 baud to see the active button.

#include <Arduino.h>
#include <MCP23009E.h>
#include <Wire.h>

#include <functional>
#include <map>

#define NOTE_E4 330
#define NOTE_G4 392
#define NOTE_C5 523
#define NOTE_E5 659
#define NOTE_G5 784
#define NOTE_A5 880
#define NOTE_B5 988
#define NOTE_F5 698
#define NOTE_A4 440
#define NOTE_F4 349
#define NOTE_D5 587
#define NOTE_B4 494

int tetrisMelody[] = {NOTE_E5, NOTE_B4, NOTE_C5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_A4,
NOTE_A4, NOTE_C5, NOTE_E5, NOTE_D5, NOTE_C5, NOTE_B4, NOTE_C5,
NOTE_D5, NOTE_E5, NOTE_C5, NOTE_A4, NOTE_A4};
int tetrisDurations[] = {200, 100, 100, 200, 100, 100, 200, 100, 100, 200,
100, 100, 150, 100, 200, 200, 200, 200, 400};

int marioMelody[] = {NOTE_E5, NOTE_E5, 0, NOTE_E5, 0, NOTE_C5, NOTE_E5, 0, NOTE_G5, 0, NOTE_G4, 0};
int marioDurations[] = {150, 150, 150, 150, 150, 150, 150, 150, 300, 300, 300, 300};

int zeldaMelody[] = {NOTE_A4, NOTE_F4, NOTE_C5, NOTE_A4, NOTE_F4,
NOTE_C5, NOTE_A4, NOTE_G4, NOTE_E4};
int zeldaDurations[] = {300, 300, 400, 300, 300, 400, 200, 200, 600};

int pokemonMelody[] = {NOTE_D5, NOTE_D5, NOTE_D5, NOTE_G4, NOTE_D5, NOTE_C5, NOTE_B4,
NOTE_A4, NOTE_G4, NOTE_D5, NOTE_E5, NOTE_F5, NOTE_G4};
int pokemonDurations[] = {200, 200, 200, 400, 200, 200, 200, 400, 200, 200, 200, 200, 600};

void playTetris() {
for (int i = 0; i < 19; i++) {
tone(SPEAKER, tetrisMelody[i], tetrisDurations[i]);
delay(tetrisDurations[i] * 1.3);
noTone(SPEAKER);
}
}

void playPokemon() {
for (int i = 0; i < 13; i++) {
tone(SPEAKER, pokemonMelody[i], pokemonDurations[i]);
delay(pokemonDurations[i] * 1.3);
noTone(SPEAKER);
}
}

void playZelda() {
for (int i = 0; i < 9; i++) {
tone(SPEAKER, zeldaMelody[i], zeldaDurations[i]);
delay(zeldaDurations[i] * 1.3);
noTone(SPEAKER);
}
}

void playMario() {
for (int i = 0; i < 12; i++) {
if (marioMelody[i] == 0) {
noTone(SPEAKER);
} else {
tone(SPEAKER, marioMelody[i], marioDurations[i]);
}
delay(marioDurations[i] * 1.3);
noTone(SPEAKER);
}
}

static const std::map<uint8_t, std::function<void()>> kMelodies = {
{MCP23009_BTN_UP, playTetris},
{MCP23009_BTN_DOWN, playMario},
{MCP23009_BTN_LEFT, playZelda},
{MCP23009_BTN_RIGHT, playPokemon},
};

TwoWire internalI2C(I2C_INT_SDA, I2C_INT_SCL);
MCP23009E expander(internalI2C, RST_EXPANDER, MCP23009_I2C_ADDR, INT_EXPANDER);

void setup() {
Serial.begin(115200);
while (!Serial && millis() < 2000) {
// Wait up to 2 s for a connected monitor — on the STeaMi USB CDC
// !Serial stays true until the host enumerates.
}

for (const auto& [pinNumber, melody] : kMelodies) {
expander.setup(pinNumber, MCP23009_DIR_INPUT, MCP23009_PULLUP);
}

internalI2C.begin();
expander.begin();

Serial.println("MusicPlayer — UP: Tetris, DOWN: Mario, LEFT: Zelda, RIGHT: Pokemon.");
}

void loop() {
for (const auto& [pinNumber, melody] : kMelodies) {
if (expander.getLevel(pinNumber) == MCP23009_LOGIC_LOW) {
melody();
Comment thread
Charly-sketch marked this conversation as resolved.
break;
}
}
delay(100);
}
49 changes: 49 additions & 0 deletions lib/mcp23009e/examples/ReactionTimer/reaction_timer.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-3.0-or-later
Comment thread
Charly-sketch marked this conversation as resolved.
//
// ReactionTime — measure how fast you press the D-PAD button after the
// red LED lights up. The LED turns on after a random delay between 300
// and 1500 ms; your reaction time is printed to the serial monitor.
//
// The MCP23009E sits on the STeaMi internal I2C bus, so spin up a
// dedicated TwoWire pointed at the variant pin macros and hand it to the
// driver. Open the serial monitor at 115200 baud to see your results.

#include <Arduino.h>
#include <MCP23009E.h>
#include <Wire.h>

TwoWire internalI2C(I2C_INT_SDA, I2C_INT_SCL);
MCP23009E expander(internalI2C, RST_EXPANDER, MCP23009_I2C_ADDR, INT_EXPANDER);

void setup() {
Serial.begin(115200);
while (!Serial && millis() < 2000) {
}

internalI2C.begin();
expander.begin();
Comment thread
Charly-sketch marked this conversation as resolved.

expander.setup(MCP23009_BTN_UP, MCP23009_DIR_INPUT, MCP23009_PULLUP);

pinMode(LED_RED, OUTPUT);
randomSeed(analogRead(0));
}

void loop() {
while (expander.getLevel(MCP23009_BTN_UP) == MCP23009_LOGIC_LOW) {
delay(10);
}

delay(random(300, 1500));
digitalWrite(LED_RED, HIGH);
uint32_t startTime = millis();
while (true) {
if (expander.getLevel(MCP23009_BTN_UP) == MCP23009_LOGIC_LOW) {
Comment thread
Charly-sketch marked this conversation as resolved.
uint32_t reactionTime = millis() - startTime;
digitalWrite(LED_RED, LOW);
Serial.println("Reaction time: " + String(reactionTime) + " ms");
break;
}
}
delay(2000);
}
Loading
Loading