MoinMoin, ich hab ein kleines Musikinstrument (nach)gebastelt.
Ich hab ein paar alte 3,5" Diskettenlaufwerke in die Finger bekommen und
bin nun dabei, ihnen ein neues Leben als Musikinstrument zu verschaffen.
Einfach mal Floppy Music bei Youtube eingeben, da findet ihr schon was.
Den Code samt Kommentaren wollt ich euch mal präsentieren, evtl habt ihr
ja mal Lust und die Möglichkeit den Code aufzuspielen und so ein
Laufwerk anzuschließen um euch die etwas wirre "Melodie" anzuhören =)
Weiter Funktionen sind geplant, zb Steuerung per Konsole, evtl sogar
MIDI, mal sehen ob ichs hinbekomme, das Protokoll umzusetzen.
Für Kommentare oder Verbesserungsvorschläge bin ich immer gern offen.
MfG Chaos
1
/*
2
* FloppyMusik.c
3
*
4
* Created: 11.07.2014 08:09:28
5
* Author: chaoskind
6
* Programm zur Steuerung von 3,5" Floppy Laufwerken, um Töne zu erzeugen
7
*
8
*
9
* Pinbelegung(Active Low) 0/1
10
*
11
* PD1 = Floppy An/Aus
12
* PD2 = Diskmotor An/Aus
13
* PD3 = Richtung Lesekopf Vor/Zurück
14
* PD4 = Schritt
15
*/
16
17
18
19
#define F_CPU 8000000UL
20
21
22
23
#define NeuerTon 1
24
#define TonAus 2
25
#define Tonaenderung 3
26
27
#define Floppy 1
28
#define Motor 2
29
#define Richtung 3
30
#define Schritt 4
31
32
#define vor 8
33
34
#include<avr/io.h>
35
#include<avr/interrupt.h>
36
37
voidInit(void);
38
39
volatileuint8_tSysTickCnt=0;
40
volatileuint8_tSchrittzaehler=0;
41
42
volatileuint16_tTonhoehe=0;//Tonhöhen müssen noch definiert und errechnet werden
43
volatileuint16_tSysTick_ms=0;
44
45
46
ISR(TIMER1_COMPA_vect)
47
{
48
uint8_tISRArbeitsreg=0;
49
50
ISRArbeitsreg=PORTD&vor;//Aktuellen Zustand einlesen und prüfen in welche Richtung der letzte Schritt ging
51
52
if(ISRArbeitsreg==vor)
53
{
54
Schrittzaehler++;
55
if(Schrittzaehler>=70)//um nicht in den Anschlag zu fahren, nach 70 Schritten Richtung wechseln
56
{
57
PORTD&=~(1<<Richtung);
58
Schrittzaehler=0;
59
}
60
else{}
61
}
62
63
else
64
{
65
Schrittzaehler++;
66
if(Schrittzaehler>=70)
67
{
68
PORTD|=(1<<Richtung);
69
Schrittzaehler=0;
70
}
71
else{}
72
}
73
74
PORTD&=~(1<<Schritt);//Schritt ausführen
75
//PORTD &= ~(1 << Floppy);
76
//PORTD |= (1 << Floppy);
77
PORTD|=(1<<Schritt);
78
}
79
80
ISR(TIMER0_COMP_vect)
81
{
82
uint8_tISRArbeitsreg=0;
83
ISRArbeitsreg=SysTickCnt;//Drei Durchläufe mit Vorteiler 8 + 232 Ticks ergibt eine ms
84
85
ISRArbeitsreg++;
86
87
if(ISRArbeitsreg==3)
88
{
89
OCR0=232;
90
}
91
92
if(ISRArbeitsreg==4)
93
{
94
SysTick_ms++;
95
ISRArbeitsreg=0;
96
}
97
98
else
99
{
100
OCR0=255;
101
}
102
103
SysTickCnt=ISRArbeitsreg;
104
}
105
106
107
108
intmain(void)
109
{
110
Init();
111
112
while(1)
113
{
114
switch(SysTick_ms)
115
{
116
case200:OCR1A=10000;break;
117
case488:OCR1A=8000;break;
118
case732:OCR1A=6000;break;
119
case1038:OCR1A=4000;break;
120
case1453:OCR1A=2000;break;
121
case2145:OCR1A=1000;break;
122
case2256:OCR1A=15555;break;
123
case2565:OCR1A=13555;break;
124
case2900:OCR1A=6443;break;
125
case3131:OCR1A=3452;break;
126
case3333:OCR1A=13431;break;
127
case3676:OCR1A=33333;break;
128
case4000:OCR1A=13333;break;
129
case4943:OCR1A=1532;break;
130
case5924:OCR1A=4245;break;
131
case8424:OCR1A=1200;break;
132
case8600:TCCR1B&=~(1<<CS01);break;
133
case8800:TCCR1B|=(1<<CS01);break;
134
case9000:OCR1A=980;break;
135
case9200:OCR1A=960;break;
136
case9400:TCCR1B&=~(1<<CS01);break;
137
case9600:TCCR1B|=(1<<CS01);break;
138
case9800:OCR1A=900;break;
139
case10000:OCR1A=890;break;
140
case10500:TCCR1B&=~(1<<CS01);break;
141
case10800:TCCR1B|=(1<<CS01);break;
142
case12000:OCR1A=860;break;
143
case12500:OCR1A=850;break;
144
case13000:OCR1A=840;break;
145
case13500:OCR1A=830;break;
146
}
147
148
if(SysTick_ms>=14000)
149
{
150
SysTick_ms=0;
151
}
152
153
154
/*if (SysTick_ms - SystickAlt >= 250)
155
{
156
OCR1A -= 5;
157
SystickAlt = SysTick_ms;
158
}*/
159
160
}
161
}
162
163
164
165
voidInit(void)
166
{
167
DDRD=0b00011110;
168
PORTD=0b00011100;
169
170
171
172
OCR1A=900;
173
TCCR1B=(1<<CS11)|(1<<WGM12);//Timer1 im CTC-Modus(Clear Timer on Compare match) Vorteiler 8
174
175
TCCR0=(1<<WGM01)|(1<<CS01);//Timer0 CTC, für Systick
176
OCR0=255;
177
178
TIMSK=(1<<OCIE1A)|(1<<OCIE0);//nach Timerablauf IRQ auslösen, in dem ein Floppyschritt gemacht wird (bestimmt die Tonhöhe)
j. t. schrieb:> Ich hab ein paar alte 3,5" Diskettenlaufwerke in die Finger bekommen und> bin nun dabei, ihnen ein neues Leben als Musikinstrument zu verschaffen.
Gähn, das gabs schon in den '70ern.
> Einfach mal Floppy Music bei Youtube eingeben, da findet ihr schon was.
Das gabs damals noch nicht.
j. t. schrieb:> Für Kommentare oder Verbesserungsvorschläge bin ich immer gern offen.
Was hälst du von einem Midi In? Dann wäre das etwas universeller und du
könntest leichter mehrstimmige Orchester aufbauen. Das ist auf der
Hardware Seite nur der Optokoppler mit Pullup und auf der Software Seite
ein Midi Receiver per UART.
@ j. t. (chaoskind)
>Den Code samt Kommentaren wollt ich euch mal präsentieren, evtl habt ihr>ja mal Lust und die Möglichkeit den Code aufzuspielen und so ein>Laufwerk anzuschließen um euch die etwas wirre "Melodie" anzuhören =)
Hab kein Floppy.
>Für Kommentare oder Verbesserungsvorschläge bin ich immer gern offen.
Dein Code ist arg BASCOM-artig.
Den SWITCH packt man besser in eine gescheite Tabelle und arbeitet die
dann über eine kleine Funktion ab. Das ist deutlich kompakter und besser
erweiterbar bzw. man kann mit neuen Tabellen neue Musik machen.
hinz schrieb:> j. t. schrieb:>> Ich hab ein paar alte 3,5" Diskettenlaufwerke in die Finger bekommen und>> bin nun dabei, ihnen ein neues Leben als Musikinstrument zu verschaffen.>> Gähn, das gabs schon in den '70ern.>>>> Einfach mal Floppy Music bei Youtube eingeben, da findet ihr schon was.>> Das gabs damals noch nicht.>> ich hab ein kleines Musikinstrument (nach)gebastelt.
Ich hab ja nie behauptet, etwas völlig neues erfunden zu haben *gg
@Matthias:
Ja das mit dem Midi ist auf weite Sicht geplant, wie schon erwähnt,
jedoch muss ich mich erstmal mit dem Midiprotokoll auseinander setzen,
kennst du evtl eine gute Bibliothek? oder muss man das auch komplett
selbst umsetzen?
@Falk:
Das ist lustig, mit BASCOM hab ich noch nie zu tun gehabt. Wie würde man
das in C denn mit so einer Tabelle machen? mit nem Array?
btw n paar von den Floppys hab ich hier noch rumfliegen, falls du eins
haben willst, schreib mir ne PN =)
An Schaltplan ist da nicht viel. Nur der Atmega32 in
Standardbeschaltung, und die 4 Pins vom Floppy. Welche da sind Floppy
an/aus an Pin 12 der geht an PD1(man kann ihn auch dauerhaft auf Masse
legen), dann der "Scheibendrehermotor" an/aus auf Pin 16, welcher an PD2
kommt(diesen kann man auch komplett weglassen), an Pin18 ist der
Directionpin, um zu bestimmen in welche Richtung der Lesekopf sich
bewegt, der geht an PD3. Und zu guter Letzt der Steppin auf Pin 24 kommt
auf PD4. Wobei der Steppin laut nem Pinbelgungsbildchen eigl auf Pin22
sein sollte, was erst für ein wenig Verwirrung sorgte, aber nach ein
wenig herumprobieren stellte sich heraus, das er zumindest bei meinem
Floppy auf Pin24 sitzt.
MfG chaos
@ j. t. (chaoskind)
>das in C denn mit so einer Tabelle machen? mit nem Array?
Ja.
Es fehlt bei deiner INIT noch ein INIt des Floppy. Denn wie willst du
sonst sicher sein, dass du den Stepper nicht über den Anschlag fährst?
Die Ausgangslage ist im allgemeinen unbekannt!
Der Puls für deinen Stepper erscheint mir etwas gering, das sind geade
mal 2 AVR Takte! Ich weiß nicht welche minimale Pulsbreiten die Floppys
brauchen, meine aber etwas im µs Bereich mal gelesen zu haben.
Für eine 1ms Zeitbasis macht du ganz schöne Handstände. Das geht
deutlich einfacher, entweder mit CTC DIREKT und dem passenden
Reload-Wert oder bei komischen Quarzfrequenzen mit CTC und Timer 1.
#define Floppy 1
#define Motor 2
#define Richtung 3
#define Schritt 4
#define vor 8
DAS ist nicht gut! Eine Mischung aus Bitmasken und Bitnummer! Entscheide
dich für EINE Variante! Siehe
http://www.mikrocontroller.net/articles/Bitmanipulation#Bitmanipulation_beim_MSP430
Naja, kann man so machen, dann muss man aber immer sowas schreiben..
@ Falk:
Dank dir erstmal für deine Antwort. Aber das mit dem 1ms_Tick hab ich
zuerst versucht mit dem Timer1 mitzulösen, übers OCR1B, jedoch wurd da
nie mein Wert erreicht, da ich im OCR1A(frequenzbestimmendes) noch
kleinere Werte hatte, und somit der Timer immer schon lange vor der ms
zurückgesetzt wurde.
Und mir ist nichts anderes eingefallen, um auf 1000 Ticks zu
kommen.(ergibt ja die Millisekunde, bei prescaler 8). Mit höherem
Prescaler würde ich immer auf krumme Werte kommen, was ja auch hieße,
das ich jeden so und sovielten Durchlauf mein OCR-Wert ändern müsste?
Der Stepperpuls scheint aber lang genug zu sein, zumindest macht er
anstandslos seine 70 Schritte in jede Richtung ohne in die Anschlage zu
fahren, nur bei höheren Frequenzen scheint er Schritte zu verlieren, da
fährt er dann teilweise nur in eine Richtung, oder auch mal garnicht.
Evtl liegt das dann aber doch an der Pulslange, ich werd mal versuchen
damit ein wenig herumzuspielen.
@Scanner:
Das ist natürlich die deluXe Variante *gg
P.S.
Könntest du mir evtl auch noch mal erläutern, warum das so schlimm ist,
Bitmasken und Nummern gemeinsam zu verwenden? Die Bitmasken brauche ich
doch für die if-abfrage, ich prüfe doch einfach darauf ob das Bit
gesetzt oder nicht, und wenn es gesetzt ist gibt das einen Wert/Bitmaske
auf den ich prüfen kann. Die Bitnummern brauche ich aber um die Pins
anzusteuern?
Heißt das, ich "soll" statt "#define x 8" "#define x 3"
und statt "if (wasimmer == x(8)" "if (wasimmer > x(3)" schreiben? Oder
wie kann ich in der Abfrage schieben? Das ist mir in dem Artikel
irgendwie nicht klar geworden.
vielen Dank schonmal und mit freundlichen Grüßen,
Chaos
@ j. t. (chaoskind)
>Könntest du mir evtl auch noch mal erläutern, warum das so schlimm ist,>Bitmasken und Nummern gemeinsam zu verwenden?
Weil man damit durcheinander kommt.
> Die Bitmasken brauche ich>doch für die if-abfrage, ich prüfe doch einfach darauf ob das Bit>gesetzt oder nicht,
Gut.
> und wenn es gesetzt ist gibt das einen Wert/Bitmaske>auf den ich prüfen kann. Die Bitnummern brauche ich aber um die Pins>anzusteuern?
das kann man genausogut mit Bitmasken machen, es sind praktisch IMMER
Bitmasken. Siehe mein Artikel oben.
die Bitnummern sind eher ein historisches Erbstück aus den Atmel
Headerfiles, die sowohl für Assembler als auch C gemacht sind. Und im
Assembler gibt es spezielle Befehle, die eben die Bitnummern brauchen.
aus der Bitnummer kann man einfach eine Bitmaske machen
Bitmaske = (1<< bitnummer).
der umgekehrte Weg ist deutlich aufwändiger, wenn gleich mit einem Makro
lösbar.
Heißt das, ich "soll" statt "#define x 8" "#define x 3"
???
Schreibe so
1
#define MeinPinMotor (1<<PD3)
Das ist SEHR gut lesbar und direkt verständlich!
>und statt "if (wasimmer == x(8)" "if (wasimmer > x(3)" schreiben?
Hä?
Einfach immer so.
1
PORTD|=MeinPinMotor;// Pin setzen
2
PORTD&=~MeinPinMotor;// Pin loeschen
Ich mach es oft sogar noch einfacher und lesbarer.
1
#define MOTOR_EIN PORTD |= (1<<PD3);
2
#define MOTOR_AUS PORTD &= ~(1<<PD3);
3
4
5
// im Programm
6
7
MOTOR_EIN
8
9
MOTOR_AUS
Das ist ohne Kommentare voll selbsterklärend.
>wie kann ich in der Abfrage schieben? Das ist mir in dem Artikel>irgendwie nicht klar geworden.
Schade. Dann lies den Artikel noch mal.
Mehr als dort steht kann ich auch nicht sagen.
Diese Variante gefällt mir sehr gut =)
#define MOTOR_EIN PORTD |= (1<<PD3);
#define MOTOR_AUS PORTD &= ~(1<<PD3);
wobei es doch von der Funktionaltiät (sehr wohl aber von der Lesbarkeit)
her keinen Unterschied macht, ob ich deine Variante wähle oder:
#define Motor 3 und dann zum Bit setzen "PORTD |= (1<<Motor);" und
zum löschen "PORTD &= ~(1<<Motor);" schreibe?
PD3 ist doch irgendwie als 3 definiert, wenn ich mir recht entsinne?
aber mir ging es hauptsächlich darum dass ich doch:
#define vor 8 definiere um abzufragen, ob der "Lesekopfrichtungspin"
gesetzt ist, oder nicht.
1
ISR(TIMER1_COMPA_vect)
2
{
3
uint8_tISRArbeitsreg=0;
4
5
ISRArbeitsreg=PORTD&vor;//Aktuellen Zustand einlesen und prüfen in welche Richtung der letzte Schritt ging
6
7
if(ISRArbeitsreg==vor)
8
{
9
Schrittzaehler++;
10
if(Schrittzaehler>=70)//um nicht in den Anschlag zu fahren, nach 70 Schritten Richtung wechseln
11
{
12
PORTD&=~(1<<Richtung);
13
Schrittzaehler=0;
14
}
ich lade in das Arbeitsreg den Zustand von PORTD mit "vor" also 8 (oder
ganz auf den Punkt gebracht mit der Wertigkeit meines Pins fürs Richtung
wählen) ver-und-et. Wie kann ich das ganze denn mit der Abfrage auf 3,
also die Bitnummer machen?
Ich hoffe mein Problem ist nun ein kleines bischen klarer geworden =)
@ j. t. (chaoskind)
>Diese Variante gefällt mir sehr gut =)
schön.
>wobei es doch von der Funktionaltiät (sehr wohl aber von der Lesbarkeit)>her keinen Unterschied macht, ob ich deine Variante wähle oder:
Sicher, aber darum geht es doch! Ich kann auch in Assembler oder gar
Maschinencode ein und die selben, funktional IDENTISCHEN Programme
schreiben, aber die Frage ist, wie ist es einfacher und weniger
fehleranfällig?
>#define Motor 3 und dann zum Bit setzen "PORTD |= (1<<Motor);" und>zum löschen "PORTD &= ~(1<<Motor);" schreibe?
Klar, aber das ist mehr Schreibarbeit.
>#define vor 8 definiere um abzufragen, ob der "Lesekopfrichtungspin">gesetzt ist, oder nicht.
sicher, aber das kann man auch so schreiben.
1
#define Richtung (1<<3)
2
3
ISR(TIMER1_COMPA_vect)
4
{
5
6
if(PORTD&Richtung)// Richtung plus?
7
{
8
Schrittzaehler++;
9
if(Schrittzaehler>=70)//um nicht in den Anschlag zu fahren, nach 70 Schritten Richtung wechseln
10
{
11
PORTD&=Richtung;
12
Schrittzaehler=0;
13
}
>ich lade in das Arbeitsreg den Zustand von PORTD mit "vor" also 8 (oder
Laden tut man in C gar nix, man liest einfach Variablen. Auch wenn diese
Variablen in Wahrheit Hardwareregister sind.
"Laden" ist eine Sprechweise aus Assembler (lade Register x mit y)
>ganz auf den Punkt gebracht mit der Wertigkeit meines Pins fürs Richtung>wählen) ver-und-et. Wie kann ich das ganze denn mit der Abfrage auf 3,>also die Bitnummer machen?
Siehe oben.
>Ich hoffe mein Problem ist nun ein kleines bischen klarer geworden =)
Ich wiederhole mich. Lies den Artikel Bitmanipulation und denk
drüber nach. Es steht ALLES drin!
Eine Frage noch zur Zeitbasis: Wie würdest du das lösen, wenn der Timer1
quasi blockiert ist. Die Timer0/2 sind ja nur 8bit breit, da sind beim
so praktischen 8er prescaler (wenn man auf 8MHz läuft) ja mehrere
Überläufe nötig, um auf eine Millisekunde zu kommen.
Wenn man andersrum OCR1B nutzt, man könnte da ja in dem
Frequenzbestimmenden OCR1A ja jede ms 1000 abziehen, hätte aber das
Gefrickel ja dann dort. Wobei bei rechter Betrachtung ist das deutlich
weniger davon.
@ j. t. (chaoskind)
>Eine Frage noch zur Zeitbasis: Wie würdest du das lösen, wenn der Timer1>quasi blockiert ist.
Ist er das wirklich?
> Die Timer0/2 sind ja nur 8bit breit, da sind beim>so praktischen 8er prescaler (wenn man auf 8MHz läuft) ja mehrere>Überläufe nötig, um auf eine Millisekunde zu kommen.
Kann man machen. Man kann aber auch mit einem Prescaler > 8 arbeiten.
Und bei diesem ehrer einfachen Beispiel muss es wahrscheinlich nicht
EXAKT 1ms sein, irgendwas mit ein par % Abweichung geht auch.
Oder nimm einen moderneren AVR ala ARmega88, der kann CTC auch auf
Timer0.
Warum sollte Timer1 blockert sein?
Er ist natürlich nicht wirklich blockiert, ich kann ihn ja benutzen.
Was ich meine ist:
1
ISR(TIMER1_COMPA_vect)
2
{
3
4
5
if(PORTD&Rueckwaerts)//Aktuellen Zustand einlesen und prüfen in welche Richtung der letzte Schritt ging
6
{
7
Schrittzaehler++;
8
if(Schrittzaehler>=70)//um nicht in den Anschlag zu fahren, nach 70 Schritten Richtung wechseln
9
{
10
KopfVor
11
Schrittzaehler=0;
12
}
13
else{}
14
}
15
16
else
17
{
18
Schrittzaehler++;
19
if(Schrittzaehler>=70)
20
{
21
KopfRueck
22
Schrittzaehler=0;
23
}
24
else{}
25
}
26
27
SchrittLow
28
SchrittHigh
29
}
30
31
ISR(TIMER1_COMPB_vect)
32
{
33
SysTick_ms++;
34
OCR1A-=1000;
35
}
Solange OCR1A größer als 1000 ist, ich den Kopf also langsamer als 1KHz
ansteuere, ist alles gut. Bspw ist OCR1A 1500 und OCR1B 1000, dann wird
nach 1000 Ticks ISR (TIMER1_COMPB_vect) angesprungen,OCRA dann auf 500
sein, nach 500 weiteren Ticks wird dann ISR (TIMER1_COMPA_vect)
angesprungen, der Kopf also nach 1,5ms bewegt... und dann kommt die Zeit
auch schon wieder aus dem Tritt...
Ich denke du hast Recht, ich verzichte einfach auf die Genauigkeit und
nimm den nächst größeren Prescaler...
Und stelle fest, ich brauch garnicht verzichten, Prescaler 64 bis 125
tuts auch auf Timer0, der kann auch CTC.
Also nochmal vielen Dank für deine Mühe
Chaos
P.S. Gibt es in C eigentlich sowas wie nop? Ich weiß eigentlich soll man
in einer ISR nicht warten, aber ich würd gern ca 10 nop´s machen, um
eine Pulslänge > 1µs zu bekommen. Oder "darf" man für sowas eine kleien
Warteschleife einfügen?
@ j. t. (chaoskind)
>ISR (TIMER1_COMPB_vect)>{> SysTick_ms ++;> OCR1A -= 1000;>}
Das muss
OCR1A += 1000;
heißen. Eben mit diesem "Trick" kann man mit EINEM, kontinuierlich
durchlaufenden Timer1 und den zwei COMPA/B Interrupts ZWEI vollkommen
unabhängige Frequenzen erzeugen!
Also KEIN CTC Mode sondern Normalmodus und das Nachstellen von OCR1A/B
wie oben!
>Solange OCR1A größer als 1000 ist, ich den Kopf also langsamer als 1KHz>ansteuere, ist alles gut. Bspw ist OCR1A 1500 und OCR1B 1000, dann wird>nach 1000 Ticks ISR (TIMER1_COMPB_vect) angesprungen,OCRA dann auf 500>sein, nach 500 weiteren Ticks wird dann ISR (TIMER1_COMPA_vect)>angesprungen, der Kopf also nach 1,5ms bewegt... und dann kommt die Zeit>auch schon wieder aus dem Tritt...
siehe oben.
>Und stelle fest, ich brauch garnicht verzichten, Prescaler 64 bis 125>tuts auch auf Timer0, der kann auch CTC.
Siehst du ;-)
>P.S. Gibt es in C eigentlich sowas wie nop?
Nicht direkt, das ist immer eine compiler- und prozessorabhängige
Erweiterung (Funktion oder Makro). Bei AVR GCC ist es schon drin.
nop();
> Ich weiß eigentlich soll man>in einer ISR nicht warten, aber ich würd gern ca 10 nop´s machen, um>eine Pulslänge > 1µs zu bekommen. Oder "darf" man für sowas eine kleien>Warteschleife einfügen?
Ja. Man darf auch 100ms warten, wenn es sowieso nix besseres zu tun gibt
und der Prozessor sich nicht sinnlos ausbremst. Warteschleifen unter
100us sind mit so einem Delay deutlich einfacher und besser zu machen.
Dafür gibt es im avr gcc auch schon fertige Funktionen.
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Warteschleifen_.28delay.h.29
So nach deinen Verbesserungsvorschlägen sieht mein Code nun so aus.
Der Tipp mit der Verlängerung der Pulslänge war auch sehr gut, er
verschluckt nun fast keine Schritte mehr.
Warum man 2 unabhängige Frequenzen erhält, wenn man in der OCR1B ISR
1000 zum OCR1A addieren soll, ist mir aber irgendwie noch nicht ganz
klar....
und zu den Arrays muss ich mich auch erstmal einlesen....
für das nop(); muss man wohl noch ne Bibliothek einbinden? Dann lass
ichs lieber bei meiner Schleife.
Also heir erstmal der Code bis jetzt:
1
/*
2
* FloppyMusik.c
3
*
4
* Created: 11.07.2014 08:09:28
5
* Author: chaoskind
6
* Programm zur Steuerung von 3,5" Floppy Laufwerken, um Töne zu erzeugen
7
*
8
*
9
* Pinbelegung(Active Low) 0/1
10
*
11
* PD1 = Floppy An/Aus
12
* PD2 = Diskmotor An/Aus
13
* PD3 = Richtung Lesekopf Vor/Zurück
14
* PD4 = Schritt
15
*/
16
17
18
19
#define F_CPU 8000000UL
20
21
#define Floppy 1
22
#define Motor 2
23
#define Richtung 3
24
#define Schritt 4
25
26
#define Rueckwaerts (1<<3)
27
28
#define FloppyAn PORTD &= ~(1 << Floppy);
29
#define FloppyAus PORTD |= (1 << Floppy);
30
31
#define MotorAn PORTD &= ~(1 << Motor);
32
#define MotorAus PORTD |= (1 << Motor);
33
34
#define KopfVor PORTD &= ~(1 << Richtung);
35
#define KopfRueck PORTD |= (1 << Richtung);
36
37
#define SchrittLow PORTD &= ~(1 << Schritt); //einmal Low High um einen Schritt auszuführen
38
#define SchrittHigh PORTD |= (1 << Schritt);
39
40
#define TonAus TCCR1B &= ~(1<<CS01);
41
#define TonAn TCCR1B |= (1<<CS01);
42
43
#define Grundstellung DDRD = 0b00011110;
44
45
46
47
#include<avr/io.h>
48
#include<avr/interrupt.h>
49
50
voidInit(void);
51
voidStartposition(void);
52
voidWildeTonfolge(void);
53
54
55
volatileuint8_tSchrittzaehler=0;
56
volatileuint8_tMinutenzaehler=0;
57
58
volatileuint16_tSysTick_ms=0;
59
60
61
ISR(TIMER1_COMPA_vect)
62
{
63
uint8_ti=0;
64
65
if(PORTD&Rueckwaerts)//Aktuellen Zustand einlesen und prüfen in welche Richtung der letzte Schritt ging
66
{
67
Schrittzaehler++;
68
if(Schrittzaehler>=60)//um nicht in den Anschlag zu fahren, nach 60 Schritten Richtung wechseln (von Anschlag zu Anschlag etwa 85)
69
{
70
KopfVor
71
Schrittzaehler=0;
72
}
73
else{}
74
}
75
76
else
77
{
78
Schrittzaehler++;
79
if(Schrittzaehler>=60)
80
{
81
KopfRueck
82
Schrittzaehler=0;
83
}
84
else{}
85
}
86
87
SchrittLow
88
while(i<10)
89
{
90
i++;
91
}
92
SchrittHigh
93
}
94
95
ISR(TIMER0_COMP_vect)
96
{
97
SysTick_ms++;
98
if(SysTick_ms==60000)
99
{
100
Minutenzaehler++;
101
SysTick_ms=0;
102
}
103
}
104
105
106
107
intmain(void)
108
{
109
Init();
110
111
while(1)
112
{
113
WildeTonfolge();
114
}
115
}
116
117
118
119
voidInit(void)
120
{
121
Grundstellung
122
FloppyAn
123
124
OCR1A=2000;
125
OCR0=125;
126
TCCR0=(1<<WGM01)|(1<<CS01)|(1<<CS00);//Timer0 CTC Vorteiler 64, für Systick 125Ticks = 1ms
127
TIMSK=(1<<OCIE1A)|(1<<OCIE0);
128
129
sei();
130
131
Startposition();
132
133
cli();
134
TCCR1B=(1<<CS11)|(1<<WGM12);//Timer1 im CTC-Modus(Clear Timer on Compare match) Vorteiler 8
135
sei();
136
}
137
138
139
140
voidStartposition(void)
141
{
142
uint8_ti=0;
143
uint8_tSysTick_alt=0;
144
145
while(SysTick_ms<100)//100ms lang 1Schritt/ms um sicher an den Endanschlag zu fahren
146
{
147
KopfRueck
148
149
if(SysTick_ms-SysTick_alt)//wenn eine ms vergangen ist, nächsten Schritt machen
150
{
151
SchrittLow
152
while(i<10)//etwas warten, damit Pulslänge > 1µs
153
{
154
i++;
155
}
156
SchrittHigh
157
}
158
159
SysTick_alt=SysTick_ms;
160
}
161
162
if(SysTick_ms>100)//weitere 15ms 1S/ms um auf definierte Startposition zu kommen.
@ j. t. (chaoskind)
>Der Tipp mit der Verlängerung der Pulslänge war auch sehr gut, er>verschluckt nun fast keine Schritte mehr.
Den Tip hast du aber maximal falsch umgesetzt. Deine Schleife schmeißt
dir der Optimierer raus. Nimm _delay_us().
>Warum man 2 unabhängige Frequenzen erhält, wenn man in der OCR1B ISR>1000 zum OCR1A addieren soll, ist mir aber irgendwie noch nicht ganz>klar....
Ist doch einfach. Der TIMER1_COMPA/B Interrupt wird ausgelöst, wenn
TIMER1 == OCR1A ist. Wenn man dann in der ISR OCR1A um einen Betrag X
erhöht (nämlich durch OCR1A += X;), wird exakt X Takte nach dem ersten
Aufruf die ISR wieder aufgerufen. Und das geht lückenlos, auch beim
Überlauf des Timers.
>für das nop(); muss man wohl noch ne Bibliothek einbinden? Dann lass>ichs lieber bei meiner Schleife.
Ist Murks und funktioniert nur, wenn die Optimierungsstufe auf AUS (-0s)
seht.
1
if(SysTick_ms-SysTick_alt)//wenn eine ms vergangen ist, nächsten Schritt machen
2
{
3
SchrittLow
4
while(i<10)//etwas warten, damit Pulslänge > 1µs
5
{
6
i++;
7
}
8
SchrittHigh
9
}
Ist eine sehr ungünstige, um nicht zu sagen irreführende Auswertung!
Schreib DIREKT was du meinst, das ist genauso schnell!
Ich hab mal bissel aufgeräumt, siehe Anhang. Dort sind lange Quelltexte
besser aufgehoben.
Makros sollte man immer GROSS schreiben, damit man sie als solche
erkennt. Siehe
Strukturierte Programmierung auf Mikrocontrollern
> Ist doch einfach. Der TIMER1_COMPA/B Interrupt wird ausgelöst, wenn> TIMER1 == OCR1A ist. Wenn man dann in der ISR OCR1A um einen Betrag X> erhöht (nämlich durch OCR1A += X;), wird exakt X Takte nach dem ersten> Aufruf die ISR wieder aufgerufen. Und das geht lückenlos, auch beim> Überlauf des Timers.
Aber was ist denn wenn wir angenommen wenn wir im OCRA 1000 stehen
haben, und im OCRb 2100. Dann wird nach 1000 Schritten OCRA auf 2000
gesetzt. Nach weiteren 1000 wirds auf 3000 gesetzt. Dann geht das ganze
doch aber ganicht im CTC? sonst wird doch das erste mal bis 1000, das
nächste mal bis 2000 gezählt. Insgesamt haben wir nun bis 3000 gezählt,
der Zähler steht auf 2000, wird nun auf 3000 gesetzt. Nun erreicht der
Zähler nach 2100 weiteren Ticks 2100 (wir hatten nun 5100 Ticks) und
erst jetzt kann doch OCRB das erste mal zuschlagen? Obwohl wir also OCRB
auf 2100 haben, löst er das erst mal erst nach 5100 Ticks aus. Und dann
würd ja der Timer doch durch OCRB auf null gesetzt, Sprich das nächste
auslösen von OCRA dauert nicht nur 1000 länger als das vorherige mal,
sondern 1100?
@j. t. (chaoskind)
>Aber was ist denn wenn wir angenommen wenn wir im OCRA 1000 stehen>haben, und im OCRb 2100. Dann wird nach 1000 Schritten OCRA auf 2000>gesetzt. Nach weiteren 1000 wirds auf 3000 gesetzt. Dann geht das ganze>doch aber ganicht im CTC?
Nein, aber das muss es auch gar nicht. Schrieb ich bereits.
>erst jetzt kann doch OCRB das erste mal zuschlagen? Obwohl wir also OCRB>auf 2100 haben, löst er das erst mal erst nach 5100 Ticks aus.
NEIN! Du vergisst den mormalen Timer. Vergiss hier CTC!
Der Timer läuft dauerhaft durch von 0-0xFFFF und so weiter. Die beiden
OCR1A/B setzen sich quasi ihr eigenes Ziel immer um einen Betrag X zum
akuellen Ziel. Je größer die Schrittweite, umso größer die Frequenz.
AAACHSO, das hatte ich dann durcheinander bekommen, weil du sagtest, man
könne ja auch den Timer2 (beim Mega88) nehmen, da der auch CTC kann.
Dann ist nun langsam alles klar. Ich hatte gestern noch erste Versuche
zum Array gemacht.
1
intmain(void)
2
{
3
uint16_tNoten[1][11]={{1000}};
4
5
uint8_ti=0;
6
uint16_tWarteReg=250;
7
8
Init();
9
10
for(i=0;i<11;i++)
11
{
12
Noten[0][i+1]=Noten[0][i]*1.083;
13
}
14
15
for(i=0;i<11;i++)
16
{
17
Noten[1][i]=Noten[0][i]*2;
18
}
19
20
Minutenzaehler=0;
21
SysTick_ms=0;
22
23
24
while(1)
25
{
26
OCR1A=Noten[0][0];
27
28
if(SysTick_ms=500)
29
{
30
OCR1A=Noten[1][1];
31
}
32
33
if(SysTick_ms=1000)
34
{
35
OCR1A=Noten[1][1];
36
}
37
38
if(SysTick_ms=1500)
39
{
40
OCR1A=Noten[1][2];
41
}
42
}
43
}
Als ich mir dann die Werte im Array angeschaut hab, hat er von [0][0] -
[1][2] alle Werte so gesetzt, wie ich mir das vorgestellt habe, aber bei
den höheren hat er gesagt, die Werte würden nicht existieren (unable to
evaluate the expression, Ungültiger Zeiger).
Und irgendwie wird die Tonhöhe auch nicht mher geändert, wenn ich den
OCR1A-Wert ändere....
Nach genauerem hinsehen hab ich festgestellt, das auch [0][11] nicht
richtig gesetzt wird. Der steht nämlich auf 2000, und der Wert von
[0][10] ist 2213, und das mal 1,083 sollte ja noch mehr sein und nicht
2000.
Dazu hab ich dann mal versucht, das i aus der Initialsierungsschleife
des Arrays kleiner zu machen. Bei 10 hat sich nichts geändert, [0][11]
war immernoch auf 2000, und bei 9 war dann der 10te Wert wie zu erwarten
0, aber[0][11] war immernoch auf 2000.
Btw kennst du, oder wer anders der mitliest, zufällig die Richtige
Formel um aus einem Ton den nächsten zu errechnen?. Ich sollte ja nach
einer Oktave bei 2000 landen....
X(n+1) = X(n) * 2 ^ n/12? ^= x(n+1) = X(n) * 1,0594?
Macht auf jeden Fall nen besseren Eindruck
x(n+1) = X(n) * 1,0594 war auch zu wenig...
Ich hab nun die math.h eingebunden, und:
for (i = 0; i < 11; i++)
{
Noten [0][i+1] = 1000 * pow(2,i/12);
}
geschrieben. Jetzt werden aber alle Werte mit 1000 geladen. Bis auf das
ominöse [0][11] das immer noch auf 2000 steht
@ j. t. (chaoskind)
>Dann ist nun langsam alles klar. Ich hatte gestern noch erste Versuche>zum Array gemacht.
Aua. Du hast ein schlechtes Konstrukt durch ein anderes ersetz. Hast du
überhaupt schon mal ein Lehrbuch zui diesem Thema befragt? Und ich mein
nicht 5 minuten die Seiten überflogen sondern mal ganze Kapitel gelesen,
drüber nachgedacht und die Beispeile nachvollzogen? Eher nicht. 8-(
j. t. schrieb:> x(n+1) = X(n) * 1,0594 war auch zu wenig...>> Ich hab nun die math.h eingebunden, und:>> for (i = 0; i < 11; i++)> {> Noten [0][i+1] = 1000 * pow(2,i/12);> }
i/12 ist eine komplizerite Schreibweise für 0
Wenn schon dann i/12.0 (und ein C Buch lesen)
Allerdings: ich würde mir eine derartige Notentabelle ehrlich gesagt auf
dem PC in Excel generieren und dann nur noch die fertigen Werte im
AVR-Programm in einer Notentabelle eintragen. Das ist einmalig zwar 10
Minuten arbeit, erspart aber dem AVR die Floating Point Rechnerei und
erleichtert auch das Erstellen von Musikstücken, wenn man in die
'Partitur' nicht irgendwelche Frequenzkennwerte eintragen muss, sondern
dort "echte" Noten einträgt.
Ob man die Umsetzung Note zu Frequenzwert mittels #define erledigt, oder
ab man dazu ein Array zu Hilfe nimmt, ist Ansichtssache. Ein
'Übersetzungsarray' hätte den Charm, das man dann in der Partitur für
die Tonhöhe nicht mehr einen uint16_t benötigt, sondern nur noch einen
uint8_t
Aber am wichtigsten scheint mir zu sein, dass du erst mal deine
C-Kenntnisse aufpolierst. Wenn du mit 90% der Möglichkeiten deiner
Programmiersprache gar nicht umgehen kannst, ist es zu früh ein Projekt
anzufangen.
@ j. t. (chaoskind)
>Nach genauerem hinsehen hab ich festgestellt, das auch [0][11] nicht>richtig gesetzt wird. Der steht nämlich auf 2000, und der Wert von>[0][10] ist 2213, und das mal 1,083 sollte ja noch mehr sein und nicht>2000.
In deinem gepostet Code wird lediglich EIN EINZIGER Wert initialisiert,
der Rest ist undefiniert!
Wi wären dir sehr verbudnen, wenn du auch den Code posten würdest, über
den du auch redestr. Siehe Netiquette!!!
>Dazu hab ich dann mal versucht, das i aus der Initialsierungsschleife>des Arrays kleiner zu machen. Bei 10 hat sich nichts geändert, [0][11]
MÜLL!!! C-Arrays werden ganz normal mit den Werten initialiseirt, die
man hinschreibt.
Wow, das klappt auf Anhieb! Danke dir Falk =).
Ich verstehe nur nicht wieso
anzahl = sizeof(Noten)/4;
du hier durch vier und nicht durch zwei teilst. Das Array hat doch 2
Elemente pro Ton?
Und ja du hast Recht, das meiste C das ich "kann", hab ich aus
irgendwelchen Tutorials zusammenüberflogen.... Die Beispiele die da drin
waren, waren meist irgendwie so abstrakt, das ich die nicht meinen
Problemen zuordnen konnte. Kannst du da evtl eines Empfehlen?
@Karl-Heinz
danke für den Hinweis i/12.0. Bei Int wird nach dem Komma einfach
abgeschnitten oder? Das erklärt dann natürlich, das überall der selbe
Wert reinkam.
j. t. schrieb:> Wow, das klappt auf Anhieb! Danke dir Falk =).> Ich verstehe nur nicht wieso>> anzahl = sizeof(Noten)/4;>> du hier durch vier und nicht durch zwei teilst. Das Array hat doch 2> Elemente pro Ton?
Ein Array-Eintrag (eine Zeile) besteht aus 2 uint16_t. Das sind in Summe
4 Bytes.
> danke für den Hinweis i/12.0. Bei Int wird nach dem Komma einfach> abgeschnitten oder?
Nein.
i ist ein int (mit einem Wert von 0 bis 11)
12 ist ein int
Also wird hier eine int DIvision gemacht. Die erzeugt keine
Nachkommastellen. Da wird also nichts abgeschnitten, weil in erster
Linie gar keine Nachkommastellen entstehen.
C-Buch!
(warum glaubt das bloss keiner? Solche Bücher haben nicht umsonst um die
200 Seiten. Die sind ja nicht nur dazu da, dass Druckerschwärze
verbaucht wird)
>Also wird hier eine int DIvision gemacht. Die erzeugt keine>Nachkommastellen. Da wird also nichts abgeschnitten, weil in erster>Linie gar keine Nachkommastellen entstehen.
Das war, was ich versuchte auszudrücken... bei 1/12 kommt 0 raus, weil
es ja mathematisch betrachtet 0.083.... sind, aber weil wir mit int
rechnen, gibt es keine Nachkommastellen(also sind sie ja quasi irgendwie
doch abgeschnitten, zumindest aus mathematischer Perspektive) also 0.
>In deinem gepostet Code wird lediglich EIN EINZIGER Wert initialisiert,>der Rest ist undefiniert!
uint16_t Noten [1][11] = {{1000}};
hiermit wird der eine Wert initialisiert,
for (i = 0; i < 11; i++)
{
Noten [0][i+1] = Noten [0][i] * 1.083;
}
und hiermit dann die restlichen Werte, achsooo zugewiesen und nicht
initialisiert, die sprachlichen Feinheiten.
Die Undefiniertheit sollte dann doch aber nach der Schleife vorbei sein?
also den Teil hab ich so aus einem Tutorial, da füllt er seine Arrays
auch per Schleife.
KarlHeinz schrieb:
>Ein Array-Eintrag (eine Zeile) besteht aus 2 uint16_t. Das sind in Summe>4 Bytes.
Ahja klar, das macht Sinn.
Was in deinem Programm auch noch nicht stimmt (stimmte)
1
uint16_tNoten[1][11]={{1000}};
2
3
...
4
Noten[0][i+1]=Noten[0][i]*1.083;
5
...
6
Noten[1][i]=Noten[0][i]*2;
wird ein Array definiert, dann wird die ANzahl der Elemente angegeben!
Ein Array
1
inta[5];
besteht aus 5 Elementen. Diese sind
1
a[0], a[1], a[2], a[3], a[4]
Zähl nach, sind genau 5 Stück.
Aber: der höchste zulässige Index in das Array ist 4 und nicht 5!
Ein Element a[5] existiert schlicht und ergreifend nicht.
Wenn du also ein
1
uint16_tNoten[1].....
hast und dann auf
1
Noten[1][.....
beschreibst, dann überbügelst du dir irgendwas im Speicher, aber sicher
nicht das Element mit dem ersten Index 1. Den das existiert schlicht und
ergreifend nicht.
Genau deshalb halte ich nicht viel von Online-Tutorien. Denn 80% von den
wichtigen Sachen stehen da nun mal nicht drinnen. Bis jetzt hab ich kaum
ein C-Tutorial gesehen, das nicht mit viel Enthusiasmus gestartet worden
wären, bei dem es dann aber nicht so gewesen wäre, dass der Autor
irgendwann aufgegeben hat, nachdem er gemerkt hat, dass sich C eben
nicht in 10 HTML Seiten erklären lässt.
Am Besten schneiden immer noch die Online-Versionen von diversen
Vorlesungsunterlagen ab. Aber auch die enthalten des öfteren Fehler bzw.
Unvollständigkeiten, die dann eben direkt in der Vorlesung angesprochen
werden und die der Studierende handschriftlich in seinen Unterlagen
ergänzt. War man aber nicht persönlich in der Vorlesung, dann bleibt man
auf den Ungenauigkeiten oder Fehlern sitzen.
den Wert für Noten[1] aus Noten[0] errechnen willst, ist meiner Meinung
nach auch mehr als fragwürdig.
Generell wäre hier für mich der perfekte Zeitpunkt, um das nächste
C-Konzept, nämlich das einer Struktur, zum Einsatz zu bringen.
Aber wie so oft: man kann natürlich nur das benutzen, was man auch
kennt. Genau deshalb ist es so wichtig, erst mal einen einigermassen
fundierten Überblick über die Möglichkeiten meiner Programmiersprache zu
haben.
Wer nur einen Hammer hat den er kennt, wird sich nun mal beim Hausbau
schwer tun.
In diesem Fall wäre das möglich, die Werte für noten[1] aus Noten[0] zu
errechnen, da eine Oktave ja jeweils eine Halbierung/Verdopplung ist.
>Generell wäre hier für mich der perfekte Zeitpunkt, um das nächste>C-Konzept, nämlich das einer Struktur, zum Einsatz zu bringen.>Aber wie so oft: man kann natürlich nur das benutzen, was man auch>kennt.
Ja da haste auch unangefochten Recht. Von Structs hab ich mal gehört,
aber kennen tu ich die nun wirklich nicht. Ich bin mal auf nem STM32F429
auf sie gestoßen, und konnte ihnen so überhaupt nichts abgewinnen. Aber
das lag glaub ich am Aufbau selbiger.
Das was ich davon verstanden hab geht wohl in die Richtung, das man ein
Struct als einen Datentyp wie bspw Int betrachten kann, nur das es in
diesem mehrere unterschiedliche Datentypen geben kann?
@ j. t. (chaoskind)
>Ja da haste auch unangefochten Recht. Von Structs hab ich mal gehört,>aber kennen tu ich die nun wirklich nicht. Ich bin mal auf nem STM32F429>auf sie gestoßen, und konnte ihnen so überhaupt nichts abgewinnen. Aber>das lag glaub ich am Aufbau selbiger.
Was zum Geier willst du mit einem 32 Bit ARM, wenn du nicht mal die
Grundlagen von C beherrschst?
Kauf dir eines der Standardbücher von C und arbeite es KOMPLETT durch!
Das kann dir kein Tutorial, Forum, STM32 oder die Mutti abnehmen!
Den gabs mal für umme mit nem Touchscreen, da kam "brauch ich" deutlich
vor "kann ich" *gg
Ich hatte gehofft, die Dinger würden sich ähnlich komfortabel in ASM
programmieren lassen wie die AVRs. Irgendwann stellte ich fest "ich muss
C lernen". nuja und immer wenn sich mal n Moment findet, bastel ich ein
wenig rum.
Eigentlich kann ich garnicht verstehen, warum ich mich so gegen ein Buch
sträube. Ich dachte ja immer, das ist völlig übertrieben mit den Büchern
bis ich dann eines Tages mal einen Tietze und Schenk in den Händen
hielt, und feststellte dass die Elektronikkenntnisse mit so einem
Schmöker rasant größer werden.
Ich vermute, ich sollte wirklcih mal über meinen Schatten springen und
mir n K&R (so heißt doch der "Tietze und Schenk" für C?) besorgen.
Sodele, ich hab noch mal ein wenig am Konzept getüftelt. Das
Tonabspielen lasse ich nun eine Funktion übernehmen, der ich die
Parameter Tonlänge, Note und Oktave übergebe.
Den Quelltext gibs als Anhang.
Nein nicht vergessen, ich hatte angefangen es gar mit nem Struct zu
versuchen.
struct AktuellerTon
{
uint8_t Laenge
uint8_t Ton
};
Und wollte dann Ton und Laenge aus unserem Array holen. Dabei kam mir
das dann aber irgendwie total unflexibel vor, zu jedem Ton auch die Zeit
festdabei zuhaben, statt die Töne einzeln gelistet, und dazu dann
beliebige Zeiten zuzugeben.
Irgendwie gefällt mir das besser, wenn ich statt nem Toene[x] einfach
Gis schreiben kann. Oder kann ich Arrays auch so benutzen das ich den
Wert von Toene[x] mit Gis aufrufen kann? wäre das dann da nächste
Konstrukt, nämlich das des Pointers? noch son den Ding, von dem ich
bisher nur hörte, aber nichts mit anzufangen wusste. Wie man so ein
Array benutzt, ist mir aus diesen abstrakten Tuts nämlich nie so ganz
klar geworden, aber nun mit einem Beispiel wo ich einen Bezug zu und
eure Hilfe hatte, ist mir klargeworden, wie man so ein Array einzusetzen
hat. Wobei es dafür sicher noch mehr Verwendungsmöglichkeiten gibt die
ich noch nicht kenne.
Ich hab die Töne nochmal ein klein wenig geändert. Sie sind nun einfach
A-Gis und der Funktion wird noch eine Variable Oktave übergeben.
void TonSpielen(uint16_t Dauer, uint16_t Ton, uint8_t Oktave)
{
Dauer = (Dauer * 10) + zeitAlt;
Ton = Ton * Oktave;