Skip to content

Apollo3: Implement AnalogIn and PwmOut #476

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jul 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmsis/device/rtos/include/mbed_boot.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

#include "mbed_toolchain.h"

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif
Expand Down
8 changes: 6 additions & 2 deletions hal/include/hal/pwmout_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ typedef struct pwmout_s pwmout_t;
* * ::pwmout_write sets the output duty-cycle in range <0.0f, 1.0f>
* * ::pwmout_read returns the current float-point output duty-cycle in range <0.0f, 1.0f>
* * ::pwmout_period sets the PWM period specified in seconds, keeping the duty cycle the same
* * ::pwmout_period_ms sets the PWM period specified in miliseconds, keeping the duty cycle the same
* * ::pwmout_period_ms sets the PWM period specified in milliseconds, keeping the duty cycle the same
* * ::pwmout_period_us sets the PWM period specified in microseconds, keeping the duty cycle the same
* * ::pwmout_read_period_us reads the PWM period specified in microseconds
* * ::pwmout_pulsewidth sets the PWM pulsewidth specified in seconds, keeping the period the same
Expand Down Expand Up @@ -84,7 +84,11 @@ void pwmout_init_direct(pwmout_t *obj, const PinMap *pinmap);
*/
void pwmout_init(pwmout_t *obj, PinName pin);

/** Deinitialize the pwmout object
/**
* @brief Deinitialize the pwmout object
*
* After this function is called, the PWM output must stop generating edges.
* The logic level is not specified -- it may be left high, low, or tristated.
*
* @param obj The pwmout object
*/
Expand Down
8 changes: 6 additions & 2 deletions hal/include/hal/spi_api.h
Original file line number Diff line number Diff line change
Expand Up @@ -276,11 +276,15 @@ int spi_master_write(spi_t *obj, int value);
*
* The total number of bytes sent and received will be the maximum of
* tx_length and rx_length. The bytes written will be padded with the
* value 0xff.
* write fill value.
*
* Note: Even if the word size / bits per frame is not 8, \c rx_length and \c tx_length
* still give lengths in bytes of input data, not numbers of words.
*
* Note: If \c tx_rx_buffers_equal_length is true in the capabilities structure, then either \c rx_length and \c tx_length
* must be the same, or one of them will be zero. If this is not the case than the HAL implementation should
* return an error.
*
* @param[in] obj The SPI peripheral to use for sending
* @param[in] tx_buffer Pointer to the byte-array of data to write to the device
* @param[in] tx_length Number of bytes to write, may be zero
Expand Down Expand Up @@ -432,7 +436,7 @@ const PinMap *spi_slave_cs_pinmap(void);
* @note On MCUs with a data cache, the return value is used to determine if a cache invalidation needs to be done
* after the transfer is complete. If this function returns true, the driver layer will cache invalidate the Rx buffer under
* the assumption that the data needs to be re-read from main memory. Be careful, because if the read was not actually
* done by DMA, and the rx data is in the CPU cache, this invalidation will corrupt it.
* done by DMA, and the rx data is in the CPU cache and NOT main memory, this invalidation will corrupt it.
*
* @note The application layer will always acquire the SPI peripheral first before calling this, including setting the frequency and the bit width. So,
* the \c bit_width argument will never be different from the SPI's currently set bit width, and can actually be ignored.
Expand Down
3 changes: 3 additions & 0 deletions targets/TARGET_Ambiq_Micro/TARGET_Apollo3/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ target_sources(mbed-apollo3
device/spi_api.c
device/us_ticker.c
device/itm_api.c
device/analogin_api.c
device/pwmout_api.c
device/mbed_overrides.c

sdk/CMSIS/AmbiqMicro/Source/system_apollo3.c

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ typedef enum
SERIAL1_TX = IO_24,
SERIAL1_RX = IO_25,

// Not a real pin on the device, but can be passed to AnalogIn to read the internal temperature sensor
INT_TEMP_SENSOR = 0x10000,

// Not connected
NC = NC_VAL
} PinName;
Expand All @@ -140,11 +143,6 @@ typedef enum
#define QWIIC_SCL I2C_SCL
#define QWIIC_SDA I2C_SDA

// SPI bus
#define SPI_SCLK IO_5
#define SPI_MOSI IO_7
#define SPI_MISO IO_6

