Issue with resolution of "software" PWM on attiny

Go To Last Post
54 posts / 0 new

Pages

Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi Guys,

I am making something for pwm driven strings of leds, I am now testing with a simple led per port, so no power issues. The attiny (861a at the moment, but I will be using '85s and 84's as well at a later stage) will need to drive ~4 outputs, independently. That means I cannot use hardware pwm, also because the dedicated pins must already be used by other stuff.

The good news is: it already works :-) It's not that difficult to make a software pwm implementation using a timer ;-)

Now what is the problem: at pwm duty cycle 1/255, the led is still quite bright. I'd like to be able to dim it even further.

So I tried 16 bits and later 12 bits, but it won't work. Whatever I put inside the prescaler fields or the counter compare registers, it won't go fast enough, the leds only flash, they're not dimming.

My actual questions:

1)
Is this something one can expect from an attiny running on 8 Mhz? I calculated that I should be able to get a 60 hz refresh using clock = 8 Mhz, prescaler = 1, counter compare = 2, step = 65536 (16 bits pwm): 8 mhz / 1 / 2 / 65536 = 61 Hz. Instead the led flashes. It won't go any faster, only slower if I increment either the prescaler or the counter compare by A LOT. Is this asking too much from an 8 Mhz device? I also tried using 12 bit pwm (4096 steps), but it doesn't make any difference!

2)
Would there be some smart way to increase the pwm resolution in the lower values only, e.g. in the 0-32 duty cycle range, while keeping 8 bit pwm?

3)
I am now using timer0, timer1 has a "high speed" feature; you can feed it 64 mhz from a pll instead. Would that help? I fear not myself... I can also have the whole mcu run on the pll (16 mhz), would that help? I think it would, but I think it wouldn't help enough for 16 or even 12 bit pwm...

4)
Any other smart thoughts? ;-)

Thanks.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

How do you know (really KNOW) you're running at 8MHz?

I do 6 RGB LEDs (that's 18 software PWM channels) @ 8MHz on a mega48, so I know it can be done.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

kk6gm wrote:
How do you know (really KNOW) you're running at 8MHz?

Good question :-)

I know for sure because:

- I made a test program that blinks a led, that I ran before and after changing the fuse
- i2c slave won't work at 1 Mhz (believe, I tried ;-))

Quote:

