AT25SF081 Tutorial 02: Reading Memory Byte


In this module we’ll begin working with the AT25SF081 Flash Memory IC by readying individual bytes of data from the memory array.


  1. Correctly create the SPISettings object.
  2. Determine which Memory Read opcode to use.
  3. Determine which which variable types to use for the address and data.
  4. Understand how to transmit the 24-bit memory address 8-bits at a time.

AT25SF81 Functionality Overview Part 1
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.

Reading AT25SF081 Memory

We’re going to start working with the flash memory using the read function, because this doesn’t affect the endurance of the NAND gates that make up the memory, so we can perfect our code without moving the chip towards the degradation point.

The individual bytes of memory are accessed using their 24 bit address, 0x000000 to 0x0FFFFF, and you can choose to start reading at any point in the array.

There are two read operations codes (opcodes), slow and fast. Slow Read, opcode 0x03, allows you to read the array up to a clock speed of 50Mhz. Fast Read, opcode 0x0B, allows you to read the array up to a clock speed of 80Mhz. The fastest clock speed the Arduino can achieve is 8Mhz, so either will suit our purposes.

For all work done on the AT25SF081, the opcode designating the action you want to take is the first item that is sent to the IC, after bringing CS low. To read data, you then send the 24 bit memory address associated with the byte you want to start with. If you choose to use the Fast Read command, you would then need to clock in a byte of junk data, for the Slow Read, this isn’t necessary.

After the correct read opcode has been issued (with the junk byte if necessary), data will begin to output on MISO, 8 bits at a time. As long as the clock keeps cycling, bytes of data will be read back in sequence indefinitely. If you reach the address at the end of the array, the internal pointer inside the AT25SF081 will move to the first address in the array, 0x000000, and start reading from there without you needing to take any action.

Once you’ve gathered as much data as you want, you bring CS high and that terminates the read operation.


We need to know three things to correctly configure the SPI settings for the AT25SF081. We need to know the speed at which it is capable of functioning, the bit order that data is transmitted in, and the SPI Data Modes the IC supports.

Page 33 of the datasheet shows that the slowest clock speed is associated with the 0x03 Slow Read opcode and is 50Mhz, so we can select the fastest Arduino clock speed of 8Mhz.

Page 6 of the datasheet states that all “data bytes are transferred with the most-significant bit (MSB) first.”

Finally, the same page of the datasheet says that the AT25SF081 supports SPI Modes 0 and 3 (0,0 and 1,1). This means that data is always sampled on the rising edge of the clock and latched on the falling edge, regardless of CPOL.

Combining these three items, we can build our SPISettings command as SPISettings AT25SF081(8000000, MSBFIRST, SPI_MODE0);


As complicated as this chip is to work with (seriously, two modules to explain the functionality), there are surprisingly few things that need to appear in the declarations of our sketch in order to read data from the chip. We need to load the SPI library, establish the chip select pin (and the hold pin for the breakout board users), and define our two different read opcodes.


Because of the effects of chip endurance subsequent to 100,000 program / erase operations on the chip, and because these are educational modules and not production code, I am going to intentionally execute only small amounts of operations on the chip. One of the easiest ways to do this, is to ensure all chip operations occur within the Setup function, rather than the loop. This is a hamfisted way of preventing code from executing more than once, but it is effective.

The start of the Setup function is pretty cut and dried: we’ll prepare the Serial monitor and initialize the SPI bus, then set the pinMode for chip select (and Hold), and define the pin state.

After that basic stuff is accomplished, we’ll want to set the memory address we’re going to read. Addresses in the array are 24 bits in length, which means they exceed the space available within an integer variable type, two bytes, 0x2211. We need a larger variable type to hold our address value and the long type does nicely as it will hold three bytes of data, 0x332211. We can start anywhere in the array we want, and it would be perfectly ok to start at the beginning, 0x000000, but I’m going to select memory address 0x0FE8FF to start reading from, only so that it’s obvious on an oscilloscope when it gets transmitted.

A byte of data is made up to two four bit chunks and in hex these are referred to as the upper nibble and the lower nibble, the most significant four bits and the least significant four bits respectively. Even though the memory address is 24 bits, two and a half bytes 0x32211, the convention is to always specify both the upper nibble and the lower nibble of a byte, even if the upper nibble is a zero, 0x032211.

We’ll need a variable to hold the data returned from the flash memory, and we also want to go ahead and create our usual JUNK variable to send on MOSI, which generates clock cycles for data return.

Now we get into the meat of communicating with the chip. We’ll use our SPISettings object, bring CS low, and then send the Slow Read opcode. I’m choosing the Slow Read because Fast Read requires an extra byte of junk data to be transmitted before data is returned, which isn’t all that much of a performance hit at 80Mhz, but since we’re Arduino limited to 8Mhz, we may as well use the one that doesn’t require extra setup time.

Sending the Memory Address

After the opcode is transmitted, we need to send our memory address. We have 28 bits to transmit (24 bits of actual address plus 8 leading zero bits), but each SPI.Transfer() command will only send 8 bits. That means we have to slice up the address into three sequenced chunks. We do this by masking off each byte of the memory address in turn, shifting that byte into the least significant position, then transmitting that result.

Data is transmitted MSB first, so we need to transmit the left most byte of address data to start with.

Address Mask - Most Significant Byte
Address Mask – Most Significant Byte

Remember, we only send eight bits, so if we just transmitted the masked data, we would only clock out the least significant eight bits, which are still just zero. That means we have to shift the values we care about into those least significant bit positions, 16 bits to the right.

For the middle byte of data, we perform a similar process. This time we mask off the middle byte of address value.

Address Mask - Middle Significant Byte
Address Mask – Middle Significant Byte

That middle byte is still 8 bits away from the LSB position, so we shift it over as well.

The last byte of address goes through a similar process. This isn’t entirely necessary, since the least significant byte is what would be transmitted by default, but I think it makes the code more clear, at the expense of a little efficiency.

Address Mask - Least Significant Byte
Address Mask – Least Significant Byte

Those byte values are sent using the SPI.transfer function one at a time…

Reading the Data

After the hoops you had to jump through to transmit the memory address, getting the data back is trivial. The chip will begin transmitting data on the next clock cycle after a valid address, and continue to do so until CS goes high again. Since we have to send data out on MOSI in order to generate the clock cycles, we’ll transmit our junk variable, and receive data back into the memoryData variable. After we receive our byte of data, we’ll bring chip select high again, release the SPI configuration and that is that.

The Full Sketch

All that’s left to do is add some Serial Monitor output, formatted nicely for ease of reading. Once this is uploaded, you can open the serial monitor and you should receive a single line of output. Sort of anticlimactic, but a good start.

AT25SF081 - Single Byte Read Output
AT25SF081 – Single Byte Read Output

Remember, a byte is considered erased if all the bits are set to 1, so if you’re working with a new chip, you’re reading an erased byte of data, which is why the value returned in the serial monitor shows as 0xFF.