SparkFun Thing Plus - Ambiq Apollo3 - part 1

Driven by the idea of building a Bluetooth keyboard like device, I’ve bought the SparkFun Artemis Thing Plus board. It’s a board designed for small, power efficient devices with Bluetooth capability.

The board carries an Ambiq Apollo3 processor. It’s an ARM Cortex-M4F chip. Runs at 48MHz but can, for a brief amount of time, go up to 96MHz. Here you can read more about the whole Apollo line-up.

The SparkFun’s board brings some cool features. It has a 1S battery charger, USB-C port (routed to the one of the UART ports which means it can be easily programmed and debugged), microphone, JTAG connector, RTC clock. It can also be programmed via the Arduino environment. SparkFun seems to be very proud to say it’s…

World’s first open source hardware module using a Cortex-M4F capable of running TensorFlow models and Arduino sketches.

The repository for the hardware can be found here.

Even though being able to use the Arduino environment is nice, I’m not interested in going that direction. For what I need to do I’ll need to use the vendor’s SDK, and that’s what I want to document with this, and upcoming, posts.

SparkFun is hosting a guide on how to setup the development environment here. Unfortunately it’s a bit lacking. That’s why I’ve decided to write down my own experience.

Some things worth noting about the Artemis module:

The Artemis module is loaded with two bootloaders: the ASB and SVL. The Ambiq Secure Boot Loader (ASB) resides from 0x00 to 0xC000. This bootloader is physically part of the IC and is configured using the info0 registers. At power up, if the ASB is not activated, it jumps to 0xC000.

The processor goes through the typical boot process, which we will explore a bit later, and starts executing code from address 0x00. In this case it’s the ASB bootloader.

The SparkFun Variable Bootloader (SVL) resides at 0xC000 and will wait for an incoming serial character. If a character is received, the baud rate will be auto-detected, the SVL will load new code, then jump to the new user code starting at 0x10000. The SVL times out after 50ms.

The second bootloader, flashed by SparkFun, is there so you can program the processor with the Arduino environment. Bootloaders are only about communication between your computer and the board itself, meaning a code which is not using the Arduino framework can also be uploaded using this bootloader. If you are using the SDK and need more memory you can overwrite the SparkFun’s bootloader. You can read more about the bootloaders here.

SparkFun’s guide suggests to download the Ambiq’s SDK and add the SparkFun’s boards BSPs (Board Support Packages), by cloning this in the root folder of the SDK.

What I would suggest is to clone SparkFun’s copy of the Ambiq’s SDK since it already references the BSPs repository as a git module. Lets go with some concrete steps:

mkdir your_project
echo '# Amazing project that will change the world' > README.md
git init
git add README.md
git commit -m "Initial commit"
git submodule add https://github.com/sparkfun/AmbiqSuiteSDK
git submodule update --init --recursive

The short list of commands above creates a folder for your project, README.md file with a Markdown header. Then a git respository gets initialized and the README.md file gets commited as an initial commit. The Ambiq SDK gets added as a submodule. If you look at the AmbiqSuiteSDK repository you’ll see it references the SparkFun_Apollo3_AmbiqSuite_BSPs repository via the boards_sfe folder. In order to init submodule in a submodule you have to run the last command in the list above. After going through those steps the project structure should look as follows:

.
├── AmbiqSuiteSDK             << git submodule
│  ├── AM-BSD-EULA.txt
│  ├── ambiq_ble
│  ├── Apollo3_Examples.txt
│  ├── boards
│  ├── boards_sfe             << git submodule
│  ├── bootloader
│  ├── CMSIS
│  ├── devices
│  ├── docs
│  ├── makedefs
│  ├── Makefile
│  ├── mcu
│  ├── pack
│  ├── README.md
│  ├── third_party
│  ├── tools
│  ├── utils
│  └── VERSION.txt
└── README.md

How I proceed from this point on is by creating a src folder in the root folder of the project. Next, I copy several files from the AmbiqSuiteSDK/boards_sfw/common/tools_sfe/templates folder:

  • makefile_template.mk - a template of the Makefile you’ll use to build the binaries.
  • startup_gcc.c - initial instructions the processor has to perform.

The makefile_template.mk gets copied but also renamed to Makefile. While we are at it, the main.c file from the AmbiqSuiteSDK/boards_sfe/common/examples/blinky/ also gets copied to the src folder. That will be our first program that will blink the LED.

.
├── AmbiqSuiteSDK
│  ├── AM-BSD-EULA.txt
│  ├── ambiq_ble
│  ├── Apollo3_Examples.txt
│  ├── boards
│  ├── boards_sfe
│  ├── bootloader
│  ├── CMSIS
│  ├── devices
│  ├── docs
│  ├── makedefs
│  ├── Makefile
│  ├── mcu
│  ├── pack
│  ├── README.md
│  ├── third_party
│  ├── tools
│  ├── utils
│  └── VERSION.txt
├── README.md
└── src
   ├── Makefile
   ├── startup_gcc.c
   └── main.c

