Site icon Pallav Aggarwal

Getting Started with Ambiq Micro Apollo 3 Blue MCU

Overview

If you are building battery powered embedded product, you must learn about Ambiq Micro Apollo 3 Blue.

Although, there are many MCU manufacturers who are focusing on Ultra low Power MCUs like , Nordic Semiconductor, Texas Instruments, Silabs, ST Micro but Ambiq Micro provides lowest power consuming microcontrollers in the world. For example, their Apollo 3 consumes 6uA per Mhz and Apollo 4 consumes 4uA per Mhz.

I have been working on low power embedded systems design since more than 2 years now and have learned a lot about how to make design which can work with couple of uA current while running. It is not that simple.

On need to look at so many things if you are aiming at < 1mA average current. For more details, read my article: Tips for low power embedded systems design.

In this article, I have explained how to setup the development environment and shared details on how to use various interfaces.

Logo and IC Image credit: Ambiq Micro

Unfortunately, Ambiq Micro has not created knowledge base articles on their website so it will be difficult for a new user to work with their MCUs. Their technical forum is also quite unresponsive.

But, I am very impressed with the MCU technology they have created so inspite of these technical hurdles I wanted to learn how to build products using Ambiq Micro MCUs.

From hardware point of view they have done a good job, they have released the hardware design files of their Evaluation boards, helps a lot and resolve most questions about the hardware.

As the design files are in Altium, it is an added advantage for engineers who are using Altium, like me 🙂

They are lacking on SDK front. There is no HAL documentation ( very bad) and the absence of simpler example code makes life of a new developer quite difficult.

I think they are just focusing on helping their old customers who are using their Apollo, Apollo 2 MCUs or they are only interested if project has very high volumes.

As of now, smaller companies need to go through their distributors or they need help themselves (struggle on your own) with whatever information is available on their website.

I thought, I can share whatever I will have learned so far so that other engineers do not need to struggle that much, hopefully 🙂

Resources on Ambiq Micro website

Setting Up Development Environment for Apollo 3 Blue

Although there Eclipse + GCC setup is also possible but I wanted to focus more on the how to use interfaces rather than development environment, so I picked up Keil uVision. In future I will add a section for other IDE environment also.

Download Keil uVision MDK

(uVision Keil for Apollo 3 firmware development)

One can use UVision Keil Lite version which is free upto 32KB

You can download latest Keil uVision MDK. You will need full a small form before you can get an access to the installer.

Download Device Family Pack

For Ambiq Micro MCU support you need to download the device family pack as well.

Download Latest Apollo 3 SDK

Download the latest sdk from Ambiq Micro website and extract it, in the package you will be able to see example codes.


Download Latest J-Link tools

The J-Link tools of interest are:

J-Flash Lite

J-Link SWO Viewer

J-Flash is used to load pre-compiled binaries into the device flash. J-Link SWO Viewer is used to see the resulting application output.

You can download J-Links tools.

Installation

After downloading Keil uVision MDK, device support package, J-link tools, install all three on your PC.

You need to also extract Apollo 3 SDK Zip.

Once installation is complete, you can go to SDK directory and search for \boards folder(select the board you are using) and go to \examples.

\AmbiqSuite-R2.5.1\boards\apollo3_evb\examples

Running default Binary Counter example

Go to binary counter code project

\AmbiqSuite-R2.5.1\boards\apollo3_evb\examples\binary_counter

go inside Keil folder and double click project file, it will open the project in Keil.

You can flash this project into your evaluation board and press reset button, you will see 5x LEDs on evaluation board running a binary counter sequence.

You can use this project as base, whatever you don’t need you can remove and add your custom code, build the project, Flash and run.

KAGA FEI ELECTRONICS

While struggling to get started with Apollo 3 Blue MCU I contacted KAGA FEI ELECTRONICS one of the distributors of Ambiq Micro and thanks to their FAE team, I could speed up my development with their support.