I do 6 RGB LEDs (that's 18 software PWM channels) @ 8MHz on a mega48, so I know it can be done.

Hmmm, maybe I could manage that as well, it goes wrong as soon as I use more bits for the duty cycle. Which for a part is understandable, because the cpu isn't made to work on non-8-bits values...

If you know a quick-and-easy way to assert the cpu clock, I'd like to know please, anyway.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You need gamma correction as PWM dims the LED linearly, but the eye has a logarithmic response.

How did you implement the PWM? I'd use a timer where you alternatively load the high, then the low time. Saves boatloads of CPU time.

Quote:
Which for a part is understandable, because the cpu isn't made to work on non-8-bits values...

I don't see how one has to do with the other.

Quote:
If you know a quick-and-easy way to assert the cpu clock, I'd like to know please, anyway.

If you want to know the clock frequency, enable the clock out fuse and use a scope or freqcounter on that pin to ascertain the true frequency.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jayjay1974 wrote:
You need gamma correction as PWM dims the LED linearly, but the eye has a logarithmic response.

Yeah I know. But a logarithmic curve on 256 steps doesn't give much choice in the end... On the host side (outside the attiny) I already count for that. It seems that 1<<value is too steep anyway, unfortunately. Something like "next step" is 1.5 times "current step" seems to work best. But difficult to implement (calculation) on a mcu, maybe using a lut, I'll think about that.

Quote:

How did you implement the PWM? I'd use a timer where you alternatively load the high, then the low time. Saves boatloads of CPU time.

That sounds like a real good idea, but you'd need one timer per output, still, which I don't have available. I have two timers (although I'd rather use one) and four outputs. Besides that, it's something I should remember for other projects.

EDIT: a smart approach would be to load the timer each time with the "next" output's pwm value (measured in duty cycle, not in index). Or maybe that is what you mean? Awsome!

Quote:
Which for a part is understandable, because the cpu isn't made to work on non-8-bits values...

jayjay1974 wrote:

I don't see how one has to do with the other.

As soon as you start calculating with values > 256 it will need to use two registers/memory addresses and extra instructions for carry adjustment etc?

Quote:

Quote:
If you know a quick-and-easy way to assert the cpu clock, I'd like to know please, anyway.

If you want to know the clock frequency, enable the clock out fuse and use a scope or freqcounter on that pin to ascertain the true frequency.

Quick and easy rules out using a scope or a counter in this case ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quick and easy rules out using a scope or a counter in this case
Yes, but I have a multimeter I can use as a frequencymeter (I know it is expensive, but it is 7years old and reliable).
And maybe you can make a counter/fmeter with an AVR (I know it is adding complexity to complexity, now: but later?)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah lol... I think I'll stick to led flasher for the moment. I can calculate the flashing frequency from the CLKio and from that it just adds up. The only thing it can't check if the timer runs from another clock (like the fast peripheral clock CLKpck), I guess.

I think the clock is good, the approach is the problem, I am now trying the other approach, using jayjay's suggestion.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Right now I do a fixed interrupt (60us) for my software PWM, and it uses only about 10% of CPU time, but I've been thinking about varying the interupt time over the course of the full PWM cycle. For example, start out with a 30us interrupt and advance it each interrupt to end up at 90us at the end of the PWM cycle. Then low PWM values will be on for a shorter time than they would be with a fixed interrupt interval.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I fudged up a brightness table using 1 2 4 8 16 32 64 128 255 but that wasnt enough steps, so I added a step inbetween those steps, something like 1 1 2 3 4 6 8 12 16 24 32 52 64 ?? 128 ?? 255 which is 17 steps or something odd like that. Maybe truncate it to 16 steps. 15 brightnesses and off?

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

EDIT: this is related to the post ABOVE this post, missed the last one, sorry...

Yes, I have been thinking about that too. I think the drawback is that the effective modulation frequency drops that way. If your effective frequency already is near 60 Hz, the leds will start to flicker. If you're way above that, it could be an interesting approach indeed.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

bobgardner wrote:
I fudged up a brightness table using 1 2 4 8 16 32 64 128 255 but that wasnt enough steps, so I added a step inbetween those steps, something like 1 1 2 3 4 6 8 12 16 24 32 52 64 ?? 128 ?? 255 which is 17 steps or something odd like that. Maybe truncate it to 16 steps. 15 brightnesses and off?

Interesting, you also found out that a multiplication by two is a too big step. Yeah, it's going to turn out to use lut indeed. Luckily I have "plenty" of flash for an attimy (8k, from which I'm already using 3.5k).

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

eriksl wrote:
Yes, I have been thinking about that too. I think the drawback is that the effective modulation frequency drops that way. If your effective frequency already is near 60 Hz, the leds will start to flicker. If you're way above that, it could be an interesting approach indeed.

I did a spreadsheet with the following results:

Start at 30us, add 2 (0.25us) each interrupt, final interval is 93.5us, total cycle time is 15746us or 63.5Hz.

Start at 45us, add 1 (0.125us) each interrupt, final interval is 76.75 us, total cycle time is 15523us or 64.4Hz.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Another alternative approach is not to use PWM, but PDM (Pulse Density Modulation), a form of delta-sigma modulation.

A while ago I tried the pseudo code in this Wikipedia article.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'd like to see some of OP's code. the original post talked of software PWM, and a distinct "flash" at low duty cycles.

Now, how is OP handling these low duty cycles? If 1/255 is a few AVR clock cycles, is this being done inline? Or is OP trying to service a turn-on and turn-off interrupt? That will indeed take many AVR cycles.

Also, for dimming one needs a fast PWM frequency and with the talk of prescalers it appears that a slow frequency is being used.

Quote:

That means I cannot use hardware pwm, also because the dedicated pins must already be used by other stuff.
Kind of a LOL, isn't it? The app is trying to do LED dimming, but for some reason the pin selection and choice of AVR model precludes using the facilities designed for it? Kind of like hauling gravel for a road project with a pickup truck.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
I'd like to see some of OP's code.

So here you are! https://github.com/eriksl/twimain
Look for "pwm" in twimain.c And please don't start about "inefficient code", I have deep enough understanding of compilers to know that this generates just as efficient code as a lot of the half assembly I see a lot over here, the difference only being that this is a lot more readable and maintainable.

Quote:
the original post talked of software PWM, and a distinct "flash" at low duty cycles.

Yes, that was on two occasions:
1) deliberately with a very large "counter compare" value (16 bits!) to artificially slow things down to be able to check the process
2) as soon as I started 16 and later 12 bits pwm

