Raspberry Pi Pico Flash Memory                 Jed Margolin    11 July 2024

 

 

This is an expanded version of the Flash Memory section of my article My Raspberry Pi Pico Projects

 

The Pico uses a separate Flash Memory for the program. The official Pico uses the Winbond W25Q16JV 3V 16M-Bit Serial Flash Memory with Dual/Quad SPI Interface. That is 2 MBytes = 2,097,152 bytes. You can use the Flash Memory that isn’t used for your program to store whatever you want. I sometimes use it to keep a time-stamped data log. Here is how that works.

 

1.  The blank state is all ‘1’s. Writing (‘Programming’) changes the selected bits to ‘0’.  Erasing changes the bits back to ‘1’s. When the memory is worn out you can’t do that anymore. (I don’t know if you cannot erase it or you cannot write it. In any event it is hosed.)

 

2.  Erasing data is done only in sectors of 4096 bytes. You cannot erase fewer than 4096 bytes. Thus there are 512 sectors in 2 MBytes.

 

Writing data is done only in pages of 256 bytes. When you do a Write you have to write 256 bytes. (There is the option of writing pages of 512 bytes but I prefer 256 bytes.)

 

The address for Erasing and Writing to the Flash Memory is a local address within the Flash Memory. It goes from 0x00 0000 to 0x1F FFFF (0 to 2,097,151).

 

The address for reading the Flash memory is in the RP2040's address space and starts at 0x1000 0000 (4,294,967,296). That is where your program starts. You are free to read however many bytes you want. More on that shortly.

 

I am allowing 512KB for my program so that for erasing and writing, my log starts at:

 

#define FLASH_TARGET_OFFSET (512 * 1024)    // 524,288 = 0x0008 0000 (Start of my flash data for erasing and writing)

 

I keep the variable ‘Sector_Index’ for erasing so that:

 

// Erase_Address = FLASH_TARGET_OFFSET + (Sector_Index * 4096);

   Erase_Address = FLASH_TARGET_OFFSET + (Sector_Index<<12);

 

The instruction for erasing a sector is:

 

   flash_range_erase(Erase_Address, FLASH_SECTOR_SIZE); 

 

FLASH_SECTOR_SIZE is predefined as 4096.

 

 

I keep the variable ‘Page_Index’ for writing so that:

 

#define FLASH_PAGE_SIZE 256 

 

//   flash_write_address = FLASH_TARGET_OFFSET + (Page_Index * 256)

   flash_write_address = FLASH_TARGET_OFFSET + (Page_Index<<8)

 

The instruction for writing a page is:

 

  flash_program(flash_write_address, Data_Buffer, FLASH_PAGE_SIZE);

 

Although Winbond allows Writes of fewer than 256 bytes the Pico People support only Writes of 256 or 512 bytes at a time.

 

Therefore, the Data_Buffer must be 256 bytes long. When I am not using all of them I set the unused bytes to ‘FF’.

 

3.  The address for reading the Flash memory is in the RP2040's address space and starts at 0x1000 0000 (4,294,967,296). You are free to read however many bytes you want.

 

Reading starts at the predefined address:

 

XIP_BASE = 0x10000000  // 0x1000 0000 (4,294,967,296). That is where the program memory starts.

 

My log starts at 512 KBytes after that so that for reading my Flash data, in the RP2040's memory space:

 

#define XIP_BASE 0x10000000     // already defined, for reading

#define FLASH_READ_ADDRESS (XIP_BASE + FLASH_TARGET_OFFSET)

 

To access it by the start of the page:

 

// RP_Read_Address = XIP_BASE + FLASH_TARGET_OFFSET + (Page_Index * 256);

RP_Read_Address = XIP_BASE + FLASH_TARGET_OFFSET + (Page_Index<<8);

 

I always read it on page boundaries but you don’t have to.

 

4.  What if your Flash entry is fewer than 256 bytes? What if it is, for example, 32 bytes?

 

You have a choice.

 

a.  You can wait until you have 8 entries (32 * 8) and then write it in Flash. To do that you would save it to RAM first. If you do that you will be able to store 32,768 entries. However, if the power goes out before you write to Flash you could lose as many as 8 entries.

 

