Silicon Labs Wireless Gecko Products offer user-friendly software timers that are suitable for most of the tasks. Despite well performing software timers there are tasks that require hardware timers. Hardware timers offer faster response time than software timers and thus they suit well for example Pulse-width modulation (PWM). This Knowledge Base article explains how to create a simple application that makes use of PWM functionality. Example application includes BLE service that is used to control the PWM functionality remotely.
Implementation
1. Creating the project.
In Simplicity Studio, create SOC – Empty example project for your device.
Import the attached gatt.xml file and generate. Open the .isc file, click on custom BLE GATT and click import from the tools section on the right. This will overwrite the existing GATT. Click generate button on the top and accept any pop-ups. We have changed the name of the device to PWM_Example and created a characteristic to control the behavior of the on-board LEDs as shown here.
New service and characteristic
Light_PWM_Control service
Control Characteristic
2. Overwrite the attached app.c in the project folder. In app.c, we have included Timer and GPIO libraries. These libraries are required for PWM signal.
#include "em_timer.h"
#include "em_gpio.h"
#include "em_cmu.h"
// this PWM_FREQ = 65000 creates about 1kHz signal.
#define PWM_FREQ 65000
3. Hardware timer uses the High Frequency oscillator which will be disabled in the sleep mode. To ensure that timer works, make sure that sleep is disabled. Disable sleep by setting DISABLE_SLEEP to 1 in app.h
/* Set this value to 1 if you want to disable deep sleep completely */
#define DISABLE_SLEEP 1
4. We initialize pins and enable clocks. In the example, WSTK LEDs are used.
/* Enable clock for GPIO module */
CMU_ClockEnable(cmuClock_GPIO, true);
/* Enable clock for TIMER0 module */
CMU_ClockEnable(cmuClock_TIMER0, true);
/* Initialize pins used for PWM */
GPIO_PinModeSet(BSP_GPIO_LED0_PORT, BSP_GPIO_LED0_PIN, gpioModePushPull, 0);
GPIO_PinModeSet(BSP_GPIO_LED1_PORT, BSP_GPIO_LED1_PIN, gpioModePushPull, 0);
5. Then we route timer channels to LED pins.
Keep in mind that routing pins are board dependent. For different platforms, the LOC30 parameter may differ. Please refer to the datasheet of your platform for details. This information can be found under "Alternate functionality overview" section of the datasheet. Look for rows starting with TIM0_CC0 and TIM0_CC1. Check the number associated with the LED port and pin. For example in BG1, port PF6 and PF7 are associated with number 30 and 30 respectively in timers TIM0_CC0 and TIM0_CC1.
/* Route pins to timer */
// $[TIMER0 I/O setup]
/* Set up CC0 */
TIMER0->ROUTELOC0 = (TIMER0->ROUTELOC0 & (~_TIMER_ROUTELOC0_CC0LOC_MASK))
| TIMER_ROUTELOC0_CC0LOC_LOC30;
TIMER0->ROUTEPEN = TIMER0->ROUTEPEN | TIMER_ROUTEPEN_CC0PEN;
/* Set up CC1 */
TIMER0->ROUTELOC0 = (TIMER0->ROUTELOC0 & (~_TIMER_ROUTELOC0_CC1LOC_MASK))
| TIMER_ROUTELOC0_CC1LOC_LOC30;
TIMER0->ROUTEPEN = TIMER0->ROUTEPEN | TIMER_ROUTEPEN_CC1PEN;
// [TIMER0 I/O setup]$
6. The timer properties and initializing is done in this section.
/* Select CC channel parameters */
TIMER_InitCC_TypeDef timerCCInit =
{
.eventCtrl = timerEventEveryEdge,
.edge = timerEdgeBoth,
.prsSel = timerPRSSELCh0,
.cufoa = timerOutputActionNone,
.cofoa = timerOutputActionNone,
.cmoa = timerOutputActionToggle,
.mode = timerCCModePWM,
.filter = false,
.prsInput = false,
.coist = false,
.outInvert = false,
};
/* Configure CC channel 0 */
TIMER_InitCC(TIMER0, 0, &timerCCInit);
/* Configure CC channel 1 */
TIMER_InitCC(TIMER0, 1, &timerCCInit);
/* Set Top Value */
TIMER_TopSet(TIMER0, CMU_ClockFreqGet(cmuClock_HFPER)/PWM_FREQ);
/* Set compare value starting at 0 - it will be incremented in the interrupt handler */
TIMER_CompareBufSet(TIMER0, 0, 0);
/* Set compare value starting at top value - it will be decremented in the interrupt handler */
TIMER_CompareBufSet(TIMER0, 1, TIMER_TopGet(TIMER0));
/* Select timer parameters */
TIMER_Init_TypeDef timerInit =
{
.enable = true,
.debugRun = true,
.prescale = timerPrescale64,
.clkSel = timerClkSelHFPerClk,
.fallAction = timerInputActionNone,
.riseAction = timerInputActionNone,
.mode = timerModeUp,
.dmaClrAct = false,
.quadModeX4 = false,
.oneShot = false,
.sync = false,
};
/* Enable overflow interrupt */
TIMER_IntEnable(TIMER0, TIMER_IF_OF);
/* Enable TIMER0 interrupt vector in NVIC */
NVIC_EnableIRQ(TIMER0_IRQn);
/* Configure timer */
TIMER_Init(TIMER0, &timerInit);
7. For the handler function in this example, there is PWM function which has dimming and breathing modes, depending on pwm_mode parameter (0-100 dimming and 101-255 constant breathing).
/* Define PWM mode. The light is constantly changing or staying constant. Value 1-100 defines constant
brightness and the value define duty-cycle. Values 101-255 sets light to constantly changing brightness
which makes breathing effect. */
uint16_t pwm_mode = 200;
/**************************************************************************//**
* @brief TIMER0_IRQHandler
* Interrupt Service Routine TIMER0 Interrupt Line
* This function is used to configures PWM modes.
*****************************************************************************/
void TIMER0_IRQHandler(void)
{
uint32_t compareValue;
/* Clear flag for TIMER0 overflow interrupt */
TIMER_IntClear(TIMER0, TIMER_IF_OF);
compareValue = TIMER_CaptureGet(TIMER0, 0);
if (pwm_mode > 100){
/* increment duty-cycle or reset if reached TOP value */
if( compareValue == TIMER_TopGet(TIMER0))
TIMER_CompareBufSet(TIMER0, 0, 0);
else
TIMER_CompareBufSet(TIMER0, 0, ++compareValue);
compareValue = TIMER_CaptureGet(TIMER0, 1);
/* decrement duty-cycle or reset if reached MIN value */
if( compareValue == 0)
TIMER_CompareBufSet(TIMER0, 1, TIMER_TopGet(TIMER0));
else
TIMER_CompareBufSet(TIMER0, 1, --compareValue);
}
/* sets the given duty-cycle value to the timer */
else if ( pwm_mode >= 0 && pwm_mode <= 100)
{
// IF YOU USE BGM121 change 1-0.01*pwm_mode to pwm_mode/100.
TIMER_CompareBufSet(TIMER0, 1, TIMER_TopGet(TIMER0)*(1-0.01*pwm_mode));
TIMER_CompareBufSet(TIMER0, 0, TIMER_TopGet(TIMER0)*(1-0.01*pwm_mode));
}
}
8. And finally, the case which configures the pwm_mode variable.
I try to put it with BGM121 (BRD4302A), but the PWM don't work.
I haven't complilation error.
What could be the problem ?
0
Hi,
I found the issue. BGM121 and BGM111 uses different pin to control leds found in the WSTK and also different location for timers need to be set. I make some testing and return shortly with repaired code.
0
I made some minor modifications to the article. In your case you need 3 small corrections.
1. Add this line.
#define PWM_FREQ 65000
2. Change locations to LOC28 as I have done below.
/* Set up CC0 */
TIMER0->ROUTELOC0 = (TIMER0->ROUTELOC0 & (~_TIMER_ROUTELOC0_CC0LOC_MASK))
| TIMER_ROUTELOC0_CC0LOC_LOC28;
TIMER0->ROUTEPEN = TIMER0->ROUTEPEN | TIMER_ROUTEPEN_CC0PEN;
/* Set up CC1 */
TIMER0->ROUTELOC0 = (TIMER0->ROUTELOC0 & (~_TIMER_ROUTELOC0_CC1LOC_MASK))
| TIMER_ROUTELOC0_CC1LOC_LOC28;
TIMER0->ROUTEPEN = TIMER0->ROUTEPEN | TIMER_ROUTEPEN_CC1PEN;
// [TIMER0 I/O setup]$
3. Modify these two lines in the TIMER0_IRQHandler.
But now, I haven't the same result on the LED if I put a pwm_mode between 0 and 100.
If I put pwm_mode = 200, it's OK.
Why the timer are different in BGM111 and BGM121 ? it isn't the same EFR32BG inside ?
0
Can you explain me what you mean with this "But now, I haven't the same result on the LED if I put a pwm_mode between 0 and 100."? How does it work with the pwm_mode values between 0-100. Value is supposed to decide brightness level in that led.
There are minor differences between BGM121 and BGM111. Example the pins used to for WSTK leds are different.
0
With BGM111, if I put pwm_mode between 0 and 100, it's proportionnal to the led brightness.
With the BGM121, the brightness don't change, it's always at the top.
0
I posted modifications too soon as I forgot to scale the pwm_mode variable, I am sorry about that. I modified my comment and document and now it works.
0
OK, thanks.
I have just replace the /100 by *0.01 and it's work fine.
0
You can upload the .zip for the BGM121 with Blue Gecko, it still does not work for me. Thank you
0
Hello,
This code work with BGM121 :
/**************************************************************************//** * @brief TIMER0_IRQHandler * Interrupt Service Routine TIMER0 Interrupt Line * This function is used to configures PWM modes. *****************************************************************************/ void TIMER0_IRQHandler(void) { uint32_t compareValue;
/* Clear flag for TIMER0 overflow interrupt */ TIMER_IntClear(TIMER0, TIMER_IF_OF);
compareValue = TIMER_CaptureGet(TIMER0, 0);
if (pwm_mode > 100){
/* increment duty-cycle or reset if reached TOP value */ if( compareValue == TIMER_TopGet(TIMER0)) TIMER_CompareBufSet(TIMER0, 0, 0); else TIMER_CompareBufSet(TIMER0, 0, ++compareValue);
compareValue = TIMER_CaptureGet(TIMER0, 1); /* decrement duty-cycle or reset if reached MIN value */ if( compareValue == 0) TIMER_CompareBufSet(TIMER0, 1, TIMER_TopGet(TIMER0)); else TIMER_CompareBufSet(TIMER0, 1, --compareValue); } /* sets the given duty-cycle value to the timer */ else if ( pwm_mode >= 0 && pwm_mode <= 100) { TIMER_CompareBufSet(TIMER0, 1, TIMER_TopGet(TIMER0)*(pwm_mode*0.01)); TIMER_CompareBufSet(TIMER0, 0, TIMER_TopGet(TIMER0)*(pwm_mode*0.01)); } }
KBA_BT_1004: Hardware Timers with EFR32xG1x
Introduction
Silicon Labs Wireless Gecko Products offer user-friendly software timers that are suitable for most of the tasks. Despite well performing software timers there are tasks that require hardware timers. Hardware timers offer faster response time than software timers and thus they suit well for example Pulse-width modulation (PWM). This Knowledge Base article explains how to create a simple application that makes use of PWM functionality. Example application includes BLE service that is used to control the PWM functionality remotely.
Implementation
1. Creating the project.
In Simplicity Studio, create SOC – Empty example project for your device.
Import the attached gatt.xml file and generate. Open the .isc file, click on custom BLE GATT and click import from the tools section on the right. This will overwrite the existing GATT. Click generate button on the top and accept any pop-ups. We have changed the name of the device to PWM_Example and created a characteristic to control the behavior of the on-board LEDs as shown here.
New service and characteristic
Light_PWM_Control service
Control Characteristic
2. Overwrite the attached app.c in the project folder. In app.c, we have included Timer and GPIO libraries. These libraries are required for PWM signal.
3. Hardware timer uses the High Frequency oscillator which will be disabled in the sleep mode. To ensure that timer works, make sure that sleep is disabled. Disable sleep by setting DISABLE_SLEEP to 1 in app.h
4. We initialize pins and enable clocks. In the example, WSTK LEDs are used.
5. Then we route timer channels to LED pins.
Keep in mind that routing pins are board dependent. For different platforms, the LOC30 parameter may differ. Please refer to the datasheet of your platform for details. This information can be found under "Alternate functionality overview" section of the datasheet. Look for rows starting with TIM0_CC0 and TIM0_CC1. Check the number associated with the LED port and pin. For example in BG1, port PF6 and PF7 are associated with number 30 and 30 respectively in timers TIM0_CC0 and TIM0_CC1.
6. The timer properties and initializing is done in this section.
7. For the handler function in this example, there is PWM function which has dimming and breathing modes, depending on pwm_mode parameter (0-100 dimming and 101-255 constant breathing).
8. And finally, the case which configures the pwm_mode variable.
9. Build and flash the app to your preferred device.
Running the Example
Open the Blue Gecko app on your mobile, then open the Bluetooth Browser tab and connect to the device, the name will display as “PWM_Exam”.
Change the decimal value between 0-255 and observe the results with on-board LEDs. You can modify the pins for you custom design.
Additional Reading
For more information on the timers please see https://www.silabs.com/documents/public/application-notes/AN0014.pdf.
Hello,
I try to put it with BGM121 (BRD4302A), but the PWM don't work.
I haven't complilation error.
What could be the problem ?
Hi,
I found the issue. BGM121 and BGM111 uses different pin to control leds found in the WSTK and also different location for timers need to be set. I make some testing and return shortly with repaired code.
I made some minor modifications to the article. In your case you need 3 small corrections.
1. Add this line.
2. Change locations to LOC28 as I have done below.
3. Modify these two lines in the TIMER0_IRQHandler.
To these
I understand to change pin locate.
But now, I haven't the same result on the LED if I put a pwm_mode between 0 and 100.
If I put pwm_mode = 200, it's OK.
Why the timer are different in BGM111 and BGM121 ? it isn't the same EFR32BG inside ?
Can you explain me what you mean with this "But now, I haven't the same result on the LED if I put a pwm_mode between 0 and 100."? How does it work with the pwm_mode values between 0-100. Value is supposed to decide brightness level in that led.
There are minor differences between BGM121 and BGM111. Example the pins used to for WSTK leds are different.
With BGM111, if I put pwm_mode between 0 and 100, it's proportionnal to the led brightness.
With the BGM121, the brightness don't change, it's always at the top.
I posted modifications too soon as I forgot to scale the pwm_mode variable, I am sorry about that. I modified my comment and document and now it works.
OK, thanks.
I have just replace the /100 by *0.01 and it's work fine.
You can upload the .zip for the BGM121 with Blue Gecko, it still does not work for me.
Thank you
Hello,
This code work with BGM121 :
/**************************************************************************//**
* @brief TIMER0_IRQHandler
* Interrupt Service Routine TIMER0 Interrupt Line
* This function is used to configures PWM modes.
*****************************************************************************/
void TIMER0_IRQHandler(void)
{
uint32_t compareValue;
/* Clear flag for TIMER0 overflow interrupt */
TIMER_IntClear(TIMER0, TIMER_IF_OF);
compareValue = TIMER_CaptureGet(TIMER0, 0);
if (pwm_mode > 100){
/* increment duty-cycle or reset if reached TOP value */
if( compareValue == TIMER_TopGet(TIMER0))
TIMER_CompareBufSet(TIMER0, 0, 0);
else
TIMER_CompareBufSet(TIMER0, 0, ++compareValue);
compareValue = TIMER_CaptureGet(TIMER0, 1);
/* decrement duty-cycle or reset if reached MIN value */
if( compareValue == 0)
TIMER_CompareBufSet(TIMER0, 1, TIMER_TopGet(TIMER0));
else
TIMER_CompareBufSet(TIMER0, 1, --compareValue);
}
/* sets the given duty-cycle value to the timer */
else if ( pwm_mode >= 0 && pwm_mode <= 100)
{
TIMER_CompareBufSet(TIMER0, 1, TIMER_TopGet(TIMER0)*(pwm_mode*0.01));
TIMER_CompareBufSet(TIMER0, 0, TIMER_TopGet(TIMER0)*(pwm_mode*0.01));
}
}
and in the main function, replace by this :
TIMER0->ROUTELOC0 = (TIMER0->ROUTELOC0 & (~_TIMER_ROUTELOC0_CC0LOC_MASK))
| TIMER_ROUTELOC0_CC0LOC_LOC28; // p104 datasheet EFR32BG1
TIMER0->ROUTEPEN = TIMER0->ROUTEPEN | TIMER_ROUTEPEN_CC0PEN;
/* Set up CC1 */
TIMER0->ROUTELOC0 = (TIMER0->ROUTELOC0 & (~_TIMER_ROUTELOC0_CC1LOC_MASK))
| TIMER_ROUTELOC0_CC1LOC_LOC28;
TIMER0->ROUTEPEN = TIMER0->ROUTEPEN | TIMER_ROUTEPEN_CC1PEN;