I²C to DFPlayer Bridge with ATtiny85

This project turns an ATtiny85 into a small, dedicated I²C-to-DFPlayer Mini interface (or “bridge”). An external I²C master (e.g. Arduino Uno) can send simple I²C commands to the ATtiny85, which then translates them into the DFPlayer’s 10-byte serial protocol. You get full control of play, pause, stop, next, volume up/down, and specific-track selection—without burdening the master with low-level serial timing.

Key Features
• I²C Slave on ATtiny85 (address 0x42)
– Uses the USI-TWI hardware on PB0 (SDA) and PB2 (SCL)
– Pull-ups on SDA/SCL (≈4.7 kΩ) complete the bus
– TinyWireS library handles START/STOP and incoming bytes
• DFPlayer Mini Control via SoftwareSerial
– Runs at 9600 baud on PB3 (TX) and PB4 (RX) with SoftwareSerial
– Sends standard 10-byte command packets (including checksum)
– Visual feedback LED on PB1 blinks for every command sent
• Supported Commands (I²C → DFPlayer)
– Play track N
– Stop playback
– Pause & resume
– Next track
– Set absolute volume (0–30)
– Volume up/down
• Master Sketch Example
– Demonstrates how to send each command over I²C using Wire.h
– Sends one command every 2 s to showcase every DFPlayer function

How It Works

  1. Power-up: ATtiny85 boots at 8 MHz, initializes I²C slave on PB0/PB2 and serial port on PB3/PB4.
  2. Slave firmware registers a receive callback (receiveEvent). When the master writes 1–2 bytes, the ATtiny85 decodes the command and calls sendDFP(cmd,param) to issue the corresponding DFPlayer serial packet.
  3. The master simply does Wire.beginTransmission(0x42); Wire.write(cmd); [Wire.write(param)]; Wire.endTransmission();—no need to handle checksums or serial timing.
  4. The DFPlayer executes each command (play, stop, volume change, etc.), and the ATtiny85 LED blinks to confirm the action.

1. ATtiny85 Slave Sketch

• Platform/Clock: ATtiny85 running at internal 8 MHz.
• DFPlayer Interface:

  • Uses SoftwareSerial on PB3 (TX → DFPlayer RX) and PB4 (RX ← DFPlayer TX) at 9 600 baud.
  • Sends 10-byte command packets to the DFPlayer (play, stop, pause, next track, volume up/down, etc).
  • Blinks PB1 LED briefly on each command for visual feedback.
    • I²C Interface:
  • Uses TinyWireS USI-TWI slave on PB0 (SDA) and PB2 (SCL), address 0x42.
  • Implements receiveEvent() to parse one- or two-byte commands from the master and invoke the corresponding DFPlayer action.
    • Supported Commands (sent from master):
  • 0x01 + [trk] → Play track number [trk]
  • 0x02 → Stop playback
  • 0x03 → Pause
  • 0x04 → Resume
  • 0x05 → Next track
  • 0x06 + [vol] → Set absolute volume [vol] (0–30)
  • 0x07 → Volume up by 1
  • 0x08 → Volume down by 1
#include <SoftwareSerial.h>
#include <TinyWireS.h>

#define I2C_ADDR   0x42

// DFPlayer on SoftwareSerial: TX->PB3(D3), RX<-PB4(D4)
const uint8_t DF_TX    = 3;
const uint8_t DF_RX    = 4;
// LED on PB1(D1) for feedback
const uint8_t LED_PIN  = 1;

SoftwareSerial df(DF_RX, DF_TX);
uint8_t volumeLevel = 15;  // initial mid-volume (0–30)