If you want to develop product using Apollo 3/4, you may want to contact them.

How to use Apollo 3 Interfaces

Before you start developing the code, please go through the Pin mapping.

You can download the Apollo 3 Blue Pinout details.

How to use GPIO as Output

First thing we all want to do when we start learning a new MCU is, blink an LED (GPIO as output), let us see how to configure a GPIO as output and control its state.

Define a macro for Pin you want to use. Check Evaluation Board schematic for LEDs Pin numbers.

#define LED1_PIN		17  // LED on Evaluation Board

declare structure variable for GPIO configuration, always initialize the variable:

am_hal_gpio_pincfg_t led1 = {0}; // Structure variable for GPIO configuration

and then inside main(), before super-loop while(1), you can configure various settings:

For details about all the parameters, please check the document \AmbiqSuite-R2.5.1\docs\apollo3_gpio\Apollo3-GPIO.pdf

// Configure LED1 as Output
led1.uFuncSel = 3; // Set GPIO function, see Pinout details
led1.ePullup = AM_HAL_GPIO_PIN_PULLUP_NONE;
led1.eGPOutcfg = AM_HAL_GPIO_PIN_OUTCFG_PUSHPULL;
led1.eDriveStrength = AM_HAL_GPIO_PIN_DRIVESTRENGTH_2MA;
am_hal_gpio_pinconfig(LED1_PIN, led1);

Once the GPIO configuration is done. You can use Write function to make Pin high or low as given below:

am_hal_gpio_state_write(LED1_PIN, AM_HAL_GPIO_OUTPUT_SET);
am_util_delay_ms(500);
am_hal_gpio_state_write(LED1_PIN, AM_HAL_GPIO_OUTPUT_CLEAR);
am_util_delay_ms(500);

You can also create a function where you can declare the variable for GPIO configuration and set all the parameters and call this function before while(1):

void ConfigureGPIOs(void)
{
	am_hal_gpio_pincfg_t led1 ={0}; // Structure variable for GPIO configuration
	
	// Configure LED1 as Output
	led1.uFuncSel = 3; //
	led1.ePullup = AM_HAL_GPIO_PIN_PULLUP_NONE;
	led1.eGPOutcfg = AM_HAL_GPIO_PIN_OUTCFG_PUSHPULL;
	led1.eDriveStrength = AM_HAL_GPIO_PIN_DRIVESTRENGTH_2MA;
	am_hal_gpio_pinconfig(LED1_PIN, led1);
	
}

How to use GPIO as Input (Polling)

Configuring GPIO as input is quite similar to how we configured GPIO as output.

Define a macro for Pin you want to use as input. See evaluation board schematic for pin numbers used for Keys/buttons

#define KEY1_PIN		16

define GPIO input configuration structure variable, always initialize the variable:

am_hal_gpio_pincfg_t key1= {0}; // Structure variable for GPIO Inputs configuration

Go through details about all parameters in the document: \AmbiqSuite-R2.5.1\docs\apollo3_gpio\Apollo3-GPIO.pdf

Now, set various parameters for GPIO Input Pin.

	// Configure KEY1 as Input
	key1.uFuncSel = 3;
	key1.ePullup = AM_HAL_GPIO_PIN_PULLUP_NONE;
	key1.eGPInput = AM_HAL_GPIO_PIN_INPUT_ENABLE;
	key1.eGPRdZero = AM_HAL_GPIO_PIN_RDZERO_READPIN;
	am_hal_gpio_pinconfig(KEY1_PIN, key1);

Once the GPIO input is configured, you can use read the pin status as given below:

You can capture pin status in a variable, example: uint32_t buttonStatus, variable need to passed to the GPIO read API as shown below:

am_hal_gpio_state_read(KEY1_PIN, AM_HAL_GPIO_INPUT_READ, &buttonStatus);

How to use GPIO as input (Interrupt)