One small thing before we can go further… We don’t have the proper compiler for the platform. You can go here and download the latest toolchain. You can unpack it in the home directory (/home/your_username - I’m assuming you are using Linux…). What I would expect your toolchain path to look like would be:

/home/your_username/
└── gcc-arm-none-eabi-9-2019-q4-major
   ├── arm-none-eabi
   ├── bin
   ├── lib
   └── share

Lets get back to our project directory. You can almost compile the blinky code. In order to do that you have to trigger the make command with some parameters specified. What I do is I create a few bash scripts in the project’s root directory that trigger the make command with the necessary parameters set. So the script for building (project_root_dir/build.sh) would look like so:

_TC=$HOME/gcc-arm-none-eabi-9-2019-q4-major/bin
PATH=$_TC:$PATH
_SDK=$PWD/AmbiqSuiteSDK

make -C src \
  ASB_UPLOAD_BAUD=115200 SVL_UPLOAD_BAUD=115200 PYTHON3=python3 COM_PORT=/dev/ttyUSB0 \
  SDKPATH=$_SDK \
  COMMONPATH=$_SDK/boards_sfe/common/ \
  BOARDPATH=$_SDK/boards_sfe/artemis_thing_plus/ \
  PROJECTPATH=..
  • _TC - variable that holds the path to the toolchain binaries.
  • PATH - a default variable that gets expanded with the path to the toolchain binaries.
  • _SDK - variable that holds the path to the SDK.

Most of the passed parameters expand the _SDK variable. Parameters like ASB_UPLOAD_BAUD and SVL_UPLOAD_BAUD specify the baudrate for the flashing process.

Final look at the project structure before we pull the trigger:

.
├── AmbiqSuiteSDK
├── README.md
├── src
│  ├── Makefile
│  ├── startup_gcc.c
│  └── main.c
└── build.sh

And lets do it:

./build.sh

And if everything went as expected… it failed. If you filter out the unnecessary noise the list of errors comes down to:

...undefined reference to 'am_devices_led_off'
...undefined reference to 'am_devices_led_on'
...undefined reference to 'am_util_delay_ms'
...undefined reference to 'am_util_stdio_printf_init'

Means we didn’t define those symbols in any of the source files or the libraries. We need to modify the Makefile a bit, so the compiler can generate those symbols and linker can find them:

SRC=
SRC+= main.c
SRC+= startup_gcc.c
SRC+= am_devices_led.c                      # << the new entry
SRC+= am_util_delay.c                       # << the new entry
SRC+= am_util_stdio.c                       # << the new entry
SRC+=

# VPATH (Add paths to where your source files are located)
VPATH=
VPATH+= $(PROJECTPATH)/src
VPATH+= $(SDKPATH)/utils
VPATH+= $(PROJECTPATH)/tools_sfe/templates
VPATH+= $(PROJECTPATH)/devices              # << the new entry
VPATH+=

We expand the list of the source files with am_devices_led, am_util_delay and am_util_stdio. The VPATH is the list of the directories that will be looked up to actually find the source files. That’s why we also need to modify it, so the compilation process can find the am_devices_led.c file.

This time running the build.sh script should successfully create a example_asb.bin file in the src/bin directory.

Few things before I’m done with this post. I’ve added clean.sh and flash.sh scripts. As you can see the difference between those and build.sh very small. Those two just pass targets (clean, bootload) to make command.

The clean.sh:

_TC=~/Downloads/gcc-arm-none-eabi-9-2019-q4-major/bin
PATH=$_TC:$PATH
_SDK=~/AmbiqSuite-Rel2.2.0

make -C src clean \
  ASB_UPLOAD_BAUD=115200 SVL_UPLOAD_BAUD=115200 PYTHON3=python3 COM_PORT=/dev/ttyUSB0 \
  SDKPATH=$_SDK \
  COMMONPATH=$_SDK/boards_sfe/common/ \
  BOARDPATH=$_SDK/boards_sfe/artemis_thing_plus/ \
  PROJECTPATH=..

The flash.sh:

_TC=~/Downloads/gcc-arm-none-eabi-9-2019-q4-major/bin
PATH=$_TC:$PATH
_SDK=~/AmbiqSuite-Rel2.2.0

make -C src bootload \
  ASB_UPLOAD_BAUD=115200 SVL_UPLOAD_BAUD=115200 PYTHON3=python3 COM_PORT=/dev/ttyUSB0 \
  SDKPATH=$_SDK \
  COMMONPATH=$_SDK/boards_sfe/common/ \
  BOARDPATH=$_SDK/boards_sfe/artemis_thing_plus/ \
  PROJECTPATH=..

There is a lot of code duplication between those 3 scripts. If that bothers you, you can wrap those scripts into one and control the behavior by passing argument. You can call the make command directly. Do what feels comfortable for you.

This was a very brief guide on how to setup a basic project. There is a lot more to explore and explain but I do encourage you to poke and probe what’s there.