#if defined(MBED_CONF_TARGET_STDIO_UART_TX)
#define STDIO_UART_TX MBED_CONF_TARGET_STDIO_UART_TX
#else
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ typedef enum
SERIAL1_TX = D24,
SERIAL1_RX = D25,

// Not a real pin on the device, but can be passed to AnalogIn to read the internal temperature sensor
INT_TEMP_SENSOR = 0x10000,

// Not connected
NC = NC_VAL
} PinName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ typedef enum
CONSOLE_TX = SERIAL_TX,
CONSOLE_RX = SERIAL_RX,

// Not a real pin on the device, but can be passed to AnalogIn to read the internal temperature sensor
INT_TEMP_SENSOR = 0x10000,

// Not connected
NC = NC_VAL
} PinName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ typedef enum
CONSOLE_TX = SERIAL_TX,
CONSOLE_RX = SERIAL_RX,

// Not a real pin on the device, but can be passed to AnalogIn to read the internal temperature sensor
INT_TEMP_SENSOR = 0x10000,

// Not connected
NC = NC_VAL
} PinName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ typedef enum
SERIAL1_TX = D9,
SERIAL1_RX = D10,

// Not a real pin on the device, but can be passed to AnalogIn to read the internal temperature sensor
INT_TEMP_SENSOR = 0x10000,

// Not connected
NC = NC_VAL
} PinName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ typedef enum
SERIAL1_TX = D1,
SERIAL1_RX = D0,

// Not a real pin on the device, but can be passed to AnalogIn to read the internal temperature sensor
INT_TEMP_SENSOR = 0x10000,

// Not connected
NC = NC_VAL
} PinName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ typedef enum {
IOM_ANY
} IOMName;

// Each IOM can be used as an SPI
#define DEVICE_SPI_COUNT 6

typedef IOMName SPIName;
typedef IOMName I2CName;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,27 @@
#include "PeripheralPins.h"
#include "PeripheralPinConfigs.h"

#include "am_hal_adc.h"
#include "objects.h"

/************RTC***************/
const PinMap PinMap_RTC[] = {
{NC, 0, 0},
};

/************ADC***************/
const PinMap PinMap_ADC[] = {
{11, ADC0_2, AM_HAL_PIN_11_ADCSE2},
{12, ADC0_9, AM_HAL_PIN_12_ADCD0NSE9},
{13, ADC0_8, AM_HAL_PIN_13_ADCD0PSE8},
{16, ADC0_0, AM_HAL_PIN_16_ADCSE0},
{29, ADC0_1, AM_HAL_PIN_29_ADCSE1},
{31, ADC0_3, AM_HAL_PIN_31_ADCSE3},
{32, ADC0_4, AM_HAL_PIN_32_ADCSE4},
{33, ADC0_5, AM_HAL_PIN_33_ADCSE5},
{34, ADC0_6, AM_HAL_PIN_34_ADCSE6},
{35, ADC0_7, AM_HAL_PIN_35_ADCSE7},
{INT_TEMP_SENSOR, ADC0_TEMP, 0},
{NC, NC, 0}
};

Expand Down Expand Up @@ -253,6 +267,48 @@ const PinMap PinMap_SPI_SSEL[] = {
};

