Writing to NOR flash memory

[Mode edit:
Split from The merits of delay() vs millis() - #227 by noiasca ]

Since there's 50+ years of accumulated experience in coding in this forum topic, maybe you guys can provide tips and room for improvement in this short piece of code I wrote for writing to a NOR flash.

The spec of the NOR flash is as follows:
256 byte page (if you have not written a driver for a NOR flash before, basically you can only write 256 bytes at a time)
each page write takes between 0.4 to 3 ms to finish

This state machine code goes inside the super loop, and all you need to provide to start the write process is:

  • set the value of W25Qxxx_wrParam.startPageNum (place in flash memory page where you want to start writing data in 256 byte boundary)
  • W25Qxxx_wrParam.length (number of bytes to write)
  • W25Qxxx_wrParam.data (pointer where the data you want to write is from)
  • set W25Qxxx_wrParam.writeActive to true;
		if(W25Qxxx_wrParam.writeActive)
		{
			switch(NOR_stateMachine)
			{
				case NOR_SET_PARAMS:

					/***************************************
					startPageNum -> NOR starting location
					length -> number of bytes to write
					data -> pointer to source data
					***************************************/
					W25Qxxx_wrParam.startPageNum = NOR_pageTracker;
					W25Qxxx_wrParam.length = 256;
					W25Qxxx_wrParam.data = fontMem;
					NOR_stateMachine = NOR_CALC_PARAMS;

					break;

				case NOR_CALC_PARAMS:

					/***************************************
					numPages -> number of pages to write
					extraBytes -> last page byte size
					***************************************/
					W25Qxxx_wrParam.numPages = W25Qxxx_wrParam.length >> 8U;
					W25Qxxx_wrParam.extraBytes = W25Qxxx_wrParam.length % 256;
					W25Qxxx_wrParam.addressOffset = 0;
					W25Qxxx_wrParam.count = 0;
					NOR_stateMachine = NOR_WRITE_PAGE;

					break;

				case NOR_WRITE_PAGE:

					if(W25Qxxx_wrParam.count < W25Qxxx_wrParam.numPages)
					{
						W25Qxxx_wrParam.addressOffset = W25Qxxx_wrParam.count << 8U;

						// write 256 byte page here
						flash_pageProgram(W25Qxxx_wrParam.startPageNum + W25Qxxx_wrParam.count,
								W25Qxxx_wrParam.data + W25Qxxx_wrParam.addressOffset, 256U);

						NOR_stateMachine = NOR_CHECK_BUSY;
					}
					else if(W25Qxxx_wrParam.extraBytes)
					{
						W25Qxxx_wrParam.addressOffset = W25Qxxx_wrParam.numPages << 8U;

						// write remaining bytes here
						flash_pageProgram(W25Qxxx_wrParam.startPageNum + W25Qxxx_wrParam.numPages,
								W25Qxxx_wrParam.data + W25Qxxx_wrParam.addressOffset,
								W25Qxxx_wrParam.extraBytes);

						W25Qxxx_wrParam.extraBytes = 0;
						NOR_stateMachine = NOR_CHECK_BUSY;
					}
					else {
						NOR_stateMachine = NOR_GET_STATS;
					}

					break;

				case NOR_GET_STATS:

					// update EERAM for last page written, last page written is being track in EERAM for housekeeping
					NOR_pageTracker = W25Qxxx_wrParam.startPageNum + W25Qxxx_wrParam.numPages;
					if(W25Qxxx_wrParam.length % 256) {
						NOR_pageTracker++;
					}

					xmega_uartD0Print("\n\n\rEERAM : 0x");
					printToHex32(NOR_pageTracker);
					eeram_write(EERAM_NOR_PAGE_WR16, 2, (uint8_t *) &NOR_pageTracker);

					W25Qxxx_wrParam.writeActive = 0;
					NOR_stateMachine = NOR_NOOP;

					break;

				case NOR_CHECK_BUSY:

					if(flash_getStatusBusy())
					{
						// start a 100 usec timer
						NOR_DELAY_FLAG = true;
						xmega_norDelay_start(100);

						NOR_stateMachine = NOR_WAIT_STATE;
					}
					else
					{
						W25Qxxx_wrParam.count++;
						NOR_stateMachine = NOR_WRITE_PAGE;
					}

					break;

				case NOR_WAIT_STATE:

					if(NOR_DELAY_FLAG) {
						NOR_stateMachine = NOR_WAIT_STATE;
					}
					else {
						NOR_stateMachine = NOR_CHECK_BUSY;
					}

					break;

				default:

					W25Qxxx_wrParam.writeActive = 0;
					NOR_stateMachine = NOR_NOOP;
			}
		}

Doesn't work because everyone thinks differently. One person's solution makes no sense to someone else. The fighting (err.. discussions) go on forever and the issues are never resolved.

-jim lee

@hzrnbgy
I have moved this to a new topic of its own as it does not belong in the middle of the discussion about delay Vs Millis. Please do not hijack other topics for your questions.

I agree completely with that, and please keep it in mind while reading the rest of this post.

Here is my code to read and write from a Microchip 24LC512 EEPROM. This was written to run on a 32 bit PIC, the register names reflect that, so would need to be modifed for another chip.

Note:

  • The function EEPROM_readWrite() to be called every time around loop() or (in the case of standard C) around main().
  • This code drives I2C directly, there is no I2C library or other code to handle the I2C hardware.
  • The 2 variables EEPROM_startAddress and EEPROM_endAddress are badly named as they are not addresses in EEPROM, they are are the start and end of the data to be written to or read back from EEPROM, in this case an array.
  • readNWrite controls whether the data is being written to or read from EEPROM.
  • The code assumes the data will be stored from address 0x00 in the EEPROM, obviously there is room for improvement there.
  • At the end of the main code below I have also included 2 short functions that start the reading or start the writing sequence.

Code offered 'as is' for you use, abuse, change or ignore as you see fit.

Main code

int16_t EEPROM_startAddress;
int16_t EEPROM_endAddress;
bool readNWrite;
const uint16_t RnW_pin = 0x0010;                                            // Read not Write pin RA4
void EEPROM_readWrite() {
    static uint32_t lastMillis;
    uint32_t currentMillis = millis();
    uint32_t wait = 10;                                                         // Wait for write operation, required maximum from data sheet is 5ms
    static uint8_t state = 0;
            
    if (EEPROM_startAddress != EEPROM_endAddress) {
        if (!(I2C2CON & 0x001f)) { 
            switch (state) {
                case 0:
                    if (EEPROM_startAddress > EEPROM_endAddress || EEPROM_startAddress > sizeof(HMI_SliderData) ||  EEPROM_endAddress > sizeof(HMI_SliderData)) {
                        EEPROM_startAddress = EEPROM_endAddress = 0;
                    }
                    LATACLR = RnW_pin;                                          // Write enable
                    I2C2CON = 0x8201;                                           // Send START
                    ++state;
                    break;
                case 1:
                    I2C2TRN = 0xa0;                                             // Write (Both read and write need the address sending as write first)
                    ++state;
                    break;
                case 2:
                    if (((~I2C2STAT) & 0x8000) && ((~I2C2STAT) & 0x4000)) {     // Check for ACKNOWLEDGE
                        I2C2TRN = (uint8_t)(EEPROM_startAddress >> 8);       // High byte of address                             
                        ++state;
                    }
                    break;
                case 3:
                    if (((~I2C2STAT) & 0x8000) && ((~I2C2STAT) & 0x4000)) {     // Check for ACKNOWLEDGE
                        I2C2TRN = (uint8_t)(EEPROM_startAddress);               // Low byte of address                     
                        ++state;
                    }
                case 4:
                    if (((~I2C2STAT) & 0x8000) && ((~I2C2STAT) & 0x4000)) {     // Check for ACKNOWLEDGE
                        if (readNWrite) {                                       // High means read, low means write
                            LATASET = RnW_pin;                                  // Disable EEPROM write
                            I2C2CONSET = 0x0002;                                // Send START for read
                            ++state;
                        } else {
                            state = 9;                                          // Start of write code
                        }
                    }
                    break;
                case 5:
                    I2C2TRN = 0xa1;                                             // Read
                    ++state;
                    break;
                case 6:
                    if (((~I2C2STAT) & 0x8000) && ((~I2C2STAT) & 0x4000)) {     // Check for ACKNOWLEDGE
                        I2C2CON = 0x8208;                                       // Enable receive
                        ++state;
                    }
               case 7:
                    if (I2C2STAT & 0x02) {                                      // Data in read buffer
                        *(HMI_SliderData[0][0] + EEPROM_startAddress) = I2C2RCV;
                        ++EEPROM_startAddress;
                        if (EEPROM_startAddress == EEPROM_endAddress) {
                            I2C2CON = 0x8204;                                   //Send STOP
                            readNWrite = 1;
                            state = 0;
                        } else {
                            if (EEPROM_startAddress == 128  || EEPROM_startAddress == 256 || EEPROM_startAddress == 384 || EEPROM_startAddress == 512) {
                               I2C2CONSET = 0x04;                               // Send STOP 
                               state = 0;                                       // Start again for new page
                            } else {
                                I2C2CONSET = 0x8210;                            //Send ACKNOWLEDGE
                                state = 8;
                            }
                        }
                    }
                    break;
                case 8:
                    I2C2CON = 0x8208;                                           // Enable receive
                    state = 7;
                    break;
                case 9:
                    I2C2TRN = *(HMI_SliderData[0][0] + EEPROM_startAddress);
                    ++state;
                    break;
                case 10:
                    if (((~I2C2STAT) & 0x8000) && ((~I2C2STAT) & 0x4000)) {     // Check for ACKNOWLEDGE
                        ++EEPROM_startAddress;
                        if (EEPROM_startAddress == EEPROM_endAddress) {         // Finished
                            I2C2CONSET = 0x04;                                  // Send STOP
                            lastMillis = millis();
                            state = 12;
                        } else {
                            if (EEPROM_startAddress == 128  || EEPROM_startAddress == 256 || EEPROM_startAddress == 384 || EEPROM_startAddress == 512) {
                               I2C2CONSET = 0x04;                               // Send STOP 
                               lastMillis = millis();                           // Wait for EEPROM write
                               state = 11;
                            } else {
                                state = 9;  
                            }
                        }
                    }
                    break;
                case 11:
                    if (currentMillis - lastMillis > wait) {
                        state = 0;                                              // Start again for new page
                    }
                    break;
                case 12:
                    if (currentMillis - lastMillis > wait) {
                        LATASET = RnW_pin;                                      // Disable EEPROM write
                        readNWrite = 1;
                        state = 0;
                    }
                    break;
                default:
                    I2C2CON = 0x8204;                                           //Send STOP
                    LATASET = RnW_pin;                                          // Disable EEPROM write
                    state = 0;
                    break;
            }
        }
    } else if (state) {
        if (currentMillis - lastMillis > wait) {
            LATASET = RnW_pin;                                  // Disable EEPROM write
            readNWrite = 1;
            state = 0;
        }
    }
}

Start the write sequence

// Write values in settings to EEPROM; only intended to be used once to initialise EEPROM on first use or if default settings are changed
void initialiseEEPROMwrite() {
    EEPROM_startAddress = 0x00;
    EEPROM_endAddress = sizeof(HMI_SliderData);
    readNWrite = 0;
}

Starts the read sequence

// Read data from EEPROM to initialise HMI_SliderData; to be used once EEPROM has been pre-configured with correct start values
void initialiseEEPROMread() {
    EEPROM_startAddress = 0x00;
    EEPROM_endAddress = sizeof(HMI_SliderData);
    readNWrite = 1;
}