Path: Home ==> AVR-EN ==> Micro beginner ==> 9. Audio generator     Diese Seite in Deutsch (extern):

# Lecture 9: An audio generator with ADC, tone table, multiplication

The OC0A output is used here as a variable audio generator with adjusting the tone's frequency with a potentiometer. Further more 8 bit numbers are multiplied and music is played here.

## 9.1 Introduction to audio generation

Generating tones is essentially the same as blinking a LED, with frequencies between 30 cs/s and 20 kcs/s (for bats: up to 40 kcs/s). So we learn here not much on timers, only how to attach a speaker to a port pin instead of a LED.

## 9.2 Hardware, components and mounting

### 9.2.1 The hardware scheme

To sound tones a speaker is needed. This is attached to OC0A. A capacitor of 47 µF decouples the DC from the port pin and transfers only the AC component.

A key and the potentiometer are attached like in the previous experiments.

### 9.2.2 The components

#### The speaker

This is the speaker. He has an impedance of 45 Ω to yield a strong audio signal. The two pins are soldered to a short cable and short pins that fit into the breadboard. The polarity of the speaker is irrelevant for our application, it has only accustic consequences if two more of those are operated.

#### The electrolytical capacitor

This is an electrolytical capacitor. This is a component for which correct polarity is essential. The minus pole is marked, the longer of the two wires is plus.

### 9.2.3 Mounting

The speaker is tied to pin 5, via the electrolyt.

With that we can start sound-generation.

Home Top Tone generation Hardware Tone control Tables Multiplication Gamut Music

## 9.3 Regulating the tone frequency

### 9.3.1 Simple task 1

Task 1 is to output audio tones via the speaker and to regulate their frequency with the potentiometer. The tones shall range between 300 cs/s and 75 kcs/s. The tone shall only be audible if the key is pressed.

### 9.3.2 Solution

#### Frequency ranges

It is already clear that the CTC mode of the timer has to be used here. The OC0A output pin has to toggle (from low to high and back). As each swing of the rectangle requires two CTC periods, the resulting frequency is half that of the CTC frequency. The frequency depends from the prescaler and the compare value. Ranges cover the following frequencies:
ClockPre-
scaler
OCR0A
=0
OCR0A
=255
9.6 Mcs/s14.8 Mcs/s18.75 kcs/s
8600 kcs/s2.34 kcs/s
6475 kcs/s292.5 Hz
25618.75 kcs/s73.1 cs/s
10244.69 kcs/s18.3 cs/s
1.2 Mcs/s1600 kcs/s2.34 kcs/s
875 kcs/s292.5 cs/s
649.38 kcs/s36.6 cs/s
2562.35 kcs/s9.15 cs/s
1024586 cs/s2.29 cs/s
At 1.2 Mcs/s clock, the audible range is well covered with a prescaler of 8.

#### AD values and OCR0A values

The higher the measured voltage from the potentiometer the higher the tone frequency should be. The OCR0A value behaves opposite: the larger the lower is the frequency. So either the potentiometer has to be reverted or a reversion of the measured value has to take place. A software solution for this would be to subtract the measured 8 bit value from hex 0xFF. That would go like this:
``````
ldi Register1,0xFF
sub Register1,Register2 ; Register2 = measured value
mov Register2,Register1
```
```
Inverting all bits in a register is a task that the controller's Central Processing Unit can do with a special instruction, see the source code, so that we do not need to take this path.

### 9.3.3 Program

This is the program, the source code here.
``````
;
; ***********************************************
; * Audio generator with key and tone regulator *
; * (C)2017 by http://www.avr-asm-tutorial.net  *
; ***********************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; --------- Registers ------------------------
; free: R0 .. R14
.def rSreg = R15 ; Save/restore status register
.def rmp = R16 ; Multi purpose register
.def rimp = R17 ; Multi purpose inside interrupts
; free: R18 .. R31
;
; --------- Ports ----------------------------
.equ pOut = PORTB ; Output port
.equ pDir = DDRB ; Direction port
.equ pInp = PINB ; Input port
.equ bLspD = DDB0 ; Speaker output direction pin
.equ bTasO = PORTB3 ; Pull up key output pin
.equ bTasI = PINB3 ; Key input pin
.equ bAdID = ADC2D ; ADC input disable
;
; --------- Timing ---------------------------
; Clock              = 1200000 cs/s
; Prescaler          = 8
; CTC TOP range      = 0 .. 255
; CTC divider range  = 1 .. 256
; Toggle divider     = 2
; Frequency range    = 75 kcs/s .. 293 cs/s
;
; --------- Reset- und Interrupt vectors -----
.CSEG ; Assemble to Code-Segment
.ORG 0 ; Start at address zero
rjmp Start ; Reset Vector, jump to init
reti ; INT0-Int, inactive
rjmp PcIntIsr ; PCINT-Int, active
reti ; TIM0_OVF, inactive
reti ; EE_RDY-Int, inactive
reti ; ANA_COMP-Int, inactive
reti ; TIM0_COMPA-Int, inactive
reti ; TIM0_COMPB-Int, inactive
reti ; WDT-Int, inactive
rjmp AdcIsr ; ADC-Int, active
;
; ---------- Interrupt Service Routines -----
PcIntIsr: ; PCINT-Interrupt key
sbic pInp,bTasI ; Skip if key input = 0
rjmp PcIntIsrOff ; Key is not pressed
ldi rimp,(1<<COM0A0)|(1<<WGM01) ; Toggle output, CTC-A
out TCCR0A,rimp ; to timer control port A
rjmp PcIntIsrRet ; return
PcIntIsrOff:
ldi rimp,(1<<COM0A1)|(1<<WGM01) ; Clear output, CTC-A
out TCCR0A,rimp ; to timer control port A
PcIntIsrRet:
reti ; return from interrupt, set I flag
;
AdcIsr: ; ADC interrupt
in rSreg,SREG ; save SREG
in rimp,ADCH ; read MSB result
com rimp ; invert value
out OCR0A,rimp ; to CTC TOP port
; Restart ADC, int enable
ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
out ADCSRA,rimp ; to ADC control port A
out SREG,rSreg ; restore SREG
reti ; return from interrupt, set I flag
;
; ---------- Program start and init ----------
Start:
; Stack init
ldi rmp,LOW(RAMEND) ; Set to SRAM end
out SPL,rmp ; to stack pointer
; In- and output ports
ldi rmp,1<<bLspD ; Speaker direction output
out pDir,rmp ; to direction port
ldi rmp,1<<bTasO ; Pull up on key port pin
out pOut,rmp ; to output port
; Configure timer as CTC
ldi rmp,(1<<COM0A1)|(1<<WGM01) ; Clear output, CTC-A
out TCCR0A,rmp ; to control port A
ldi rmp,1<<CS01 ; Prescaler = 8, start timer
out TCCR0B,rmp ; to timer control port B
; Configure and start AD conversion
ldi rmp,(1<<ADLAR)|(1<<MUX1) ; Left adjust, ADC2
out ADMUX,rmp ; to ADC MUX port
ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
out ADCSRA,rmp ; to ADC control port A, start
; Configure PCINT for key input
ldi rmp,1<<PCINT3 ; Enable PB3 interrupt
out PCMSK,rmp ; in PCINT mask port
ldi rmp,1<<PCIE ; Enable PCINT
out GIMSK,rmp ; in interrupt mask port
; Enable sleep mode
ldi rmp,1<<SE ; Sleep mode idle
out MCUCR,rmp ; to MCU control port
; Enable interrupts
sei
; ------------ Main program loop --------
Loop:
sleep ; go to sleep
nop ; Wake up dummy
rjmp Loop ; go to sleep again
;
; End of source code
;
```
```
The following instruction is new:
• COM Register: inverts all bits in the register. From 0x00 to 0xFF and from 0xFF to 0x00. Subtract the register content from 0xFF, so called one's complement.
With the machine we can generate morse code with a regulated tone.

Home Top Tone generation Hardware Tone control Tables Multiplication Gamut Music

### 9.3.4 Simulation of the program

Simulation goes with avr_sim as follows.

The I/O port PB0 is set to be output, the output pin is low.

The key input portbit PB3 is set to switch on the pull-up resistor. Closing the key pushes the input to low.

On PB3 the PCINT enable mask is active and the PCINT enable bit is set.

The timer TC0 is set to CTC mode, with clearing the counter after the compare match in A has been reached. The output PB0 is cleared on compare match, leaving PB0 at low voltage. The prescaler is at eight, dividing 1,200,000 Hz to 150,000 Hz counting frequency.

