I2C problem sending commands from Arduino to Attiny84

Hi there,

I have a project in progress, where I want an Arduino (at the moment Mega2650, in the final project it will be a nano) to send commands to multiple ATtiny84. Each ATtiny has to control a 28BYJ-48 with ULN2003. Also a hall sensor is attached to the ATtiny for finding zero possition of the steppers. The steppers should turn only in one direction and can stop at 12 evenly distributed positions. For fine-tuning there can be set an offset of zero position.

Possible commands: find zero pos, goto pos x, increment offset, return address, return position, ...

ChatGPT was really helpful to get as far as I've been come.
But now I need help from real intelligent beings.

  • I've tested basic control of steppers with the ATtiny
  • I've tested basic communication from Arduino to ATtiny via I2C
  • I've got a combination of both working... somehow, but not fully

I use a switch case statement for the received commands.
I always read 3 bytes and have a write buffer of 5 bytes which I read/set at the beginning of receiveEvent.

The first commands in the switch statement work perfectly fine, but at 0x20 they start not to work. 0x21 works somehow, but I think the position to go to is not correct. The rest from there one seams to do nothing.
All requests result in 0.
Firstly I had the commands starting with 0x4? assigned to 0xA? and higher and I thought that this could be the problem. But no.

When I had the ledIndicators turned on (at the moment they are commented out), my debugging led blinked for the above written working commands. It looks like there is nothing coming on at the ATtiny using the broken commands.

Has someone an idea where I can dig into a bit more to find the error?

Thanks a lot!

slave.ino

// - - -   I N C L U D E S   - - -

#include <Wire.h>
#include <EEPROM.h>
#include <avr/wdt.h>



// - - -   V A R I A B L E S   - - -

// eeprom defines
#define EEPROM_I2C_ADDRESS 1
#define EEPROM_OFFSET 2
#define EEPROM_DIRECTION 3


// pin defines
#define LED_PIN 10
#define SENSOR_PIN 9
#define SDA_PIN 6
#define SCL_PIN 4
#define RESET_PIN 7


// i2c defines
#define POS_BASE 0x10
#define POS_MAX (POS_BASE + POS_COUNT - 1)
#define DEFAULT_ADDRESS 0x10


// movement defines
#define STEPS_PER_REV 2048
#define STEP_DELAY 2
#define POS_COUNT 12


// motor constant arrays
const int motorPins[4] = {0, 1, 2, 3};
const uint8_t halfStepSequence[8][4] = {
  {1, 0, 0, 0},
  {1, 1, 0, 0},
  {0, 1, 0, 0},
  {0, 1, 1, 0},
  {0, 0, 1, 0},
  {0, 0, 1, 1},
  {0, 0, 0, 1},
  {1, 0, 0, 1}
};


// movement constants and variables
volatile short stepperPos = 0;
volatile short posToStep = 0;
volatile unsigned long lastStepTime = 0;
volatile float stepsPerPos = 170.6666;
volatile short stepsOffset = 0;
volatile bool forward = false;
volatile bool gotoZero = false;


// other variables
volatile uint8_t readBuffer[3] = {0, 0, 0};
volatile uint8_t writeBuffer[5] = {0, 0, 0, 0, 0};
volatile uint16_t resetTimer = 0;
volatile uint8_t address = 0;
volatile bool saveConfig = false;




// - - -   S T A T U S   F U N C T I O N S   - - -

// led status indicator
void ledIndicator(short count, short timeOff, short timeOn) {
  delay(timeOff);
  for (uint8_t i = 0; i < count; i++) {
    delay(timeOff);
    digitalWrite(LED_PIN, HIGH);
    delay(timeOn);
    digitalWrite(LED_PIN, LOW);
  }
  delay(timeOff * 2);
}




// - - -   M O T O R   M O V E M E N T   - - -

// make one step forward or backward regarding 'forward' boolean
void stepMotor() {
  
  // set pins
  for (uint8_t pin = 0; pin < 4; pin++) {
    digitalWrite(motorPins[pin], halfStepSequence[stepperPos % 8][pin]);
  }

  // make one step forwards or backwards depending on direction preventing overflow with modulo
  if (forward) {
    stepperPos = (stepperPos + 1) % STEPS_PER_REV;
  } else {
    stepperPos = (stepperPos - 1 + STEPS_PER_REV) % STEPS_PER_REV;
  }
}


// stop motor by setting every pin to low
void stopMotor() {
  for (uint8_t pin = 0; pin < 4; pin++) {
    digitalWrite(motorPins[pin], LOW);
  }
}




