; useful_functions.asm - a listing of useful functions in assembly for
; microdec programming.  register limitations are shown for each program.

; to use these, just copy and paste into your program, and be sure that your
; register usage doesnt overlap. a listing of all your register usage at the
; beginning of your file is a good way to check for this.  be sure to replace
; UID with your unique id for your code, or you will get assembly conflicts.
; check the microdec wiki to see which numbers have been assigned.  if other
; people use your code, it shouldnt conflict with their assembly either.

; list of functions in this file
;
; 1.  reading and writing to the codec.
; 2.  reading from the sram - 16b
; 3.  reading from the sram - 18b
; 4.  writing to the sram - 16b
; 5.  writing to the sram - 18b
; 6.  sampling the switch to change functions - no initialization
; 7.  sampling the switch to change functions - with initialization
; 8.  initialization routine
; 9.  16b signed x 16b signed to (16b or 32b) signed number
; 10. 16b signed x 8b unsigned to (16b or 24b) signed number
; 11. 16b signed x 16b unsigned to (16b or 32b) signed number
; 12. two's complement of 16b number
; 13. two's complement of 24b number
; 14. adding signed numbers of different lengths

; 1. reading and writing to the codec.
;
; this function will write 16b stereo data to the codec, and read out 16b
; stereo data from the codec.  this function must be run at a fixed interval,
; or audio glitches will be heard. you must read and write all 16b of both
; channels each time, or the codec will not operate.  you can change the bit
; depth for the codec, and its rate in the setup registers in the main file.
; only 16b stereo data has been tested at this point (both 44.1kHz and 48khz
; have been tested, but the microdec hardware only supports 44.1kHz). this
; function assumes you have the spi register configured appropriately (the
; main file does this).

; register usage:
;
; r2  left lsb out - can be any register
; r3  left msb out - can be any register
; r4  right lsb out - can be any register
; r5  right msb out - can be any register
; r6  left lsb in - can be any register
; r7  left msb in - can be any register
; r8  right lsb in - can be any register
; r9  right msb in - can be any register
; r17 temporary swap register - can be any register

; code:
;
;the timing of this first operation is critical. do not change the next
;three lines for any reason.
sbi portb,portb0 ; toggle slave select pin to begin transfer
out spdr,r3 ; send out left channel msb
cbi portb,portb0 ; clear slave select pin
;
;more code can be placed here to save cycles, up to 16 lines without much
;effect on the total time that it takes to run this code.
;
wait1_UID: ; check if byte has been sent

in r17,spsr
sbrs r17,spif
rjmp wait1_UID
in r7,spdr ; recieve in left channel msb
out spdr,r2 ; send out left channel lsb
;
;more code can be placed here to save cycles, up to 16 lines without much
;effect on the total time that it takes to run this code.
;
wait2_UID: ; check if byte has been sent

in r17,spsr
sbrs r17,spif
rjmp wait2_UID
in r6,spdr ; recieve in left channel lsb
out spdr,r5 ; send out right channel msb
;
;more code can be placed here to save cycles, up to 16 lines without much
;effect on the total time that it takes to run this code.
;
wait3_UID: ; check if byte has been sent

in r17,spsr
sbrs r17,spif
rjmp wait3_UID
in r9,spdr ; recieve in right channel msb
out spdr,r4 ; send out right channel lsb
;
;more code can be placed here to save cycles, up to 16 lines without much
;effect on the total time that it takes to run this code.
;
wait4_UID: ; check if byte has been sent

in r17,spsr
sbrs r17,spif
rjmp wait4_UID
in r8,spdr ; recieve in left channel lsb
;
; function end


; 2. reading from the sram - 16b
;
; this code will read a 16b value from the sram, using a 16b address.  this
; is faster than using the full 18b address space. the 16b address space
; is also convenient to use when difficult address calculations are
; required (i.e. youre doing more than just incrementing the address).

; register usage:
;
; r16 data lsb - can be any register
; r17 data msb - can be any register
; r18 swap register - must be one of r16 - r31
; r28 read address lsb - can be any register
; r29 read address msb  - can be any register

; code:
;
;setup sram for read - this can be omitted if your code already has these
;registers set appropriately elsewhere. if you are unsure, you should set
;them here, as you can severly damage your sram if its not setup
;correctly. you only need to to do this once for any consecutive reads.
;there are also other ways of accomplishing this task, but this is the
;easiest way.
clr r18 ; prepare to set i/o to input for data read
out ddra,r18 ; set porta as input for data read
out ddrc,r18 ; set portc as input for data read
ldi r18,$04
out portg,r18 ; pull ce low, we high, and set high bits of register to b00

