#include ;===================================== ; NOTES ; The pin allocation on the mega8 is more interesting than the 16. ; There is no port A. Port B has 8 pins, of which we'd ideally ; like to keep 3,4, and 5 free for programming. Port C has ; six pins. Port D has 8 pins. ; ; ------------- ; /RESET PC6 | 1 28| PC5 ADC5,SCL ; RXD PD0 | 2 A 27| PC4 ADC4,SDA ; TXD PD1 | 3 T 26| PC3 ADC3 ; INT0 PD2 | 4 M 25| PC2 ADC2 ; INT1 PD3 | 5 E 24| PC1 ADC1 ; XCK,T0 PD4 | 6 G 23| PC0 ; VCC | 7 A 22| GND ; GND | 8 8 21| AREF ; TOSC1 PB6 | 9 20| AVCC ; TOSC2 PB7 |10 19| PB5 SCK < Used ; T1 PD5 |11 D 18| PB4 MISO < for ; AIN0 PD6 |12 I 17| PB3 MOSI,OC2 < programming ; AIN1 PD7 |13 P 16| PB2 /SS,OC1B ; ICP1 PB0 |14 15| PB1 OC1A ; ------------- ; ; .arch atmega8 ; Has 512 bytes of eeprom ; Has 1024 bytes of sram ; Has 8192 bytes of flash ; Constants we get for free from the header: ; RAMEND, E2END, FLASHEND ; Constants we want but are not in the header: .equ RAMSTART , 0x60 ; Conventions ; gr general register ; lgr low general regstier (can't operate on immediately) ; ir interrupt register (allocate own to minimize stack use) ; Generic low (can't load immediates) #define lgr r2 #define lgr2 r3 #define lgr3 r4 #define lgr4 r5 ; General registers for main code. Also used for passing arguments to ; subroutines. If they are used for scratch in subroutines, must be ; saved and restored. #define gr r16 #define gr2 r17 #define gr3 r18 #define gr4 r19 ;#define gr5 r20 ;#define gr6 r21 ; Scratch register for the interrupt handler. Having a separate one ; saves pushing and popping it all the bloody time. There is no need to ; push these, because at most one interrupt handler can be active at once. #define irl r14 #define irl2 r15 #define ir r23 ;==================================== ; SRAM locations: ; Use a macro to set up variables with specified lengths. .macro SRAMALIGN bits ; equivalent to .align in our klugy SRAM system _CURSRAM=(_CURSRAM+(1<<\bits)-1)>>\bits<<\bits .endm .macro SRAMVAR name, length \name = _CURSRAM _CURSRAM = _CURSRAM + \length ; If we've run out of memory (allow six bytes for stack), warn ; and abort. .if _CURSRAM > RAMEND - 6 .print "SRAM Error: Not enough memory while defining \name" .err .endif .endm _CURSRAM = RAMSTART ;==================================== ; EEPROM information: .section .eeprom ;==================================== ; Macros ; Use high-numbered labels in macros to avoid confusion with main code; ; you can jump to a label in a macro from the main code. Hopefully soon ; I will upgrade to the new gas which supports truly local labels. .macro ADDI reg value ; Just remember that the carry flag will be wrong; this isn't a ; *real* addition. SUBI \reg, -\value .endm ;==================================== ; Code: .section .text #if defined(__AVR_ATmega8__) ; It is quite important to note that each interrupt is *one* word ; here, unlike the atmega16 parts, because RJMP can reach all addresses. .org 0 ; RESET RJMP init .org 2 ; INT0 - External interrupt 0 RETI .org 4 ; INT1 - External interrupt 1 RETI .org 6 ; TIMER2 COMP - Compare Match for Timer 2 RETI .org 8 ; TIMER2 OVF - Overflow for Timer 2 RETI .org 10 ; TIMER1 CAPT - Capture Event for Timer 1 RETI .org 12 ; TIMER1 COMPA - Compare A Match for Timer 1 RETI .org 14 ; TIMER1 COMPB - Compare B Match for Timer 1 RETI .org 16 ; TIMER1 OVF - Overflow for Timer 1 RETI .org 18 ; TIMER0 OVF - Overflow for Timer 0 RETI .org 20 ; SPI,STC - Serial Transfer Complete RETI .org 22 ; USART RXC - Receive Complete on UART RETI .org 24 ; USART UDRE - Data Register Empty for UART Transmit RETI .org 26 ; USART TXC - Transmit Complete for UART RETI .org 28 ; ADC - Analog/Digital Conversion Complete RETI .org 30 ; EE_RDY - EEPROM Ready RETI .org 32 ; ANA_COMP - Analog Comparator RETI .org 34 ; TWI - 2-sire Serial RETI .org 36 ; SPM_RDY - Store Program Memory ready ; Now we must alias the names which changed between the parts. We refer ; to the atmega163 -> atmega16 application note for the list, and omit ; the ones which were done for us in the header. #define MCUSR MCUCSR #define UBRR UBRRL #define UBRRHI UBRRH #define GICR GIMSK #else #error You must modify this code to work with this device #endif init: ; Set up the stack (required for interrupts to work) LDI gr, lo8(RAMEND) OUT SPL, gr #ifdef SPH LDI gr, hi8(RAMEND) OUT SPH, gr #endif ; Here we play the actual sound LDI gr, 0b1111 OUT DDRC, gr CLR gr3 ; counter for number of times played in a row LDI gr, 0b1000 ; turn on pullup on PD3 (INT1) OUT PORTD, gr ; Disable analog comparator, which for some reason is ON by default. LDI gr, 1<; @b=unpack("C*", $f); print join "\n .byte ",@b' < /tmp/ni.raw > /tmp/ni.code ; ; That was for the wasting-half-the-space version. Now we have a version ; which packs two nybbles into a byte, and it's a beast: ; ; perl -e 'undef $/; $f=<>; $b=unpack("H*", $f); print join "\n\t.byte 0x",map{substr($_,0,1).substr($_,2,1)} $b=~/(.{4})/g' < /tmp/ni4.raw ; ; THis version is better, but can't be used directly in the editor because ; it replaces % signs. ; ; perl -e 'undef $/; $f=<>; while($f){printf "\t.byte 0x%X%X,0x%X%X,0x%X%X,0x%X%X,0x%X%X,0x%X%X,0x%X%X,0x%X%X\n", map {ord(substr($f,$_*2,1))/16} (0..15); substr($f,0,16,undef)};' < /tmp/decay.raw ; ; Check the normalization like this: ; ; perl -e 'undef $/; $f=<>; while($f){printf "%X\n", map {ord(substr($f,$_*2,1))/16} (0); substr($f,0,1,undef)};' < /tmp/decay.raw | sort | uniq -c | les testdata: .byte 0x12 .byte 0x34 .byte 0x56 .byte 0x78 .byte 0x9A .byte 0xBC .byte 0xDE .byte 0xF0 .byte 0x00 sounddata: ; This set of data is offset a bit towards the high side, because ; I'm using an emitter follower for amplification, and thus the values ; of 0 or 1, which would be below 0.6v, will be clipped. .byte 0x01,0x12,0x23,0x34,0x45,0x56,0x67,0x78 ; ** Insert your actual data here ** ; Ramp down to zero .byte 0x77,0x66,0x55,0x44,0x33,0x22,0x11,0x10 .byte 0 ; vi:sts=8:sw=8:so=10