Path: Home =>
Applications => Multitimer tn24
A multitimer with ATtiny24
Multitimer with ATtiny24 and 12 LEDs
This describes a timer for between 5 seconds and
seven minutes in 12 intervals. Selected time and
run time is displayed with 12 red (running) or green
(selection) LEDs. Selection is controlled by three
buttons: up, down and run/stop.
This whole page as PDF
(20 pages, 804 kB).
This is it:
LED currents in this setting were measured and are
between 10 mA for a green LED and 12 mA
for a red LED. This is sufficient under normal
operation. ATtiny24 operating current is smaller
and is optimized by interrupt operation and sleep
mode idle. In time selection mode LEDs are switched
off after 60 seconds of inactivity, in run mode
the LEDs are switched on and off for blinking so that
the overall power consumption is further reduced.
- An ATtiny24 does the timing, reads the switches
and does the LED control.
- 12 red/green duo LEDs do the display and are
connected in a 4-by-3 matrix with port A of the
- Three buttons are attached to port B of the
controller and driven high by the internal pull
- The whole device is operated with two AAA batteries
at 3 V.
This is the 45-by-53 mm PCB layout. The dimensions
were chosen to fit into a Euro-PCB of 100-by-160 mm.
This the component placement on the PCB.
This is the drill plan for the PCB, all unmarked holes are
This is the top view of the device as it results from
the component placement on the PCB. The buttons and
the switch are designed for right-hand operation (sorry,
This is the sideview on the mounted device. It shows
the acrylic glass casing on top and on the bottom and
how the controller, the LEDs, the buttons and the
AAA batteries fit into that. The battery is mounted to
the PCB with two short cables and can be easily replaced
by removing the lower acryl glass layer.
This is the PCB with all components mounted.
This is the drillplan for the acrylic glass cover.
The software is written in assembler, of course,
to have strict control over the whole timing. The
controller is in sleep mode to lower the power
consumption as far as possible down to the active
LED current. Sleep mode is only interrupted by
the timer (0.1 second rhythm) and external key
The following chapters demonstrate the structure
of the software and how it works in detail.
- 3.1 provides links to the
- 3.2 presents the two
options for debugging the hardware of the
- 3.3 show the key's PCINT
- 3.4 show the timer's interrupt
generation at each 0.1 seconds,
- 3.5 demonstrates the 0.1 s
interrupt processing, and
- 3.6 shows the blinking rhythm's
characteristic and how to achieve it.
One special design decision of the software is that
all functions are performed within the two interrupt
service routines. No code is executed outside this
except the sleep and jump instruction back to the
loop. This feature, while a little bit exotic, allows
to produce compact code.
The assembler source code is available
for download and can be viewed
within the internet browser in HTML format.
The source code starts with two hardware debug options:
Debug_Leds = 1 switches the LEDs on with the first round
in green and the second round in red and repeating forever,
Debug_Switches = 1 lights one specific LED if one of the
three buttons are pressed. You can use these options to
test your hardware.
This interrupt occurs whenever a key input pin
changes its state. The routine has to
First of all the input port, to which the keys are
attached to, is read. It then masks all bits in the
port to which no key is attached, by a one and
compares the result with 0xFF. If that is the case
no key is pressed and the routine returns from
- detect the pressed key (Down, Start/Stop, Up),
- take the selected actions, and
- debounce the key inputs by setting a register
(rTgl) to its start value whenever a key has
been pressed, and to
- not to accept any key action whenever rTgl is
If at least one key is pressed it is tested if the
toggle register rTgl is at zero. This register is
If rTgl is larger than zero, the initial value is
restarted again and the service routine is finalized.
- set by any detected key event to an initial
- is downcounted in each tenth of a second by
the decisecond routine, and
- when zero allows key events.
If, with rTgl at zero, the Run/Stop key is pressed
(input pin is low) the bRun flag is inverted. If the
flag is zero, the number of the current LED is set
to the initially selected LED number and the LED
is updated. The LED's color is green when bRun is
If bRun is set after inversion, the start procedure
The selected LED is copied to the rState register
and is displayed. The further processing of the LED
display (down counting, PWM type of blinking) is
performed by the Decisecond interrupt service routine.
- the 16 bit seconds counter rSecH:rSecL is set
to the number of seconds of the selected LED,
- the registers rPort, rDdr and rLedOff are set
according to the number of seconds.
If the Run/Stop key is not pressed it is first checked
if the bRun flag is clear (no reaction on keys when down
counting is active).
If the Up key is pressed, it is checked if the selected
key number is already at its maximum (12). If yes, no
action follows. If no, the selected LED is increased,
copied to rState and the new LED is displayed.
If the Down key is pressed, it is checked if the
selected key number is at its minumum (1). If yes, no
action follows. If not the selected LED is decreased,
copied to rState and the LED is displayed.
The source code for this flow is not listed here, it
can be seen from the source code.
By default the ATtiny24 runs with a clock rate of
1 MHz by dividing the internal RC oscillator's
frequency of 8 MHz by the clock prescaler of 8.
For the blinking of the red LEDs (see 3.4)
a 0.1 s timing is necessary. This is achieved by
This provides with a compare match interrupt every
0.1 s. Of course, for the second the 0.1 s
pulse has to be divided by 10 before the time advances.
- prescaling the 16 bit timer/counter TC1 by 8, and
- dividing the prescaled clock rate of 125 kHz
by 12,500 in CTC mode.
This is the complete timer interrupt flow chart. The
different sections are marked to demonstrate their
On start-up the flag bLedtest is set and the LEDs run
up from 1 to 12. During this phase the register rState
is increased and the LED in rState is displayed in red
color. If rState reaches 13 the flags bLedtest is cleared
and rSelect is set to the desired start value (by default
5). Further execution is the same as if the timer has
counted down to zero: the flag bRun is cleared, the
LED number rState is set to its pre-selected value in
rSelect and the seconds counter is set to its default
time out value (cTO = 600 for 60 seconds). The LED in
rState is displayed (now in green because bRun is clear).
If outside the ledtest the debouncer register rTgl is
decreased if it is not zero. This provides for the default
of 0.3 s debouncing time.
If the down-counter is not running the inactivity time
in 16 bit counter rSecH:rSecL is decreased. If that
reaches zero, the LED is switched off by clearing the
direction port of the LEDs.
If running the rCnt register, which counts the tenth of
seconds, is decreased. If that does not reach zero the
rCnt value is compared with the rOff value. If equal the
LED is switched off by writing 0 to the DDRA port (LED
blinking in PWM mode).
If the 10-divider reaches zero, it is reloaded with 10
and the seconds counter in rSecH:rSecL is decreased. If
this 16 bit register does not reach zero, the second
count is converted to
The LED is switched on in red (bRun flag = 1).
- the LED number in rState, and
- the rOff value of this second.
If the 16 bit counter reaches zero, the bRun flag is
cleared, the rState is set to the rSelected register,
the time-out value for inactivity is set to its default
(600 = 60 s) and the LED in rState is displayed.
This is the LED control setting. As an example the LED5
is displayed. This LED has its green anode on portpin
PA0, its red anode on PA4. To be on, both direction
bits have to be high. If PA0 is high and PA4 is low
current flows in the green direction. If both bits
are reversed the current flows in the opposite direction
and the LED is red. All other outputs have their
direction bits clear so their polarity does not matter.
To switch the LED off for blinking it is sufficient to
write zero to the direction port, to turn it on again
to write the direction byte to the direction port
This is the assembler code to control the color of
LED5, depending from the current state of the bRun
flag. An exclusive or (eor) with 0x7F inverts the bit
polarity for all LED bits.
If the 12 LED combinations of the direction and output
ports are written to a table in the source code, even
falsely mounted LEDs can be adopted: just reverse the
output bit combinations.
In run mode the red LED blinking is working like that
(here described for the 30 second selection):
- The 30 second LED is blinking, duty cycle
for on is 0.1 second with 0.9 seconds
- The nearer the time approaches 20 seconds
left, the higher is the duty cycle (see diagram).
With 21 seconds left the duty cycle is 90%
(0.9 seconds on, 0.1 seconds off).
- When 20 seconds are reached, LED20 is
pulsing red with 0.1 seconds on and
0.9 seconds off. The nearer the remaining
time comes to 10 seconds the longer the duty
cycle gets (see diagram).
- The same happens when 5 seconds are
- Finally, at zero rest time, the run mode is
switched off and the LED30 is permanently green.
This is the cycle between 120 and 90 and the resulting
LED duty over time.
The calculation of the LED's duty cycle goes as follows
(here with the example of times between 20 and down to
11 seconds and time at 13 seconds.
In software the LED's duty cycle is achieved by
multiplying the difference between the time in seconds
to the next lower limit (10) with a factor f that
represents the difference between the upper (20) and
lower (10) limit, divided into 9 stages and multiplied
by 256 to avoid floating point math (just because it is
simpler, less time consuming and less memory extensive
that floating point math). f is calculated with the
f = 9 * 256 / (Nupper - Nlower)
f for the different time periods is
in any case smaller than 256.
- between 420 and 121: 38,
- between 120 and 31: 77,
- between 30 and 11: 230,
In the example's case the multiplication factor f is
230. The multiplication of the difference of 3 with
230 leads to a 16 bit wide result of decimal 690 or
0x02B2. Dividing this result by 256 (simply ignoring
the LSB of the result) leads to 2. Adding 1 to it
yields the switch off cycle in register rOff:
N = (T - Nlower) * f / 256 + 1
Note that the division result is rounded down by
ignoring the LSB of the result.
In the example: with the time in seconds at 13 the rOff
value is 3. The LED will be on for the 0.1 s pulses
in rCnt between the tenth and the third cycle, then off
for three cycles.
In assembler the following procedures are followed.
The LED to be blinking at a certain time is calculated
by stepping through a table of durations and counting
at which table entry the time is smaller or equalling
the table entry. The count is the LED to be blinked.
; Calculate LED from second counter
; Converts the counter value in rCntH:rCntL
; to the LED to be driven in rState
clr rmp ; rmp is counter
inc rmp ; Increase counter
lpm XL,Z+ ; Read LSB from table to X
cpc rSecL,XL ; Compare LSB
cpc rSecH,XH ; Compare MSB
brcc Sec2Led1 ; Repeat
mov rState,rmp ; Set LED number
; Duration table
.dw 0 ; Value is needed as lower limit for LED5
.dw 65535 ; End of table
Between 420 and 11 this is an 8-by-8 bit multiplication
with a 16 bit result (of which the LSB is calculated
but ignored). As the ATtiny24 has no hardware multiplicator
multiplication this is done via software:
The results of these calculations over the whole
counting range from 420 down to 1 is shown in the
; Calculate LED duty cycle
; R16 has LED number between 0 and 11
; R17 is the difference between the next lower time limit
; and the current time
; Result is in rDuty
clr ZH ; Result MSB to zero
tst R17 ; Is the difference zero?
ldi ZH,High(2*MultList) ; Multiplicator list
add ZL,R16 ; Add LED number
adc ZH,R16 ; Add carry
lpm R16,Z ; Read multiplicator
tst R16 ; Zero or one?
breq DutyLow ; Yes, treat different
clr ZH ; Z for multiplication result
push R0 ; Save, use as MSB for multiplicator
clr R0 ; Clear MSB
lsr R16 ; Shift lowest bit to carry
brcc DutyMult1 ; If carry clear do not add to result
add ZL,R17 ; Add multiplicator LSB
adc ZH,R0 ; Add multiplicator MSB and carry
lsl R17 ; Shift Multiplicator left
rol R0 ; And highest bit to MSB
tst R16 ; Already done?
brne DutyMult ; Go on multiplying
pop R0 ; Restore R0
inc ZH ; Add one to MSB
mov rDuty,ZH ; Set rDuty from MSB result
; Smaller or equal 10
ldi ZH,High(2*TenTable) ; Point to ten table
add ZL,R17 ; Add to list
lpm rDuty,Z ; Read from list
; Multiplicator list for LED5 to LED420
.db 0,0 ; LED5 and LED10 are extra
.db 230,230 ; LED20 and LED30
.db 77,77 ; LED60 and LED90
.db 77,38 ; LED120 and LED180
.db 38,38 ; LED240 and LED300
.db 38,38 ; LED360 and LED420
; For the seconds from 10 to 1 it is easier to derive the
; tenth of seconds from a short table instead. The content
; of the table would be:
; Table with tenth of second duty cycle beween seconds
; 10 and 0
; Note: the higher the number the shorter the LED-on time
.db 9,8,6,4,2,10 ; Note: last value for even number of bytes
Shown are the LED's numbers that are blinking
during the different time phases and the durations
over which theses LEDs are on: one tenth of a second
for a very short pulse, five tens of a second for a
half on/half off pulse and nine tenths of a second
for a very long pulse.
Praise, error reports, scolding and spam please via the
To the top of that page
©2018 by http://www.avr-asm-tutorial.net