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