ESP32 OTA Update via I2C communication

Hello everyone,

I am having an issue performing an OTA update using I2C. I have 2 boards, a master and a slave. The master is connecting to the internet via Ethernet (project requirement). The master is communicating with the Slave via I2C. There are some situations where I may need to update the firmware to the slave. However, the slave can't connect out to the internet. My thinking is that I want to sent the firmware via I2C.

I have 98% of the code working. The 2% is one bug that I am trying to figure out. The logic works. But, when I add in the code for writing the flash, that is where things start to break down. Basically what happens is that the slave gets a Buffer overflow error on the I2C line. However, when I comment that part out (writing to the flash) everything works as it is suppose to.

My most educated guess is that somehow the writing to flash is happening so slowly that it is messing everything up. I am not sure how I can fix this issue hence, I am posting on the forum. Below is a code example:

The checkI2Ccomms function gets called in my loop function:

void checkI2CComms()
{
    if(slaveUpdateMode && fwByteNeedProcessed){
        // Calculate the CRC32 checksum and write the bytes to flash
        for (int i = 0; i < bytesRead; i++) {
            crc.update(firmwareReceiveBuffer[i]);
            //InternalStorage.write();
            //Wire.readBytes(firmwareReceiveBuffer, I2C_FW_BUFFER_SIZE);
        }

        firmwareSize -= bytesRead;

        if (firmwareSize <= 0) {
            slaveUpdateMode = false;
            firmwareSize = 0;

            InternalStorage.close();
            uint32_t checksum = crc.finalize();

            // Check the checksum
            if (checksum == firmwareCRCValue) {
                printStatus(F("Checksums match"));
                //InternalStorage.apply(); 
            } else {
                printStatus(F("Checksums do not match"));
            }

            // transmit something to confirm to master ending Slave OTA update
            printStatus(F("Firmware update complete"));

            delay(100); // Give status text time to send out
            //reset();
        } else {
            fwPrintBytesCounter++;

            if (fwPrintBytesCounter >= 100) {
                Serial.print("Bytes: ");
                Serial.println(firmwareSize);
                fwPrintBytesCounter = 0;
            }
        }

        fwByteNeedProcessed = false;
    } 
    // Just a placeholder function
}

And this is the code for the I2Crecieve event:

void i2cRecieveEvent(int bytesToRead)
{
    if(!slaveUpdateMode){
        memset(receivedMessage, 0, I2C_READ_BUFFER * sizeof(char));
        int bytesRead = Wire.readBytesUntil('/', receivedMessage, I2C_READ_BUFFER);
        printStatus(String(receivedMessage));

        if (!channelConfigIsComplete){
            if (String(receivedMessage).indexOf("end") != -1){
                printStatus("end received");
                channelConfigIsComplete = true;
                //Serial.println("Locked off");
            } else {
                receivedMessage[bytesRead] = '/';
                printStatus("Processing line: ");
                printStatus(receivedMessage);
                ParseSDCardFile(receivedMessage);
            }
        } else {
            parseEthernetCommands(receivedMessage);
        }
    } else {

        esp_task_wdt_reset(); // Just to make sure that the WDT doesn't hit
        //printStatus("Slave Update Mode");
        uint8_t tempArray[I2C_FW_BUFFER_SIZE];

        // Read up to 32-bytes of data and store in the read buffer
        memset(firmwareReceiveBuffer, 0, I2C_FW_BUFFER_SIZE * sizeof(uint8_t));
        memset(tempArray, 0, I2C_FW_BUFFER_SIZE * sizeof(uint8_t));
        
        bytesRead = Wire.readBytes(tempArray, I2C_FW_BUFFER_SIZE);

        memcpy(firmwareReceiveBuffer, tempArray, bytesRead);

        // Set the flag to indicate that bytes have arrived for the fw upload
        fwByteArrived = true;
        fwByteNeedProcessed = true;
    }
}

Some useful globals:

uint8_t firmwareReceiveBuffer[I2C_FW_BUFFER_SIZE];
int bytesRead = 0;
uint32_t firmwareUpdateSize = 0;
bool fwByteArrived = false; // Set whenever a byte is received during the firmware update process
bool fwByteNeedProcessed = false; 

There are some additional functions like getting the CRC and getting the firmware size, that is handled by a separate communication scheme. I am not too worried about the amount of time it will take to update the firmware on the slave. My first step is to get it to work

It's pretty easy to use Update.h directly..
just need an update.begin(sizeofFirm), then update.write(chunkOfFirm), then update.end(true)..

used it here..

and yes, may take a bit..

good luck.. ~q

Hello, thank you for your reply.

I was able to kinda solve the issue. I implemented a polling system so that the I2C master checks the state on the slave and if the slave is writing to flash, the master will not transmit bytes.

I verified that I am getting all of the bytes on the slave.

Now my other issue is that when I run the InternalStorage.apply() function, the new firmware is not applied or run even though I have it open and wrote bytes to the flash.

  1. Under what conditions would the ESP32 not boot into the other partition?
  2. Is there some way for me to check the other partition that I have data there?