Nanjg 105C Ramping Code Alpha Release

Releasing a ramping only code for the 105C ATtiny 13A. This is an Alpha release. Why is it an Alpha release? Because:

  1. their is no eeprom wear leveling.
  2. no low voltage battery warning.
  3. there is a bunch of debugging stuff included. Want to see? Uart TX is located on PortB.4 at 2400 bps.

The UI is for a standard single power switch type of button operation (i.e. no momentary involved). Click on and off in less than a second and you are in the ramping mode next time you click on (I,m pretty sure that is the way it works, it’s been awhile since I’ve actually worked with this code). 64 levels (gamma corrected) of ramp power are cycled through from the last saved ramp power until power is removed. Next power on starts at new power level.

The hex file is located here: https://sourceforge.net/projects/flopdriver/files/ATtiny%2013A%20Code/
The clock fuse is set for 9.6Mhz.

I am not so sure this topic hasn’t been covered before, but some of the coding threads are really drawn out, and a little dry for the reading unless you are directly involved. Discuss.

Damn should of checked BLF before building tonight’s drivers, just flashed two with my normal ramping only version of luxdrv.

I will D/L and try this tomorrow.

CK, lemme know if it works. The ramping is low -> high, low -> high. I kind of like low -> high -> low with a bit of a pause at the high and low values. If there is enough interest out there, I’ll iron out the kinks.

TY. I will look at it next week when I get some time (I hope) to play with lights :-)

Ok I’ve got it running on the bench, I think it would help to add a .5sec delay on the highest (for sure) and probably the lowest levels to make it easier to hit them, the steps are pretty fast. I do like that from a set level a double tap of the reverse clicky is a shortcut to set it on low so it may not need any delay on low cause of that. I will get this into a light later today for better testing.

Hey thanks for testing.

Yep on the pause at high and low. The UI was derived with a forward clicky in mind. So thinking the pause at low might have to do.

The ramp is a little fast. This provides a nice smooth transition between steps, but makes it hard to hit a particular level. With too slow a ramp, the power level steps can become kind of distracting. Have to recheck best compromise.

Looks like I,m going to have to find out where my test rig is hiding, and get to going.

I tried this on my test driver. It seems to work exactly as described, and I can see lots of activity happening on some of the extra pins (I have LEDs attached to those pins, so the serial activity makes them light up).

It’d be nice if the low was lower though.

I’m making a ramping UI soon for momentary switch lights. Not sure of the exact details yet, but it’ll ramp while holding the button, switch directions if you release and hold again within like 2 seconds, and remember the last level for next time (short-press will just turn it on and off at the saved level). Maybe some extra stuff for shortcuts to max/min too.

The lowest levels on mine will be pretty distinct from each other though… I’ll be taking the PWM down all the way to 1/255, and when it’s that low a change of just one makes a big visual difference. High levels should be pretty smooth though.

@toykeeper,
I have used a low that is somewhat conservative of 4/256, and should work in most all situations. Going below this entails a more thoughtful approach, and maybe hardware selection. A lot of my development was with a high Vf led, whereby a 1/256 just won't cut the mustard.

As mentioned earlier I like the ping-pong ramp approach, and in a relatively quick fashion. I like your approach to the momentary switch.

Agreed on maximizing the response over the low range, to make an-otherwise logarithmic output into a linear one. You can see the first 16 values of my 64 table:

4
5
5
6
7
8
10
11
12
14
15
17
19
20
22
24

So I have 16/64 or 25% of the light level being achieved at 24/256 power (approx. 9%).

EDIT: I had PB2 as an open input for the yet to be coded voltage divider. So that pin can bounce all over the place.

Oh, I’m currently using a FET, so even a PWM level of 0 will still light up the LED a little when the attiny isn’t sleeping. I’ll definitely make the lowest level configurable though, since the lower end is highly hardware-dependent. I have some hardware which won’t light up until 9 or so.

