(This is a bit sentimental intro) Sure, I searched for a ready firmware.. and didn't find anything close to what I want. Then sure, I wanted to take some good-looking project as a basis. I looked at some, and thought Yenya's is most "advanced" and suitable. It has some data structures for different blinking patterns, for buttons handling, for just about everything - the author is computer scientist. However, because my buttons handling was supposed to be far more flexible.. and because my case was different in many other aspects.. I realized there are really few things I want to take from it. Eventually I decided to only borrow the idea of implementation of "multi-tasking" (simple really).
I decided to not try to make any software toolkits (that would limit handling in some ways), and to simply introduce a large set of global variables that defines the state, and implement everything with many-many "if"s. Then I would go thru those "if"s on each WDT interrupt (at 60Hz). Yes, this way is known to be prone to confusion - but mainly after adding new stuff to the old code over many years. But since I planned it all - in advance, and from scratch, I could design my set of variables to be mutually-orthogonal, and have well-defined purpose and meaning - before I started to write any code. I did it - as much as it could be reasonably done. And the code is well structured (as it looks to me). In this sense there are few "outlandish" pieces (yes, there are some.. I didn't foresee some peculiar traits that came up later during debugging).
One could move the buttons handling itself into a small fast WDT handler, and the rest - make big, slow, and really, logically interruptable. However, (a) as a simple experiment showed, even 128kHz is enough to (almost) always finish all processing during 1 WDT period, and (b) such button-handling accuracy would only come at the price of (significant) complexity of the majority of the code - to implement interruptability reliably. This is why on a logical level all the buttons/LEDs handling is atomic.
The whole firmware weighs around 4.5k, i.e. doesn't fit in the tiny45. So, for calibration (if you want it), first remove some non-essential functionality (like CANTOR_LOCKING), enable calibration define CANTOR_V_T_SETUP, calibrate. Then re-program with removed calibration defines, and with added essential functionality. For tiny85 one can define everything, and not bother.
I (tiny45) use version without: CANTOR_LOCKING_SAVE_STATE (not enough space after recent changes; driver will always require password on a new battery connection) and CANTOR_V_T_SETUP (and of course, without CANTOR_SPEED, EE_LOG - this is for debug), and with: CANTOR_SLOW_WDT, CANTOR_LOCKING_USE (locking use enabled; without it - no passwords, ever), CANTOR_LOCKING_EDIT_PASSWD, CANTOR_LEARN_SINGLE_BLINK.
To my taste - fully debugged and ironed out.
For multi-tasking, one could use some tiny operating system, like FemtoOS, instead of executing the "if"s on each WDT interrupt. However:
So using an OS looks more an unnecessary complication than a simplification or readability improvement. Still, I'd explore the FemtoOS more. But mainly because it would be really cool to use an OS in AVR! :)
I used a mega168 - it has serial interface - very convenient to send commands in there, and receive debug output from it. I have Atmel STK500 board, but its LEDs and buttons have different/inverse connection. So I soldered up a prototype, with a connector to power and re-program it, and with a separate communication connector (needs an interface electronics - which is built-in on the STK500)
As to tiny45 - I had to program it on STK500, and then inserted it in the panel connected to the prototype.
The problem "I pressed short (or wanted to press short..), but it came out as long" is obviously not solvable, even if you make a single unified electronics for both Far/Near headlights (or implement a communication channel between them). Introduction of some "ignore zone" (instead of the sharp boundary) in the duration analysis will lead to user frustration "I pressed, but it didn't respond".
Hence the rule: Normally (unless in setup), let your long press be long, your short press be short, and the successive presses have short pauses in between. (what "short" and "long" mean - see the Learn mode).
When electronics Far/Near is separate (my case), each half has to know about brightness level of the other. Then the problem will manifest itself in knocking off the pre-set brightness difference between Far and Near. This particular *symptom* - is solvable, but the solution is complex (communication channel). It's far easier to just get used to the rule, and introduce a special command to reset the mode into some common default - this is what I did.
I inserted a delay in the main loop, to simulate slower frequency. Then, for some generic user activity (like on/strobe/change brightness/mode/off/etc ~30-seconds), I recorded # of times the firmware couldn't complete all processing within 1 WDT period.
Results. 1st line - delay, ms, taken away from 1 WDT period, ~22.4ms (nominally should be 16). Values are very approximate absolutely, but more accurate relative to each other. 2nd line - # of times.
# 1MHz CPU, all ADC included <8 10 12-14 16 18 0 5 6-7 11 40 # 128kHz, all ADC included 0-4 5 6 8 7 8 14 130 # 128kHz, long ADC excluded, short included 0-1 1.5 2-4 6 0 2-3 6 12
No comments, pretty clear. 128kHz is barely, but enough. I use this 128kHz frequency for all head, and brake lights.
(Note: as of rev. Q, 08/2013 of the tiny45 datasheet, Atmel measures idle current *with analog comparator - AC - on*, which unnecessarily consumes additional power due to internal ref voltage. They confirmed that this is confusing, and will clarify it in the future datasheet release.)
Vcc=3.5V. Brake. Minimal tail light, LED eats ~0.18mA (LED only, no MCU). 60Hz WDT (not slowing down). No user activity: some CPU processing, and a bit of idle-sleep for PWM. AC on. MCU (only, w/o LED) consumes 0.30mA @1MHz.
If frequency=128kHz (also everywhere below): 300->67uA.
If AC disabled (also everywhere below): 67->24uA.
If WDT is slowed down to 4s (this gives idle current): 24->17uA.
This current (17uA) is what the MCU with my Cantor firmware consumes when
there's no user activity and no info-blinking, but the power LED is on.
If not sleeping at all (this gives active current): 24->74uA (PWM=timer0 is on, and 70uA if off).
(so if active=74, idle=17, average=24, the average active duty cycle would be (24-17)/(74-17)=0.123 - unexpectedly small, and shows that 128kHz is - on average - far high enough frequency.)
Pull-ups consume many times more current than MCU, info led - many times more than pull-ups, and power LED - many times more than info led.. So this MCU power reduction is not really needed. It's mainly to satisfy my aesthetic taste (properly done). And to make it a verified example-starting-firmware for many other purposes.
In power-down mode (no LED PWM, WDT stopped) datasheet says ~0.2uA (I couldn't measure anything). This is what the headlight consumes when it's off.