The ADC works with the clock divided by 128: 1,200,000 Hz means 9,375 Hz or, for 13 ADC cycles, requires 1.386 ms per conversion. The reference voltage is 5 V. The voltage value of 0.675 V shall lead to an ADC value of 138. The left adjust should yield a MSB of 34 (0x22). The first conversion has been started (the progress status bar is visible).

The first AD conversion has made some progress. As the conversion lasts 1.386 ms the controller remains sleeping.

An ADC complete interrupt has occurred and the ADC result is read to R17 using the instruction:

in rimp,ADCH ; read MSB result

Conversion to the complement of 0x22 using

com rimp ; invert value

yields 0xFF - 0x22 = 0xDD. In that way 38 inverts to 221.

With "out OCR0A,rimp ; to CTC TOP port" this is written to the timer's compare portregister. This does not lead to an audible change as far as the "PB0 clear" condition remains unchanged.

This changes only if a PCINT is executed. Initiated either by changing the input signal in PINB3 manually or by clicking on PCINT3 such a PCINT is initiated and, after four wait cycles and if no other interrupt request is pending, executed.

Within the interrupt service routine, the two instructions

ldi rimp,(1<<COM0A0)|(1<<WGM01) ; Toggle output, CTC-A
out TCCR0A,rimp ; to timer control port A

switch toggling of the PB0 portpin by the timer on.

After reaching the compare value in A and restarting at TCNT = 0 the respective portbit changes polarity ...

The pin PB0 now is high.

Since the last CTC 1.4775 ms have elapsed. The time for two such CTC cycles corresponds to an audio frequency of 338 Hz, something between e1 and f1.

Home Top Tone generation Hardware Tone control Tables Multiplication Gamut Music

## 9.4 Task 2: To play the tones of the gamut

### 9.4.1 Task

With this part of the lecture the gamut shall be played, the potentiometer shall select the tone to be played.

### 9.4.2 Introduction to tables

#### The gamut table

Unfortunately german/european/american taste allows only certain tones. Softly changing tone heights are out and not allowed here. We need a table of those allowed tones. Those are collected in the following table.

To teach those tones to the controller's timer, the CTC values and prescalers are associated to those tones. As the timer does not exactly fit the desired frequency, the resulting frequencies and the deviation from the gamut tone are also given.
ToneCs/sPrescCTCReal(cs/s)Delta(%)#
a4408170441.180.27%0
h4958152493.42-0.32%1
cis5508136551.470.27%2
d586.668128585.94-0.12%3
e6608114657.89-0.32%4
fis733.338102735.290.27%5
gis825891824.18-0.10%6
a'880885882.350.27%7
h'990876986.84-0.32%8
cis'11008681102.940.27%9
d'1173.328641171.88-0.12%10
e'13208571315.79-0.32%11
fis'1466.668511470.590.27%12
gis'16508451666.671.01%13
A17608431744.19-0.90%14
H19808381973.68-0.32%15
CIS22008342205.880.27%16
D2346.648322343.75-0.12%17
E264012272643.170.12%18
FIS2933.3212052926.82-0.22%19
GIS330011823296.70-0.10%20
A'352011703529.410.27%21
H'396011523947.36-0.32%22
CIS'440011364411.760.27%23
D'4693.2811284687.5-0.12%24
E'528011145263.15-0.32%25
FIS'5866.6411025882.350.27%26
GIS'66001916593.40-0.10%27
A''70401857058.820.27%28
The deviations are, all in all, relatively small. I am not able to realize a difference between 440 and 441 cs/s. So we can accept that, without having an extra xtal oscillator of 1.1968 Mcs/s (which is not available anyway).

To enable the timer to play those frequency we need a gamut table from which the controller can read the CTC and prescaler values. As the values from a to a', from a' to A, from A to A' and from A' to A'' always differ by a constant value of 2, we could calculate those from a base table. But that would be complicated because of the changes in the optimal prescaler value (at high frequencies above D = 1, at smaller ones = 8). So the table should as well hold the prescaler value together with the CTC value. The table length covers four octaves.

#### Tables and their placement

The table needs 2*29 = 58 bytes length. There are principally three locations in the controller where this table can be placed:
1. the SRAM storage. In an ATtiny13 64 bytes of SRAM are available. The SRAM would be rather full and conflicts with the stack are to be expected., which also uses SRAM.
2. the EEPROM storage. This provides also 64 bytes. That would fit, but would be rather full.
3. the Flash storage. This has 512 words or 1,024 bytes and would provide enough space, even for additional octaves.
##### Table in SRAM
In this first case there is no other opportunity than writing each value of the table, e.g. with the instruction STS Address,Register, to its place. The following would have to be programmed:
``````
ldi R16,8 ; Prescaler value
sts 0x60,R16 ; store at address 0x0060 in SRAM
ldi R16,170 ; CTC value
sts 0x60+1,R16 ; store at address 0x0061 in SRAM
[...]
ldi R16,1 ; Prescaler value
sts 0x60+56,R16 ; store at address 0x0098 in SRAM
ldi R16,85 ; CTC value
sts 0x61,R16 ; store at address 0x0099 in SRAM
```
```
Each value pait would require four instructions, of which two (STS) are double-word instructions (six instruction words per pair). Even if we simplify those instructions a little bit by using the instruction ST Z+,Register, this would be a lengthy affair. ST Z+ goes as follows:
``````
ldi ZH,HIGH(0x0060) ; Pointer Z to SRAM start address, MSB
ldi ZL,LOW(0x0060) ; dto., LSB
ldi R16,8 ; Prescaler value
st Z+,R16 ; store R16 in SRAM und increase address in Z
ldi R16,170 ; CTC value
st Z+,R16 ; to next address
[...]
ldi R16,1 ; Prescaler value
st Z+,R16 ; store in SRAM and increase address in Z
ldi R16,85 ; CTC value
st Z,R16 ; store R16 to last address in SRAM
```
```
With the last value to be written the storing with automatic address increment is changed to ST Z,Register, without increasing Z.

The opposite instruction, to decrease the address in Z, is also available, but works a bit different. ST -Z,Register the address is first decreased and then the register content is written to SRAM to the already decreased address. Norwegian language constructors know how to confuse beginners.

Besides the lengthy procedure to code the assembler source, the SRAM is not a conventient place to locate a lengthy table.
##### Table in EEPROM
The second location to place the table is the EEPROM. Which offers slightly better conditions for that. Here the construction would be:
``````
.ESEG
.ORG 0
.db 8,170
[...]
.db 1,85
.CSEG
``````
``` ``` The assembler directive ".ESEG" places the resulting code not into the storage flash memory but into an extra hex file named ".EEP", which can be written to the EEPROM directly, byte by byte. At the end of the EEPROM content the directive ".CSEG" switches back into the code segment, so that further instructions can be programmed to the flash memory.

The ".ORG 0" directice defines the address place to which the table is written in the EEOROM. The 0 places the EEPROM table to the beginning.

The numerous ".DB" directives with the comma-separated bytes are placed to subsequent addresses in the EEPROM. .DB accepts bytes, words or text (ASCII characters in '', character by character).

How we access to the EEPROM's content we learn in a later lecture.

This is a more comfortable manner, but not as elegant like the third ooportunity.
##### Table in program flash memory
Now it is a little bit more complicated, because the program storage memory is organized wordwise, in 16 bit words. With
``````
Table:
.db 8,170
[...]
.db 1,85
```
```
the following will happen:
• The "8" will be converted to the LSB, the "170" as MSB and placed as a 16 bit word into the program memory. The current address at which this word is stored is given by the label "Table:".
• All subsequent ".DB" directives place similar words to the following addresses.
It is clear that per ".DB" ALWAYS whole words are written to program memory. If ".DB 1" is written to the source code, effectively 0x0001 is written there. The automatic addition of 0x00 as MSB, if the number of bytes placed with a single .DB directive is odd, will be signalled by a warning during the assembly process. In the .DB directive the first byte written is always the LSB.

The same warning of the assembler results, if we place text with a .DB directive into flash memory, e.g. with "ABC", and if the text has an odd number of characters. In that case a 0x00 character is added, if we do not formulate this different, e.g. as .DB "ABC ".

A second opportunity to construct the table in the flash is by definitely words to the table, e.g.
``````
Table:
.dw 8+170*256
[...]
.dw 1+85*256
```
```
This creates directly the words to be placed to the table, and we can select which part is the LSB and which part is the MSB.