// - - -   C O M M U N I C A T I O N   - - -

// receive commands
void receiveEvent(int howMany) {
  readBuffer[0] = Wire.read();
  readBuffer[1] = Wire.read();
  readBuffer[2] = Wire.read();

  writeBuffer[0] = 0;
  writeBuffer[1] = 0;
  writeBuffer[2] = 0;
  writeBuffer[3] = 0;
  writeBuffer[4] = 0;

  ledIndicator(1, 300, 300);
  
  switch (readBuffer[0]) {

    // movement commands
    // stop motor
    case 0x00:
      //ledIndicator(1, 300, 300);
      posToStep = stepperPos;
      break;

    // find zero position
    case 0x01:
      //ledIndicator(1, 300, 300);
      posToStep = -1;
      break;

    // set stepper pos to go to by calculating steps for searched position
    case POS_BASE ... POS_MAX:
      //ledIndicator(1, 300, 300);
      short endPos = readBuffer[0] - POS_BASE;
      posToStep = floor(endPos * stepsPerPos) + stepsOffset;
      break;

    // reserved for request current position
    case 0x20:
      //ledIndicator(1, 300, 300);
      writeBuffer[0] = stepperPos;
      writeBuffer[1] = 0;
      writeBuffer[2] = 0;
      writeBuffer[3] = 0;
      writeBuffer[4] = 0;
      break;

    // set stepper pos to go to
    case 0x21:
      //ledIndicator(1, 300, 300);
      posToStep = ((readBuffer[1] << 8) | readBuffer[2]);
      break;


    // movement configuration
    // reverse stepper direction
    case 0x40:
      //ledIndicator(1, 300, 300);
      forward = !forward;
      break;

    // offset zero position
    case 0x41:
      //ledIndicator(1, 300, 300);
      stepsOffset += 8;
      posToStep += stepsOffset;
      break;

    // reset offset
    case 0x42:
      //ledIndicator(1, 300, 300);
      stepsOffset = 0;
      break;
      

    // communication commands
    // request current variables
    case 0x50:
      //ledIndicator(1, 300, 300);
      writeBuffer[0] = address;
      writeBuffer[1] = highByte(stepperPos);
      writeBuffer[2] = lowByte(stepperPos);
      writeBuffer[3] = stepsOffset;
      writeBuffer[4] = forward;
      break;

    // set address
    case 0x51:
      //ledIndicator(1, 300, 300);
      if (readBuffer[1] >= 0x08 && readBuffer[1] <= 0x77) {
        address = readBuffer[1];
      }
      break;

    // save configuration
    case 0x59:
      //ledIndicator(1, 300, 300);
      saveConfig = true;
      break;

  }
}


void requestEvent() {
  Wire.write(writeBuffer[0]);
  Wire.write(writeBuffer[1]);
  Wire.write(writeBuffer[2]);
  Wire.write(writeBuffer[3]);
  Wire.write(writeBuffer[4]);
}



// - - -   B A S E   F U N C T I O N S   - - -

// startup sequence
void setup() {
  // Disable watchdog on startup if triggered by a watchdog reset
  MCUSR &= ~(1 << WDRF);
  wdt_disable();

  pinMode(LED_PIN, OUTPUT);
  ledIndicator(1, 250, 3000);
  
  // initialize pins
  for (uint8_t i = 0; i < 4; i++) {
    pinMode(motorPins[i], OUTPUT);
  }
  pinMode(SENSOR_PIN, INPUT);
  pinMode(RESET_PIN, INPUT_PULLUP);

  ledIndicator(1, 250, 250);

  // initalize variables
  stepsPerPos = STEPS_PER_REV / POS_COUNT;
  stepsOffset = EEPROM.read(EEPROM_OFFSET);
  forward = EEPROM.read(EEPROM_DIRECTION) > 0;
  posToStep = 0;

  ledIndicator(1, 250, 250);
  
  // initialize communication
  uint8_t i2cAddress = EEPROM.read(EEPROM_I2C_ADDRESS);
  if (i2cAddress < 0x08 || i2cAddress > 0x77) {
    i2cAddress = DEFAULT_ADDRESS;
    EEPROM.update(EEPROM_I2C_ADDRESS, i2cAddress);
  }
  Wire.begin(i2cAddress);
  Wire.onReceive(receiveEvent);
  Wire.onRequest(requestEvent);

  ledIndicator(5, 250, 250);
}


