How to code PWM  - Page 1
Forum section: electronics / general

author: DVX
author_rank: Contributor
timestamp: March 06, 2017, 06:28:38 pm
content: Hi,My MCU PIC32 at 80Mhz has all the hardware PWM modules (outputs) assigned to an LCD display and they can not be remapped. I have written some code to use a timer + Interrupt to generate a PWM output on a port pin in proportion to a variable which can range from 0-100, the pin will feed a back light on the same LCD display to control the back light brightness. I have some knowledge for how PWM works but I don't know the best way to code this, so any help is welcome.My thoughts are as follows:10 steps for PWM control each step represents 10% change in display brightness from 0 to 10 steps = 100%I have set a timer to generate an interrupt every 5ms and toggle a pin high and low, code is inside the interrupt routine, so fast and code size should be small. At 5ms or 0.005 sec I should get 200 pulses / sec, I check this on my scope and it is 100 pulses/sec or 100Hz as maybe rise and fall edges are counted.There needs to be a framing period over which the PWM works and then repeats, so for my ten steps above, it will repeat every 0.1 sec, or give 10 frames /sec. Anything less than 25 frames / sec or preferably 50 frames /sec will produce flicker, so a higher frame rate is better. My example has 10 frames /sec which is too low, but shows the concept.From 0% and with each increase of 10% of PWM input the frame stays on for an extra pulse, so 40% = 40 pulses on and 60 off, 70% = 70 pulses on and 30 off. The output pin is set high at the start of the frame (unless 0 PWM), the PWM is / 10 to get the number of pulses per frame, then the code waits until the total number of pulse as been counted, it then resets the number of pulses variable, ready to repeat for the next frame, after waiting number of pulse to stay off. Then repeats.// PWM for TFT levelPWM_interrupt_count ++; // inc every interruptPWM_pulse_count = PWM_percent / 10; // 20 pulses = 100% PWM, PWM_percent is user set PWM levelif(PWM_interrupt_count > 9) // is 1 frame of 10 pulses reached ?{PWM_interrupt_count = 0; // reset frame counter}if (PWM_interrupt_count < PWM_pulse_count) // is PWM on time still valid?{ LATB10_bit = 1; } // yes, set B10 as highelse{ LATB10_bit = 0; } // no, set port pin lowOne problem I have with the PIC32 is the MCU stops when anything less than 5ms is used to setup the timer (3), 1ms - 4ms stops the MCU, so for now I am looking at better code to work at 5ms with a higher frame rate or no flicker. This is a hobby project and I am not that experienced in C coding.

author: JPortici
author_rank: Super Contributor
timestamp: March 06, 2017, 07:48:00 pm
content: Quote from: DVX on March 06, 2017, 06:28:38 pmwhat do you mean... you can't use the PPS to re route the outputs?Anyway, the idea of software PWM is this:- Interrupt at timer, Interrupt frequency is SPWM frequency- Inside interrupt increment counter-> is counter equal or greater than period register? if yes clear the counter-> Is counter equal or greater than duty cycle? If yes CLEAR the output otherwise SET the outputQuoteI'm also not sure of what you mean by this

author: westfw
author_rank: Super Contributor
timestamp: March 06, 2017, 10:55:34 pm
content: That doesn't sound unreasonable. For one output, you could save some interrupts:Turn on output; Set timer to interrupt after "on" time.In ISR: Turn off output. Set timer to interrupt after "off" time, with "on"+"off"= <some constant>This gives you two interrupts per PWM cycle, regardless of the period or brightness. You can pre-compute the on/off times, so the ISR code is very simple.

author: Buriedcode
author_rank: Super Contributor
timestamp: March 06, 2017, 11:15:34 pm
content: I too am confused by this. JPortici has pretty much covered everything (PPS to assign PWM output pins, use of interrupts etcc).The only things I'll add are about using PWM for your backlight.If the display has an LED backlight that is directly powered (or switched with an NPN transistor) then ideally you want a PWM frequency of > 500Hz. Many say you 'can't detect 100Hz' but you can, especially if your gaze moves across the display (turning your head). Even at 500Hz, at low duty (say 10%) one may detect some flicker because it is at the lowest duty that there is the largest gap between LED on times.This can also be overcome by using more complicated versions of PWM rather than the standard 'Output on when timer = 0, output off when timer = period'. I won't go into detail because I believe you can use your hardware PWM's which can easily provide >1kHz period with >10 steps (a lot more, should you want it).If the backlight is powered by a boost converter - some backlights are LED's in series requiring up to 28V - and it as a 'PWM dimming' pin, then the PWM frequency range depends on this boost.10 steps is a reasonable resolution for controlling a backlight. Often people go overboard with 8-bit (256 steps) up to 12-bit (4096) but that is only really required for colour mixing or fading.Finally, there are a number of ways to implement one or more PWM's using timers, interrupts (as well as of course the hardware PWM) and yours appears to be needlessly complicated - although looks like it will work exactly how you want to it. The downside of course is for each PWM 'step' the interrupt must fire that number of times per PWM period. So for 100Hz - PWM period of 10ms, if you want 10 levels, your interrupt must fire every 1ms.A better method would be to calculate the on/off time, and set the timer to fire the interrupt after these times. For example, for 200Hz, period =- 5ms. For 10 steps, that is 500us resolution.For duty = 40%. Get 40% of 5ms = 5*0.4 = 2ms. On-time = 2ms. Of time = period - on-time = 5-2 = 3ms. You calculate on_time and off_time each time you change the period. All this does is set the timer to fire after 'off_time' if the output is on, and 'on_time' if the output is off.Interrupt pseudocode:Code:if(output_on) {output_on = 0;output_pin = 0;timer_period = off_time;}else {output_on = 1;output_pin = 1;timer_period = on_time;}This way, the interrupt is only called twice per PWM period. But it only works for one PWM - your method would be more appropriate for multiple PWM's of the same period.Edit: westfw beat me to it :/ This is why I should 'review' my reply when posts were made whilst I was replying