Now we have to access this table to read values from it. To read the first byte, we can use the LPM instruction. Without parameters this instruction reads the byte on address Z in the flash memory to the register R0. Note that the address is in the bits 1 to 15 of Z while bit 0 specifies if the lower (0) or upper (1) byte on that address shall be read. We can formulate as follows:
``````
ldi ZH,HIGH(2*Table) ; MSB pointer to Z
ldi ZL,LOW(2*Table) ; dto., LSB
lpm ; Load from Program Memory
```
```
Note that the address "Table:" is multiplied by two, because each address location holds two bytes. "2*Table" accesses the LSB. If access to the the MSB is desired write "2*Table+1".

The LSB read now is stored to register R0. To read it to somewhere else we formulate
``````
ldi ZH,HIGH(2*Table) ; MSB pointer to Z
ldi ZL,LOW(2*Table) ; dto., LSB
lpm R16,Z ; Load from Program Memory to R16
```
```
To read both bytes one after the other, the LPM r,Z+ instruction has to be executed: the Z+ increments the content of Z automatically after reading the content at Z. Like this:
``````
ldi ZH,HIGH(2*Table) ; MSB pointer to table in Z
ldi ZL,LOW(2*Table) ; dto., LSB
lpm XL,Z+ ; Load LSB from Program Memory to register XL
lpm XH,Z+ ; Load MSB from Program Memory to register XH
```
```
This increases the address automatically. Backwards the norwegian programming style is again to decrease the address first and then to read the content, with LPM r,-Z. But beware: this instruction is not implemented in the ATtiny13. By the way, the registers XL and XH as well as YH and YL and ZH and ZL as well as the double register pairs X, Y and Z are defined in the "def.inc" file. If you do not include this in the assembler source header, you will get error messages from the assembler, unless you defined those e.g. with ".def ZH = R31". This has historic reasons because the first AVR devices (e.g. the ancient AT90S1200) had no pointer register pairs.

With that, the gamut table is constructable, like this:
``````
GamutTable:
.db 1<<CS01, 169 ; a #0
.db 1<<CS01, 151 ; h #1
.db 1<<CS01, 135 ; cis #2
.db 1<<CS01, 127 ; d #3
.db 1<<CS01, 113 ; e #4
.db 1<<CS01, 101 ; fis #5
.db 1<<CS01, 90 ; gis #6
.db 1<<CS01, 84 ; a' #7
.db 1<<CS01, 75 ; h' #8
.db 1<<CS01, 67 ; cis' #9
.db 1<<CS01, 63 ; d' #10
.db 1<<CS01, 56 ; e' #11
.db 1<<CS01, 50 ; fis' #12
.db 1<<CS01, 44 ; gis' #13
.db 1<<CS01, 42 ; A #14
.db 1<<CS01, 37 ; H #15
.db 1<<CS01, 33 ; CIS #16
.db 1<<CS01, 31 ; D #17
.db 1<<CS00, 226 ; E #18
.db 1<<CS00, 204 ; FIS #19
.db 1<<CS00, 181 ; GIS #20
.db 1<<CS00, 169 ; A' #21
.db 1<<CS00, 151 ; H' #22
.db 1<<CS00, 135 ; CIS' #23
.db 1<<CS00, 127 ; D' #24
.db 1<<CS00, 113 ; E' #25
.db 1<<CS00, 101 ; FIS' #26
.db 1<<CS00, 90 ; GIS' #27
.db 1<<CS00, 84 ; A'' #28
```
```
This table requires 29 words in the flash storage, which are 5.7% of the memory of the ATtiny13 and is an acceptable size.

If we want to read the tenth note from the table and write it to the timer, the code for that goes like this:
``````
ldi R16,10 ; tenth note
lsl R16 ; Note multiplied by 2 (2 bytes per note)
ldi ZH,HIGH(2*GamuTable)
ldi ZL,LOW(2*GamutTable)
add ZL,R16 ; add note to pointer
ldi R16,0 ; CLR would also clear the carry flag!
adc ZH,R16 ; add eventual carry
lpm R0,Z+ ; read prescaler value
out TCCR0B,R0 ; write to timer control port B
lpm R0,Z ; read CTC value
out OCR0A,R0 ; write to CTC compare value
``````
``` ``` With that, the storage, the reading and the use of the gamut table is resolved for our case here.

Home Top Tone generation Hardware Tone control Tables Multiplication Gamut Music

### 9.4.3 Introduction to multiplication

One problem solved, but another problem comes up immediately. The ADC provides measurement data between 0 and 1,023 (without ADLAR) or 0 and 255 (with ADLAR). But our gamut table has 29 entries only. We could resolve that by just limiting the values down to 28, and the problem is already solved. That would not be a really nice and intelligent solution, because those 28 different notes would be available within 2.8% of the potentiometer range (10 bit), while note #29 takes all the rest (97.2%). With ADLAR 11% cover 28 tones while 89% covers only one single note, not very much more convenient. Not a real linear coverage, A'' will be seriously overrated.

Somehow the incoming 1,023 or 255 should be linearly downsized to 28. The C programmer has no problem with that, he divides by 36.5 resp. by 9.1 and rounds the result. Unfortunately dividing with those numbers the C compiler envokes the floating number library. And this library alone fills the available space in an ATtiny13 completely, and even exceeds that. The C programmer now moves to a 96 pin ATxmega to have enough flash memory for his monster lib. The assembler programmer instead invests a little bit of intelligence and comes up with a much more clever solution, well fitting to the available memory of an ATtiny13.

The solution is to multiply the ADC result, with ADLAR set, with 29 and to divide it by 256. As a math formula: "Result = 29 * ADC / 256".

Dividing by 256 in binary math is as simple as can be: just skip the last eight bits of the result of the multiplication (or: just ignore the LSB).

The problem therefore is reduced to the question on how to multiply the ADC result with 29. Several methods are possible to do this.

#### Simplest multiplication

The simplest type of multiplication is to add the ADC result 29 times to a 16 bit result. E.g. like this (with number of necessary clock cycles to get execution times):
``````
in R0,ADCH ; Read ADC result, +1 = 1
clr R2 ; Clear R2:R1 at start adding, +1 = 2
clr R1 ; +1 = 3
ldi R16,29 ; Multiplicator 29, +1 = 4
AddLoop:
add R1,R0 ; add ADC to result, LSB, +29*1 = 33
brcc PostCarry ; No overflow to carry, +29*1 = 62
inc R2 ; Increase MSB when carry is set, +29*1 = 91
PostCarry:
dec R16 ; count downwards, + 29*1 = 130
brne AddLoop ; add once again, +28*2 + 1 = 187
```
```
The 187 clock cycles that are needed are not very long. At 1.2 Mcs/s those are 156 µs, less than a single audio sine wave.

The method to add can be used in all cases where the multiplicator is not too large and where the extended execution time is not too large. E.g. in case of 10 it is a preferred method: add the base number once, multiply the result twice by 2 (LSL, ROL), then add the base number again and multiply the result by 2 (again LSL and ROL).

In our case the 187 clock cycles are not too much, but there are other methods to multiply that are very much faster.

#### Faster multiplication

Multiplying by 2 is, in the binary world, the simplest and fastest task (LSL and ROL). We can multply by 2 on and on until we are near our 29. Then we add or subtract a little bit and we have the 29 fold. The next 2 potence is 32, from which we can subtract the ADC result three times. Like in this example:
``````
in R0,ADCH ; Read result from ADC to R0, +1 = 1
clr R2 ; R2:R1 is the result, +1 = 2
mov R1,R0 ; Copy ADC result once, +1 = 3
lsl R1 ; LSB * 2, +1 = 4
rol R2 ; MSB * 2 plus carry, +1 = 5
lsl R1 ; LSB * 4, +1 = 6
rol R2 ; MSB * 4 plus carry, +1 = 7
lsl R1 ; LSB * 8, +1 = 8
rol R2 ; MSB * 8 plus carry, +1 = 9
lsl R1 ; LSB * 16, +1 = 10
rol R2 ; MSB * 16 plus carry, +1 = 11
lsl R1 ; LSB * 32, +1 = 12
rol R2 ; MSB * 32 plus carry, +1 = 13
sub R1,R0 ; Subtract once, +1 = 14
brcc NoCarry1 ; Carry clear, +1/2 = 15/16
dec R2 ; Decrease MSB, +1 = 16
NoCarry1:
sub R1,R0 ; Subtract twice, +1 = 17
brcc KeinCarry2 ; Carry clear, +1/2 = 18/19
dec R2 ; Decrease MSB, +1 = 19
NoCarry2:
sub R1,R0 ; Subtract three times, +1 = 20
brcc NoCarry3 ; Carry clear, +1/2 = 21/22
dec R2 ; Decrease MSB, +1 = 22
NoCarry3:
```
```
New is the instruction ROL register. This is the left-rolling version, compared to the right-rolling ROR. ROL rolls
• the bits 0 to 6 to the next left bit (1 to 7),
• the carry bit to bit 0 of the register, and
• bit 7 of the register to the carry flag.
The 22 clock cycles of this mode of multiplication are by a factor of eight faster than the primitive multiplication. But we can do it even faster.