// main loop
void loop() {

  //TinyWireS_stop_check();

  // check reset
  if (!digitalRead(RESET_PIN)) {
    digitalWrite(LED_PIN, HIGH);

    if (resetTimer == 0) {
      resetTimer = millis();
    }
    
    if (millis() - resetTimer >= 5000) {
      wdt_enable(WDTO_15MS);
      while (true) {}
    }

  } else {
    resetTimer = 0;
    digitalWrite(LED_PIN, LOW);
  }

  // save configuration
  if (saveConfig) {
    EEPROM.update(EEPROM_I2C_ADDRESS, address);
    EEPROM.update(EEPROM_OFFSET, stepsOffset);
    EEPROM.update(EEPROM_DIRECTION, forward ? 1 : 0);
    saveConfig = false;
  }
  
  // check hall sensor
  if (!digitalRead(SENSOR_PIN)) {
    digitalWrite(LED_PIN, HIGH);
    if (posToStep < 0) {
      stepperPos = 0;
      posToStep = 0;
    }
  } else {
    digitalWrite(LED_PIN, LOW);
  }

  // step forward if neccessary
  if (millis() - lastStepTime >= STEP_DELAY) {
    lastStepTime = millis();

    // Nur bei Bewegung: einen Schritt ausführen
    if (stepperPos != posToStep) {
      stepMotor();
    } else {
      stopMotor();
    }
  }
}

master.ino

#include <Wire.h>

uint8_t slaveAddress = 0x10;  // Aktive I2C-Adresse

void setup() {
  Serial.begin(9600);
  Serial.write("\n\n\n\n\n\n");
  Serial.write("enter command:\n");
  Wire.begin();
  delay(5000);
}

void loop2() {
  Wire.beginTransmission(slaveAddress);
  Wire.write(0x10);
  Wire.endTransmission(false);
  //delay(500);
  Wire.beginTransmission(slaveAddress);
  Wire.write(0x11);
  Wire.endTransmission(false);
  //delay(10);
  Wire.requestFrom(slaveAddress, 2);
  byte high = Wire.read();
  byte low = Wire.read();
  Serial.println((high << 8) | low);
  delay(500);
}

void loop() {
  if (Serial.available()) {
    String cmd = Serial.readStringUntil('\n');

    cmd.trim();  // Whitespace entfernen
    Serial.print(cmd);

    if (cmd == "s") {
      Wire.beginTransmission(slaveAddress);
      Wire.write(0x00);
      Wire.write(0xFF);
      Wire.write(0xFF);
      Wire.endTransmission(false);
      delay(100);
      
    } else if (cmd == "z") {
      Wire.beginTransmission(slaveAddress);
      Wire.write(0x01);
      Wire.write(0xFF);
      Wire.write(0xFF);
      Wire.endTransmission(false);
      delay(100);
    
    } else if (cmd.startsWith("p ")) {
      int pos = cmd.substring(2).toInt();
      if (pos >= 0 && pos < 12) {
        Serial.print(": ");
        Serial.print(pos);
        Wire.beginTransmission(slaveAddress);
        Wire.write(0x10 + pos);
        Wire.write(0xFF);
        Wire.write(0xFF);
        Wire.endTransmission(false);
        delay(100);
      }
    
    } else if (cmd == "p") {
      Wire.beginTransmission(slaveAddress);
      Wire.write(0x20);
      Wire.write(0xFF);
      Wire.write(0xFF);
      Wire.endTransmission(false);
      delay(100);
      Wire.requestFrom(slaveAddress, 5);
      byte high = Wire.read();
      byte low = Wire.read();
      Serial.print(": ");
      Serial.println((high << 8) | low);
      Serial.println(Wire.read());
      Serial.println(Wire.read());
      Serial.println(Wire.read());
      delay(100);
    
    } else if (cmd.startsWith("q ")) {
      int pos = cmd.substring(2).toInt();
      if (pos >= 0 && pos < 2048) {
        Serial.print(": ");
        Serial.print(pos);
        Serial.print(" (");
        Serial.print(highByte(pos));
        Serial.print(", ");
        Serial.print(lowByte(pos));
        Serial.print(")");
        Wire.beginTransmission(slaveAddress);
        Wire.write(0x21);
        Wire.write(highByte(pos));
        Wire.write(lowByte(pos));
        Wire.endTransmission(false);
        delay(100);
      }
    
    } else if (cmd == "r") {
      Wire.beginTransmission(slaveAddress);
      Wire.write(0x40);
      Wire.write(0xFF);
      Wire.write(0xFF);
      Wire.endTransmission(false);
      delay(100);
    
    } else if (cmd == "+") {
      Wire.beginTransmission(slaveAddress);
      Wire.write(0x41);
      Wire.write(0xFF);
      Wire.write(0xFF);
      Wire.endTransmission(false);
      delay(100);
    
    } else if (cmd == "x") {
      Wire.beginTransmission(slaveAddress);
      Wire.write(0x42);
      Wire.write(0xFF);
      Wire.write(0xFF);
      Wire.endTransmission(false);
      delay(100);
    
    } else if (cmd == "?") {
      Wire.beginTransmission(slaveAddress);
      Wire.write(0x50);
      Wire.write(0xFF);
      Wire.write(0xFF);
      Wire.endTransmission(false);
      delay(100);
      Wire.requestFrom(slaveAddress, 5);
      Serial.print(": ");
      Serial.println(Wire.read());
      Serial.println(Wire.read());
      Serial.println(Wire.read());
      Serial.println(Wire.read());
      Serial.println(Wire.read());
      delay(100);
    
    } else if (cmd.startsWith("a ")) {
      int adr = (int) strtol(cmd.substring(2).c_str(), nullptr, 0);
      if (adr >= 0x08 && adr <= 0x77) {
        Serial.print(": ");
        Serial.print(adr);
        Wire.beginTransmission(slaveAddress);
        Wire.write(0x51);
        Wire.write(adr);
        Wire.write(0xFF);
        Wire.endTransmission(false);
        delay(100);
      }
    
    } else if (cmd == "w") {
      Wire.beginTransmission(slaveAddress);
      Wire.write(0x59);
      Wire.write(0xFF);
      Wire.write(0xFF);
      Wire.endTransmission(false);
      delay(100);

    } else if (cmd.startsWith("0x")) {
      byte hex = strtol(cmd.c_str(), nullptr, 0);
      Wire.beginTransmission(slaveAddress);
      Wire.write(hex);
      Wire.write(0xFF);
      Wire.write(0xFF);
      Wire.endTransmission(false);
      delay(100);
      Wire.requestFrom(slaveAddress, 5);
      Serial.println(Wire.read());
      Serial.println(Wire.read());
      Serial.println(Wire.read());
      Serial.println(Wire.read());
      Serial.println(Wire.read());
      delay(100);
    
    }

    Serial.println();
  }
}