On 8 bits, not in "debug" mode, it works as expected (like I wrote there).

Quote:
Now, how is OP handling these low duty cycles? If 1/255 is a few AVR clock cycles, is this being done inline? Or is OP trying to service a turn-on and turn-off interrupt? That will indeed take many AVR cycles.

I don't know exactly what you mean here, but I was using the linear/dumb approach where an ISR is called on every "duty cycle tick" (0..255) and then check all pwm ports if they would need to get set to off. This is the code currently in github.

I am now refactoring it into something smarter, where the timer compare value gets set to the "next" tick where one of the ports needs to get set to off.

Quote:
Also, for dimming one needs a fast PWM frequency and with the talk of prescalers it appears that a slow frequency is being used.

I think you'd have to agree that 8 Mhz is a tiny bit too fast for a pwm base frequency, not? I am now using something like 32 khz, which boils down to 122 hz effective modulation frequency.

For the 16 bits tests, I also tested with prescaler = 1/off and very low counter compare values.

That's is NOT where problem lies.

Quote:
Kind of a LOL, isn't it? The app is trying to do LED dimming, but for some reason the pin selection and choice of AVR model precludes using the facilities designed for it? Kind of like hauling gravel for a road project with a pickup truck.

I think you should not make such statements without knowing ALL of the relevant data. For now you will simply have to accept that I have my reasons to do so and using other hardware is not an option nor is it necessary.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Just a little report from me, it might or might not be directly interesting for those who replied, but it might help others running into the same issues...

I have been struggling a lot to get 16 or even 12 bits software pwm running on my attiny861 running on 8 Mhz (confirmed), the actual update frequency of the output was way too low compared to what I calculated (using 8 bits pwm this was exactly like calculated).

Step 1 was reimplementing the pwm using a smarter approach (thanks JayJay), where the timer compare gets programmed to the next "event" instead of having the compare ISR being called at every possible pwm tick.

One test I did explained all. I had the overflow ISR record the current value of the counter register at the begin of the ISR and at the end. Now it appears, that at a timer frequency / prescaler value that is necessary for flickerless pwm output (122 Hz in this case), the timer ticks so fast, the counter has advanced by more (far more...) than 255 ticks within the ISR itself. This even counted for 12 bits pwm.

Basics:

- 8 Mhz cpu from internal oscillator, prescaler = 1
- 16 bits timer runs on prescaler = 1, 8000000 Hz
- timer completes a complete pwm wave phase in 8 Mhz / 65536 = 122 Hz.

From this data it can be easily seen that a 8 Mhz mcu (no matter which) is way too slow for this task. It would mean, for maximum resolution, that each call to the ISR could take exactly one clock tick :-O

For 12 bit resolution, this would result in 8 ticks for the ISR (due to limited amount of prescalers). So that's also not feasable.

