first ideas regarding automatic power-off after 1 loop of playback
[Chiptunes.git] / foo.S
1 /* REGISTER NAMES */
2 #define acc r16
3 #define i0 r17
4 #define i1 r18
5 #define i2 r19
6 #define i3 r20
7 #define n r21
8 #define s r22
9 #define t r23 //==Ml
10 #define x r24 //==a1==Mh
11 #define _ r25 //==a2
12 #define Xlo r26
13 #define Xhi r27
14 #define one r28
15 ; r29
16 ; r30 Zlo
17 ; r31 Zhi
18 ; aliases:
19 #define Ml t //mod3 vars
20 #define Mh x // -"-
21 #define a1 x //mul_ vars
22 #define a2 _ // -"-
23
24 /* I/O REGISTERS */
25 OCR0AL = 0x26
26 DDRB = 0x01
27 PORTB = 0x02
28 PUEB = 0x03
29 SPL = 0x3D
30 SPH = 0x3E
31 CCP = 0x3C
32 CLKPSR = 0x36
33 OSCCAL = 0x39
34 WDTCSR = 0x31
35 SMCR = 0x3A
36 TCCR0A = 0x2E
37 TCCR0B = 0x2D
38 TIMSK0 = 0x2B
39 TIFR0 = 0x2A
40 RAMEND = 0x5F
41 FLASHM = 0x4000
42
43 .section .text
44 .org 0x0000 ; RESET interrupt
45 CLR i0
46 .org 0x0002 ; INT0 external interrupt
47 CLR i1
48 .org 0x0004 ; PCINT0 pin change interrupt
49 CLR i2
50 RJMP main
51 .org 0x0008 ; TIM0_OVF interrupt
52 RJMP sample
53
54 notes:
55 .byte 0x84, 0x9d, 0xb0, 0x69, 0x9d, 0x84, 0x69, 0x58
56 .byte 0x75, 0x8c, 0xb0, 0x69, 0x8c, 0x75, 0x69, 0x58
57
58 mod3: ; mod3(Mh.Ml) -> t
59 #define tmp _
60 ADD Ml, Mh
61 CLR Mh
62 ADC Mh, Mh ; store carry in Mh
63 MOV tmp, Ml
64 SWAP tmp
65 ANDI tmp, 0x0f
66 SWAP Mh
67 OR tmp, Mh
68 ANDI Ml, 0x0f
69 ADD Ml, tmp
70 MOV tmp, Ml
71 LSR tmp
72 LSR tmp
73 ANDI Ml, 0x03
74 ADD Ml, tmp
75 MOV tmp, Ml
76 LSR tmp
77 LSR tmp
78 ANDI Ml, 0x03
79 ADD Ml, tmp
80 CPI Ml, 3
81 BRCS skip
82 SUBI Ml, 3
83 skip:
84 RET
85 #undef tmp
86
87 g: ; g(i, t) -> t
88 CLR a1
89
90 #define tmp _
91 #define zero a1
92 ANDI t, 0x07
93 MOV tmp, i2
94 ANDI tmp, 3
95 CPSE tmp, zero
96 SUBI t, -8
97 #undef zero
98 #undef tmp
99
100 LDI Xlo, lo8(notes)
101 ADD Xlo, t ; NOTE: can't overflow, since RAMEND == 0x5F
102 LD t, X
103
104 CLR a2
105
106 ; begin of mulitiplication:
107 LSR t
108 BRCC skip1
109 ADD a1, i0
110 ADC a2, i1
111 skip1:
112 LSR a2
113 ROR a1
114 LSR t
115 ; BRCC skip2 -- this bit is always zero
116 ; ADD a1, i0
117 ; ADC a2, i1
118 ;skip2:
119 LSR a2
120 ROR a1
121 LSR t
122 BRCC skip3
123 ADD a1, i0
124 ADC a2, i1
125 skip3:
126 LSR a2
127 ROR a1
128 LSR t
129 BRCC skip4
130 ADD a1, i0
131 ADC a2, i1
132 skip4:
133 LSR a2
134 ROR a1
135 LSR t
136 BRCC skip5
137 ADD a1, i0
138 ADC a2, i1
139 skip5:
140 LSR a2
141 ROR a1
142 LSR t
143 BRCC skip6 ;sbrc t, NNN
144 ADD a1, i0
145 skip6:
146 LSR a1
147 LSR t
148 BRCC skip7
149 ADD a1, i0
150 skip7:
151 LSR a1
152 LSR t
153 BRCC skip8
154 ADD a1, i0
155 skip8:
156 LSR a1
157
158 MOV t, a1 ;;TODO: use a1 in loop: directly
159 RET
160
161 main: ; setup routine
162 ; NOTE: clr i0..i2 moved to .ord 0x0
163 CLR i3
164 CLR acc ; we output a dummy sample before the actual first one
165 LDI Xhi, hi8(FLASHM + notes) ; never changes
166 LDI one, 1 ; mostly for clearing TIM0_OVF bit
167
168 #define zero i0
169 LDI x, RAMEND
170 OUT SPL, x ; init stack ptr
171 OUT SPH, zero ; -"-
172 OUT PUEB, zero ; disable pullups
173 LDI x, 0x05
174 OUT DDRB, x ; PORTB0:pwm, PORTB2:debug
175 LDI x, 0xd8
176 OUT CCP, x ; change protected ioregs
177 OUT CLKPSR, one ; clock prescaler 1/2 (4Mhz)
178 LDI x, 0xa7 ; determined by trial-and-error (->PORTB2)
179 OUT OSCCAL, x ; set oscillator calibration
180 OUT WDTCSR, zero; turn off watchdog
181
182 ;set timer/counter0 to 8bit fastpwm, non-inverting, no prescaler
183 LDI x, 0x81
184 OUT TCCR0A, x
185 LDI x, 0x09
186 OUT TCCR0B, x
187 OUT TIMSK0, one ; enable tim0_ovf
188 SEI
189 #undef zero
190
191 loop:
192 SLEEP ; wait for interrupt
193 RJMP loop
194
195 sample:
196 OUT OCR0AL, acc ; start by outputting a sample, because routine has variable runtime
197 #ifdef DEBUG
198 SBI PORTB, 2 ; to measure runtime
199 #endif // DEBUG
200
201 MOV n, i2
202 LSL n
203 LSL n
204 #define tmp _
205 MOV tmp, i1
206 SWAP tmp
207 ANDI tmp, 0x0f
208 LSR tmp
209 LSR tmp
210 OR n, tmp
211 #undef tmp
212 MOV s, i3
213 LSR s
214 ROR s
215 ANDI s, 0x80
216 #define tmp _
217 MOV tmp, i2
218 LSR tmp
219 OR s, tmp
220 #undef tmp
221
222 ; voice 1:
223 MOV t, n
224 RCALL g
225 SWAP t
226 ANDI t, 1
227 MOV acc, t
228
229 ; voice 2:
230 #define tmp _
231 MOV tmp, i2
232 LSL tmp
233 LSL tmp
234 LSL tmp
235 MOV t, i1
236 SWAP t
237 ANDI t, 0xf
238 LSR t
239 OR t, tmp
240 #undef tmp
241 EOR t, n
242 RCALL g
243 LSR t
244 LSR t
245 ANDI t, 3
246 AND t, s
247 ADD acc, t
248
249 ; voice 3:
250 MOV Ml, i2
251 SWAP Ml
252 ANDI Ml, 0xf0
253 LSL Ml
254 #define tmp _
255 MOV tmp, i1
256 LSR tmp
257 LSR tmp
258 LSR tmp
259 OR Ml, tmp
260 #undef tmp
261 MOV Mh, i3
262 SWAP Mh
263 ANDI Mh, 0xf0
264 LSL Mh
265 #define tmp _
266 MOV tmp, i2
267 LSR tmp
268 LSR tmp
269 LSR tmp
270 OR Mh, tmp
271 #undef tmp
272 RCALL mod3
273 ADD t, n
274 RCALL g
275 LSR t
276 LSR t
277 ANDI t, 3
278 MOV x, s
279 INC x
280 #define tmp _
281 MOV tmp, x
282 LSR tmp
283 LSR tmp
284 ADD tmp, x
285 ROR tmp
286 LSR tmp
287 ADD tmp, x
288 ROR tmp
289 LSR tmp
290 ADD tmp, x
291 ROR tmp
292 LSR tmp
293 AND t, tmp
294 #undef tmp
295 ADD acc, t
296
297 ; voice 4:
298 MOV Ml, i2
299 SWAP Ml
300 ANDI Ml, 0xf0
301 LSL Ml
302 LSL Ml
303 #define tmp _
304 MOV tmp, i1
305 LSR tmp
306 LSR tmp
307 OR Ml, tmp
308 #undef tmp
309 MOV Mh, i3
310 SWAP Mh
311 ANDI Mh, 0xf0
312 LSL Mh
313 LSL Mh
314 #define tmp _
315 MOV tmp, i2
316 LSR tmp
317 LSR tmp
318 OR Mh, tmp
319 #undef tmp
320 RCALL mod3
321 SUB t, n
322 NEG t
323 SUBI t, -8
324 RCALL g
325 LSR t
326 ANDI t, 3
327 INC s
328 #define tmp _
329 MOV tmp, s
330 LSR tmp
331 ADD tmp, s
332 ROR tmp
333 LSR tmp
334 LSR tmp
335 ADD tmp, s
336 ROR tmp
337 ADD tmp, s
338 ROR tmp
339 LSR tmp
340 LSR tmp
341 AND t, tmp
342 #undef tmp
343 ADD acc, t
344
345 SWAP acc ; acc<<4, to be passed to OCR0AL
346
347 SUBI i0, -1
348 SBCI i1, -1
349 SBCI i2, -1
350 SBCI i3, -1
351
352 cpi i2, 0x78 ; 16m23 -- one loop
353 breq halt
354
355 #ifdef DEBUG
356 CBI PORTB, 2 ; end runtime measurement
357 #endif // DEBUG
358 OUT TIFR0, one ; clear pending interrupt (routine takes two intr.cycles)
359 RETI ; reenables interrupts
360
361 ; POWER SAVING MODE
362 ;
363 ; To reduce physical size, we won't use a latching power switch. Instead,
364 ; playback will stop after a full iteration (16m23s). Then, a momentary
365 ; button will trigger a RESET, INT0 or PCINT0 interrupt to resume
366 ; playback. Meanwhile, all periphials will be shut down and the clock
367 ; turned off. The watchdog is permanently off to reduce power consumption.
368 ; Note: it is not necessary to switch the clock source from the 8MHz
369 ; oscillator to the 128kHz one, since power-down mode disables the clock
370 ; completely.
371
372 ; TODO: if we are using pcint on the audio-out pin, we could instruct the user to short the audio plug to ground to start playback. this would eliminate the button (and associated wiring) completely.
373
374 halt:
375 ;set all pins as input & enable pullups and disable periphials to conserve energy
376 ldi x, 0x00
377 out DDRB, x
378 ldi x, 0x0f
379 out PUEB, x
380 PRR = 0x35
381 ldi x, 0x3 ; bit0: disable Timer0, bit1: disable attiny5/10's ADC
382 out PRR, x
383
384 #if 0
385 ;enable (PC)INT0
386 EIMSK = 0x13
387 ; EICRA(0x15)==00: generate INT0 on low level (default, can skip).
388 sbi EIMSK, 0 ; enable INT0
389 ; TODO: the ISR should clear the interrupt raised flag, disable itself, reset DDRB/PUEB/PRR and RETI.
390
391 ;EIFR(0x14): set to 1 to clear int0 flag
392
393 /* alternatively, using PCINT:
394 PCICR(0x12): set to 1 to enable pcint0
395 PCIFR(0x11): set to 1 to clear pcint0 flag
396 PCMSK(0x10): PCINT0 mask (lsb: portb0, msb: portb3)
397 */
398 #endif
399
400 ;enter power-down-mode
401 ldi x, 0x05 ; power-down mode:____010_ ; enable:_______1
402 out SMCR, x
403 SLEEP
404 ; NOTE: at this point, only INT0, PCINT0 and RESET can wake up the MCU.
405
406 #if 0
407 ;disable power-down-mode
408 clr x; xxx: should do before sleep-ing
409 out SMCR, x
410 #endif
Imprint / Impressum