/************PWM***************/
const PinMap PinMap_PWM[] = {
// Note: The Apollo3 has fairly flexible PWM pin mapping options. Each IO pin which has a PWM function
// can actually map to one of 6 different PWM module outputs. However, there are as many PWM module
// outputs as there are pins, so we don't need to use this just to give every pin its own PWM output.
// For now, we always use the first possible option (Output Selection 2 in Table 814).
const PinMap PinMap_PWM_OUT[] = {
{IO_12, CTIMER_A0_OUT1, AM_HAL_PIN_12_CTIM0},
{IO_25, CTIMER_A0_OUT2, AM_HAL_PIN_25_CTIM1},
{IO_13, CTIMER_B0_OUT1, AM_HAL_PIN_13_CTIM2},
{IO_26, CTIMER_B0_OUT2, AM_HAL_PIN_26_CTIM3},
{IO_18, CTIMER_A1_OUT1, AM_HAL_PIN_18_CTIM4},
{IO_27, CTIMER_A1_OUT2, AM_HAL_PIN_27_CTIM5},
{IO_19, CTIMER_B1_OUT1, AM_HAL_PIN_19_CTIM6},
{IO_28, CTIMER_B1_OUT2, AM_HAL_PIN_28_CTIM7},
{IO_5, CTIMER_A2_OUT1, AM_HAL_PIN_5_CTIM8},
{IO_29, CTIMER_A2_OUT2, AM_HAL_PIN_29_CTIM9},
{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10},
{IO_30, CTIMER_B2_OUT2, AM_HAL_PIN_30_CTIM11},
{IO_22, CTIMER_A3_OUT1, AM_HAL_PIN_22_CTIM12},
{IO_31, CTIMER_A3_OUT2, AM_HAL_PIN_31_CTIM13},
{IO_23, CTIMER_B3_OUT1, AM_HAL_PIN_23_CTIM14},
{IO_32, CTIMER_B3_OUT2, AM_HAL_PIN_32_CTIM15},
{IO_42, CTIMER_A4_OUT1, AM_HAL_PIN_42_CTIM16},
{IO_4, CTIMER_A4_OUT2, AM_HAL_PIN_4_CTIM17},
{IO_43, CTIMER_B4_OUT1, AM_HAL_PIN_43_CTIM18},
{IO_7, CTIMER_B4_OUT2, AM_HAL_PIN_7_CTIM19},
{IO_44, CTIMER_A5_OUT1, AM_HAL_PIN_44_CTIM20},
{IO_24, CTIMER_A5_OUT2, AM_HAL_PIN_24_CTIM21},
{IO_45, CTIMER_B5_OUT2, AM_HAL_PIN_45_CTIM22},
{IO_33, CTIMER_B5_OUT2, AM_HAL_PIN_33_CTIM23},
{IO_46, CTIMER_A6_OUT1, AM_HAL_PIN_46_CTIM24},
{IO_47, CTIMER_B6_OUT1, AM_HAL_PIN_47_CTIM26},
{IO_35, CTIMER_B6_OUT2, AM_HAL_PIN_35_CTIM27},

// Different from normal mapping since output selection 2 doesn't give a unique timer on this pin
{IO_39, CTIMER_A6_OUT2, AM_HAL_PIN_39_CTIM25},

// For these last four, we have to duplicate other timers, as CTIMER_x7 is
// used for the us ticker.
{IO_48, CTIMER_A3_OUT1, AM_HAL_PIN_48_CTIM28},
{IO_37, CTIMER_A3_OUT2, AM_HAL_PIN_37_CTIM29},
{IO_49, CTIMER_B3_OUT1, AM_HAL_PIN_49_CTIM30},
{IO_11, CTIMER_B3_OUT2, AM_HAL_PIN_11_CTIM31},

{NC, NC, 0}
};
167 changes: 167 additions & 0 deletions targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/analogin_api.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/* mbed Microcontroller Library
* Copyright (c) 2025 Jamie Smith
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


#include "mbed_assert.h"
#include "analogin_api.h"
#include "pinmap.h"
#include "PeripheralPins.h"

#include "am_hal_adc.h"

void* am_adc_handle = NULL;

// ADC constants
#define ADC_RESOLUTION_SEL AM_HAL_ADC_SLOT_14BIT
#define ADC_RESOLUTION_BITS 14
#define ADC_CONVERSION_FACTOR (1.0f / (1 << ADC_RESOLUTION_BITS))

#define ADC_REFERENCE_SEL AM_HAL_ADC_REFSEL_INT_2P0

static uint32_t powerControlADC(bool on){
uint32_t status = AM_HAL_STATUS_SUCCESS;

if(on){
status = am_hal_adc_initialize(0, &am_adc_handle);
if(status != AM_HAL_STATUS_SUCCESS){ return status; }

status = am_hal_adc_power_control(am_adc_handle, AM_HAL_SYSCTRL_WAKE, false);
if(status != AM_HAL_STATUS_SUCCESS){ return status; }
}else{
status = am_hal_adc_disable(am_adc_handle);
if(status != AM_HAL_STATUS_SUCCESS){ return status; }

status = am_hal_pwrctrl_periph_disable(AM_HAL_PWRCTRL_PERIPH_ADC);
if(status != AM_HAL_STATUS_SUCCESS){ return status; }

status = am_hal_adc_deinitialize(am_adc_handle);
if(status != AM_HAL_STATUS_SUCCESS){ return status; }
}

return status;
}

static uint32_t initializeADC( void ){
am_hal_adc_config_t ADCConfig;

// Power on the ADC.
powerControlADC(true);

// Set up the ADC configuration parameters. These settings are reasonable
// for accurate measurements at a low sample rate.
ADCConfig.eClock = AM_HAL_ADC_CLKSEL_HFRC;
ADCConfig.ePolarity = AM_HAL_ADC_TRIGPOL_RISING;
ADCConfig.eTrigger = AM_HAL_ADC_TRIGSEL_SOFTWARE;
ADCConfig.eReference = ADC_REFERENCE_SEL;
ADCConfig.eClockMode = AM_HAL_ADC_CLKMODE_LOW_LATENCY;
ADCConfig.ePowerMode = AM_HAL_ADC_LPMODE0;
ADCConfig.eRepeat = AM_HAL_ADC_SINGLE_SCAN;

return am_hal_adc_configure(am_adc_handle, &ADCConfig);
}


void analogin_init(analogin_t *obj, PinName pin)
{
// Find ADC slot and pin from pinmap
const am_hal_adc_slot_chan_e adcSlot = pinmap_peripheral(pin, PinMap_ADC);
const uint32_t pinFunction = pinmap_function(pin, PinMap_ADC);

// Configure pin as an analog pin
am_hal_gpio_pincfg_t pincfg = g_AM_HAL_GPIO_INPUT;
pincfg.uFuncSel = pinFunction;
am_hal_gpio_pinconfig(pin, pincfg);

/* Initialize the ADC the first time it is being used,
* but don't reinitialize it again afterwards.
*/
static bool is_adc_initialized = false;
if (!is_adc_initialized)
{
initializeADC();
is_adc_initialized = true;
}

obj->slot = adcSlot;
}