So I resolved into moving other functions from the OC* pins to other pins and started using the OC* pins in combinations with the 10-bits hardware pwm from the 861 and that works like a charm.

Interesting note: one of the OC* pins is shared with the isp pins. I was afraid that would be a problem, but I increased the series resistor to the led from 220 Ohm to 1 kOhm and it works like a charm, both pwm and isp.

Case closed :-)

BTW I keep the 8 bits soft pwm (smart approach) around for situations where the hardware pwm cannot be used ;-) Works really well to.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hmmm, my software PWM ISR for 18 LEDs (6 RGB) runs in about 75 clocks. Your situation requires 16-bit math (more clocks) but only 4 LEDs (fewer clocks). I would expect there to be no problem keeping your ISR well under 255 clocks.

Care to post your software PWM ISR code?

EDIT: I may be misunderstanding your reference to the timer counting more than 255 ticks. If you are trying to achieve a PWM resolution of 125ns then yes, of course that is impossible with software PWM.

Last Edited: Mon. Aug 13, 2012 - 03:36 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

http://github.com/eriksl/twimain/blob/softpwm-16bit/twimain.c

The isr's are right at the top.

Note: this is an old branch I haven't developed on since the test.

Last Edited: Mon. Aug 13, 2012 - 03:31 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I'll show you mine if you show me yours

//---------------------------------------------
#define CH1ON()  PORTA &= ~0x03; PORTA |= 0x01
#define CH1OFF() PORTA &= ~0x03; PORTA |= 0x02

#define CH2ON()  PORTA &= ~0x0c; PORTA |= 0x04
#define CH2OFF() PORTA &= ~0x0c; PORTA |= 0x08

#define CH3ON()  PORTA &= ~0x30; PORTA |= 0x10
#define CH3OFF() PORTA &= ~0x30; PORTA |= 0x20

#define CH4ON()  PORTA &= ~0xc0; PORTA |= 0x40
#define CH4OFF() PORTA &= ~0xc0; PORTA |= 0x80

#define ALLOFF() PORTA =  0xAA

//-------------------------
void pwm(void){
//do 4 ch pwm
//tics counts 00 thru FF (256 levels)

  tics++;
  if(tics==0){
    ALLOFF();
    return;
  }
  if(ch1==tics) CH1ON();
  if(ch2==tics) CH2ON();
  if(ch3==tics) CH3ON();
  if(ch4==tics) CH4ON();
}

//-------------------------------
#pragma interrupt_handler timer0_comp_isr:iv_TIMER0_COMP
void timer0_comp_isr(void){
//compare occured TCNT0=OCR0   5khz
  pwm();
}


Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah, that is the "simple" approach. That's the way I used to do it. You won't get that working using a 16 bits timer by a mile ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah but it does work 8 bit. How many brightness levels can you perceive? Dont tell me 4096. You'll have to show me data from a double blind experiment where some operator hits keys and says up and down and records your response as visible or invisible, but he doesnt know whether the program really went up or down.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I also mentioned that I have no problems using 8 bits pwm.

The problem is at the very low duty cycles. At duty 1/255, the leds are still a bit too bright and that is why I need the extra resolution, it's not for the medium and higher levels. Also I mentioned before that I am all in for another smart approach to solve that ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The sw method might be able to be pushed to 9 or 10 bits. Can you rig up a 1/512 and a 1/1024 test to see if those are dim enough? If so, great.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I now have 10 bits hardware pwm running on the timer1 (see earlier pos). Works like a charm. Still not perfect lowest possible brightness, but alas, better than 8 bits. Apparently this is where the boundary resides of what's possible with an attiny.

I might make a construction using an extra io port that simply inserts a series resistor or similar.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So I could do a 1024 level sw pwm at 70Hz using a 15usec interrupt. If the handler only took 1usec, there's still some cpu left over for Real Work.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yeah about just ;-) Unfortunately I can't accept any flickering and the choices of prescalers is also limited...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

