S pomočjo vezja FRI-SMS sem realiziral sintetizator melodije, ki komunicira preko RS232 povezave. Uporabil sem mikro krmilnik FRI-SMS in piskač. Note sem s pomočjo piskača zaigral tako, da sem spreminjal razmerje časa med piskom in ne piskom.
Najprej se uporabniku izpiše začetno sporočilo, ki mu poda napotke.
Slika1: Začetno sporočilo
Nato lahko začne vpisovati melodijo. Lahko izbira med "c", "d", "e", "f", "g", "a", "h" in pa "_", ki pa pomeni premor. Lahko pa vpiše tudi v naprej določene ukaze. Za prikaz teh ukazov mora v terminal vpisati "/help". Vsak ukaz se začne z znakom "/", potrditev vnosa pa se naredi z pritiskom na "ENTER".Slika2: Sporočilo /help
Uporabnik lahko vnese neko zaporedje tonov, katere se mu med vnašanjem igrajo.
Slika3: Primer melodije
Po vnešeni melodiji ima možnost sharanjevanja te melodije. Vpiše ukaz "/save". Če ima uporabnik že shranjeno neko melodijo, jo ta ukaz izbriše in shrani.
Slika4: Shranjevanje melodije
Ko ima neko melodijo shranjeno, vpiše ukaz "/play" in se mu ta melodija ponovno zaigra. To lahko stori večkrat. Možnost ima pa tudi brisanja te melodije z ukazom "/clr". Ko uporabnik hoče zaključiti izvajanje, vpiše ukaz "/quit".
Program se začne z inicializacijo vseh komponent. Inicializira se TC0, TC1, LED lučka, BUZZER in enota DBGU. Nato se zgodi izpis začetnega sporočila.
Nato pa se začne glavna zanka programa. Najprej program čaka na nek vnos v terminal. To naredi v funkciji RCV_DEBUG. Ko prejme nek znak, najprej preveri, če ima dobljen znak ASCII kodo 0x2f. To je znak /. V tem primeru v terminal izpiše znak \n, kar pomeni, da gre v novo vrstico in izvede funkcijo COMMAND_CATCH. V funkciji COMMAND_CATCH nato počaka, da uporabnik vnese nek ukaz. Ukaz uporabnik potrdi z pritiskom gumba ENTER. Ko pritisne enter se najprej v terminal izpiše \n. Nato pa se preveri, če je ukaz enak kateremu od vnaprej določenih. Vsebuje ukaze: /help, /save, /play, /clr, /quit.
- Ukaz /help izpiše vsebovane ukaze in kaj naredijo.
- Ukaz /save shrani nazadnje zaigrano melodijo.
- Ukaz /play zaigra shranjeno melodijo ponovno.
- Ukaz /clr počisti shranjeno melodijo.
- Ukaz /quit zaključi izvajanje programa.
Vse funkcije, ki se sprožijo pri ukazih so navedene in razložene spodaj v točki: 5. Funkcije ukazov. Po tem pa se program vrne na začetek glavne zanke in čaka na nov vnos.
V primeru, da vnesen znak ni bil /, se celotni del preskoči in se najprej izvede funkcija ADD_NOTE, ki trenutno zaigran ton označi in doda v pomnilnik. Nato se izvede funckija NOTE_FREQ, ki pogleda ASCII kodo znaka in na podlagi te vrne neko vrednost, ki določi ton.
Na koncu pa se izvede še funkcija BUZZ.
Funkcija BUZZ najprej začne časovnik za igranje note (TC1). Nato vstopi v zanko, ki se bo izvajala toliko časa, dokler časovnik TC1 ne postavi CPCS zastavice. Časovnik TC1 vedno traja 0.5s.
bl INIT_LED
bl INIT_TC0
bl INIT_TC1
bl INIT_BUZZER
bl DEBUG_INIT
V tem delu se inicializira časovnik TC0. Inicializira se z nastavitvami:
- WAVE=1, WAVESEL=10
- Frekvenca urinega signala:
$MCK/2 = 240000000Hz$ $RC=24$
Ta časovnik torej čaka 1µs. Poskuša doseči največjo natančnost, med tem ko je za pod enoto izbrana neka smiselna vrednost.
INIT_TC0:
stmfd r13!, {r0, r2, r14}
ldr r2, =PMC_BASE /*Enable PMC for TC0 */
mov r0, #(1 << 17)
str r0, [r2,#PMC_PCER]
/*Initialize TC0 MCK/2, RC=24 (1 µs) */
ldr r2, =TC0_BASE
mov r0, #0b110 << 13 /*WAVE=1, WAVSEL= 10*/
add r0, r0, #0b000 /* MCK/2 */
str r0, [r2, #TC_CMR]
ldr r0, =375
str r0, [r2, #TC_RC]
mov r0, #0b0101 /*TC_CLKEN,TC_SWTRG*/
str r0, [r2, #TC_CCR]
ldmfd r13!, {r0, r2, r15}
V tem delu se inicializira časovnik TC1. Inicializira se z nastavitvami:
- WAVE=1, WAVESEL=10
- Frekvenca urinega signala:
$SLCK=32768 Hz$ $RC=16384$
INIT_TC1:
INIT_TC1:
stmfd r13!, {r0, r2, r14}
ldr r2, =PMC_BASE /*Enable PMC for TC1 */
mov r0, #(1 << 18)
str r0, [r2,#PMC_PCER]
/*Initialize TC1 SLCK, RC=16384 (0.5s) */
ldr r2, =TC1_BASE
mov r0, #0b110 << 13 /*WAVE=1, WAVSEL= 10*/
add r0, r0, #0b100 /* SLCK = 32768Hz */
str r0, [r2, #TC_CMR]
ldr r0, =16384 /* 0.5s at 32768Hz */
str r0, [r2, #TC_RC]
/* mov r0, #0b0101 */
/* TC_CLKEN,TC_SWTRG */
/* str r0, [r2, #TC_CCR] */
ldmfd r13!, {r0, r2, r15}
V tem delu se inicializira piskač. Na ustrezno mesto se zapiše bit 1 v registrih PIO_PER (PIO ENABLE REGISTER) in PIO_OER (PIO OUTPUT ENABLE REGISTER).
INIT_BUZZER:
stmfd r13!, {r0, r1, r14}
mov r1, #0b1 << 26
ldr r0, =PIOA_BASE
str r1, [r0, #PIO_PER]
str r1, [r0, #PIO_OER]
ldmfd r13!, {r0, r1, r15}
To je glavna zanka programa, ki se ponavlja do prekinitve izvajanja. Najprej se izvede branje znaka, nato pa generira odziv glede na vpisano. Najprej preveri, če gre za ukaz, drugače pa zaigra zapisano noto.
LOOP:
bl RCV_DEBUG
cmp r2, #0x2f /* Check if char is "/" */
bne SKIP0
ldr r2, =10
bl SND_DEBUG
ldr r2, =0x2f
bl SND_DEBUG
bl COMMAND_CATCH
/* is for sure not a command */
SKIP0:
bl SND_DEBUG
bl ADD_NOTE
bl NOTE_FREQ
bl BUZZ
b LOOP
V tej funkciji program čaka na vnos znaka uporabnika.
RCV_DEBUG:
stmfd r13!, {r0, r1, r14}
ldr r1, =DBGU_BASE
RCVD_LP:
ldr r0, [r1, #DBGU_SR]
tst r0, #1
beq RCVD_LP
ldr r2, [r1, #DBGU_RHR]
ldmfd r13!, {r0, r1, pc}
Ta funkcija izpiše znak v terminal.
SND_DEBUG:
stmfd r13!, {r1, r3, r14}
ldr r1, =DBGU_BASE
SNDD_LP:
ldr r3, [r1, #DBGU_SR]
tst r3, #(1 << 1)
beq SNDD_LP
str r2, [r1, #DBGU_THR]
ldmfd r13!, {r1, r3, pc}
Ta funkcija najprej počaka, da uporabnik vnese nek ukaz. Pravzaprav v zanki bere zapisane znake, branje pa prekini, ko uporabnik pritisne gumb ENTER. Nato pa se izvede preverjanje zapisanega. Če pride do ujemanja, se izvede primerna funkcija za določen ukaz. Če ukaz ni najden, pa vrne vse registre na prejsnje stanje in skoči na začetek glavne zanke.
COMMAND_CATCH:
stmfd r13!, {r0, r1, r2}
ldr r0, =Command
CONTINUE_READING:
bl RCV_DEBUG
bl SND_DEBUG
strb r2, [r0]
add r0, r0, #1
cmp r2, #13 /* IS ENTER? */
bne CONTINUE_READING
ldr r2, =10
bl SND_DEBUG
/* PRINT NEWLINE */
ldr r0, =Command
ldr r1, [r0]
ldr r2, =0x706C6568
cmp r1, r2 /* IF r1=="help" */
bleq COMMAND_HELP
ldr r2, =0x65766173
cmp r1, r2 /* IF r1=="save" */
bleq COMMAND_SAVE
ldr r2, =0x79616C70
cmp r1, r2 /* IF r1=="play" */
bleq COMMAND_PLAY
ldr r2, =0x0D726C63
cmp r1, r2 /* IF r1=="clr" */
bleq COMMAND_CLEAR
ldr r2, =0x74697571
cmp r1, r2 /* IF r1=="quit" */
bleq COMMAND_QUIT
ldmfd r13!, {r0, r1, r2}
b LOOP
Naloga te funkcije je določati čas čakanja TC0 v funkciji BUZZ. Primerja vnesen znak z njihovo ASCII kodo in če pride do ujemanja zapiše v register r2 vrednost čakanja. Nato bo TC0 čakal toliko mikrosekund, kot je vnesena vrednost.
NOTE_FREQ:
/*
note given with char in r2
return freq in r2
*/
stmfd r13!, {r14}
cmp r2, #0x0
beq PLAY_MELODY
cmp r2, #0x63
ldreq r2, =45
beq FREQ_END
cmp r2, #0x64
ldreq r2, =41
beq FREQ_END
cmp r2, #0x65
ldreq r2, =37
beq FREQ_END
cmp r2, #0x66
ldreq r2, =36
beq FREQ_END
cmp r2, #0x67
ldreq r2, =33
beq FREQ_END
cmp r2, #0x61
ldreq r2, =30
beq FREQ_END
cmp r2, #0x68
ldreq r2, =27
beq FREQ_END
cmp r2, #0x5f
ldreq r2, =1
FREQ_END:
ldmfd r13!, {pc}
Piskač se prižge z vpisom bita 1 na ustrezno mesto v registru PIO_SODR (Set Output Data Register).
BUZZER_ON:
stmfd r13!, {r0, r1, r14}
ldr r0, =PIOA_BASE
mov r1, #0b1 << 26
str r1, [r0, #PIO_SODR]
ldmfd r13!, {r0, r1, pc}
Prav tako program vsebuje BUZZER_OFF funkcijo, ki deluje enako kot BUZZER_ON, le da je vnesen bit 0.
ADD_NOTE:
stmfd r13!, {r0, r1, r14}
ldr r0, =Num_played
ldrb r1, [r0]
ldr r0, =Current_melody
strb r2, [r0, r1]
add r1, r1, #1
ldr r0, =Num_played
strb r1, [r0]
ldmfd r13!, {r0, r1, pc}
Ta funkcija je za samo igranje tonov. Potek le te je razložen zgoraj v točki 3.
BUZZ:
stmfd r13!, {r0, r1, r2, r3, r4, r14}
/* start timer for buzz */
ldr r3, =TC1_BASE
mov r0, #0b0101 /*TC_CLKEN,TC_SWTRG*/
ldr r1, [r3, #TC_SR] /* READ TC_SR REGISTER FOR THE BIT 4 RESET */
str r0, [r3, #TC_CCR]
BACK:
bl BUZZER_ON
bl LED_ON
mov r0, r2
bl DELAY_TC0
bl BUZZER_OFF
bl LED_OFF
mov r0, r2
bl DELAY_TC0
ldr r1, [r3, #TC_SR]
tst r1, #1 << 4 /* CPCS Flag ?*/
beq BACK
mov r0, #4000
bl DELAY_TC0
ldmfd r13!, {r0, r1, r2, r3, r4, r14}
Je preprosta funkcija, ki izpiše sporočilo, zapisano v pomnilniku.
COMMAND_HELP:
stmfd r13!, {r0, r14}
ldr r0, =help_msg
bl SNDS_DEBUG
ldmfd r13!, {r0, pc}
Je funkcija, ki shrani nazandnje zaigrano melodijo. Najprej izpiše sporočilo. Nato pa prepiše znake zapisanje v pomnilniku pod imenom Current_melody v pomnilnik pod imenom Saved_melody.
COMMAND_SAVE:
stmfd r13!, {r0, r1, r2, r3, r14}
ldr r0, =save_msg
bl SNDS_DEBUG
ldr r0, =Saved_melody
ldr r1, =Current_melody
mov r2, #0
SAVE_BACK:
ldrb r3, [r1]
strb r3, [r0]
strb r2, [r1]
add r0, r0, #1
add r1, r1, #1
cmp r3, #0
bne SAVE_BACK
ldr r0, =Num_saved
ldr r1, =Num_played
ldrb r3, [r1]
strb r2, [r1]
strb r3, [r0]
ldmfd r13!, {r0, r1, r2, r3, pc}
Je funkcija, ki gre čez znake zapisane v pomnilniku pod imenom Saved_melody in jih zaigra.
COMMAND_PLAY:
stmfd r13!, {r0, r1, r2, r14}
ldr r0, =Saved_melody
ldr r1, =Num_saved
ldrb r1, [r1]
COMMAND_PLAYback:
subs r1, r1, #1
blt COMMAND_PLAYend
ldrb r2, [r0]
add r0, r0, #1
bl NOTE_FREQ
bl BUZZ
b COMMAND_PLAYback
COMMAND_PLAYend:
bl BUZZER_OFF
ldmfd r13!, {r0, r1, r2, pc}
Je funkcija, ki pobriše znake zapisane pod imenom Saved_melody.
COMMAND_CLEAR:
stmfd r13!, {r0, r1, r2, r3, r14}
ldr r3, =Num_saved
ldrb r1, [r3]
ldr r0, =Saved_melody
mov r2, #0
COMMAND_CLEARback:
subs r1, r1, #1
strb r2, [r0, r1]
bhi COMMAND_CLEARback
strb r2, [r3]
ldmfd r13!, {r0, r1, r2, r3, pc}
Zaključek izvajanja programa.
COMMAND_QUIT:
b _wait_for_ever