Thank you for being honest about AI. Do you understand the code? If you do, then you don't need help, if you don't, then AI misled or made some other error. AI can't write real programs unless it finds the exact solution on the net. It excels at code snippets under the right conditions.
Even after more than 50 years of programming, I still build in increments. Get one part working before adding more code.
Good luck.

I think, I've got the problem:

case POS_BASE ... POS_MAX:

for testing i separated each position as single command, and no the commands seem to go further.

I used AI mostly to find errors. Reading documentations and digging into forums is always the base.

That's the methodology termed as SSS (Start with Small Step) Strategy which I have practiced for 15 years with Schlumberger Wire Line Ltd. to troubleshooting and fixing their Logging Equipment.

Ok, I always called it 'divide and conquer', but your acronym is more descriptive; mine comes from troubleshooting, where it is more applicable.

What country are you located in? I am in Canada, northern Ontario, where I bet some of your equipment is used.

Interesting. In the last year or so, a lot of effort here on this forum has been devoted to resolving the AI-generated errors and misconceptions in code provided by beginners who cannot comprehend the code AI provided, much less find the errors.

But regardless, my first comment on your project is, how far apart are the I2C devices? I2C is not a robust, long-distance bus, but rather is intended for inter-device communication within the confines of a circuit board. Yes, there are examples of the bus being run for several meters, but generally it is not a robust approach.

At the moment, all is on a breadboard. But in the final project, there will be (hopefully less) around 500 and 1000 mm wire between master and furthest slave.

Not a good idea with I2C. UART serial will be much more reliable, over much longer distances.

I've tested successful with a 1 meter long wire between an arduino and an attiny. Could it be, that there will be problems with more than one attiny?

You should expect problems when using I2C in ways for which it was not intended or designed.

If this is for a hobby project, learning a lesson is cheap. If this is for an employer, or contract, it could be a costly and embarrassing mistake.
You know, we do not.

It's a hobby project, to learn something.

Which problems do you speak of? All that I know, UART is a bit tricky to setup on the ATtiny84, because of lack of hardware UART, and not ideal with multiple Slaves.

Errors in data transmission and failures due to external electrical noise. The longer the leads, the worse the problem. Certainly, you should try it out and learn for yourself!