📝 15 May 2020
There's one thing truly remarkable about the Apache Mynewt embedded operating system... Almost any feature may be switched on by editing a configuration file!
Today we'll learn to configure Mynewt OS to enable access to SPI Flash Memory on PineTime Smart Watch... Just by editing two configuration files (syscfg.yml
and pkg.yml
), and making some minor code changes (hal_bsp.c
).
These steps will work for any Nordic nRF52 device, and probably STM32 devices too (see diagram above)...
Configure pins for SPI Port in syscfg.yml
Configure flash interface in syscfg.yml
Configure flash timings insyscfg.yml
Include spiflash.h
in hal_bsp.c
Define internal and external flash devices in hal_bsp.c
Access flash devices by ID in hal_bsp.c
Add spiflash
driver to pkg.yml
Read on for the details...
syscfg.yml
: Configuration for Board Support Package
syscfg.yml:
Configure Pins for SPI PortThe first configuration file we'll edit is syscfg.yml
from Mynewt's Board Support Package. For PineTime, this file is located at...
Let's add the PineTime SPI settings according to the PineTime Wiki: PineTime Port Assignment...
nRF52 Pin | Function | Description |
---|---|---|
P0.02 | SPI-SCK, LCD_SCK | SPI Clock |
P0.03 | SPI-MOSI, LCD_SDI | SPI MOSI (master to slave) |
P0.04 | SPI-MISO | SPI MISO (slave to master) |
P0.05 | SPI-CE# (SPI-NOR) | SPI Chip Select |
Here's how it looks in syscfg.yml
...
syscfg.vals:
...
# Default Pins for Peripherals
# Defined in http://files.pine64.org/doc/PineTime/PineTime%20Port%20Assignment%20rev1.0.pdf
# SPI port 0 connected to ST7789 display and XT25F32B flash
SPI_0_MASTER_PIN_SCK: 2 # P0.02/AIN0: SPI-SCK, LCD_SCK SPI clock for display and flash
SPI_0_MASTER_PIN_MOSI: 3 # P0.03/AIN1: SPI-MOSI, LCD_SDI SPI MOSI for display and flash
SPI_0_MASTER_PIN_MISO: 4 # P0.04/AIN2: SPI-MISO SPI MISO for flash only
...
For pin numbers on nRF52, we may drop the P0
prefix and write P02.02
as 2
.
We'll add SPI Chip Select (Pin 5
) in a while.
syscfg.yml:
Configure flash interfaceNow we'll edit syscfg.yml
to tell Mynewt how to access our Flash Memory. From the PineTime Wiki...
SPI Flash information: XTX XT25F32B 32 Mb (4 MB) SPI NOR Flash
Data sheets for this part are hard to find but it acts similar to other QuadSPI SPI NOR Flash such as Macronix 32 Mb (4 MB) SPI NOR Flash
IDs for XT25F32B are: Manufacturer (0x0b
), Device (0x15
), Memory Type (0x40
), Density (0x16
)
Confused about MB
and Mb
? MB
stands for Mega Byte (roughly a million bytes), whereas Mb
stands for Mega Bit (roughly a million bits). Divide Mb
by 8 to get MB
.
We don't have the datasheet for XT25F32B Flash Memory... But fortunately the JEDEC IDs for Manufacturer, Memory Type and Density (Capacity) are all that we need for syscfg.yml
...
syscfg.vals:
...
# SPI Flash
# XTX XT25F32B 32 Mb (4 MB) SPI NOR Flash (similar to QuadSPI SPI NOR Flash like Macronix 32 Mb (4 MB) MX25L3233F)
# manufacturer (0x0b), device (0x15), memory type (0x40), density (0x16)
# Settings below are documented at https://github.com/apache/mynewt-core/blob/master/hw/drivers/flash/spiflash/syscfg.yml
SPIFLASH: 1 # Enable SPI Flash
SPIFLASH_SPI_NUM: 0 # SPI Interface 0
SPIFLASH_SPI_CS_PIN: 5 # SPI interface CS pin: P0.05/AIN3, SPI-CE# (SPI-NOR)
SPIFLASH_BAUDRATE: 8000 # Requested baudrate, 8000 is the fastest baudrate supported by nRF52832
SPIFLASH_MANUFACTURER: 0x0B # Expected SpiFlash manufacturer as read by Read JEDEC ID command 9FH
SPIFLASH_MEMORY_TYPE: 0x40 # Expected SpiFlash memory type as read by Read JEDEC ID command 9FH
SPIFLASH_MEMORY_CAPACITY: 0x16 # Expected SpiFlash memory capactity as read by Read JEDEC ID command 9FH (2 ^ 0x16 = 32 Mb)
SPIFLASH_SECTOR_COUNT: 1024 # Number of sectors: 1024 sectors of 4 KB each
SPIFLASH_SECTOR_SIZE: 4096 # Number of bytes that can be erased at a time: 4 KB sector size
SPIFLASH_PAGE_SIZE: 256 # TODO Number of bytes that can be written at a time
...
The JEDEC Device ID (0x15
) is not used.
SPIFLASH_SECTOR_COUNT
and SPIFLASH_SECTOR_SIZE
were copied from Macronix MX25L3233F since it's similar to our XT25F32B.
SPIFLASH_PAGE_SIZE
was copied from another Mynewt configuration: black_vet6
When setting SPIFLASH
to 1, remember to enable the SPI Port in the Application Firmware and Bootloader by setting SPI_0_MASTER
to 1...
syscfg.vals:
OS_MAIN_STACK_SIZE: 1024 # Small stack size: 4 KB
MSYS_1_BLOCK_COUNT: 64 # Allocate extra MSYS buffers
SPIFLASH: 1 # Enable SPI Flash
SPI_0_MASTER: 1 # Enable SPI port 0 for ST7789 display and SPI Flash
Refer to this Application Firmware Configuration and MCUBoot Bootloader Configuration
syscfg.yml:
Configure Flash TimingsFinally we edit syscfg.yml
to tell Mynewt the timing characteristics of our Flash Memory...
syscfg.vals:
...
# Copied from https://github.com/apache/mynewt-core/blob/master/hw/bsp/black_vet6/syscfg.yml
SPIFLASH_TBP1_TYPICAL: 20 # Byte program time (first byte) (us)
SPIFLASH_TBP1_MAXIMUM: 50 # Maximum byte program time (first byte) (us)
SPIFLASH_TPP_TYPICAL: 700 # Page program time (us)
SPIFLASH_TPP_MAXIMUM: 3000 # Maximum page program time (us)
SPIFLASH_TSE_TYPICAL: 30000 # Sector erase time (4KB) (us)
SPIFLASH_TSE_MAXIMUM: 400000 # Maximum sector erase time (us)
SPIFLASH_TBE1_TYPICAL: 120000 # Block erase time (32KB) (us)
SPIFLASH_TBE1_MAXIMUM: 800000 # Maximum block erase time (32KB) (us)
SPIFLASH_TBE2_TYPICAL: 150000 # Block erase time (64KB) (us)
SPIFLASH_TBE2_MAXIMUM: 1000000 # Maximum block erase time (64KB) (us)
SPIFLASH_TCE_TYPICAL: 3000000 # Chip erase time (us)
SPIFLASH_TCE_MAXIMUM: 10000000 # Maximum chip erase time (us)
...
If we have the Flash Memory Datasheet, fill in the numbers from the datasheet.
The above settings were copied from another Mynewt configuration: black_vet6
hal_bsp.c
: Code for Board Support Package
hal_bsp.c:
Include spiflash.h
After editing syscfg.yml
in the Board Support Package, let's make some minor tweaks to the source code (hal_bsp.c
) of the Board Support Package.
For PineTime, the source file is located at...
First we insert the header file for the SPI Flash Driver into hal_bsp.c
like this...
#if MYNEWT_VAL(SPIFLASH) // If External SPI Flash exists...
#include <spiflash/spiflash.h>
#endif // MYNEWT_VAL(SPIFLASH)
hal_bsp.c:
Define Internal and External Flash DevicesNext we define two Mynewt Flash Devices...
Edit hal_bsp.c
and insert this block of code...
/// Array of Flash Devices
static const struct hal_flash *flash_devs[] = {
[0] = &nrf52k_flash_dev, // Internal Flash ROM
#if MYNEWT_VAL(SPIFLASH) // If External SPI Flash exists...
[1] = &spiflash_dev.hal, // External SPI Flash
#endif // MYNEWT_VAL(SPIFLASH)
};
Later we'll use Flash Device ID 1 when accessing SPI Flash.
hal_bsp.c:
Access Flash Devices by IDFinally we edit the code to fetch the Flash Devices by the Flash Device ID.
Edit hal_bsp.c
. Look for the function hal_bsp_flash_dev()
and replace the function by this code...
/// Return the Flash Device for the ID. 0 for Internal Flash ROM, 1 for External SPI Flash
const struct hal_flash *
hal_bsp_flash_dev(uint8_t id)
{
if (id >= ARRAY_SIZE(flash_devs)) {
return NULL;
}
return flash_devs[id];
}
This function returns the Internal Flash ROM for ID 0, and External SPI Flash for ID 1.
pkg.yml
: Drivers for Board Support Package
pkg.yml:
Add spiflash
driverThe last file we'll edit is pkg.yml
from the Board Support Package. For PineTime this file is located at...
Edit pkg.yml
. Look for the pkg.deps
section and add spiflash
like this...
pkg.deps:
...
- "@apache-mynewt-core/hw/drivers/flash/spiflash" # SPI Flash Driver
This starts up the SPI Flash Driver whenever Mynewt boots. And we're done!
Now let's write a simple program to read, write and erase the SPI Flash.
Here's the C code to test reading, writing and erasing SPI Flash on Mynewt: flash_test.c
. The code was derived from Mynewt's test code for Flash Devices.
The test code calls Mynewt's Flash HAL (Hardware Adaptation Layer) to access the flash memory...
Erase Flash hal_flash_erase(id, offset, size)
Erase internal / external flash memory at the offset
address, for size
bytes. See hal_flash_erase
Read Flash hal_flash_read(id, offset, buf, size)
Read internal / external flash memory from the offset
address into the buf
buffer, for size
bytes. See hal_flash_read
Write Flash hal_flash_write(id, offset, buf, size)
Write internal / external flash memory from the buf
buffer to the offset
address, for size
bytes. See hal_flash_write
For the above functions, id
is 0 for Internal Flash ROM, 1 for External SPI Flash.
For easier testing, the above functions are wrapped inside the flash_cmd()
function, which is also defined in flash_test.c
Let's call flash_cmd()
to test the SPI Flash functions...
Here's the test code in flash_test.c
to read Internal Flash ROM and External SPI Flash...
/// Test internal flash ROM and external SPI flash
int test_flash() {
// Keep running tests until a test returns an error (non-zero result)
if (
////////////////////////////////
// Read Flash
// <flash-id> <offset> <size>
// Read internal flash ROM
flash_cmd(READ_COMMAND, 0, 0x0, 32) ||
// Read external SPI flash
flash_cmd(READ_COMMAND, 1, 0x0, 32) ||
...
0
) { return -1; } // Tests failed
return 0; // Tests OK
}
This code reads 32 bytes, starting at offset 0, from both Internal Flash ROM and External SPI Flash. As expected, the flash memory contents are different for Internal Flash ROM and External SPI Flash...
Testing flash...
Read Internal Flash ROM...
Read 0x0 + 20
0x0000: 0x00 0x00 0x01 0x20 0xd9 0x00 0x00 0x00
0x0008: 0x35 0x01 0x00 0x00 0x37 0x01 0x00 0x00
0x0010: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x0018: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
Read External SPI Flash...
Read 0x0 + 20
0x0000: 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08
0x0008: 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10
0x0010: 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18
0x0018: 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f 0x20
Next in flash_test.c
we erase the External SPI Flash...
/////////////////////////////////////
// Erase Flash: Set all bits to 1
// <flash-id> <offset> <size>
// Erase external SPI flash
flash_cmd(ERASE_COMMAND, 1, 0x0, 32) ||
Erase External SPI Flash...
Erase 0x0 + 20
Done!
After erasing, let's read both Internal Flash ROM and External SPI Flash (flash_test.c
)...
////////////////////////////////////////
// Read Flash
// <flash-id> read <offset> <size>
// Read internal flash ROM
flash_cmd(READ_COMMAND, 0, 0x0, 32) ||
// Read external SPI flash
flash_cmd(READ_COMMAND, 1, 0x0, 32) ||
Here are the contents...
Read Internal Flash ROM...
Read 0x0 + 20
0x0000: 0x00 0x00 0x01 0x20 0xd9 0x00 0x00 0x00
0x0008: 0x35 0x01 0x00 0x00 0x37 0x01 0x00 0x00
0x0010: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x0018: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
Read External SPI Flash...
Read 0x0 + 20
0x0000: 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0x0008: 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0x0010: 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
0x0018: 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff
Why was SPI Flash wiped out with 0xff
?
That's expected when we erase NOR Flash Memory... All bits will get set to 1
.
When we write to NOR Flash Memory, we may only flip 1
bits to 0
, not 0
to 1
.
Hence before writing any data into NOR Flash Memory, we need to erase all bits to 1
. Then we write the data to flash, flipping some 1
bits to 0
.
Now that SPI Flash has been flipped to 1
, let's write some data (flash_test.c
)...
//////////////////////////////////////////////
// Write Flash: Write 0x01, 0x02, 0x03, ...
// (Must erase before writing)
// <flash-id> <offset> <size>
// Write external SPI flash
flash_cmd(WRITE_COMMAND, 1, 0x0, 32) ||
This shows...
Write External SPI Flash...
Write 0x0 + 20
Done!
flash_cmd()
writes to SPI Flash the bytes 0x01
, 0x02
, 0x03
, ...
Let's read SPI Flash and check (flash_test.c
)...
////////////////////////////////////////////
// Read Flash
// <flash-id> <offset> <size>
// Read internal flash ROM
flash_cmd(READ_COMMAND, 0, 0x0, 32) ||
// Read external SPI flash
flash_cmd(READ_COMMAND, 1, 0x0, 32) ||
Here's the result...
Read Internal Flash ROM...
Read 0x0 + 20
0x0000: 0x00 0x00 0x01 0x20 0xd9 0x00 0x00 0x00
0x0008: 0x35 0x01 0x00 0x00 0x37 0x01 0x00 0x00
0x0010: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0x0018: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
Read External SPI Flash...
Read 0x0 + 20
0x0000: 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08
0x0008: 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f 0x10
0x0010: 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18
0x0018: 0x19 0x1a 0x1b 0x1c 0x1d 0x1e 0x1f 0x20
Yep the flipping of bits from 1
to 0
worked!
To check whether SPI Flash is correctly configured, we may dump the Flash Sector Map like this (flash_test.c
)...
////////////////////
// Dump Sector Map
// Dump sector map for internal flash ROM
map_cmd(0) ||
// Dump sector map for external SPI flash
map_cmd(1) ||
Here's the output...
Sector Map for Internal Flash ROM...
Flash 0 at 0x0 size 0x80000 with 128 sectors, alignment req 1 bytes
0: 1000
1: 1000
2: 1000
...
127: 1000
Sector Map for External SPI Flash...
Flash 1 at 0x0 size 0x400000 with 1024 sectors, alignment req 1 bytes
0: 1000
1: 1000
2: 1000
...
1023: 1000
This says that SPI Flash has been configured with 1024 sectors, each sector 4 KB in size (0x1000
).
How fast can we read SPI Flash? Compared with Internal Flash ROM?
Let's run the flash speed tests found in flash_test.c
...
//////////////////////
// Test Flash Speed
// <flash_id> <addr> <rd_sz>|range [move]
// range=0 for size mode, range=1 for range mode, move=1 for move
// Internal flash ROM, size mode, no move
speed_cmd(0, 0x0, 32, 0, 0) ||
// External SPI flash, size mode, no move
speed_cmd(1, 0x0, 32, 0, 0) ||
This code repeatedly reads 32 bytes from Internal Flash ROM and External SPI Flash... And counts how many Read Operations were completed in 2 seconds.
Here's the output...
Speed Test for Internal Flash ROM...
Speed test, hal_flash_read(0, 0x0, 32)
207503
Speed Test for External SPI Flash...
Speed test, hal_flash_read(1, 0x0, 32)
16107
That's 207,503 Read Operations Per 2 Seconds for Internal Flash ROM, and 16,107 Read Operations Per 2 Seconds for External SPI Flash.
When we divide the numbers by 2, we get the Read Operations Per Second...
Size (Bytes) | Flash ROM Reads/Sec | SPI Flash Reads/Sec |
---|---|---|
32 | 103,751 | 8,053 |
As expected, Internal Flash ROM is faster than External SPI Flash, roughly 13 times faster.
Let's run the test speed on a range of data sizes, from 1 byte to 256 bytes (flash_test.c
)...
// Internal flash ROM, range mode, no move
speed_cmd(0, 0x0, 0, 1, 0) ||
// External SPI flash, range mode, no move
speed_cmd(1, 0x0, 0, 1, 0) ||
Now we get a table of Read Operations Per 2 Seconds (second column), for data sizes ranging from 1 byte to 256 bytes (first column)...
Speed Test for Internal Flash ROM...
Speed test, hal_flash_read(0, 0x0, X)
1 271962
2 261931
4 271962
8 260862
16 241174
24 221913
32 207503
48 182082
64 162210
96 133148
128 112917
192 86600
256 70232
Speed Test for External SPI Flash...
Speed test, hal_flash_read(1, 0x0, X)
1 44139
2 42048
4 37684
8 31639
16 23955
24 19250
32 16107
48 12132
64 9731
96 6971
128 5431
192 3766
256 2883
Divide the numbers by 2 to get Read Operations Per Second for various data sizes...
Size (Bytes) | Flash ROM Reads/Sec | SPI Flash Reads/Sec |
---|---|---|
1 | 135,981 | 22,069 |
2 | 130,965 | 21,024 |
4 | 135,981 | 18,842 |
8 | 130,431 | 15,819 |
16 | 120,587 | 11,977 |
24 | 110,956 | 9,625 |
32 | 103,751 | 8,053 |
48 | 91,041 | 6,066 |
64 | 81,105 | 4,865 |
96 | 66,574 | 3,485 |
128 | 56,458 | 2,715 |
192 | 43,300 | 1,883 |
256 | 35,116 | 1,441 |
Internal Flash ROM is still faster than External SPI Flash. For reads of 1 byte, Internal Flash ROM is faster by 6 times. But for reads of 256 bytes, Internal Flash ROM is faster by 24 times!
MCUBoot is an open-source Bootloader that supports Mynewt, RIOT and Zephyr operating systems... It's the first thing that will run on PineTime when it boots.
During firmware updates, MCUBoot stores the previous version of the firmware into Internal Flash ROM, and rolls back to the previous firmware if the new firmware doesn't start properly.
But on constrained devices like PineTime, this robustness will cost us... We might run out of space in Internal Flash ROM to store the old firmware!
Fortunately MCUBoot on Mynewt works seamlessly with SPI Flash... Watch how the new firmware FLASH_AREA_IMAGE_0
coexists with the old firmware FLASH_AREA_IMAGE_1
in PineTime's Flash Memory Map for MCUBoot: hw/bsp/nrf52/bsp.yml
# Flash Memory Map for PineTime: Internal Flash ROM and External SPI Flash
bsp.flash_map:
areas:
# System areas.
FLASH_AREA_BOOTLOADER: # MCUBoot
device: 0 # Internal Flash ROM
offset: 0x00000000 # Start of Internal Flash ROM
size: 16kB
FLASH_AREA_IMAGE_0: # Active Firmware Image
device: 0 # Internal Flash ROM
offset: 0x00008000
size: 464kB # Max size of Firmware Image
FLASH_AREA_IMAGE_1: # Standby Firmware Image
device: 1 # External SPI Flash
offset: 0x00000000 # Start of External SPI Flash
size: 464kB # Max size of Firmware Image
FLASH_AREA_IMAGE_SCRATCH: # Used by MCUBoot for swapping Active and Standby Firmware
device: 0 # Internal Flash ROM
offset: 0x0007c000
size: 4kB
# User areas.
FLASH_AREA_REBOOT_LOG: # For logging debug messages during startup
user_id: 0
device: 0 # Internal Flash ROM
offset: 0x00004000
size: 16kB
FLASH_AREA_NFFS: # For user files
user_id: 1
device: 1 # External SPI Flash
offset: 0x00074000
size: 3632kB
Note that the new firmware FLASH_AREA_IMAGE_0
resides on Flash Device 0 (Internal Flash ROM), while the old firmware FLASH_AREA_IMAGE_1
resides on Flash Device 1 (External SPI Flash).
This means that we won't waste any previous space in Internal Flash ROM for storing the old firmware... MCUBoot automatically swaps the old firmware into External SPI Flash! Using MCUBoot Bootloader with SPI Flash is really that easy!
When using SPI Flash, remember to enable the SPI Port in the Application Firmware and Bootloader by setting SPI_0_MASTER
to 1...
syscfg.vals:
OS_MAIN_STACK_SIZE: 1024 # Small stack size: 4 KB
MSYS_1_BLOCK_COUNT: 64 # Allocate extra MSYS buffers
SPIFLASH: 1 # Enable SPI Flash
SPI_0_MASTER: 1 # Enable SPI port 0 for ST7789 display and SPI Flash
Refer to this MCUBoot Bootloader Configuration
If we're using the MCUBoot Bootloader (like on PineTime), debugging and testing SPI Flash can be somewhat challenging.
Remember that we added the SPI Flash Driver to the Board Support Package?
The Board Support Package is used by both the MCUBoot Bootloader as well as the Application Firmware. Which means that the SPI Flash Driver is loaded when MCUBoot starts.
What happens if the SPI Flash Driver is configured incorrectly?
MCUBoot may crash... Before starting the Application Firmware! (This happened to me)
Hence for debugging and testing SPI Flash, I strongly recommend switching the Bootloader to a simpler one that doesn't require any drivers: the Stub Bootloader.
The Stub Bootloader doesn't do anything... It simply jumps to the Application Firmware.
This will enable us to debug and test SPI Flash with our Application Firmware, before using it with MCUBoot.
Also MCUBoot expects the Application Firmware Image to start with the MCUBoot Image Header. When the GDB debugger flashes the Firmware ELF File into ROM, the Image Header is empty. So MCUBoot won't work start the Application Firmware properly when the debugger is running. Switching MCUBoot to the Stub Bootloader will solve this.
Source code for Stub Bootloader
To switch the PineTime Bootloader from MCUBoot to the Stub Bootloader, edit the Bootloader Target Settings targets/nrf52_boot/target.yml
.
Comment out this line (insert #
at the beginning of the line)...
# target.app: "@mcuboot/boot/mynewt" # Use MCUBoot, which doesn't support debugging
And uncomment this line (remove #
from the beginning of the line)...
target.app: "apps/boot_stub" # Use Stub Bootloader, which supports debugging
Then edit the Bootloader OpenOCD Script scripts/nrf52/flash-boot.ocd
Comment out the program
line below (insert #
at the beginning of the line)...
# For MCUBoot (debugging not supported):
# program bin/targets/nrf52_boot/app/boot/mynewt/mynewt.elf.bin verify 0x00000000
And uncomment the program
line below (remove #
from the beginning of the line)...
# For Stub Bootloader (supports debugging):
program bin/targets/nrf52_boot/app/apps/boot_stub/boot_stub.elf.bin verify 0x00000000
Build the Stub Bootloader by clicking Terminal -> Run Task -> Build Bootloader
. Or run the Bootloader Build Script: scripts/nrf52/build-boot.sh
Flash the Stub Bootloader to PineTime by clicking Terminal -> Run Task -> Flash Bootloader
. Or run the Bootloader Flash Script: scripts/nrf52/flash-boot.sh
Our PineTime now boots with the Stub Bootloader... Ready for debugging!
We have been using Mynewt's SPI Flash Driver: spiflash.h
and spiflash.c
Remember the configuration we have set in syscfg.yml
like SPIFLASH_MANUFACTURER
, SPIFLASH_MEMORY_TYPE
, SPIFLASH_MEMORY_CAPACITY
?
The flash driver uses these configuration settings to access our SPI flash memory. Everything in Mynewt's SPI Flash Driver is configured via syscfg.yml
... Amazing!
Mynewt's SPI Flash Driver seemed very mysterious... Until I stumbled upon this Board Support Package in Mynewt: black_vet6/syscfg.yml
The configuration and code in this article were derived from that Board Support Package.
We have seen Mynewt's Flash HAL functions for reading, writing and erasing SPI Flash at the byte and sector level. That's too low-level for us firmware programmers.
Can we have files and directories in SPI Flash?
Yes we can, with a Flash File System like littlefs.
Earlier we saw the PineTime Flash Memory Map: hw/bsp/nrf52/bsp.yml
bsp.flash_map:
areas:
...
FLASH_AREA_NFFS: # For user files
user_id: 1
device: 1 # External SPI Flash
offset: 0x00074000
size: 3632kB
FLASH_AREA_NFFS
is a Flash Area that's available for use by the Application Firmware for storing application data. Note that it's located on Flash Device 1, which is the External SPI Flash.
Mynewt will let us install a File System into the FLASH_AREA_NFFS
Flash Area for storing our files and directories.
Here's a simple example from littlefs/README.md
that updates a file named boot_count
every time the program runs. The program can be interrupted at any time without losing track of how many times it has been booted and without corrupting the filesystem...
#include "lfs.h"
// variables used by the filesystem
lfs_t lfs;
lfs_file_t file;
// configuration of the filesystem is provided by this struct
const struct lfs_config cfg = {
// block device operations
.read = flash_read, // TODO: Implement with hal_flash_read()
.prog = flash_prog, // TODO: Implement with hal_flash_write()
.erase = flash_erase, // TODO: Implement with hal_flash_erase()
.sync = flash_sync, // TODO: Provide a sync function
// block device configuration
.read_size = 16,
.prog_size = 16,
.block_size = 4096,
.block_count = 128,
.cache_size = 16,
.lookahead_size = 16,
.block_cycles = 500,
};
// entry point
int test_littlefs(void) {
// mount the filesystem
int err = lfs_mount(&lfs, &cfg);
// reformat if we can't mount the filesystem
// this should only happen on the first boot
if (err) {
lfs_format(&lfs, &cfg);
lfs_mount(&lfs, &cfg);
}
// read current count
uint32_t boot_count = 0;
lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT);
lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count));
// update boot count
boot_count += 1;
lfs_file_rewind(&lfs, &file);
lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count));
// remember the storage is not updated until the file is closed successfully
lfs_file_close(&lfs, &file);
// release any resources we were using
lfs_unmount(&lfs);
// print the boot count
console_printf("boot_count: %d\n", boot_count); console_flush();
}
The lfs_config
struct points to functions that will be used by littlefs for reading, writing and erasing the flash device: flash_read
, flash_prog
, flash_erase
.
These functions may be implemented with similar functions defined in Mynewt's Flash HAL: hal_flash_read
, hal_flash_write
, hal_flash_erase
.
lfs_config
is documented here: lfs.h
Note that Mynewt's Flash HAL uses absolute addresses (instead of addresses relative to the Flash Area) when accessing SPI Flash. We need to map Flash Area addresses to absolute addresses using Mynewt's Flash Memory Map functions: flash_map.h
Got a question, comment or suggestion? Create an Issue or submit a Pull Request here...