ATTiny85 SoftwareSerial and TinyWireS

Hi all,

I bought a DFPlayer Mini module. This module receives command via Serial communication. It works fine. I just want to talk to it by i2c instead of Serial using an ATTiny85.

Programming the ATTiny85 gives me some challanges.

I use a nano as i2c master. It has to send commands to the ATTiny. ATTiny should receive these commands and send them to DFPlayer using SoftwareSerial.

The serial part works. The i2c part does not.

My question is: Are SoftwareSerial and TinyWireS incompatible?

Hi again,

Here are some code. First the ATtiny85 I2C slave code:

#include "TinyWireS.h"    
#define I2C_SLAVE_ADDR  0x25  

#include "SoftwareSerial.h"
const int Rx = 3; // this is physical pin 2
const int Tx = 4; // this is physical pin 3
SoftwareSerial mySerial(Rx, Tx);

#define Start_Byte 0x7E
#define Version_Byte 0xFF
#define Command_Length 0x06
#define End_Byte 0xEF
#define Acknowledge 0x00 //Returns info with command 0x41 [0x01: info, 0x00: no info]

int i=0;
char buf[12];
char myCmd = ' ';
int myPar = 0;
unsigned long tid;
int nr;

void setup() {
  pinMode(Rx, INPUT);
  pinMode(Tx, OUTPUT);
  TinyWireS.begin(I2C_SLAVE_ADDR);      // init I2C Slave mode
  TinyWireS.onReceive(receiveEvent);
  mySerial.begin(9600); // send serial data at 9600 bits/sec 
  mySerial.println("Serial ready"); // For debugging
//  execute_CMD(0x06,0,5);
  delay(500);
//  execute_CMD(0x03,0,1);
  tid = millis();
}

void receiveEvent(uint8_t howMany) {
  mySerial.println("Got data");   // For debugging
  char c=' ';
  while (1 < TinyWireS.available()) { // loop through all but the last
    c = TinyWireS.receive();      // receive byte as a character
  }
  int x = TinyWireS.receive();    // receive byte as an integer
  myCmd = c;
  myPar = x;
}

void execute_CMD(byte CMD, byte Par1, byte Par2) // Excecute the command and parameters
{
 // Calculate the checksum (2 bytes)
 int16_t checksum = -(Version_Byte + Command_Length + CMD + Acknowledge + Par1 + Par2);
 // Build the command line
 byte Command_line[10] = { Start_Byte, Version_Byte, Command_Length, CMD, Acknowledge, Par1, Par2, checksum >> 8, checksum & 0xFF, End_Byte};
 //Send the command line to the module
 for (byte k=0; k<10; k++)
 {
  mySerial.write( Command_line[k]);
 }
}

void loop() {
  // put your main code here, to run repeatedly:
  if (myCmd == 'p') {
    execute_CMD(0x03,0,myPar);
    myCmd = ' ';
  }
  if (myCmd == 'v') {
    execute_CMD(0x06,0,myPar);
    myCmd = ' ';
  }
  if (myCmd != ' ') {
    mySerial.println(myCmd);
    mySerial.println(myPar);
    myCmd = ' ';
  }
  if (mySerial.available()>0){
    buf[i]= mySerial.read(); 
    if (int(buf[i])==13 || int(buf[i])==10 ){ //If Carriage return has been reached
      mySerial.println(buf);
      for(int x=0;x<=10;x++){
        buf[x]=' ';
      }
      i=0; //start over again
    } //if enter
    i++; 
  } //If mySerial.available  
  if (millis() > (tid + 5000)) {
    tid=millis();
    nr=nr+1;
    TinyWireS_stop_check;
    mySerial.print("Tick "); // For debugging
    mySerial.println(nr);
  }
}

I am monotoring the serial communication from ATtiny85 through a FTDI USB-TTL device. I receive “Serial ready” and all the ticks from the end of the loop. But I never receive “Got data” from the receiveEvent. If I hook up an LED on SDA line between master and slave, I see a week blink as expected each time data is send from Arduino Nano. There are pull-up resistors on SDA and SCL lines.

This code runs on the master. That is an Arduino Nano.

#include <Wire.h>

void setup() {
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);
  delay(100);
  digitalWrite(13, LOW);
  Wire.begin(); // join i2c bus (address optional for master)
  Wire.beginTransmission(0x25); // transmit to device #25
  Wire.write("v");          // sends a char
  Wire.write(5);            // sends one byte
  Wire.endTransmission();   // stop transmitting
  delay(100);
  digitalWrite(13, HIGH);
  delay(100);
  digitalWrite(13, LOW);
}

byte x = 0;

void loop() {
  Wire.beginTransmission(0x25); // transmit to device #25
  Wire.write("p");           // sends a char
  Wire.write(1);             // sends one byte
  Wire.endTransmission();    // stop transmitting
  digitalWrite(13, HIGH);
  delay(100);
  digitalWrite(13, LOW);
  x++;
  delay(1000);
}

If SoftwareSerial and TinyWireS are incompatible, I should not use more time looking in this direction.

Reason could be something like interrupts or timers, but I do not know much about this. If anybody could confirm or deny that there is a problem here, I would be happy.

If you have combined SoftwareSerial and TinyWireS on an ATtiny85 without problems, then please let me know. You do not have to solve my problem, just let me know.

SoftwareSerial is incompatible with almost any other piece of software that uses interrupts (the TinyWireS does). I'm surprised that it even works on the Tiny, especially with the high (for SoftwareSerial) baudrate of 9600. Do you clock the Tiny by a crystal or the internal oscillator?
You didn't tell which tiny core you're using.

I am using the internal oscillator.

I'm using attiny by David A. Mellis version 1.0.2

