]>
Commit | Line | Data |
---|---|---|
a43c9c44 TG |
1 | .area FUSE (ABS) |
2 | .org 0x3ff*2 | |
3 | .word ( 0x0260 | 1<<0 | 5<<2 | 1<<7 | 3<<10 ) | |
4 | ; reserved_bits | security_off | lvr_1v8 | io_drv_norm | boot_fast | |
5 | ||
428f6a3d | 6 | .area OSEG (ABS,OVR,DATA) |
ec2152ea TG |
7 | notes: .ds 16 ; 0x00 .. 0x0f |
8 | i0: .ds 1 ; 0x10 | |
9 | i1: .ds 1 ; 0x11 | |
10 | i2: .ds 1 ; 0x12 | |
11 | n: .ds 1 ; 0x13 | |
bdcf57fd | 12 | pwm: .ds 1 ; 0x14 |
ec2152ea | 13 | tmp_1: .ds 1 ; 0x15 |
bdcf57fd TG |
14 | .even ; make next two bytes word-aligned |
15 | tmp_lo: .ds 1 ; 0x16 | |
16 | tmp_hi: .ds 1 ; 0x17 | |
a43c9c44 TG |
17 | |
18 | .even ; SP must be aligned | |
19 | stack_start: .ds 1 | |
ec2152ea TG |
20 | .area SSEG |
21 | stack: .ds 1 | |
22 | ||
23 | ; aliases for memory locations: | |
bdcf57fd | 24 | notes_ix = tmp_lo |
ec2152ea TG |
25 | t = tmp_1 |
26 | mul2 = tmp_hi | |
27 | mul1 = tmp_lo | |
28 | mod3hi = tmp_hi | |
29 | mod3lo = tmp_lo | |
30 | ||
a43c9c44 TG |
31 | ; io addresses |
32 | clkmd = 0x03 | |
33 | inten = 0x04 | |
34 | intrq = 0x05 | |
35 | tm2c = 0x1C | |
36 | tm2b = 0x09 | |
37 | tm2s = 0x17 | |
38 | t16m = 0x06 | |
39 | eoscr = 0x0A | |
40 | padier = 0x0D | |
41 | pa = 0x10 | |
42 | pac = 0x11 | |
43 | paph = 0x12 | |
44 | misc = 0x1B | |
45 | gpcc = 0x1A | |
46 | ihrcr = 0x0B | |
47 | ||
48 | ; Calibration Parameters: | |
49 | ; Bitshift Variations calls for an 8kHz sample rate; with an interrupt every | |
50 | ; 512 cycles (the next power of two above the 495 cycles the program needs for | |
51 | ; execution), this gives us a clock speed of 512 * 8khz = 4.096MHz. The MCU | |
936a9ec2 | 52 | ; will be powered by a 3V lithium coin cell. |
8410984a | 53 | do_calib = 1 |
a43c9c44 TG |
54 | calib_freq = 4096000 ; Hz |
55 | calib_vdd = 3000 ; mV | |
56 | ||
54babda0 TG |
57 | ; Clock Parameters: |
58 | ; during playback: IHRC/4, WDT off, keep ILRC on | |
59 | active_clock = (( 0<<5 | 1<<4 | 0<<3 | 1<<2 | 0<<1 | 0<<0 )) | |
60 | ; during deep-sleep: ILRC/1, WDT off | |
61 | sleep_clock = (( 7<<5 | 1<<4 | 0<<3 | 1<<2 | 0<<1 | 0<<0 )) | |
bdcf57fd | 62 | ; for extra power saving, consider: 6<<5|0<<3 for ilrc/4, 2<<5|1<<3 for ilrc/16 |
54babda0 | 63 | |
c8495413 TG |
64 | ; cycle count (worst-case) |
65 | ; mod3: 28 | |
66 | ; g: 81 | |
67 | ; sample: 115 + 4*g + 2*mod3 = 495 | |
936a9ec2 TG |
68 | ; isr overhead: 12 |
69 | ; TOTAL: sample + overhead = 507 | |
ec2152ea | 70 | |
a43c9c44 TG |
71 | ; portA.4: audio out |
72 | ; portA.6: debug pin | |
73 | ||
ec2152ea TG |
74 | |
75 | .area CSEG (CODE,ABS) | |
ec2152ea | 76 | .org 0x0000 |
ec2152ea TG |
77 | GOTO init |
78 | ||
a43c9c44 TG |
79 | .org 0x0020 |
80 | GOTO interrupt | |
ec2152ea | 81 | |
bdcf57fd | 82 | .org 0x0040 ; leave some space after IVT to allow 'overprogramming' |
ec2152ea TG |
83 | mod3: |
84 | MOV a, mod3hi | |
85 | ADD mod3lo, a ; mod3lo = hi+lo | |
86 | MOV a, #0 | |
87 | ADDC a ; mod3hi, 1bit | |
88 | SWAP a | |
89 | MOV mod3hi, a | |
90 | ||
91 | MOV a, mod3lo | |
92 | SWAP a | |
93 | AND a, #0xf ; (mod3lo>>4) | |
94 | XCH mod3lo ; a=mod3lo, mod3lo=mod3lo>>4 | |
95 | AND a, #0xF ; a=mod3lo&0xf, mod3lo=mod3lo>>4 | |
96 | ADD a, mod3lo ; (mod3lo & 0xF) | |
97 | ADD a, mod3hi | |
98 | MOV mod3lo, a | |
99 | ||
100 | AND a, #0x3 ; a = (mod3lo & 0x3) | |
101 | SR mod3lo | |
102 | SR mod3lo ; (mod3lo >> 2) | |
103 | ADD a, mod3lo | |
104 | MOV mod3lo, a | |
105 | ||
106 | AND a, #0x3 ; a = (mod3lo & 0x3) | |
107 | SR mod3lo | |
108 | SR mod3lo ; (mod3lo >> 2) | |
109 | ADD a, mod3lo | |
110 | ||
111 | SUB a, #3 | |
112 | T0SN f, c | |
113 | ADD a, #3 | |
114 | RET | |
115 | ||
116 | g: | |
bdcf57fd | 117 | CLEAR mul2 ; this is notes_ix_hi (and should be 0) |
ec2152ea | 118 | AND a, #0x7 |
ebad4b34 | 119 | MOV notes_ix, a ; assumes notes are the first thing in SRAM |
ec2152ea TG |
120 | ; test i2 & 3: |
121 | MOV a, i2 | |
122 | AND a, #3 | |
02a29960 TG |
123 | T0SN f, z |
124 | GOTO skip_plus8 | |
125 | MOV a, #8 | |
126 | ADD notes_ix, a | |
127 | skip_plus8: | |
ec2152ea TG |
128 | IDXM a, notes_ix |
129 | ||
130 | MOV t, a | |
bdcf57fd | 131 | CLEAR mul1 ; alias of notes_ix, so clear after idxm |
ec2152ea TG |
132 | ; note: LSB of result (mul0) is not needed for our purposes |
133 | ;;1/8: | |
134 | SR t | |
135 | T1SN f, c | |
136 | GOTO skip1 | |
137 | MOV a, i0 | |
138 | ADD mul1, a | |
139 | MOV a, i1 | |
140 | ADDC mul2, a | |
141 | skip1: SR mul2 | |
142 | SRC mul1 | |
143 | ;;2/8: | |
144 | SR t | |
145 | skip2: SR mul2 | |
146 | SRC mul1 | |
147 | ;;3/8: | |
148 | SR t | |
149 | T1SN f, c | |
150 | GOTO skip3 | |
151 | MOV a, i0 | |
152 | ADD mul1, a | |
153 | MOV a, i1 | |
154 | ADDC mul2, a | |
155 | skip3: SR mul2 | |
156 | SRC mul1 | |
157 | ;;4/8: | |
158 | SR t | |
159 | T1SN f, c | |
160 | GOTO skip4 | |
161 | MOV a, i0 | |
162 | ADD mul1, a | |
163 | MOV a, i1 | |
164 | ADDC mul2, a | |
165 | skip4: SR mul2 | |
166 | SRC mul1 | |
167 | ;;5/8: | |
168 | SR t | |
169 | T1SN f, c | |
170 | GOTO skip5 | |
171 | MOV a, i0 | |
172 | ADD mul1, a | |
173 | MOV a, i1 | |
174 | ADDC mul2, a | |
175 | skip5: SR mul2 | |
176 | SRC mul1 | |
177 | ;;6/8: | |
178 | SR t | |
179 | T1SN f, c | |
180 | GOTO skip6 | |
181 | MOV a, i0 | |
182 | ADD mul1, a | |
183 | skip6: | |
bdcf57fd | 184 | SR mul1 |
ec2152ea TG |
185 | ;;7/8: |
186 | SR t | |
187 | T1SN f, c | |
188 | GOTO skip7 | |
189 | MOV a, i0 | |
190 | ADD mul1, a | |
191 | skip7: | |
bdcf57fd | 192 | SR mul1 |
ec2152ea TG |
193 | ;;8/8: |
194 | SR t | |
195 | T1SN f, c | |
196 | GOTO skip8 | |
197 | MOV a, i0 | |
198 | ADD mul1, a | |
199 | skip8: | |
bdcf57fd | 200 | SR mul1 |
ec2152ea TG |
201 | |
202 | MOV a, mul1 | |
203 | RET | |
204 | ||
205 | init: | |
54babda0 | 206 | ; clock setup: |
a43c9c44 | 207 | SET1 clkmd, #4 ; enable IHRC |
54babda0 TG |
208 | MOV a, #active_clock |
209 | MOV clkmd, a ; switch to IHRC | |
a43c9c44 | 210 | |
bdcf57fd | 211 | ; calibration placeholder: |
8410984a | 212 | .if do_calib |
bdcf57fd TG |
213 | AND a, #'R' |
214 | AND a, #'C' | |
215 | AND a, #1 ; IHRC | |
216 | AND a, #( calib_freq ) | |
217 | AND a, #( calib_freq>>8 ) | |
218 | AND a, #( calib_freq>>16 ) | |
219 | AND a, #( calib_freq>>24 ) | |
220 | AND a, #( calib_vdd ) | |
221 | AND a, #( calib_vdd>>8 ) | |
222 | AND a, #ihrcr | |
8410984a TG |
223 | .else |
224 | .word 0xffff, 0xffff, 0xffff, 0xffff, 0xffff | |
225 | .word 0xffff, 0xffff, 0xffff, 0xffff, 0xffff | |
226 | .endif | |
a43c9c44 TG |
227 | |
228 | ;stack setup: | |
229 | MOV a, #stack_start | |
230 | MOV sp, a | |
231 | ||
232 | ; portA setup: | |
54babda0 TG |
233 | MOV a, #0x50 ; data direction: PWM & debug output, rest input |
234 | MOV pac, a ; (conserves power, apparently) | |
235 | MOV a, #(( 1<<4 )) | |
236 | MOV padier, a ; disable pin wakeup, except on audio pin | |
6b911797 TG |
237 | MOV paph, a ; enable pull-up on audio pin |
238 | ; Line input typically has an impedance of 10k-100kOhm, so we need our | |
239 | ; pull-ups to be even higher. Since the ones of the PMS150C are | |
240 | ; typically 100k-220k, we can use it and don't need an external one. | |
241 | MOV a, #0 | |
a43c9c44 | 242 | MOV pa, a ; PortA data = 0 |
a43c9c44 TG |
243 | |
244 | ; timer2/pwm setup: | |
245 | ; Since (unlike in the ATTiny4 version) the interrupt timer is not tied | |
246 | ; to the PWM frequency, we can use a much faster clock for PWM. The | |
247 | ; highest "carrier frequency" for the PCM samples we can generate is by | |
248 | ; setting Timer2 to 6 bit, (IHRC/1)/1 mode, giving a frequency of | |
82fa1697 | 249 | ; (4*4.096MHz)/2^6 = 256kHz. We really use (4*4.096MHz)/2^8 = 64kHz. |
54babda0 TG |
250 | MOV pwm, a ; clear |
251 | MOV tm2b, a ; clear | |
a43c9c44 TG |
252 | MOV a, #(( 2<<4 | 3<<2 | 1<<1 | 0<<0 )) |
253 | MOV tm2c, a ; timer2: IHRC, PA4, PWM, not inverted | |
82fa1697 | 254 | MOV a, #(( 0<<7 | 0<<5 | 0<<0 )) |
a43c9c44 TG |
255 | MOV tm2s, a ; 8bit, /4 prescaler, divide by (0+1) |
256 | ||
257 | ;timer16/ivr setup | |
e8e4d78f | 258 | MOV a, #(( 0<<0 | 1<<3 | 4<<5 )) ; ovf@bit8 (512cy; ยง9.2.5), clk/4, ihrc |
bdcf57fd | 259 | ;^xxx: could use 0b000000 syntax for compact binary values |
a43c9c44 TG |
260 | MOV t16m, a |
261 | MOV a, #(1<<2) ; enable timer16 int, disable all others | |
262 | MOV inten, a | |
263 | ||
264 | ; misc setup: | |
265 | SET1 eoscr, #0 ; disable bandgap and lvr | |
266 | SET0 gpcc, #7 ; disable comparator | |
267 | ||
268 | ; memory setup: | |
ec2152ea TG |
269 | CLEAR i0 |
270 | CLEAR i1 | |
271 | CLEAR i2 | |
272 | ||
a43c9c44 | 273 | ;rom is not mmapped; must load notes into ram first |
ec2152ea TG |
274 | MOV a, #0x84 |
275 | MOV notes+0x0, a | |
276 | MOV notes+0x5, a | |
277 | MOV a, #0x9d | |
278 | MOV notes+0x1, a | |
279 | MOV notes+0x4, a | |
280 | MOV a, #0xb0 | |
281 | MOV notes+0x2, a | |
282 | MOV notes+0xA, a | |
283 | MOV a, #0x69 | |
284 | MOV notes+0x3, a | |
285 | MOV notes+0x6, a | |
286 | MOV notes+0xB, a | |
287 | MOV notes+0xE, a | |
288 | MOV a, #0x58 | |
289 | MOV notes+0x7, a | |
290 | MOV notes+0xF, a | |
291 | MOV a, #0x75 | |
292 | MOV notes+0x8, a | |
293 | MOV notes+0xD, a | |
294 | MOV a, #0x8c | |
295 | MOV notes+0x9, a | |
296 | MOV notes+0xC, a | |
297 | ||
a43c9c44 | 298 | ENGINT |
ec2152ea TG |
299 | |
300 | loop: | |
54babda0 TG |
301 | MOV a, i2 |
302 | CEQSN a, #0x78 ; compare, skip next if equal | |
a43c9c44 TG |
303 | ; Note: usually, this is the place where the MCU is put into some |
304 | ; sort of low power/sleep mode. But the Padauk's stopexe instruction | |
305 | ; causes the ISR to a) run at greatly reduced frequency (100hz vs | |
306 | ; 1khz for timer16@bit11; probably due to slow wakeup), b) | |
307 | ; double-fire some (20-30%) of the time, c) jitter -50% to +10%. so | |
308 | ; we don't sleep at all between samples (which is only a short time | |
309 | ; anyways). | |
ec2152ea TG |
310 | GOTO loop |
311 | ||
54babda0 TG |
312 | ; at this point, i2==0x78, i.e. the music is finished. |
313 | ; => goto halt (fallthrough) | |
314 | halt: | |
315 | DISGINT | |
316 | CLEAR i2 ; clear halting signal | |
317 | ||
318 | ; Note: disabling the timers isn't strictly necessary (as stopsys halts | |
319 | ; all timers anyways), but I'm hoping it may reduce power consumption. | |
320 | ; We're lucky that we only need to toggle a single bit to switch | |
321 | ; between the required clock source and 'off' (0010xxxx->0000xxxx for | |
322 | ; timer2, 100xxxxx->000xxxxx for timer16), so we can hack our way out | |
323 | ; of loading an immediate each time. | |
324 | SET0 tm2c, #5 | |
325 | SET0 t16m, #7 | |
326 | ||
327 | SET1 pa, #4 ; assert a high level on the audio pin for good measure | |
328 | SET0 pac, #4 ; ... before setting it to input mode (optional) | |
329 | ||
330 | ;switch to ilrc clock | |
331 | MOV a, #sleep_clock | |
332 | MOV clkmd, a | |
333 | SET0 clkmd, #4 ; disable ihrc | |
334 | ||
335 | STOPSYS | |
336 | ; (at this point, we wait for an i/o-toggle wake up event to resume execution) | |
337 | ||
338 | MOV a, #active_clock | |
339 | MOV clkmd, a ; switch to IHRC again | |
340 | ||
341 | SET1 pac, #4 ; restore output mode for audio pin | |
342 | ||
343 | ;reenable timer16, timer2 | |
344 | SET1 tm2c, #5 | |
345 | SET1 t16m, #7 | |
346 | ||
347 | ENGINT | |
348 | GOTO loop | |
349 | ||
a43c9c44 TG |
350 | interrupt: |
351 | PUSH af | |
a43c9c44 TG |
352 | T1SN intrq, #2 ; if intrq.t16 is triggered, skip next |
353 | GOTO ivr_end | |
354 | ||
355 | ;clear t16int: | |
356 | SET0 intrq, #2 | |
357 | ||
358 | SET1 pa, #6 ; debug | |
359 | ||
54babda0 | 360 | ; send pwm data to timer2: |
a43c9c44 | 361 | MOV a, pwm |
a43c9c44 | 362 | MOV tm2b, a |
ec2152ea | 363 | |
a43c9c44 | 364 | ; generate new sample: |
6b911797 | 365 | voice1: |
ec2152ea TG |
366 | MOV a, i2; "mov mem,mem" |
367 | MOV n, a; does not exist | |
368 | SL n | |
369 | SL n | |
370 | MOV a, i1 | |
371 | SWAP a | |
372 | AND a, #0xf | |
373 | SR a | |
374 | SR a | |
375 | OR n, a | |
376 | ||
377 | MOV a, n | |
378 | CALL g | |
379 | SWAP a | |
380 | AND a, #0x1 | |
381 | MOV pwm, a | |
382 | ||
6b911797 | 383 | voice2: |
ec2152ea TG |
384 | MOV a, i2 |
385 | SL a | |
386 | SL a | |
387 | SL a | |
388 | MOV tmp_1, a ; fresh tmp_1: | |
389 | MOV a, i1 | |
390 | SWAP a | |
391 | AND a, #0xf | |
392 | SR a | |
393 | OR a, tmp_1 ; tmp_1 done. | |
394 | XOR a, n | |
395 | CALL g | |
396 | SR a | |
397 | AND a, i2 | |
398 | SR a | |
399 | AND a, #3 | |
400 | ADD pwm, a | |
401 | ||
6b911797 | 402 | voice3: |
ec2152ea TG |
403 | MOV a, i2 |
404 | MOV mod3hi, a | |
405 | SR mod3hi | |
406 | SR mod3hi | |
407 | SR mod3hi | |
408 | SWAP a | |
409 | AND a, #0xf0 | |
410 | SL a | |
411 | MOV mod3lo, a | |
412 | MOV a, i1 | |
413 | SR a | |
414 | SR a | |
415 | SR a | |
416 | OR mod3lo, a | |
417 | CALL mod3 | |
418 | ADD a, n | |
419 | CALL g | |
420 | SR a | |
421 | SR a | |
422 | MOV tmp_1, a ; a saved in tmp_1; fresh a | |
423 | MOV a, i2 | |
424 | ; shift-divide by six | |
bdcf57fd | 425 | ; note: i2 is max 0x78; so a will <= 20. (breaks for values >=128) |
ec2152ea TG |
426 | SR a |
427 | ADD a, i2 | |
428 | SR a | |
429 | SR a | |
430 | ADD a, i2 | |
431 | SR a | |
432 | SR a | |
433 | ADD a, i2 | |
434 | SR a | |
435 | SR a | |
436 | SR a | |
437 | ; end divide by six | |
438 | AND a, tmp_1 ; a restored from tmp_1 | |
439 | AND a, #3 | |
440 | ADD pwm, a | |
441 | ||
6b911797 | 442 | voice4: |
ec2152ea TG |
443 | MOV a, i2 |
444 | MOV mod3hi, a | |
445 | SR mod3hi | |
446 | SR mod3hi | |
447 | SWAP a | |
448 | AND a, #0xf0 | |
449 | SL a | |
450 | SL a | |
451 | MOV mod3lo, a | |
452 | MOV a, i1 | |
453 | SR a | |
454 | SR a | |
455 | OR mod3lo, a | |
456 | CALL mod3 | |
457 | SUB a, n | |
458 | SUB a, #8 | |
459 | NEG a | |
460 | CALL g | |
461 | SR a | |
462 | MOV tmp_1, a ; a saved in tmp_1; fresh a | |
463 | MOV a, i2 | |
464 | ; shift-divide by ten | |
465 | ; note: i2 is max 0x78; so a will <= 12. | |
466 | INC i2 | |
467 | SR a | |
468 | ADD a, i2 | |
469 | SR a | |
470 | SR a | |
471 | SR a | |
472 | ADD a, i2 | |
473 | SR a | |
474 | ADD a, i2 | |
475 | SWAP a | |
476 | DEC i2 | |
477 | ; end divide by ten | |
478 | AND a, tmp_1 ; a restored from tmp_1 | |
479 | AND a, #3 | |
29ec6906 | 480 | ADD a, pwm |
ec2152ea | 481 | |
6b911797 | 482 | |
ec2152ea | 483 | SWAP a |
a43c9c44 | 484 | MOV pwm, a |
ec2152ea TG |
485 | ; next sample is now ready. |
486 | ||
487 | INC i0 | |
488 | ADDC i1 | |
489 | ADDC i2 | |
490 | ||
f60af06d | 491 | SET0 pa, #6 ; debug |
a43c9c44 TG |
492 | ivr_end: |
493 | POP af | |
ec2152ea | 494 | RETI |