Configure Mynewt for SPI Flash on PineTime Smart Watch (nRF52)

Configure Mynewt for SPI Flash on PineTime Smart Watch (nRF52)

Larger image here

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)...

  1. Configure pins for SPI Port in syscfg.yml

  2. Configure flash interface in syscfg.yml

  3. Configure flash timings insyscfg.yml

  4. Include spiflash.h in hal_bsp.c

  5. Define internal and external flash devices in hal_bsp.c

  6. Access flash devices by ID in hal_bsp.c

  7. Add spiflash driver to pkg.yml

Read on for the details...

syscfg.yml: Configuration for Board Support Package

syscfg.yml: Configuration for Board Support Package

1 syscfg.yml: Configure Pins for SPI Port

The first configuration file we'll edit is syscfg.yml from Mynewt's Board Support Package. For PineTime, this file is located at...

hw/bsp/nrf52/syscfg.yml

Let's add the PineTime SPI settings according to the PineTime Wiki: PineTime Port Assignment...

nRF52 Pin    FunctionDescription
P0.02SPI-SCK, LCD_SCKSPI Clock
P0.03SPI-MOSI, LCD_SDI    SPI MOSI (master to slave)
P0.04SPI-MISOSPI MISO (slave to master)
P0.05SPI-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.

2 syscfg.yml: Configure flash interface

Now 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

3 syscfg.yml: Configure Flash Timings

Finally 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: Code for Board Support Package

4 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...

hw/bsp/nrf52/src/hal_bsp.c

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)

5 hal_bsp.c: Define Internal and External Flash Devices

Next 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.

6 hal_bsp.c: Access Flash Devices by ID

Finally 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: Drivers for Board Support Package

7 pkg.yml: Add spiflash driver

The last file we'll edit is pkg.yml from the Board Support Package. For PineTime this file is located at...

hw/bsp/nrf52/pkg.yml

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.

8 Test 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...

  1. Erase Flash hal_flash_erase(id, offset, size)

    Erase internal / external flash memory at the offset address, for size bytes. See hal_flash_erase

  2. 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

  3. 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...

8.1 Read SPI Flash

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 

8.2 Erase SPI Flash

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.

8.3 Write SPI Flash

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!

8.4 SPI Flash Sector Map

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).

9 SPI Flash Benchmark

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
32103,7518,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
1135,98122,069
2130,96521,024
4135,98118,842
8130,43115,819
16120,58711,977
24110,9569,625
32103,7518,053
4891,0416,066
6481,1054,865
9666,5743,485
12856,4582,715
19243,3001,883
25635,1161,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!

10 MCUBoot Bootloader with SPI Flash

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

11 Debug SPI Flash with MCUBoot Bootloader

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

12 Switch MCUBoot to 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)...

And uncomment the program line below (remove # from the beginning of the line)...

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!

13 Inside the SPI Flash Driver

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.

14 SPI Flash File System

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

More about littlefs

Design of littlefs

Specification of littlefs

15 Further Reading

Check out the other PineTime articles