b.  Or you can write the 32 bytes, making the remaining 224 bytes ‘0xFF’. If you do that you will be able to store 4096 entries. If the power goes out you won’t lose any data as long as the power doesn’t go out during the write to Flash. Then you would lose that entry.

 

My Pump Monitor entries are 32 bytes and only has to store maybe 4 entries per day. If I use the first method I would lose 2 days of data, so I use the second method. At 4 entries per day that gives me about 1024 days (2.8 years) of data.   

 

If you were to use the BME-280 to measure temperature, humidity, and air pressure (32 bytes per entry) and log it every 5 minutes you could have 8 entries per 256 byte Page and store 81,920 minutes of data (56.8 days). If you lose power you would lose at most 8 entries (40 minutes) of data.

 

It depends on what you want.

 

5.  I have added something to my use of Flash for my Pump Monitor. I want to know the date that my program started logging data. Ideally, that would be when the pump was put into service so I would know the number of lifetime pump starts it has had. The new pump in my well was put into service last year so that will not be possible but what I am doing is close enough.

 

Submerged Pumps for Deep Wells should last 8 - 15 years. The main factor is how often it is used. Although pumps are generally rated for continuous operation they are also generally rated for only 500 - 1,000 pump starts per day. That is because startup is hard on the pump motor and the pump turbine. Startup produces more heat than it does once the water is flowing. The heat is bad for the motor windings and for the turbine bearings.

 

I keep the Start Date in its own Flash Page and I keep it as a string so all I have to do is read it and send it out through the uart. I keep it above my data at the beginning of Flash Sector 258 which is Page 4128. This leaves a space of one sector between my entry data and the Start date.

 

When the program starts up it checks if Page 4128 is blank. If it is, it writes the current date (from the ds3231 RTC) to it. The Start Date can also be manually updated through the keyboard commands.

 

6.  When the program starts I find the first blank page so I know where to add new entries. I also check if my Flash Data has been filled up once before or if it is still on its first go-around.

 

If it has been filled up at least once before, and if the Page to be written is the last page in the Sector, I erase the next Sector. That produces a circular memory so that new data erases the oldest data. If it is on its first go-around this step is omitted.

 

7.  Because my program is a demo I had to choose data for the entries. The easiest to do is ADC data. This is how the data in the entries is structured:

 

//Time-stamped log 32 bytes

struct log

{

   uint16_t ts_number;     // index number                  

  

   uint8_t ts_date;       // ASCII time-stamped date

   uint8_t ts_day;        // ASCII time-stamped day

   uint8_t ts_month;      // ASCII time-stamped month

   uint8_t ts_year;       // ASCII time-stamped year

 

   uint8_t ts_hours;      // ASCII time-stamped hour

   uint8_t ts_minutes;    // ASCII time-stamped minutes

   uint8_t ts_seconds;    // ASCII time-stamped second

 

   uint16_t adc_val;

 

   uint8_t data[20];      // just data

 

   uint8_t flag;          // flag for non-blank

};

 

‘ts-number’ is just an incrementing number that starts at ‘0000’ and wraps after 999.

 

‘flag’ is a number (‘00’) so that a page that has been written will always show as being non-blank regardless of what other data has been stored in the page.  

 

A typical data entry looks like this:

 

Index   Day      Date            Time       ADC      Test Data

0055    Thu    07/04/24    22:03:07    2120      38  39  3A  3B  3C  3D  3E  3F  40  41  42  43  44  45  46  47  48  49  4A  4B 

 

8.  My program uses the uart for commands and getting the data out.

 

I use the uart with a CH340/CH341 USB to TTL Adaptor. This is not USB to Serial Port. Serial Port is RS-232 which was originally a +22V/-22V signal level and used a DB25 connector. Later the voltage level was dropped to as little as +5V/-5V and a DB9 connector could be used. The CH340 is not that. The CH340 does TTL levels but +3.3V is supported. There is a USB connector on one end and a 6-pin header on the other. Select 3V3 with the Header Plug.

 

5V

Don’t Use

VCC

Header Plug

3V3

Header Plug

TXD

To My RXD

RXD

To my TXD

GND

To My GND

 