// Reconfigure ADC slot 0 to target the given channel
static void ap3_config_channel(am_hal_adc_slot_chan_e channel){
am_hal_adc_slot_config_t ADCSlotConfig;

// Set up an ADC slot
ADCSlotConfig.eMeasToAvg = AM_HAL_ADC_SLOT_AVG_1;
ADCSlotConfig.ePrecisionMode = ADC_RESOLUTION_SEL;
ADCSlotConfig.eChannel = channel;
ADCSlotConfig.bWindowCompare = false;
ADCSlotConfig.bEnabled = true;

MBED_ASSERT(am_hal_adc_disable(am_adc_handle) == AM_HAL_STATUS_SUCCESS);

MBED_ASSERT(am_hal_adc_configure_slot(am_adc_handle, 0, &ADCSlotConfig) == AM_HAL_STATUS_SUCCESS);

MBED_ASSERT(am_hal_adc_enable(am_adc_handle) == AM_HAL_STATUS_SUCCESS);
}

// Read an analog in channel as a 14-bit number
static uint16_t readAnalogIn(analogin_t *obj)
{
// Target this channel
ap3_config_channel(obj->slot);

// Clear any set interrupt flags
uint32_t intStatus;
am_hal_adc_interrupt_status(am_adc_handle, &intStatus, false);
MBED_ASSERT(AM_HAL_STATUS_SUCCESS == am_hal_adc_interrupt_clear(am_adc_handle, intStatus));

// Issue SW trigger
am_hal_adc_sw_trigger(am_adc_handle);

do { // Wait for conversion complete interrupt
MBED_ASSERT(AM_HAL_STATUS_SUCCESS == am_hal_adc_interrupt_status(am_adc_handle, &intStatus, false));
} while(!(intStatus & AM_HAL_ADC_INT_CNVCMP));
MBED_ASSERT(AM_HAL_STATUS_SUCCESS == am_hal_adc_interrupt_clear(am_adc_handle, intStatus));

uint32_t numSamplesToRead = 1;
am_hal_adc_sample_t sample;
MBED_ASSERT(AM_HAL_STATUS_SUCCESS == am_hal_adc_samples_read(am_adc_handle, false, NULL, &numSamplesToRead, &sample));

return sample.ui32Sample;
}

uint16_t analogin_read_u16(analogin_t *obj)
{
uint16_t reading = readAnalogIn(obj);

/* Return a 16-Bit ADC value. */
return reading << (16 - ADC_RESOLUTION_BITS);
}

float analogin_read(analogin_t *obj)
{
/* Read the raw 14-Bit value from the ADC. */
uint16_t analog_in_raw = readAnalogIn(obj);

/* Convert it to a voltage value. */
return (analog_in_raw * ADC_CONVERSION_FACTOR);
}


const PinMap *analogin_pinmap()
{
return PinMap_ADC;
}
Loading