AT25SF081 Tutorial 04: Programming Memory


We can pull back all the information we want from the chip, so let’s work on sending some data for it to store instead.


  1. Determine which opcode is used to program the array.
  2. Understand how the array handles writing past the end of a page boundary.

AT25SF081 Tutorial 02: Reading Memory Byte
SPI Signals
Adesto AT25SF081 Datasheet

Education Shield – AT25SF081 Flash Memory Subsystem

For this module, you’ll need the following equipment:

1. Mate the Education Shield with your Arduino UNO R3. If you’re using the AT25SF081 Flash Memory Breakout Board, connect 5V to 5V, 3V3 to 3.3V, GND to GND, HOLD to Digital09,CS to Digital10, MOSI to Digital11, MISO to Digital12, and CLK to Digital13. That’s a lot of pins!

2. Connect your Arduino to your USB cable, and use the Arduino IDE to upload the “Bare Minimum” sketch from the Basics section of the Examples.

Working with the HOLD pin is not necessary for any of the tutorials, it is only included on the breakout board for completeness. In the sketch it will always be set as an output in a high state, and on the I2C and SPI Education Shield, the pin is tied directly to the 3V3 rail on the PCB.

Programming the AT25SF081 Memory Array

You can write anywhere from a single byte of data up to 256 bytes in a single shot to the AT25SF081. To do this, you use the opcode 0x02. The process you follow is a little more involved than the method used for reading from the array…

  1. Set the WEL bit.
  2. Send the program opcode.
  3. Send the starting memory address you want to write to.
  4. Send the data you want to program to the memory array.

Both setting the WEL bit, and the program command operations need to be wrapped inside bringing the chip select pin low then high.

Memory Pages

It is important to understand how the memory is organized inside the chip and how that will affect your write commands. Each byte inside the chip is part of a discrete page of memory, laid out in chunks of 256 bytes. You know if you’re at the beginning of a page because the least significant byte of the address will be a zero.

AT25SF081 Page Address Diagram
AT25SF081 Page Address Diagram

Single Byte Program

If all you do is write a single byte to the array at a time, then everything will function just as you expect. You specify the byte address, write a single byte and move on from there.

Multiple Byte Program

Writing more than a single byte can have an unexpected consequence. Multiple bytes of data sent with a single command are at risk for overflowing the end of the page. If that happens, the chip will automatically begin writing the subsequent data to the start of the same page, which is a little weird. This is very important because if you begin overwriting previously programmed data, you will just store gibberish. Overwriting flash memory is not supported. If you want to write new data to a byte, it has to be erased first due to error correction code performed within the chip itself. Care must therefore be taken to make sure you know where your writing your data, how much data you’re writing, and how much free space within the page you have to work with.

Programming a Single Byte

We’ll start off by storing a single byte of data in our array. Program and Erase commands are responsible for chewing into the endurance of the chip, so our test code will be carefully designed to only run once if at all possible. We can use the previously completed sketch as a starting point, since we’ll want to confirm our activity by displaying before and after output.

We don’t have to change the existing code so much as we need to make some additions to it. The only real change is to move the chip initialization into it’s own function, just as we did for the I2C devices. For additions, firstly we need to add the Program Byte opcode 0x02 and the Write Enable opcode 0x06 to our declarations section (and an opcode for reading the status register, but we’ll get to that). Since this is just an example, we’ll also hard code the data that we’re going to program to the memory as well. The major change will be in creating a function that performs all the steps listed above to perform the program routine. I’m going to call this function writeByte.

Set the WEL Bit

The Write Enable bit is a part of Status Register 01, and it is set and cleared using the Write Enable opcode 0x06 and the Write Disable opcode 0x04 respectively. For the most part, we only need to set the WEL bit, because it will automatically be cleared after the successful execution of a program command.

Program the Array

The WEL bit is now set so we can perform a program operation. First the opcode is sent, then as we did when reading the array, we have to break the starting 24 bit address into discrete 8 bit chunks, and we’ll use the same commands to accomplish that. Once the address is clocked in, you can start to transfer the data over MOSI to the chip.

You can immediately see that for both setting the WEL bit and the program commands, each is surrounded by code to manipulate the state of the chip select pin. The WEL bit change doesn’t take effect until the pin is brought high to latch the value in, so it has to be separated from the rest of the commands. If you tried to do it all at once, the program commands would be ignored by the AT25S081 because the WEL bit would still be cleared.

The full function then looks like this…

Testing and Timing

Read commands are executed instantaneously, since all you are doing is sampling the existing state of a NAND gate array within the chip. Program and Erase commands change the state of those gates and that takes a bit of time, so you need to accommodate that delay after any program / erase cycle.

The method for doing this is polling the READY/BUSY bit, bit 0, in Status Register 01. If the chip is executing an erase or program command the bit is set automatically, and all you have to do is keep checking to see when the bit gets cleared. The datasheet defines the lengths of time it takes to write or erase sections of the memory array, but also says it’s more efficient to simply check the state of that bit, rather than doing a hard coded time out based on the times listed. Fair enough.

It’s very simple to poll the busy status of the chip, and we’ll create a function called checkStatus to do this…

We create a small variable called readyBusy and set it equal to 1. Chip select is brought load and we clock in the Status Register 01 Read opcode. Then we repeatedly load readyBusy with the value of the least significant bit of Status Register 01, the RDY/BSY bit, and wait for it to evaluate as cleared. Therefore, as long as the chip is busy, it will stick in this while loop, which is a quick and dirty way of pausing our sketch while the chip is performing some task. The datasheet says that after you clock in the read opcode, subsequent clocks cause the value of Status Register 01 to be transmitted over and over and over again, so you don’t have to constantly cycle chip select and resend the opcode.

Mind you, this is definitely quick and dirty. It’s blocking code, which means that your Arduino is prevented from performing any other task while this is executing. It’s also somewhat indeterminate as to what happens if there’s a problem with the SPI bus during this time. What happens it if jams up and just sends back 1s indefinitely? The code will hang at this point and never evaluate to 0, ending the loop. More robust code would want to evaluate against a timer of some sort and if the maximum length of time has passed that should have been necessary to execute the command, break out of the while loop and blink LEDs or something to indicate an error has occurred.

But for our purposes of testing the functionality of the chip, this is good enough.

Quick Check of Execution Logic

Think through the execution of the program and what we want to have happen. Ideally, we want to see a whole bunch of FF values indicating that everything is in an erased state, then after we program our byte, we would expect to see one of those values changed to whatever value we’re sending (my code is sending 0xCC). Critically, we want to verify that it changed because of our code by showing the value of the byte before the program command and then the value of the byte after the program command.

If we just upload this sketch with the execution of the program opcode in Setup as we’ve done in the previous two sketches, we screw things up because it will execute immediately after upload, then execute again when you open the serial monitor. As a result, you won’t see any change occur because it already happened right after you clicked upload.

That means using our buttons will come in handy. We’ll tie the execution of our little memory program test to the Latch button so we can cause it to fire off when we choose… meaning after we upload and get the serial monitor window open.

Here is the full sketch including all the changes.

Upload that, then open the serial monitor and press the Latch button and you should see the before and after states of the first page of the memory array.

Now, remember how I said you can’t overwrite values because of the internal error correction of the chip? Since you don’t have any erase code yet, anything you do will be an attempt to overwrite the value you set in your code. Change the value of the someData variable to other things and upload to see what happens. This is why it’s crucial to know how much you’re planning to write and where you’re going to wind up writing it.

And don’t worry, the next module will cover erasing, so you can reset your chip 🙂