TinyWireS is by rambo.

I used TinyWireS with success in an earlier project http://forum.arduino.cc/index.php?topic=160680.0 where I also used PinChangeInterrupt.

In my current setup SoftwareSerial works OK. The receiveEvent function is never called.

Edit: I got TinyWireS from playground.arduino.cc

I just tried to use the I2C Scanner from this page on the Arduino Nano, which should act as the master.

This is the result:

I2C Scanner
Scanning...
I2C device found at address 0x25 !
done

So this is as expected. But why does the receive event not trigger? Does the problem reside on the master not the slave?

It is now working. I will trim the code and post. When i started to request data and not only send data from master to slave it began to work. I’m not sure why. Maybe both requestEvent and receiveEvent has to be implemented.

I got some interesting findings.
If the master doesn't send a request to the slave. The slave will not receive either.

Now my masters loop first write to the slave and 1 sec later it request a byte from the slave. Then there is another 1 sec delay before the master writes to the slave again and so forth. On the slave the receive event are followed immediately by the request event. Then there are a 2 second delay.

Can anyone explain this strange behavior?

Can anyone explain this strange behavior?

Post your code.

My guess is that you expect the Nano to write at the time you call the write method. But the data is actually written when you call the Wire.endTransaction() method. In contrary the Wire.requestFrom() method sends the read request and reads all requested bytes before it returns. The read method just returns the internal buffer one byte after the other.

Here are the working code for a setup where ATtiny85 is used as i2c driver chip for DFPlayer sound module.
The code on the ATtiny85 working as i2c slave:

#include "TinyWireS.h"    
#define I2C_SLAVE_ADDR  0x25  

#include "SoftwareSerial.h"
const int Rx = 3; // this is physical pin 2 - you do not need to connect this to DFPlayer
const int Tx = 4; // this is physical pin 3 - connect to RX pin on DFPlayer
SoftwareSerial mySerial(Rx, Tx);

#define Start_Byte 0x7E
#define Version_Byte 0xFF
#define Command_Length 0x06
#define End_Byte 0xEF
#define Acknowledge 0x00 //Returns info with command 0x41 [0x01: info, 0x00: no info]

char myCmd = ' ';
int myPar = 0;

void setup() {
  pinMode(Rx, INPUT);
  pinMode(Tx, OUTPUT);
  mySerial.begin(9600); // send serial data at 9600 bits/sec 
  TinyWireS.begin(I2C_SLAVE_ADDR);      // init I2C Slave mode
  TinyWireS.onReceive(receiveEvent);
  TinyWireS.onRequest(requestEvent);
  delay(500);
  execute_CMD(0x06,0,5);
}

void requestEvent()
{  
  TinyWireS.send(myCmd);
}

void receiveEvent(uint8_t howMany) {
  char c=' ';
  while (1 < TinyWireS.available()) { // loop through all but the last
    c = TinyWireS.receive();      // receive byte as a character
  }
  int x = TinyWireS.receive();    // receive byte as an integer
  myCmd = c;
  myPar = x;
}

void execute_CMD(byte CMD, byte Par1, byte Par2) // Excecute the command and parameters
{
 // Calculate the checksum (2 bytes)
 int16_t checksum = -(Version_Byte + Command_Length + CMD + Acknowledge + Par1 + Par2);
 // Build the command line
 byte Command_line[10] = { Start_Byte, Version_Byte, Command_Length, CMD, Acknowledge, Par1, Par2, checksum >> 8, checksum & 0xFF, End_Byte};
 //Send the command line to the module
 for (byte k=0; k<10; k++)
 {
  mySerial.write( Command_line[k]);
 }
}

void loop() {
  // put your main code here, to run repeatedly:
  switch (myCmd) {
    case 'p':
    case 'P': execute_CMD(0x03,0,myPar); myCmd = ' '; break;
    case 'v':
    case 'V': execute_CMD(0x06,0,myPar); myCmd = ' '; break;
  }
  TinyWireS_stop_check;
}

The execute_CMD was found here and the set of commands can easily be expanded.

And the code running on Arduini Nano working as master

#include <Wire.h>

void setup() {
  byte error;
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);
  delay(100);
  digitalWrite(13, LOW);
  Serial.begin(9600);
  Wire.begin(); // join i2c bus (address optional for master)
  delay(100);
  digitalWrite(13, HIGH);
  delay(100);
  digitalWrite(13, LOW);
  Serial.println("Sending started");
  SoundCmd('v',2); // set volumne to 2
}

byte x = 0;

void loop() {
  digitalWrite(13, HIGH);
  delay(100);
  digitalWrite(13, LOW);
  x++;
  SoundCmd('P',x);  // Play sound no x
  if (x==5) x=0;
  delay(10000);
}

void SoundCmd(char cmd, int par) {
  Wire.beginTransmission(0x25); // transmit to device #25
  Wire.write(cmd);           // sends a char
  Wire.write(par);             // sends one byte
  Wire.endTransmission();
  Wire.requestFrom(0x25,1);    // request asinglr  byte from slave device #25
  while(Wire.available())    // slave may send less than requested
  { 
    char c = Wire.read();    // receive a byte as character
  }
}

Using the ATtiny85 means that you no longer need to use the serial to communicate with DFPlayer module.

A strange thing is that I need to request a byte of data for the write to take effect.

You should declare myCmd and myPar "volatile" because they usually are changed inside an interrupt and the compiler doesn't know about that and may optimize out some necessary code. By declaring them volatile the compiler gets informed that it isn't allowed to optimize on these variables (p.e. keeping them in registers) as they might be changed outside the normal program flow. This might explain the symptoms you experienced.

volatile char myCmd = ' ';
volatile int myPar = 0;