Hallo Leute,
ich steige grade von Bascom auf C um. Also alles neu. Ich programmiere
hin und wieder kleine Sachen, aber hier bin ich mit meinem Latein am
Ende.
Ich habe eine Gabellichtschranke an INT1 an einem Atmega32.
Timer1 ist so konfiguriert, dass er bei jeder negativen Flanke auslöst.
Zur Kontrolle des ganzen toogelt eine LED an Portc.0. Soweit so gut,
dieses Beispiel ist fast 1:1 aus dem Netz, ich habe es verstanden und
habe es etwas erweitert.
Ich habe eine Variable "n" erstellt, mit der ich die Impulse zählen
will. Das ganze soll dann per UART an den PC geschickt werden.
MAX232 mit Kondis ist vorhanden, echte RS232 ebenfalls. Als Oszillator
nutze ich den internen, aber für erste Test sollte es reichen.
UART ist initialisiert wie hier im Tutorial beschrieben.
Code lässt sich mit AVR Studio compilieren, aber es erfolgt keine
Ausgabe per UART. Wenn ich RX und TX verbinde empfange ich das auf der
Tastatur eingegebene Zeichen.
Frage: Was mache ich falsch?
Hier der Quellcode:
Paul schrieb:> #define BAUD 19200UL // Baudrate>> #define UBRR_VAL ((F_CPU+BAUD*8)/(BAUD*16)-1) // clever runden> #define BAUD_REAL (F_CPU/(16*(UBRR_VAL+1))) // Reale Baudrate> #define BAUD_ERROR ((BAUD_REAL*1000)/BAUD) // Fehler in Promille, 1000 => kein Fehler.>> #if ((BAUD_ERROR<900) || (BAUD_ERROR>1100))> #error Systematischer Fehler der Baudrate grösser 1% und damit zu> hoch!> #endif
Dafür bietet die avr libc direkt ein paar Makros an, welche die viele
Rechnerei für dich übernehmen, siehe [1]. Damit reduziert sich das Ganze
im Wesentlichen auf die Angabe der Baudrate.
Paul schrieb:> int n=0;
Du verändert die Variable innerhalb deiner ISR, willst aber gleichzeitig
innerhalb der Hauptschleife darauf zugreifen. Ohne die Kennzeichnung von
"volatile" wird das nicht funktionieren sobald der Optimierer sich ans
Werk macht, siehe [2].
Paul schrieb:> DDRB = 0x01; // Setup P0 as output> PORTD |= 0x08; // Activate Apullupressistor of> PD3> GICR |= (1 << INT1); // Enable INT1> MCUCR |= (1 << ISC11), (0 << ISC10); // INT1 is> executed on any edge>> sei(); // Set the I-bit in SREG
Es macht absolut keinen Sinn dieses ganze Setup in jeder Iteration der
Hauptschleife durchzuführen. Das ist etwas, dass du nur einmalig machen
musst.
Paul schrieb:> UDR = "n";
Bei "n" handelt es sich um einen String, nicht um ein Zeichen. Einzelne
Zeichen werden in C von Single Quotes umschlossen, also in etwa so:
1
UDR='c';
Dein Fehler sollte auch zu einer Compilerwarnung führen, die in etwa
lautet:
> assignment makes integer from pointer without a cast [enabled by default]
Compilerwarnungen sollte man ernst nehmen und unter allen Umständen
vermeiden.
Außerdem solltest du dich nicht blind darauf verlassen, dass du zum
aktuellen Zeitpunkt überhaupt etwas schreiben kannst und die UART
Hardware nicht noch mit etwas anderem beschäftigt ist. Hierfür gibt es
z.B. das UDRE Bit im UCSRA Register.
Bringe all das mal auf Vordermann, und poste dann bitte den Quellcode
unter Verwendung der Mittel hier im Forum ([c] Tag).
Mit freundlichen Grüßen,
Karol Babioch
[1]:
http://www.nongnu.org/avr-libc/user-manual/group__util__setbaud.html
[2]: http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_volatile
Vielen Dank für deine Antwort.
Ich habe nun die von dir vorgeschlagenen Änderungen durchgeführt.
Der Code ist nun viel übesichtlicher, was mich sehr freut :).
Nur funktionieren will er leider immer noch nicht. Das toogeln der LED
funktioniert prächtig, aber die Ausgabe per UART haut noch immer nicht
hin.
Gibt es da noch etwas was ich übersehen haben könnte?
Hier der neue Code:
1
#include<avr/io.h>
2
#include<avr/interrupt.h>
3
4
5
#define F_CPU 1000000
6
#define BAUD 19200
7
#include<util/setbaud.h>
8
9
10
11
volatileintn;
12
13
14
15
16
17
intmain(void)
18
{
19
20
21
22
DDRB=0x01;// Setup P0 as output
23
PORTD|=0x08;// Activate Apullupressistor of PD3
24
GICR|=(1<<INT1);// Enable INT1
25
MCUCR|=(1<<ISC11),(0<<ISC10);// INT1 is executed on any edge
Paul schrieb:> Gibt es da noch etwas was ich übersehen haben könnte?
Ja.
Etwas das du von BASCOM nicht gewöhnt bist. Datentypen!
Ein 'n' ist einfach nur der Buchstabe 'n'.
Der hat aber nichts mit der Variablen n in deinem Progamm zu tun.
Zuallererst machst du dir erst mal eine Funktion, die ein einzelnes
Zeichen ausgibt. Hier ist sie
1
voiduart_putc(charc)
2
{
3
while(!(UCSRA&(1<<UDRE)))
4
{
5
}
6
7
UDR=c;
8
}
Damit könntest du schon mal schreiben
1
intmain()
2
{
3
uart_init();
4
5
...
6
7
while(1)
8
{
9
uart_putc('n');
10
}
11
}
und hättest dasselbe: Am Terminal kommen lauter Buchstaben 'n' an.
Basierend auf dieser Funktion machst du dir eine weitere Funktion, die
einen String ausgeben kann. Hier ist sie
1
voiduart_puts(constchar*s)
2
{
3
while(*s)
4
uart_putc(*s++);
5
}
damit kannst du dann schon Strings ausgeben
1
intmain()
2
{
3
uart_init();
4
5
...
6
7
while(1)
8
{
9
uart_puts("Hallo World\n");
10
}
11
}
Und basierend auf der Stringausgabe, machst du die eine Funktion, die
die zu einem int gehörende String-Repräsentierung bestimmt und dann den
String ausgibt
1
voiduart_puti(inti)
2
{
3
charbuffer[7];
4
5
itoa(i,buffer,10);
6
uart_puts(buffer);
7
}
damit kannst du dann schreiben
1
intmain()
2
{
3
intcnt;
4
5
uart_init();
6
7
...
8
9
cnt=0;
10
11
while(1)
12
{
13
uart_puts("Wert: ");
14
uart_puti(cnt);
15
uart_putc('\n');
16
17
cnt++;
18
}
19
}
Jetzt hast du ein erstes Repertoire an verfügbaren Ausgabefunktionen auf
die USART, die du je nach Anwendungszweck einsetzen kannst.
Gewöhn dich daran. In C schreibt man sich viel mehr Funktionen als in
BASCOM, um sich dadurch von immer gleichen Routine-Aufgabe zu befreien
und die Details dieser Aufgaben soweit abzuschieben, dass man sich auf
die wesentlichen Dinge konzentrieren kann.
Und kauf dir ein C-Buch. Ohne wirst du Schiffbruch erleiden. Selbst
dann, wenn du schon BASCOM programmiert hast.
Peter II schrieb:> naja, zu viel gekürzt. Die baudrate muss schon noch gesetzt werden.
Hab ich doch mi #define BAUD 19200UL gemacht, oder?
Ziegenpeter schrieb:> Schon im ersten Code fehlt der Aufruf von uart_init.
Ahh, jetzt weiß ich was du meinst.
Ziegenpeter schrieb:> Und im zweiten fehlt die Funktion ganz.
Ja, weil ich dachte sie wurde in die setbaud.h ausgelagert.
Karl Heinz schrieb:> Jetzt hast du ein erstes Repertoire an verfügbaren Ausgabefunktionen auf> die USART, die du je nach Anwendungszweck einsetzen kannst.
Ich danke dir! Zugegeben, recht umständlich im Vergleich zu Bascom, aber
dafür halt um einiges tiefgründiger.
Karl Heinz schrieb:> Und kauf dir ein C-Buch.
Meinst du ein reines C-Buch für die Programmiersprache oder eins was
sich speziell mit C bei AVRs beschäftigt?
Für letzteres wäre ich für gute Buchvorschläge offen.
Aber ein Problem habe ich noch mit meinem jetzigen Code.
Ich bekomme danke der netten Hilfestellung hier nun jede Sekunde einen
Wert geliefert den mir Hyper-Terminal auch anzeigt.
Das eigenartige ist nur, dass die Ausgabe "Wert: 0" immer genau eine
Zeile nach unten springt (das macht ja sicherlich das /n), aber auch
immer ein Stück nach rechts. Wenn es am rechten Rand "anrennt, wird es
einfach auf der nächsten seite fortgesetzt. Hat jemand eine Erklärung
dafür?
Und das hochzählen der Variable cnt funktioniert leider auch noch nicht.
Die LED blinkert, aber cnt bleibt bei "0" stehen.
Ich habe das dumme Gefühl das cnt bei jedem ISR Aufruf auf 0 gesetzt
wird, aber wenn ich das "int cnt" in der ISR weglasse, bekomme ich 3
Fehler.
Was könnte die Ursache des Problems sein?
Hier nochmal der aktuelel Code. (Gibts eigentlich eine Faustregel ab
wann ich den Code lieber anhängen soll, statt ihn im Beitrag zu posten?)
Paul schrieb:> Peter II schrieb:>> naja, zu viel gekürzt. Die baudrate muss schon noch gesetzt werden.>> Hab ich doch mi #define BAUD 19200UL gemacht, oder?
nein.
C ist nicht BASCOM.
Mit dem #define hast du lediglich gesagt, dass im weiteren Text der
Präprozessor überall wo er den TExt BAUD vorfindet, diesen Text durch
den Text 19200UL ersetzen soll.
Kommt also zb irgendwo vor
1
i=BAUD*5;
dann ersetzt der Präprozessor den Text und es entsteht
1
i=19200UL*5;
und erst das geht dann zum eigentlichen C-Compiler.
C ist nicht BASCOM.
In C gibt es nichts vergleichbares zu den BASCOM Config Anweisungen. Das
geht schon alleine deswegen nicht, weil es C Compiler vom kleinsten µC
bis hinauf zu Supercomputern gibt. Die Anzahl der Plattformen, auf denen
BASCOM läuft kannst du dagegen an einer Handf abzählen. BASCOM kann es
sich daher leisten, spezielle ANweisungen und Spezialsyntax nur für
diese Plattformen bereit zu stellen. Die Sprache C hingegen ist gewollt
von jeglicher Abhängigkeit von einer Plattform befreit. WEnn du etwas
von der Hardware willst, dann muss es dazu eine C-Sprachkonforme
Anweisung in einer Funktion geben, die dann auch zur Laufzeit mal
ausgeführt wird.
Sagte ich schon, dass du ein C-Buch brauchst?
Paul schrieb:> Ich danke dir! Zugegeben, recht umständlich im Vergleich zu Bascom, aber> dafür halt um einiges tiefgründiger.
Findest du?
Der C-Baukasten setzt halt einfach nur etwas tiefer an. Das ist alles.
Nach kurzer Zeit hat dann sowieso jeder Programmierer seinen für sich
massgeschneiderten Baukasten.
Es hindert dich ja zb niemand eine weitere Funktion einzuführen, die
Uhrzeiten ausgeben kann
wieder das gleiche Prinzip: aus den Angaben einen String zusammenbauen
(diesmal mit sprintf, weil ich seine Formatiereigenschaften hier gut
brauchen kann) und den String ausgeben. Gut, dass ich mir eine
String-Funktion gemacht habe :-)
>> Und kauf dir ein C-Buch.> Meinst du ein reines C-Buch für die Programmiersprache oder eins was> sich speziell mit C bei AVRs beschäftigt?
reines C-Buch.
Zb. der Klassiker "Kernighan&Ritchie"
Damit dann erst mal (auf dem PC) etwas C lernen. Nach dem ersten Drittel
des Buches bist du in C fit genug um dann die Besonderheiten der
µC-Programmierung anzugehen.
K&R ist der Klassiker. Ich hab letzte Woche für meinen Neffen auch einen
gekauft. Kostet in der Indien-ANSI-Ausgabe gerade mal 14 Euro (und ein
paar zerquetschte). Gut investiertes Geld.
> Das eigenartige ist nur, dass die Ausgabe "Wert: 0" immer genau eine> Zeile nach unten springt (das macht ja sicherlich das /n), aber auch> immer ein Stück nach rechts. Wenn es am rechten Rand "anrennt, wird es> einfach auf der nächsten seite fortgesetzt. Hat jemand eine Erklärung> dafür?
\n ist der Zeilenvorschub
\r ist der Carriage Return, welcher den Cursor wieder an den linken Rand
schiebt.
Ob du einen \r brauchst oder nicht, hängt von der Einstellung des
Terminals ab. Die meisten Terminals kann man da umschalten.
Oder aber
Paul schrieb:> Und das hochzählen der Variable cnt funktioniert leider auch noch nicht.> Die LED blinkert, aber cnt bleibt bei "0" stehen.
Diese Variable 'cnt' ...
> int main()> {> ....>> int cnt;> ....> }
... und diese Variable 'cnt' ...
> ISR(INT1_vect)> {> int cnt;> PORTB ^= 0x01; // Toggle PB0> cnt++;>> }
... sind zwei verschiedende Variablen, die nurt zufällig den gleichen
Namen tragen. Die eine existiert in main(), die andere in der ISR().
Aber abgesehen vom gleichen Namen (welcher überhaupt kein Problem ist,
denn die Variablen existieren ja in verschiedenen Funktionen), haben die
beiden Variablen nichts miteinander zu tun.
Sagte ich schon, dass du ein C-Buch brauchst?
Paul schrieb:> int cnt;>> uart_init();Paul schrieb:> ISR(INT1_vect)> {> int cnt;> PORTB ^= 0x01; // Toggle PB0> cnt++;>> }
Du hast zwei verscheidene cnt Variablen hier. Beide jeweils sichtbar und
gültig in der Funktion in der sie deklariert seind.
Dabei hast du gleich mehrere Probleme:
- In der ISR wird immer wieder neu die Variable cnt eingerichtet (Platz
geschaffen) aber diese nicht initialisiert. D.h. cnt hat einen
zufälligen Wert zu beginn der ISR und Zufällig+1 am Ende. Beim nächsten
Aufruf der ISR passiert das gleiche. Auch hier ist der Wert von cnt
nicht bekannt. (Es ist mitnichten das cnt vom letzten Aufruf)
- In deiner Main Funktion ist ein cnt. Das verändert sich nie nachdem es
auf 0 gesetzt wurde.
- Beide cnt sind unterscheidliche Variablen.
=> Deklariere die Variable cnt ausserhalb jeder Funktion. Damit wird die
Variable cnt global. d.h. Sie wird beim Start des Programms angelegt und
jeder kann auf diese Variable zugreifen.
=> Deklariere sie mit dem Hinweis "volatile". D.h. dass sich die
Variable zu jedem Zeitpunkt innerhalb eines Programms ändern könnte.
Damit wird dein Programm angewiesen immer mit cnt im Speicher zu
arbeiten statt sich eine Kopie in einem Register anzulegen, die dann
eventuell veraltet ist bis sie genutzt wird.
Karl Heinz schrieb:> ... sind zwei verschiedende Variablen, die nurt zufällig den gleichen> Namen tragen. Die eine existiert in main(), die andere in der ISR().
Lustig.
In deiner ersten Programmversion ganz oben, hast du es fast richtig
gemacht (wohl ohne zu wissen warum).
Noch ein volatile davor und zumindest dieser Teil wäre richtig gewesen.
Übrigens, um die Verwirrung komplett zu machen: Mit den "klassischen"
Datentypen wie z.B. "int" solltest du gerade auf Mikrocontrollern
vorsichtig sein. I.d.R. ist es empfehlenswerter die entsprechenden
Datentypen aus <stdint.h> zu verwenden, um garantiert die richtige Länge
des Typs zu erhalten, siehe [1].
Ansonsten läufst du nämlich Gefahr, dass der gewählte Typ über- bzw.
unter dimensioniert (z.B. bei Verwendung von -mint8) ist. Das eine wird
zu "Fehlerverhalten" führen, das andere resultiert in unnötig großem
Code.
Mit freundlichen Grüßen,
Karol Babioch
[1]:
https://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Ganzzahlige_Datentypen_.28Integer.29