As for implementation, I’m not planning to use a table if I can avoid it. Instead I’m hoping to do a multiplier for both directions via fixed-point integer math (and right-shift it to make the result fit into the 8-bit PWM range). I’m just not 100% sure it’ll all fit into the available space. I expect it should be okay as long as I never divide anything though… one division takes like an extra 120 bytes!

@toykeeper
When is zero PWM not zero? You need to poke the register to disable?, haha.

Guess I’m having a hard time on how integer math is going to be used to fit a logarithmic function. You could go with the ATtiny25 for some more room on a larger table? or your math? I’ve played with bigger tables, and haven’t seen the need. Not likely you are going to hit on a particular number anyway, unless the ramp is painfully slow, and distracting at low levels.

0 PWM with "Fast PWM" is never truly off while the attiny is on (a good exploit for even lower moonlight levels with the FETs)

Interesting, I guess I hadn’t noticed that 0 PWM behavior yet. Good to know.

I tried it today, and it worked. However, the lowest level ramps annoyingly slowly because the logarithmic growth takes a long time to go from 1 to 2.

It’s easy to do with fixed-point math, which is common on systems without good float support. Not sure if this will make sense, but here’s an example of one ramp step using fixed-point:

    uint16_t brightness;
    int8_t ramp_direction = 1;
    brightness = 2 << 4;  // PWM of 2/255
    ...
    brightness = (brightness * (16 + ramp_direction)) >> 4;
    if (brightness > (255 << 4)) { brightness = (255 << 4); }
    if (brightness < (LOWEST_MODE << 4)) { brightness = (LOWEST_MODE << 4); }
    PWM_LVL = brightness >> 4;

So, it steps up/down by 1/16th each time. This particular example steps from a PWM_LVL of 2.0/255 to 2.125/255. The main difficulty is that it can roll over at the high end since it’s using only a 16-bit integer. That’s easily fixed by using 32 bits instead (which gives us more fixed-point resolution too), but it takes a bit more flash space.

And I’m not really convinced it’s better than using a table. The main benefit is that it works regardless of what “LOWEST_MODE” is hard-coded to. But it also isn’t as easy to tweak the shape of the curve. So, I’ll probably just try both approaches before deciding on one. Maybe include a short script with the source to generate an appropriate table for any given LOWEST_MODE value.

Bit fuzzy on the maths, heh, but might be interesting if you could actually express the extra bits in PWM? When using my table, the spreadsheet uses an Yexp2.2x (gamma), which gives a lot steeper curve? Interested to see what you come up with.

Just for info: PIC’s have a 10 bit PWM resolution capability if the clock is high enough, and the PWM frequency low enough, (e.g. 20MHz and 19.5kHz). Also in play would be using the 16bit Timer/Comparator module to effect the PWM in software.

The extra bits are not expressible in PWM, since the attiny13 has a single 8-bit counter. That’s kind of the problem, and why the lowest modes don’t ramp smoothly. I have to truncate those extra bits away in order to fit the value into the PWM level. The fixed-point math simply lets me do the calculations at a higher resolution, without the cost of importing a floating-point emulation library.

It’s nice that PICs can do 10 bits or even 16 bits, but I’m not dealing with a PIC.

In any case, my implementation is currently pretty buggy. Having some trouble getting it to reverse and un-reverse the ramp direction at the right times, and getting it to turn back on after saving the current level with a short press. But I also only spent about an hour on it so far and was rather tired at the time so I probably missed some obvious things.

One way I see making the 8bit PWM smoother at low levels would be to somehow dither the output. That would mean using say 8 (or 16) single shot PWM’s. So like 7 shots at 2/256 and 1 at 3/256, six at 2/256 and 2 at 3/256, and so forth; kinda complicated.

My example saves each step to eeprom, which is why it needs eeprom wear leveling to be added :slight_smile: .

I don’t think I care to manually switch the PWM level at high speed to emulate a middle level. It’s do-able, but I think I’ll need those bytes to make the interface more robust. Definitely an idea to keep in mind for later though, in case I have any room left over.

