From b82783fac71674283d251731fe7ef9a2ba603b92 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Tue, 1 Jul 2025 23:55:44 -0700 Subject: [PATCH 1/8] Apollo3: Implement ADC --- hal/include/hal/spi_api.h | 8 +- .../TARGET_Apollo3/CMakeLists.txt | 1 + .../TARGET_SFE_ARTEMIS/PinNames.h | 3 + .../TARGET_SFE_ARTEMIS_ATP/PinNames.h | 3 + .../TARGET_SFE_ARTEMIS_DK/PinNames.h | 3 + .../TARGET_SFE_ARTEMIS_MODULE/PinNames.h | 3 + .../TARGET_SFE_ARTEMIS_NANO/PinNames.h | 3 + .../TARGET_SFE_ARTEMIS_THING_PLUS/PinNames.h | 3 + .../TARGET_Apollo3/device/PeripheralNames.h | 3 + .../TARGET_Apollo3/device/PeripheralPins.c | 14 ++ .../TARGET_Apollo3/device/analogin_api.c | 166 ++++++++++++++++++ .../TARGET_Apollo3/device/objects.h | 9 +- .../TARGET_Apollo3/device/objects_adc.h | 52 ++++++ .../TARGET_Apollo3/device/spi_api.c | 4 +- targets/targets.json5 | 14 +- 15 files changed, 274 insertions(+), 15 deletions(-) create mode 100644 targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/analogin_api.c create mode 100644 targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_adc.h diff --git a/hal/include/hal/spi_api.h b/hal/include/hal/spi_api.h index 90e836f52ab..2004d22ce68 100644 --- a/hal/include/hal/spi_api.h +++ b/hal/include/hal/spi_api.h @@ -276,10 +276,14 @@ 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 @@ -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. diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/CMakeLists.txt b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/CMakeLists.txt index d4d69c1705f..7013dda31c7 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/CMakeLists.txt +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/CMakeLists.txt @@ -50,6 +50,7 @@ target_sources(mbed-apollo3 device/spi_api.c device/us_ticker.c device/itm_api.c + device/analogin_api.c sdk/CMSIS/AmbiqMicro/Source/system_apollo3.c diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS/PinNames.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS/PinNames.h index 30e91115272..7656b6870a7 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS/PinNames.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS/PinNames.h @@ -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 + ADC_TEMP = 0x10000, + // Not connected NC = NC_VAL } PinName; diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_ATP/PinNames.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_ATP/PinNames.h index 24e2686fb06..d30facf2e04 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_ATP/PinNames.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_ATP/PinNames.h @@ -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 + ADC_TEMP = 0x10000, + // Not connected NC = NC_VAL } PinName; diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_DK/PinNames.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_DK/PinNames.h index d8da27c3180..d8dea0319dd 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_DK/PinNames.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_DK/PinNames.h @@ -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 + ADC_TEMP = 0x10000, + // Not connected NC = NC_VAL } PinName; diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_MODULE/PinNames.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_MODULE/PinNames.h index 494bbbcf502..49a4edcca89 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_MODULE/PinNames.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_MODULE/PinNames.h @@ -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 + ADC_TEMP = 0x10000, + // Not connected NC = NC_VAL } PinName; diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_NANO/PinNames.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_NANO/PinNames.h index b19f9a50604..b88b13a8479 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_NANO/PinNames.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_NANO/PinNames.h @@ -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 + ADC_TEMP = 0x10000, + // Not connected NC = NC_VAL } PinName; diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_THING_PLUS/PinNames.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_THING_PLUS/PinNames.h index 7aedda12847..5ab8f63408c 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_THING_PLUS/PinNames.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_THING_PLUS/PinNames.h @@ -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 + ADC_TEMP = 0x10000, + // Not connected NC = NC_VAL } PinName; diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralNames.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralNames.h index 4c3a534e2d6..0a25b7c982a 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralNames.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralNames.h @@ -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; diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c index 1eb737ddfcb..7397d6ed2b3 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c @@ -17,6 +17,9 @@ #include "PeripheralPins.h" #include "PeripheralPinConfigs.h" +#include "am_hal_adc.h" +#include "objects.h" + /************RTC***************/ const PinMap PinMap_RTC[] = { {NC, 0, 0}, @@ -24,6 +27,17 @@ const PinMap PinMap_RTC[] = { /************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}, + {ADC_TEMP, ADC0_TEMP, 0}, {NC, NC, 0} }; diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/analogin_api.c b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/analogin_api.c new file mode 100644 index 00000000000..b6765e60877 --- /dev/null +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/analogin_api.c @@ -0,0 +1,166 @@ +/* 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" + +static void* g_ADCHandle; + +// 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, &g_ADCHandle); + if(status != AM_HAL_STATUS_SUCCESS){ return status; } + + status = am_hal_adc_power_control(g_ADCHandle, AM_HAL_SYSCTRL_WAKE, false); + if(status != AM_HAL_STATUS_SUCCESS){ return status; } + }else{ + status = am_hal_adc_disable(g_ADCHandle); + 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(g_ADCHandle); + 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(g_ADCHandle, &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(g_ADCHandle) == AM_HAL_STATUS_SUCCESS); + + MBED_ASSERT(am_hal_adc_configure_slot(g_ADCHandle, 0, &ADCSlotConfig) == AM_HAL_STATUS_SUCCESS); + + MBED_ASSERT(am_hal_adc_enable(g_ADCHandle) == 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(g_ADCHandle, &intStatus, false); + MBED_ASSERT(AM_HAL_STATUS_SUCCESS == am_hal_adc_interrupt_clear(g_ADCHandle, intStatus)); + + // Issue SW trigger + am_hal_adc_sw_trigger(g_ADCHandle); + + do { // Wait for conversion complete interrupt + MBED_ASSERT(AM_HAL_STATUS_SUCCESS == am_hal_adc_interrupt_status(g_ADCHandle, &intStatus, false)); + } while(!(intStatus & AM_HAL_ADC_INT_CNVCMP)); + MBED_ASSERT(AM_HAL_STATUS_SUCCESS == am_hal_adc_interrupt_clear(g_ADCHandle, intStatus)); + + uint32_t numSamplesToRead = 1; + am_hal_adc_sample_t sample; + MBED_ASSERT(AM_HAL_STATUS_SUCCESS == am_hal_adc_samples_read(g_ADCHandle, 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 12-Bit value from the ADC. */ + float analog_in_raw = (float)analogin_read_u16(obj); + /* Convert it to a voltage value. */ + return (analog_in_raw * ADC_CONVERSION_FACTOR); +} + + +const PinMap *analogin_pinmap() +{ + return PinMap_ADC; +} diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects.h index f94c6422588..f1c29d1cc57 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects.h @@ -21,19 +21,12 @@ #include "am_bsp.h" #include "am_util.h" -#ifdef __cplusplus -extern "C" { -#endif - #include "objects_flash.h" #include "objects_gpio.h" #include "objects_uart.h" #include "objects_iom.h" #include "objects_spi.h" #include "objects_i2c.h" - -#ifdef __cplusplus -} -#endif +#include "objects_adc.h" #endif diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_adc.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_adc.h new file mode 100644 index 00000000000..60e7a4d875b --- /dev/null +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_adc.h @@ -0,0 +1,52 @@ +/* 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. + */ + +#ifndef OBJECTS_ADC_H +#define OBJECTS_ADC_H + +#include "am_hal_adc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// ADC input names +typedef enum { + ADC0_0 = AM_HAL_ADC_SLOT_CHSEL_SE0, + ADC0_1 = AM_HAL_ADC_SLOT_CHSEL_SE1, + ADC0_2 = AM_HAL_ADC_SLOT_CHSEL_SE2, + ADC0_3 = AM_HAL_ADC_SLOT_CHSEL_SE3, + ADC0_4 = AM_HAL_ADC_SLOT_CHSEL_SE4, + ADC0_5 = AM_HAL_ADC_SLOT_CHSEL_SE5, + ADC0_6 = AM_HAL_ADC_SLOT_CHSEL_SE6, + ADC0_7 = AM_HAL_ADC_SLOT_CHSEL_SE7, + ADC0_8 = AM_HAL_ADC_SLOT_CHSEL_SE8, + ADC0_9 = AM_HAL_ADC_SLOT_CHSEL_SE9, + ADC0_TEMP = AM_HAL_ADC_SLOT_CHSEL_TEMP +} ADCName; + +struct analogin_s +{ + // Slot that this ADC input connects to on the ADC + am_hal_adc_slot_chan_e slot; +}; + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/spi_api.c b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/spi_api.c index ebfb737a555..84bc6181497 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/spi_api.c +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/spi_api.c @@ -70,8 +70,8 @@ void spi_get_capabilities(PinName ssel, bool slave, spi_capabilities_t *cap) cap->word_length = 0x00000080; cap->slave_delay_between_symbols_ns = 0; cap->clk_modes = 0x0F; - cap->support_slave_mode = (iom_ssel == IOM_ANY) ? true : false; - cap->hw_cs_handle = false; + cap->support_slave_mode = false; + cap->hw_cs_handle = iom_ssel == IOM_ANY; cap->async_mode = false; cap->tx_rx_buffers_equal_length = false; } diff --git a/targets/targets.json5 b/targets/targets.json5 index 4b4c7102e41..e739d3b5689 100644 --- a/targets/targets.json5 +++ b/targets/targets.json5 @@ -9693,7 +9693,8 @@ mode is recommended for target MCUs with small amounts of flash and RAM.", "SPI", "I2C", "SLEEP", - "ITM" + "ITM", + "ANALOGIN", ], "macros": [ "CORDIO_ZERO_COPY_HCI", @@ -9710,14 +9711,21 @@ mode is recommended for target MCUs with small amounts of flash and RAM.", "std", "small" ] - } + }, + + "overrides": { + // Currently the analog in HAL driver sets the voltage reference to the internal 2V reference + // (though the hardware also has a 1.5V reference and an external ref input) + "default-adc-vref": 2.0 + }, + + "is_mcu_family_target": true }, "AMA3B1KK": { "public": false, "inherits": ["FAMILY_Apollo3"], "macros_add": ["AM_PACKAGE_BGA", "MBED_TICKLESS"], "device_name": "AMA3B1KK-KBR", - "is_mcu_family_target": true, "memory_bank_config": { // Leave room for the SparkFun SVL bootloader in flash "IROM1": { From f799850d9cad426e2841546cb9064255912604b4 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Sun, 13 Jul 2025 10:11:38 -0700 Subject: [PATCH 2/8] Start implementing PWM --- .../TARGET_Apollo3/CMakeLists.txt | 1 + .../TARGET_Apollo3/device/PeripheralPins.c | 44 +++ .../TARGET_Apollo3/device/objects.h | 1 + .../TARGET_Apollo3/device/objects_pwm.h | 104 ++++++ .../TARGET_Apollo3/device/pwmout_api.c | 331 ++++++++++++++++++ targets/targets.json5 | 1 + 6 files changed, 482 insertions(+) create mode 100644 targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_pwm.h create mode 100644 targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/CMakeLists.txt b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/CMakeLists.txt index 7013dda31c7..57868eacdad 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/CMakeLists.txt +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/CMakeLists.txt @@ -51,6 +51,7 @@ target_sources(mbed-apollo3 device/us_ticker.c device/itm_api.c device/analogin_api.c + device/pwmout_api.c sdk/CMSIS/AmbiqMicro/Source/system_apollo3.c diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c index 7397d6ed2b3..d5c991109e7 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c @@ -267,6 +267,50 @@ const PinMap PinMap_SPI_SSEL[] = { }; /************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[] = { + {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_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, +{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, + {NC, NC, 0} }; diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects.h index f1c29d1cc57..916548ff26e 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects.h @@ -28,5 +28,6 @@ #include "objects_spi.h" #include "objects_i2c.h" #include "objects_adc.h" +#include "objects_pwm.h" #endif diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_pwm.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_pwm.h new file mode 100644 index 00000000000..bced82db0ba --- /dev/null +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_pwm.h @@ -0,0 +1,104 @@ +/* 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. + */ + +#ifndef OBJECTS_PWM_H +#define OBJECTS_PWM_H + +#include "am_hal_ctimer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// This MCU has 8 CTIMER modules, each of which contains two segments. The two segments are functionally independent +// except that the B segment can optionally be combined with the A segment for a 32-bit counter (which we don't use). +// Each segment has two independent compare outputs, meaning that we have a total of 32 possible PWM outputs. + +// PWM name is composed of the CTIMER number, whether it's an A or B segment, and whether this is the first or second +// output from it. +#define APOLLO3_PWM_NAME(ctimerNum, aOrB, outputNum) ((ctimerNum << 2) | (aOrB << 1) | outputNum) + +typedef enum { + CTIMER_A0_OUT1 = APOLLO3_PWM_NAME(0, 0, 0), + CTIMER_A0_OUT2 = APOLLO3_PWM_NAME(0, 0, 1), + + CTIMER_A1_OUT1 = APOLLO3_PWM_NAME(1, 0, 0), + CTIMER_A1_OUT2 = APOLLO3_PWM_NAME(1, 0, 1), + + CTIMER_A2_OUT1 = APOLLO3_PWM_NAME(2, 0, 0), + CTIMER_A2_OUT2 = APOLLO3_PWM_NAME(2, 0, 1), + + CTIMER_A3_OUT1 = APOLLO3_PWM_NAME(3, 0, 0), + CTIMER_A3_OUT2 = APOLLO3_PWM_NAME(3, 0, 1), + + CTIMER_A4_OUT1 = APOLLO3_PWM_NAME(4, 0, 0), + CTIMER_A4_OUT2 = APOLLO3_PWM_NAME(4, 0, 1), + + CTIMER_A5_OUT1 = APOLLO3_PWM_NAME(5, 0, 0), + CTIMER_A5_OUT2 = APOLLO3_PWM_NAME(5, 0, 1), + + CTIMER_A6_OUT1 = APOLLO3_PWM_NAME(6, 0, 0), + CTIMER_A6_OUT2 = APOLLO3_PWM_NAME(6, 0, 1), + + CTIMER_A7_OUT1 = APOLLO3_PWM_NAME(7, 0, 0), + CTIMER_A7_OUT2 = APOLLO3_PWM_NAME(7, 0, 1), + + CTIMER_B0_OUT1 = APOLLO3_PWM_NAME(0, 1, 0), + CTIMER_B0_OUT2 = APOLLO3_PWM_NAME(0, 1, 1), + + CTIMER_B1_OUT1 = APOLLO3_PWM_NAME(1, 1, 0), + CTIMER_B1_OUT2 = APOLLO3_PWM_NAME(1, 1, 1), + + CTIMER_B2_OUT1 = APOLLO3_PWM_NAME(2, 1, 0), + CTIMER_B2_OUT2 = APOLLO3_PWM_NAME(2, 1, 1), + + CTIMER_B3_OUT1 = APOLLO3_PWM_NAME(3, 1, 0), + CTIMER_B3_OUT2 = APOLLO3_PWM_NAME(3, 1, 1), + + CTIMER_B4_OUT1 = APOLLO3_PWM_NAME(4, 1, 0), + CTIMER_B4_OUT2 = APOLLO3_PWM_NAME(4, 1, 1), + + CTIMER_B5_OUT1 = APOLLO3_PWM_NAME(5, 1, 0), + CTIMER_B5_OUT2 = APOLLO3_PWM_NAME(5, 1, 1), + + CTIMER_B6_OUT1 = APOLLO3_PWM_NAME(6, 1, 0), + CTIMER_B6_OUT2 = APOLLO3_PWM_NAME(6, 1, 1), + + CTIMER_B7_OUT1 = APOLLO3_PWM_NAME(7, 1, 0), + CTIMER_B7_OUT2 = APOLLO3_PWM_NAME(7, 1, 1), +} PWMName; + +// Get the CTIMER number of a PWM +#define APOLLO3_PWMNAME_GET_CTIMER(pwmName) (pwmName >> 2) + +// Convert from PWM name to AM_HAL_CTIMER_TIMERA/AM_HAL_CTIMER_TIMERB macro +#define APOLLO3_PWMNAME_GET_SEGMENT(pwmName) (pwmName & 0b10 ? AM_HAL_CTIMER_TIMERB : AM_HAL_CTIMER_TIMERA) + +// Convert from PWM name to AM_HAL_CTIMER_OUTPUT_NORMAL/AM_HAL_CTIMER_OUTPUT_SECONDARY enum value +#define APOLLO3_PWMNAME_GET_OUTPUT(pwmName) (pwmName & 0b1 ? AM_HAL_CTIMER_OUTPUT_SECONDARY : AM_HAL_CTIMER_OUTPUT_NORMAL) + +struct pwmout_s +{ + // PWM name that this channel is using + PWMName pwmName; +}; + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c new file mode 100644 index 00000000000..60cc262e2ef --- /dev/null +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c @@ -0,0 +1,331 @@ +/* mbed Microcontroller Library + * Copyright (c) 2024, Arm Limited and affiliates. + * 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. + */ + + +#if DEVICE_PWMOUT + +#include "hal/pwmout_api.h" +#include "PeripheralPins.h" +#include "pinmap.h" +#include "objects.h" +#include "mbed_assert.h" + +#include + +// Change to 1 to enable debug prints of what's being calculated. +// Must comment out the critical section calls in PwmOut to use. +#define APOLLO3_PWMOUT_DEBUG 1 + +#if APOLLO3_PWMOUT_DEBUG +#include +#include +#endif + +struct pwm_clock_freq { + uint32_t clockSetting; + uint32_t frequency; +}; + +// Table of options for PWM clock source vs clock frequency, in decreasing order of clock frequency +// Note that the Apollo3 uses a fixed external oscillator frequency, so this is possible to define statically. +// There are three oscillators available, each of which can be used for PWM: +// - HFRC - internal high freq RC oscillator, 48MHz +-3.5% +// - XT - external crystal, 32.768kHz, likely 50ppm or better tolerance +// - LFRC - internal low freq RC oscillator, 1.024kHz +-32% (no that's not a typo!) +// This means we have quite a wide range of base clock frequencies available, though period accuracy will be pretty +// poor if the LFRC gets selected. +#define NUM_CLOCK_OPTIONS 16 +static const struct pwm_clock_freq pwm_clocks[NUM_CLOCK_OPTIONS] = { + {AM_HAL_CTIMER_HFRC_12MHZ, 12000000}, + {AM_HAL_CTIMER_HFRC_3MHZ, 3000000}, + {AM_HAL_CTIMER_HFRC_187_5KHZ, 187500}, + {AM_HAL_CTIMER_HFRC_47KHZ, 46875}, + {AM_HAL_CTIMER_XT_32_768KHZ, 32768}, + {AM_HAL_CTIMER_XT_16_384KHZ, 16384}, + {AM_HAL_CTIMER_HFRC_12KHZ, 11719}, + {AM_HAL_CTIMER_XT_DIV4,8192}, + {AM_HAL_CTIMER_XT_DIV8,4096}, + {AM_HAL_CTIMER_XT_2_048KHZ,2048}, + {AM_HAL_CTIMER_XT_DIV32,1024}, + + // Note: NOT adding this one because the accuracy is bad + // {AM_HAL_CTIMER_LFRC_512HZ, 512}, + + {AM_HAL_CTIMER_XT_256HZ, 256}, + {AM_HAL_CTIMER_LFRC_32HZ, 32}, + {AM_HAL_CTIMER_LFRC_1HZ, 1}, + + // Note: there's also a 1/16Hz option but not adding it b/c the struct is using an integer for frequency +}; + +/// Largest top count value supported by hardware. Using this value will provide the highest duty cycle resolution. +const uint16_t MAX_TOP_COUNT = 65534; + + + +/// Calculate the effective PWM period (in floating point seconds) based on a divider and top_count value +static float calc_effective_pwm_period(float divider, uint16_t top_count) +{ + // Note: The hardware counts to top_count *inclusively*, so we have to add 1 + // to get the number of clock cycles that a given top_count value will produce + return 1.0f / ((clock_get_hz(clk_sys) / divider) / (top_count + 1)); +} + +/// Calculate the best possible top_count value (rounding up) for a divider and a desired pwm period +static uint16_t calc_top_count_for_period(float divider, float desired_pwm_period) +{ + // Derivation: + // desired_pwm_period = 1.0f / ((clock_get_hz(clk_sys) / divider) / (top_count + 1)) + // desired_pwm_period = (top_count + 1) / (clock_get_hz(clk_sys) / divider) + // desired_pwm_period * (clock_get_hz(clk_sys) / divider) - 1 = top_count + + long top_count_float = lroundf(desired_pwm_period * (clock_get_hz(clk_sys) / divider) - 1); + MBED_ASSERT(top_count_float <= MAX_TOP_COUNT); + return (uint16_t)top_count_float; +} + +/// Calculate the best possible floating point divider value for a desired pwm period. +/// This function assumes that top_count is set to MAX_TOP_COUNT. +static float calc_divider_for_period(float desired_pwm_period) +{ + // Derivation: + // (desired_pwm_period * clock_get_hz(clk_sys)) / divider - 1 = top_count + // (desired_pwm_period * clock_get_hz(clk_sys)) / divider = top_count + 1 + // divider = (desired_pwm_period * clock_get_hz(clk_sys)) / (top_count + 1) + + return (desired_pwm_period * clock_get_hz(clk_sys)) / (MAX_TOP_COUNT + 1); +} + +/// Convert PWM divider from floating point to a fixed point number (rounding up). +/// The divider is returned as an 8.4 bit fixed point number, which is what the Pico registers use. +static uint16_t pwm_divider_float_to_fixed(float divider_float) +{ + // To convert to a fixed point number, multiply by 16 and then round up + uint16_t divider_exact = ceil(divider_float * 16); + + // Largest supported divider is 255 and 15/16 + if(divider_exact > 0xFFF) + { + divider_exact = 0xFFF; + } + return divider_exact; +} + +/// Convert PWM divider from the fixed point hardware value (8.4 bits) to a float. +static float pwm_divider_fixed_to_float(uint16_t divider_fixed) +{ + return divider_fixed / 16.0f; +} + +void pwmout_init(pwmout_t *obj, PinName pin) +{ + MBED_ASSERT(obj != NULL); + + // Find PWM module from pinmap + const PWMName pwmName = pinmap_peripheral(pin, PinMap_PWM_OUT); + + /* Populate PWM object with values. */ + obj->pwmName = pwmName; + + // Configure the pin + am_hal_ctimer_output_config(APOLLO3_PWMNAME_GET_CTIMER(pwmName), + APOLLO3_PWMNAME_GET_SEGMENT(pwmName), + pin, + APOLLO3_PWMNAME_GET_OUTPUT(pwmName), + AM_HAL_GPIO_PIN_DRIVESTRENGTH_12MA); +} + +/** Deinitialize the pwmout object + * + * Parameter obj The pwmout object + */ +void pwmout_free(pwmout_t *obj) +{ + MBED_ASSERT(obj != NULL); + pwm_set_enabled(obj->slice, false); +} + +/** Set the output duty-cycle in range <0.0f, 1.0f> + * + * pulse 0.0f represents 0 percentage, 1.0f represents 100 percent. + * Parameter obj The pwmout object + * Parameter percent The floating-point percentage number + */ +void pwmout_write(pwmout_t *obj, float percent) +{ + obj->percent = percent; + + // Per datasheet section 4.5.2.2, a period value of top_count + 1 produces 100% duty cycle + int32_t new_reset_counts = lroundf((obj->top_count + 1) * percent); + + // Clamp to valid values + if(new_reset_counts > obj->top_count + 1) + { + new_reset_counts = obj->top_count + 1; + } + else if(new_reset_counts < 0) + { + new_reset_counts = 0; + } + +#if APOLLO3_PWMOUT_DEBUG + printf("new_reset_counts: %" PRIu32 "\n", new_reset_counts); +#endif + + pwm_set_chan_level(obj->slice, obj->channel, new_reset_counts); + pwm_set_enabled(obj->slice, true); +} + +/** Read the current float-point output duty-cycle + * + * Parameter obj The pwmout object + * Return A floating-point output duty-cycle + */ +float pwmout_read(pwmout_t *obj) +{ + /* Return percentage stored in object instead of calculating the value. + * This prevents floating point rounding errors. + */ + return obj->percent; +} + +void pwmout_period(pwmout_t *obj, const float desired_period) +{ + // To find the period, we perform the following steps: + // - Determine the slowest clock frequency that we can use while still hitting the needed period + // - Calculate the correct top_count value that will produce as close to the desired period as possible + + + if(period <= calc_effective_pwm_period(1, MAX_TOP_COUNT)) + { + // Short period. Leave divider at 1 and reduce top_count to match the expected period + obj->clock_divider = 1.0f; + obj->cfg.div = PWM_CHn_DIV_1; + obj->top_count = calc_top_count_for_period(obj->clock_divider, period); + } + else + { + // Long period, need to use divider. + + // Step 1: Calculate exact desired divider such that top_count would equal MAX_TOP_COUNT + float desired_divider = calc_divider_for_period(period); + + // Step 2: Round desired divider upwards to the next value the hardware can do. + // We go upwards so that the top_count value can be trimmed downwards for the best period accuracy. + uint16_t divider_fixed_point = pwm_divider_float_to_fixed(desired_divider); + obj->cfg.div = divider_fixed_point; + + // Step 3: Get the divider we'll actually be using as a float + obj->clock_divider = pwm_divider_fixed_to_float(divider_fixed_point); + + // Step 4: For best accuracy, recalculate the top_count value using the divider. + obj->top_count = calc_top_count_for_period(obj->clock_divider, period); + +#if APOLLO3_PWMOUT_DEBUG + printf("period = %f, desired_divider = %f\n", + period, + desired_divider); +#endif + } + + // Save period for later + obj->period = period; + +#if APOLLO3_PWMOUT_DEBUG + printf("obj->clock_divider = %f, obj->cfg.div = %" PRIu32 ", obj->top_count = %" PRIu16 "\n", + obj->clock_divider, + obj->cfg.div, + obj->top_count); +#endif + + // Set the new divider and top_count values. + pwm_config_set_wrap(&(obj->cfg), obj->top_count); + pwm_init(obj->slice, &(obj->cfg), false); +} + +/** Set the PWM period specified in miliseconds, keeping the duty cycle the same + * + * Parameter obj The pwmout object + * Parameter ms The milisecond period + */ +void pwmout_period_ms(pwmout_t *obj, int period) +{ + /* Set new period. */ + pwmout_period(obj, period / 1000.0f); +} + +/** Set the PWM period specified in microseconds, keeping the duty cycle the same + * + * Parameter obj The pwmout object + * Parameter us The microsecond period + */ +void pwmout_period_us(pwmout_t *obj, int period) +{ + /* Set new period. */ + pwmout_period(obj, period / 1000000.0f); +} + +/** Read the PWM period specified in microseconds + * + * @param obj The pwmout object + * @return A int output period + */ +int pwmout_read_period_us(pwmout_t *obj) +{ + return lroundf(1000000 * calc_effective_pwm_period(obj->clock_divider, obj->top_count)); +} + +/** Set the PWM pulsewidth specified in seconds, keeping the period the same. + * + * Parameter obj The pwmout object + * Parameter seconds The floating-point pulsewidth in seconds + */ +void pwmout_pulsewidth(pwmout_t *obj, float pulse) +{ + pwmout_write(obj, pulse / obj->period); +} + +/** Set the PWM pulsewidth specified in miliseconds, keeping the period the same. + * + * Parameter obj The pwmout object + * Parameter ms The floating-point pulsewidth in miliseconds + */ +void pwmout_pulsewidth_ms(pwmout_t *obj, int pulse) +{ + pwmout_write(obj, (pulse * .001f) / obj->period); +} + +/** Set the PWM pulsewidth specified in microseconds, keeping the period the same. + * + * Parameter obj The pwmout object + * Parameter us The floating-point pulsewidth in microseconds + */ +void pwmout_pulsewidth_us(pwmout_t *obj, int pulse) +{ + pwmout_write(obj, (pulse * .000001f) / obj->period); +} + +int pwmout_read_pulsewidth_us(pwmout_t *obj) { + return lroundf(obj->period * obj->percent * 1000000); +} + +const PinMap *pwmout_pinmap() +{ + return PinMap_PWM_OUT; +} + +#endif // DEVICE_PWMOUT diff --git a/targets/targets.json5 b/targets/targets.json5 index e739d3b5689..7101154834c 100644 --- a/targets/targets.json5 +++ b/targets/targets.json5 @@ -9695,6 +9695,7 @@ mode is recommended for target MCUs with small amounts of flash and RAM.", "SLEEP", "ITM", "ANALOGIN", + "PWMOUT" ], "macros": [ "CORDIO_ZERO_COPY_HCI", From 0c46963502dc0d9d63439fd8f0caee80839ff40d Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Mon, 14 Jul 2025 22:58:05 -0700 Subject: [PATCH 3/8] Map all PWM pins --- .../TARGET_Apollo3/device/PeripheralPins.c | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c index d5c991109e7..f89eb0717e2 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c @@ -282,35 +282,31 @@ const PinMap PinMap_PWM[] = { {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_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_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, -{IO_6, CTIMER_B2_OUT1, AM_HAL_PIN_6_CTIM10}, + {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}, + {IO_48, CTIMER_A7_OUT1, AM_HAL_PIN_48_CTIM28}, + {IO_49, CTIMER_B7_OUT1, AM_HAL_PIN_49_CTIM30}, + {IO_11, CTIMER_B7_OUT2, AM_HAL_PIN_11_CTIM31}, + + // Note: These two are slightly different as they use PWM output selections 6 and 7 instead of 2. + // However, the AM HAL handles that distinction internally so we don't need to do anything different. + {IO_39, CTIMER_A6_OUT2, AM_HAL_PIN_39_CTIM25}, + {IO_37, CTIMER_A7_OUT2, AM_HAL_PIN_37_CTIM29}, {NC, NC, 0} }; From f31cd07c8df5a76a5b930015e336eca0784b7ee4 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Sat, 19 Jul 2025 15:19:22 -0700 Subject: [PATCH 4/8] Initial pwmout_period implementation --- .../TARGET_Apollo3/device/objects_pwm.h | 14 +- .../TARGET_Apollo3/device/pwmout_api.c | 169 ++++++------------ 2 files changed, 64 insertions(+), 119 deletions(-) diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_pwm.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_pwm.h index bced82db0ba..5d12c906161 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_pwm.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_pwm.h @@ -83,18 +83,24 @@ typedef enum { } PWMName; // Get the CTIMER number of a PWM -#define APOLLO3_PWMNAME_GET_CTIMER(pwmName) (pwmName >> 2) +#define APOLLO3_PWMNAME_GET_CTIMER(pwm_name) (pwm_name >> 2) // Convert from PWM name to AM_HAL_CTIMER_TIMERA/AM_HAL_CTIMER_TIMERB macro -#define APOLLO3_PWMNAME_GET_SEGMENT(pwmName) (pwmName & 0b10 ? AM_HAL_CTIMER_TIMERB : AM_HAL_CTIMER_TIMERA) +#define APOLLO3_PWMNAME_GET_SEGMENT(pwm_name) (pwm_name & 0b10 ? AM_HAL_CTIMER_TIMERB : AM_HAL_CTIMER_TIMERA) // Convert from PWM name to AM_HAL_CTIMER_OUTPUT_NORMAL/AM_HAL_CTIMER_OUTPUT_SECONDARY enum value -#define APOLLO3_PWMNAME_GET_OUTPUT(pwmName) (pwmName & 0b1 ? AM_HAL_CTIMER_OUTPUT_SECONDARY : AM_HAL_CTIMER_OUTPUT_NORMAL) +#define APOLLO3_PWMNAME_GET_OUTPUT(pwm_name) (pwm_name & 0b1 ? AM_HAL_CTIMER_OUTPUT_SECONDARY : AM_HAL_CTIMER_OUTPUT_NORMAL) struct pwmout_s { // PWM name that this channel is using - PWMName pwmName; + PWMName pwm_name; + + // Clock period configured on this PWM, in floating point seconds + float clock_period; + + // Number of counts that the PWM output will make before a new PWM cycle starts + uint32_t top_count; }; #ifdef __cplusplus diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c index 60cc262e2ef..7fc933602d1 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c @@ -23,6 +23,7 @@ #include "pinmap.h" #include "objects.h" #include "mbed_assert.h" +#include #include @@ -48,8 +49,8 @@ struct pwm_clock_freq { // - LFRC - internal low freq RC oscillator, 1.024kHz +-32% (no that's not a typo!) // This means we have quite a wide range of base clock frequencies available, though period accuracy will be pretty // poor if the LFRC gets selected. -#define NUM_CLOCK_OPTIONS 16 -static const struct pwm_clock_freq pwm_clocks[NUM_CLOCK_OPTIONS] = { +#define NUM_CLOCK_SOURCE_OPTIONS 16 +static const struct pwm_clock_freq pwm_clock_sources[NUM_CLOCK_SOURCE_OPTIONS] = { {AM_HAL_CTIMER_HFRC_12MHZ, 12000000}, {AM_HAL_CTIMER_HFRC_3MHZ, 3000000}, {AM_HAL_CTIMER_HFRC_187_5KHZ, 187500}, @@ -73,63 +74,8 @@ static const struct pwm_clock_freq pwm_clocks[NUM_CLOCK_OPTIONS] = { }; /// Largest top count value supported by hardware. Using this value will provide the highest duty cycle resolution. -const uint16_t MAX_TOP_COUNT = 65534; - - - -/// Calculate the effective PWM period (in floating point seconds) based on a divider and top_count value -static float calc_effective_pwm_period(float divider, uint16_t top_count) -{ - // Note: The hardware counts to top_count *inclusively*, so we have to add 1 - // to get the number of clock cycles that a given top_count value will produce - return 1.0f / ((clock_get_hz(clk_sys) / divider) / (top_count + 1)); -} - -/// Calculate the best possible top_count value (rounding up) for a divider and a desired pwm period -static uint16_t calc_top_count_for_period(float divider, float desired_pwm_period) -{ - // Derivation: - // desired_pwm_period = 1.0f / ((clock_get_hz(clk_sys) / divider) / (top_count + 1)) - // desired_pwm_period = (top_count + 1) / (clock_get_hz(clk_sys) / divider) - // desired_pwm_period * (clock_get_hz(clk_sys) / divider) - 1 = top_count - - long top_count_float = lroundf(desired_pwm_period * (clock_get_hz(clk_sys) / divider) - 1); - MBED_ASSERT(top_count_float <= MAX_TOP_COUNT); - return (uint16_t)top_count_float; -} - -/// Calculate the best possible floating point divider value for a desired pwm period. -/// This function assumes that top_count is set to MAX_TOP_COUNT. -static float calc_divider_for_period(float desired_pwm_period) -{ - // Derivation: - // (desired_pwm_period * clock_get_hz(clk_sys)) / divider - 1 = top_count - // (desired_pwm_period * clock_get_hz(clk_sys)) / divider = top_count + 1 - // divider = (desired_pwm_period * clock_get_hz(clk_sys)) / (top_count + 1) - - return (desired_pwm_period * clock_get_hz(clk_sys)) / (MAX_TOP_COUNT + 1); -} - -/// Convert PWM divider from floating point to a fixed point number (rounding up). -/// The divider is returned as an 8.4 bit fixed point number, which is what the Pico registers use. -static uint16_t pwm_divider_float_to_fixed(float divider_float) -{ - // To convert to a fixed point number, multiply by 16 and then round up - uint16_t divider_exact = ceil(divider_float * 16); - - // Largest supported divider is 255 and 15/16 - if(divider_exact > 0xFFF) - { - divider_exact = 0xFFF; - } - return divider_exact; -} - -/// Convert PWM divider from the fixed point hardware value (8.4 bits) to a float. -static float pwm_divider_fixed_to_float(uint16_t divider_fixed) -{ - return divider_fixed / 16.0f; -} +/// The hardware performs (CMPR register value + 1) counts and it's a 16-bit register, so the actual max top count is 2^16. +const uint32_t MAX_TOP_COUNT = 65536; void pwmout_init(pwmout_t *obj, PinName pin) { @@ -139,7 +85,7 @@ void pwmout_init(pwmout_t *obj, PinName pin) const PWMName pwmName = pinmap_peripheral(pin, PinMap_PWM_OUT); /* Populate PWM object with values. */ - obj->pwmName = pwmName; + obj->pwm_name = pwmName; // Configure the pin am_hal_ctimer_output_config(APOLLO3_PWMNAME_GET_CTIMER(pwmName), @@ -206,87 +152,80 @@ float pwmout_read(pwmout_t *obj) void pwmout_period(pwmout_t *obj, const float desired_period) { // To find the period, we perform the following steps: - // - Determine the slowest clock frequency that we can use while still hitting the needed period + // - Determine the fastest clock frequency that we can use while still hitting the needed period // - Calculate the correct top_count value that will produce as close to the desired period as possible - - - if(period <= calc_effective_pwm_period(1, MAX_TOP_COUNT)) - { - // Short period. Leave divider at 1 and reduce top_count to match the expected period - obj->clock_divider = 1.0f; - obj->cfg.div = PWM_CHn_DIV_1; - obj->top_count = calc_top_count_for_period(obj->clock_divider, period); + // - Write the new top_count value into the hardware + + size_t clk_source_idx; + bool found_clk_source = false; + for(clk_source_idx = 0; clk_source_idx < NUM_CLOCK_SOURCE_OPTIONS; ++clk_source_idx) { + const float divider_max_period = MAX_TOP_COUNT / (float)pwm_clock_sources[clk_source_idx].frequency; + if(divider_max_period >= desired_period) { + found_clk_source = true; + break; + } + } + if(!found_clk_source) { + MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_DRIVER_PWM, MBED_ERROR_CODE_INVALID_ARGUMENT), "Clock frequency too slow!"); } - else - { - // Long period, need to use divider. - - // Step 1: Calculate exact desired divider such that top_count would equal MAX_TOP_COUNT - float desired_divider = calc_divider_for_period(period); - - // Step 2: Round desired divider upwards to the next value the hardware can do. - // We go upwards so that the top_count value can be trimmed downwards for the best period accuracy. - uint16_t divider_fixed_point = pwm_divider_float_to_fixed(desired_divider); - obj->cfg.div = divider_fixed_point; - - // Step 3: Get the divider we'll actually be using as a float - obj->clock_divider = pwm_divider_fixed_to_float(divider_fixed_point); - // Step 4: For best accuracy, recalculate the top_count value using the divider. - obj->top_count = calc_top_count_for_period(obj->clock_divider, period); + // Now that we have found the best clock source, calculate top_count to hit the desired period + obj->clock_period = 1.0f / (float)pwm_clock_sources[clk_source_idx].frequency; + obj->top_count = lroundf(desired_period / obj->clock_period); -#if APOLLO3_PWMOUT_DEBUG - printf("period = %f, desired_divider = %f\n", - period, - desired_divider); -#endif + // The hardware cannot support a top_count of less than 2. If that happened than it means the + // frequency is too fast. + if(obj->top_count < 2) { + MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_DRIVER_PWM, MBED_ERROR_CODE_INVALID_ARGUMENT), "Clock frequency too fast!"); } - // Save period for later - obj->period = period; - #if APOLLO3_PWMOUT_DEBUG - printf("obj->clock_divider = %f, obj->cfg.div = %" PRIu32 ", obj->top_count = %" PRIu16 "\n", - obj->clock_divider, - obj->cfg.div, + printf("clk_source_idx = %zu, obj->clock_period = %f, obj->top_count = %" PRIu32 "\n", + clk_source_idx, + obj->clock_period, obj->top_count); #endif - // Set the new divider and top_count values. - pwm_config_set_wrap(&(obj->cfg), obj->top_count); - pwm_init(obj->slice, &(obj->cfg), false); + // Set new clock source. This stops the timer. + am_hal_ctimer_config_single(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), + APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name), + AM_HAL_CTIMER_FN_PWM_REPEAT | clk_source_idx); + + // Set new period value. Note that: + // - We need to program a value of (top_count - 1) into the HW register + // - As part of setting the period we also need to program the duty cycle. For now we program it to zero, + // in anticipation of a future pwmout_write() call + if(APOLLO3_PWMNAME_GET_OUTPUT(obj->pwm_name) == AM_HAL_CTIMER_OUTPUT_NORMAL) { + am_hal_ctimer_period_set(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), + APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name), + obj->top_count - 1, + 0); + } + else { + am_hal_ctimer_aux_period_set(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), + APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name), + obj->top_count - 1, + 0); + } + + am_hal_ctimer_start(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name)); } -/** Set the PWM period specified in miliseconds, keeping the duty cycle the same - * - * Parameter obj The pwmout object - * Parameter ms The milisecond period - */ void pwmout_period_ms(pwmout_t *obj, int period) { /* Set new period. */ pwmout_period(obj, period / 1000.0f); } -/** Set the PWM period specified in microseconds, keeping the duty cycle the same - * - * Parameter obj The pwmout object - * Parameter us The microsecond period - */ void pwmout_period_us(pwmout_t *obj, int period) { /* Set new period. */ pwmout_period(obj, period / 1000000.0f); } -/** Read the PWM period specified in microseconds - * - * @param obj The pwmout object - * @return A int output period - */ int pwmout_read_period_us(pwmout_t *obj) { - return lroundf(1000000 * calc_effective_pwm_period(obj->clock_divider, obj->top_count)); + return lroundf(1000000 * obj->top_count * obj->clock_period); } /** Set the PWM pulsewidth specified in seconds, keeping the period the same. From b2d44a12733ec825763c52e40bd207a95ec8dc40 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Sun, 20 Jul 2025 09:40:05 -0700 Subject: [PATCH 5/8] Finish PWM driver, start getting PWM and ADC working --- drivers/source/PwmOut.cpp | 56 +++---- hal/include/hal/pwmout_api.h | 8 +- .../TARGET_SFE_ARTEMIS/PinNames.h | 5 - .../TARGET_Apollo3/device/PeripheralPins.c | 2 +- .../TARGET_Apollo3/device/analogin_api.c | 5 +- .../TARGET_Apollo3/device/objects_pwm.h | 4 + .../TARGET_Apollo3/device/pwmout_api.c | 156 +++++++----------- tools/cmake/mbed_python_interpreter.cmake | 3 +- 8 files changed, 107 insertions(+), 132 deletions(-) diff --git a/drivers/source/PwmOut.cpp b/drivers/source/PwmOut.cpp index a956f22f7e6..f5b9a48e3e0 100644 --- a/drivers/source/PwmOut.cpp +++ b/drivers/source/PwmOut.cpp @@ -57,74 +57,74 @@ PwmOut::~PwmOut() void PwmOut::write(float value) { - core_util_critical_section_enter(); + //core_util_critical_section_enter(); pwmout_write(&_pwm, value); - core_util_critical_section_exit(); + //core_util_critical_section_exit(); } float PwmOut::read() { - core_util_critical_section_enter(); + //core_util_critical_section_enter(); float val = pwmout_read(&_pwm); - core_util_critical_section_exit(); + //core_util_critical_section_exit(); return val; } void PwmOut::period(float seconds) { - core_util_critical_section_enter(); + //core_util_critical_section_enter(); pwmout_period(&_pwm, seconds); - core_util_critical_section_exit(); + //core_util_critical_section_exit(); } void PwmOut::period_ms(int ms) { - core_util_critical_section_enter(); + //core_util_critical_section_enter(); pwmout_period_ms(&_pwm, ms); - core_util_critical_section_exit(); + //core_util_critical_section_exit(); } void PwmOut::period_us(int us) { - core_util_critical_section_enter(); + //core_util_critical_section_enter(); pwmout_period_us(&_pwm, us); - core_util_critical_section_exit(); + //core_util_critical_section_exit(); } int PwmOut::read_period_us() { - core_util_critical_section_enter(); + //core_util_critical_section_enter(); auto val = pwmout_read_period_us(&_pwm); - core_util_critical_section_exit(); + //core_util_critical_section_exit(); return val; } void PwmOut::pulsewidth(float seconds) { - core_util_critical_section_enter(); + //core_util_critical_section_enter(); pwmout_pulsewidth(&_pwm, seconds); - core_util_critical_section_exit(); + //core_util_critical_section_exit(); } void PwmOut::pulsewidth_ms(int ms) { - core_util_critical_section_enter(); + //core_util_critical_section_enter(); pwmout_pulsewidth_ms(&_pwm, ms); - core_util_critical_section_exit(); + //core_util_critical_section_exit(); } void PwmOut::pulsewidth_us(int us) { - core_util_critical_section_enter(); + //core_util_critical_section_enter(); pwmout_pulsewidth_us(&_pwm, us); - core_util_critical_section_exit(); + //core_util_critical_section_exit(); } int PwmOut::read_pulsewidth_us() { - core_util_critical_section_enter(); + //core_util_critical_section_enter(); auto val = pwmout_read_pulsewidth_us(&_pwm); - core_util_critical_section_exit(); + //core_util_critical_section_exit(); return val; } @@ -135,24 +135,24 @@ int PwmOut::read_pulsewitdth_us() void PwmOut::suspend() { - core_util_critical_section_enter(); + //core_util_critical_section_enter(); if (_initialized) { _duty_cycle = PwmOut::read(); _period_us = PwmOut::read_period_us(); PwmOut::deinit(); } - core_util_critical_section_exit(); + //core_util_critical_section_exit(); } void PwmOut::resume() { - core_util_critical_section_enter(); + //core_util_critical_section_enter(); if (!_initialized) { PwmOut::init(); PwmOut::period_us(_period_us); PwmOut::write(_duty_cycle); } - core_util_critical_section_exit(); + //core_util_critical_section_exit(); } void PwmOut::lock_deep_sleep() @@ -183,7 +183,7 @@ void PwmOut::_call_pwmout_init(PwmOut *thisPtr) void PwmOut::init() { - core_util_critical_section_enter(); + //core_util_critical_section_enter(); if (!_initialized) { @@ -194,12 +194,12 @@ void PwmOut::init() _initialized = true; } - core_util_critical_section_exit(); + //core_util_critical_section_exit(); } void PwmOut::deinit() { - core_util_critical_section_enter(); + //core_util_critical_section_enter(); if (_initialized) { pwmout_free(&_pwm); @@ -207,7 +207,7 @@ void PwmOut::deinit() _initialized = false; } - core_util_critical_section_exit(); + //core_util_critical_section_exit(); } } // namespace mbed diff --git a/hal/include/hal/pwmout_api.h b/hal/include/hal/pwmout_api.h index dc8fd84182e..3747cb70615 100644 --- a/hal/include/hal/pwmout_api.h +++ b/hal/include/hal/pwmout_api.h @@ -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 @@ -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 */ diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS/PinNames.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS/PinNames.h index 7656b6870a7..367de8d7426 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS/PinNames.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS/PinNames.h @@ -143,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 diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c index f89eb0717e2..5aa99897214 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c @@ -271,7 +271,7 @@ const PinMap PinMap_SPI_SSEL[] = { // 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[] = { +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}, diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/analogin_api.c b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/analogin_api.c index b6765e60877..ba6934e16d7 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/analogin_api.c +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/analogin_api.c @@ -153,8 +153,9 @@ uint16_t analogin_read_u16(analogin_t *obj) float analogin_read(analogin_t *obj) { - /* Read the raw 12-Bit value from the ADC. */ - float analog_in_raw = (float)analogin_read_u16(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); } diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_pwm.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_pwm.h index 5d12c906161..1ea3ac14587 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_pwm.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_pwm.h @@ -101,6 +101,10 @@ struct pwmout_s // Number of counts that the PWM output will make before a new PWM cycle starts uint32_t top_count; + + // Number of counts that the PWM output will stay on for. + // Zero = full off, top_count = full on + uint32_t on_counts; }; #ifdef __cplusplus diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c index 7fc933602d1..72fbe091fa2 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c @@ -37,8 +37,8 @@ #endif struct pwm_clock_freq { - uint32_t clockSetting; - uint32_t frequency; + uint32_t clock_setting; + float frequency; }; // Table of options for PWM clock source vs clock frequency, in decreasing order of clock frequency @@ -50,27 +50,31 @@ struct pwm_clock_freq { // This means we have quite a wide range of base clock frequencies available, though period accuracy will be pretty // poor if the LFRC gets selected. #define NUM_CLOCK_SOURCE_OPTIONS 16 +#define HFRC_FREQ 48000000 +#define XT_FREQ 32768 +#define LFRC_FREQ 1024 static const struct pwm_clock_freq pwm_clock_sources[NUM_CLOCK_SOURCE_OPTIONS] = { - {AM_HAL_CTIMER_HFRC_12MHZ, 12000000}, - {AM_HAL_CTIMER_HFRC_3MHZ, 3000000}, - {AM_HAL_CTIMER_HFRC_187_5KHZ, 187500}, - {AM_HAL_CTIMER_HFRC_47KHZ, 46875}, - {AM_HAL_CTIMER_XT_32_768KHZ, 32768}, - {AM_HAL_CTIMER_XT_16_384KHZ, 16384}, - {AM_HAL_CTIMER_HFRC_12KHZ, 11719}, - {AM_HAL_CTIMER_XT_DIV4,8192}, - {AM_HAL_CTIMER_XT_DIV8,4096}, - {AM_HAL_CTIMER_XT_2_048KHZ,2048}, - {AM_HAL_CTIMER_XT_DIV32,1024}, + {AM_HAL_CTIMER_HFRC_12MHZ, HFRC_FREQ/4.0f}, + {AM_HAL_CTIMER_HFRC_3MHZ, HFRC_FREQ/16.0f}, + {AM_HAL_CTIMER_HFRC_187_5KHZ, HFRC_FREQ/256.0f}, + {AM_HAL_CTIMER_HFRC_47KHZ, HFRC_FREQ/1024.0f}, + {AM_HAL_CTIMER_XT_32_768KHZ, XT_FREQ}, + {AM_HAL_CTIMER_XT_16_384KHZ, XT_FREQ/2.0f}, + {AM_HAL_CTIMER_HFRC_12KHZ, HFRC_FREQ/4096.0f}, + {AM_HAL_CTIMER_XT_DIV4,XT_FREQ/4.0f}, + {AM_HAL_CTIMER_XT_DIV8,XT_FREQ/8.0f}, + {AM_HAL_CTIMER_XT_2_048KHZ, XT_FREQ/16.0f}, + {AM_HAL_CTIMER_XT_DIV32,XT_FREQ/32.0f}, // Note: NOT adding this one because the accuracy is bad - // {AM_HAL_CTIMER_LFRC_512HZ, 512}, + // {AM_HAL_CTIMER_LFRC_512HZ, LFRC_FREQ/2.0f}, - {AM_HAL_CTIMER_XT_256HZ, 256}, - {AM_HAL_CTIMER_LFRC_32HZ, 32}, - {AM_HAL_CTIMER_LFRC_1HZ, 1}, + {AM_HAL_CTIMER_XT_256HZ, XT_FREQ/128.0f}, + {AM_HAL_CTIMER_LFRC_32HZ, LFRC_FREQ/32.0f}, + {AM_HAL_CTIMER_LFRC_1HZ, LFRC_FREQ/1024.0f}, - // Note: there's also a 1/16Hz option but not adding it b/c the struct is using an integer for frequency + // Note: there may also be a 1/16Hz clock source option, but the SDK and datasheet seem + // to disagree about it. }; /// Largest top count value supported by hardware. Using this value will provide the highest duty cycle resolution. @@ -95,62 +99,58 @@ void pwmout_init(pwmout_t *obj, PinName pin) AM_HAL_GPIO_PIN_DRIVESTRENGTH_12MA); } -/** Deinitialize the pwmout object - * - * Parameter obj The pwmout object - */ void pwmout_free(pwmout_t *obj) { MBED_ASSERT(obj != NULL); - pwm_set_enabled(obj->slice, false); + am_hal_ctimer_stop(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), + APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name)); } -/** Set the output duty-cycle in range <0.0f, 1.0f> - * - * pulse 0.0f represents 0 percentage, 1.0f represents 100 percent. - * Parameter obj The pwmout object - * Parameter percent The floating-point percentage number - */ void pwmout_write(pwmout_t *obj, float percent) { - obj->percent = percent; + MBED_ASSERT(obj != NULL); + if (percent < 0 || percent > 1) { + MBED_ERROR(MBED_MAKE_ERROR(MBED_MODULE_DRIVER_PWM, MBED_ERROR_CODE_INVALID_ARGUMENT), "Invalid PWM duty cycle!"); + } - // Per datasheet section 4.5.2.2, a period value of top_count + 1 produces 100% duty cycle - int32_t new_reset_counts = lroundf((obj->top_count + 1) * percent); + // Calculate how many counts out of top_count we should be on + obj->on_counts = lroundf(percent * obj->top_count); - // Clamp to valid values - if(new_reset_counts > obj->top_count + 1) - { - new_reset_counts = obj->top_count + 1; + // Set new period value. Note that: + // - We have to set the top count and the on count at the same time + // - The HW adds 1 to the programmed values, so we have to subtract 1 when passing them in + if(APOLLO3_PWMNAME_GET_OUTPUT(obj->pwm_name) == AM_HAL_CTIMER_OUTPUT_NORMAL) { + am_hal_ctimer_period_set(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), + APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name), + obj->top_count - 1, + obj->on_counts - 1); } - else if(new_reset_counts < 0) - { - new_reset_counts = 0; + else { + am_hal_ctimer_aux_period_set(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), + APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name), + obj->top_count - 1, + obj->on_counts - 1); } #if APOLLO3_PWMOUT_DEBUG - printf("new_reset_counts: %" PRIu32 "\n", new_reset_counts); + printf("obj->on_counts: %" PRIu32 "\n", obj->on_counts); #endif - pwm_set_chan_level(obj->slice, obj->channel, new_reset_counts); - pwm_set_enabled(obj->slice, true); + // Start timer if not running + am_hal_ctimer_start(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), + APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name)); } -/** Read the current float-point output duty-cycle - * - * Parameter obj The pwmout object - * Return A floating-point output duty-cycle - */ float pwmout_read(pwmout_t *obj) { - /* Return percentage stored in object instead of calculating the value. - * This prevents floating point rounding errors. - */ - return obj->percent; + return ((float)obj->on_counts) / obj->top_count; } void pwmout_period(pwmout_t *obj, const float desired_period) { + MBED_ASSERT(obj != NULL); + const float old_duty_cycle = pwmout_read(obj); + // To find the period, we perform the following steps: // - Determine the fastest clock frequency that we can use while still hitting the needed period // - Calculate the correct top_count value that will produce as close to the desired period as possible @@ -159,7 +159,7 @@ void pwmout_period(pwmout_t *obj, const float desired_period) size_t clk_source_idx; bool found_clk_source = false; for(clk_source_idx = 0; clk_source_idx < NUM_CLOCK_SOURCE_OPTIONS; ++clk_source_idx) { - const float divider_max_period = MAX_TOP_COUNT / (float)pwm_clock_sources[clk_source_idx].frequency; + const float divider_max_period = MAX_TOP_COUNT / pwm_clock_sources[clk_source_idx].frequency; if(divider_max_period >= desired_period) { found_clk_source = true; break; @@ -170,7 +170,7 @@ void pwmout_period(pwmout_t *obj, const float desired_period) } // Now that we have found the best clock source, calculate top_count to hit the desired period - obj->clock_period = 1.0f / (float)pwm_clock_sources[clk_source_idx].frequency; + obj->clock_period = 1.0f / pwm_clock_sources[clk_source_idx].frequency; obj->top_count = lroundf(desired_period / obj->clock_period); // The hardware cannot support a top_count of less than 2. If that happened than it means the @@ -180,35 +180,19 @@ void pwmout_period(pwmout_t *obj, const float desired_period) } #if APOLLO3_PWMOUT_DEBUG - printf("clk_source_idx = %zu, obj->clock_period = %f, obj->top_count = %" PRIu32 "\n", + printf("clk_source_idx = %zu, obj->clock_period = %f ms, obj->top_count = %" PRIu32 "\n", clk_source_idx, - obj->clock_period, + obj->clock_period*1e3f, obj->top_count); #endif // Set new clock source. This stops the timer. am_hal_ctimer_config_single(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name), - AM_HAL_CTIMER_FN_PWM_REPEAT | clk_source_idx); + AM_HAL_CTIMER_FN_PWM_REPEAT | pwm_clock_sources[clk_source_idx].clock_setting); - // Set new period value. Note that: - // - We need to program a value of (top_count - 1) into the HW register - // - As part of setting the period we also need to program the duty cycle. For now we program it to zero, - // in anticipation of a future pwmout_write() call - if(APOLLO3_PWMNAME_GET_OUTPUT(obj->pwm_name) == AM_HAL_CTIMER_OUTPUT_NORMAL) { - am_hal_ctimer_period_set(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), - APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name), - obj->top_count - 1, - 0); - } - else { - am_hal_ctimer_aux_period_set(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), - APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name), - obj->top_count - 1, - 0); - } - - am_hal_ctimer_start(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name)); + // Set the old duty cycle, which also writes the period and starts the timer + pwmout_write(obj, old_duty_cycle); } void pwmout_period_ms(pwmout_t *obj, int period) @@ -225,41 +209,27 @@ void pwmout_period_us(pwmout_t *obj, int period) int pwmout_read_period_us(pwmout_t *obj) { + MBED_ASSERT(obj != NULL); return lroundf(1000000 * obj->top_count * obj->clock_period); } -/** Set the PWM pulsewidth specified in seconds, keeping the period the same. - * - * Parameter obj The pwmout object - * Parameter seconds The floating-point pulsewidth in seconds - */ void pwmout_pulsewidth(pwmout_t *obj, float pulse) { - pwmout_write(obj, pulse / obj->period); + pwmout_write(obj, pulse / (obj->top_count * obj->clock_period)); } -/** Set the PWM pulsewidth specified in miliseconds, keeping the period the same. - * - * Parameter obj The pwmout object - * Parameter ms The floating-point pulsewidth in miliseconds - */ void pwmout_pulsewidth_ms(pwmout_t *obj, int pulse) { - pwmout_write(obj, (pulse * .001f) / obj->period); + pwmout_write(obj, (pulse * .001f) / (obj->top_count * obj->clock_period)); } -/** Set the PWM pulsewidth specified in microseconds, keeping the period the same. - * - * Parameter obj The pwmout object - * Parameter us The floating-point pulsewidth in microseconds - */ void pwmout_pulsewidth_us(pwmout_t *obj, int pulse) { - pwmout_write(obj, (pulse * .000001f) / obj->period); + pwmout_write(obj, (pulse * .000001f) / (obj->top_count * obj->clock_period)); } int pwmout_read_pulsewidth_us(pwmout_t *obj) { - return lroundf(obj->period * obj->percent * 1000000); + return lroundf(obj->on_counts * obj->clock_period * 1e6f); } const PinMap *pwmout_pinmap() diff --git a/tools/cmake/mbed_python_interpreter.cmake b/tools/cmake/mbed_python_interpreter.cmake index 39ff80a110d..1d563c3ac28 100644 --- a/tools/cmake/mbed_python_interpreter.cmake +++ b/tools/cmake/mbed_python_interpreter.cmake @@ -38,7 +38,8 @@ if(MBED_CREATE_PYTHON_VENV) message(STATUS "Python venv deleted or unusable. Recreating using system Python...") # Launch a new search for Python3 - unset(Python3_EXECUTABLE) + unset(Python3_EXECUTABLE CACHE) + unset(Python_EXECUTABLE CACHE) unset(_Python3_EXECUTABLE CACHE) unset(_Python3_INTERPRETER_PROPERTIES CACHE) unset(_Python3_INTERPRETER_SIGNATURE CACHE) From 5e553e5eb94c40575f22cef423c9a1baa70f2868 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Sun, 20 Jul 2025 21:44:12 -0700 Subject: [PATCH 6/8] PWM passing tests! --- .../TARGET_Apollo3/device/objects_pwm.h | 6 ++ .../TARGET_Apollo3/device/pwmout_api.c | 74 ++++++++++++------- 2 files changed, 53 insertions(+), 27 deletions(-) diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_pwm.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_pwm.h index 1ea3ac14587..7a99aa1de91 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_pwm.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_pwm.h @@ -93,9 +93,15 @@ typedef enum { struct pwmout_s { + // Pin that the channel is on + PinName pin; + // PWM name that this channel is using PWMName pwm_name; + // True iff the pin is connected to the PWM timer + bool pin_is_connected_to_pwm; + // Clock period configured on this PWM, in floating point seconds float clock_period; diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c index 72fbe091fa2..b7c23df2c42 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c @@ -25,6 +25,7 @@ #include "mbed_assert.h" #include +#include #include // Change to 1 to enable debug prints of what's being calculated. @@ -89,14 +90,9 @@ void pwmout_init(pwmout_t *obj, PinName pin) const PWMName pwmName = pinmap_peripheral(pin, PinMap_PWM_OUT); /* Populate PWM object with values. */ + memset(obj, 0, sizeof(pwmout_t)); obj->pwm_name = pwmName; - - // Configure the pin - am_hal_ctimer_output_config(APOLLO3_PWMNAME_GET_CTIMER(pwmName), - APOLLO3_PWMNAME_GET_SEGMENT(pwmName), - pin, - APOLLO3_PWMNAME_GET_OUTPUT(pwmName), - AM_HAL_GPIO_PIN_DRIVESTRENGTH_12MA); + obj->pin = pin; } void pwmout_free(pwmout_t *obj) @@ -116,29 +112,53 @@ void pwmout_write(pwmout_t *obj, float percent) // Calculate how many counts out of top_count we should be on obj->on_counts = lroundf(percent * obj->top_count); - // Set new period value. Note that: - // - We have to set the top count and the on count at the same time - // - The HW adds 1 to the programmed values, so we have to subtract 1 when passing them in - if(APOLLO3_PWMNAME_GET_OUTPUT(obj->pwm_name) == AM_HAL_CTIMER_OUTPUT_NORMAL) { - am_hal_ctimer_period_set(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), - APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name), - obj->top_count - 1, - obj->on_counts - 1); - } - else { - am_hal_ctimer_aux_period_set(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), - APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name), - obj->top_count - 1, - obj->on_counts - 1); - } - #if APOLLO3_PWMOUT_DEBUG printf("obj->on_counts: %" PRIu32 "\n", obj->on_counts); #endif - // Start timer if not running - am_hal_ctimer_start(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), - APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name)); + // If we want 0% or 100% duty cycle, we need to do that by connecting the pin to forceed 0 or forced 1 + if (obj->on_counts == 0 || obj->on_counts == obj->top_count) { + am_hal_ctimer_stop(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), + APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name)); + am_hal_ctimer_output_config(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), + APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name), + obj->pin, + obj->on_counts == 0 ? AM_HAL_CTIMER_OUTPUT_FORCE0 : AM_HAL_CTIMER_OUTPUT_FORCE1, + AM_HAL_GPIO_PIN_DRIVESTRENGTH_12MA); + obj->pin_is_connected_to_pwm = false; + } + else { + + // If the pin is not connected to the PWM timer, set that up + if (!obj->pin_is_connected_to_pwm) { + am_hal_ctimer_output_config(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), + APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name), + obj->pin, + APOLLO3_PWMNAME_GET_OUTPUT(obj->pwm_name), + AM_HAL_GPIO_PIN_DRIVESTRENGTH_12MA); + obj->pin_is_connected_to_pwm = true; + } + + // Set new period value. Note that: + // - We have to set the top count and the on count at the same time + // - The HW adds 1 to the programmed values, so we have to subtract 1 when passing them in + if(APOLLO3_PWMNAME_GET_OUTPUT(obj->pwm_name) == AM_HAL_CTIMER_OUTPUT_NORMAL) { + am_hal_ctimer_period_set(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), + APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name), + obj->top_count - 1, + obj->on_counts - 1); + } + else { + am_hal_ctimer_aux_period_set(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), + APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name), + obj->top_count - 1, + obj->on_counts - 1); + } + + // Start timer if not running + am_hal_ctimer_start(APOLLO3_PWMNAME_GET_CTIMER(obj->pwm_name), + APOLLO3_PWMNAME_GET_SEGMENT(obj->pwm_name)); + } } float pwmout_read(pwmout_t *obj) @@ -210,7 +230,7 @@ void pwmout_period_us(pwmout_t *obj, int period) int pwmout_read_period_us(pwmout_t *obj) { MBED_ASSERT(obj != NULL); - return lroundf(1000000 * obj->top_count * obj->clock_period); + return lroundf(1e6f * obj->top_count * obj->clock_period); } void pwmout_pulsewidth(pwmout_t *obj, float pulse) From 95138e4d6a756c72a87cc87a6a87cae4b40d47eb Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Mon, 21 Jul 2025 08:03:17 -0700 Subject: [PATCH 7/8] Oops remove debug prints --- drivers/source/PwmOut.cpp | 56 +++++++++---------- .../TARGET_Apollo3/device/pwmout_api.c | 2 +- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/drivers/source/PwmOut.cpp b/drivers/source/PwmOut.cpp index f5b9a48e3e0..a956f22f7e6 100644 --- a/drivers/source/PwmOut.cpp +++ b/drivers/source/PwmOut.cpp @@ -57,74 +57,74 @@ PwmOut::~PwmOut() void PwmOut::write(float value) { - //core_util_critical_section_enter(); + core_util_critical_section_enter(); pwmout_write(&_pwm, value); - //core_util_critical_section_exit(); + core_util_critical_section_exit(); } float PwmOut::read() { - //core_util_critical_section_enter(); + core_util_critical_section_enter(); float val = pwmout_read(&_pwm); - //core_util_critical_section_exit(); + core_util_critical_section_exit(); return val; } void PwmOut::period(float seconds) { - //core_util_critical_section_enter(); + core_util_critical_section_enter(); pwmout_period(&_pwm, seconds); - //core_util_critical_section_exit(); + core_util_critical_section_exit(); } void PwmOut::period_ms(int ms) { - //core_util_critical_section_enter(); + core_util_critical_section_enter(); pwmout_period_ms(&_pwm, ms); - //core_util_critical_section_exit(); + core_util_critical_section_exit(); } void PwmOut::period_us(int us) { - //core_util_critical_section_enter(); + core_util_critical_section_enter(); pwmout_period_us(&_pwm, us); - //core_util_critical_section_exit(); + core_util_critical_section_exit(); } int PwmOut::read_period_us() { - //core_util_critical_section_enter(); + core_util_critical_section_enter(); auto val = pwmout_read_period_us(&_pwm); - //core_util_critical_section_exit(); + core_util_critical_section_exit(); return val; } void PwmOut::pulsewidth(float seconds) { - //core_util_critical_section_enter(); + core_util_critical_section_enter(); pwmout_pulsewidth(&_pwm, seconds); - //core_util_critical_section_exit(); + core_util_critical_section_exit(); } void PwmOut::pulsewidth_ms(int ms) { - //core_util_critical_section_enter(); + core_util_critical_section_enter(); pwmout_pulsewidth_ms(&_pwm, ms); - //core_util_critical_section_exit(); + core_util_critical_section_exit(); } void PwmOut::pulsewidth_us(int us) { - //core_util_critical_section_enter(); + core_util_critical_section_enter(); pwmout_pulsewidth_us(&_pwm, us); - //core_util_critical_section_exit(); + core_util_critical_section_exit(); } int PwmOut::read_pulsewidth_us() { - //core_util_critical_section_enter(); + core_util_critical_section_enter(); auto val = pwmout_read_pulsewidth_us(&_pwm); - //core_util_critical_section_exit(); + core_util_critical_section_exit(); return val; } @@ -135,24 +135,24 @@ int PwmOut::read_pulsewitdth_us() void PwmOut::suspend() { - //core_util_critical_section_enter(); + core_util_critical_section_enter(); if (_initialized) { _duty_cycle = PwmOut::read(); _period_us = PwmOut::read_period_us(); PwmOut::deinit(); } - //core_util_critical_section_exit(); + core_util_critical_section_exit(); } void PwmOut::resume() { - //core_util_critical_section_enter(); + core_util_critical_section_enter(); if (!_initialized) { PwmOut::init(); PwmOut::period_us(_period_us); PwmOut::write(_duty_cycle); } - //core_util_critical_section_exit(); + core_util_critical_section_exit(); } void PwmOut::lock_deep_sleep() @@ -183,7 +183,7 @@ void PwmOut::_call_pwmout_init(PwmOut *thisPtr) void PwmOut::init() { - //core_util_critical_section_enter(); + core_util_critical_section_enter(); if (!_initialized) { @@ -194,12 +194,12 @@ void PwmOut::init() _initialized = true; } - //core_util_critical_section_exit(); + core_util_critical_section_exit(); } void PwmOut::deinit() { - //core_util_critical_section_enter(); + core_util_critical_section_enter(); if (_initialized) { pwmout_free(&_pwm); @@ -207,7 +207,7 @@ void PwmOut::deinit() _initialized = false; } - //core_util_critical_section_exit(); + core_util_critical_section_exit(); } } // namespace mbed diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c index b7c23df2c42..0f6a081f19b 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c @@ -30,7 +30,7 @@ // Change to 1 to enable debug prints of what's being calculated. // Must comment out the critical section calls in PwmOut to use. -#define APOLLO3_PWMOUT_DEBUG 1 +#define APOLLO3_PWMOUT_DEBUG 0 #if APOLLO3_PWMOUT_DEBUG #include From 0af9486bbdb3c214063cd6beb321924b3a7ce4d6 Mon Sep 17 00:00:00 2001 From: Jamie Smith Date: Tue, 22 Jul 2025 08:40:42 -0700 Subject: [PATCH 8/8] A few fixes --- cmsis/device/rtos/include/mbed_boot.h | 2 ++ hal/include/hal/spi_api.h | 2 +- .../TARGET_Apollo3/CMakeLists.txt | 1 + .../TARGET_SFE_ARTEMIS/PinNames.h | 2 +- .../TARGET_SFE_ARTEMIS_ATP/PinNames.h | 2 +- .../TARGET_SFE_ARTEMIS_DK/PinNames.h | 2 +- .../TARGET_SFE_ARTEMIS_MODULE/PinNames.h | 2 +- .../TARGET_SFE_ARTEMIS_NANO/PinNames.h | 2 +- .../TARGET_SFE_ARTEMIS_THING_PLUS/PinNames.h | 2 +- .../TARGET_Apollo3/device/PeripheralPins.c | 16 +++++---- .../TARGET_Apollo3/device/analogin_api.c | 30 ++++++++--------- .../TARGET_Apollo3/device/mbed_overrides.c | 33 +++++++++++++++++++ .../TARGET_Apollo3/device/objects_adc.h | 5 +++ .../TARGET_Apollo3/device/pwmout_api.c | 2 +- .../{common => }/SFE_ARTEMIS_DK.cmake | 12 ++++--- .../common/sparkfun_artemis.cmake | 3 -- .../upload_methods/UploadMethodPYOCD.cmake | 5 ++- 17 files changed, 83 insertions(+), 40 deletions(-) create mode 100644 targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/mbed_overrides.c rename targets/upload_method_cfg/{common => }/SFE_ARTEMIS_DK.cmake (67%) diff --git a/cmsis/device/rtos/include/mbed_boot.h b/cmsis/device/rtos/include/mbed_boot.h index 6594512543b..af10f4d2ec2 100644 --- a/cmsis/device/rtos/include/mbed_boot.h +++ b/cmsis/device/rtos/include/mbed_boot.h @@ -19,6 +19,8 @@ #include "mbed_toolchain.h" +#include + #ifdef __cplusplus extern "C" { #endif diff --git a/hal/include/hal/spi_api.h b/hal/include/hal/spi_api.h index 2004d22ce68..5673cead694 100644 --- a/hal/include/hal/spi_api.h +++ b/hal/include/hal/spi_api.h @@ -280,7 +280,7 @@ int spi_master_write(spi_t *obj, int 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. diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/CMakeLists.txt b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/CMakeLists.txt index 57868eacdad..b4805c5b5e1 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/CMakeLists.txt +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/CMakeLists.txt @@ -52,6 +52,7 @@ target_sources(mbed-apollo3 device/itm_api.c device/analogin_api.c device/pwmout_api.c + device/mbed_overrides.c sdk/CMSIS/AmbiqMicro/Source/system_apollo3.c diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS/PinNames.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS/PinNames.h index 367de8d7426..de438a5649b 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS/PinNames.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS/PinNames.h @@ -129,7 +129,7 @@ typedef enum SERIAL1_RX = IO_25, // Not a real pin on the device, but can be passed to AnalogIn to read the internal temperature sensor - ADC_TEMP = 0x10000, + INT_TEMP_SENSOR = 0x10000, // Not connected NC = NC_VAL diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_ATP/PinNames.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_ATP/PinNames.h index d30facf2e04..c20bdeb8f5b 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_ATP/PinNames.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_ATP/PinNames.h @@ -165,7 +165,7 @@ typedef enum SERIAL1_RX = D25, // Not a real pin on the device, but can be passed to AnalogIn to read the internal temperature sensor - ADC_TEMP = 0x10000, + INT_TEMP_SENSOR = 0x10000, // Not connected NC = NC_VAL diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_DK/PinNames.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_DK/PinNames.h index d8dea0319dd..f12f31daefe 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_DK/PinNames.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_DK/PinNames.h @@ -135,7 +135,7 @@ typedef enum CONSOLE_RX = SERIAL_RX, // Not a real pin on the device, but can be passed to AnalogIn to read the internal temperature sensor - ADC_TEMP = 0x10000, + INT_TEMP_SENSOR = 0x10000, // Not connected NC = NC_VAL diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_MODULE/PinNames.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_MODULE/PinNames.h index 49a4edcca89..d8aa5301703 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_MODULE/PinNames.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_MODULE/PinNames.h @@ -98,7 +98,7 @@ typedef enum CONSOLE_RX = SERIAL_RX, // Not a real pin on the device, but can be passed to AnalogIn to read the internal temperature sensor - ADC_TEMP = 0x10000, + INT_TEMP_SENSOR = 0x10000, // Not connected NC = NC_VAL diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_NANO/PinNames.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_NANO/PinNames.h index b88b13a8479..18514381921 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_NANO/PinNames.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_NANO/PinNames.h @@ -131,7 +131,7 @@ typedef enum SERIAL1_RX = D10, // Not a real pin on the device, but can be passed to AnalogIn to read the internal temperature sensor - ADC_TEMP = 0x10000, + INT_TEMP_SENSOR = 0x10000, // Not connected NC = NC_VAL diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_THING_PLUS/PinNames.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_THING_PLUS/PinNames.h index 5ab8f63408c..40ca09b1295 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_THING_PLUS/PinNames.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/TARGET_SFE_ARTEMIS_THING_PLUS/PinNames.h @@ -138,7 +138,7 @@ typedef enum SERIAL1_RX = D0, // Not a real pin on the device, but can be passed to AnalogIn to read the internal temperature sensor - ADC_TEMP = 0x10000, + INT_TEMP_SENSOR = 0x10000, // Not connected NC = NC_VAL diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c index 5aa99897214..89781cb10cc 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/PeripheralPins.c @@ -37,7 +37,7 @@ const PinMap PinMap_ADC[] = { {33, ADC0_5, AM_HAL_PIN_33_ADCSE5}, {34, ADC0_6, AM_HAL_PIN_34_ADCSE6}, {35, ADC0_7, AM_HAL_PIN_35_ADCSE7}, - {ADC_TEMP, ADC0_TEMP, 0}, + {INT_TEMP_SENSOR, ADC0_TEMP, 0}, {NC, NC, 0} }; @@ -299,14 +299,16 @@ const PinMap PinMap_PWM_OUT[] = { {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}, - {IO_48, CTIMER_A7_OUT1, AM_HAL_PIN_48_CTIM28}, - {IO_49, CTIMER_B7_OUT1, AM_HAL_PIN_49_CTIM30}, - {IO_11, CTIMER_B7_OUT2, AM_HAL_PIN_11_CTIM31}, - // Note: These two are slightly different as they use PWM output selections 6 and 7 instead of 2. - // However, the AM HAL handles that distinction internally so we don't need to do anything different. + // 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}, - {IO_37, CTIMER_A7_OUT2, AM_HAL_PIN_37_CTIM29}, + + // 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} }; diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/analogin_api.c b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/analogin_api.c index ba6934e16d7..df123d7d4f2 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/analogin_api.c +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/analogin_api.c @@ -23,7 +23,7 @@ #include "am_hal_adc.h" -static void* g_ADCHandle; +void* am_adc_handle = NULL; // ADC constants #define ADC_RESOLUTION_SEL AM_HAL_ADC_SLOT_14BIT @@ -36,19 +36,19 @@ static uint32_t powerControlADC(bool on){ uint32_t status = AM_HAL_STATUS_SUCCESS; if(on){ - status = am_hal_adc_initialize(0, &g_ADCHandle); + status = am_hal_adc_initialize(0, &am_adc_handle); if(status != AM_HAL_STATUS_SUCCESS){ return status; } - status = am_hal_adc_power_control(g_ADCHandle, AM_HAL_SYSCTRL_WAKE, false); + 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(g_ADCHandle); + 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(g_ADCHandle); + status = am_hal_adc_deinitialize(am_adc_handle); if(status != AM_HAL_STATUS_SUCCESS){ return status; } } @@ -71,7 +71,7 @@ static uint32_t initializeADC( void ){ ADCConfig.ePowerMode = AM_HAL_ADC_LPMODE0; ADCConfig.eRepeat = AM_HAL_ADC_SINGLE_SCAN; - return am_hal_adc_configure(g_ADCHandle, &ADCConfig); + return am_hal_adc_configure(am_adc_handle, &ADCConfig); } @@ -110,11 +110,11 @@ static void ap3_config_channel(am_hal_adc_slot_chan_e channel){ ADCSlotConfig.bWindowCompare = false; ADCSlotConfig.bEnabled = true; - MBED_ASSERT(am_hal_adc_disable(g_ADCHandle) == AM_HAL_STATUS_SUCCESS); + MBED_ASSERT(am_hal_adc_disable(am_adc_handle) == AM_HAL_STATUS_SUCCESS); - MBED_ASSERT(am_hal_adc_configure_slot(g_ADCHandle, 0, &ADCSlotConfig) == 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(g_ADCHandle) == 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 @@ -125,20 +125,20 @@ static uint16_t readAnalogIn(analogin_t *obj) // Clear any set interrupt flags uint32_t intStatus; - am_hal_adc_interrupt_status(g_ADCHandle, &intStatus, false); - MBED_ASSERT(AM_HAL_STATUS_SUCCESS == am_hal_adc_interrupt_clear(g_ADCHandle, 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(g_ADCHandle); + 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(g_ADCHandle, &intStatus, false)); + 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(g_ADCHandle, intStatus)); + 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(g_ADCHandle, false, NULL, &numSamplesToRead, &sample)); + MBED_ASSERT(AM_HAL_STATUS_SUCCESS == am_hal_adc_samples_read(am_adc_handle, false, NULL, &numSamplesToRead, &sample)); return sample.ui32Sample; } diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/mbed_overrides.c b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/mbed_overrides.c new file mode 100644 index 00000000000..dabad60dea0 --- /dev/null +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/mbed_overrides.c @@ -0,0 +1,33 @@ +/* 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 + +#include + +// Needed for am_hal_clkgen.h +#include +#include + +#include + +void mbed_sdk_init(void) +{ + // Turn on frequency adjustment. This improves HFRC clock accuracy by a factor + // of 10 or more. + am_hal_clkgen_control(AM_HAL_CLKGEN_CONTROL_HFADJ_ENABLE, NULL); +} \ No newline at end of file diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_adc.h b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_adc.h index 60e7a4d875b..fd29e9af016 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_adc.h +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/objects_adc.h @@ -45,6 +45,11 @@ struct analogin_s am_hal_adc_slot_chan_e slot; }; +// Opaque handle to the ADC peripheral. This is needed to +// use certain AM HAL calls from user code. This will be initialized +// the first time that any AnalogIn is initialized. +extern void* am_adc_handle; + #ifdef __cplusplus } #endif diff --git a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c index 0f6a081f19b..446ad106b8e 100644 --- a/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c +++ b/targets/TARGET_Ambiq_Micro/TARGET_Apollo3/device/pwmout_api.c @@ -45,7 +45,7 @@ struct pwm_clock_freq { // Table of options for PWM clock source vs clock frequency, in decreasing order of clock frequency // Note that the Apollo3 uses a fixed external oscillator frequency, so this is possible to define statically. // There are three oscillators available, each of which can be used for PWM: -// - HFRC - internal high freq RC oscillator, 48MHz +-3.5% +// - HFRC - internal high freq RC oscillator, 48MHz +-3.5% uncalibrated, but better with auto-calibration. // - XT - external crystal, 32.768kHz, likely 50ppm or better tolerance // - LFRC - internal low freq RC oscillator, 1.024kHz +-32% (no that's not a typo!) // This means we have quite a wide range of base clock frequencies available, though period accuracy will be pretty diff --git a/targets/upload_method_cfg/common/SFE_ARTEMIS_DK.cmake b/targets/upload_method_cfg/SFE_ARTEMIS_DK.cmake similarity index 67% rename from targets/upload_method_cfg/common/SFE_ARTEMIS_DK.cmake rename to targets/upload_method_cfg/SFE_ARTEMIS_DK.cmake index 88f964402c7..00d944661e6 100644 --- a/targets/upload_method_cfg/common/SFE_ARTEMIS_DK.cmake +++ b/targets/upload_method_cfg/SFE_ARTEMIS_DK.cmake @@ -3,10 +3,7 @@ # include mbed_toolchain_setup and where you add mbed-os as a subdirectory. # Notes: -# 1. Support for this device exists in PyOCD main branch but has not been released yet (as of Jun 2025). -# This version will be used automatically by Mbed if the python venv is enabled. If not, you need to install it via: -# pip install git+https://github.com/pyocd/pyOCD.git -# 2. Unlike all other SparkFun Artemis boards, this board has a CMSIS-DAP interface MCU on it, so it +# 1. Unlike all other SparkFun Artemis boards, this board has a CMSIS-DAP interface MCU on it, so it # should be debuggable & flashable out of the box via PyOCD. set(UPLOAD_METHOD_DEFAULT MBED) @@ -17,6 +14,13 @@ set(PYOCD_UPLOAD_ENABLED TRUE) set(PYOCD_TARGET_NAME ama3b1kk_kbr) set(PYOCD_CLOCK_SPEED 4000k) +# Config options for JLINK +# ------------------------------------------------------------- +set(JLINK_UPLOAD_ENABLED TRUE) +set(JLINK_CPU_NAME AMA3B1KK-KBR) +set(JLINK_CLOCK_SPEED 4000) +set(JLINK_UPLOAD_INTERFACE SWD) + # Config options for MBED # ------------------------------------------------------------- set(MBED_UPLOAD_ENABLED TRUE) \ No newline at end of file diff --git a/targets/upload_method_cfg/common/sparkfun_artemis.cmake b/targets/upload_method_cfg/common/sparkfun_artemis.cmake index 4f81da3c820..3ee970b5805 100644 --- a/targets/upload_method_cfg/common/sparkfun_artemis.cmake +++ b/targets/upload_method_cfg/common/sparkfun_artemis.cmake @@ -5,9 +5,6 @@ # Notes: # 1. This board does not have an onboard debugger. You must use an external debugger, e.g. a PicoProbe # or J-Link, if you wish to debug code. -# 2. Support for this device exists in PyOCD main branch but has not been released yet (as of Jun 2025). -# This version will be used automatically by Mbed if the python venv is enabled. If not, you need to install it via: -# pip install git+https://github.com/pyocd/pyOCD.git set(UPLOAD_METHOD_DEFAULT AMBIQ_SVL) diff --git a/tools/cmake/upload_methods/UploadMethodPYOCD.cmake b/tools/cmake/upload_methods/UploadMethodPYOCD.cmake index 04949ba2674..b6ca4ec8945 100644 --- a/tools/cmake/upload_methods/UploadMethodPYOCD.cmake +++ b/tools/cmake/upload_methods/UploadMethodPYOCD.cmake @@ -11,9 +11,8 @@ set(UPLOAD_SUPPORTS_DEBUG TRUE) ### Find PyOCD package -# Use the Git version so that we get Ambiq Apollo3 support (as that was not included in the latest release -# before PyOCD development stopped, as of Jun 2025) -mbed_check_or_install_python_package(HAVE_PYTHON_PYOCD pyocd git+https://github.com/pyocd/pyOCD.git) +# Use >=0.37 so that we get Ambiq Apollo3 support +mbed_check_or_install_python_package(HAVE_PYTHON_PYOCD pyocd pyocd>=0.37) set(UPLOAD_PYOCD_FOUND ${HAVE_PYTHON_PYOCD})