[SOLVED] Is an assignment to 8 bit-long variable on 8 bit processor always atomic?

Ok. I guess its because a call of ISR is not along the linear execution of the code, because, well, as you have written "any time". So to prevent the compiler to run some optimization that may lead, e.g., loose the connection to the updated value.

I have rewritten my code. Except that there is no flag. The flag is part of the struct

// Define type for I2CBuffer
  struct I2CBuffer {
    byte data[I2C_BUFFER_SIZE][I2C_BUFFER_DEPTH];
    byte readIdx;                               // Index into oldest row with data
    byte writeIdx;                              // Index into empty / writeable row of data
    boolean Overflow;                           // Will be true if overflow has occured
    boolean receiveOk;                          // Will be false if received data is not ok
  };
// Declare I2CBuffer
  volatile I2CBuffer i2cBuffer = {{},0,0,false,true}; // No Overflow, received is fine

So, I think, as I do the reading to readIdx, writeIdx, Overflow, and receiveOk while interrupts are disabled, I get a consistent set of data (readIdx, writeIdx, ...) and thus a valid new (if any) set of received data will be extracted from data because readIdx (and all other) are valid, and the ISR does not mess with this read-index around.

So, all in all I guess all done :thinking::

//#include <Arduino.h>
#include <util/atomic.h> 
#include <Wire.h>

// Some enumeration to make life easier
  enum class OpMode : byte {Sleep, WakeUp, Awake, LightOn, FallASleep, ClearBuffer};
  enum Mode : byte {Normal, Configure};
  enum Cmd  : byte {PowerOff, PowerOn, LightOn};

// I2C BUS
  #define I2C_BUFFER_DEPTH    6                 // There will be always 6 bytes send
  #define I2C_BUFFER_SIZE    16                 // Buffer can up to (SIZE-1) messages, shall be 2^n
  #define I2C_BUFFER_MASK (I2C_BUFFER_SIZE - 1) // Used in buffer code for fast implementation
  #define POWER_ADDRESS      0b0001000          // I2C-Adress of PowerStage
// Define type for I2CBuffer
  struct I2CBuffer {
    byte data[I2C_BUFFER_SIZE][I2C_BUFFER_DEPTH];
    byte readIdx;                               // Index into oldest row with data
    byte writeIdx;                              // Index into empty / writeable row of data
    boolean Overflow;                           // Will be true if overflow has occured
    boolean receiveOk;                          // Will be false if received data is not ok
  };
// Declare I2CBuffer
  volatile I2CBuffer i2cBuffer = {{},0,0,false,true}; // No Overflow, received is fine
  
// ================================================================================================

void receiveEvent(int byteNum) {
  if (byteNum == I2C_BUFFER_DEPTH) { 
    byte next = ((i2cBuffer.writeIdx + 1) & I2C_BUFFER_MASK);  // cycling [ 0 : I2C_BUFFER_SIZE-1 ]
    if (i2cBuffer.readIdx == next) {            // Y: Overflow of buffer? (one row remains unused)
      i2cBuffer.Overflow = true;                // --> indicate it
    }
    else {                                      // N: Data seems ok, so fill Buffer with new data
      Wire.readBytes( (byte*) i2cBuffer.data[i2cBuffer.writeIdx], byteNum);
      i2cBuffer.writeIdx = next;                // Update writeIdx ro next empty row, atomic!
      i2cBuffer.Overflow = false;               // No overflow
    }
    i2cBuffer.receiveOk = true;                 // N: Message not as expected, no copy but report
  }
  else {
    i2cBuffer.receiveOk = false;                // Message not as expected, no copy but report
  }
}

// ================================================================================================

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  // I2C-Bus  
  Wire.begin(POWER_ADDRESS);
  Wire.onReceive(receiveEvent);
}

// ================================================================================================

