welcome: please sign in
location: DelayTutorialAsm

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.

   1 ; ****************************************************************************
   2 ; * program: delay-16b-rotary.asm                                            *
   3 ; * UID = Tutorial - unique id to eliminate conflicts between variables,     *
   4 ; *       replace each instance of UID in the code with this unique id.      *
   5 ; * 16b address space (.7s delay time)                                       *
   6 ; * stereo data                                                              *
   7 ; * rotary encoder (MOD2) delay time (0s - .7s)                              *
   8 ; ****************************************************************************
   9 ; *                                                                          *
  10 ; * PROGRAM OVERVIEW                                                         *
  11 ; *                                                                          *
  12 ; * data is read in from memory and written to the codec at the same time    *
  13 ; * new data is written to the memory from the codec.  the delay time is set *
  14 ; * by the rotary encoder (MOD2) on PORTJ.  if the rotary encoder is turned  *
  15 ; * to the right, the delay time is increased by a fixed amount.  if it is   *
  16 ; * turned to the left, the delay time is decreased by a fixed amount.  the  *
  17 ; * rotary encoder only changes the desired delay time, and once each sample *
  18 ; * sample period, the desired delay is compared to the actual delay.  if    *
  19 ; * they are not equal, the actual delay is either incremented by 2 samples  *
  20 ; * decreased by one sample.  since the memory pointers are incrementing by  *
  21 ; * 1 sample each sample period, this gives an effective increase of 1       *
  22 ; * sample, or decrease of 2 samples per period.  this has the effect of     *
  23 ; * doubling the read rate to go forward, or playing in reverse to go        *
  24 ; * backwards.  this eliminates the audio glitches of quickly jumping to the *
  25 ; * new location in the SRAM.                                                *
  26 ; *                                                                          *
  27 ; * register usage - may be redefined in other sections                      *
  28 ; *                                                                          *
  29 ; * r0                                                                       *
  30 ; * r1                                                                       *
  31 ; * r2  left lsb out                                                         *
  32 ; * r3  left msb out                                                         *
  33 ; * r4  right lsb out                                                        *
  34 ; * r5  right msb out                                                        *
  35 ; * r6  left lsb in                                                          *
  36 ; * r7  left msb in                                                          *
  37 ; * r8  right lsb in                                                         *
  38 ; * r9  right msb in                                                         *
  39 ; * r10                                                                      *
  40 ; * r11                                                                      *
  41 ; * r12                                                                      *
  42 ; * r13 desired delay msb                                                    *
  43 ; * r14                                                                      *
  44 ; * r15 switch/rotary encoder sample counter                                 *
  45 ; * r16 temporary swap register                                              *
  46 ; * r17 temporary swap register                                              *
  47 ; * r18                                                                      *
  48 ; * r19                                                                      *
  49 ; * r20                                                                      *
  50 ; * r21                                                                      *
  51 ; * r22 null register (always equals $00)                                    *
  52 ; * r23                                                                      *
  53 ; * r24 write address lsb                                                    *
  54 ; * r25 write address msb                                                    *
  55 ; * r26 actual delay lsb                                                     *
  56 ; * r27 actual delay msb                                                     *
  57 ; * r28 read address lsb                                                     *
  58 ; * r29 read address msb                                                     *
  59 ; * r30 jump location for interrupt lsb                                      *
  60 ; * r31 jump location for interrupt msb                                      *
  61 ; * t                                                                        *
  62 ; ****************************************************************************
  63 
  64 
  65 ; * NOTES: - all unprocessed (dry) audio data comes in from the SPI port, via
  66 ;            the SPDR i/o register
  67 ;          - all processed (wet) audio goes out over the SPI port, also via
  68 ;            the SPDR i/o register
  69 ;          - all external memory addressing happens over the PORTD and PORTH
  70 ;            i/o registers  
  71 ;          - all external memory control happens over PORTG i/o register
  72 ;          - all data we are saving and reading back (like our delayed audio)
  73 ;            goes out over the PORTA and PORTC i/o registers, and comes in
  74 ;            via the PINA and PINC i/o registers
  75 
  76 
  77 ; **********************
  78 ; **** PROGRAM START
  79 ; **********************
  80 
  81 ; ### initiate data transfer to the codec
  82 
  83 sbi portb, portb0           ; set bit PORTB0 (aka DACLRC - DAC sample rate
  84                             ; left/right clock)
  85                             ;
  86                             ; **NOTE: from WM8731 data sheet, page 36:
  87                             ; "DACLRC is an alignment clock that controls
  88                             ; whether Left or Right channel data is present
  89                             ; on DACDAT"
  90 
  91 ; ### now on SPI - LEFT MSB OUT
  92 
  93 out spdr, r3                ; send out processed left channel msb (or
  94                             ; whatever's currently in r3) to SPI data register
  95 cbi portb, portb0           ; clear DACLRC
  96 
  97 adiw r25:r24, 0x01          ; increment write address pointer by 1
  98 adiw r29:r28, 0x01          ; increment read address pointer by 1
  99 ldi  r22, 0x00              ; setup null register
 100 
 101 
 102 ; **********************
 103 ; **** wait1
 104 ; **********************
 105 
 106 wait1_UID:                 ; wait to see if processed left channel msb has
 107                            ; been sent
 108 
 109 in   r17, spsr             ; move SPSPR i/o register (SPI status register) to
 110                            ; r17 so we evaluate it
 111 sbrs r17, spif             ; skip next instruction if the SPIF (SPIF interrupt
 112                            ; flag) bit is set
 113                            ; **NOTE: from atmega3250P data sheet, page 162:
 114                            ; "When a serial transfer is complete, the SPIF
 115                            ; flag is set"
 116 rjmp wait1_UID             ; keep checking until SPIF is set
 117                            ; and then...
 118 
 119 ; ### after wait1, new SPI data ready
 120 
 121 in r7, spdr                ; move SPIDR (SPI data register) into r7
 122                            ; (remember SPDR is read/write, page 162)
 123                            ; this moves the incoming (dry) left channel msb
 124                            ; to r7
 125 
 126 ; ### end of wet/dry left msb transfer
 127 ; ### now on SPI - LEFT LSB OUT
 128 
 129 out spdr, r2               ; send the processed (wet) left lsb out
 130 
 131 ; ### retrieve stored left channel data from SRAM:
 132 
 133 out portd, r28             ; move read address lsb pointer to PORTD
 134 sts porth, r29             ; move read address msb pointer to PORTH
 135                            ; **NOTE: - this is a special instruction because
 136                            ; the i/o PORTH register is wayyy up there in the
 137                            ; sram - 'out' completes in one cycle, 'sts'
 138                            ; completes in two
 139 
 140 nop                        ; read address msb hits the port, now wait for  (1)
 141 nop                        ; a latch time of two cycles...                 (2)
 142 
 143                            ; DING! your data is ready, please pick it up!
 144 in r2, pina                ; D0-7, aka lsb, is on PORTA, now stored in r2
 145 in r3, pinc                ; D8-15, aka msb, is on PORTC, now stored in r3
 146 
 147 ; ### left channel SRAM retrieve completed
 148 
 149                            ; now we've got a little time to kill, so...
 150 adiw r29:r28, 0x01         ; increment read address pointer by 1
 151 
 152 
 153 ; **********************
 154 ; **** wait2
 155 ; **********************
 156 
 157 wait2_UID:                 ; wait to see if wet left channel lsb has been sent
 158 
 159 in   r17, spsr             ; SPI status register to r17 (looking familiar?)
 160 sbrs r17, spif             ; skip next if SPIF bit not set...
 161 rjmp wait2_UID             ; loop until SPIF set
 162 
 163                            ; **NOTE: and now a couple of words about rjmp:
 164                            ; - it can only move within 2k of memory (ie,
 165                            ;   can't jump all the way to the start, or end,
 166                            ;   of a long program)
 167                            ; - it doesn't do anything to the stack  (so if
 168                            ;   it loops a thousand times, the stack isn't
 169                            ;   going to overflow)
 170                            ; - it takes two cycles
 171 
 172 ; ### after wait2 is finished, new SPI data ready
 173 
 174 in r6, spdr                ; receive in dry left channel lsb
 175                            ; **NOTE: that gives us both bytes of the
 176                            ; incoming left channel, and both wet bytes have
 177                            ; been sent so we can now move on to the right
 178                            ; channel
 179 
 180 ; ### end of wet/dry left lsb transfer
 181 ; ### now on SPI - RIGHT MSB OUT
 182 
 183 out spdr, r5               ; send out (wet) right msb
 184 
 185 ; ### writing (dry) left channel to SRAM:
 186 
 187 out portd, r24             ; update data address ports to reflect where we
 188                            ; want to write
 189 sts porth, r25             ; remember 'out' and 'sts'?
 190 
 191 out portg, r22             ; woah, PORTG? what's this?  Check out the SRAM
 192                            ; data sheet and the schematic:
 193 
 194                            ; r22 is currently 0x00, so we are pulling CE
 195                            ; (Chip Enable) and WE (Write Enable) low, and
 196                            ; writing our high address bits to zero
 197 
 198                            ; **NOTE:  from AS7C4098 data sheet, page 2:
 199                            ; "Data on input pins IO1-IO16 are written on
 200                            ; the rising edge of WE..."
 201 
 202                            ; "To avoid bus contention, external devices
 203                            ; should drive IO pins only after outputs have
 204                            ; been disabled with OE (output enable) or WE"
 205 
 206                            ; "A read cycle is accomplished by asserting OE
 207                            ; and CE, with WE high."
 208 
 209 ldi r17, 0xFF              ; prepare a salad of ones
 210 out ddra, r17              ; send them to the PORTA direction register
 211 out ddrC, r17              ; and to the PORTC direction register
 212                            ; to set the ports as output
 213                            ; see ATmega3250P data sheet, page 65,
 214                            ; 'Switching Between Input and Output'
 215 
 216 out porta, r6              ; with these two 'out' instructions
 217 out portc, r7              ; we send left channel dry data to the SRAM
 218 
 219 sbi portg, portg2          ; and as soon as we pull WE on PORTG2 high,
 220                            ; zzztt! a single audio sample is written into
 221                            ; memory, just like that.
 222 
 223 ; ### left channel SRAM write completed
 224 
 225 out ddra, r22              ; now, we happen to know that r22 contains a
 226 out ddrc, r22              ; bunch of zeros so we can use that register to
 227                            ; set PORTA and PORTC back to an input state.
 228 
 229 
 230 ; **********************
 231 ; **** wait3
 232 ; **********************
 233 
 234 wait3_UID:                 ; meanwhile, back at the codec, we are still
 235                            ; transferring the right channel lsb
 236 
 237 in   r17, spsr             ; check out the SPSR, check out the SPIF,
 238 sbrs r17, spif             ; we know what we're doing here now, right?
 239 rjmp wait3_UID             ; loop until transfer completed
 240 
 241 ; ### end of wait3, new data ready!
 242 
 243 in r9, spdr                ; recieve in (dry) right channel msb
 244 
 245 ; ### end of wet/dry right msb transfer 
 246 ; ### now on SPI - RIGHT LSB OUT
 247 
 248 out spdr, r4               ; send out (wet) right channel lsb
 249 
 250 ; ### retrieve stored right channel data from SRAM
 251 
 252 out portd, r28             ; set up the address we want to read from
 253 sts porth, r29             ; on PORTD and PORTH
 254 
 255 nop                        ; killing time again...
 256 nop                        ; two cycles, while the SRAM latches the address
 257 
 258                            ; and once more, ding!  our data is now waiting
 259                            ; on the SRAM data lines
 260 in r4, pina                ; right lsb
 261 in r5, pinc                ; right msb (this is audio data we saved in the
 262                            ; past)
 263 
 264 ; ### right channel SRAM data retrieval completed
 265 
 266 adiw r25:r24, 0x01         ; increment the write address (we're going to be
 267                            ; using that pointer next)
 268 
 269 
 270 ; **********************
 271 ; **** wait4
 272 ; **********************
 273 
 274 wait4_UID:                 ; checking up on that SPI transfer
 275 
 276 in r17, spsr
 277 sbrs r17, spif
 278 rjmp wait4_UID             ; loop until SPIF is set
 279 
 280 ; ### end of wait4, more new data!
 281 
 282 in r8, spdr                ; bring in right channel lsb
 283 
 284 ; ### end of wet/dry right lsb transfer
 285 
 286                            ; now that we have both bytes of the right
 287                            ; channel dry audio, we can store it in the
 288                            ; SRAM...
 289 
 290 ; ### writing (dry) right channel to SRAM
 291 
 292 out portd, r24             ; give the SRAM the write address, lsb
 293 sts porth, r25             ; now msb
 294 
 295 out portg, r22             ; pull WE low, (CE is already low, WE was high
 296                            ; for read operations)
 297 ldi r17, 0xFF              ; prepare a bevy of ones
 298 out ddra, r17              ; set PORTA as output
 299 out ddrc, r17              ; set PORTC as output
 300 out porta, r8              ; put the data on the ports, lsb
 301 out portc, r9              ; msb
 302 sbi portg, portg2          ; zzzt!  pull WE high and write the data
 303 
 304 out ddra, r22              ; reconfigure PORTA as input
 305 out ddrc, r22              ; reconfigure PORTC as input
 306 
 307 
 308 ; ****************************************************************************
 309 ; **** check rotary encoder and adjust delay time
 310 ; ****************************************************************************
 311 ; * The rotary encoder is externally debounced, so we don't have to do that
 312 ; * here. You'll see it in the schematic labeled MOD2 on PORTJ0, PORTJ1, and
 313 ; * PORTJ2.
 314 ; *
 315 ; * The encoder's pin1 is sampled on a transition from high to low on PINJ0.
 316 ; * if PINJ1 is high, a left turn occurred, if PINJ1 is low, a right turn
 317 ; * occurred.
 318 ; ****************************************************************************
 319 
 320 dec r15                    ; well, let's do a little debouncing anyway
 321 brne adjust_UID            ; brne checks the Z (zero) register,
 322                            ; if r15 was not zero after the last operation,
 323                            ; it will branch us to adjust_UID to check if the
 324                            ; current delay matches the desired delay
 325 
 326 ldi r17, 0x40              ; prepare a constant in r17
 327 mov r15, r17               ; put 0x40 in the sample register to set the
 328                            ; rotary encoder sample counter.  this is set to 
 329                            ; catch all rising edges (~1.5ms sample time)
 330 lds r17, pinj              ; move PORTJ data into r17
 331 sbrs r17, PINJ0            ; skip next if PINJ0 is set
 332 rjmp edgecheck_UID         ; if it's not set, is it a falling edge?
 333 
 334 clt                        ; clear T reg (in SREG) to indicate PINJ0 is high
 335 rjmp switchsample_UID      ; done looking at MOD2, look at the function
 336                            ; selector switch
 337 
 338 ; **********************
 339 ; **** edgecheck
 340 ; **********************
 341 
 342 edgecheck_UID:             ; checks for falling edge
 343 brts switchsample_UID      ; if the T flag in SREG is set, PINJ0 was low
 344                            ; on a previous sample - so its not an edge
 345 set                        ; otherwise set the T flag to indicate a falling
 346                            ; edge
 347 sbrs r17, PINJ1            ; check if PINJ1 is high
 348 rjmp upcount_UID           ; if PINJ0 has just gone low and PINJ1 is low,
 349                            ; a right turn has transpired and
 350                            ; upcount will therefore increase the delay
 351                            ; otherwise, PINJ1 is high and a left turn has
 352                            ; transpired, so we should decrease the delay
 353 
 354 ; ### downcount
 355 
 356 ldi r17, 0x01              ; load our decrement amount into r17
 357                            ; (0x01 = 256 samples = 0.006s)
 358 sub r13, r17               ; decrement desired delay MSB
 359                            ; **NOTE: there is no overflow checking here, so
 360                            ; the count will wrap around if it goes too low   
 361 rjmp switchsample_UID      ; done looking at MOD, look at the function
 362                            ; selector switch
 363 
 364 ; ### upcount
 365 
 366 upcount_UID:               ; increment desired delay register
 367 ldi r17, 0x01              ; load increment amount into r17
 368 add r13, r17               ; increment MSB
 369                            ; **NOTE: there is no overflow checking here, so
 370                            ; the count will wrap around if it goes too high
 371 
 372 
 373 ; **********************
 374 ; **** switchsample
 375 ; **********************
 376 
 377 switchsample_UID:          ; sample the function selector switch
 378                            ; done at the same rate as the rotary encoder
 379 
 380 lds  r31, pinj             ; put switch data into jump location MSB reg
 381 andi r31, 0x78             ; mask off all but rotary switch bits 0b01111000
 382 
 383 ; ### convert switch position data to program memory location
 384 
 385 lsr  r31                   ; shift switch bits to the right
 386 lsr  r31                   ; shift again (multiply by 4)
 387 ldi  r17, 0x02             ; prepare a constant to add to the switch data         
 388 add r31, r17               ; add 2 to the memory position to get past the
 389                            ; data stored for the main file
 390 
 391 
 392 ; **********************
 393 ; **** adjust
 394 ; **********************
 395 
 396 adjust_UID:                ; since we've only changed the desired delay in
 397                            ; the previous section, we need to implement that
 398                            ; delay
 399                            ; this checks to see if the delay time is correct,
 400                            ; and if it's not it makes an effort to move
 401                            ; slightly closer to the correct delay
 402 
 403 andi r26, 0xFE             ; this sets the delay time to an even number by
 404                            ; forcing the last bit to be zero.  this is
 405                            ; necessary because we have stereo data, and every
 406                            ; two memory positions represent one moment in time
 407 cp r26, r22                ; compare actual delay lsb to zero (null register)
 408 cpc r27, r13               ; compare with carry actual delay msb with desired
 409                            ; delay msb
 410 breq done_UID              ; If equal, head to done, yay!
 411 brsh indexdown_UID         ; If the same or higher, branch to indexdown
 412                            ; otherwise, we can assume it is too low
 413 
 414 ; ### indexup
 415 
 416 adiw r27:r26, 0x04         ; so increment delay register by 0x04 (2 sample
 417                            ; periods times 2 (stereo) data entries)
 418 rjmp done_UID              ; and head to the end
 419 
 420 ; ### indexdown
 421 
 422 indexdown_UID:
 423 
 424 sbiw r27:r26, 0x02         ; decrement delay reg by 0x02 (1 sample period
 425                            ; times 2 (stereo) data entries)
 426 
 427 
 428 ; **********************
 429 ; **** done
 430 ; **********************
 431 
 432 done_UID:                  ; It's been a long hard row to hoe, but we did it!
 433                            ; oh wait, what?  We're going to have to do it
 434                            ; again?  from the beginning?
 435                            ; but we still need to get our memory pointers all
 436                            ; lined up!
 437 
 438 movw r29:r28, r25:r24      ; sync write destination and read address
 439 sub r28, r26               ; now subtract by the delay in samples (first lsb)
 440 sbc r29, r27               ; subtract msb with carry from previous
 441 
 442 reti                       ; return from interrupt so we can get back to our
 443                            ; idling
 444 

MICrODEC

Microdec Software

DelayTutorialAsm (last edited 2010-08-21 01:53:49 by guest)