If I were to make the table bigger, I could encode the dual-level PWM pattern into the bits of another byte… like, 01010101 for a 50% blend, 00010001 for a 25% blend, etc. And the attiny can do sub-millisecond delays between, which would give it a secondary PWM speed of perhaps 2 kHz. It’d probably still have a visible flicker though.

As for eeprom, I’m using a momentary switch so I can just use RAM. It remembers everything until the battery is physically disconnected, which doesn’t happen often. However, if I wanted to support dual-switch lights I’d need to save the saved level to eeprom instead of just RAM. This happens only on an e-switch button press though, so it’s very unlikely to wear the eeprom too much. If I need it, I’ll copy the relevant code from the clicky switch version of STAR.

So… I think I found a different, better way to get in-between PWM levels.

Normally at 1/255 it’s on for 1 “frame” then off for 255 “frames”, right? At 2/255 it’s on for 2 and off for 254… 3/255 is on for 3 and off for 253… etc. 100/255 is on for 100 and off for 156… Right?

I think the attiny13 has a way to adjust both the on-time and the off-time. So, it could do 1/255, 1/239, 1/224, 1/210, 1/197, 1/185, 1/173, 1/162, 1/152, 1/142, 1/133, then perhaps 2/255, 2/239, 2/224, 2/210, 2/196, 2/185, then 3/255, and so on. Smooth ramping between the really low modes. This is accomplished by changing the PWM frequency when the duty cycle resolution isn’t high enough.

Now, I suppose I need to find a way to free up 128 bytes of ROM for a table of 64 PWM levels and 64 PWM ceiling values… and then I can have a really smooth 64-level ramp.

Good call on changing the frequency! Never tried adjusting on the fly, so not sure on what effects, or resolution, you might expect on the PWM output. Something I always wanted to try is this Pulse Frequency Modulation (PFM).

I think there might be a problem using that idea with the 13A though. Looking at the PWM frequency formula you are only given the option of changing the prescale I think, which is just too rough. This is doable with the ATtiny25 series by changing the OCR1C register. I tried this once with the 25 to get a very close approximation of 20khz frequency PWM, and it went something like this:

#define PWM_LED OCR1B
dir PortB.4 out
TCCR1 = B'00000010'         'PRESCALE 1/2, 8MHz CK fuse, equals 16.5KHz PWM
OCR1C = 210         'With PCK 1/2, GETS 20khZ
GTCCR = B'01100000'         'enable PWM1B

EDIT: I'm sorry I needed to change the comment back to the original CLK fuse of 8MHz on the ATtiny25

So, FWIW… I tried it on an attiny13, and it works.

It took me a little while to figure out why it was wonky with a large table, but it turns out I needed to declare it as PROGMEM and use pgm_read_byte() to access it. After getting that part worked out, and spending a while reading the attiny13 reference manual, I managed to get it working nicely.

So, the only limit now is how low the lowest mode can be. Even 1/255 on this thing is still brighter than I’d like.

Oh, and I also had to add in sort of a “fudge factor” in the table generator calculations to make the output transition smoothly from N/X to (N+1)/255. But it works, and I’ll include the source in my open firmware repository after I get this ramping UI code working better. The ramp is great, but now I need to do the rest of the UI. :slight_smile:

For now, the secret is:

#define PWM_LVL     OCR0B   // OCR0B is the output compare register for PB1
#define CEIL_LVL    OCR0A   // OCR0A is the number of "frames" per PWM loop
...
TCCR0A = 0x23; // phase-correct PWM
TCCR0B = 0x08 | 0x01; // 8 = "top" compare mode, 1 = pre-scaler-set-to-1
...
PWM_LVL = pgm_read_byte(modes + mode_idx);
CEIL_LVL = pgm_read_byte(ceilings + mode_idx);

… and there you have the basics for PFM / pulse frequency modulation combined with PWM / pulse width modulation.