Forum: Mikrocontroller und Digitale Elektronik C-Code für Zähler mit INT, Ausgabe per UART streikt


von Paul (Gast)


Lesenswert?

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:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
 
5
#define BAUD 19200UL      // Baudrate
6
7
#define UBRR_VAL ((F_CPU+BAUD*8)/(BAUD*16)-1)   // clever runden
8
#define BAUD_REAL (F_CPU/(16*(UBRR_VAL+1)))     // Reale Baudrate
9
#define BAUD_ERROR ((BAUD_REAL*1000)/BAUD) // Fehler in Promille, 1000 = kein Fehler.
10
 
11
#if ((BAUD_ERROR<900) || (BAUD_ERROR>1100))
12
  #error Systematischer Fehler der Baudrate grösser 1% und damit zu hoch! 
13
#endif
14
15
16
int n=0;
17
18
19
void uart_init(void)
20
{
21
  UBRRH = UBRR_VAL >> 8;
22
  UBRRL = UBRR_VAL & 0xFF;
23
 
24
  UCSRB |= (1<<TXEN);  // UART TX einschalten
25
  UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);  // Asynchron 8N1 
26
}
27
28
29
30
31
int main(void)
32
{
33
while(1){
34
35
     DDRB = 0x01;                        // Setup P0 as output 
36
     PORTD |= 0x08;                      // Activate Apullupressistor of PD3
37
     GICR |= (1 << INT1);                // Enable INT1
38
     MCUCR |= (1 << ISC11), (0 << ISC10);              // INT1 is executed on any edge
39
40
     sei();                              // Set the I-bit in SREG
41
42
     UDR = "n";
43
}
44
     return 0;                           // This line will never be executed
45
}
46
47
48
// Interrupt subroutine for external interrupt 1
49
 ISR(INT1_vect)                            
50
{
51
     PORTB ^= 0x01;                      // Toggle PB0
52
   n++;
53
54
}

Danke für eure Hilfe

Paul

: Bearbeitet durch User
von Karol B. (johnpatcher)


Lesenswert?

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

: Bearbeitet durch User
von Paul (Gast)


Lesenswert?

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
volatile int n;
12
13
                  
14
15
16
17
int main(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
26
27
sei();                     // Set the I-bit in SREG
28
29
30
31
while(1){
32
33
                                  
34
while (!(UCSRA & (1<<UDRE)))  
35
    {
36
    }
37
 
38
    UDR = 'n';                      
39
  
40
 
41
}
42
     return 0;                               // This line will never be executed
43
}
44
45
46
                        // Interrupt subroutine for external interrupt 1
47
 ISR(INT1_vect)                            
48
{
49
     PORTB ^= 0x01;                          // Toggle PB0
50
   n++;
51
52
}

von Peter II (Gast)


Lesenswert?

Paul schrieb:

> Gibt es da noch etwas was ich übersehen haben könnte?

naja, zu viel gekürzt. Die baudrate muss schon noch gesetzt werden.

von Ziegenpeter (Gast)


Lesenswert?

Schon im ersten Code fehlt der Aufruf von uart_init.
Und im zweiten fehlt die Funktion ganz.

von Karl H. (kbuchegg)


Lesenswert?

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
void uart_putc( char c )
2
{
3
  while (!(UCSRA & (1<<UDRE)))
4
  {
5
  }
6
7
  UDR = c;
8
}

Damit könntest du schon mal schreiben
1
int main()
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
void uart_puts( const char* s )
2
{
3
  while( *s )
4
    uart_putc( *s++ );
5
}
damit kannst du dann schon Strings ausgeben
1
int main()
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
void uart_puti( int i )
2
{
3
  char buffer[7];
4
5
  itoa( i, buffer, 10 );
6
  uart_puts( buffer );
7
}

damit kannst du dann schreiben
1
int main()
2
{
3
  int cnt;
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.

: Bearbeitet durch User
von Paul (Gast)


Lesenswert?

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?)
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <util/delay.h>
4
5
6
#define BAUD 19200UL
7
 
8
#define UBRR_VAL ((F_CPU+BAUD*8)/(BAUD*16)-1)   // clever runden
9
#define BAUD_REAL (F_CPU/(16*(UBRR_VAL+1)))     // Reale Baudrate
10
#define BAUD_ERROR ((BAUD_REAL*1000)/BAUD) // Fehler in Promille, 1000 = kein Fehler.
11
 
12
#if ((BAUD_ERROR<900) || (BAUD_ERROR>1100))
13
  #error Systematischer Fehler der Baudrate grösser 1% und damit zu hoch! 
14
#endif
15
16
17
18
void uart_init(void)
19
{
20
  UBRRH = UBRR_VAL >> 8;
21
  UBRRL = UBRR_VAL & 0xFF;
22
 
23
  UCSRB |= (1<<TXEN);  // UART TX einschalten
24
  UCSRC = (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0);  // Asynchron 8N1 
25
}
26
27
28
void uart_putc( char c )
29
{
30
  while (!(UCSRA & (1<<UDRE)))
31
  {
32
  }
33
34
  UDR = c;
35
}
36
37
38
void uart_puts( const char* s )
39
{
40
  while( *s )
41
    uart_putc( *s++ );
42
}
43
44
45
void uart_puti( int i )
46
{
47
  char buffer[7];
48
49
  itoa( i, buffer, 10 );
50
  uart_puts( buffer );
51
}
52
53
54
55
              
56
                                  
57
int main()
58
{
59
60
61
DDRB = 0x01;                            // Setup P0 as output 
62
PORTD |= 0x08;                          // Activate Apullupressistor of PD3
63
GICR |= (1 << INT1);                    // Enable INT1
64
MCUCR |= (1 << ISC11), (0 << ISC10);        // INT1 is executed on any edge
65
66
sei();  
67
68
69
int cnt;
70
71
  uart_init();
72
73
  
74
75
  cnt = 0;
76
77
  while( 1 )
78
  {
79
    uart_puts( "Wert: " );
80
    uart_puti( cnt );
81
    uart_putc( '\n' );
82
83
  _delay_ms(1000);
84
85
  
86
  }
87
}
88
89
90
                        // Interrupt subroutine for external interrupt 1
91
 ISR(INT1_vect)                            
92
{
93
  int cnt;
94
     PORTB ^= 0x01;                          // Toggle PB0
95
   cnt++;
96
97
}

von Karl H. (kbuchegg)


Lesenswert?

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?

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

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
1
void uart_put_time( uint8_t hour, uint8_t minute, uint8_t second )
2
{
3
  char buff[20];
4
5
  sprintf( buff, "%02d:%02d:%02d", hour, minute, second );
6
  uart_puts( buff );
7
}

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
1
  uart_puts( "\r\n" );

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

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?

: Bearbeitet durch User
von Maxx (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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.
1
...
2
volatile int cnt;
3
4
int main()
5
{
6
  ...
7
8
  cnt = 0;
9
10
  ...
11
    uart_puti( cnt );
12
  ...
13
}
14
15
ISR( ... )
16
{
17
  cnt++;
18
}

von Karol B. (johnpatcher)


Lesenswert?

Ü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

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.