;read data from sram
out portd,r28 ; set address lsb
sts porth,r29 ; set address msb
nop ; wait required microcontroller i/o setup time of 2 cycles
nop ; can place other code here for efficient usage of time
in r16,pina ; get data lsb
in r17,pinc ; get data msb
;
; function done


; 3. reading from the sram - 18b
;
; this code will read a 16b value from the sram, using an 18b address.

; register usage:
;
; r16 data lsb - can be any register
; r17 data msb - can be any register
; r18 swap register - must be one of r16 - r31
; r23 read address high byte - can be any register
; r28 read address lsb - can be any register
; r29 read address msb  - can be any register

; code:
;
;setup sram for read - this can be omitted if your code already has these
;registers set appropriately elsewhere. if you are unsure, you should set
;them here, as you can severly damage your sram if its not setup
;correctly. you only need to to do this once for any consecutive reads.
;there are also other ways of accomplishing this task, but this is the
;easiest way.
clr r18 ; prepare to set i/o to input for data read
out ddra,r18 ; set porta as input for data read
out ddrc,r18 ; set portc as input for data read
ldi r18,$04 ; this line and the next can be omitted if the "out portg,r23"
; line directly follows "out ddrc,r18" from above.
out portg,r18 ; pull ce low, we high, and set high bits of register to b00

;read data from sram
out portg,r23 ; pull ce low, we high, and set high bits of register
; it is critical that r23 be of the format b000001dd where dd are the
; address high bits (i.e. only $04 - $07 are valid). damage to the sram
; can occur if this is not followed.
out portd,r28 ; set address lsb
sts porth,r29 ; set address msb
nop ; wait required microcontroller i/o setup time of 2 cycles
nop ; can place other code here for efficient usage of time
in r16,pina ; get data lsb
in r17,pinc ; get data msb
;
; function done


; 4. writing to the sram - 16b
;
; this code will write a 16b value to the sram, using a 16b address.  this
; is faster than using the full 18b address space. the 16b address space
; is also convenient to use when difficult address calculations are
; required (i.e. youre doing more than just incrementing the address).

; register usage:
;
; r16 data lsb - can be any register
; r17 data msb - can be any register
; r18 swap register - can be any register
; r19 swap register - must be one of r16 - r31
; r24 write address lsb - can be any register
; r25 write address msb  - can be any register

; code:
;
; this is not the most efficient method of writing to the sram if you
; are doing many consecutive writes, but is more efficient for 
; consecutive reads, as it leaves the sram in the read state, and assumes
; it is in the read state to begin with.  check the sram datasheet for
; other write modes for your application if speed is critical.
;
;setup sram - can be omitted if already setup, or if previous operation
;was a read as shown above.
clr r18 ; prepare to set i/o to input for data read
out ddra,r18 ; set porta as input for data read
out ddrc,r18 ; set portc as input for data read
ldi r19,$04
out portg,r19 ; pull ce low, we high, and set high bits of register to b00

;sram write
clr r18 ; prepare for sram setting and microcontroller i/o setting - can be
; omitted if a register is set to zero (null register) elsewhere.
out portd,r24 ; set address lsb
sts porth,r25 ; set address msb
out portg,r18 ; pull ce low,we low,and set high bits of address
ldi r19,$ff
out ddra,r19 ; set porta as output for data write
out ddrc,r19 ; set portc as output for data write
out porta,r16 ; set data lsb
out portc,r17 ; set data msb
sbi portg,portg2 ; pull we high to write
out ddra,r18 ; set porta as input for data lines
out ddrc,r18 ; set portc as input for data lines
;
; function end


; 5. writing to the sram - 18b
;
; this code will write a 16b value to the sram, using an 18b address.

; register usage:
;
; r16 data lsb - can be any register
; r17 data msb - can be any register
; r18 swap register - can be any register
; r19 swap register - must be one of r16 - r31
; r22 write address high byte -  can be any register
; r24 write address lsb - can be any register
; r25 write address msb  - can be any register

; code:
;
; this is not the most efficient method of writing to the sram if you
; are doing many consecutive writes, but is more efficient for 
; consecutive reads, as it leaves the sram in the read state, and assumes
; it is in the read state to begin with.  check the sram datasheet for
; other write modes for your application if speed is critical.
;
;setup sram - can be omitted if already setup, or if previous operation
;was a read as shown above.
clr r18 ; prepare to set i/o to input for data read
out ddra,r18 ; set porta as input for data read
out ddrc,r18 ; set portc as input for data read
ldi r19,$04
out portg,r19 ; pull ce low, we high, and set high bits of register to b00

