=== MICrODEC === ==== Programming Example ==== Writing code in assembly is a lot like that riddle about the farmer who has a chicken, a fox, and a bag of corn that he's trying to take across a river, but he only has one boat to do it with. But on the bright side, none of our registers are going to eat each other, and we're not going to be attacked by any foxes! (actually, your registers might eat each other, so declare them clearly at the beginning of your code) If the text isn't flowing right on your browser, try maximizing the window. {{{#!highlight nasm ; **************************************************************************** ; * program: delay-16b-rotary.asm * ; * UID = Tutorial - unique id to eliminate conflicts between variables, * ; * replace each instance of UID in the code with this unique id. * ; * 16b address space (.7s delay time) * ; * stereo data * ; * rotary encoder (MOD2) delay time (0s - .7s) * ; **************************************************************************** ; * * ; * PROGRAM OVERVIEW * ; * * ; * data is read in from memory and written to the codec at the same time * ; * new data is written to the memory from the codec. the delay time is set * ; * by the rotary encoder (MOD2) on PORTJ. if the rotary encoder is turned * ; * to the right, the delay time is increased by a fixed amount. if it is * ; * turned to the left, the delay time is decreased by a fixed amount. the * ; * rotary encoder only changes the desired delay time, and once each sample * ; * sample period, the desired delay is compared to the actual delay. if * ; * they are not equal, the actual delay is either incremented by 2 samples * ; * decreased by one sample. since the memory pointers are incrementing by * ; * 1 sample each sample period, this gives an effective increase of 1 * ; * sample, or decrease of 2 samples per period. this has the effect of * ; * doubling the read rate to go forward, or playing in reverse to go * ; * backwards. this eliminates the audio glitches of quickly jumping to the * ; * new location in the SRAM. * ; * * ; * register usage - may be redefined in other sections * ; * * ; * r0 * ; * r1 * ; * r2 left lsb out * ; * r3 left msb out * ; * r4 right lsb out * ; * r5 right msb out * ; * r6 left lsb in * ; * r7 left msb in * ; * r8 right lsb in * ; * r9 right msb in * ; * r10 * ; * r11 * ; * r12 * ; * r13 desired delay msb * ; * r14 * ; * r15 switch/rotary encoder sample counter * ; * r16 temporary swap register * ; * r17 temporary swap register * ; * r18 * ; * r19 * ; * r20 * ; * r21 * ; * r22 null register (always equals $00) * ; * r23 * ; * r24 write address lsb * ; * r25 write address msb * ; * r26 actual delay lsb * ; * r27 actual delay msb * ; * r28 read address lsb * ; * r29 read address msb * ; * r30 jump location for interrupt lsb * ; * r31 jump location for interrupt msb * ; * t * ; **************************************************************************** ; * NOTES: - all unprocessed (dry) audio data comes in from the SPI port, via ; the SPDR i/o register ; - all processed (wet) audio goes out over the SPI port, also via ; the SPDR i/o register ; - all external memory addressing happens over the PORTD and PORTH ; i/o registers ; - all external memory control happens over PORTG i/o register ; - all data we are saving and reading back (like our delayed audio) ; goes out over the PORTA and PORTC i/o registers, and comes in ; via the PINA and PINC i/o registers ; ********************** ; **** PROGRAM START ; ********************** ; ### initiate data transfer to the codec sbi portb, portb0 ; set bit PORTB0 (aka DACLRC - DAC sample rate ; left/right clock) ; ; **NOTE: from WM8731 data sheet, page 36: ; "DACLRC is an alignment clock that controls ; whether Left or Right channel data is present ; on DACDAT" ; ### now on SPI - LEFT MSB OUT out spdr, r3 ; send out processed left channel msb (or ; whatever's currently in r3) to SPI data register cbi portb, portb0 ; clear DACLRC adiw r25:r24, 0x01 ; increment write address pointer by 1 adiw r29:r28, 0x01 ; increment read address pointer by 1 ldi r22, 0x00 ; setup null register ; ********************** ; **** wait1 ; ********************** wait1_UID: ; wait to see if processed left channel msb has ; been sent in r17, spsr ; move SPSPR i/o register (SPI status register) to ; r17 so we evaluate it sbrs r17, spif ; skip next instruction if the SPIF (SPIF interrupt ; flag) bit is set ; **NOTE: from atmega3250P data sheet, page 162: ; "When a serial transfer is complete, the SPIF ; flag is set" rjmp wait1_UID ; keep checking until SPIF is set ; and then... ; ### after wait1, new SPI data ready in r7, spdr ; move SPIDR (SPI data register) into r7 ; (remember SPDR is read/write, page 162) ; this moves the incoming (dry) left channel msb ; to r7 ; ### end of wet/dry left msb transfer ; ### now on SPI - LEFT LSB OUT out spdr, r2 ; send the processed (wet) left lsb out ; ### retrieve stored left channel data from SRAM: out portd, r28 ; move read address lsb pointer to PORTD sts porth, r29 ; move read address msb pointer to PORTH ; **NOTE: - this is a special instruction because ; the i/o PORTH register is wayyy up there in the ; sram - 'out' completes in one cycle, 'sts' ; completes in two nop ; read address msb hits the port, now wait for (1) nop ; a latch time of two cycles... (2) ; DING! your data is ready, please pick it up! in r2, pina ; D0-7, aka lsb, is on PORTA, now stored in r2 in r3, pinc ; D8-15, aka msb, is on PORTC, now stored in r3 ; ### left channel SRAM retrieve completed ; now we've got a little time to kill, so... adiw r29:r28, 0x01 ; increment read address pointer by 1 ; ********************** ; **** wait2 ; ********************** wait2_UID: ; wait to see if wet left channel lsb has been sent in r17, spsr ; SPI status register to r17 (looking familiar?) sbrs r17, spif ; skip next if SPIF bit not set... rjmp wait2_UID ; loop until SPIF set ; **NOTE: and now a couple of words about rjmp: ; - it can only move within 2k of memory (ie, ; can't jump all the way to the start, or end, ; of a long program) ; - it doesn't do anything to the stack (so if ; it loops a thousand times, the stack isn't ; going to overflow) ; - it takes two cycles ; ### after wait2 is finished, new SPI data ready in r6, spdr ; receive in dry left channel lsb ; **NOTE: that gives us both bytes of the ; incoming left channel, and both wet bytes have ; been sent so we can now move on to the right ; channel ; ### end of wet/dry left lsb transfer ; ### now on SPI - RIGHT MSB OUT out spdr, r5 ; send out (wet) right msb ; ### writing (dry) left channel to SRAM: out portd, r24 ; update data address ports to reflect where we ; want to write sts porth, r25 ; remember 'out' and 'sts'? out portg, r22 ; woah, PORTG? what's this? Check out the SRAM ; data sheet and the schematic: ; r22 is currently 0x00, so we are pulling CE ; (Chip Enable) and WE (Write Enable) low, and ; writing our high address bits to zero ; **NOTE: from AS7C4098 data sheet, page 2: ; "Data on input pins IO1-IO16 are written on ; the rising edge of WE..." ; "To avoid bus contention, external devices ; should drive IO pins only after outputs have ; been disabled with OE (output enable) or WE" ; "A read cycle is accomplished by asserting OE ; and CE, with WE high." ldi r17, 0xFF ; prepare a salad of ones out ddra, r17 ; send them to the PORTA direction register out ddrC, r17 ; and to the PORTC direction register ; to set the ports as output ; see ATmega3250P data sheet, page 65, ; 'Switching Between Input and Output' out porta, r6 ; with these two 'out' instructions out portc, r7 ; we send left channel dry data to the SRAM sbi portg, portg2 ; and as soon as we pull WE on PORTG2 high, ; zzztt! a single audio sample is written into ; memory, just like that. ; ### left channel SRAM write completed out ddra, r22 ; now, we happen to know that r22 contains a out ddrc, r22 ; bunch of zeros so we can use that register to ; set PORTA and PORTC back to an input state. ; ********************** ; **** wait3 ; ********************** wait3_UID: ; meanwhile, back at the codec, we are still ; transferring the right channel lsb in r17, spsr ; check out the SPSR, check out the SPIF, sbrs r17, spif ; we know what we're doing here now, right? rjmp wait3_UID ; loop until transfer completed ; ### end of wait3, new data ready! in r9, spdr ; recieve in (dry) right channel msb ; ### end of wet/dry right msb transfer ; ### now on SPI - RIGHT LSB OUT out spdr, r4 ; send out (wet) right channel lsb ; ### retrieve stored right channel data from SRAM out portd, r28 ; set up the address we want to read from sts porth, r29 ; on PORTD and PORTH nop ; killing time again... nop ; two cycles, while the SRAM latches the address ; and once more, ding! our data is now waiting ; on the SRAM data lines in r4, pina ; right lsb in r5, pinc ; right msb (this is audio data we saved in the ; past) ; ### right channel SRAM data retrieval completed adiw r25:r24, 0x01 ; increment the write address (we're going to be ; using that pointer next) ; ********************** ; **** wait4 ; ********************** wait4_UID: ; checking up on that SPI transfer in r17, spsr sbrs r17, spif rjmp wait4_UID ; loop until SPIF is set ; ### end of wait4, more new data! in r8, spdr ; bring in right channel lsb ; ### end of wet/dry right lsb transfer ; now that we have both bytes of the right ; channel dry audio, we can store it in the ; SRAM... ; ### writing (dry) right channel to SRAM out portd, r24 ; give the SRAM the write address, lsb sts porth, r25 ; now msb out portg, r22 ; pull WE low, (CE is already low, WE was high ; for read operations) ldi r17, 0xFF ; prepare a bevy of ones out ddra, r17 ; set PORTA as output out ddrc, r17 ; set PORTC as output out porta, r8 ; put the data on the ports, lsb out portc, r9 ; msb sbi portg, portg2 ; zzzt! pull WE high and write the data out ddra, r22 ; reconfigure PORTA as input out ddrc, r22 ; reconfigure PORTC as input ; **************************************************************************** ; **** check rotary encoder and adjust delay time ; **************************************************************************** ; * The rotary encoder is externally debounced, so we don't have to do that ; * here. You'll see it in the schematic labeled MOD2 on PORTJ0, PORTJ1, and ; * PORTJ2. ; * ; * The encoder's pin1 is sampled on a transition from high to low on PINJ0. ; * if PINJ1 is high, a left turn occurred, if PINJ1 is low, a right turn ; * occurred. ; **************************************************************************** dec r15 ; well, let's do a little debouncing anyway brne adjust_UID ; brne checks the Z (zero) register, ; if r15 was not zero after the last operation, ; it will branch us to adjust_UID to check if the ; current delay matches the desired delay ldi r17, 0x40 ; prepare a constant in r17 mov r15, r17 ; put 0x40 in the sample register to set the ; rotary encoder sample counter. this is set to ; catch all rising edges (~1.5ms sample time) lds r17, pinj ; move PORTJ data into r17 sbrs r17, PINJ0 ; skip next if PINJ0 is set rjmp edgecheck_UID ; if it's not set, is it a falling edge? clt ; clear T reg (in SREG) to indicate PINJ0 is high rjmp switchsample_UID ; done looking at MOD2, look at the function ; selector switch ; ********************** ; **** edgecheck ; ********************** edgecheck_UID: ; checks for falling edge brts switchsample_UID ; if the T flag in SREG is set, PINJ0 was low ; on a previous sample - so its not an edge set ; otherwise set the T flag to indicate a falling ; edge sbrs r17, PINJ1 ; check if PINJ1 is high rjmp upcount_UID ; if PINJ0 has just gone low and PINJ1 is low, ; a right turn has transpired and ; upcount will therefore increase the delay ; otherwise, PINJ1 is high and a left turn has ; transpired, so we should decrease the delay ; ### downcount ldi r17, 0x01 ; load our decrement amount into r17 ; (0x01 = 256 samples = 0.006s) sub r13, r17 ; decrement desired delay MSB ; **NOTE: there is no overflow checking here, so ; the count will wrap around if it goes too low rjmp switchsample_UID ; done looking at MOD, look at the function ; selector switch ; ### upcount upcount_UID: ; increment desired delay register ldi r17, 0x01 ; load increment amount into r17 add r13, r17 ; increment MSB ; **NOTE: there is no overflow checking here, so ; the count will wrap around if it goes too high ; ********************** ; **** switchsample ; ********************** switchsample_UID: ; sample the function selector switch ; done at the same rate as the rotary encoder lds r31, pinj ; put switch data into jump location MSB reg andi r31, 0x78 ; mask off all but rotary switch bits 0b01111000 ; ### convert switch position data to program memory location lsr r31 ; shift switch bits to the right lsr r31 ; shift again (multiply by 4) ldi r17, 0x02 ; prepare a constant to add to the switch data add r31, r17 ; add 2 to the memory position to get past the ; data stored for the main file ; ********************** ; **** adjust ; ********************** adjust_UID: ; since we've only changed the desired delay in ; the previous section, we need to implement that ; delay ; this checks to see if the delay time is correct, ; and if it's not it makes an effort to move ; slightly closer to the correct delay andi r26, 0xFE ; this sets the delay time to an even number by ; forcing the last bit to be zero. this is ; necessary because we have stereo data, and every ; two memory positions represent one moment in time cp r26, r22 ; compare actual delay lsb to zero (null register) cpc r27, r13 ; compare with carry actual delay msb with desired ; delay msb breq done_UID ; If equal, head to done, yay! brsh indexdown_UID ; If the same or higher, branch to indexdown ; otherwise, we can assume it is too low ; ### indexup adiw r27:r26, 0x04 ; so increment delay register by 0x04 (2 sample ; periods times 2 (stereo) data entries) rjmp done_UID ; and head to the end ; ### indexdown indexdown_UID: sbiw r27:r26, 0x02 ; decrement delay reg by 0x02 (1 sample period ; times 2 (stereo) data entries) ; ********************** ; **** done ; ********************** done_UID: ; It's been a long hard row to hoe, but we did it! ; oh wait, what? We're going to have to do it ; again? from the beginning? ; but we still need to get our memory pointers all ; lined up! movw r29:r28, r25:r24 ; sync write destination and read address sub r28, r26 ; now subtract by the delay in samples (first lsb) sbc r29, r27 ; subtract msb with carry from previous reti ; return from interrupt so we can get back to our ; idling }}} [[MICrODEC]] [[MicrodecSoftware|Microdec Software]]