A Pulse Width Modelation (PWM) has a duty and a cycle. The duty can be something between 0% and 100% of the cycle time.
For Ambiq Micro the CTIMER peripheral can be used to generate a PWM.
First of all the GPIOs have to be set, see also Using GPIOs for more details.
The resulting code for the initialization of GPIO43 / PAD43 as PWM of timer TCTB0:
GPIO->PADKEY = 0x00000073; //unlock pin selection
GPIO->PADREGK_b.PAD43FNCSEL = 2; //set TCTB0 on pin 43
GPIO->PADREGK_b.PAD43INPEN = 1; //enable input on pin 43
GPIO->CFGF_b.GPIO43OUTCFG = 1; //output is push-pull
GPIO->PADKEY = 0; //lock pin selection
Next the CTIMER must be initialized. The CTIMER consists of several timers in one module. Following schematic can be found in the datasheet:
In general the maximum amount of resulting PWMs are 8, located to the output functionalies TCTA0, TCTB0, TCTA1, TCTB1, TCTA2, TCTA2, TCTA3 and TCTB3.
In the example TCTB0 at GPIO43 / PAD43 will be used.
First of all the timer must be cleared:
CTIMER->CTRL0_b.TMRB0CLR = 1; //clear timer B
Now different features can be set:
CTIMER->CTRL0_b.TMRB0PE = 1; //enable output timer B
CTIMER->CTRL0_b.TMRB0IE = 1; //enable IRQ timer B
CTIMER->CTRL0_b.TMRB0FN = 3; //repeated pulse count timer B
CTIMER->CTRL0_b.TMRB0CLK = 0x07; //XT div 2 == 48KHz timer B
In general the cycle time is related to the selected clock and divisor in the TMR<A/B><n>CLK field. In the case of XT div 2 the clock source is 48KHz. The cycle value defines the maximum value of the duty value and also the frequency. The frequency is f = fin / cycle_value. If the cycle value is 64, and fin 48KHz, the resulting frequency is 750Hz.
Now the cycle register and the duty register will be set:
CTIMER->CMPRB0_b.CMPR0B0 = 1; //periode – on-time timer B
CTIMER->CMPRB0_b.CMPR1B0 = 64; //on-time timer B
For Apollo it makes sense to update the duty value in a IRQ rouine, so IRQs must be enabled. For this all IRQ flags must be cleared and the interrupt for the compare value 0 must be enabled:
CTIMER->INTEN = 0;
CTIMER->INTEN_b.CTMRB0INT = 1; //enable interrupt for compare value 0
Now the NVIC must be configured to use IRQs for the corresponding CTIMER as well. The NVIC is part of CMSIS and is decribed at the CMSIS website: https://www.keil.com/pack/doc/CMSIS/Core/html/group__NVIC__gr.html
The initialization looks like this:
NVIC_ClearPendingIRQ(CTIMER_IRQn); //clear pending flag for ADC
NVIC_EnableIRQ(CTIMER_IRQn); //enable IRQ
NVIC_SetPriority(CTIMER_IRQn,1); //set priority of ADC IRQ, smaller value means higher priority
At last the timer must be released and started:
CTIMER->CTRL0_b.TMRB0CLR = 0; //release clear timer B
CTIMER->CTRL0_b.TMRB0EN = 1; //start timer B
Interrupt Service Routine
First a global variable should be defined:
volatile uint32_t u32Duty = 1;
The interrupt service routine is using this variable to update the duty value with the correct timing:
if (CTIMER->INTSTAT_b.CTMRB0INT == 1)
CTIMER->INTCLR_b.CTMRB0INT = 1; //cear interrupt flag
CTIMER->CMPRB0_b.CMPR0B0 = u32Duty; //set new duty value
The main application can now write the duty value into the variable and the ISR will update the HW automatically:
for(int i = 1;i < 63;i++)
u32Duty = i;
SCB->SCR |= (1 << SCB_SCR_SLEEPDEEP_Pos); //set goto deepsleep (only possible if TPIU or SWO is not enabled
__WFI(); //wait for interrupt
Run the code
See also MCU Templates
The Apollo1 and Apollo2 MCUs and EVKs are available at the webshop of Fujitsu Electronics Europe GmbH (FEEU):