There are drivers for several operating systems: Windows 7, Windows 10, probably Windows 11, MAC, Linux. There does not appear to be drivers for Chromebook unless you make it a Linux machine. My Windows 10 PC already had the drivers for it. I use it with a freeware serial program called AccessPort from http://www.sudt.com/en/ap/index.html  If you use the keyboard check the box that says Real Time Send.

 

9.  A keyboard command dumps my entire Flash Data, but only the entries that have been written, not blank pages. I can also use a keyboard command to erase my entire Flash Data.

 

These are my keyboard commands (They are case insensitive):

 

V:  Show Version

A:  Show ADC

O:  Log One ADC

L:  Log ADC Every 5 minutes

X:  Stop Logging ADC

D:  Dump All of the Flash Logs to uart

E:  Erase Data Flash?  (then Y/N)

S:  Reset Start Date?  (then Y/N)

H : Repeat This List

 

 

10.  The memory is rated for a minimum of 100K endurance cycles. An endurance cycle is also called a ‘wear cycle’. `What is a wear cycle? That should be an easy question to answer but it isn’t.

 

a.  Reading the memory does not use up a wear cycle.

 

b.  Erasing a sector is definitely a wear cycle. Erasing a sector makes all of the bits in the sector a ‘1’.

 

c.  Writing (‘programming’) a page might or might not always be a wear cycle. Consider this example.

 

1.  Choose an address such as Address = 1,064,960. This is an even boundary of Sector 260 (1,064,960/4096)  and Page 4160 (1,064,960/256).

 

2.  Erase Sector 260.

 

3.  I set up a buffer called ‘out_buffer’ with a length of 256 bytes. I set all 256 bytes to all ones (0xFF).

 

4.  I write data into the first 32 locations of ‘out_buffer’. For example My Data 1. The remaining 224 bytes are still 0xFF.

 

Data

My Data 1

0xFF   ....................................................................................0xFF

Location

0 ..........31

32 ............................................................................................255

 

I write ‘out_buffer’ to Page 4160.

 

I read Page 4160 into another buffer (‘in_buffer’) and the data matches the data in the ‘out_buffer’. Locations 0 - 31 are My Data 1. The remaining 224 bytes are still 0xFF.

 

Then I write 32 bytes of more data into my ‘out_buffer’ starting at location 32. For example My Data 2. Thus I have filled locations 0 - 63 with my data. The remaining 192 bytes are still 0xFF.

 

Data

My Data 1

My Data 2

0xFF.................................................................0xFF

Location

0 ..........31

32.........63

64.......................................................................255

 

Then I write ‘out_buffer’ to Page 4160 again.

 

Then I read Page 4160 again into ‘in_buffer’.  Locations 0 - 31 are the original My Data 1. Locations 32 - 63 are the new data My Data 2.

 

I can do this 8 times before I fill up the page with 256 bytes of my data.

 

I do not change existing data.

 

I have not done any erasing except at the beginning. I am only setting additional bits to zero as required by the new data.

 

I am only doing write cycles.

 

I have tried this on my Raspberry Pi Pico which uses the Winbond W25Q16JV 3V 16M-Bit Serial Flash Memory with Dual/Quad SPI Interface and it works. For the full results of my test Click Here.

 

Will I be using one wear cycle or 8 wear cycles? Remember, I am not doing any erase cycles during this operation except at the beginning.

 

I asked Winbond tech support. The first time I received a non-responsive answer. The second time I provided the explicit results of my test and also asked if the charge pump has wear cycles, too. Their response was better but did not give me confidence that they completely understood my question. I suspect that tech support has a script. My question was not on the script so they gave me the answer to the question they thought was the closest to mine.

 

Therefore, I cannot recommend that you use my method of writing partial data updates to pages. Either accumulate 256 bytes of data before writing it to a page or use an entire page (256 bytes) even when you don’t have a full page of data to store.

 

 

Documentation: 

 

Winbond W25Q16JV 3V 16M-Bit Serial Flash Memory with Dual/Quad SPI Interface

https://www.winbond.com/hq/support/documentation/downloadV2022.jsp?__locale=en&xmlPath=/support/resources/.content/item/DA00-W25Q16JV_1.html&level=1

 

For a local copy Click Here.

 

.end