Mynewt GPIO ported to PineCone BL602 RISC-V Board

đź“ť 15 Jan 2021

A month ago we started porting Apache Mynewt, a modern embedded operating system, to PineCone BL602…

Then last week we learnt about the Hardware Abstraction Layer provided by the BL602 IoT SDK for controlling GPIO…

Today we shall…

  1. Embed BL602’s Hardware Abstraction Layer inside Mynewt

  2. Map Mynewt’s GPIO Functions to BL602’s Hardware Abstraction Layer

  3. Build Mynewt BL602 automatically with GitHub Actions

If you’re new to PineCone BL602, check out my article…

PineCone BL602 RISC-V Evaluation Board with LED controlled by Apache Mynewt

PineCone BL602 RISC-V Evaluation Board with LED controlled by Apache Mynewt

§1 Our Mynewt GPIO Program

Here’s the Mynewt Program that actually runs on our PineCone BL602 Board and switches on the Blue LED: main.c

#include <sysinit/sysinit.h>  //  Init Functions
#include <os/os.h>            //  Mynewt Functions
#include <bsp/bsp.h>          //  Board Support Package
#include <hal/hal_gpio.h>     //  Mynewt HAL for GPIO

int main(int argc, char **argv) {
    //  Initialise Mynewt drivers
    sysinit();

    //  Set the LED GPIOs to output mode. 
    //  Switch off the LEDs (1 = Off)
    hal_gpio_init_out(LED_BLUE_PIN,  1);
    hal_gpio_init_out(LED_GREEN_PIN, 1);
    hal_gpio_init_out(LED_RED_PIN,   1);

    //  Switch on Blue LED (0 = On)
    hal_gpio_write(LED_BLUE_PIN,  0);

    //  Loop forever
    for(;;) {}
}

We’re looking at the beauty of Mynewt… Minimal fuss, easy to read, perfect for learning embedded programming!

Mynewt Programs are Portable too… hal_gpio_init_out and hal_gpio_write will work on many microcontrollers: STM32 Blue Pill (Arm), Nordic Semi nRF52 (Arm too), SiFive HiFive1 (RISC-V)… And now PineCone BL602 (RISC-V yay!)

The GPIO Pin Numbers will differ. But on PineCone, this Mynewt Program lights up the Blue LED, exactly like the pic above.

The LED GPIOs are defined in bsp/bsp.h…

//  Define the LED GPIOs: 11 (Blue), 14 (Green), 17 (Red)
#define LED_BLUE_PIN  11
#define LED_GREEN_PIN 14
#define LED_RED_PIN   17

Let’s find out how we made this work.

§2 But Why Mynewt?

We have many options for learning Embedded Programming on PineCone BL602… Why Mynewt?

BL602’s native IoT SDK looks easy for coding BL602 in C… Supports Multitasking through FreeRTOS (like reading sensor data and sending to network concurrently)

What if we wrap up the BL602 IoT SDK with Arduino Libraries?

Like Mbed OS? The newer Arduino SAMD boards support mbed OS

Zephyr? It’s well supported by Linux Foundation and many microcontroller manufacturers

Alrighty Nitpicky… We’re left with Mynewt. But it’s not as popular as Zephyr

What about Embedded Rust? We have many fans.

So we’ll have Rust on Mynewt?

UPDATE: Check out Apache NuttX operating system for BL602

§3 Mynewt and BL602 Layers

How does Mynewt create programs that are portable to other microcontrollers?

By using layers of code that isolate the differences between microcontrollers (like BL602) and boards (like PineCone).

(Dressing in layers is a very good thing… Especially in Winter!)

Here’s how we layer the code in Mynewt…

Mynewt and BL602 IoT SDK Layers

  1. Main Function: We’ve seen the Main Function at the top of the article… It lights up the Blue LED.

    Assuming that the GPIO Pin Number is defined correctly, the same Main Function will light up the LED on any microcontroller.

    That’s why this is portable, Hardware Independent code. A key feature of modern embedded operating systems.

  2. Board Support Package: This layer contains code that’s specific to the Hardware Board, like PineCone.

    The Blue LED is connected at GPIO 11 on PineCone… And these details will vary depending on the BL602 Board that we use (say PineCone vs Pinenut).

    So it makes sense to capture such Board Specific details inside the Board Support Package layer.

  3. Microcontroller Package: This layer is specific to the Microcontroller (like BL602). The Microcontroller Package is reused by all Boards that are based on the same Microcontroller (like PineCone and Pinenut).

  4. Hardware Abstraction Layer: This layer is lifted directly from the BL602 IoT SDK (with minimal changes).

    Here we find the functions that control the BL602 hardware: GPIO, PWM, UART, I2C, SPI, …

  5. Standard Driver: This layer is unique to BL602. The Hardware Abstraction Layer in BL602 calls this layer to access the Hardware Registers to perform hardware functions.

What about the rest of the BL602 IoT SDK?

We have integrated the smallest subset of functions from the BL602 SDK that are needed for Mynewt. The rest are not needed (yet).

In particular, we don’t compile under Mynewt the FreeRTOS driver code from the BL602 SDK. Because running two operating systems side by side would be a disaster!

