Week 11
This week at Fab Academy, we dived into how to connect multiple microcontrollers so they can work together. Either through wired or wireless networks. This is a key step in designing more complex, distributed, and modular systems.
Let's make an Attiny412 have a friend to talk to.
Since it's tiny I created a board to surface-mount it and place it over the breadboard:



Because I knew this was not going to be easy, the goal was to freely try the connections without the compromise of re-doing a full board.
The idea is simple; connect two Attiny412 via I2C, one would be the master who would tell the other, the slave.
Master would have a tilt switch and some leds.


The slave on the other hand, would be wired to a DFPlayer Mini, which I had tried during last week.

When not tilted ---> DFPlayer Mini plays song until done.
When tilted -------> DFPlayer would stops and the leds turn on.
So before going deep into I2C, I thougth it would be better to try the connection of the components within one board. And since Attiny412 doesn't have UART communication, for the purpose of possible debugging, I tried the code in the Barduino.
With ChatGPT's help we got it working.
#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>
#define TILT_PIN 3 // Tilt switch is connected to PA3 (pin 3)
#define LED_PIN 7 // LED is connected to PA7 (pin 7)
#define DFPLAYER_RX 1 // DFPlayer TX goes to this pin (PA1)
#define DFPLAYER_TX 2 // DFPlayer RX goes to this pin (PA2)
SoftwareSerial dfSerial(DFPLAYER_RX, DFPLAYER_TX); // RX, TX for software serial
DFRobotDFPlayerMini dfplayer;
bool playing = false; // Keeps track of whether audio is currently playing
void setup() {
pinMode(TILT_PIN, INPUT_PULLUP); // Set tilt pin as input with internal pull-up resistor
pinMode(LED_PIN, OUTPUT); // Set LED pin as output
dfSerial.begin(9600); // Start SoftwareSerial at 9600 baud
if (!dfplayer.begin(dfSerial)) { // Try to connect to DFPlayer
// If connection fails, stop here (optional)
while (true);
}
dfplayer.volume(20); // Set initial volume (0 to 30)
}
void loop() {
bool tilted = digitalRead(TILT_PIN) == LOW; // Check if tilt switch is tilted
if (tilted && !playing) {
digitalWrite(LED_PIN, LOW); // Turn off LED
dfplayer.play(1); // Start playing track 001.mp3
playing = true; // Remember that it's playing
}
else if (!tilted && playing) {
digitalWrite(LED_PIN, HIGH); // Turn on LED
dfplayer.stop(); // Stop playback
playing = false; // Remember that it's not playing
}
delay(200); // Wait a bit to avoid spamming the DFPlayer or reacting to noise
}
"Can you help me write a sketch for an ATtiny412 that:
Uses a tilt switch on pin PA3
Turns an LED on/off on PA7 based on the tilt state
Plays or stops audio using a DFPlayer Mini
Connects the DFPlayer via SoftwareSerial on PA1 (RX) and PA2 (TX)
Makes sure the audio only plays once per tilt and stops if tilt returns"
Understanding the code
I tried to understand the code from scratch to truly internalize it. First I declare the variables.
Then this part was a little confusing.
DFRobotDFPlayerMini dfplayer;
Aparently, this line creates an object named dfplayer from the DFRobotDFPlayerMini class. Other examples of "SomeLibraryClass nameYouChoose;" that you might recognize:
SoftwareSerial mySerial;
Adafruit_NeoPixel strip;
Servo myServo;
dfplayer.begin(...) is a function That means it requires some input (called "arguments") in parentheses — just like when you write:
Serial.begin(9600);
You’re telling Serial what speed to use. So, for the DFPlayer:
dfplayer.begin(dfSerial);
You're telling the dfplayer object: "Start using this Serial connection to talk to the actual DFPlayer module." Why dfSerial? Earlier in the code (or at least in most DFPlayer setups), you create a serial connection like this:
SoftwareSerial dfSerial(0, 1); // Or whatever pins you're using
So dfSerial is the software serial connection between your microcontroller (like an ATtiny or Arduino) and the DFPlayer board. That’s how they talk.
When the library function .begin() runs, it needs to know what connection to use — so we give it dfSerial.
Functions use parentheses to take input arguments.
dfplayer.begin(...) expects a serial connection as input.
You pass it dfSerial, because that’s the connection you made to the DFPlayer.
You learn what to pass in by reading the library documentation or examples.
while (true);
is an infinite loop that does nothing. It’s a clean way to halt a program if something goes wrong, like a hardware failure. It's the same as writing while (true){}
bool playing = false;
I'm creating a variable named playing that will keep track of whether music is currently playing. Right now, I’m saying it’s not playing.
🧠 Purpose:
It's a flag you can flip later in your code.
You use it to decide when to start or stop the DFPlayer.
It helps prevent triggering the same action over and over again when the switch doesn’t change.
Hey! I didn't show you how it worked! Look ;)
