; ; puppetcode.44.asm ; ; This code is for the ATTiny44 microprocessor. ; Data is acquired from a microphone to the ADC. ; Output pins control 3 servo motors. ; Timing is done with an off-chip 20 MHz crystal. ; ; Steve Leibman ; MIT 12/7/2007 ; ; Code primarily based on the tiny45 hello-world series of ; assembly codes from: ; Neil Gershenfeld ; CBA MIT 10/28/07 ; ; (c) Massachusetts Institute of Technology 2007 ; Permission granted for experimental and personal use; ; license for commercial sale available from MIT. ; .include "tn44def.inc" ;; Notes: ;; ;; Changed transmit pin to PA3 ;; ;; Switched to include tn44def.inc ;; ;; Use programming instructions as follows: ;; To use the offchip 20 MHz crystal, do: ;; avrdude -p t44 -c bsd -U lfuse:w:0x7E:m ;; To load hex file: ;; avrdude -p t44 -c bsd -U flash:w:file.hex ;; except that I use the USB-dasa programming mechanism. So actually... ;; avrdude -p t44 -P /dev/ttyUSB0 -c dasa -U lfuse:w:0x7E:m ;; avrdude -p t44 -P /dev/ttyUSB0 -c dasa -U flash:w:puppetcode.44.hex ;; ;; Change ADMUX reference to have Vcc as analog reference, via: ; cbi ADMUX, REFS1 ; Vcc as analog reference ; cbi ADMUX, REFS0 ; " ; ; definitions ; .equ rx_pin = PA3; transmit pin .equ motor1_pin = PA0; .equ motor2_pin = PA1; .equ motor3_pin = PA2; .equ nloop = 200 ; number of samples between framing ; ; registers ; .def bit_count = R16; bit counter .def temp = R17; temporary storage .def motor3position = R18; temporary storage .def rxbyte = R19; data byte .def loop_count = R20 ; loop counter; .def temp2 = R21; temporary storage .def temp3 = R22; temporary storage .def temp4 = R23; temporary storage .def temp5 = R24; temporary storage .def motor1position = R25 .def motor2position = R26 ; code segment ; .cseg .org 0 ; ; vector table ; rjmp reset ; Reset Handler .dw 0 ; External Interrupt Request 0 rjmp getmotorcmd ; PCINT0 Handler .dw 0 ; PCINT1 Handler .dw 0 ; Watchdog time-out .dw 0 ; Timer1 Capture event .dw 0 ; Timer1 Compare A Handler .dw 0 ; Timer1 Compare B Handler .dw 0 ; Timer1 Overflow Handler .dw 0 ; Timer0 Compare A Handler .dw 0 ; Timer0 Compare B Handler .dw 0 ; Timer0 Overflow Handler .dw 0 ; Analog comparator .dw 0 ; ADC conversion complete .dw 0 ; EEPROM ready .dw 0 ; USI START .dw 0 ; USI overflow ; ; halfbit delay ; serial (half)bit delay ; ; If clock = 8 MHz and we want 115200 baud, do 8e6/115200/2 = 35 cycles .equ b = 7; 115200 baud (8MHz clock /1) ;; The halfbit_delay loop will give us (b+1)*3+4 cycles, plus another 6 cycles ;; in the calling code for a total of (b+1)*3+10 cycles. ;;; halfbit_delay: ldi temp, b bitloop: dec temp brne bitloop ret igetchar_on: ; enable "pin change interrupt 0" on PCINT3 (pin PA3) in temp, PCMSK0 sbr temp, (1 << PCINT3) out PCMSK0, temp ; enable "pin change interrupt enable 0" in temp, GIMSK sbr temp, (1 << PCIE0) out GIMSK, temp ret ; ; getchar ; assumes no line driver (doesn't invert bits) ; getchar: ldi bit_count,9 ; 8 data bit + 1 stop bit getchar1: sbis PINA, rx_pin ; wait for start bit ; 2 cycles rjmp getchar1 ; we would have 3 nop's here, ; aiming for a total of 6 cycles of overhead, to be similar to ; the other halfbit_delay calls. But we usually do an rcall ; to getchar after we know that the pin has gone high (due to an ; interrupt), and that rcall gives us 3 cycles of overhead.... ; this is already probably a little too much, because interrupt ; costs at least 4 clock cycles (p15 tiny44 spec), but within tolerance... rcall halfbit_delay ; 0.5 bit delay ; rcall costs 3 cycles getchar2: ; we call halfbit_delay twice, and have an additional 13 cycles of overhead rcall halfbit_delay ; 1 bit delay ; rcall costs 3 cycles rcall halfbit_delay ; 1 bit delay ; rcall costs 3 cycles clc ; clear carry ; 1 cycle sbis PINA, rx_pin ; if RX pin high skip ; 1 or 2 cycles sec ; otherwise set carry dec bit_count ; 1 cycle breq getchar3 ; return if all bits read ; 1 cycle (in common case) ror rxbyte ; otherwise shift bit into receive byte 1 cycle rjmp getchar2 ; go get next bit ; 2 cycles getchar3: ret ; ; The wavehello routine was used for debugging... tells all motors to ; wave hello when an interrupt happens ; wavehello: push temp ldi motor1position, 0xff ; send motor1 to max ldi motor2position, 0x01 ; send motor2 to min ldi motor3position, 0xff ; send motor3 to max rcall motorize1s ldi motor1position, 0x01 ; send motor1 to min ldi motor2position, 0xff ; send motor2 to max ldi motor3position, 0x01 ; send motor3 to min rcall motorize1s ; Clear interrupt flag by writing a logical 1 to it (p. 54, tiny44 spec) in temp, GIFR sbr temp, (1 << PCIF0) out GIFR, temp pop temp reti getmotorcmd: rcall getchar push temp2 push temp3 mov temp2, rxbyte ; temp2 now tells us which motor to move rcall getchar ; rxbyte now tells us where to move the motor motorcmd_try1: ldi temp3, 0x31 ; identifier for motor1 (ASCII '1' character) cpse temp2,temp3 ; skip next instruction if temp2 indicates motor1 rjmp motorcmd_try2 mov motor1position, rxbyte ; load instruction for motor1 rjmp motorcmd_done motorcmd_try2: ldi temp3, 0x32 ; identifier for motor2 (ASCII '2' character) cpse temp2,temp3 ; skip next instruction if temp2 indicates motor2 rjmp motorcmd_try3 mov motor2position, rxbyte ; load instruction for motor2 rjmp motorcmd_done motorcmd_try3: ldi temp3, 0x33 ; identifier for motor3 (ASCII '3' character) cpse temp2,temp3 ; skip next instruction if temp2 indicates motor3 rjmp motorcmd_done mov motor3position, rxbyte ; load instruction for motor3 rjmp motorcmd_done motorcmd_done: ; Clear interrupt flag by writing a logical 1 to it (p. 54, tiny44 spec) in temp2, GIFR sbr temp2, (1 << PCIF0) out GIFR, temp2 pop temp3 pop temp2 reti ; ; char_delay ; delay between characters ; char_delay: ldi temp, 255 char_delay_loop: ldi temp2, 10 char_delay_loop1: dec temp2 brne char_delay_loop1 dec temp brne char_delay_loop ret ; If the clock is at 8 MHz, then 1ms = 8000 cycles. mindelay: ldi temp4, 241 ; 1 cycle mindelayouter: ldi temp5, 10 ; 1 cycle mindelayinner: dec temp5 ; 1 cycle brne mindelayinner ; 2 cycles (except when it continues, in which case 1 cycle) dec temp4 brne mindelayouter ret ; assumes 8 MHz clock (32,000 cycles) wait4ms: ldi temp2, 121 ; 1 cycle wait4outer: ldi temp3, 87 ; 1 cycle wait4inner: dec temp3 ; 1 cycle brne wait4inner ; 2 cycles dec temp2 ; 1 cycle brne wait4outer ; 2 cycles ret motorize1s: ldi temp, 120 motorloop: rcall gomotor1 rcall gomotor2 rcall gomotor3 dec temp brne motorloop ret ; If the clock is at 8 MHz, then 1ms = 8000 cycles. motor1extradelay: mov temp4, motor1position ; 1 cycle motor1extradelayouter: ldi temp5, 9 ; 1 cycle motor1extradelayinner: dec temp5 ; 1 cycle brne motor1extradelayinner ; 2 cycles (except when it continues, in which case 1 cycle) dec temp4 brne motor1extradelayouter ret ; If the clock is at 8 MHz, then 1ms = 8000 cycles. motor2extradelay: mov temp4, motor2position ; 1 cycle motor2extradelayouter: ldi temp5, 9 ; 1 cycle motor2extradelayinner: dec temp5 ; 1 cycle brne motor2extradelayinner ; 2 cycles (except when it continues, in which case 1 cycle) dec temp4 brne motor2extradelayouter ret ; If the clock is at 8 MHz, then 1ms = 8000 cycles. motor3extradelay: mov temp4, motor3position ; 1 cycle motor3extradelayouter: ldi temp5, 9 ; 1 cycle motor3extradelayinner: dec temp5 ; 1 cycle brne motor3extradelayinner ; 2 cycles (except when it continues, in which case 1 cycle) dec temp4 brne motor3extradelayouter ret ; TODO: you can disable interrupts in the gomotor routines (see p14 of tiny44 spec) gomotor1: sbi PORTA, motor1_pin ; bring motor1 pin high rcall mindelay; rcall motor1extradelay cbi PORTA, motor1_pin ; bring motor1 pin low rcall wait4ms; ret gomotor2: sbi PORTA, motor2_pin ; bring motor2 pin high rcall mindelay; rcall motor2extradelay cbi PORTA, motor2_pin ; bring motor2 pin low rcall wait4ms; ret gomotor3: sbi PORTA, motor3_pin ; bring motor3 pin high rcall mindelay; rcall motor3extradelay cbi PORTA, motor3_pin ; bring motor3 pin low rcall wait4ms; ret ; ; main program ; reset: ; ; set clock divider to /1 ; ldi temp, (1 << CLKPCE) ldi temp2, (0 << CLKPS3) | (0 << CLKPS2) | (0 << CLKPS1) | (0 << CLKPS0) out CLKPR, temp out CLKPR, temp2 ; ; set stack pointer to top of RAM ; ldi temp, high(RAMEND) out SPH, temp ldi temp, low(RAMEND) out SPL, temp ; ; init motor1_pin for output ; sbi PORTA, motor1_pin sbi DDRA, motor1_pin sbi PORTA, motor2_pin sbi DDRA, motor2_pin sbi PORTA, motor3_pin sbi DDRA, motor3_pin ; send all motors to somewhere in the middle ldi motor1position, 0x90 ldi motor2position, 0x90 ldi motor3position, 0x90 rcall gomotor1 rcall gomotor2 rcall gomotor3 rcall wait4ms sei ; enable interrupts rcall igetchar_on ; ; main loop ; main_loop: rcall gomotor1 rcall gomotor2 rcall gomotor3 rcall wait4ms ; ldi motor1position, 0xff ; send motor1 to max ; ldi motor2position, 0x01 ; send motor2 to min ; ldi motor3position, 0xff ; send motor3 to max ; rcall motorize1s ; ldi motor1position, 0x01 ; send motor1 to min ; ldi motor2position, 0xff ; send motor2 to max ; ldi motor3position, 0x01 ; send motor3 to min ; rcall motorize1s rjmp main_loop