§4 Calling the Mynewt and BL602 Layers

To better understand the Mynewt and BL602 Layers, let’s walk through the chain of function calls for a GPIO operation…

Mynewt and BL602 IoT SDK Layers

  1. In the Hardware-Agnostic Main Function, we set the LED GPIO to output mode like so: main.c

    int main(int argc, char **argv) {
        //  Set the Blue LED GPIO to output mode
        hal_gpio_init_out(LED_BLUE_PIN,  1);
    

    The Mynewt GPIO Function hal_gpio_init_out works on any microcontroller.

  2. LED_BLUE_PIN is the GPIO Pin Number for the Blue LED.

    The LED GPIO Pin Number will differ across Boards, so LED_BLUE_PIN is defined in the Board Support Package for PineCone: bsp/bsp.h

    //  Define the Blue LED GPIO
    #define LED_BLUE_PIN  11
    
  3. hal_gpio_init_out is a standard Mynewt GPIO Function that works on any microcontroller. But its implementation is specific to the microcontroller.

    Here’s the implementation of hal_gpio_init_out in our Microcontroller Package for BL602: bl602/hal_gpio.c

    int hal_gpio_init_out(int pin, int val) {
        int rc = bl_gpio_enable_output(pin, 0, 0);
    
  4. The above implementation calls bl_gpio_enable_output, which is defined in the Hardware Abstraction Layer from BL602 IoT SDK: bl602_hal/bl_gpio.c

    int bl_gpio_enable_output(uint8_t pin, uint8_t pullup, uint8_t pulldown) {
        ...
        GLB_GPIO_Init(&cfg);
    
  5. And finally we call GLB_GPIO_Init, which is defined in the Standard Driver from BL602 IoT SDK: StdDriver/bl602_glb.c

    GLB refers to BL602’s Global Register.

    GLB_GPIO_Init manipulates the GLB Hardware Register to control the GPIO Hardware and switch GPIO 11 to output mode.

§5 Compile BL602 SDK under Mynewt

How do we specify which folders of BL602 IoT SDK to compile under Mynewt?

Remember that we’re compiling the smallest subset of functions from the BL602 IoT SDK that are needed for Mynewt…

  1. BL602 Hardware Abstraction Layer: hal_drv/bl602_hal

  2. BL602 Standard Driver: bl602/bl602_std/ bl602_std/StdDriver

We specify these folders in Mynewt’s Microcontroller Package for BL602: hw/mcu/bl/ bl602/pkg.yml

pkg.src_dirs:
    - src
    # Select the BL602 IoT SDK folders to be included for the build
    - ext/bl_iot_sdk/components/hal_drv/bl602_hal
    - ext/bl_iot_sdk/components/bl602/bl602_std/bl602_std/StdDriver/Src

The Microcontroller Package for BL602 also specifies the Include Folders and the GCC Compiler Options: pkg.yml

pkg.cflags: 
    - -march=rv32imac 
    - -mabi=ilp32
    # BL602 IoT SDK definitions
    - -DCONF_USER_ENABLE_PSRAM 
    - -DconfigUSE_TICKLESS_IDLE=0 
    - -DFEATURE_WIFI_DISABLE=1 
    - -DCFG_FREERTOS 
    - -DARCH_RISCV 
    - -DBL602 
    ...
    # Where the BL602 IoT SDK include files are located
    - -Ihw/mcu/bl/bl602/ext/bl_iot_sdk/components/bl602/bl602_std/bl602_std/Common/partition
    - -Ihw/mcu/bl/bl602/ext/bl_iot_sdk/components/bl602/bl602_std/bl602_std/Common/sim_print
    - -Ihw/mcu/bl/bl602/ext/bl_iot_sdk/components/bl602/bl602_std/bl602_std/Common/soft_crc
    ...

The above options were obtained by running make in Trace Mode when building the BL602 IoT SDK…

make --trace

To preserve the integrity of the BL602 IoT SDK, the entire SDK is mounted under the Mynewt Project as a Git Submodule at…

§6 FreeRTOS References in BL602 SDK

Does the BL602 IoT SDK depend on any External Library?

Unfortunately yes… The BL602 IoT SDK (Hardware Abstraction Layer) depends on FreeRTOS. And this complicates the porting to Mynewt.

Let’s look at this code from the BL602 Security (Crypto) HAL: bl602_hal/bl_sec.c

int bl_sec_init(void) {
    g_bl_sec_sha_mutex = xSemaphoreCreateMutexStatic(&sha_mutex_buf);

This code calls FreeRTOS to create a Mutex (Mutually Exclusive Lock) to prevent tasks from accessing some shared data concurrently.

Mynewt fails to compile this because xSemaphoreCreateMutexStatic isn’t defined. To work around this, we gave Mynewt a Mock Declaration for the undefined function: semphr.h

It compiles OK under Mynewt for now. But eventually we need to implement xSemaphoreCreateMutexStatic with a Mynewt Semaphore.

Here are the other Mock Declarations for FreeRTOS on Mynewt…

Should the BL602 Hardware Abstraction Layer call FreeRTOS?

This is highly unusual… The Hardware Abstraction Layer (HAL) is Low-Level Code that’s meant to called by various Operating Systems. So we don’t expect BL602 HAL to call FreeRTOS directly.

(STM32 Blue Pill HAL and nRF52 HAL don’t call any Operating Systems either)

This unusual structure seems similar to ESP32, where FreeRTOS is embedded into the ESP32 HAL.

We can still go ahead and port Mynewt (and other Operating Systems) to BL602. Just that we need to emulate the FreeRTOS functions in Mynewt (and other Operating Systems).

§7 Fix BL602 SDK for Mynewt

Mynewt is strict and uptight when compiling C code with GCC… Any warnings emitted by GCC will fail the Mynewt build.

We made the following fixes to the BL602 IoT SDK to resolve the warnings…

§7.1 Mismatched Types

Here we’re passing adc_pin as a number: components/hal_drv/ bl602_hal/bl_adc.c

uint8_t adc_pin = gpio_num;
GLB_GPIO_Func_Init(GPIO_FUN_ANALOG, &adc_pin, 1);
//  Fails because GCC expects adc_pin to be an enum, not a number

Which displeases the GCC Compiler because the function GLB_GPIO_Func_Init expects an enum GLB_GPIO_Type, not a number.

The fix is simple…

//  Declare as enum instead of number
GLB_GPIO_Type adc_pin = gpio_num;

§7.2 Buffer Overflow

This potential Buffer Overflow seems scary: components/hal_drv/ bl602_hal/hal_button.c

int i = ...;
char gpio_node[10] = "gpio";
sprintf(gpio_node, "gpio%d", i);
//  Fails because gpio_node may overflow

GCC thinks that i may exceed 5 digits (because it’s a 32-bit integer), causing gpio_node to overflow.

For our safety (and to placate GCC), we switch sprintf to snprintf, which limits the output size…

//  Limit formatting to size of gpio_node
snprintf(gpio_node, sizeof(gpio_node), "gpio%d", i);

§7.3 External Pointer Reference

Here we use a pointer that’s defined in a GCC Linker Script: components/hal_drv/ bl602_hal/hal_sys.c

extern uint8_t __global_pointer_head$;
memset(&__global_pointer_head$, 0, 0x498);
//  Fails because the pointer references a single byte, not 0x498 bytes

GCC thinks that the pointer references a single byte… Copying 0x498 bytes to the pointer would surely cause an overflow!

Thus we do the right thing and tell GCC that it’s really a pointer to an array of 0x498 bytes…

//  Pointer to an array of 0x498 bytes
extern uint8_t __global_pointer_head$[0x498];

§7.4 Pull Request and Pending Analysis

The above fixes (plus a few minor ones) have been submitted upstream as a Pull Request…

4 fixes have not been pushed upstream yet, because they need more Impact Analysis…

  1. Variable set but not used

  2. Mismatched format strings

  3. Misplaced main function

  4. Missing include

§8 Automated Build with GitHub Actions

When porting Mynewt to BL602, it’s good to make sure that we don’t break any existing code by accident. (Especially the BL602 IoT SDK, which we have tweaked slightly for Mynewt)

That’s why we use GitHub Actions to compile automatically the Mynewt code (plus the core parts of BL602 IoT SDK) whenever we commit any changes.

How long does GitHub take to compile our Mynewt + BL602 SDK Code?

TWO MINUTES. Thus if we ever commit some bad code (that can’t be compiled) into the repo, GitHub will alert us in TWO MINUTES (via email) that something has gone terribly wrong in our repo.

We’ll see the results of the Automated Build here (please log in to GitHub first)…

To complete the build in TWO MINUTES, we use some caching magic inside our GitHub Actions Workflow.

Let’s learn how it works: .github/workflows/main.yml

§8.1 Trigger Conditions

At the top of the GitHub Actions Workflow, we state the conditions that will trigger the Automated Build…

## Name of this Workflow
name: Build Firmware

## When to run this Workflow...
on:
  ## Run this Workflow when files are updated (Pushed) in this Branch
  push:
    branches: [ main ]    

This says that the Automated Build will be triggered whenever we commit code to the main branch.

§8.2 Build Environment

We’ll use an Ubuntu x64 virtual machine (hosted at GitHub) to compile our code…

## Steps to run for the Workflow
jobs:
  build:
    ## Run these steps on Ubuntu
    runs-on: ubuntu-latest

§8.3 Checkout Source Files

Here begins the steps for our Mynewt + BL602 SDK Automated Build with GitHub Actions.

First we check out the source files from the repo recursively, including the following submodules…

  1. BL602 IoT SDK at hw/mcu/bl/bl602/ext

  2. Mynewt Core, NimBLE, MCU Manager and MCUBoot at repos

    steps:        
    - name: Checkout source files
      uses: actions/checkout@v2
      with:
        submodules: 'recursive'

§8.4 Check Cache for newt

Mynewt doesn’t use make to build… It uses its own build tool named newt.

Developed in Go, newt runs on Linux, macOS and Windows CMD.

We fetch the newt executable from our GitHub Actions Cache, if it exists…

    - name: Check cache for newt
      id:   cache-newt
      uses: actions/cache@v2
      env:
        cache-name: cache-newt
      with:
        path: ${{ runner.temp }}/mynewt-newt
        key:  ${{ runner.os }}-build-${{ env.cache-name }}
        restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}

(If newt isn’t found in our cache, we build newt and cache it in the next step)

Each cache has a name, ours is cache-newt…

      env:
        cache-name: cache-newt

The Cache Action actions/cache requires 3 parameters: path, key and restore-keys…

      with:
        path: ${{ runner.temp }}/mynewt-newt
        key:  ${{ runner.os }}-build-${{ env.cache-name }}
        restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}

Given that our GitHub Actions Environment is defined as…

runner.temp    = /home/runner/work/_temp
runner.os      = Linux
env.cache-name = cache-newt

Our parameters will get expanded to…

path:         /home/runner/work/_temp/mynewt-newt
key:          Linux-build-cache-newt
restore-keys: Linux-build-cache-newt

Thus the Cache Action will cache and restore the newt folder at this temporary folder…

path: /home/runner/work/_temp/mynewt-newt

And to avoid confusion with other caches in the same workflow, we give it a unique key…

key: Linux-build-cache-newt

§8.5 Download and Build newt

Here’s how we download and build newt if it doesn’t exist in our cache…

    - name: Install newt
      ## Install newt if not found in cache
      if:   steps.cache-newt.outputs.cache-hit != 'true'  
      run:  |
        source scripts/install-version.sh
        cd ${{ runner.temp }}
        git clone --branch $mynewt_version https://github.com/apache/mynewt-newt/
        cd mynewt-newt/
        ./build.sh
        newt/newt version
        export PATH=$PATH:${{ runner.temp }}/mynewt-newt/newt
        newt version

(We set mynewt_version to mynewt_1_8_0_tag in scripts/install-version.sh)

Note the condition: We execute this step only when newt doesn’t exist in our cache…

      ## Install newt if not found in cache
      if:   steps.cache-newt.outputs.cache-hit != 'true'

(steps.cache-newt refers to the cache checking from the previous step)

After building newt, the Cache Action actions/cache (from the previous step) caches our newt folder…

path:  /home/runner/work/_temp/mynewt-newt

And restores the newt folder whenever we run the Automated Build.

This caching enables us to complete the Automated Build in two minutes. We’ll use caching again for the GCC Compiler.

§8.6 Show Files

For troubleshooting our Automated Build, we dump the GitHub Actions Environmment Variables and the File System like so…

    - name: Show files
      run:  set ; pwd ; ls -l

And here’s the output…

Mynewt Automated Build completed in 2 minutes

Mynewt Automated Build completed in 2 minutes

§8.7 Check Cache for GCC Compiler

Remember how we cached the newt tool to cut down on the build time? We’ll do the same for our GCC Compiler…

    - name: Check cache for xPack RISC-V Toolchain xpack-riscv-none-embed-gcc
      id:   cache-toolchain
      uses: actions/cache@v2
      env:
        cache-name: cache-toolchain
      with:
        path: xpack-riscv-none-embed-gcc
        key:  ${{ runner.os }}-build-${{ env.cache-name }}
        restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}

First we try to load the GCC Compiler from the cache with these settings…

path:         xpack-riscv-none-embed-gcc
key:          Linux-build-cache-toolchain
restore-keys: Linux-build-cache-toolchain

If the GCC Compiler exists in our cache, the Cache Action will restore the GCC folder xpack-riscv-none-embed-gcc into the current directory (which is the root of our repo).

§8.8 Download GCC Compiler

If the GCC Compiler doesn’t exist in our cache, we download the xPack RISC-V Toolchain: xpack-riscv-none-embed-gcc

    - name: Install xPack RISC-V Toolchain xpack-riscv-none-embed-gcc
      ## Install toolchain if not found in cache
      if:   steps.cache-toolchain.outputs.cache-hit != 'true'  
      run:  |
        wget -qO- https://github.com/xpack-dev-tools/riscv-none-embed-gcc-xpack/releases/download/v8.3.0-2.3/xpack-riscv-none-embed-gcc-8.3.0-2.3-linux-x64.tar.gz | tar -xz
        mv xpack-riscv-none-embed-gcc-* xpack-riscv-none-embed-gcc

(Remember: We check steps.cache-toolchain and skip this step if the GCC Compiler is already in our cache)

After downloading the GCC Compiler, the Cache Action actions/cache (from the previous step) caches our GCC folder…

path:  xpack-riscv-none-embed-gcc

Caching the GCC Compiler is essential for reducing the Automated Build time to 2 minutes… Because each download of the xPack RISC-V Toolchain takes a whopping 400 MB! (Zipped!)

§8.9 Build Mynewt Firmware

Now that we have the build tools ready (newt and GCC Compiler), let’s build Mynewt with GitHub Actions…

    - name: Build Application Firmware
      run:  |
        export PATH=$PATH:${{ runner.temp }}/mynewt-newt/newt
        ./scripts/build-app.sh

This sets the path of newt and calls the build script: scripts/build-app.sh

##  Add GCC to the PATH
export PATH="$PWD/xpack-riscv-none-embed-gcc/bin:$PATH"

##  Build the Mynewt Firmware
newt build pinecone_app

##  Display the firmware size
newt size -v pinecone_app

We can see the results of the Automated Build here (please log in to GitHub first)…

Here’s a peek at the Automated Build log at GitHub Actions…

Linking /home/runner/work/pinecone-rust-mynewt/pinecone-rust-mynewt/bin/targets/pinecone_app/app/apps/blinky/blinky.elf
Target successfully built: targets/pinecone_app
+ newt size -v pinecone_app
Size of Application Image: app
Mem flash: 0x22008000-0x22014000
Mem ram: 0x22014000-0x22020000

Note that the “Flash Memory” actually points to the Cache Memory at 0x2200 8000.

This means that our firmware is meant to be tested and debugged with OpenOCD and GDB (or VSCode). (Instead of being flashed to Flash Memory)

  flash     ram 
      6     529 *fill*
    172       0 @apache-mynewt-core_hw_hal.a
   4442    8213 @apache-mynewt-core_kernel_os.a
     80       0 @apache-mynewt-core_libc_baselibc.a
    702     128 @apache-mynewt-core_sys_flash_map.a
      2       0 @apache-mynewt-core_sys_log_modlog.a
    782      29 @apache-mynewt-core_sys_mfg.a
     30       5 @apache-mynewt-core_sys_sysinit.a
     72       0 @apache-mynewt-core_util_mem.a
     36       0 apps_blinky.a
     44      12 hw_bsp_pinecone.a
   3486     228 hw_mcu_bl_bl602.a
     92       0 pinecone_app-sysinit-app.a
    292    1064 libg.a

Here are the components of our firmware. The BL602 IoT SDK (GPIO HAL and Standard Driver) occupies 3.4 KB of code and read-only data in hw_mcu_bl_bl602.a.

The Mynewt Kernel apache-mynewt-core_kernel_os.a occupies 4.3 KB of code and read-only data, plus another 8 KB of read-write data in RAM. (Mostly for the Kernel Stack)

Loading compiler /home/runner/work/pinecone-rust-mynewt/pinecone-rust-mynewt/compiler/riscv-none-embed, buildProfile debug
objsize
   text	   data	    bss	    dec	    hex	filename
  11318	     28	   9100	  20446	   4fde	/home/runner/work/pinecone-rust-mynewt/pinecone-rust-mynewt/bin/targets/pinecone_app/app/apps/blinky/blinky.elf

The build creates a Firmware ELF File blinky.elf that contains 11 KB of code and read-only data.

§8.10 Upload Mynewt Firmware

To save the firmware files generated by the Automated Build (and allow anyone to download), we call actions/upload-artifact…

    - name: Upload Application Firmware
      uses: actions/upload-artifact@v2
      with:
        name: blinky.elf
        path: bin/targets/pinecone_app/app/apps/blinky/blinky.elf

Here we save the Mynewt Firmware ELF File blinky.elf as an Artifact, to make it accessible for everyone to download. Download here

    - name: Upload Application Firmware Outputs
      uses: actions/upload-artifact@v2
      with:
        name: blinky.zip
        path: bin/targets/pinecone_app/app/apps/blinky/blinky.*

Next we upload another Artifact named blinky.zip that contains some useful files from the Mynewt Automated Build…

  1. blinky.elf.bin: Mynewt Firmware Binary.

    Contains only the firmware code and read-only data, without the debugging symbols.

  2. blinky.elf.map: GCC Linker Map for our Mynewt Firmware.

    Shows the addresses of every function and global variable in our firmware.

  3. blinky.elf.lst: RISC-V Assembly Code for our Mynewt Firmware.

    Disassembled from our firmware build.

§8.11 Show Output

It’s good to keep a record of the files created from the build: pathname, timestamp, file size, …

Here’s how we capture the build output info with GitHub Actions…

    - name: Find output
      run:  |
        find bin/targets/pinecone_app/app/apps/blinky -name "blinky.*" -ls

And here’s the captured info…

5439531     12 -rwxr-xr-x   1 runner   docker      11348 Jan 10 02:26 bin/targets/pinecone_app/app/apps/blinky/blinky.elf.bin
5439528    348 -rw-r--r--   1 runner   docker     354281 Jan 10 02:26 bin/targets/pinecone_app/app/apps/blinky/blinky.elf.map
5439529    392 -rwxr-xr-x   1 runner   docker     401104 Jan 10 02:26 bin/targets/pinecone_app/app/apps/blinky/blinky.elf
5439530      4 -rw-r--r--   1 runner   docker       3309 Jan 10 02:26 bin/targets/pinecone_app/app/apps/blinky/blinky.elf.cmd
5439532    300 -rw-r--r--   1 runner   docker     303790 Jan 10 02:26 bin/targets/pinecone_app/app/apps/blinky/blinky.elf.lst

§8.12 Caching Considerations

Remember how we cached newt and the GCC Compiler with GitHub Actions?

Note that the cache will be populated only when the build succeeds.

When creating a new GitHub Actions Workflow, we should first comment out the build steps (to let the build succeed), let the caching happen, then uncomment the build steps.

With caching in effect, we save a lot of time testing and troubleshooting our Automated Build.

§9 Run Mynewt on PineCone

To test and debug the updated Mynewt on PineCone with our Linux / macOS / Windows computer…

  1. Download and install newt, GCC, OpenOCD, VSCode and pinecone-rust-mynewt…

    “Build the Firmware”

  2. Build the Mynewt firmware on our computer using the instructions above.

    Alternatively, download blinky.elf from here and copy it to…

    pinecone-rust-mynewt/bin/targets/pinecone_app/app/apps/blinky
    
  3. Connect PineCone to our computer with a JTAG Debugger and start the VSCode Debugger…

    “Debug Firmware with VSCode”

In the GDB Debug Console we’ll see this…

Breakpoint 1 at 0x220092ba: file apps/blinky/src/main.c, line 30.
Breakpoint 2 at 0x22008242: file repos/apache-mynewt-core/kernel/os/src/arch/rv32imac/os_fault.c, line 30.
Remote debugging using | xpack-openocd/bin/openocd -c "gdb_port pipe; log_output openocd.log" -f openocd.cfg
Running executable
xPack OpenOCD, x86_64 Open On-Chip Debugger 0.10.0+dev-00378-ge5be992df (2020-06-26-12:31)

This says that we have set two breakpoints. And that OpenOCD has been started, talking to PineCone.

0x21000000 in ?? ()
Not implemented stop reason (assuming exception): undefined

OpenOCD has taken control of BL602 and has halted the execution at address 0x2100 0000

Which is really interesting because 0x2100 0000 is the address of BL602’s Boot ROM. Thus we have evidence that BL602 starts running the Boot ROM code at 0x2100 0000 whenever it reboots.

Loading section .init, size 0xa2 lma 0x22008000
Loading section .text, size 0x1c1c lma 0x220080a4
Loading section .tcm_code, size 0xaa0 lma 0x22009cc0
Loading section .rodata, size 0x94 lma 0x2200a760
Loading section .sdata2.HFXOSC_PLL_256_MHZ, size 0x8 lma 0x2200a7f4
Loading section .sdata2._global_impure_ptr, size 0x4 lma 0x2200a7fc
Loading section .data, size 0x438 lma 0x2200a800
Loading section .sdata, size 0x1c lma 0x2200ac38
Start address 0x22008000, load size 11346
Transfer rate: 2 KB/sec, 1418 bytes/write.

Our GDB Script (see this) has loaded our firmware to BL602 at address 0x2200 8000, which is in Cache Memory.

(BL602 doesn’t support flashing firmware to Flash Memory with OpenOCD. More details)

Breakpoint 1, main (argc=0, argv=0x0) at apps/blinky/src/main.c:30
30	int main(int argc, char **argv) {

When the debugger code hits the first breakpoint (in the main function), click the Continue button in the Debug Toolbar. (Or press F5)

§9.1 JTAG Foiled By GPIO

Calamity strikes as our Mynewt GPIO Firmware runs.

Yes the Blue LED lights up, but we see this error in GDB…

Debugger is not authenticated to target Debug Module. (dmstatus=0x0).
Use `riscv authdata_read` and `riscv authdata_write` commands to authenticate.

That’s because PineCone’s RGB LED is connected to the same pins as the JTAG port!

PineCone PinLED PinJTAG Pin
GPIO 11BlueTDO
GPIO 14GreenTCK
GPIO 17RedTDI

(See “If you love the LED… Set it free!”)

When we flip the LEDs on and off, the JTAG Debugger connection drops.

But our firmware is indeed flipping the LEDs correctly, so we’re done with GPIO testing on Mynewt!

Sensors and actuators to be tested with PineCone BL602

Sensors and actuators to be tested with PineCone BL602

§10 What’s Next

We’ll be testing Mynewt on PineCone BL602 with an interesting mix of Sensors and Actuators shown above. Here’s the list

Most of the Sensors and Actuators will work fine with GPIO on Mynewt. But some require additional support from Mynewt: Pulse Width Modulation and Analog to Digital Converter.

We shall integrate with Mynewt the respective Hardware Abstraction Layers from the BL602 IoT SDK.

What about DHT11, the Temperature and Humidity Sensor? (Lower right corner)

DHT11 will be a problem because it transmits Serial Data in an unusual way (bidirectional single wire) that’s incompatible with UART, I2C and SPI. See DHT11 Datasheet

We would have to read the data from DHT11 via Bit Banging. Which is hard to do reliably on a multitasking operating system like Mynewt. More about the evils of Bit Banging

Is there a better Temperature and Humidity Sensor that connects with standard interfaces… And works well with mutitasking?

Yes BME280 senses Temperature, Humidity and Altitude (Air Pressure). BME280 supports both I2C and SPI interfaces. More about BME280

We shall we using BME280 for testing the implementation of I2C and SPI on Mynewt BL602.

How do we find Software Drivers for BME280 and other Sensors and Actuators?

This will be a problem for all operating systems running on BL602: Getting the Sensor / Actuator Drivers for Mynewt, FreeRTOS, Zephyr, RIOT, …

Each operating system has its own Hardware Abstraction Layer (HAL) for talking to hardware interfaces (like I2C, SPI and Timers). Thus we’ll never find drivers that will work across all operaing system.

What if we could standardise the HAL across all operating systems?

There’s one possibility: Adopt the Rust Embedded HAL. I have previously ported the Rust Embedded HAL to Mynewt (for PineTime). More details

Once we have the Rust Embedded HAL ported to Mynewt (and other operating systems), we’ll be able to use the Rust Embedded Drivers. List of Rust Embedded Drivers

(And we’ll code our firmware in Rust of course)

We shall experiment with the BME280 Rust Driver on Mynewt BL602.

Anything else we’ll be supporting in Mynewt BL602?

Yes we shall be adding support for UART on Mynewt BL602, so that we can view debugging messages.

And Timers so that we can poll for GPIO Inputs.

Stay Tuned!

Got a question, comment or suggestion? Create an Issue or submit a Pull Request here…

lupyuen.github.io/src/gpio.md

§11 Notes

  1. My thoughts on Teaching IoT and Embedded Programming with Sensors and Actuators?

    Let’s move away from boring old GPIO-based Sensors and Actuators… And teach modern ones based on I2C and SPI!

    (Like BME280)

  2. To teach IoT with PineCone or Pinenut BL602, we need to add on…

  3. Will newbies find it difficult to code firmware for an SPI Display?

    Not if we adopt a Common Graphics Library like LVGL that will work on all BL602 operating systems. More about LVGL

    On PineTime we have proven that LVGL (and the ST7789 driver) works perfectly fine on FreeRTOS, Mynewt, RIOT, Zephyr, …

    We have even created Web Simulators for PineTime based on LVGL + WebAssembly. See this

    LVGL is a great way for newbies to create Graphical Embedded Apps in C and Rust, our PineTime experience has shown. LVGL on Rust

    (Especially when BL602 has more RAM and Flash Memory than PineTime)

  4. There is a GPIO feature that hasn’t been implemented on Mynewt BL602: Trigger an interrupt when a GPIO Input changes. See this

    This requires CLNT Interrupts to be supported on Mynewt BL602.

§12 Appendix: Inventory of Sensors and Actuators

Sensors and actuators to be tested with PineCone BL602

Sensors and actuators to be tested with PineCone BL602

§12.1 Sensors

SensorOutputs
Linear HallAnalog, Digital
Shock SwitchDigital
Knock SwitchDigital
Mini Reed SwitchDigital
Analog TemperatureAnalog
Analog & Digital
Temperature
Analog, Digital
Button SwitchDigital
Tilt SwitchDigital
PhotoresistorAnalog
Digital Temperature
& Humidity (DHT11)
Digital (Serial)
High Sensitivity AudioAnalog, Digital
Metal TouchAnalog, Digital
FlameAnalog, Digital

§12.2 Actuators

ActuatorInput
Laser TransmitterDigital
Active BuzzerDigital
Passive BuzzerDigital
RelayDigital

§12.3 Sensor + Actuator

Sensor + ActuatorInput / Output
Infrared Transmitter & ReceiverDigital

§13 Appendix: GitHub Actions Environment

Here’s how we dump the GitHub Actions Environmment Variables and the File System…

    - name: Show files
      run:  set ; pwd ; ls -l

And here’s the output…

§13.1 Environment Variables

AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache
ANDROID_HOME=/usr/local/lib/android/sdk
ANDROID_SDK_ROOT=/usr/local/lib/android/sdk
ANT_HOME=/usr/share/ant
AZURE_EXTENSION_DIR=/opt/az/azcliextensions
BASH=/usr/bin/bash
BASHOPTS=checkwinsize:cmdhist:complete_fullquote:extquote:force_fignore:globasciiranges:hostcomplete:interactive_comments:progcomp:promptvars:sourcepath
BASH_ALIASES=()
BASH_ARGC=()
BASH_ARGV=()
BASH_CMDS=()
BASH_LINENO=([0]="0")
BASH_SOURCE=([0]="/home/runner/work/_temp/e317ee99-ed5d-4e4d-bf6d-765dc458355d.sh")
BASH_VERSINFO=([0]="5" [1]="0" [2]="17" [3]="1" [4]="release" [5]="x86_64-pc-linux-gnu")
BASH_VERSION='5.0.17(1)-release'
CHROMEWEBDRIVER=/usr/local/share/chrome_driver
CHROME_BIN=/usr/bin/google-chrome
CI=true
CONDA=/usr/share/miniconda
DEBIAN_FRONTEND=noninteractive
DEPLOYMENT_BASEPATH=/opt/runner
DIRSTACK=()
DOTNET_MULTILEVEL_LOOKUP='"0"'
DOTNET_NOLOGO='"1"'
DOTNET_SKIP_FIRST_TIME_EXPERIENCE='"1"'
EUID=1001
GECKOWEBDRIVER=/usr/local/share/gecko_driver
GITHUB_ACTION=run2
GITHUB_ACTIONS=true
GITHUB_ACTION_REF=
GITHUB_ACTION_REPOSITORY=
GITHUB_ACTOR=lupyuen
GITHUB_API_URL=https://api.github.com
GITHUB_BASE_REF=
GITHUB_ENV=/home/runner/work/_temp/_runner_file_commands/set_env_64d8b762-fb69-4a06-8cda-f76ea66e06b5
GITHUB_EVENT_NAME=push
GITHUB_EVENT_PATH=/home/runner/work/_temp/_github_workflow/event.json
GITHUB_GRAPHQL_URL=https://api.github.com/graphql
GITHUB_HEAD_REF=
GITHUB_JOB=build
GITHUB_PATH=/home/runner/work/_temp/_runner_file_commands/add_path_64d8b762-fb69-4a06-8cda-f76ea66e06b5
GITHUB_REF=refs/heads/main
GITHUB_REPOSITORY=lupyuen/pinecone-rust-mynewt
GITHUB_REPOSITORY_OWNER=lupyuen
GITHUB_RETENTION_DAYS=90
GITHUB_RUN_ID=474939609
GITHUB_RUN_NUMBER=5
GITHUB_SERVER_URL=https://github.com
GITHUB_SHA=bafe061d1fa6e96cc014e4e9a3444a43a1c5144d
GITHUB_WORKFLOW='Build Firmware'
GITHUB_WORKSPACE=/home/runner/work/pinecone-rust-mynewt/pinecone-rust-mynewt
GOROOT=/opt/hostedtoolcache/go/1.14.13/x64
GOROOT_1_14_X64=/opt/hostedtoolcache/go/1.14.13/x64
GOROOT_1_15_X64=/opt/hostedtoolcache/go/1.15.6/x64
GRADLE_HOME=/usr/share/gradle
GROUPS=()
HOME=/home/runner
HOMEBREW_CELLAR='"/home/linuxbrew/.linuxbrew/Cellar"'
HOMEBREW_PREFIX='"/home/linuxbrew/.linuxbrew"'
HOMEBREW_REPOSITORY='"/home/linuxbrew/.linuxbrew/Homebrew"'
HOSTNAME=fv-az29-728
HOSTTYPE=x86_64
IFS=$' \t\n'
INVOCATION_ID=448992154cf94065a2a551779d186946
ImageOS=ubuntu20
ImageVersion=20201210.0
JAVA_HOME=/usr/lib/jvm/adoptopenjdk-11-hotspot-amd64
JAVA_HOME_11_X64=/usr/lib/jvm/adoptopenjdk-11-hotspot-amd64
JAVA_HOME_8_X64=/usr/lib/jvm/adoptopenjdk-8-hotspot-amd64
JOURNAL_STREAM=9:19739
LANG=C.UTF-8
LEIN_HOME=/usr/local/lib/lein
LEIN_JAR=/usr/local/lib/lein/self-installs/leiningen-2.9.5-standalone.jar
M2_HOME=/usr/share/apache-maven-3.6.3
MACHTYPE=x86_64-pc-linux-gnu
OPTERR=1
OPTIND=1
OSTYPE=linux-gnu
PATH=/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/opt/pipx_bin:/usr/share/rust/.cargo/bin:/home/runner/.config/composer/vendor/bin:/home/runner/.dotnet/tools:/snap/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
PERFLOG_LOCATION_SETTING=RUNNER_PERFLOG
PIPX_BIN_DIR='"/opt/pipx_bin"'
PIPX_HOME='"/opt/pipx"'
POWERSHELL_DISTRIBUTION_CHANNEL=GitHub-Actions-ubuntu20
PPID=1419
PS4='+ '
***
RUNNER_OS=Linux
RUNNER_PERFLOG=/home/runner/perflog
RUNNER_TEMP=/home/runner/work/_temp
RUNNER_TOOL_CACHE=/opt/hostedtoolcache
RUNNER_TRACKING_ID=github_bd1e8ac9-ce6e-4066-bc5e-9e1017d2a08c
RUNNER_USER=runner
RUNNER_WORKSPACE=/home/runner/work/pinecone-rust-mynewt
SELENIUM_JAR_PATH=/usr/share/java/selenium-server-standalone.jar
SHELL=/bin/bash
SHELLOPTS=braceexpand:errexit:hashall:interactive-comments
SHLVL=1
SWIFT_PATH=/usr/share/swift/usr/bin
TERM=dumb
UID=1001
USER=runner
VCPKG_INSTALLATION_ROOT=/usr/local/share/vcpkg
_=/usr/bin/bash

§13.2 File System

/home/runner/work/pinecone-rust-mynewt/pinecone-rust-mynewt
total 52
-rw-r--r-- 1 runner docker 11357 Jan 10 02:24 LICENSE
-rw-r--r-- 1 runner docker   241 Jan 10 02:24 NOTICE
-rw-r--r-- 1 runner docker   583 Jan 10 02:24 README.md
drwxr-xr-x 3 runner docker  4096 Jan 10 02:24 apps
drwxr-xr-x 3 runner docker  4096 Jan 10 02:24 compiler
drwxr-xr-x 4 runner docker  4096 Jan 10 02:24 hw
-rw-r--r-- 1 runner docker  1807 Jan 10 02:24 openocd.cfg
-rw-r--r-- 1 runner docker  1081 Jan 10 02:24 project.yml
drwxr-xr-x 7 runner docker  4096 Jan 10 02:24 repos
drwxr-xr-x 2 runner docker  4096 Jan 10 02:24 scripts
drwxr-xr-x 4 runner docker  4096 Jan 10 02:24 targets