;sram write
clr r18 ; prepare for microcontroller i/o setting - can be
; omitted if a register is set to zero (null register) elsewhere.
out portd,r24 ; set address lsb
sts porth,r25 ; set address msb
out portg,r22 ; pull ce low, we low, and set high bits of register
; it is critical that r22 be of the format b000000dd where dd are the
; address high bits (i.e. only $00 - $03 are valid). damage to the sram
; can occur if this is not followed.
ldi r19,$ff
out ddra,r19 ; set porta as output for data write
out ddrc,r19 ; set portc as output for data write
out porta,r16 ; set data lsb
out portc,r17 ; set data msb
sbi portg,portg2 ; pull we high to write
out ddra,r18 ; set porta as input for data lines
out ddrc,r18 ; set portc as input for data lines
;
; function end


; 6. sampling the switch to change functions - no initialization
;
; this must be done in a consistent fashion so all subprograms work with the
; main loop. r31:r30 hold the jump vector, and must be reset at the end of
; any subprogram.  you can either not alter these register, store them on
; the stack, or recalculate them on each pass through the subprogram.  i
; usually leave them unaltered, as this consumes the least time.  the
; program shown here samples the switch once every 256 cycles.  this gives
; a little bit more glitch immunity (as the switch is not debounced in
; hardware), and keeps the microcontroller from starting every program it
; passes along the way between the first switch position and the last. this
; isnt required, but makes things a little nicer.

; register usage
;
; r15 switch sample counter - can be any register
; r17 temporary register - must be one of r16 - r31
; r31 jump location msb - must be r31

; code:
;
;this code assumes there is no initialization in the subprogram (i.e. the
;program starts at initial jump location), and that r30 is set to $00, and
;is not altered during the subprogram, ever.
dec r15 ; downcount sample counter
brne done_UID ; check switch every 256 cycles
lds r31,pinj ; get switch data
andi r31,$78 ; mask off rotary switch
ldi r17,$02 ; prepare to adjust switch position to program memory location
lsr r31
lsr r31
add r31,r17 ; adjust switch position to program memory location

done_UID:

reti ; i usually place the switch sampling at the end of my code, but other
; code can follow in place of the reti
;
; function end


; 7. sampling the switch to change functions - with initialization
;
; this must be done in a consistent fashion so all subprograms work with the
; main loop. r31:r30 hold the jump vector, and must be reset at the end of
; any subprogram.  you can either not alter these register, store them on
; the stack, or recalculate them on each pass through the subprogram.  i
; usually leave them unaltered, as this consumes the least time.  the
; program shown here samples the switch once every 256 cycles.  this gives
; a little bit more glitch immunity (as the switch is not debounced in
; hardware), and keeps the microcontroller from starting every program it
; passes along the way between the first switch position and the last. this
; isnt required, but makes things a little nicer.

; register usage
;
; r15 switch sample counter - can be any register
; r16 temporary register - must be one of r16 - r31
; r17 temporary register - must be one of r16 - r31
; r30 jump locatoin lsb - must be r30
; r31 jump location msb - must be r31

; code
;
;this code assumes that r30 is modified in the initialization routine (the
;routine at codespace corresponding to r30=$00), and that it is not modified
;in any way by the subprogram after that. 
dec r15 ; downcount sample counter
brne done_UID ; sample every 256 cycles
lds r16,pinj ; get switch data to temporary register
andi r16,$78 ; mask off rotary switch
ldi r17,$02 ; prepare to adjust switch position
lsr r16
lsr r16
add r16,r17 ; adjust switch position to program memory location
cpse r16,r31 ; check if location has changed - dont reset r30 if it hasnt
clr r30 ; reset jump location lsb to intial state ($00)
mov r31,r16 ; set jump location msb to current swtich position

done_UID:

reti ; i usually place the switch sampling at the end of my code, but other
; code can follow in place of the reti
;
; function end


; 8. initialization routine
;
; this places a small set of code which is only run once when a subprogram
; is first called.  it adjusts the jump location accordingly.  this must be
; used with the switchsample routine which accounts for initialization. r30
; can not be modified anywhere in the subprogram.

; register usage:
;
; r30 jump location lsb - must be r30

