compudanzas

tomatimer

a small pomodoro timer on an attiny85, programmed with avr-asm.

part of the s-camino practice.

photo of the tomatimer: a small circuit with two leds, a buzzer, the attiny85, all of them over a plastic card.

about

tomatimer works as a 25 and 5 minutes timer, cycling between them.

a couple of LEDs indicate via blinking which one of the two timers is active at a given moment.

at the end of each timer, a buzzer plays an alarm during some seconds.

all the durations can be reconfigured via the source code if needed.

connections

     ┌──────┐
     │1    8│  VCC
     │2    7│  PB2: LED state 1
     │3    6│  PB1: LED state 0
GND  │4    5│  PB0: Buzzer
     └──────┘

LEDs are connected such that an output of "high" will turn them on.

source code

with billingual comments

; tomatimer.S
#include <avr/io.h>

; TIMER1 tiene su reloj a clk/512 (~1953 Hz)
; contando 244 pulsos se va a 8Hz (frame freq)
; i.e. 8 frames son 1 segundo
; r16 y r17 se usan como registros auxiliares

; duraciones en minutos de los timers
#define DUR_TIMER0 25
#define DUR_TIMER1 5
; duraciones en segundos de la alarma
#define DUR_ALARMA0 3
#define DUR_ALARMA1 5

; más chico es más agudo
#define WAVE_PERIOD0 3
#define WAVE_PERIOD1 6

; teórico: 244
#define TIMER1_PERIOD 240

#define rFrames r20
#define rSegundos r21
#define rMinutos r22
#define rFlags r23

#define fAlarmOn 0
#define fAlarmLevel 1
#define fCualTimer 2

#define pinAlarm PB0
#define pinState0 PB1
#define pinState1 PB2

; para hacer fast modulo 8
#define MASK_FRAMES 0x07

; ***********************
; vectores de interrupts:
; ***********************
; Reset
.org 0x0000
	rjmp main

; dirección 0x0003 * 2
.org 0x0006
	rjmp timer1compareA_isr

; dirección 0x0009 * 2
.org 0x0012
	rjmp timer1compareB_isr

; ***********************
; Main
; ***********************
.org 0x001E
main:
	;----------------------------------
	; configuración general
	;----------------------------------

	; habilita interrupciones
	sei

	; pin PB0 como pin de salida
	ldi r16, (1<<DDB2) | (1<<DDB1) | (1<<DDB0) ; pins de salida
	sts DDRB, r16

	;----------------------------------
	; configuración TIMER0 para buzzer
	;---------------------------------
	; CS0: 0b011, clock select clk/64
	; WGM0: 0b010, Clear timer on compare match (CTC mode)
	; COM0A: 0b01, toggle OCOA on compare match (0b00 para apagar)
	ldi r16, (0<<WGM02) | (0<<CS02) | (1<<CS01) | (1<<CS00)
	sts TCCR0B, r16
	ldi r16, (0<<COM0A1) | (1<<COM0A0) | (1<<WGM01) | (0<<WGM00)
	sts TCCR0A, r16

	; el TOP es el valor en OCR0A
	; más pequeño es más agudo
	ldi r16, WAVE_PERIOD0
	sts OCR0A, r16

	;----------------------------------
	; configuración TIMER1 para frames
	;----------------------------------
	; CS1 en 0b1010 es CK/512
	ldi r16, (1<<CTC1) | (1<<CS13) | (0<<CS12) | (1<<CS11) | (0<<CS10)
	sts TCCR1, r16

	; establece el valor A al que cuenta el timer1 para interrupt
	ldi r16, TIMER1_PERIOD
	sts OCR1A, r16

	; establece el valor C al que cuenta el timer1 para resetear
	sts OCR1C, r16

	; establece el valor B para mitad de periodo
	lsr r16 ; divide entre 2
	sts OCR1B, r16

	; set timer output compare A interrupt enable 1
	; y timer output compare B interrupt enable
	ldi r16, (1<<OCIE1A) | (1<<OCIE1B)
	sts TIMSK, r16

	;enciende alarma
	ldi rFlags, (1<<fAlarmOn)

	; time count
	ldi rFrames, 0 ; frames
	ldi rSegundos, 0 ; seconds
	ldi rMinutos, 0 ; minutes

	; enciede state 0
	ldi r16, (1<<pinState0)
	sts PORTB, r16

loop:
	sleep
	rjmp loop

