Path: Home => AVR-EN => Applications => Multitimer tn24
Multitimer small AVR applications

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).

Play a video.

Page top Hardware Mounting Software

1 Hardware

Multitimer hardware schematic 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.

Page top Hardware Mounting Software

2 Mounting

2.1 PCB

Multitimer PCB This is the 45-by-53 mm PCB layout. The dimensions were chosen to fit into a Euro-PCB of 100-by-160 mm.

Multitimer component placement This the component placement on the PCB.

Multitimer drillplan PCB This is the drill plan for the PCB, all unmarked holes are 0.8 mm.

2.2 Mounting

Multitimer seen from above 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, left-handers).

Multitimer mounting sideview 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.
Multitimer mounted, top view This is the PCB with all components mounted.

Multitimer drillplan cover This is the drillplan for the acrylic glass cover.

Page top Hardware Mounting Software

3 Software

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 events (PCINT).

The following chapters demonstrate the structure of the software and how it works in detail.
  1. 3.1 provides links to the source code,
  2. 3.2 presents the two options for debugging the hardware of the device,
  3. 3.3 show the key's PCINT interrupt,
  4. 3.4 show the timer's interrupt generation at each 0.1 seconds,
  5. 3.5 demonstrates the 0.1 s interrupt processing, and
  6. 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.

3.1 Source code

The assembler source code is available here for download and can be viewed here within the internet browser in HTML format.

3.2 Hardware debug options

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.

3.3 Key PCINT interrupt

Key action flow 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 interrupt.

If at least one key is pressed it is tested if the toggle register rTgl is at zero. This register is
  1. set by any detected key event to an initial value (cTgl),
  2. is downcounted in each tenth of a second by the decisecond routine, and
  3. when zero allows key events.
If rTgl is larger than zero, the initial value is restarted again and the service routine is finalized.

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 cleared.

If bRun is set after inversion, the start procedure is absolved:
  1. the 16 bit seconds counter rSecH:rSecL is set to the number of seconds of the selected LED,
  2. the registers rPort, rDdr and rLedOff are set according to the number of seconds.
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.

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.

3.4 Decisecond timer generation

Multitimer tn24 timing 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.

3.5 Decisecond timer interrupt

TC1 0.1 seconds flow This is the complete timer interrupt flow chart. The different sections are marked to demonstrate their structure.

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
  1. the LED number in rState, and
  2. the rOff value of this second.
The LED is switched on in red (bRun flag = 1).

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.

3.6 Blinking rhythm

LED control 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 again.

LED control asm example 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.

LED duty cycle between 30 and 0 In run mode the red LED blinking is working like that (here described for the 30 second selection):
LED duty between 120 and 90 This is the cycle between 120 and 90 and the resulting LED duty over time.

LED duty calculation 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 formula
f = 9 * 256 / (Nupper - Nlower)

f for the different time periods is in any case smaller than 256.

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
  ldi ZH,High(2*LedDur)
  ldi ZL,Low(2*LedDur)
  clr rmp ; rmp is counter
  inc rmp ; Increase counter
  lpm XL,Z+ ; Read LSB from table to X
  lpm XH,Z+
  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 5,10,20,30
.dw 60,90,120,180
.dw 240,300,360,420
.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:

; 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?
  breq DutyZero
  ldi ZH,High(2*MultList) ; Multiplicator list
  ldi ZL,Low(2*MultList)
  add ZL,R16 ; Add LED number
  ldi R16,0
  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
  clr ZL
  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
  ldi ZL,Low(2*TenTable)
  add ZL,R17 ; Add to list
  ldi R16,0
  adc ZH,R16
  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

Diagram counting 420 to 1 The results of these calculations over the whole counting range from 420 down to 1 is shown in the diagram.

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.
Page top Hardware Mounting Software

Praise, error reports, scolding and spam please via the comment page to me.

To the top of that page

©2018 by