Artemis and Apollo

In the last entry, I talked about porting Inferno to a microcontroller in the SAMD51 range of products. I continued to plug away at the port for a while, trying to get the USB peripheral to do what I wanted. The board was a bit temperamental to get code onto. Sometimes it wouldn't recognise the awkward double-tap needed to get it into bootloader mode; at other times the flashing software would fail to send the payload to the MCU. Ultimately, during a USB debugging session, the MCU failed somehow, leaving the board effectively bricked.

At around that time I noticed that Pimoroni was having a summer sale which included a load of MicroMod products that they seem to be clearing out and not restocking. This gave me the excuse to buy a couple more of the processor boards for porting purposes and a pair of carrier boards to put them on. The first of these is the Artemis processor board, which hosts another Cortex-M4-based MCU, the Ambiq Apollo3 Blue.

A new learning curve

In hindsight the port to the SAMD51 MCU was fairly straightforward and actually quite a relief to get running, especially given that it had been a daunting experience when I first looked at its datasheet. I expected, or hoped, that the Apollo3 MCU would also seem simple after all the time and effort spent learning about Cortex-M4 systems. I fired up my bare metal development environment and started probing the MCU.

The first thing I try to do with these boards is to get the status LED to blink, or at least light up. This usually involves looking at enabling clocks and power, then propagating these things to peripherals like the GPIO pins. To get blinking to work can involve setting up timers, or it can simply involve putting delay loops in the code. In this case it was frustrating to get things working because some peripherals restrict access to many of their registers until a key value is written to a particular register. It's also not obvious just what needs to be enabled for some things, and some reading between the lines was needed.

An example of the fuzziness that surrounded attempts to enable peripherals was the way the UARTs are handled. Usually, I know that I need to dig around in the power and clock sections of a datasheet to find which clocks need to be configured and which bits need to be set to supply power to peripherals. In this case, it's useful to know that enabling power to either of the UARTs will cause a status bit for the high frequency reference clock (HFRC) to be set when the UART is powered.

Confusion was also caused by the initial disabled state of interrupts and by the need to manually set the address of the vector table in user code in order to use SysTick and other exceptions. I'm not sure if these issues are related to the SparkFun Variable Loader (SVL) bootloader which is installed on the MCU. The bootloader itself is simple to use, with a Python script to send payloads via a USB cable. There's no need to push buttons to enter a boot mode, just sending a new binary causes the MCU to reset, enter the bootloader and start receiving data.

In many ways the MCU is easier to use than I expected, with less of a need to configure basic features. It's just that I expected to have to set up things like clock scaling, and to configure pads and pins in more detail than was eventually necessary. The MCU reminds me slightly of the Ingenic SoCs that are pretty straightforward to configure, though it would help if the datasheet provided some guidance about common configuration tasks like datasheets from other vendors sometimes do.

Booting Inferno

Although the bare metal setup for the Apollo3 took longer than I would have liked, branching an existing Inferno port and applying the information gleaned from the bare metal code took less than a day. The results are, as usual, not too surprising:

Initial Dis: "/osinit.dis"
** Inferno
** Vita Nuova
apollo3$ cat /dev/memory
      38816      194022       39040         596         206          34      155194 main
      19168      158745       19168         140          20          12      139565 heap
          0           0           0           0           0           0           0 image

The Apollo3 MCU has 384K of RAM, which is luxurious compared to the 192K of the STM32F405 MCU of an earlier port. This means that the effort I made to reduce RAM usage for the STM32 is not quite as acutely needed here, though every little saving will be useful as expectations grow with the amount of RAM to play with.

Code for the port can be found in the apollo3 branch of this repository.

David Boddie
22 August 2023