; code:
;
;program starts here first time
;initialze z pointer for correct jump
;this assumes a less than 256 word jump
ldi r30,$05 ; set jump location to program start - this constant must be
; calculated to coincide with the subsequent start location.  this can
; either be done by counting the instructions that follow, and noting
; their memory usage (as given in the instruction set), or by assembling
; the file and looking at the result in the disassembler (avrstudio has a
; a nice disassembler that you can quickly look at to find the right value).
nop ; place your initialization routine here instead of the nops
nop
nop
reti ; return and wait for next interrupt
;
; function end


; 9. 16b signed x 16b signed to (16b or 32b) signed number
;
; this is a simple multiplaction routine for 16b signed numbers that
; returns a 32b number, of which you can use the upper 16b if thats all
; that is required. multiplicands start out in r11:r10 and r9:r8, and the
; result ends up in r5:r4:r3:r2. you can use whichever starting or ending
; registers you wish.

; register usage
;
; r0  = multiply interim result lsb - must be this register
; r1  = multiply interim result msb - must be this register
; r2  = multiply final result lsb - can be any register
; r3  = multiply final resutl mlb - can be any register
; r4  = multiply final result mhb - can be any register
; r5  = multiply final result msb - can be any register
; r18 = multiplicand A lsb - must be one of register r16 - r23
; r19 = multiplicand A msb - must be one of register r16 - r23
; r20 = multiplicand B lsb - must be one of register r16 - r23
; r21 = multiplicand B msb - must be one of register r16 - r23
; r22 = null register - can be any register

; code
;
clr r22 ; clear null register for carry addition/subtraction - not needed
; if already done elsewhere in the code
movw r19:r18,r9:r8 ; move values to multiply register - not necessary if
; your values are already in those registers
movw r21:r20,r11:r10 ; move values to multiply register - not necessary if
; your values are already in those registers
muls r19,r21 ; (signed)Ah * (signed)Bh - multiply high bytes
movw r5:r4,r1:r0 ; store high bytes result for later
mul	r18,r20	; (unsigned)Al * (unsigned)Bl ; multiply low bytes
movw r3:r2,r1:r0 ; store low byets for later
mulsu r19,r20 ; (signed)Ah * (unsigned)Bl - multiply middle bytes
sbc	r5,r22 ; r22 is cleared above - subtract sign bit
add	r3,r0 ; accumulate result
adc	r4,r1
adc	r5,r22 ; r22 is cleared above
mulsu r21,r18 ; (signed)Bh * (unsigned)Al - multiply middle bytes
sbc	r5,r22 ; r22 is cleared above - subtract sign bit
add	r3,r0 ; accumulate result
adc	r4,r1
adc	r5,r22 ; r22 is cleared above
;
; function end


; 9. 16b signed x 8b unsigned to (16b or 24b) signed number
;
; this is a simple multiplaction routine for 16b signed numbers that
; returns a 24b number, of which you can use the upper 16b if thats all
; that is required. multiplicands start out in r11:r10 and r9, and the
; result ends up in r5:r4:r3. you can use whichever starting or ending
; registers you wish.

; register usage
;
; r0  = multiply interim result lsb - must be this register
; r1  = multiply interim result msb - must be this register
; r3  = multiply final result fractional byte - can be any register
; r4  = multiply final result lsb - can be any register
; r5  = multiply final result msb - can be any register
; r18 = multiplicand A lsb - can be any register
; r19 = multiplicand A msb - must be one of register r16 - r23
; r21 = multiplicand B - must be one of register r16 - r23
; r22 = null register - can be any register

; code
;
clr r22 ; clear null register for carry addition/subtraction - not needed
; if already done elsewhere in the code
mov r21,r9 ; move value to multiply register - not necessary if
; your values are already in those registers
movw r19:r18,r11:r10 ; move values to multiply register - not necessary if
; your values are already in those registers
mulsu r19,r21 ; (signed)Ah * (unsigned)B - multiply high byte
movw r5:r4,r1:r0 ; store result
mul r18,r21 ; (unsigned)Al * (unsigned)B - multiply low byte
mov r3,r0 ; store fractional byte - not needed for 16b result
add r4,r1 ; accumulate result
adc r5,r22 ; r22 is cleared above
;
; function end


; 10. 16b signed x 16b unsigned to (16b or 32b) signed number
;
; this is a simple multiplaction routine for 16b signed/unsigned numbers
; that returns a 32b number, of which you can use the upper 16b if thats all
; that is required. multiplicands start out in r11:r10 and r9:r8, and the
; result ends up in r5:r4:r3:r2. you can use whichever starting or ending
; registers you wish.