70Hz works with CRTs with a 30ms phosphor. I guess 100Hz is better with LEDs. You are correct.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Apparently this is where the boundary resides of what's possible with an attiny.

Fooey.
Quote:

And please don't start about "inefficient code",...

1) As near as I can see in a thread scan, you haven't told us what speed your mystery Tiny is running at.
2) >>Of course<< if you are only going to do your port work inside of ISRs, you can't get back immediately (within a very few AVR clock cycles) to do a next operation.
3) To make a skinny ISR, you need to set out to create a skinny ISR. With all the local variables and looping and array accesses--fuggedabout it.
4) So, you could do very short duty cycles "in line", for one case.
5) Your main() returns immediately. Where does it return >>to<<? What does your toolchain do when that happens?
6) Short answer is to unroll the loops, use global register variables (problematic with your toolchain), and prepare all the values outside of ISRs.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The attiny is running at 8 Mhz, I have mentioned that several times...

I agree with most of your remarks. BUT the bottom line is, no matter how efficient you make your code, the ISR will never complete within 1 or 16 clock cycles which I calculated in an earlier post.

The way I have programmed this, makes it very easy to adapt the code to other configurations, the number of io's, what ports they're on etc. cannot be hardcoded into the program.

If were confident that it could be actually done (that is: 12 or 16 bits pwm in software on 8 Mhz attiny), I would examine the assembly output of the ISR to optimise the C code even further. But I am very confident now it simply cannot be done, so that would be waste of time.

The function usi_twi_slave doesn't return, at all. It calls twi_callback whenever a complete i2c frame (start--data--stop) has been received. In the meantime it spends most of it time in a sleep_mode. As the attiny's have no support for an interrupt on a stop condition, I need to do it by polling the stop condition bit. And that is where it spends all of it's "spare" time, when it's not in an ISR and not in sleep_mode().

What is your grudge with local variables anyway? The only thing that happens is a decrement of the stack pointer...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

BUT the bottom line is, no matter how efficient you make your code, the ISR will never complete within 1 or 16 clock cycles which I calculated in an earlier post.

Well, "Duh". No experiment is needed to come to that conclusion.

Quote:

The way I have programmed this, makes it very easy to adapt the code to other configurations, the number of io's, what ports they're on etc. cannot be hardcoded into the program.

I don't get the point. Let's agree that your code is quite elegant--I'm losing it on the "hardcoded" point.

Quote:

If were confident that it could be actually done (that is: 12 or 16 bits pwm in software on 8 Mhz attiny),

What is the purpose of high-resolution LED dimming? The eye is only likely to perceive like log(bits) different levels anyway. And a relatively high refresh rate is needed to avoid flicker.

For modest "software PWM" rates, as is used for hobby servos, interleaved channels work fine. Tiny or Mega or whatever.

Quote:

The function usi_twi_slave doesn't return, at all.

I'm talking about main(). As I said.

Quote:

What is your grudge with local variables anyway?

Your aim is skinny ISRs. You are talking about getting down to a few cycles. So examine one of your ISRs. Adjusting the stack pointer takes no cycles?

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
Quote:

BUT the bottom line is, no matter how efficient you make your code, the ISR will never complete within 1 or 16 clock cycles which I calculated in an earlier post.

Well, "Duh". No experiment is needed to come to that conclusion.

Well don't forget I am a newbie at microcontrollers (not quite at other hardware and software)...

I asked and nobody said "don't waste your time because it's nonsense". So I found out myself ;-)

Quote:

I don't get the point. Let's agree that your code is quite elegant--I'm losing it on the "hardcoded" point.

I guess we're talking about difference aspects of hardcoded. Yes you are right, all of the ports and bits are known at compile-time. But there is simply no way to completely get this kind of flexibility only using enums and #defines (i.o.w. compile times), which, I agree, is always better, if possible.

Quote:

Quote:

If I were confident that it could be actually done (that is: 12 or 16 bits pwm in software on 8 Mhz attiny),

