3 .word ( 0x0260 | 1<<0 | 5<<2 | 1<<7 | 3<<10 )
4 ; reserved_bits | security_off | lvr_1v8 | io_drv_norm | boot_fast
6 .area OSEG (ABS,OVR,DATA)
7 notes: .ds 16 ; 0x00 .. 0x0f
14 .even ; make next two bytes word-aligned
19 .even ; SP must be aligned
24 ; aliases for memory locations:
49 ; Calibration Parameters:
50 ; Bitshift Variations calls for an 8kHz sample rate; with an interrupt every
51 ; 512 cycles (the next power of two above the 495 cycles the program needs for
52 ; execution), this gives us a clock speed of 512 * 8khz = 4.096MHz. The MCU
53 ; will be powered by a 3V lithium coin cell.
55 calib_freq = 4096000 ; Hz
59 ; during playback: IHRC/4, WDT off, keep ILRC on
60 active_clock = (( 0<<5 | 1<<4 | 0<<3 | 1<<2 | 0<<1 | 0<<0 ))
61 ; during deep-sleep: ILRC/1, WDT off
62 sleep_clock = (( 7<<5 | 1<<4 | 0<<3 | 1<<2 | 0<<1 | 0<<0 ))
63 ; for extra power saving, consider: 6<<5|0<<3 for ilrc/4, 2<<5|1<<3 for ilrc/16
65 ; cycle count (worst-case)
68 ; sample: 115 + 4*g + 2*mod3 = 495
70 ; TOTAL: sample + overhead = 507
83 .org 0x0040 ; leave some space after IVT to allow 'overprogramming'
86 ADD mod3lo, a ; mod3lo = hi+lo
94 AND a, #0xf ; (mod3lo>>4)
95 XCH mod3lo ; a=mod3lo, mod3lo=mod3lo>>4
96 AND a, #0xF ; a=mod3lo&0xf, mod3lo=mod3lo>>4
97 ADD a, mod3lo ; (mod3lo & 0xF)
101 AND a, #0x3 ; a = (mod3lo & 0x3)
103 SR mod3lo ; (mod3lo >> 2)
107 AND a, #0x3 ; a = (mod3lo & 0x3)
109 SR mod3lo ; (mod3lo >> 2)
118 CLEAR mul2 ; this is notes_ix_hi (and should be 0)
120 MOV notes_ix, a ; assumes notes are the first thing in SRAM
132 CLEAR mul1 ; alias of notes_ix, so clear after idxm
133 ; note: LSB of result (mul0) is not needed for our purposes
208 SET1 clkmd, #4 ; enable IHRC
210 MOV clkmd, a ; switch to IHRC
212 ; calibration placeholder:
217 AND a, #( calib_freq )
218 AND a, #( calib_freq>>8 )
219 AND a, #( calib_freq>>16 )
220 AND a, #( calib_freq>>24 )
221 AND a, #( calib_vdd )
222 AND a, #( calib_vdd>>8 )
225 .word 0xffff, 0xffff, 0xffff, 0xffff, 0xffff
226 .word 0xffff, 0xffff, 0xffff, 0xffff, 0xffff
234 MOV a, #0x50 ; data direction: PWM & debug output, rest input
235 MOV pac, a ; (conserves power, apparently)
237 MOV padier, a ; disable pin wakeup, except on audio pin
238 MOV paph, a ; enable pull-up on audio pin
239 ; Line input typically has an impedance of 10k-100kOhm, so we need our
240 ; pull-ups to be even higher. Since the ones of the PMS150C are
241 ; typically 100k-220k, we can use it and don't need an external one.
243 MOV pa, a ; PortA data = 0
246 ; Since (unlike in the ATTiny4 version) the interrupt timer is not tied
247 ; to the PWM frequency, we can use a much faster clock for PWM. The
248 ; highest "carrier frequency" for the PCM samples we can generate is by
249 ; setting Timer2 to 6 bit, (IHRC/1)/1 mode, giving a frequency of
250 ; (4*4.096MHz)/2^6 = 256kHz. We really use (4*4.096MHz)/2^8 = 64kHz.
253 MOV a, #(( 2<<4 | 3<<2 | 1<<1 | 0<<0 ))
254 MOV tm2c, a ; timer2: IHRC, PA4, PWM, not inverted
255 MOV a, #(( 0<<7 | 0<<5 | 0<<0 ))
256 MOV tm2s, a ; 8bit, /4 prescaler, divide by (0+1)
259 MOV a, #(( 0<<0 | 1<<3 | 4<<5 )) ; ovf@bit8 (512cy; ยง9.2.5), clk/4, ihrc
260 ;^xxx: could use 0b000000 syntax for compact binary values
262 MOV a, #(1<<2) ; enable timer16 int, disable all others
266 SET1 eoscr, #0 ; disable bandgap and lvr
267 SET0 gpcc, #7 ; disable comparator
274 ;rom is not mmapped; must load notes into ram first
304 CEQSN a, #0x78 ; compare, skip next if equal
305 ; Note: usually, this is the place where the MCU is put into some
306 ; sort of low power/sleep mode. But the Padauk's stopexe instruction
307 ; causes the ISR to a) run at greatly reduced frequency (100hz vs
308 ; 1khz for timer16@bit11; probably due to slow wakeup), b)
309 ; double-fire some (20-30%) of the time, c) jitter -50% to +10%. so
310 ; we don't sleep at all between samples (which is only a short time
314 ; at this point, i2==0x78, i.e. the music is finished.
315 ; => goto halt (fallthrough)
318 CLEAR i2 ; clear halting signal
320 ; Note: disabling the timers isn't strictly necessary (as stopsys halts
321 ; all timers anyways), but I'm hoping it may reduce power consumption.
322 ; We're lucky that we only need to toggle a single bit to switch
323 ; between the required clock source and 'off' (0010xxxx->0000xxxx for
324 ; timer2, 100xxxxx->000xxxxx for timer16), so we can hack our way out
325 ; of loading an immediate each time.
329 SET1 pa, #4 ; assert a high level on the audio pin for good measure
330 SET0 pac, #4 ; ... before setting it to input mode (optional)
332 ;switch to ilrc clock
335 SET0 clkmd, #4 ; disable ihrc
338 ; (at this point, we wait for an i/o-toggle wake up event to resume execution)
341 MOV clkmd, a ; switch to IHRC again
343 SET1 pac, #4 ; restore output mode for audio pin
345 ;reenable timer16, timer2
354 T1SN intrq, #2 ; if intrq.t16 is triggered, skip next
362 ; send pwm data to timer2:
366 ; generate new sample:
368 MOV a, i2; "mov mem,mem"
369 MOV n, a; does not exist
390 MOV tmp_1, a ; fresh tmp_1:
395 OR a, tmp_1 ; tmp_1 done.
424 MOV tmp_1, a ; a saved in tmp_1; fresh a
426 ; shift-divide by six
427 ; note: i2 is max 0x78; so a will <= 20. (breaks for values >=128)
440 AND a, tmp_1 ; a restored from tmp_1
464 MOV tmp_1, a ; a saved in tmp_1; fresh a
466 ; shift-divide by ten
467 ; note: i2 is max 0x78; so a will <= 12.
480 AND a, tmp_1 ; a restored from tmp_1
487 ; next sample is now ready.