]> git.gir.st - Chiptunes-pms150c.git/blob - bsv.asm
use sdcc's ucsim to simulate the program
[Chiptunes-pms150c.git] / bsv.asm
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
6 .area OSEG (ABS,OVR,DATA)
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
12 pwm: .ds 1 ; 0x14
13 tmp_1: .ds 1 ; 0x15
14 .even ; make next two bytes word-aligned
15 tmp_lo: .ds 1 ; 0x16
16 tmp_hi: .ds 1 ; 0x17
17 simif: .ds 1 ;0x18
18
19 .even ; SP must be aligned
20 stack_start: .ds 1
21 .area SSEG
22 stack: .ds 1
23
24 ; aliases for memory locations:
25 notes_ix = tmp_lo
26 t = tmp_1
27 mul2 = tmp_hi
28 mul1 = tmp_lo
29 mod3hi = tmp_hi
30 mod3lo = tmp_lo
31
32 ; io addresses
33 clkmd = 0x03
34 inten = 0x04
35 intrq = 0x05
36 tm2c = 0x1C
37 tm2b = 0x09
38 tm2s = 0x17
39 t16m = 0x06
40 eoscr = 0x0A
41 padier = 0x0D
42 pa = 0x10
43 pac = 0x11
44 paph = 0x12
45 misc = 0x1B
46 gpcc = 0x1A
47 ihrcr = 0x0B
48
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.
54 do_calib = 1
55 calib_freq = 4096000 ; Hz
56 calib_vdd = 3000 ; mV
57
58 ; Clock Parameters:
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
64
65 ; cycle count (worst-case)
66 ; mod3: 28
67 ; g: 81
68 ; sample: 115 + 4*g + 2*mod3 = 495
69 ; isr overhead: 12
70 ; TOTAL: sample + overhead = 507
71
72 ; portA.4: audio out
73 ; portA.6: debug pin
74
75
76 .area CSEG (CODE,ABS)
77 .org 0x0000
78 GOTO init
79
80 .org 0x0020
81 GOTO interrupt
82
83 .org 0x0040 ; leave some space after IVT to allow 'overprogramming'
84 mod3:
85 MOV a, mod3hi
86 ADD mod3lo, a ; mod3lo = hi+lo
87 MOV a, #0
88 ADDC a ; mod3hi, 1bit
89 SWAP a
90 MOV mod3hi, a
91
92 MOV a, mod3lo
93 SWAP a
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)
98 ADD a, mod3hi
99 MOV mod3lo, a
100
101 AND a, #0x3 ; a = (mod3lo & 0x3)
102 SR mod3lo
103 SR mod3lo ; (mod3lo >> 2)
104 ADD a, mod3lo
105 MOV mod3lo, a
106
107 AND a, #0x3 ; a = (mod3lo & 0x3)
108 SR mod3lo
109 SR mod3lo ; (mod3lo >> 2)
110 ADD a, mod3lo
111
112 SUB a, #3
113 T0SN f, c
114 ADD a, #3
115 RET
116
117 g:
118 CLEAR mul2 ; this is notes_ix_hi (and should be 0)
119 AND a, #0x7
120 MOV notes_ix, a ; assumes notes are the first thing in SRAM
121 ; test i2 & 3:
122 MOV a, i2
123 AND a, #3
124 T0SN f, z
125 GOTO skip_plus8
126 MOV a, #8
127 ADD notes_ix, a
128 skip_plus8:
129 IDXM a, notes_ix
130
131 MOV t, a
132 CLEAR mul1 ; alias of notes_ix, so clear after idxm
133 ; note: LSB of result (mul0) is not needed for our purposes
134 ;;1/8:
135 SR t
136 T1SN f, c
137 GOTO skip1
138 MOV a, i0
139 ADD mul1, a
140 MOV a, i1
141 ADDC mul2, a
142 skip1: SR mul2
143 SRC mul1
144 ;;2/8:
145 SR t
146 skip2: SR mul2
147 SRC mul1
148 ;;3/8:
149 SR t
150 T1SN f, c
151 GOTO skip3
152 MOV a, i0
153 ADD mul1, a
154 MOV a, i1
155 ADDC mul2, a
156 skip3: SR mul2
157 SRC mul1
158 ;;4/8:
159 SR t
160 T1SN f, c
161 GOTO skip4
162 MOV a, i0
163 ADD mul1, a
164 MOV a, i1
165 ADDC mul2, a
166 skip4: SR mul2
167 SRC mul1
168 ;;5/8:
169 SR t
170 T1SN f, c
171 GOTO skip5
172 MOV a, i0
173 ADD mul1, a
174 MOV a, i1
175 ADDC mul2, a
176 skip5: SR mul2
177 SRC mul1
178 ;;6/8:
179 SR t
180 T1SN f, c
181 GOTO skip6
182 MOV a, i0
183 ADD mul1, a
184 skip6:
185 SR mul1
186 ;;7/8:
187 SR t
188 T1SN f, c
189 GOTO skip7
190 MOV a, i0
191 ADD mul1, a
192 skip7:
193 SR mul1
194 ;;8/8:
195 SR t
196 T1SN f, c
197 GOTO skip8
198 MOV a, i0
199 ADD mul1, a
200 skip8:
201 SR mul1
202
203 MOV a, mul1
204 RET
205
206 init:
207 ; clock setup:
208 SET1 clkmd, #4 ; enable IHRC
209 MOV a, #active_clock
210 MOV clkmd, a ; switch to IHRC
211
212 ; calibration placeholder:
213 .if do_calib
214 AND a, #'R'
215 AND a, #'C'
216 AND a, #1 ; IHRC
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 )
223 AND a, #ihrcr
224 .else
225 .word 0xffff, 0xffff, 0xffff, 0xffff, 0xffff
226 .word 0xffff, 0xffff, 0xffff, 0xffff, 0xffff
227 .endif
228
229 ;stack setup:
230 MOV a, #stack_start
231 MOV sp, a
232
233 ; portA setup:
234 MOV a, #0x50 ; data direction: PWM & debug output, rest input
235 MOV pac, a ; (conserves power, apparently)
236 MOV a, #(( 1<<4 ))
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.
242 MOV a, #0
243 MOV pa, a ; PortA data = 0
244
245 ; timer2/pwm setup:
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.
251 MOV pwm, a ; clear
252 MOV tm2b, a ; clear
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)
257
258 ;timer16/ivr setup
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
261 MOV t16m, a
262 MOV a, #(1<<2) ; enable timer16 int, disable all others
263 MOV inten, a
264
265 ; misc setup:
266 SET1 eoscr, #0 ; disable bandgap and lvr
267 SET0 gpcc, #7 ; disable comparator
268
269 ; memory setup:
270 CLEAR i0
271 CLEAR i1
272 CLEAR i2
273
274 ;rom is not mmapped; must load notes into ram first
275 MOV a, #0x84
276 MOV notes+0x0, a
277 MOV notes+0x5, a
278 MOV a, #0x9d
279 MOV notes+0x1, a
280 MOV notes+0x4, a
281 MOV a, #0xb0
282 MOV notes+0x2, a
283 MOV notes+0xA, a
284 MOV a, #0x69
285 MOV notes+0x3, a
286 MOV notes+0x6, a
287 MOV notes+0xB, a
288 MOV notes+0xE, a
289 MOV a, #0x58
290 MOV notes+0x7, a
291 MOV notes+0xF, a
292 MOV a, #0x75
293 MOV notes+0x8, a
294 MOV notes+0xD, a
295 MOV a, #0x8c
296 MOV notes+0x9, a
297 MOV notes+0xC, a
298
299 ; ENGINT
300
301 loop:
302 call interrupt
303 MOV a, i2
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
311 ; anyways).
312 GOTO loop
313
314 ; at this point, i2==0x78, i.e. the music is finished.
315 ; => goto halt (fallthrough)
316 halt:
317 DISGINT
318 CLEAR i2 ; clear halting signal
319
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.
326 SET0 tm2c, #5
327 SET0 t16m, #7
328
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)
331
332 ;switch to ilrc clock
333 MOV a, #sleep_clock
334 MOV clkmd, a
335 SET0 clkmd, #4 ; disable ihrc
336
337 STOPSYS
338 ; (at this point, we wait for an i/o-toggle wake up event to resume execution)
339
340 MOV a, #active_clock
341 MOV clkmd, a ; switch to IHRC again
342
343 SET1 pac, #4 ; restore output mode for audio pin
344
345 ;reenable timer16, timer2
346 SET1 tm2c, #5
347 SET1 t16m, #7
348
349 ENGINT
350 GOTO loop
351
352 interrupt:
353 PUSH af
354 T1SN intrq, #2 ; if intrq.t16 is triggered, skip next
355 nop; GOTO ivr_end
356
357 ;clear t16int:
358 SET0 intrq, #2
359
360 SET1 pa, #6 ; debug
361
362 ; send pwm data to timer2:
363 MOV a, pwm
364 MOV tm2b, a
365
366 ; generate new sample:
367 voice1:
368 MOV a, i2; "mov mem,mem"
369 MOV n, a; does not exist
370 SL n
371 SL n
372 MOV a, i1
373 SWAP a
374 AND a, #0xf
375 SR a
376 SR a
377 OR n, a
378
379 MOV a, n
380 CALL g
381 SWAP a
382 AND a, #0x1
383 MOV pwm, a
384
385 voice2:
386 MOV a, i2
387 SL a
388 SL a
389 SL a
390 MOV tmp_1, a ; fresh tmp_1:
391 MOV a, i1
392 SWAP a
393 AND a, #0xf
394 SR a
395 OR a, tmp_1 ; tmp_1 done.
396 XOR a, n
397 CALL g
398 SR a
399 AND a, i2
400 SR a
401 AND a, #3
402 ADD pwm, a
403
404 voice3:
405 MOV a, i2
406 MOV mod3hi, a
407 SR mod3hi
408 SR mod3hi
409 SR mod3hi
410 SWAP a
411 AND a, #0xf0
412 SL a
413 MOV mod3lo, a
414 MOV a, i1
415 SR a
416 SR a
417 SR a
418 OR mod3lo, a
419 CALL mod3
420 ADD a, n
421 CALL g
422 SR a
423 SR a
424 MOV tmp_1, a ; a saved in tmp_1; fresh a
425 MOV a, i2
426 ; shift-divide by six
427 ; note: i2 is max 0x78; so a will <= 20. (breaks for values >=128)
428 SR a
429 ADD a, i2
430 SR a
431 SR a
432 ADD a, i2
433 SR a
434 SR a
435 ADD a, i2
436 SR a
437 SR a
438 SR a
439 ; end divide by six
440 AND a, tmp_1 ; a restored from tmp_1
441 AND a, #3
442 ADD pwm, a
443
444 voice4:
445 MOV a, i2
446 MOV mod3hi, a
447 SR mod3hi
448 SR mod3hi
449 SWAP a
450 AND a, #0xf0
451 SL a
452 SL a
453 MOV mod3lo, a
454 MOV a, i1
455 SR a
456 SR a
457 OR mod3lo, a
458 CALL mod3
459 SUB a, n
460 SUB a, #8
461 NEG a
462 CALL g
463 SR a
464 MOV tmp_1, a ; a saved in tmp_1; fresh a
465 MOV a, i2
466 ; shift-divide by ten
467 ; note: i2 is max 0x78; so a will <= 12.
468 INC i2
469 SR a
470 ADD a, i2
471 SR a
472 SR a
473 SR a
474 ADD a, i2
475 SR a
476 ADD a, i2
477 SWAP a
478 DEC i2
479 ; end divide by ten
480 AND a, tmp_1 ; a restored from tmp_1
481 AND a, #3
482 ADD a, pwm
483
484
485 SWAP a
486 MOV pwm, a
487 ; next sample is now ready.
488 mov a, #'w'
489 mov simif, a
490 mov a, pwm
491 mov simif, a
492
493
494 INC i0
495 ADDC i1
496 ADDC i2
497
498 SET0 pa, #6 ; debug
499 ivr_end:
500 POP af
501 ret
502 RETI
Imprint / Impressum