What is the purpose of high-resolution LED dimming? The eye is only likely to perceive like log(bits) different levels anyway. And a relatively high refresh rate is needed to avoid flicker.

Like I explained a few posts ago. I don't care for the actual resolution, it's just that the steps at the very low end (1..10/256) are still too big. I actually don't _need_ 10, 12 or 16 bits of pwm resolution, I _need_ to have smaller duty cycles at the very low/dim end (only). But there is simply no way I can think of to solve that without expanding the resolution.

Quote:

Quote:

The function usi_twi_slave doesn't return, at all.

I'm talking about main(). As I said.

Main calls usi_twi_slave() and never returns from that function. What is exactly not clear? The return(0) is never reached and is only there to keep the compiler happy.

Quote:

Quote:

What is your grudge with local variables anyway?

Your aim is skinny ISRs. You are talking about getting down to a few cycles. So examine one of your ISRs. Adjusting the stack pointer takes no cycles?

Yep, and if I'd know the 12/16 bits pwm would actually be possible in software, then I'd actually care, if you know what I mean ;-)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

eriksl wrote:
One test I did explained all. I had the overflow ISR record the current value of the counter register at the begin of the ISR and at the end. Now it appears, that at a timer frequency / prescaler value that is necessary for flickerless pwm output (122 Hz in this case), the timer ticks so fast, the counter has advanced by more (far more...) than 255 ticks within the ISR itself. This even counted for 12 bits pwm.

Basics:

- 8 Mhz cpu from internal oscillator, prescaler = 1
- 16 bits timer runs on prescaler = 1, 8000000 Hz
- timer completes a complete pwm wave phase in 8 Mhz / 65536 = 122 Hz.

From this data it can be easily seen that a 8 Mhz mcu (no matter which) is way too slow for this task. It would mean, for maximum resolution, that each call to the ISR could take exactly one clock tick :-O

Huh?
In the range where one needs a very short pulse,
have the pulse begin and end within the ISR.
At 1000 Hz, a 1/1000 duty cycle only requires a microsecond, 8 cycles.
Interrupt housekeeping takes more than 8 cycles,
so this will consume more than 0.2 percent of your cpu time.
You have two handles on the duty cycle: pulse frequency and pulse length.
You don't even need 16 bits.

250 Hz and 32 cycles will give the same duty cycle.
If you want to do dimming and 32 cycles isn't too many,
you now have 33 possible brightness values without adjusting the frequency.

Moderation in all things. -- ancient proverb

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

But there is simply no way I can think of to solve that without expanding the resolution.

I mentioned how to do that--handle short periods inline instead of invoking another ISR. You can run 32-bit or more PWM, and you still cannot service consecutive ISRs any faster. I'd wager your ISRs are in the hundreds of AVR cycles.

In fact, if that is the aim of the whole exercise--soft PWM for a finite number of LED channels--then you would approach it as is done for "soft DDS" and similar: You turn the app around and work in a mainline loop. Either counting cycles, or use a free-running timer for reference.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

At prescaler 1 (so counter ticks at 8 Mhz), the ISR takes around 700 ticks, so that probably can be somewhat a lot ligher, indeed.

Yet I am still convinced that a soft pwm with true 16 bit resolution is not possible on a 8 Mhz clock, however "lightweight" the ISR may be, even when run from the main loop instead of from a timer interrupt.