; -----------------------
; interrupts

; main time counting
timer1compareA_isr:
		; apaga alarm level
		clt
		bld rFlags, fAlarmLevel

		; apaga pwm output
		lds r16, TCCR0A
		andi r16, ~(1<<COM0A0)
		sts TCCR0A, r16

		; incrementa frames
		inc rFrames
		andi rFrames, MASK_FRAMES
		breq incrementsec ; salta si rFrames%8 == 0
	reti

		incrementsec:
		inc rSegundos ; cuenta segundos

		lds r16, PORTB
		; en qué timer estamos?
		bst rFlags, fCualTimer
		brts checksegundos1 ; salta si es el 1

		checksegundos0:
		bst rSegundos, 0
		brts ledstate0off

			ledstate0on:
			ori r16, (1<<pinState0)
			rjmp comparesegundos0

			ledstate0off:
			andi r16, ~(1<<pinState0)

		comparesegundos0:
		sts PORTB, r16
		cpi rSegundos, DUR_ALARMA0 ; time == DUR_ALARMA0?
		brne updateminutes
		rjmp turnalarmoff

		checksegundos1:
		bst rSegundos,0
		brts ledstate1off

			ledstate1on:
			ori r16, (1<<pinState1)
			rjmp comparesegundos1

			ledstate1off:
			andi r16, ~(1<<pinState1)

		comparesegundos1:
		sts PORTB, r16
		cpi rSegundos, DUR_ALARMA1 ; time == DUR_ALARMA0?
		brne updateminutes

		turnalarmoff: ; si es la duración correspondiente, apaga alarma
			clt
			bld rFlags, fAlarmOn

		updateminutes:
		cpi rSegundos,60 ; segundos == 60?
		breq minute
	reti

		minute:
		ldi rSegundos, 0 ; reset seconds
		inc rMinutos ; increment minutes

		bst rFlags, fCualTimer
		brts checkminutes1

		checkminutes0:
		cpi rMinutos, DUR_TIMER0
		breq firealarm
	reti

		checkminutes1:
		cpi rMinutos, DUR_TIMER1
		breq firealarm
	reti

		firealarm:
		ldi rMinutos, 0 ; reset minutes
		; inicia alarma
		set
		bld rFlags, fAlarmOn
		; invierte timer
		bst rFlags, fCualTimer
		brts settimer0 ; salta si el timer es 1

		settimer1:
		set
		bld rFlags,fCualTimer

		; cambia tono de alarma
		ldi r16, WAVE_PERIOD1
		sts OCR0A, r16

		; apaga otro led
		lds r16, PORTB
		andi r16, ~(1<<pinState0)
		sts PORTB, r16
	reti
		settimer0:
		clt
		bld rFlags,fCualTimer

		; cambia tono de alarma
		ldi r16, WAVE_PERIOD0
		sts OCR0A, r16

		; apaga otro led
		lds r16, PORTB
		andi r16, ~(1<<pinState1)
		sts PORTB, r16
	reti


; enciende pin de alarma
timer1compareB_isr:
		bst rFlags, fAlarmOn ; bit store

		; branch if T bit is set
		brts turnonalarmlevel
	reti

		turnonalarmlevel:
		; set alarmlevel on
		set
		bld rFlags, fAlarmLevel

		; enciende pwm output
		lds r16, TCCR0A
		ori r16, (1<<COM0A0)
		sts TCCR0A, r16
	reti

makefile

to assemble:

make hex

to flash:

make flash

makefile

# Makefile
# nombre del programa sin .S :
PROG = tomatimer
# config hardware
BOARD = attiny85
PROGRAMMER = usbasp

# ensambla programa .S a .hex
hex:
	avr-gcc -Os -DF_CPU=8000000 -mmcu=$(BOARD) -c $(PROG).S
	avr-ld -o $(PROG).elf $(PROG).o
	avr-objcopy $(PROG).elf -O ihex $(PROG).hex
	rm $(PROG).o $(PROG).elf

# flashea
flash:
	avrdude -c $(PROGRAMMER) -p $(BOARD) -U flash:w:$(PROG).hex:i

# lee la memoria flash a read.hex
read:
	avrdude -c $(PROGRAMMER) -p $(BOARD) -U flash:r:read.hex:i

# prueba conexión con programador y micro
test:
	avrdude -c $(PROGRAMMER) -p $(BOARD)

incoming links