I just posted yesterday about new software for my PWM dimmers that provides smoother control of the light levels (having 256 levels plus off, rather than 63 levels plus off) with a 256:1 brightness range rather than the approximately 256:6 range of the previous code.
There was a bit of a problem with the lowest light level—it was more like 1/20th as bright as the second highest, rather than 1/2, and it flickered annoyingly. The other low light levels were also rather far apart, causing noticeable steps in brightness for about the first 10 steps.
So today I rewrote the code to have a genuine 512:1 brightness range (plus off), without the super-dim setting. This was a bit tricky to do, because the ATtiny13A has only a single 8-bit counter to use for PWM or any other timing. I also wanted the minimum on-time for a pulse to be at least 5µs and the maximum PWM period to be less than 5ms.
What I did was to use the 8-bit PWM for the higher brightness levels, but turn it off for 3 PWM periods out of 4 for the low light levels. I use the timer overflow interrupt to keep track of which PWM period I’m on, and to turn the PWM on or off. To avoid glitches, I wait until the counter has counted up past the end of the PWM pulse, so that the PWM is only turned on or off when the output is low. (That’s not quite true—for turning the light off or operating in the top half of brightness, I can switch the PWM mode at any time—it is only when I’m trying to do one out of four PWM periods that I need to be careful about glitches.)
I don’t have a full 1024-bit PWM range. In addition to OFF, I have 303 light levels, 63 for low light levels (from a duty cycle of 2/1024 to 64/1024 in steps of /1024) and 240 for high light levels (from a duty cycle of 17/256 to 256/256 in steps of 1/256)
The conversion from the linear 10-bit analog-to-digital reading (0–1023) to pulse lengths is done by the following method:
if ADC >= 512 use SHORT PWM (256) expon = ADC>>7 frac = 0x80 + (ADC & 0x7f) pulse_len = 1+ (frac>> (7-expon)) else if ADC >= 8 use LONG PWM (1024) pulse_len = 1 + (ADC >> 3) else turn PWM off
The transition between the short and long PWM periods is evident in a plot of the pulse width and duty cycle:
The minimum pulse width is about 6.7µs, and the long PWM period is about 3.4ms. There is a trivial modification to allow 1/1024 duty cycle (change the first threshold from 8 to 4), but the shortest pulse is then too short to turn on the LED boards, giving either a very dim light or no light, depending on exactly how low the drain voltage gets in the available time.
I also added some hysteresis to the reading of the analog-to-digital converter, to avoid flicker from digitization noise when the value was near a threshold. The new code is a bit bigger than the previous code (372 bytes instead of 222), but still well below the 1kB limit of the flash.
I’ll need to reprogram the desk lamps again.