author: bson
author_rank: Unknown
timestamp: March 06, 2017, 11:36:36 pm
content: If you interrupt and toggle the pin every 5ms you will get a 10ms 50% duty cycle.What you want is to have:CYCLE=5msSTATE=LOWDUTY=some epsilonNEW_DUTY=0-5mson interrupt:if state == LOW:DUTY=NEW_DUTYset next interrupt to CYCLE-DUTYelse:set next interrupt to DUTYtoggle STATEset pin to STATEThen, to change the duty, update NEW_DUTY and on the next even cycle it will become the new duty cycle.Express duty in timer ticks. So if you run the timer at 10kHz and you want a 5ms cycle, CYCLE, DUTY, and NEW_DUTY become tick counts in the range 0-(10000*.005-1) = 0-49, so if you want 10 distinct levels the tick count becomes 5*level.If you want to invert the polarity, load DUTY=NEW_DUTY in the high state (in the else clause in the pseudo code above).CYCLE is likely a constant derived from the main CPU clock and prescaler setup, and only NEW_DUTY is volatile.

author: JPortici
author_rank: Super Contributor
timestamp: March 07, 2017, 06:22:28 am
content: Quote from: Buriedcode on March 06, 2017, 11:15:34 pmalmost ANY project i work with, i have either the SysTick timer or or an equivalent already firing an interrupt. a timer that is counting every 100 us to 10 ms for timekeeping, debouncing and timers for state machines.adding three or four line of codes is not going to change much... and the more i think of it the more i think that software PWM is a good idea in this case, so you don't waste precious high speed modules.As you pointed out.. the actual frequency depends on the number of steps. if your timekeeping interrupt was fired every 1ms and you had 10 steps the resulted frequency would be 100 Hzand as you pointed out, need to know how the backlight is controlled

author: DVX
author_rank: Contributor
timestamp: March 07, 2017, 05:28:25 pm
content: Thanks for all your replies, to cover some of the questions, the LCD has a basic driver circuit included with 3.3v logic high/low input, this turns on /off the LED back-lighting, by connecting the display back-lighting input to a PIC32 port pin the back-lighting can be turned on/off or somewhere in between with PWM.I have checked for PPS to remap the Output Compare on PIC32MX460 but have not found data to show PPS is supported on the MCU.I have made a simple diagram to show my understanding for the pulse structure and total number of pulses needed, see attached JPG. From this 10 pulse are needed to give 10% PWM steps and with 50 frames/sec this would be over 500Hz, or a ticker interrupt running at 1KhHz, or better still 10KHz.I have a general house keeping interrupt on timer 2 running every 5ms (ticker), I have yet to find the reason why the MCU will not run when this is set to 1ms or faster. I have setup another timer and this shows the same issue, any PIC32 experts who can help?

author: JPortici
author_rank: Super Contributor
timestamp: March 07, 2017, 05:58:05 pm
content: You are right on your pic there is no PPS, unfortunately. Unless you can change the pin arrangement, SW PWM it is

author: DVX
author_rank: Contributor
timestamp: March 07, 2017, 06:44:14 pm
content: After a long time trying to increase the PWM timer(3) speed from 5ms to 1ms without the MCU failing to start, I set the 2nd timer(2) used for general house keeping, also to 1ms and then the MCU starts OK. Maybe it's a quirk of the PC32MX460 and using two timers at different but close speeds, I also have a UART reading a GPS module and a DS3231 as an RTC on I2C and also a TSL2561 on I2C, so the overheads might be high. The TSL2561 produces 2 outputs for measuring ambient light levels, IR and IR+effective eye level, from these I have calculated LUX and from this a percentage light level to input to the PWM code which then auto sets the LCD brightness.Thanks everyone for your help.