void sendDFP(uint8_t cmd, uint16_t param) {
  uint8_t pkt[10] = {
    0x7E, 0xFF, 0x06, cmd, 0x00,
    uint8_t(param >> 8), uint8_t(param & 0xFF),
    0, 0, 0xEF
  };
  uint16_t sum = 0;
  for (uint8_t i = 1; i <= 6; i++) sum += pkt[i];
  uint16_t chk = 0xFFFF - sum + 1;
  pkt[7] = highByte(chk);
  pkt[8] = lowByte(chk);

  for (uint8_t i = 0; i < 10; i++) df.write(pkt[i]);

  digitalWrite(LED_PIN, HIGH);
  delay(30);
  digitalWrite(LED_PIN, LOW);
}

// I²C receive callback: master writes one or two bytes
void receiveEvent(uint8_t howMany) {
  if (howMany < 1) return;
  uint8_t cmd = TinyWireS.read();

  switch (cmd) {
    case 0x01: {  // Play track N
        if (TinyWireS.available()) {
          uint8_t trk = TinyWireS.read();
          sendDFP(0x03, trk);
        }
        break;
      }
    case 0x02:     // Stop
      sendDFP(0x16, 0x0000);
      break;
    case 0x03:     // Pause
      sendDFP(0x0E, 0x0000);
      break;
    case 0x04:     // Resume
      sendDFP(0x0D, 0x0000);
      break;
    case 0x05:     // Next track
      sendDFP(0x01, 0x0000);
      break;
    case 0x06: {   // Set volume V
        if (TinyWireS.available()) {
          uint8_t vol = TinyWireS.read();
          if (vol > 30) vol = 30;
          volumeLevel = vol;
          sendDFP(0x06, volumeLevel);
        }
        break;
      }
    case 0x07:     // Volume up
      if (volumeLevel < 30) volumeLevel++;
      sendDFP(0x06, volumeLevel);
      break;
    case 0x08:     // Volume down
      if (volumeLevel > 0) volumeLevel--;
      sendDFP(0x06, volumeLevel);
      break;
      // add more DFPlayer commands here if needed
  }
}

void setup() {
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);

  // 1) init I²C slave on PB0/PB2
  TinyWireS.begin(I2C_ADDR);
  TinyWireS.onReceive(receiveEvent);

  // 2) init DFPlayer serial on PB3/PB4
  df.begin(9600);
  delay(200);
}

void loop() {
  TinyWireS_stop_check();
  // no further background tasks
}



2. Arduino Master Sketch

How it works

  1. Serial Input
    • You type commands into the Serial Monitor (115 200 baud).
    • The code reads a line, splits into a text keyword and optional integer.
  2. Command->I²C
    • Depending on the keyword, it calls send1(cmd) or send2(cmd,param), which wrap Wire.beginTransmission().endTransmission().
    • These send 1–2 bytes to the ATtiny85 at address 0x42.
  3. ATtiny85 Slave
    • Its receiveEvent() sees the incoming byte(s) and invokes sendDFP() with the appropriate DFPlayer command code and parameter.

With this in place, you simply open Serial Monitor, type “play 1” to hear track 1, “vol 3” to set volume, “next” to skip, and so on—all via I²C → DFPlayer bridge on your ATtiny85.Create VideoCreate Practice QuestionCreate Practice Test

#include <Wire.h>

const uint8_t SLAVE_ADDR = 0x42;

// Text commands mapping to slave codes:
#define CMD_PLAY_TRACK 0x01
#define CMD_STOP       0x02
#define CMD_PAUSE      0x03
#define CMD_RESUME     0x04
#define CMD_NEXT       0x05
#define CMD_SET_VOL    0x06
#define CMD_VOL_UP     0x07
#define CMD_VOL_DOWN   0x08

void setup() {
  Serial.begin(115200);
  while (!Serial) {
    ;
  }
  Wire.begin();
  Serial.println(F("I2C Master ready. Enter commands:"));
  Serial.println(F(" play N   stop   pause   resume"));
  Serial.println(F(" next     vol N  up      down"));
}