Reading GPIO in pooling mode is useful but many a times you need to capturing digital input at high speed and need to use interrupt.

Let us see how to use interrupt on a GPIO input Pin.

To enable interrupt on any GPIO, you need to do a few extra things:

Configuring interrupt

// Configure KEY2 as Input (Interrupt)
key1.uFuncSel = 3;
key1.eIntDir = AM_HAL_GPIO_PIN_INTDIR_HI2LO;
key1.eGPInput = AM_HAL_GPIO_PIN_INPUT_ENABLE;
am_hal_gpio_pinconfig(KEY1_PIN, key1);

create mask & enable interrupt

// Clear the GPIO Interrupt (write to clear).
AM_HAL_GPIO_MASKCREATE(GpioIntMask); // this function create a variable, pGpioIntMask, for pin masking
am_hal_gpio_interrupt_clear(AM_HAL_GPIO_MASKBIT(pGpioIntMask, KEY1_PIN));
	
// Enable the GPIO/button interrupt.
am_hal_gpio_interrupt_enable(AM_HAL_GPIO_MASKBIT(pGpioIntMask, KEY1_PIN));

//Enable GPIO interrupts
NVIC_EnableIRQ(GPIO_IRQn);

//Enable Master Interrupt
am_hal_interrupt_master_enable();

GPIO Input interrupt service routine (ISR), this is one method where a common ISR is used and one more method is there where you can register unique ISR per GPIO input. I will explain that later.

Method 1: Common ISR for all GPIO Inputs

void am_gpio_isr(void)
{
    uint64_t GpioIntStatusMask = 0;

    am_hal_gpio_interrupt_status_get(false, &GpioIntStatusMask); // read interrupt status register to identify which GPIO interrupt came
    am_hal_gpio_interrupt_clear(GpioIntStatusMask); // Clear the interrupt

    // detect which GPIO interrupt came
    if(GpioIntStatusMask & 0x40000LL) // ((0x01 << (Pin Number)) - example for Pin 18
    {
        // do something;
    }
    else if(GpioIntStatusMask & 0x80000LL) // ((0x01 << (Pin Number)) - example for Pin 19
    {
        // do something;
    }

}

How to use UART

Sending data and receiving data over UART is the most simple serial interface. Following are the steps needed:

We need a UART handle which will be passed to HAL function for configuration.

A Macro “CHECK_ERRORS” is used to detect if any errors has occurred when a HAL function is called.

// UART handle.
void *phUART;

#define CHECK_ERRORS(x)                                                       \
    if ((x) != AM_HAL_STATUS_SUCCESS)                                         \
    {                                                                         \
        error_handler(x);                                                     \
    }

volatile uint32_t ui32LastError;

// Catch HAL errors.
void error_handler(uint32_t ui32ErrorStatus)
{
    ui32LastError = ui32ErrorStatus;

    while (1); // you can replace this with your own code to handle the error condition
}

We need to create two buffers, one for TX and one for RX.

// UART buffers.
uint8_t g_pui8TxBuffer[256];
uint8_t g_pui8RxBuffer[1];

Now, define UART configuration structure variable and set the required parameters:

const am_hal_uart_config_t g_sUartConfig =
{
    //
    // Standard UART settings: 9600-8-N-1
    //
    .ui32BaudRate = 9600,
    .ui32DataBits = AM_HAL_UART_DATA_BITS_8,
    .ui32Parity = AM_HAL_UART_PARITY_NONE,
    .ui32StopBits = AM_HAL_UART_ONE_STOP_BIT,
    .ui32FlowControl = AM_HAL_UART_FLOW_CTRL_NONE,

    //
    // Set TX and RX FIFOs to interrupt at half-full.
    //
    .ui32FifoLevels = (AM_HAL_UART_TX_FIFO_1_2 |
                       AM_HAL_UART_RX_FIFO_1_2),

    //
    // Buffers
    //
    .pui8TxBuffer = g_pui8TxBuffer,
    .ui32TxBufferSize = sizeof(g_pui8TxBuffer),
    .pui8RxBuffer = g_pui8RxBuffer,
    .ui32RxBufferSize = sizeof(g_pui8RxBuffer),
};