Besides that, some of us have other things to be done in the main loop, like twi stop condition detection :-(

Anyway I am satisfied with how it works now.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

Yet I am still convinced that a soft pwm with true 16 bit resolution is not possible on a 8 Mhz clock, however "lightweight" the ISR may be, even when run from the main loop instead of from a timer interrupt.

I don't see why. 16 bit resolution means the counter counts to 65536 and with \1 prescaling this means the max frequency is 8000000/65536 = 122Hz but as long as you can live with that where's the problem?

if the ISR entry/exit is a problem (maybe 30 cycle overhead?) then why not poll it though I agree that slipping in the TWI with everything else could be "fun".

Do LEDs really need 65536 levels of PWM anyway? Can a human eye see the difference between an LED lit at a 57,203 and 57,204 PWM duty?

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The problem is that handling the interrupt takes so many cycles that the effective (!) resolution goes under 8 bits. Then you'd better use real 8 bits pwm, saves a lot of cpu cycles...

I've already explained a few times. It's not the range from, say, 10-65536, but the range from 0 to 10 where I need the resolution. My leds aren't dim enough with 1/256 duty cycle, that's all. I now do it with 10 bits hardware pwm, that's already a bit better still not perfect.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

The problem is that handling the interrupt takes so many cycles that the effective (!) resolution goes under 8 bits. Then you'd better use real 8 bits pwm, saves a lot of cpu cycles...

Well that's a job for a clever programmer. For a start ditch C and take control in Asm. But as there's a 12+ cycle overhead for even the empty RETI consider whether you really want to waste those cycles using interrupts anyway.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

You should vary the current through the LEDs; as an extra control atop of PWM.

That's how professional LEd drivers do it to achieve 1% dimming capability.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The OP is up against the logarithmic response of the human eye. Trying to deal with that in a linear control system is always a problem when you need more than a decade of response.

Various hacks have been suggested. In the end, none are as satisfying as starting with enough resolution and shaping the drive which throws away resolution at the bright end. Alternatively, you can approximate it piecewise linearly by using some second decade scale control agent. Jayjay has suggested one such way.

Jim

 

Until Black Lives Matter, we do not have "All Lives Matter"!

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

255 isnt dim enough. Is 512 dim enough? You can do that in hw or sw. 1024 you probably need hw pwm.

Imagecraft compiler user

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I was recently experimenting with a pwm led dimmer.

I used a table driven approach where I just step through the different pulse widths and can adjust them as i like.

what i found was that even a very short pulse resulted in observable light being emitted.

I inserted a sequence of zeroes in the table to give the led/eye/photons a chance to decide that the led was off.

regards
Greg

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Even in C, one could probably get a single cycle pulse.
How often would you need a single cycle pulse to give
you the lowest non-zero apparent brightness desired?
How often for the highest brightness desired?
Don't just say it can't be done that way.
Even if it can't all be done that way,
the numbers are still useful.

Moderation in all things. -- ancient proverb

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

For a start ditch C and take control in Asm.

Cliff, Cliff, Cliff...

If indeed you are tossing this C vs. ASM w.r.t. ISRs bait into the water, I will rise to it. Unless there is great advantage in the algorithm to the hit list of stuff that C doesn't have (that includes rotate, 24-bit arithmetic, and a couple other well-discussed items), there is no inherent advantage in ASM to constructing a skinny ISR versus a C implementation.

Now, your >>toolchain of choice<< may not have good facilities. But that is a different war.

[In this particular case, OP is making an ISR that in all probability takes three figures of cycle counts. And then having problems in getting back there in a few counts. Repeated suggestions on doing short duty work inline have been roundly ignored.]

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Lee, Lee, Lee,

Suggest you read the entire thread including this:

Quote:
So here you are! https://github.com/eriksl/twimain

I take it you did not follow the link/open the files else you would know that OP is, indeed, using GCC so my suggestion that you can shave some cycles by Asm implementing the ISRs (and in particular looking at the prologue/epilogue and actual register usage) *IS* a most relevant suggestion here.

Early looks at 4.7.x suggest its peep-hole optimisation is getting better (and LTO etc also help) but I think it's unlikely OP is using that and he almost certainly has 4.3.4 from WinAVR or 4.6.2 from AS6 so the skinniest solution for ISR would be hand crafted Asm. (usual rules about generating the .s then hand optimising apply).

Cliff

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jayjay1974 wrote:
You should vary the current through the LEDs; as an extra control atop of PWM.

That's how professional LEd drivers do it to achieve 1% dimming capability.


With an AVR I've used software PWM along with much faster hardware PWM (completes an entire PWM cycle in each software PWM slot) to control the current through the common of the LEDs.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi there

This may not suit your application, but if you can tolerate a maximum 25% duty cycle for your 4 LEDs (e.g. by using more efficient ones), AND can spare one 16-bit PWM output, you can get a 14-bit resolution at 61Hz.

Connect all LEDs' common (e.g. all anodes) to the PWM output, and each of the other ends (cathodes, in this case) to its own output. Set the PWM cycle length to 16k, and interrupt once per cycle. Each interrupt, tri-state the current LED's output, enable (low) the next one, and load a new duty cycle (0..16383). Note the duty cycle will be out-of-phase with the selected LED, because of the double-buffering, but that's a Small Matter of Programming... Because the LEDs are only on one at a time, each gets a max 25% duty cycle, but excellent resolution, and extremely low interrupt overhead.

Cheers

Jonathan

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Let's do some math.
Suppose you want the dimmest possible non-dark LED given a pulse frequency at least 60 Hz.
A pulse is at least a single cycle.
With a CPU frequency of 8 MHz, the duty cycle is
(1/(8MHz))/(1s/60)=7.5*10**-6 .
From previous comments, a duty cycle of 1.0*10**-3 might be the brightest useful.
This can be achieved with a 32-cycle pulse every 4ms, i.e. at 250 Hz.

The ratio of duty cycles is about 133:1 .

These numbers do not depend on the timer resolution.
You can do both with an 8-bit timer.

Moderation in all things. -- ancient proverb

Last Edited: Thu. Aug 16, 2012 - 01:14 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Are your LEDs active high?
If so, are they bright enough if you power them through the built-in pull-up resistors?

Moderation in all things. -- ancient proverb

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

jayjay1974 wrote:
You should vary the current through the LEDs; as an extra control atop of PWM.

That's how professional LEd drivers do it to achieve 1% dimming capability.


Yes, exactly what I had in mind.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:

Early looks at 4.7.x suggest its peep-hole optimisation is getting better (and LTO etc also help) but I think it's unlikely OP is using that and he almost certainly has 4.3.4 from WinAVR or 4.6.2 from AS6 so the skinniest solution for ISR would be hand crafted Asm. (usual rules about generating the .s then hand optimising apply).
Cliff

eos erik:~ $ avr-gcc -v
Using built-in specs.
COLLECT_GCC=avr-gcc
COLLECT_LTO_WRAPPER=/usr/libexec/gcc/avr/4.6.3/lto-wrapper
Target: avr
Configured with: ../gcc-4.6.3/configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --target=avr --enable-languages=c,c++ --disable-nls --disable-libssp --with-system-zlib --enable-version-specific-runtime-libs --with-pkgversion='Fedora 4.6.3-1.fc17' --with-bugurl=https://bugzilla.redhat.com/
Thread model: single
gcc version 4.6.3 (Fedora 4.6.3-1.fc17)

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

skeeve wrote:
Are your LEDs active high?
If so, are they bright enough if you power them through the built-in pull-up resistors?

The leds will be switched using a mosfet, I've now connected simple "dummy" led, "normal" 20 mA ones, with a series resistor in between. This is also an interesting point. It should be possible, I've heard, to connect a 20 mA led directly, without harming either the mcu or the led. But I don't dare ;-) So I put a 330 Ohm resistor in series. Some of the leds even have a 1 kOhm resistor in series, because they share the isp pins. On the other hand, once you're going to connect a mosfet, you need a resistor of ~ 50 Ohms instead. This might pose a problem for the shared pins. But I'll cross that bridge when I come to it. I have some specialised mosfet drivers lying around, maybe I'll deploy these.

To answer your question, so, yes the leds are driven "high" from the mcu itself, it sources, not sinks.

Pages