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

Oh, yes, thanks for the observation. Code modified.

Unfortunately that functions seems not to be available on Arduino Uno. So I had to stick to the old while-loop.

And I added quite a lot of volatiles and ATOMIC things, because ...

... there aren't any serious timing requirements.

Well, this is to avoid. And that's why I struggle using volatile when I don't have a exact scenario in mind where I alway need latest value ... which is not the case here. And I don't(!) want to share intermediate results.
Besides, as i2cBuffer is global, I guess it is sufficient to declare i2cBuffer as volatile (see code below), but not the local copies in loop()? (even though right now there are volatile).

Well, this is not done. Nobody waits. Latest values are then taken in next loop of loop() - ok, which is somehow waiting, but not in the sense that execution of loop() is blocked.
But isn't it similar to a global variable that one function changes while the other reads it? I.e. would be the consequence that any global variable has to be volatile then?

I start thinking about that ...

Here is the code

#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
      byte col = 0;
      while (Wire.available()) {
        i2cBuffer.data[i2cBuffer.writeIdx][col] = Wire.read();
        col++;
      }
      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
  volatile byte i2cWriteIdx = i2cBuffer.writeIdx; // ISR-changed, but reading just a byte always fine
  volatile byte i2cReadIdx  = i2cBuffer.readIdx;  // locally here updated
  volatile boolean i2cOverflow  = i2cBuffer.Overflow;  // True, if overflow has occured
  volatile boolean i2cReceiveOk = i2cBuffer.receiveOk; // Ture, if #bytes received are as expected
  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;
  }
}