one more configuration which is required is, GPIO pin configuration. Create Macro for RX, TX numbers

#define UART0_TX_PIN 22
#define UART0_RX_PIN 23

Declare two variables one for tx and another rx gpio pin configuration. Set the alternate function for both pins.

// GPIO pin configuration for UART

am_hal_gpio_pincfg_t uart0ConfigTx;
am_hal_gpio_pincfg_t uart0ConfigRx;

uart0ConfigTx.uFuncSel = AM_HAL_PIN_22_UART0TX; // Set GPIO function, see Pinout details
uart0ConfigTx.eDriveStrength = AM_HAL_GPIO_PIN_DRIVESTRENGTH_2MA;

uart0ConfigRx.uFuncSel = AM_HAL_PIN_23_UART0RX; // Set GPIO function, see Pinout details

then before super loop / while(1), we need to initialize the UART instance, UART0 in this case.

// Initialize the interface for UART output.
CHECK_ERRORS(am_hal_uart_initialize(0, &phUART));

Configure the Power State for UART, Possible states are:

CHECK_ERRORS(am_hal_uart_power_control(phUART, AM_HAL_SYSCTRL_WAKE, false));

Call the configuration function with configuration structure variable(defined above) as input parameter

CHECK_ERRORS(am_hal_uart_configure(phUART, &g_sUartConfig));

next you can call GPIO configuration function

am_hal_gpio_pinconfig(UART0_TX_PIN, uart0ConfigTx);
am_hal_gpio_pinconfig(UART0_RX_PIN, uart0ConfigRx);

then enable the UART interrupt

// Enable interrupts.
NVIC_EnableIRQ((IRQn_Type)(UART0_IRQn));

This completes the configuration.

Now, we need a data transmission function.

Create a function which takes uint8_t type data array and data length as input parameters. Inside the function you need to declare and set parameters of uart transfer variable and then call am_hal_uart_transfer function as shown below:

// UART Send Data
void Uart0SendData(uint8_t *buffer, uint8_t bufferLen)
{

    uint32_t ui32BytesWritten = 0;

    // Write data to UART
    const am_hal_uart_transfer_t sUartWrite =
    {
        .ui32Direction = AM_HAL_UART_WRITE,
        .pui8Data = buffer,
        .ui32NumBytes = bufferLen,
        .ui32TimeoutMs = 10,
        .pui32BytesTransferred = &ui32BytesWritten,
    };

    CHECK_ERRORS(am_hal_uart_transfer(phUART, &sUartWrite));

    if (ui32BytesWritten != bufferLen)
    {
        // Couldn't send the whole data!!
        while(1); // you can replace this with your own code to handle the error condition
    }
}

in order to use send data function, you need to create a buffer and then pass it to the function:

uint8_t buf[10] = {0, 10, 23, 34, 45}; // array with dummy data
Uart0SendData(buf, 5); // send 5 bytes over UART0

for receiving data on interrupt, we need to create an ISR function

void am_uart_isr(void) is used for UART0 and void am_uart1_isr(void) is used for UART1. This is defined in the startup_keil.s file, you can change the function name as per your wish.

Inside ISR function, you need to get the interrupt status which gives information about the type of interrupt, clear the interrupt and execute user code.


I will share details about below interface soon, stay tuned!


How to use ADC

How to use I2C (Master)

How to use SPI (Master)

How to use Bluetooth

How to Reduce Power Consumption

I hope you found this post interesting.


If you like the post, please share it with others. Any suggestions or comments, let me know here.

Read my other articles on embedded systems design.

Happy learning to you!


Exit mobile version