; register usage
;
; r0  = multiply interim result lsb - must be this register
; r1  = multiply interim result msb - must be this register
; r2  = multiply final result lsb - can be any register
; r3  = multiply final resutl mlb - can be any register
; r4  = multiply final result mhb - can be any register
; r5  = multiply final result msb - can be any register
; r18 = multiplicand A lsb - can be any register
; r19 = multiplicand A msb - must be one of register r16 - r23
; r20 = multiplicand B lsb - must be one of register r16 - r23
; r21 = multiplicand B msb - must be one of register r16 - r23
; r22 = null register - can be any register

; code
;
clr r22 ; clear null register for carry addition/subtraction - not needed
; if already done elsewhere in the code
movw r19:r18,r9:r8 ; move values to multiply register - not necessary if
; your values are already in those registers
movw r21:r20,r11:r10 ; move values to multiply register - not necessary if
; your values are already in those registers
mulsu r19,r21 ; (signed)Ah * (unsigned)Bh - multiply high bytes
movw r5:r4,r1:r0 ; store high bytes result for later
mul	r18,r20	; (unsigned)Al * (unsigned)Bl ; multiply low bytes
movw r3:r2,r1:r0 ; store low byets for later
mulsu r19,r20 ; (signed)Ah * (unsigned)Bl - multiply middle bytes
sbc	r5,r22 ; r22 is cleared above - subtract sign bit
add	r3,r0 ; accumulate result
adc	r4,r1
adc	r5,r22 ; r22 is cleared above
mul r21,r18 ; (unsigned)Bh * (unsigned)Al - multiply middle bytes
add	r3,r0 ; accumulate result
adc	r4,r1
adc	r5,r22 ; r22 is cleared above
;
; function end


; 11. two's complement of 16b number
;
; this function will give you the negative value of a signed number. i.e.
; positive values will become negative, and negative values will become
; positive.  it is good for 16b numbers. value $8000 (-32768) stays the same
; as there is no positive value that large (be careful of this if trying to
; take the absolute value of a number).  this function also gives you the
; complementary distance from a value to $0000, i.e. $0000 - r5:r4.

; register usage
;
; r4  = lsb - can be any register
; r5  = msb - can be any register
; r16 = temporary register - can be any register

; code
;
clr r16 ; not required if a register already has value $00
neg r4 ; invert
adc r5,r16
neg r5
;
; end function


; 12. two's complement of 24b number
;
; this function will give you the negative value of a signed number. i.e.
; positive values will become negative, and negative values will become
; positive.  it is good for 24b numbers. value starts in r5:r4:r3, and gets
; modified, and remains in those registers. value $800000 (-8388608 decimal),
; stays the same value, as there is no positive value that large (be careful
; of this if trying to take the absolute value of a number).  this function
; also gives you the complementary distance from a value to $000000, i.e.
; $000000 - r5:r4:r3.

; register usage
;
; r3  = fractional byte - can be any register
; r4  = lsb - can be any register
; r5  = msb - can be any register
; r16 = temporary register - must be one of r16 - r31
; r17 = temporary register - can be any register

; code
;
ldi r16,$01 ; does not need to be done if a register already has value $01
clr r17 ; does not need to be done if a register already has value $00
com r3 ; invert value
com r4
com r5
add r3,r16 ; r16 set to $01 above
adc r4,r17 ; r17 cleared above
adc r5,r17
;
; end function


; 14. adding signed numbers of different lengths
;
; this function adds signed number of different byte lengths, where the
; shorter length number represents a fraction of the higher length number.
; this is useful for accumulation, where 8b or 16b numbers will need to be
; added to 24b or 32b accumulation registers.  converting your number to an
; unsigned integer before accumulation is a workaround for not having to do
; this.

; register usage
;
; r0  = value 1 lsb - can be any register
; r1  = value 1 msb - can be any register
; r2  = value 2 lsb - can be any register
; r3  = value 2 mlb - can be any register
; r4  = value 2 mhb - can be any register
; r5  = value 2 msb - can be any register
; r16 = null register - can be any register

; code
;
clr r16 ; does not need to be done if already cleared
add r2,r0 ; begin accumulation
adc r3,r1
sbrc r1,$07 ; check if r1:r0 is negative
dec r16 ; set null register to $ff if its negative (ldi r16,$ff could be
; used here as well, but requires a register of r16 -> r31)
adc r4,r16 ; add in rest of carry bits
adc r5,r16
clr r16 ; reset null register - does not need to be done if later code does
; not assume a null register
;
; end function