#### Even faster multiplication

The previous methods are tailored closely to the task. The real multiplication, applicable to any combination of two 8 bit binaries, is not so complicated that even C programmer can learn that method (to avoid monster libraries and the associated monster controllers).

The binary multiplication is even simpler than decimal multiplication. But let us start with decimal to understand the mechanism.

Decimal multiplication goes like this. The first step is to multiply the number with the least significant digit and to add this to the result. Then the number is shifted once left (multiplied by 10), multiplied by the second significant digit and again added to the result. This is repeated until all digits of the multiplicator have been multiplied and added.

The binary multiplication is even simpler, because only a 0 or a 1 can roll out. Which means no adding to the sum (0) or adding to the sum (1). The mechanism is the same (to roll out one bit to the right, to add the left-shifted number (or not) and to left-shift the number.

The multiplication of 255 by 29 is shown in the picture.

The second number (in R16) is shifted to the right, by which the next 0 or 1 is shifted to the carry flag. If that is 0, the first number (in R1:R0) is not added. If it is a 1 it is added 16 bit wise to the result (in R3:R2). Then the first number (in R1:R0) is shifted left (16 bit left shift). Again a right-shift of the second number, to add or not to add, etc. If all ones are shifted out of the second number, the multiplication is ended.

That is how the code looks like:
``````
in R0,ADCH ; Read MSB from the ADC as LSB, +1 = 1
clr R1 ; Clear MSB, +1 = 2
clr R2 ; Clear LSB result, +1 = 3
clr R3 ; dto., MSB, +1 = 4
ldi R16,29 ; Set multiplicator, +1 = 5
MultLoop:
lsr R16 ; Shift lowest bit to carry, +5*1 = 10
brcc NoAdding ; C = 0, do not add, +1*2+4*1 = 16
add R2,R0 ; add LSB, +5*1 = 21
adc R3,R1 ; add MSB with carry, +5*1 = 21
NoAdding:
lsl R0 ; Left shift first number, LSB, +5*1 = 26
rol R1 ; Left shift MSB and roll carry to MSB, +5*1 = 31
tst R16 ; End reached?, +1*5 = 36
brne MultLoop ; still ones to be processed, +4*2+1*1 = 45
```
```
The result (28) is now in register R3 (we ignore the LSB R2 = division by 256).

Ok, these are 45 clock cycles, and more than method 2. But the routine is processing any 8-by-8 bit multiplication and is not tailored to the 29 in our case. The routine is so simple that even C programmer can learn that, without having to import massive libraries.

So our problem is solved on how to make 28 out of an input of 255 (or whatever value the ADC returns). Without dividing (division by 256 does not really count as a division in the binary world). Many mathmatical problems, that require a division, can be solved in that way by avoiding divisions.

Home Top Tone generation Hardware Tone control Tables Multiplication Gamut Music

### 9.4.4 Programming the gamut

With that we have the basis to implement the gamut. The program follows, the source code can be downloaded here.
``````
;
; ***************************************************
; * Gamut tones with a potentiometer on an ATtiny13 *
; * (C)2017 by www.avr-asm-tutorial.net             *
; ***************************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; --------- Registers -----------------
; Used: R0 for LPM and calculations
; Used: R1 for calculations
.def rMultL = R2 ; Multiplicator, LSB
.def rMultH = R3 ; dto., MSB
; free: R4 .. R14
.def rSreg = R15 ; Save/restore SREG
.def rmp = R16 ; Multi purpose outside ints
.def rimp = R17 ; Multi purpose inside ints
.def rFlag = R18 ; Flag register
.equ bAdcR = 0 ; Read in ADC value
; free: R18 .. R29
; Used: R31:R30, Z = ZH:ZL for LPM
;
; --------- Ports ---------------------
.equ pOut = PORTB ; Output port
.equ pDir = DDRB ; Direction port
.equ pInp = PINB ; Input port
.equ bSpkD = DDB0 ; Speaker output pin
.equ bKeyO = PORTB3 ; Pull up Tasteneingang
.equ bKeyI = PINB3 ; Tasteninputpin
.equ bAdID = ADC2D ; ADC Input Disable pin
;
; --------- Timing --------------------
; Clock             = 1200000 cs/s
; Prescaler         = 1 or 8
; CTC TOP range     = 0 .. 255
; CTC divider range = 1 .. 256
; Toggle divider    = 2
; Frequency range   = 600 kcs/s .. 293 cs/s
;
; ---- Reset- and Interrupt vectors ---
.CSEG ; Assemble to code segment
.ORG 0 ; At start address
rjmp Start ; Reset vector, Init
reti ; INT0-Int, inactive
rjmp PcIntIsr ; PCINT-Int, active
reti ; TIM0_OVF, inactive
reti ; EE_RDY-Int, inactive
reti ; ANA_COMP-Int, inactive
reti ; TIM0_COMPA-Int, inactive
reti ; TIM0_COMPB-Int, inactive
reti ; WDT-Int, inactive
rjmp AdcIsr ; ADC-Int, active
;
; ----- Interrupt Service Routines ----
PcIntIsr: ; PCINT-Interrupt key
sbic pInp,bKeyI ; Skip next instruction if key = 0
rjmp PcIntIsrOff ; Key is not pressed
; Tone output on
ldi rimp,(1<<COM0A0)|(1<<WGM01) ; Toggle OC0A, CTC-A
out TCCR0A,rimp ; to timer control port A
rjmp PcIntIsrRet ; return
PcIntIsrOff:
; Tone output off
ldi rimp,(1<<COM0A1)|(1<<WGM01) ; Clear OC0A, CTC-A
out TCCR0A,rimp ; to timer control port A
PcIntIsrRet:
reti
;
AdcIsr: ; ADC-Interrupt
in rSreg,SREG ; Save SREG
in rMultL,ADCH ; Read MSB of ADC (ADLAR)
sbr rFlag,1<<bAdcR ; Set flag new ADC value
; Restart ADC
ldi rimp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
out ADCSRA,rimp ; to ADC control port A
out SREG,rSreg ; Restore SREG
reti
;
; ---------- Program start and Init ----------
Start:
; Init stack
ldi rmp,LOW(RAMEND) ; to SRAM end
out SPL,rmp ; to stack pointer
; Init In- and Output ports
ldi rmp,1<<bSpkD ; Speaker output direction
out pDir,rmp ; to direction port
ldi rmp,1<<bKeyO ; Pull up on key pin
out pOut,rmp ; to output port
; Configure timer as CTC
ldi rmp,(1<<COM0A1)|(1<<WGM01) ; Clear OC0A, CTC-A
out TCCR0A,rmp ; to timer control port A
; Prescaler and timer start by ADC int
; Configure ADC and start
ldi rmp,(1<<ADLAR)|(1<<MUX1) ; Left adjust, ADC2
out ADMUX,rmp ; to ADC MUX port
ldi rmp,(1<<ADEN)|(1<<ADSC)|(1<<ADIE)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
out ADCSRA,rmp ; to ADC control port A, start ADC
; PCINT for key input
ldi rmp,1<<PCINT3 ; Enable PB3-Int
out PCMSK,rmp ; to PCINT mask port
ldi rmp,1<<PCIE ; Enable PCINT
out GIMSK,rmp ; to Interrupt Mask port
; Enable sleep
ldi rmp,1<<SE ; Sleep mode idle
out MCUCR,rmp ; to MCU control port
; Enable interrupts
sei
; ------------ Main program loop --------
Loop:
sleep ; go to sleep
nop ; After wake up by int
sbrc rFlag,bAdcR ; Skip next if Adc flag zero
rcall AdcCalc ; Convert ADC value to tone
rjmp Loop ; go to sleep again
;
; ------------ Convert AD value ----------------
; ADC value to tone height and timer start
; AD value is in rMultL
AdcCalc:
cbr rFlag,1<<bAdcR ; Clear flag
; Multiply ADC value by 29
clr rMultH ; Clear MSB
clr R0 ; Clear result LSB
clr R1 ; dto., MSB
ldi rmp,29 ; Number of tones plus one
AdcCalcShift:
lsr rmp ; Shift lowest bit to carry
brcc AdcCalcNoAdd
add R0,rMultL ; add LSB to result
adc R1,rMultH ; add MSB with carry
AdcCalcNoAdd:
lsl rMultL ; Multiply by two
rol rMultH ; Roll carry to MSB und multiply by two
tst rmp ; rmp already empty?
brne AdcCalcShift ; no, go on multiplying
; Tone from gamut table
lsl R1 ; Tone number multiply by two
ldi ZH,HIGH(2*GamutTable) ; Z to tone table
ldi ZL,LOW(2*GamutTable)
add ZL,R1 ; Add tone number
ldi rmp,0 ; Add carry
adc ZH,rmp ; add 0 with carry
lpm R0,Z+; Read table value LSB to R0
out TCCR0B,R0 ; to timer control port B
lpm ; Read next table value MSB to R0
out OCR0A,R0 ; to compare match A port
ret ; done
;
; ------- Gamut table -----------
GamutTable:
.db 1<<CS01, 169 ; a #0
.db 1<<CS01, 151 ; h #1
.db 1<<CS01, 135 ; cis #2
.db 1<<CS01, 127 ; d #3
.db 1<<CS01, 113 ; e #4
.db 1<<CS01, 101 ; fis #5
.db 1<<CS01, 90 ; gis #6
.db 1<<CS01, 84 ; a' #7
.db 1<<CS01, 75 ; h' #8
.db 1<<CS01, 67 ; cis' #9
.db 1<<CS01, 63 ; d' #10
.db 1<<CS01, 56 ; e' #11
.db 1<<CS01, 50 ; fis' #12
.db 1<<CS01, 44 ; gis' #13
.db 1<<CS01, 42 ; A #14
.db 1<<CS01, 37 ; H #15
.db 1<<CS01, 33 ; CIS #16
.db 1<<CS01, 31 ; D #17
.db 1<<CS00, 226 ; E #18
.db 1<<CS00, 204 ; FIS #19
.db 1<<CS00, 181 ; GIS #20
.db 1<<CS00, 169 ; A' #21
.db 1<<CS00, 151 ; H' #22
.db 1<<CS00, 135 ; CIS' #23
.db 1<<CS00, 127 ; D' #24
.db 1<<CS00, 113 ; E' #25
.db 1<<CS00, 101 ; FIS' #26
.db 1<<CS00, 90 ; GIS' #27
.db 1<<CS00, 84 ; A'' #28
;
; End of source code
;
```
```
Besides the already applied LPM instructions in all of its sub variants no new instructions are used here.

A serious hint for programming this into the chip: on pin 5 a large electrolytic capacitor and a relatively low resistance of the speaker are attached. Those components conflict with the relatively high serial programming pulses, and error messages from the programmer will result. So please remove the electrolyt first, before starting to program, and plug it in again after this is finished.

Home Top Tone generation Hardware Tone control Tables Multiplication Gamut Music

### 9.4.5 Debugging with avr_sim

The simulation with avr_sim shows the following results. Simulated are the multiplication routine and the gamut table access with LPM.

To initiate the hardware we step through the first instructions until the interrupts were enabled by SEI. Then we click on the PCINT in the ports display and initiate starting to play the melody.

Like in the first case the audio output on pin PB0 is set as output and is cleared.

Pin PB3 has its pullup resistor on and the PCINT interrupt mask enables interrupts on both edges.

The AD converter is constantly running to measure the input voltage of ADC2, which is on portpin PB4. The input voltage simulated is 3.75 V, which should yield 3.75 / 5.00 * 1023 = 767 or, in the left adjusted operating mode, 191 decimal or 0xBF.

After reaching the ADC conversion complete interrupt, the interrupt service routine has written the upper eight bits of the result to register R2 and has set the bAdcR flag. This triggers the routine AdcCalc: after the interrupt woke up the controller from SLEEP. Now we have to calculate which gamut note is selected with the potentiometer.

The instructions
```   124: 000032   2433  clr rMultH ; Clear MSB
125: 000033   2400  clr R0 ; Clear result LSB
126: 000034   2411  clr R1 ; dto., MSB
127: 000035   E10D  ldi rmp,29 ; Number of tones plus one
```
clear rMultH (R3), clear the multiplication result in R0 and R1 and load the number of available gamut tones, decimal 29, to register rmp (R16). Now multiplication can start.

The instruction
```   129: 000036   9506  lsr rmp ; Shift lowest bit to carry
```
shifts R16 to the right and shifts bit 0 to the carry flag in the SREG. In this case the bit is 1.

The instructions
```   130: 000037   F410  brcc AdcCalcNoAdd
131: 000038   0C02  add R0,rMultL ; add LSB to result
132: 000039   1C13  adc R1,rMultH ; add MSB with carry
```
first check if the carry flag is cleared, and adding will be skipped. Here this is not the case and the 16 bit number in R3:R2 is added to the result in R1:R0. In the third instrcution ADC handles the carry from any overflows to the MSB that might have occurred during the first ADD.

The two insructions
```   134: 00003A   0C22  lsl rMultL ; Multiply by two
135: 00003B   1C33  rol rMultH ; Roll carry to MSB und multiply by two
```
shift the multiplicator in R3:R2 one position to the left and multiply the content by two.

As R16 is still not zero, the multiplication is continued by shifting the next bit in R16 to the carry flag. This time a zero is shifted, so adding R3:R2 to R1:R0 is skipped.

The next step is again multiplying R3:R2 by two, again shift and rotate.

There are still ones in rmp, so we have to repeat shifting. With the third shift, again a one is shifted to the carry.

Again R3:R2 are added to R1:R0 in 16-bit manner, with ADD/ADC. We do not display the boring multiplication of R3:R2 by 2 that follows next.

It is getting boring, but we have to shift rmp again. This time a one is shifted to the carry. We do not display addition of R3:R2 to R1:R0, but this has to be done next.

The next adding of R3:R2 to R1:R0 happens here.

And the next multiplication by 2 for R3:R2 here.

Now the last 1 rolls out to carry, and we are nearly done with multiplication.

The last addition of R3:R2 to R1:R0. The following next multiplication by 2 is not necessary any more and makes no sense because R16 is already empty and the multiplication loop will not repeat any more.

The stop watch was used to measure the time for multiplication. It shows 55 µs, which is rather fast considering the many steps of multiplication. No need to upgrade to a ATmega with built-in hardware multiplication though, to avoid the necessary time (that we have plenty of) and replacing the 12 instructions of the multiplication routine by just one single MUL instruction.

Now Z is set to the gamut table. The instructions
```   138: 	; Tone from gamut table
139: 00003E   0C11  lsl R1 ; Tone number multiply by two
140: 00003F   E0F0  ldi ZH,HIGH(2*GamutTable) ; Z to tone table
141: 000040   E9E2  ldi ZL,LOW(2*GamutTable)
```
multiply the result of the multiplication by 2 and load the register pair Z with the doubled address of the gamut table.

The gamut table is here:
```   151: ; ------- Gamut table -----------
152: GamutTable:
153: .db 1<<CS01, 169 ; a #0
000049 A902
154: .db 1<<CS01, 151 ; h #1
00004A 9702
...
```
It's address is 0x000049, which is doubled to 0x0092 in Z to access the content of the table bytewise.

Now the doubled value in R1, 0x2A, is added to the table address 0x0092.
```   142: 000041   0DE1  add ZL,R1 ; Add tone number
143: 000042   E000  ldi rmp,0 ; Add carry
144: 000043   1FF0  adc ZH,rmp ; add 0 with carry
```
Here, ZL and R1 are added, the result goes to ZL. Then rmp is set to zero and is added with the carry flag to ZH. This ensures that the MSB is correct. Please note that CLR rmp would have also cleared the carry flag, so LDI is a better choice. Now Z points to 0x00BC.

The note to play at 0x00BC, which corresponds to table address 0x005E, is
```   174: .db 1<<CS00, 169 ; A' #21
00005E A901
```
So the prescaler will be set to CS00 (prescaling by 1) and the compare match A value to 169. That means a frequency of

f = 1,200,000 / 1 / (169 + 1) / 2 = 3,529.4 Hz

, which is roughly the gamut note A' (should be 3,520 Hz).

Now the instruction
```   145: 000044   9005  lpm R0,Z+; Read table value LSB to R0
```
reads the LSB of the table value to R0 and increases the address in Z by one. The result in R0 is written to the timer control register TCCR0B and sets the prescaler to 1.

Now the instruction
```   147: 000046   95C8  lpm ; Read next table value MSB to R0
```
reads in the second byte in the gamut table, 0xA9 or decimal 169, to R0. LPM always loads to R0 and Z is not increased here. This value is written to the compare match A portregister.

Prescaler and compare match A value of timer TC0 have been set and are ready to play gamut tone A' now, but the portpin PB0 is still cleared on compare match and the sound is off.

Home Top Tone generation Hardware Tone control Tables Multiplication Gamut Music

### 9.4.6 Debugging with the Studio

If we write such routines such as the multiplication or the reading from the gamut table, we would like to know if that part really works fine. At the latest if no tone emerges from the speaker when the key is pressed, we know that we have a bug in our source code software. There are opportunities to follow the controller's work step by step and so to search for such bugs and to identify and correct those. The step-by-step analysis is integrated into the Studio and its name is simulator.

To start the simulator, we just select "Start Debugging" from the menue. Before we do that with our source code, we add the following lines:
``````
; ---- To debug the multiplication routine
.equ debug = 1 ; Switch debugging on
.if debug == 1 ; if switch is on do the following:
ldi rmp,0xFF ; To preselect a simulated ADC value
mov rMultL,rmp ; copy to the register used for that
rjmp AdcCalc ; Jump directly to the multiplication routine
.else
; Skip all the following when the debug switch is on:
;
; ---- Reset- and Interrupt vectors ---
.CSEG ; Assemble to code segment
.ORG 0 ; To the beginning
rjmp Start ; Reset vector, Init
reti ; INT0-Int, inactive
rjmp PcIntIsr ; PCINT-Int, active
reti ; TIM0_OVF, inactive
reti ; EE_RDY-Int, inactive
reti ; ANA_COMP-Int, inactive
reti ; TIM0_COMPA-Int, inactive
reti ; TIM0_COMPB-Int, inactive
reti ; WDT-Int, inactive
rjmp AdcIsr ; ADC-Int, active

.endif
;
```
```
Those lines mask the reset and vector table if the debug switch is on. With that the simulator loads a test number to rMultL and jumps directly to the routine, without all the other init procedures. The reason is that the int and vector table does not allow to add code lines prior to address 0. We could also change the init source code section to load our test value and to jump to the routine.

After the simulator has been started, he offers manyfold information on the interior of the simulated controller. We can look at the content of the registers (below, left), you can start a stop watch counting cycles and execution times (left, middle). The yellow cursor (above, right) points to the first executable instruction.

With the menue entry "Step into" (F11) the execution of this instruction is simulated. Executed was the source code "ldi rmp,0xFF", with rmp = R16. In the list of registers the changed value of R16 is marked in red, the program counter is now at 0001 and the cursor points to the next instruction.

If we do not want to single step through lengthy code, we can add breakpoints. Those are points in the code where the simulation stops and register values or ports can be examined. Only executable instructions can have a breakpoint, other lines in the source code cannot. To add a breakpoint we set the cursor to that code line and select "Toggle breakpoint" from the context menue. With "Run" in the debug menue the simulator runs until he comes to such a breakpoint. He then stops.

In our case of debugging the multiplication routine is of interest and the end of writing the table values to the timer ports. In the first case the register R1 holds 0x1C, which is the expected decimal 28.

This is the state of the timer when the second breakpoint is reached. The two ports that we wrote the gamt table values to, TCCR0B and OCR0A show the correct values from the gamut table for tone #28. Because we skipped the timer init procedure the other timer ports are incorrect, but our multiplication and timer write is correct

With those tools we can search and identify errors in our source code.

Home Top Tone generation Hardware Tone control Tables Multiplication Gamut Music

## 9.5 Playing music

### 9.5.1 Task

The controller shall play the Internationale when the key is pressed.

### 9.5.2 The melody to be played

This has to be played.

Those are the notes to translate the melody to the gamut table. In order to translate it would be more convenient to not handle notes numbers but to have names associated with those numbers. To do this we add ".equ name=number"e; for each note. Those names start with an n, then the tones a to g, followed by the octave 0 to 4. E.g. like that:

``````
; Notes symbols
.equ na0 = 0
.equ nh0 = 1
.equ nc0 = 2
.equ nd0 = 3
.equ ne0 = 4
.equ nf0 = 5
.equ ng0 = 6
.equ na1 = 7
.equ nh1 = 8
.equ nc1 = 9
.equ nd1 = 10
.equ ne1 = 11
.equ nf1 = 12
.equ ng1 = 13
.equ nA2 = 14
.equ nH2 = 15
.equ nC2 = 16
.equ nD2 = 17
.equ nE2 = 18
.equ nF2 = 19
.equ nG2 = 20
.equ nA3 = 21
.equ nH3 = 22
.equ nC3 = 23
.equ nD3 = 24
.equ nE3 = 25
.equ nF3 = 26
.equ nG3 = 27
.equ nA4 = 28
```
```
The original notation of notes, that works with small and large letters cannot be used in assembler because assembler does not know the difference between small and capital letters.

With these definitions the table for our melody goes like this:
``````
Melody:
.db ne1,nd1,nc1,ng0,ne0,na1,nf0,0xFF
```
```
This can be conveniently handled.

The trailing 0xFF signals that the melody ends here (if that would not be here the controller would interprete his whole flash storage as melody and would play that on and on).

### 9.5.3 Tone duration

The symbols used in music writing encode the duration over which a tone has to be played. We therefore need an additional information on tone duration for our melody. To encode this to our melody table we could add two bits to our notes table, one means 1/8, two means 1/4, three means 3/8 and four means 1/2. Our melody would look like this:
``````
.equ bLow = 1<<5 ; Duration encoding, low bit
.equ bHigh = 1<<6 ; Duration encoding, high bit
.equ d12 = bLow + bHigh ; Duration 1/2, both duration bits high
.equ d14 = bHigh ; Duration 1/4, upper duration bit high
.equ d38 = bLow ; Duration 3/8, lower duration bit high
.equ d18 = 0 ; Duration 1/8, neither upper nor lower bit high
Melody:
.db ne1+d38,nd1+d14,nc1+d12,ng0+d38,ne0+d18,na1+d12,nf0+d14,0xFF
```
```
In order to decode those notes we would program the following:
``````
; Z points to melody table
ldi ZH,HIGH(2*Musik) ; MSB pointer for LPM
ldi ZL,LOW(2*Musik) ; dto., LSB
; Read note byte
lpm R17,Z ; Read first note from table to R17
; Check end of melody
cpi R17,0xFF ; End of melody signature?
breq MelodyOff ; Switch music off
; Decode duration of note
andi R17,0b01100000 ; Isolate bits 5 and 6
ldi R16,1 ; Number of octas
sbrc R17,5 ; Check bit 5
subi R16,-1 ; Increase by one (addi is not available)
sbrc R17,6 ; Check bit 6
subi R17,-2 ; Add two
; Convert note to timer prescaler and timer CTC
lpm R17,Z+ ; Again read note, inc pointer
andi R17,0b10011111 ; Clear duration bits
[Convert and play note for duration in R16]
MelodyOff:
[Switch off music output]
```
```
We can also encode the duration in an extra byte. This would ease note processing, but double the length of the melody table. With our simple and short melody we can affort this without risking flash memory shortages:
``````
Melody: ; LSB: Note or FF, MSB: Duration in octa
.db ne1,3,nd1,2,nc1,4,ng0,3,ne0,1,na1,4,nf0,2,0xFF,0xFF
```
```
Now an additional 0xFF has to be added to the table to reach an even number of bytes in the table.

### 9.5.4 Tone pauses

To play notes means not only tones but also pauses in between. Not between the first and the second note of that melody, but in between any other notes. So we need not only an end signature but also a pause signal. We select 0xFE for that. Our table now looks like that:
``````
Musik: ; LSB: Note or FF, MSB: Duration in octa
.db ne1,3,nd1,2,0xFE,1,nc1,4,0xFE,1,ng0,3,0xFE,1,ne0,1,0xFE,1,na1,4,0xFE,1,nf0,2,0xFF,0xFF
```
```

### 9.5.5 Duration to play different notes

From the controller point of view another problem arises. If we play different tones with frequencies between 440 and 7,040 cs/s the duration of each half wave is very different. A different number of half waves have to be absolved to come to the same tone duration. At 440 cs/s 880 CTC events have to be absolved to yield one second, but at 7,040 cs/s those have to be 14,080 CTC events long. The number of CTC events to be performed is reversely proportional to the frequency. We can resolve this with two opportunities:
• We divide 600.000 by the prescaler and the CTC value and get the number of CTC cycles, or
• we add this number to our gamut table and read that out with LPM.
As we are lazy and are not willing to divide 24 bit integers by 8 bit numbers in assembler and as we do not want the controller to do that lengthy and boring procedure each time he reads a note, we choose the second option. The C programmer sees no problem here and imports its floating point arithmetic library here, but changes to an ATxmega.

The formulation of the gamut table, now with the duration of the tone in one octa of a second, looks like this:
``````
GamutTable_Duration:
.DB 1<<CS01, 169, 110, 0 ; a #0
.DB 1<<CS01, 151, 123, 0 ; h #1
.DB 1<<CS01, 135, 138, 0 ; cis #2
.DB 1<<CS01, 127, 146, 0 ; d #3
.DB 1<<CS01, 113, 164, 0 ; e #4
.DB 1<<CS01, 101, 184, 0 ; fis #5
.DB 1<<CS01, 90, 206, 0 ; gis #6
.DB 1<<CS01, 84, 221, 0 ; a' #7
.DB 1<<CS01, 75, 247, 0 ; h' #8
.DB 1<<CS01, 67, 20, 1 ; cis' #9
.DB 1<<CS01, 63, 37, 1 ; d' #10
.DB 1<<CS01, 56, 73, 1 ; e' #11
.DB 1<<CS01, 50, 112, 1 ; fis' #12
.DB 1<<CS01, 44, 161, 1 ; gis' #13
.DB 1<<CS01, 42, 180, 1 ; A #14
.DB 1<<CS01, 37, 237, 1 ; H #15
.DB 1<<CS01, 33, 39, 2 ; CIS #16
.DB 1<<CS01, 31, 74, 2 ; D #17
.DB 1<<CS00, 226, 149, 2 ; E #18
.DB 1<<CS00, 204, 220, 2 ; FIS #19
.DB 1<<CS00, 181, 56, 3 ; GIS #20
.DB 1<<CS00, 169, 114, 3 ; A' #21
.DB 1<<CS00, 151, 219, 3 ; H' #22
.DB 1<<CS00, 135, 79, 4 ; CIS' #23
.DB 1<<CS00, 127, 148, 4 ; D' #24
.DB 1<<CS00, 113, 36, 5 ; E' #25
.DB 1<<CS00, 101, 191, 5 ; FIS' #26
.DB 1<<CS00, 90, 112, 6 ; GIS' #27
.DB 1<<CS00, 84, 229, 6 ; A'' #28
; Pause for one octa of a second
; .DB (1<<CS01)|(1<<CS00), 255, 18, 0 ; Pause #254
```
```
Our table now has four bytes per tone and we can start programming the melody.

### 9.5.4 Processing structure

To program this we need to
• decide on a key press event, if the melody is still processed. If not, the process has to be started by setting a start flag,
• read the next note from the melody table,
• to read the tone's prescaler and CTC values from the gamut table and to write these to the timer,
• to read the duration over which the note will be played from the gamut table, to read the duration factors (full notes, half notes, etc.) from the melody table and to write this to a 16 bit counter accordingly,
• switch on (in tones) or off (in pauses) the speaker pulse output mode,
• detect the end of the melody and if detected to switch off the speaker output, clear the timer and to re-enable key events.
Within interrupt service routines the following has to done:
• PCINT: To detect the polarity of the key input, if high then set a start flag.
• TC0-Compare-A-Int: Decrease the 16 bit counter, if zero then set an end flag.
Within the main program loop both flags are ckecked and the associated actions performed (start melody from scratch, output next note, etc,).

### 9.5.5 Program

This is the final program, the source code is here.
``````
;
; ***************************************
; * To play a melody with an ATtiny13   *
; * (C)2017 by www.avr-asm-tutorial.net *
; ***************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; ------- Register ---------------------
; free: R0 .. R14
.def rSreg = R15 ; Save SREG
.def rmp = R16 ; Multi purpose register
.def rFlag = R17 ; Flag register
.equ bStart = 0 ; Start melody play
.equ bNote = 1 ; Play next note
; free: R18 .. R23
.def rCtrL = R24 ; 16 bit counter CTC events, LSB
.def rCtrH = R25 ; dto., MSB
; used: X, XH:XL for duration calculation and to save Z
; used: Y, YH:YL for octa duration
; used: Z, ZH:ZL for reading from program memory, as melody pointer
;
; ------ Ports -------------------------
.equ pOut = PORTB ; Output port
.equ pDir = DDRB ; Direction port
.equ pInp = PINB ; Input port
.equ bSpkD = DDB0 ; Speaker output pin
.equ bKeyO = PORTB3 ; Pull up key input pin, write
.equ bTasI = PINB3 ; Key input pin, read
;
; --------- Timing --------------------
; Clock             = 1200000 cs/s
; Prescaler         = 1, 8, 64
; CTC TOP range     = 0 .. 255
; CTC divider range = 1 .. 256
; Toggle divider    = 2
; Frequency range   = 600 kcs/s to 36 cs/s
;
; ---- Reset- and Interrupt vectors ---
.CSEG ; Assemble to the code segment
.ORG 0 ; To the beginning
rjmp Start ; Reset-Vector, Init
reti ; INT0-Int, inactive
rjmp PcIntIsr ; PCINT-Int, active
reti ; TIM0_OVF, inactive
reti ; EE_RDY-Int, inactive
reti ; ANA_COMP-Int, inactive
rjmp TC0CAIsr ; TIM0_COMPA-Int, active
reti ; TIM0_COMPB-Int, inactive
reti ; WDT-Int, inactive
reti ; ADC-Int, inactive
;
; ---- Interrupt service routines ---
PcIntIsr: ; PCINT on key events
in rSreg,SREG ; Save SREG
sbic pInp,bTasI ; Skip next if input is low
rjmp PcIntIsrRet ; Ready
brts PcIntIsrRet ; If T flag is set, skip
sbr rFlag,1<<bStart ; Set start flag
PcIntIsrRet:
out SREG,rSreg ; Restore SREG
reti
;
TC0CAIsr: ; Timer CTC A Int
in rSreg,SREG ; Save SREG
sbiw rCtrL,1 ; Downcount 16 bit counter
brne TC0CAIsrRet ; not yet zero
sbr rFlag,1<<bNote ; Set flag for next note
TC0CAIsrRet:
out SREG,rSreg ; Restore SREG
reti
;
; ---- Program start, Init -------------
Start:
; Init stack
ldi rmp,LOW(RAMEND) ; Stack pointer to SRAM end
out SPL,rmp
; Clear T flag
clt ; Set flag inactive
; Init and configure portpins
ldi rmp,1<<bSpkD ; Speaker output pin output
out pDir,rmp ; to direction port
ldi rmp,1<<bKeyO ; Pull up on key port pin
out pOut,rmp ; to output port
; Start timer as CTC
ldi rmp,(1<<COM0A1)|(1<<WGM01) ; Clear OC0A, CTC-A
out TCCR0A,rmp ; to timer control port A
; Prescaler, timer start and Int in start routine
; PCINT for key input events
ldi rmp,1<<PCINT3 ; Anable PB3 Int
out PCMSK,rmp ; in PCINT mask port
ldi rmp,1<<PCIE ; Enable PCINT
out GIMSK,rmp ; in Interrupt mask port
; Enable sleep
ldi rmp,1<<SE ; Sleep mode idle
out MCUCR,rmp ; in MCU control port
; Enable interrupts
sei
; ---- Main program loop -----------
Loop:
sleep ; Go to sleep
nop ; Wake up
sbrc rFlag,bStart ; Skip next if start flag zero
rcall MelodyStart ; Start melody output
sbrc rFlag,bNote ; Skip next if no note to be played
rcall PlayNote ; Play next note
rjmp Loop ; Go back to sleep again
;
; ----- Flag handling routines -------------
MelodyStart: ; Start melody output
cbr rFlag,1<<bStart ; Clear flag
set ; Set T flag
ldi ZH,HIGH(2*Melody) ; Pointer to melody
ldi ZL,LOW(2*Melody)
rcall PlayNote ; Output next note
ldi rmp,1<<OCIE0A ; Enable CTC ints
out TIMSK0,rmp ; to timer interrupt mask
ret
;
PlayNote: ; Output next note
cbr rFlag,1<<bNote ; Clear flag
rcall PlayNext ; Output next note
ret
;
PlayNext: ; Play the note to which Z points
lpm rmp,Z+ ; Read note from melody table
cpi rmp,0xFF ; Check melody end
brne PlayNext1 ; Not at end
; Melody is over
ldi rmp,(1<<COM0A1)|(1<<WGM01) ; Clear OC0A, CTC-A
out TCCR0A,rmp ; to timer control port A
clr rmp ; Clear timer interrupts
out TIMSK0,rmp ; in TC0 Interrupt mask port
pop rmp ; Remove call address from stack
pop rmp
clt ; Clear T flag
ret
PlayNext1: ; Not at end
cpi rmp,0xFE ; Pause?
brne PlayNext2 ; No
; Pause, output off
ldi rmp,(1<<COM0A1)|(1<<WGM01) ; Clear OC0A, CTC-A
out TCCR0A,rmp ; to timer control port A
ldi rmp,(1<<CS01)|(1<<CS00) ; Prescal = 64
out TCCR0B,rmp ; to timer control port B
ldi rmp,255 ; CTC to largest value
ldi rCtrL,18 ; Counter to one eights
ldi rCtrH,0
lpm R16,Z+ ; Overread duration byte
ret
PlayNext2: ; Normal note
mov XH,ZH ; Save melody pointer
mov XL,ZL
ldi ZH,HIGH(2*GamutTable_Duration) ; Pointer to gamut table
ldi ZL,LOW(2*GamutTable_Duration)
lsl rmp ; Note number * 2
lsl rmp ; * 4
add ZL,rmp ; add to pointer
ldi rmp,0 ; Carry adder
adc ZH,rmp ; Add with carry
lpm rmp,Z+ ; Read prescaler
out TCCR0B,rmp ; to timer control port B
lpm rmp,Z+ ; Read CTC value
out OCR0A,rmp ; to compare match A port
lpm YL,Z+ ; Read octa duration to Y
lpm YH,Z+
mov ZH,XH ; Restore pointer to melody
mov ZL,XL
lpm rmp,Z+ ; Read duration byte
mov XH,YH ; Copy single duration
mov XL,YL
PlayNext3:
dec rmp ; Decrease duration byte
breq PlayNext4 ; final
add XL,YL ; Add octa duration, LSB
adc XH,YH ; dto. MSB plus carry
rjmp PlayNext3
PlayNext4:
mov rCtrL,XL ; copy to counter, LSB
mov rCtrH,XH ; dto., MSB
ldi rmp,(1<<COM0A0)|(1<<WGM01) ; Toggle OC0A, CTC-A
out TCCR0A,rmp ; to timer control port A
ret
;
; Gamut table with duration
GamutTable_Duration:
.DB 1<<CS01, 169, 110, 0 ; a #0
.DB 1<<CS01, 151, 123, 0 ; h #1
.DB 1<<CS01, 135, 138, 0 ; cis #2
.DB 1<<CS01, 127, 146, 0 ; d #3
.DB 1<<CS01, 113, 164, 0 ; e #4
.DB 1<<CS01, 101, 184, 0 ; fis #5
.DB 1<<CS01, 90, 206, 0 ; gis #6
.DB 1<<CS01, 84, 221, 0 ; a' #7
.DB 1<<CS01, 75, 247, 0 ; h' #8
.DB 1<<CS01, 67, 20, 1 ; cis' #9
.DB 1<<CS01, 63, 37, 1 ; d' #10
.DB 1<<CS01, 56, 73, 1 ; e' #11
.DB 1<<CS01, 50, 112, 1 ; fis' #12
.DB 1<<CS01, 44, 161, 1 ; gis' #13
.DB 1<<CS01, 42, 180, 1 ; A #14
.DB 1<<CS01, 37, 237, 1 ; H #15
.DB 1<<CS01, 33, 39, 2 ; CIS #16
.DB 1<<CS01, 31, 74, 2 ; D #17
.DB 1<<CS00, 226, 149, 2 ; E #18
.DB 1<<CS00, 204, 220, 2 ; FIS #19
.DB 1<<CS00, 181, 56, 3 ; GIS #20
.DB 1<<CS00, 169, 114, 3 ; A' #21
.DB 1<<CS00, 151, 219, 3 ; H' #22
.DB 1<<CS00, 135, 79, 4 ; CIS' #23
.DB 1<<CS00, 127, 148, 4 ; D' #24
.DB 1<<CS00, 113, 36, 5 ; E' #25
.DB 1<<CS00, 101, 191, 5 ; FIS' #26
.DB 1<<CS00, 90, 112, 6 ; GIS' #27
.DB 1<<CS00, 84, 229, 6 ; A'' #28
;
; ---- Notes symbols ----------
;
.equ na0 = 0
.equ nh0 = 1
.equ nc0 = 2
.equ nd0 = 3
.equ ne0 = 4
.equ nf0 = 5
.equ ng0 = 6
.equ na1 = 7
.equ nh1 = 8
.equ nc1 = 9
.equ nd1 = 10
.equ ne1 = 11
.equ nf1 = 12
.equ ng1 = 13
.equ nA2 = 14
.equ nH2 = 15
.equ nC2 = 16
.equ nD2 = 17
.equ nE2 = 18
.equ nF2 = 19
.equ nG2 = 20
.equ nA3 = 21
.equ nH3 = 22
.equ nC3 = 23
.equ nD3 = 24
.equ nE3 = 25
.equ nF3 = 26
.equ nG3 = 27
.equ nA4 = 28
;
; ---- Melody -----------------
Melody: ; LSB: Note or FF, MSB: Duration in octa
;   Voel- ker          hoert        die
.db ne1,3,nd1,2,0xFE,1,nc1,4,0xFE,1,ng0,3,0xFE,1
;   Sig-         na-          le!
.db ne0,1,0xFE,1,na1,4,0xFE,1,nf0,2,0xFF,0xFF
;
;
; End of source code
;
```
```
The source code uses one new instruction in a somehow strange way. It is the POP instruction. This copies the upmost byte that was pushed onto the stack, e.g. by a call to a subroutine, and increases the stack pointer by one. If one does that two times and ignores the byte content, the calling address is removed from the stack. A return now jumps to the previous calling address and ignores the last call. Make sure in this case that this is in fact the calling address of the previous call and not a pushed data byte or something else.

Home Top Tone generation Hardware Tone control Tables Multiplication Gamut Music

### 9.5.6 Simulating execution

Simulation uses avr_sim to check execution times of the first two notes of the melody played.

The first note of the melody has been loaded. Timer/counter 0 is configured as CTC with Compare A as TOP value, which is at 56. The prescaler is 8 and the execution time of each CTC cycle is

tCTC = 8 * (56 + 1) / 1.2 = 380 µs

The compare match interrupt is enabled and portpin PB0 is toggled each time CTC occurs. This generates a tone with

f1 = 1,000,000 / 380 / 2 = 1,316 Hz

The first note in the melody table is ne1:
```   252: Melody: ; LSB: Note or FF, MSB: Duration in octa
253: ;   Voel- ker          hoert        die
254: .db ne1,3,nd1,2,0xFE,1,nc1,4,0xFE,1,ng0,3,0xFE,1
0000A7 030B 020A 01FE 0409
```
ne1 is defined as
```   232: .equ ne1 = 11
```
being note 11, which is corresponding to the following entry in the gamut table:
```   200: .DB 1<<CS01, 56, 73, 1 ; e' #11
000083 3802 0149
```
With CS01 in TCCR0B the prescaler is 8, the 56 defines the compare match A value. 73 and 1 are the durations of that note, which is 1*256 + 73 = 329. The 3 following the ne1 in the melody table says "increase duration by a factor of 3, which yields 987 CTC cycles (or 0x03DB) and a duration of 987 * 380 µs = 375 ms.

The duration 0x03DB is in the double register R25:R24 and will be decreased in the interrupt service routine of the compare match A int.

Z is pointing to the next note in the flash (0x0150), which points to address 0x00A8 and is multiplied by 2. This is correct (see the above listing of the melody).

This is the time when the registers R25:R24 reach zero and the next note will have to be played. The 379 ms are nearly exact (should be 375).

The second note of the melody has been loaded to TC0. The prescaler is again 8, but the compare match A value is now 63. That corresponds to

f2 = 1,200,000 / 8 / (63 + 1) / 2 = 1,171 Hz

The note to be played is nd1:
```   253: ;   Voel- ker          hoert        die
254: .db ne1,3,nd1,2,0xFE,1,nc1,4,0xFE,1,ng0,3,0xFE,1
```
which is note #10:
```   231: .equ nd1 = 10
```
And note #10 is d':
```   199: .DB 1<<CS01, 63, 37, 1 ; d' #10
000081 3F02 0125
```
So the prescaler and the compare match A are fine.

The duration of the note is now (1*256 + 37) * 2 = 586 or 0x024A CTC cycles or 586 * 380 µs = 223 ms.

The counter value R25:R24 is fine, the register Z points to the third note.

250 ms have elapsed, just slightly above the 223 ms that were desired and calculated.

The third note is a pause (0xFE). The only difference between a note and a pause is that OCR0A is not toggled but cleared, switching PB0 output to be always low.

Simulation is a powerful tool to debug even complex procedures step-by-step and to measure execution times exactly. Use it to test your own designs and to verify that the controller really does what you thought that he should do and in a timely manner.

Home Top Tone generation Hardware Tone control Tables Multiplication Gamut Music

©2017 by http://www.avr-asm-tutorial.net