EmbeddedRelated.com
Forums

Poor man's PWM

Started by Don Y January 27, 2020
With "few" intensity levels desired, I plan to drive LED
with a crude PWM signal directly from an IRQ (i.e., push
bits out a hardware port at each IRQ).

But, instead of having N different times at which the IRQ
might be signalled (for the N+1 different duty cycles) in
each refresh interval, I plan on having log2(N) times, each
delimiting an period "twice" as long as the previous.  This
keeps the hardware cheaper than dirt and minimizes the
real-time load on the processor.

[There are (refresh_rate * log2(N)) IRQs each second instead
of (refresh_rate * N) IRQs -- though the *instantaneous* IRQ
rate varies considerably]

Then, at the level above the driver, build a schedule for which
intervals to activate the LED.

E.g., for each level: turn the LED on in intervals:
0   None
1   1
2   2
3   1&2
4   4
5   1&4
6   2&4
...
N   All

What problems might I encounter with this approach?  (I'm
mainly worrying about visual artifacts)
On Monday, January 27, 2020 at 7:53:54 PM UTC-5, Don Y wrote:
> With "few" intensity levels desired, I plan to drive LED > with a crude PWM signal directly from an IRQ (i.e., push > bits out a hardware port at each IRQ). > > But, instead of having N different times at which the IRQ > might be signalled (for the N+1 different duty cycles) in > each refresh interval, I plan on having log2(N) times, each > delimiting an period "twice" as long as the previous. This > keeps the hardware cheaper than dirt and minimizes the > real-time load on the processor. > > [There are (refresh_rate * log2(N)) IRQs each second instead > of (refresh_rate * N) IRQs -- though the *instantaneous* IRQ > rate varies considerably] > > Then, at the level above the driver, build a schedule for which > intervals to activate the LED. > > E.g., for each level: turn the LED on in intervals: > 0 None > 1 1 > 2 2 > 3 1&2 > 4 4 > 5 1&4 > 6 2&4 > ... > N All > > What problems might I encounter with this approach? (I'm > mainly worrying about visual artifacts)
What controls the IRQ period? I assume each IRQ will modify a hardware timer in the CPU? The "list" items you describe are just bits in the value you wish to specify the duty cycle. An 8 bit byte will give you 255 brightness levels with 8 different period interrupts. A nybble gives 15 brightness levels with 4 periods. Each bit in the value determines if the LED is off or on during the corresponding IRQ. A simple bit mask shifted with each IRQ can be anded with each LED value to indicate if that LED should be on during that IRQ. When the bit mask reaches zero, start over with the longest IRQ and highest order bit mask. -- Rick C. - Get 1,000 miles of free Supercharging - Tesla referral code - https://ts.la/richard11209
On 28/01/2020 01:53, Don Y wrote:
> With "few" intensity levels desired, I plan to drive LED > with a crude PWM signal directly from an IRQ (i.e., push > bits out a hardware port at each IRQ). > > But, instead of having N different times at which the IRQ > might be signalled (for the N+1 different duty cycles) in > each refresh interval, I plan on having log2(N) times, each > delimiting an period "twice" as long as the previous.  This > keeps the hardware cheaper than dirt and minimizes the > real-time load on the processor. > > [There are (refresh_rate * log2(N)) IRQs each second instead > of (refresh_rate * N) IRQs -- though the *instantaneous* IRQ > rate varies considerably] > > Then, at the level above the driver, build a schedule for which > intervals to activate the LED. > > E.g., for each level: turn the LED on in intervals: > 0   None > 1   1 > 2   2 > 3   1&2 > 4   4 > 5   1&4 > 6   2&4 > ... > N   All > > What problems might I encounter with this approach?  (I'm > mainly worrying about visual artifacts)
It sounds doable, but complicated. I think you'd need a good many different intensity levels before this would pay off - the overhead of tracking and setting the different time intervals won't make sense until you are using a huge number of levels. And if you wanted a huge number of levels, you'd be using a better quality method of generating the PWM signal that doesn't have as much jitter. As far as processor load goes, there are two key points. One is the worst case rate of the IRQs - and that is determined by the lowest digit, and will be the same for a regular pulse or for this complex scheme. The other is the amount of processor work done in each IRQ, and that will be massively more than with a simple system, outweighing it in total work until you have a huge number of levels. Let me guess some numbers - and you can correct them if they are inappropriate. Pick a refresh frequency of 100 Hz, which is fast enough that you won't see blinking even if you move your eyes quickly. We'll have 16 levels, which is more than enough for normal circumstances. That puts your timer interrupt at 1600 Hz - not an onerous burden for most systems. And being a regular timer interrupt, you can use the same tick for other purposes, unlike your specialised one. Below is a simple example. It can easily be modified to have different pattern types (minimise switching, or spread the on/off time evenly within the cycle), different mapping between logical levels and total duty cycle (to better suit the visual brightness impression), multiple leds, etc. #include <stdint.h> enum { levels = 16 }; _Static_assert(levels <= 16, "Pattern type needs to suit levels"); typedef uint16_t pattern_t; static const pattern_t patterns[levels + 1] = { 0x0000, 0x0001, 0x0101, 0x0841, 0x1111, 0x2491, 0x4949, 0x54a9, 0x5555, 0xab55, 0xb5b5, 0xdb6d, 0xdddd, 0xf7bd, 0xfdfd, 0xfffd, 0xffff }; static int ledLevel; void setLedLevel(int level) { if (level < 0) level = 0; if (level >= levels) level = levels; ledLevel = level; } extern void ledOn(void); extern void ledOff(void); static pattern_t stepper = 1; static const pattern_t last_step = (1u << (levels - 1)); void ledTimerInterrupt(void) { pattern_t pattern = patterns[ledLevel]; if (pattern & stepper) { ledOn(); } else { ledOff(); } if (stepper == last_step) { stepper = 1; } else { stepper = stepper << 1; } }
Am 28.01.2020 um 01:53 schrieb Don Y:
> With "few" intensity levels desired, I plan to drive LED > with a crude PWM signal directly from an IRQ (i.e., push > bits out a hardware port at each IRQ). > > But, instead of having N different times at which the IRQ > might be signalled (for the N+1 different duty cycles) in > each refresh interval, I plan on having log2(N) times, each > delimiting an period "twice" as long as the previous.&nbsp; This > keeps the hardware cheaper than dirt
I rather much doubt that. If there are indeed "few" intensity levels, i.e. 16 rather than 1024, this approch will at most reduce the overall software-PWM interrupt rate by a factor of 4; and the hardware still has to be capable of handling each of those interrupts fast enough, consistently, that you there will be no flickering caused by the shortest possible time slot being prolonged or shortened by delays in interrupt processing. This puts a load on the overall design that this scheme cannot reduce. Overall, hardware that can implement this scheme will thus hardly be any cheaper than hardware that can implement the usual one.
> Then, at the level above the driver, build a schedule for which > intervals to activate the LED.
Building this schedule doesn't actually come for free, either.
> What problems might I encounter with this approach?&nbsp; (I'm > mainly worrying about visual artifacts)
The most obvious one, which would earn me a stern veto from the HW department at may outfit, is an increase in the overall number of LED switching edges, increasing, among other things, EM emissions. Your plan also destroys any possibilities to distribute those EM emissions (and power supply loads) more evenly by staggering the PWM cycles of several LEDs. That aspect becomes more important as the loads get heavier, of course: 20 mA LEDs are a piece of cake, 20 Ampere heaters not so much.
On 1/28/2020 2:53, Don Y wrote:
> With "few" intensity levels desired, I plan to drive LED > with a crude PWM signal directly from an IRQ (i.e., push > bits out a hardware port at each IRQ). > > But, instead of having N different times at which the IRQ > might be signalled (for the N+1 different duty cycles) in > each refresh interval, I plan on having log2(N) times, each > delimiting an period "twice" as long as the previous.&nbsp; This > keeps the hardware cheaper than dirt and minimizes the > real-time load on the processor. > > [There are (refresh_rate * log2(N)) IRQs each second instead > of (refresh_rate * N) IRQs -- though the *instantaneous* IRQ > rate varies considerably] > > Then, at the level above the driver, build a schedule for which > intervals to activate the LED. > > E.g., for each level: turn the LED on in intervals: > 0&nbsp;&nbsp; None > 1&nbsp;&nbsp; 1 > 2&nbsp;&nbsp; 2 > 3&nbsp;&nbsp; 1&2 > 4&nbsp;&nbsp; 4 > 5&nbsp;&nbsp; 1&4 > 6&nbsp;&nbsp; 2&4 > ... > N&nbsp;&nbsp; All > > What problems might I encounter with this approach?&nbsp; (I'm > mainly worrying about visual artifacts)
Hi Don, whatever PW you do beware the randomness, even small, from period to period. I did something like that once many years ago (just wanted to have 3 colours out of a 2 colour LED) and the mixed colour was quite visibly flickering. I don't remember how much randomness there was and how it might compare to yours but using IRQ and bit banging it does invite some, I'd test that before rolling it out. I think mine used IRQ just to switch tasks and measure time on a HC11 back then (some 20 years ago) but I don't think I pushed much to get rid of the flicker, it was not a problem. Dimiter ====================================================== Dimiter Popoff, TGI http://www.tgi-sci.com ====================================================== http://www.flickr.com/photos/didi_tgi/
On Tuesday, January 28, 2020 at 8:43:12 AM UTC-5, Hans-Bernhard Br&ouml;ker wrote:
> Am 28.01.2020 um 01:53 schrieb Don Y: > > With "few" intensity levels desired, I plan to drive LED > > with a crude PWM signal directly from an IRQ (i.e., push > > bits out a hardware port at each IRQ). > > > > But, instead of having N different times at which the IRQ > > might be signalled (for the N+1 different duty cycles) in > > each refresh interval, I plan on having log2(N) times, each > > delimiting an period "twice" as long as the previous.&nbsp; This > > keeps the hardware cheaper than dirt > > I rather much doubt that. If there are indeed "few" intensity levels, > i.e. 16 rather than 1024, this approch will at most reduce the overall > software-PWM interrupt rate by a factor of 4; and the hardware still has > to be capable of handling each of those interrupts fast enough, > consistently, that you there will be no flickering caused by the > shortest possible time slot being prolonged or shortened by delays in > interrupt processing. This puts a load on the overall design that this > scheme cannot reduce. > > Overall, hardware that can implement this scheme will thus hardly be any > cheaper than hardware that can implement the usual one. > > > Then, at the level above the driver, build a schedule for which > > intervals to activate the LED. > > Building this schedule doesn't actually come for free, either.
Actually it is free. If you use a 4 bit quantity to describe the 15 brightness levels, each bit in that level value corresponds to one of the four pulse widths.
> > What problems might I encounter with this approach?&nbsp; (I'm > > mainly worrying about visual artifacts) > > The most obvious one, which would earn me a stern veto from the HW > department at may outfit, is an increase in the overall number of LED > switching edges, increasing, among other things, EM emissions. > > Your plan also destroys any possibilities to distribute those EM > emissions (and power supply loads) more evenly by staggering the PWM > cycles of several LEDs. That aspect becomes more important as the loads > get heavier, of course: 20 mA LEDs are a piece of cake, 20 Ampere > heaters not so much.
I don't think this method will "increase" the EM. I think it will spread the love over a wider bandwidth with lower peak values in most cases. Just as with the linear case the details will depend on the brightness level being displayed. Also, the content will likely predominate a higher frequency which is easier to filter out. To know the details better will require some analysis. -- Rick C. + Get 1,000 miles of free Supercharging + Tesla referral code - https://ts.la/richard11209
Hi Dimiter,

On 1/28/2020 8:39 AM, Dimiter_Popoff wrote:
> whatever PW you do beware the randomness, even small, > from period to period. I did something like that once many years ago > (just wanted to have 3 colours out of a 2 colour LED) and the mixed > colour was quite visibly flickering. I don't remember how much > randomness there was and how it might compare to yours but using > IRQ and bit banging it does invite some, I'd test that before > rolling it out. I think mine used IRQ just to switch tasks and > measure time on a HC11 back then (some 20 years ago) but I don't > think I pushed much to get rid of the flicker, it was not a problem.
How were you driving it? I.e., was it a two-lead (bi-color) device? Or, THREE? In the former case, I could see how the duty cycle in one direction (terminal 1 to terminal 2) differing from that of the other direction (terminal 2 to terminal 1) would alter the relative mix of the two colors and, thus, the resulting color. In the latter case, perhaps driving one (or both) anodes/cathodes with a particular duty cycle to "tune" the color? The obvious fix is to double-buffer the drive to the device so that the same signal that prompts the ISR to change the drive *also* locks in the previous decision to the actual indicator. This makes the entire IRQ period available for servicing the ISR without having visual consequences -- for the cost of (an) external latch(es). Latency issues go away (largely). [But, this either requires a "terminal count output" from an internal timer/counter -or- an external counter/timer driving an IRQ input (that can also trigger the latches).] A related problem is how "nearby" indicators are perceived. E.g., will two different indicators be seen as having differing intensities even though *intended* to have the same intensity? I.e., is the visual output of a device "matched" to others ACROSS THE FULL RANGE OF DRIVE CONDITIONS? Or, just at a particular test criteria? And, how far apart do such devices need to be in order for the user NOT to be able to perceive (minor) differences? Two die in the same package (your case!) is obviously one extreme! I'm not keen on having to purchase "matched" devices or any "select at test". So, would rather just AVOID issues that could lead to visual aberrations, etc.
Hi Rick,

On 1/27/2020 8:29 PM, Rick C wrote:
> On Monday, January 27, 2020 at 7:53:54 PM UTC-5, Don Y wrote: >> Then, at the level above the driver, build a schedule for which intervals >> to activate the LED. > > What controls the IRQ period? I assume each IRQ will modify a hardware > timer in the CPU?
The timer will either be a "spare" internal timer or something like an SOIC8 -- whose *sole* purpose will be to output a set of edges at the desired interval spacings (amusing to think of "wasting" an entire MCU just for such a banal task!) The "main" processor can synchronize to this signal by detecting the longest such interval wrt its internal "system timebase".
> The "list" items you describe are just bits in the value you wish to specify > the duty cycle. An 8 bit byte will give you 255 brightness levels with 8 > different period interrupts. A nybble gives 15 brightness levels with 4 > periods. Each bit in the value determines if the LED is off or on during > the corresponding IRQ. A simple bit mask shifted with each IRQ can be anded > with each LED value to indicate if that LED should be on during that IRQ. > When the bit mask reaches zero, start over with the longest IRQ and highest > order bit mask.
N.B. You always have the "off" intensity available so 8b yields 25_6_ levels, etc. However, as the number of levels increases, the period of the shortest inevitably decreases. This has direct consequences on the *peak* IRQ rate -- which, in turn, has implications on maximum tolerable latency (and complexity of the ISR itself). Double-buffering the outputs (see my reply to Dimiter) eases the timeliness constraints on the ISR (e.g., 100us at 10KHz) [Note that there is nothing that forces the periods to be multiples of 2; that's just the easiest way to describe this approach. I could, instead, pick intervals of 2, 3 and 4 to yield duty cycles of {0, 22, 33, 44, 55, 67, 78, 100}%. Or, 2, 2 and 2 to yield {0, 33, a different 33, yet another 33, 67, a different 67, yet another 67, 100}%] Consider, also, you want an implementation that scales to more than a single LED. So, doing that work *in* the ISR quickly becomes the bottleneck. Hence "scheduling" the ISR's actions in the background (as a non-time-critical activity)
Hi David,

On 1/28/2020 3:29 AM, David Brown wrote:
> On 28/01/2020 01:53, Don Y wrote: >> Then, at the level above the driver, build a schedule for which >> intervals to activate the LED. > > It sounds doable, but complicated. I think you'd need a good many > different intensity levels before this would pay off - the overhead of > tracking and setting the different time intervals won't make sense until > you are using a huge number of levels. And if you wanted a huge number > of levels, you'd be using a better quality method of generating the PWM > signal that doesn't have as much jitter.
There is a high parts cost to using, e.g., a programmable current source/sink per indicator. And, more data that needs to reside "outside" the CPU (i.e., *in* those devices). So, any practical or economical solution has to be implemented in software. The IRQ overhead is negligible; "do NOTHING in an ISR if it can be done OUTSIDE the ISR!" Hence my comment re: building a schedule (to feed the ISR): /* XXX PORTME -- hardware dependent (all) */ typedef void (*isr_t)() typedef ulonglong LED_t // for example LED_t schedule[4]; isr_t LED_isr; isr_t period1() { *LED_LATCH = schedule[0]; LED_isr = &period2; } isr_t period2() { *LED_LATCH = schedule[1]; LED_isr = &period4; } isr_t period4() { *LED_LATCH = schedule[2]; LED_isr = &period8; } isr_t period8() { *LED_LATCH = schedule[3]; LED_isr = &period1; // refresh schedule[] } If using an internal (programmable) timer, each of these ISRs would also jam the appropriate CONSTANTS into the timer hardware to setup the NEXT period. You can now build the schedule[] in a non-real-time context.
> As far as processor load goes, there are two key points. One is the > worst case rate of the IRQs - and that is determined by the lowest > digit, and will be the same for a regular pulse or for this complex > scheme. The other is the amount of processor work done in each IRQ, and > that will be massively more than with a simple system, outweighing it in > total work until you have a huge number of levels.
There is no need to do any "work" in the ISR -- other than arranging for the PRECOMPUTED set of outputs to be delivered to the actual hardware. All of the "thinking" can be done when the application "decides" what intensity level it wants for a particular indicator (and the "schedule" is built). Latency *can* be a potential problem -- but one easily (and economically) solved by the addition of a second level of (hardware) buffering -- see my earlier replies. Lean ISRs make it possible to drive the IRQ rate up (period down) to minimize the "consequences" of saccades. (though I'm not designing a *display* so the user isn't expected to be fixating on the presentation so much that this would be an annoyance)
> Pick a refresh frequency of 100 Hz, which is fast enough that you won't > see blinking even if you move your eyes quickly. We'll have 16 levels, > which is more than enough for normal circumstances.
"16" *seems* like a good number -- note that ANSI3.64 gave us just a few colors and intensities to "present information". I don't recall anyone complaining that they wish they had more colors or intensities! I may find that 4 is more appropriate esp if they don't have to be geometrically related to each other in terms of durations -- like {0, 20, 80, 100%}. [Anything done with *hardware* "LED drivers" puts constraints on what you can do, going forward.] As I said, originally, I'm trying to anticipate what *visual* consequences might arise from this approach before settling on an implementation. If it's not possible for a user to reliably differentiate between 16 levels, then why implement that many? Or, if two "level 7" indicators appear to have dramatically differing intensities, then the expectation of them being "the same" (or even "similar") is foolhardy. [N.B. The same could be true of a pure hardware implementation that sought to display similar intensities]
> That puts your timer interrupt at 1600 Hz - not an onerous burden for > most systems. And being a regular timer interrupt, you can use the same > tick for other purposes, unlike your specialised one.
Note that you, *still*, have a REGULAR, PERIODIC INTERRUPT (e.g., suitable for a system timer) by selecting ANY *one* of these to also invoke that service (obviously, you'd pick the isr_t that gives you the most latency tolerance before the NEXT scheduled invocation). E.g., in your example, there would be FOUR candidates for a "100Hz periodic interrupt"; pick the one that is least "crowded" by the following ISRs activation.
Hi Hans-Bernhard,

On 1/28/2020 6:43 AM, Hans-Bernhard Br&ouml;ker wrote:
> Am 28.01.2020 um 01:53 schrieb Don Y: >> But, instead of having N different times at which the IRQ >> might be signalled (for the N+1 different duty cycles) in >> each refresh interval, I plan on having log2(N) times, each >> delimiting an period "twice" as long as the previous. This >> keeps the hardware cheaper than dirt > > I rather much doubt that. If there are indeed "few" intensity levels, i.e. 16 > rather than 1024, this approch will at most reduce the overall software-PWM > interrupt rate by a factor of 4;
Which means the timer can be four times FASTER!
> and the hardware still has to be capable of > handling each of those interrupts fast enough, consistently, that you there > will be no flickering caused by the shortest possible time slot being prolonged > or shortened by delays in interrupt processing. This puts a load on the > overall design that this scheme cannot reduce.
Obviously you're not a hardware person. Double buffering gives you the entire IRQ period to process the data for the NEXT IRQ. And, the critical path is ONLY the shortest timer interval, not all of them. E.g., with a 10KHz *peak* IRQ rate, you'd have 100us to respond to THAT IRQ and get the next data ready -- simply COPYING it from a FIXED LOCATION to another FIXED I/O LOCATION -- for the timer to transfer to the LED "(hammer) drivers" coincident with the *next* IRQ. That 100us period would correspond to a ~600Hz refresh rate (assuming a 1/2/4/8 weighting of timer periods) That's something you could do 40 years ago with a 6MHz 8085! [A set of latches are cheap -- and, occupy very little board space in SOIC packages. Note that they don't need high current capacity as they are just acting to buffer *data*; the hammer drivers that follow them do the real *work*! Furthermore, if the MCU doesn't have enough dedicated I/O pins for each of the indicators, you're already dealing with external packages to "store" the drive data]
> Overall, hardware that can implement this scheme will thus hardly be any > cheaper than hardware that can implement the usual one.
Really? How much for 20 PWM channels? 200? 2000? (I have a 2000 LED "moving message" sign at my feet, here -- wanna bet it DOESN'T have 2000 PWM channels? Or, 2000 programmable current sources?)
>> Then, at the level above the driver, build a schedule for which >> intervals to activate the LED. > > Building this schedule doesn't actually come for free, either.
It's dirt cheap! Take N "intensity codes" and map them (statically) to N sequences of timer intervals. Pack these into an array of structures that can be passed to an ISR that *simply* emits the next structure in sequence. See pseudocode posted in other replies.
>> What problems might I encounter with this approach? (I'm >> mainly worrying about visual artifacts) > > The most obvious one, which would earn me a stern veto from the HW department > at may outfit, is an increase in the overall number of LED switching edges, > increasing, among other things, EM emissions. > > Your plan also destroys any possibilities to distribute those EM emissions (and > power supply loads) more evenly by staggering the PWM cycles of several LEDs. > That aspect becomes more important as the loads get heavier, of course: 20 mA > LEDs are a piece of cake, 20 Ampere heaters not so much.
They are *indicators*, not *illuminators*. I'm going to give you 20 (or 200 or 2000) DEDICATED PWM channels. How are YOU going to stagger theur cycles? Tell me how your implementation is going to be any better? And *simpler*??! EMI is relatively easy to control -- watch edge transition times.