This discussion will guide you through ARM Cortex-M3 LPC1768 PWM programming Tutorial. It is also applicable for LPC1769. I’ll be covering Single Edge PWM along with examples like how to use to control Motors like Servos and dimming an LED. I assume you are comfortable with Timers and its usage in ARM Cortex-M3 based LPC176x micro-controllers since PWM block is basically a Timer.
Pulse Width Modulation Basics:
I’ve have posted a Beginner PWM tutorial. If you are new to PWM please have a look there. A Diagram that sums it up is given below. It shows Single Edge PWM with T-ON, T-OFF & Period. Duty Cycle is simply T-ON Divided by Period.
ARM Cortex-M3 LPC1768 PWM Module
These MCUs Contains a single PWM Module called PWM1 and supports 2 types of PWM:
1) Single Edge PWM – Pulse starts with new Period i.e Pulse is always at the beginning
2) Double Edge PWM – Pulse can be present anywhere within the Period
A PWM block, similar to a Timer block, has a Timer Counter and an associated Prescale Register along with Match Registers. These work exactly the same way as in the case of Timers. I’ve explained them in the introduction part of the LPC1768 Timer tutorial . Match Registers 1 to 6 (except 0) are pinned on LPC176x i.e. the corresponding outputs are given to actual Pins on LPC176x MCU. The PWM function must be selected for these Pins, using PINSELx Register, to get the PWM output. These pins are:
Output | PWM1.1 | PWM1.2 | PWM1.3 | PWM1.4 | PWM1.5 | PWM1.6 |
Pin Name | P1.18 P2.0 |
P1.20 P2.1 P3.25 |
P1.21 P2.2 P3.26 |
P1.23 P2.3 |
P1.24 P2.4 |
P1.26 P2.5 |
There are 7 match registers inside the PWM1 block. The first Match register PWM1MR0 is used to generate PWM period and hence we are left with 6 Match Registers PWM1MR1 to PWM1MR6 to generate 6 Single Edge PWM signals or 3 Double Edge PWM signals. Double edge PWM uses 2 match registers hence we can get only 3 double edge outputs.
- 1) All single edged PWM outputs will go high at the beginning of a PWM cycle unless their match value is 0.
- 2) Each PWM output will go low when its match value is reached. Note: If no match occurs i.e Match value is greater than Period then the output will remain high!
Consider that our PWM Period duration is 5 milliseconds and TC increments every 1 millisecond using appropriate prescale value. We set PWM1MR0 to 5 i.e. 5 ticks of PWM TC. We have PWM1MR1 = 2 and PWM1MR2 = 4. Whenever the value in TC matches the value in any of the match register, its corresponding output it set to low until the start of next PWM cycle as shown in PWM Timing Diagram below:
For Double Edge Rules you can refer the User Manual Pg. 524.
Registers used in LPC176x PWM Programming
Now lets have a look at some important Registers:
- Bit 0 : When 1 both PWM Timer counter and PWM Prescale counter are enabled. When 0 both are disabled.
- Bit 1 : When set to 1 it will reset Reset both Timer and Prescale counter of PWM block (at next edge of PCLK).
- Bit 3 : Enables PWM mode i.e the PWM outputs.
- Other Bits : Reserved.
2) PWM1PR – PWM Prescale Register: PWMPR is used to control the resolution of the PWM outputs. The Timer Counter(TC) will increment every PWMPR+1 Peripheral Clock Cycles (PCLK).
3) PWM1MR0 – PWM1MR6 (Match Registers): These are the seven Match registers as explained above which contain Pulse Width Values i.e the Number of PWM1TC Ticks.
4) PWM1MCR – PWM Match Control Registers: The PWM Match Control Register is used to specify what operations can be done when the value in a particular Match register equals the value in TC. For each Match Register we have 3 options : Either generate an Interrupt , or Reset the TC , or Stop .. which stops the counters and disables PWM. Hence this register is divided into group of 3 bits. The first 3 bits are for Match Register 0 i.e PWMMR0 , next 3 for PWMMR1 , and so on.
- 1) Bit 0 : Interrupt on PWM1MR0 Match – If set to 1 then it will generate an Interrupt else disable if set to 0.
- 2) Bit 1 : Reset on PWM1MR0 Match – If set to 1 it will reset the PWM Timer Counter i.e. PWM1TC else disabled if set to 0.
- 3) Bit 2 : Stop on PWM1MR0 Match – If this bit is set 1 then both PWM1TC and PWM1PC will be stopped & disable the Counters.
- *) Similarly {Bits 3,4,5} for PWM1MR1 , {Bits 6,7,8} for PWM1MR2 , {Bits 9,10,11} for PWM1MR3 ,{Bits 12,13,14} for PWM1MR4 ,{Bits 15,16,17} for PWM1MR5 , {Bits 18,19,20} for PWM1MR6.
5) PWM1IR – PWM Interrupt Register: If an interrupt is generated by any of the Match Register then the corresponding bit in PWM1IR will be set high. Writing a 1 to the corresponding location will clear that interrupt.
- 1) Bits 0,1,2,3 are for PWM1MR0, PWM1MR1, PWM1MR2, PWM1MR3 respectively and
- 2) Bits 8,9,10 are for PWM1MR4 , PWM1MR5 , PWM1MR6 respectively. Other bits are reserved.
6) PWMLER – Load Enable Register: The PWM Load Enable Register is used to control the way Match Registers are updated when PWM generation is active. When PWM mode is active and we apply new values to the Match Registers the new values won’t get applied immediately. Instead what happens is that the value is written to a “Shadow Register” .. it can be thought of as a duplicate Match Register. Each Match Register has a corresponding Shadow Register. The value in this Shadow Register is transferred to the actual Match Register when: PWM1TC is reset (i.e at the beginning of the next period) and the corresponding Bit in PWM1LER is 1.
Hence only when these 2 conditions are satisfied the value is copied to Match Register. Bit ‘x’ in PWM1LER corresponds to match Register ‘x’. I.e Bit 0 is for PWM1MR0 , Bit 1 for PWM1MR1 , .. and so on. Using PWM1LER will be covered in the examples section.
7) PWM1PCR – PWM Control Register: This register is used for Selecting between Single Edged & Double Edged outputs and also to Enable/Disable the 6 PWM outputs which go to their corresponding Pins.
- 1) Bits 2 to 6 are used to select between Single or Double Edge mode for PWM 2,3,4,5,6 outputs. If Bit 2 is set to 1 then PWM1.2(i.e the one corresponding to PWM1MR2) output is double edged else if set 0 then its Single Edged. Similarly Bit x for PWM1.x correspondingly.
- 2) Bits 9 to 14 are used to Enable/Disable PWM outputs. If Bit 9is set to 1 then PWM1.1 output is enabled , else disabled if set to 0. Similarly remaining bits for PWM1.x correspondingly.
Configuring and Initializing PWM Block
Now, lets see how to use PWM block. Configuring PWM module is very much similar to Configuring Timer except, additionally, we need to enable the outputs and select PWM functions for the corresponding PIN on which PWM output will be available. But first we need to do some basic Math Calculations for defining the Period time, the resolution using a prescale value and then Pulse Widths. For this First we need to define the resolution of our PWM signal. PWM resolution is the minimum time delay that can used to increase or decrease the pulse width. More smaller the increment more fine will be the resolution. PWM Resolution is set using an appropriate Prescale Value. The calculation for PWM Prescaler is the same which I had shown in the Timer Tutorial, here it is once again:
LPC1768 PWM Prescale (PWM1PR) Calculations:
The general formula for PWM Resolution at X Mhz PCLK(CCLK) and a given value for prescale (PR) is as given below:
=
Seconds
Hence, we get the Formula for Prescaler (PR) for required PWM resolution (PWMRES in Secs) at given PCLK(in Hz) frequency as:
Note that here, the PWM Resolution is also the time delay required to increment PWM TC by 1.
Hence, Prescaler value for 1 micro-second resolution i.e. 1us time delay at 25 Mhz PCLK(CCLK) is,
Prescale for 1 mS (milli-second) resolution at 25Mhz PCLK(CCLK) is,
Note: The calculations for LPC1769 are same except it operates at 120Mhz(max CCLK). Hence default PCLK will be 30Mhz.
- Select the PWM function for the PIN on which you need the PWM output using applicable LPC_PINCON->PINSELx register.
- Select Single Edge or Double Edge Mode using LPC_PWM1->PCR. By default its Single Edge Mode.
- Assign the Calculated value to LPC_PWM1->PR.
- Set the Value for PWM Period in LPC_PWM1->MR0.
- Set the Values for other Match Registers i.e the Pulse Widths.
- Set appropriate bit values in LPC_PWM1->MCR .. like for e.g. resetting PWM1TC for PWMMR0 match and optionally generate interrupts if required
- Set Load Enable Bits for the Match Registers that you’ve used. This is important!
- Then Enable PWM outputs using LPC_PWM1->PCR.
- Now Reset PWM Timer using LPC_PWM1->TCR.
- Finally, Enable Timer Counter and PWM Mode using LPC_PWM1->TCR.
Example :
//Select PWM Function Using appropriate PINSEL register
LPC_PWM1->PCR = ... ; //Select PWM type - By default its Single Edged
LPC_PWM1->PR = ... ; //assign calculated PR value
LPC_PWM1->MR0 = ... ; //Assign Period Duration
LPC_PWM1->MRx = ... ; //Assign pulse duration i.e widths for other Match Regs.. x=1 to 6
LPC_PWM1->MCR = (1<<1) | ... ; //Reset PWM1TC on PWMMR0 match & Other conditions
LPC_PWM1->LER = (1<<1) | ... ; //update MR0 and other Match Registers
LPC_PWM1->PCR = ... ; //enable PWM outputs as required
LPC_PWM1->TCR = (1<<1) ; //Reset PWM TC & PR
//Now , the final moment - enable PWM TC
LPC_PWM1->PWM1TCR = (1<<0) | (1<<3); //enable counters and PWM Mode
//Done!
Updating Match Registers i.e. the Pulse Width
Once PWM is initialized, you can update any of the Match Registers at anytime using PWMLER. This can be done as follows:
LPC_PWM1->MR1 = 50; //Update Pulse Width
LPC_PWM1->LER = (1<<1); //set the corresponding bit to 1
When you want to update Multiple Match Registers then this can be done as follows:
LPC_PWM1->MR1 = 50;
LPC_PWM1->MR2 = 68;
LPC_PWM1->MR3 = 20;
LPC_PWM1->LER = (1<<1) | (1<<2) | (1<<3); //Update Load Enable bit for all MRs together
Some Real World PWM Examples with sample code
Below I have given LPC1768 PWM Examples. For LCP1769 just change the PWM Prescale from 24 to 29 and Timer Prescale from 24999 to 29999.
LPC1768 PWM Example 1: Servo Control
In this LPC1768 PWM Example we will sweep a servo from 1000us to 2000us pulse widths, back and forth in steps. The period is set to 20ms(20000us) which is common for servos. PWM1.1 output is selected and will be available on Pin P1.18. In the C++ code given below, I have also used Timer0 which is used by delayMS(..) function to generate delay before we update PWM1MR1 register with new value. Warning: Double check all connections when connecting the motor or it will damage your board.
Source Code for Example 1:
/*(C) Umang Gajera - www.ocfreaks.com
More Embedded tutorials @ www.ocfreaks.com/cat/embedded
LPC1768 PWM Tutorial Example 1 - Simple RC Servo Control - KEIL uV5 ARM.
License: GPL */
#include <LPC17xx.h>
#define PWMPRESCALE (25-1) //25 PCLK cycles to increment TC by 1 i.e. 1 Micro-second
void initPWM(void);
void updatePulseWidth(unsigned int pulseWidth);
void delayMS(unsigned int milliseconds);
void initTimer0(void);
int main(void)
{
//SystemInit(); //gets called by Startup code before main()
initTimer0(); //Initialize Timer
initPWM(); //Initialize PWM
int pulseWidths[] = {1000,1250,1500,1750,2000};
const int numPulseWidths = 5;
int count=1;
int dir=0; //direction, 0 = Increasing, 1 = Decreasing
while(1)
{
updatePulseWidth(pulseWidths[count]); //Update Servo Pulse Width
delayMS(1000); //Wait for servo to reach the new position
if(count == (numPulseWidths-1) || count == 0)
{
dir = !dir; //Toggle direction if we have reached count limit
}
if(dir) count--;
else count++;
}
//return 0; //normally this won't execute ever
}
void initPWM(void)
{
/*Assuming that PLL0 has been setup with CCLK = 100Mhz and PCLK = 25Mhz.*/
LPC_PINCON->PINSEL3 |= (1<<5); //Select PWM1.1 output for Pin1.18
LPC_PWM1->PCR = 0x0; //Select Single Edge PWM - by default its single Edged so this line can be removed
LPC_PWM1->PR = PWMPRESCALE; //1 micro-second resolution
LPC_PWM1->MR0 = 20000; //20000us = 20ms period duration
LPC_PWM1->MR1 = 1250; //1ms - default pulse duration i.e. width
LPC_PWM1->MCR = (1<<1); //Reset PWM TC on PWM1MR0 match
LPC_PWM1->LER = (1<<1) | (1<<0); //update values in MR0 and MR1
LPC_PWM1->PCR = (1<<9); //enable PWM output
LPC_PWM1->TCR = (1<<1); //Reset PWM TC & PR
LPC_PWM1->TCR = (1<<0) | (1<<3); //enable counters and PWM Mode
//PWM Generation goes active now!
//Now you can get the PWM output on Pin P1.18
}
void updatePulseWidth(unsigned int pulseWidth)
{
LPC_PWM1->MR1 = pulseWidth; //Update MR1 with new value
LPC_PWM1->LER = (1<<1); //Load the MR1 new value at start of next cycle
}
void delayMS(unsigned int milliseconds) //Using Timer0
{
LPC_TIM0->TCR = 0x02; //Reset Timer
LPC_TIM0->TCR = 0x01; //Enable timer
while(LPC_TIM0->TC < milliseconds); //wait until timer counter reaches the desired delay
LPC_TIM0->TCR = 0x00; //Disable timer
}
void initTimer0(void) //To setup Timer0 used delayMS() function
{
/*Assuming that PLL0 has been setup with CCLK = 100Mhz and PCLK = 25Mhz.*/
LPC_TIM0->CTCR = 0x0;
LPC_TIM0->PR = 25000-1; //Increment TC at every 24999+1 clock cycles
//25000 clock cycles @25Mhz = 1 mS
LPC_TIM0->TCR = 0x02; //Reset Timer
}
LPC1768 PWM Example 2: LED Dimming
This example is similar to Example 1, except here we reduce the PWM Period to 1ms and Duty-cycle will vary from 0%(LED OFF) to 100%(Brightest). Connect the LED anode to P1.18 and cathode to GND using a suitable resistor (like >330 Ohms).
Source code for Example 2
/*(C) Umang Gajera - www.ocfreaks.com
More Embedded tutorials @ www.ocfreaks.com/cat/embedded
LPC1768 PWM Tutorial Example 2 - LED Dimming using PWM - KEIL uV5 ARM.
License: GPL.
*/
#include <LPC17xx.h>
#define PWMPRESCALE (25-1) //25 PCLK cycles to increment TC by 1 i.e. 1 Micro-second
void initPWM(void);
void updatePulseWidth(unsigned int pulseWidth);
void delayMS(unsigned int milliseconds);
void initTimer0(void);
int main(void)
{
//SystemInit(); //gets called by Startup code before main()
initTimer0(); //Initialize Timer
initPWM(); //Initialize PWM
int pulseWidths[] = {0,250,500,750,1000}; //Pulse Widths for varying LED Brightness
const int numPulseWidths = 5;
int count=1;
int dir=0; //direction, 0 = Increasing, 1 = Decreasing
while(1)
{
updatePulseWidth(pulseWidths[count]); //Update LED Pulse Width
delayMS(1000);
if(count == (numPulseWidths-1) || count == 0)
{
dir = !dir; //Toggle direction if we have reached count limit
}
if(dir) count--;
else count++;
}
//return 0; //normally this won't execute ever
}
void initPWM(void)
{
/*Assuming that PLL0 has been setup with CCLK = 100Mhz and PCLK = 25Mhz.*/
LPC_PINCON->PINSEL3 |= (1<<5); //Select PWM1.1 output for Pin1.18
LPC_PWM1->PCR = 0x0; //Select Single Edge PWM - by default its single Edged so this line can be removed
LPC_PWM1->PR = PWMPRESCALE; //1 micro-second resolution
LPC_PWM1->MR0 = 1000; //1000us = 1ms period duration
LPC_PWM1->MR1 = 250; //250us - default pulse duration i.e. width
LPC_PWM1->MCR = (1<<1); //Reset PWM TC on PWM1MR0 match
LPC_PWM1->LER = (1<<1) | (1<<0); //update values in MR0 and MR1
LPC_PWM1->PCR = (1<<9); //enable PWM output
LPC_PWM1->TCR = (1<<1); //Reset PWM TC & PR
LPC_PWM1->TCR = (1<<0) | (1<<3); //enable counters and PWM Mode
//PWM Generation goes active now!
//Now you can get the PWM output on Pin P1.18
}
void updatePulseWidth(unsigned int pulseWidth)
{
LPC_PWM1->MR1 = pulseWidth; //Update MR1 with new value
LPC_PWM1->LER = (1<<1); //Load the MR1 new value at start of next cycle
}
void delayMS(unsigned int milliseconds) //Using Timer0
{
LPC_TIM0->TCR = 0x02; //Reset Timer
LPC_TIM0->TCR = 0x01; //Enable timer
while(LPC_TIM0->TC < milliseconds); //wait until timer counter reaches the desired delay
LPC_TIM0->TCR = 0x00; //Disable timer
}
void initTimer0(void) //To setup Timer0 used delayMS() function
{
/*Assuming that PLL0 has been setup with CCLK = 100Mhz and PCLK = 25Mhz.*/
LPC_TIM0->CTCR = 0x0;
LPC_TIM0->PR = 25000-1; //Increment TC at every 24999+1 clock cycles
//25000 clock cycles @25Mhz = 1 mS
LPC_TIM0->TCR = 0x02; //Reset Timer
}