void loop() {
  // Data flow control for state machine
  static OpMode opMode     = OpMode::Sleep;       // Sleep after big-bang
  static OpMode opModeLast = OpMode::Sleep;       // dito
  // Power LED commands
  static Mode cmdMode      = Normal;              // Commanded Mode of CtrlUnit (=Normal,Configure,)
  static Cmd  cmdParameter = PowerOff;            //          and associated commands
  static int cmdFilterTau  = 0;                   // Normal: time constant of 1st order filter
  static int cmdBrightFac  = 0;                   //         normalized brightness
  static int cmdColorT     = 0;                   //         reference colortemperature
  // I2C Buffer
  noInterrupts();
  byte i2cWriteIdx = i2cBuffer.writeIdx;          // ISR-changed, but reading just a byte always fine
  byte i2cReadIdx  = i2cBuffer.readIdx;           // locally here updated
  boolean i2cOverflow  = i2cBuffer.Overflow;      // True, if overflow has occured
  boolean i2cReceiveOk = i2cBuffer.receiveOk;     // Ture, if #bytes received are as expected
  interrupts();
  byte i2cReadIdxNext  = ((i2cReadIdx + 1) & I2C_BUFFER_MASK); // Calculate in advance
   
  // ================================================================================================
  // R E A D   I N P U T   D A T A   
  // ================================================================================================

  if ((!i2cOverflow) && i2cReceiveOk) {           // No errors on bus in receiving data?
    if (i2cWriteIdx != i2cReadIdx) {              // New message received (indices are different then)
      switch (i2cBuffer.data[i2cReadIdx][0] >> 4) {       // What mode the Powerstage shall work with?
        case 0:                                   // Normal Mode
          cmdMode = Normal;                       // Y: read ALL remaining data
          switch (i2cBuffer.data[i2cReadIdx][0] & 0x0F) {   // Translate cmd into local meaning (enum)
            case  0: cmdParameter = PowerOff; break;
            case  8: cmdParameter = PowerOn;  break;
            case 12: cmdParameter = LightOn;  break;
            default: cmdParameter = PowerOff; break;
          }
          cmdFilterTau =  i2cBuffer.data[i2cReadIdx][1];
          cmdColorT    = ((int)i2cBuffer.data[i2cReadIdx][2] << 8) | // ColorT spread on two bytes
                         ((int)i2cBuffer.data[i2cReadIdx][3]);
          cmdBrightFac = ((int)i2cBuffer.data[i2cReadIdx][4] << 8) | // BrightFac spread on two bytes
                         ((int)i2cBuffer.data[i2cReadIdx][5]);
          break;
        case 1:                                 // Configuration Mode. To come ...
        default:
          break;
      }
      // Now update readIdx for I2C-Buffer for all Modes of Powerstage
      ATOMIC_BLOCK (ATOMIC_RESTORESTATE) {
        i2cBuffer.readIdx = i2cReadIdxNext;     // Writing back to buffer. Should be fine for a byte
      }  

      // DEBUGGING ONLY: Show powerCMD to evaluate at beginning of loop()
      if (cmdMode == Normal) {
        byte freeMem = (i2cReadIdx > i2cWriteIdx) ? (i2cReadIdx - i2cWriteIdx) : 
                                                    (I2C_BUFFER_SIZE + i2cReadIdx - i2cWriteIdx - 1);
        Serial.print(F("Mode\t\t: "));     Serial.println(byte(cmdMode));
        Serial.print(F("Parameter\t: "));  Serial.println(cmdParameter);
        Serial.print(F("Delay\t\t: "));    Serial.println(cmdFilterTau);
        Serial.print(F("colorT\t\t: "));   Serial.println(cmdColorT);
        Serial.print(F("brightFac\t: "));  Serial.println(cmdBrightFac);
        Serial.print(F("FreeMemory\t: "));  Serial.println(freeMem);
        if (opModeLast != opMode) {
          Serial.print(F("Last Operating Mode\t:"));
          if (opModeLast == OpMode::Sleep)        Serial.print(F("Sleep"));
          else if (opModeLast == OpMode::WakeUp)  Serial.print(F("WakeUp"));
          else if (opModeLast == OpMode::Awake )  Serial.print(F("Awake"));
          else if (opModeLast == OpMode::LightOn) Serial.print(F("LightOn"));
          else if (opModeLast == OpMode::FallASleep) Serial.print(F("FallASleep"));
          else if (opModeLast == OpMode::ClearBuffer) Serial.print(F("ClearBuffer"));
          Serial.print("\t Acutal Mode \t:");
          if (opModeLast == OpMode::Sleep)        Serial.print(F("Sleep"));
          else if (opMode     == OpMode::WakeUp)  Serial.print(F("WakeUp"));
          else if (opMode     == OpMode::Awake )  Serial.print(F("Awake"));
          else if (opMode     == OpMode::LightOn) Serial.print(F("LightOn"));
          else if (opMode     == OpMode::FallASleep) Serial.print(F("FallASleep"));
          else if (opMode     == OpMode::ClearBuffer) Serial.print(F("ClearBuffer"));
        }
        Serial.println(" ");
      }  
    }
  }
  else {
    cmdMode = Normal;                           // Shut down in normal Modus, create feasible
    cmdParameter = PowerOff;                    // ... command to shut down 
    cmdFilterTau = 100;                         // ... if needed, or directly. State machine below
    cmdColorT    = 0;                           // ... will take care of it
    cmdBrightFac = 0;
  }

  // ================================================================================================
  // O P E R A T E   L I G H T C O N T R O L
  // ================================================================================================
  switch (opMode) {
    case OpMode::Sleep:
      if (cmdMode == Normal) {
      }
      break;

    case OpMode::WakeUp:    
      if (cmdMode == Normal) {
      }
      break;

    case OpMode::FallASleep:
      if (cmdMode == Normal) {
      }
      break;
      
    case OpMode::Awake:
      if (cmdMode == Normal) {
      }
      break;
      
    case OpMode::LightOn:
      if (cmdMode == Normal) {
      }
      break;

    case OpMode::ClearBuffer:
      opModeLast = opMode;
      opMode     = OpMode::Sleep;
      break;  
      
    default:
      break;
  }
}

By the way:

  1. is there a difference between cli()/sei() and noInterrupts()/interrupts()?

  2. Where is the documentation for Wire.readBytes()?. I just could find something for ESP32, which makes me believe it is not a standard function of wire.h? https://www.arduino.cc/en/Reference/Wire is not mention that function either ...