void loop() {
  if (!Serial.available()) return;

  String line = Serial.readStringUntil('\n');
  line.trim();
  if (line.length() == 0) return;

  // Tokenize
  char cmd[16];
  int  val = 0;
  int  args = sscanf(line.c_str(), "%15s %d", cmd, &val);

  if (strcasecmp(cmd, "play") == 0 && args == 2) {
    send2(CMD_PLAY_TRACK, val);
    Serial.print(F("→ Play track ")); Serial.println(val);
  }
  else if (strcasecmp(cmd, "stop") == 0) {
    send1(CMD_STOP);
    Serial.println(F("→ Stop"));
  }
  else if (strcasecmp(cmd, "pause") == 0) {
    send1(CMD_PAUSE);
    Serial.println(F("→ Pause"));
  }
  else if (strcasecmp(cmd, "resume") == 0) {
    send1(CMD_RESUME);
    Serial.println(F("→ Resume"));
  }
  else if (strcasecmp(cmd, "next") == 0) {
    send1(CMD_NEXT);
    Serial.println(F("→ Next track"));
  }
  else if (strcasecmp(cmd, "vol") == 0 && args == 2) {
    send2(CMD_SET_VOL, val);
    Serial.print(F("→ Set volume ")); Serial.println(val);
  }
  else if (strcasecmp(cmd, "up") == 0) {
    send1(CMD_VOL_UP);
    Serial.println(F("→ Volume up"));
  }
  else if (strcasecmp(cmd, "down") == 0) {
    send1(CMD_VOL_DOWN);
    Serial.println(F("→ Volume down"));
  }
  else {
    Serial.println(F("Unknown command"));
  }
}

// Send one‐byte command
void send1(uint8_t c) {
  Wire.beginTransmission(SLAVE_ADDR);
  Wire.write(c);
  Wire.endTransmission();
}

// Send two‐byte command + parameter
void send2(uint8_t c, uint8_t p) {
  if (p > 30) p = 30;          // clamp volume if needed
  Wire.beginTransmission(SLAVE_ADDR);
  Wire.write(c);
  Wire.write(p);
  Wire.endTransmission();
}



3. Wiring Diagram

ATtiny85 (DIP-8, flat side up) connections:
• Pin 8 (VCC) → +5 V
• Pin 4 (GND) → GND
• PB0 (D0, pin 5) → I²C SDA ←→ master SDA (A4)
– 4.7 kΩ pull-up resistor from PB0 to +5 V
• PB2 (D2, pin 7) → I²C SCL ←→ master SCL (A5)
– 4.7 kΩ pull-up resistor from PB2 to +5 V
• PB3 (D3, pin 2) → DFPlayer “RX” pin (SoftwareSerial TX)
• PB4 (D4, pin 3) → DFPlayer “TX” pin (SoftwareSerial RX)
• PB1 (D1, pin 6) → LED (with 220 Ω resistor) → GND for visual feedback

DFPlayer Mini connections:
• VCC → +5 V (shared)
• GND → GND (shared)
• RX → PB3 (ATtiny85)
• TX → PB4 (ATtiny85)

Arduino (e.g. Uno) master I²C:
• SDA (A4) → PB0 (ATtiny85)
• SCL (A5) → PB2 (ATtiny85)
• GND → common GND

Note: Ensure all devices share the same 5 V and ground. The two 4.7 kΩ pull-ups on SDA/SCL complete the I²C bus.

With this setup you can fully control your DFPlayer Mini—play, stop, change volume, etc.—via simple I²C commands from your Arduino master, while the ATtiny85 offloads the bit-banged serial protocol.

Why Use This Bridge?
• Offloads timing-critical DFPlayer serial from your main Arduino or host CPU.
• Exposes a simple, byte-oriented I²C interface—ideal for high-level control or distributed systems.
• Fits into 8-pin DIP and uses only five pins (two for I²C, two for serial, one for LED).

In one small ATtiny85 you get a robust, easy-to-use I²C gateway to your DFPlayer Mini—perfect for projects where you want to delegate audio playback to a tiny coprocessor.

Leave a Reply