Forum: Mikrocontroller und Digitale Elektronik Bitte um Prüfung meines C-Codes


von feg_zeven (Gast)


Lesenswert?

Hallo Leute. :)

Ich habe mir ein kleines Programm zusammen gebastelt was einen 
Drehencoder ausliest und eine Variable hoch- bzw runterzählt. Diese 
Variable wird sekündlich über USART an meinen Rechner geschickt - was 
auch funktioniert. Leider tut sich beim drehen am Encoder nichts. Die 
Variable bleibt bei 0 ^^

Dadurch, dass ich über USB programmiere sind meine DebugMöglichkeiten 
nicht so das gelbe vom Ei. Ich konnte feststellen, dass er in die ISR 
des Timer E geht, in dem die Abfrage des Encoders stattfindet.  Es 
scheint als stimme die Abfrage der Pins nicht. Bin mir aber nicht 
sicher. Könnte vielleicht mal jemand ein Auge auf den Code werfen?

Viele Grüße
1
/*
2
 *
3
 * USARTC0, Tx Pin -> PC3.
4
 * 9600 baud mit 2 MHz clock. BSCALE = 0
5
 * BSEL = ( 32000000 / (2^0 * 16*19200)) -1 = 12
6
 * Fbaud = 32000000 / (2^0 * 16 * (12+1))  = 9615 bits/sec
7
 */ 
8
9
10
#define PHASE_A     (PORTD_IN & 0x01)
11
#define PHASE_B     (PORTD_IN & 0x02)
12
13
14
#include <avr/io.h>
15
#include <avr/interrupt.h>
16
#include <string.h>
17
#include <asf.h>
18
#include <stdio.h>
19
20
void Clock_init(void);
21
void Int_init(void);
22
void UART_init(void);
23
24
void TimerC0_init(void);
25
void TimerE0_init(void);
26
27
void Send_UART(unsigned int data);
28
void impulsgeber_init (void);
29
int8_t encode_read1(void);
30
31
//Variablen
32
char lenght = 0x00;
33
char Counter = 0x00;
34
35
unsigned int TCTW=0x85ED;      // TimerXTopWert
36
unsigned int TETW=0x7D00;
37
38
unsigned int new = 0;
39
volatile int8_t enc_delta;          // -128 ... 127
40
static int8_t last;
41
int8_t encval = 0;
42
43
44
int main(void)
45
{
46
  Clock_init();  
47
  Int_init();
48
  UART_init();
49
  
50
  TimerC0_init();
51
  TimerE0_init();
52
53
  
54
  
55
  PORTB.DIR = 0x00;      //Datenrichtung auf Eingang
56
  PORTD.DIR = 0x00;  
57
  TCC0.PER = TCTW;  
58
  TCE0.PER = TETW;
59
  
60
    while(1)
61
    {
62
    
63
  encval=encode_read1();
64
  
65
    }
66
}
67
68
69
//____________INITFunktionen_______________
70
71
void Clock_init(void)
72
{  
73
  OSC.CTRL |= OSC_RC32MEN_bm;                                              // Oszillator auf 32Mhz stellen
74
  while(!(OSC.STATUS & OSC_RC32MEN_bm));                                        // Warten bis der Oszillator bereit ist
75
  CCP = CCP_IOREG_gc;
76
  CLK.CTRL = CLK_SCLKSEL_RC32M_gc;                                          // Clock auf 32MHz stellen                                              
77
}
78
79
void Int_init(void)
80
{
81
  PMIC.CTRL |= PMIC_LOLVLEN_bm | PMIC_MEDLVLEN_bm | PMIC_HILVLEN_bm;                          // Interrupts (Highlevel, Mediumlevel und Lowlevel freigeben)
82
  sei();                                                        // Globale Interruptfreigabe
83
}
84
85
void UART_init(void)
86
{
87
  PORTC.DIR = 0xEF;  
88
  
89
  USARTC0.BAUDCTRLB = 0;                                                // BSCALE = 0 
90
  USARTC0.BAUDCTRLA = 0x67;                                                // Baudrate 19200 @ 41MHz
91
  USARTC0.CTRLB = USART_TXEN_bm | USART_RXEN_bm;                                    // RX+TX Enable CLK
92
  USARTC0.CTRLC = 0x03;                                                  // Async, no parity, 8 bit data, 1 stop bit
93
  USARTC0.CTRLA = 0;    
94
}
95
96
void impulsgeber_init (void)
97
{
98
  if( PHASE_A )
99
  new = 3;
100
  if( PHASE_B )
101
  new ^= 1;            // new = new^1 ---- convert gray to binary
102
  last = new;                   // power on state
103
  enc_delta = 0;
104
}
105
106
107
//_______________________TIMER________________________
108
109
void TimerC0_init()
110
{
111
  TCC0.CTRLA = TC_CLKSEL_DIV1024_gc;                                          // Vorteiler einstellen
112
  TCC0.CTRLB = 0x00;                                                  // Timer in Normalmodus stellen
113
  TCC0.INTCTRLA = 0x03;                                                // Interrupt konfigurieren  
114
}
115
116
void TimerE0_init(void)
117
{
118
  TCE0.CTRLA = TC_CLKSEL_DIV2_gc;                                          // Vorteiler einstellen
119
  TCE0.CTRLB = 0x00;                                                  // Timer in Normalmodus stellen
120
  TCE0.INTCTRLA = 0x02;
121
}
122
123
124
//______________________TimerISR_______________________
125
126
ISR(TCC0_OVF_vect)
127
{
128
129
  Send_UART(encval);
130
  
131
  TCC0.PER = TCTW;
132
133
}
134
135
ISR(TCE0_OVF_vect)
136
{
137
    int8_t new, diff;
138
        new = 0;
139
    if( PHASE_A )
140
    new = 3;
141
    if( PHASE_B )
142
    new ^= 1;                   // convert gray to binary
143
    diff = last - new;                // difference last - new
144
    if( diff & 1 ){               // bit 0 = value (1)
145
      last = new;                 // store new as next last
146
      enc_delta += (diff & 2) - 1;        // bit 1 = direction (+/-)
147
    }
148
    
149
   TCE0.PER=TETW;
150
151
}
152
153
154
//_______________Sonstige Funktionen__________________
155
156
void Send_UART(unsigned int data)
157
{
158
  while (!(USARTC0.STATUS & USART_DREIF_bm));
159
  USARTC0.DATA = data;
160
}
161
162
163
int8_t encode_read1( void )         // read single step encoders
164
{
165
  int8_t val;
166
  
167
  cli();
168
  val = enc_delta;
169
  enc_delta = 0;
170
  sei();
171
  return val;                   // counts since last call
172
}

von feg_zeven (Gast)


Lesenswert?

Ach und ich nutze eine XMega128A1 auf dem Xplained-Board.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Dieser "atomare" Zugriff ist unnötig, weil enc_delta eh' nur 8 Bit hat 
und sowieso unterbrechungsfrei ausgelesen wird:
1
  cli();
2
  val = enc_delta;
3
  enc_delta = 0;
4
  sei();

feg_zeven schrieb:
> Leider tut sich beim drehen am Encoder nichts.
Wie sieht die Hardware aus? Funktioniert die? Hast du Pulse am uC-Pin?
Im Code werden keine Pullups am PortC eingeschlatet. Hast du externe 
Pullups?

: Bearbeitet durch Moderator
von Karl H. (kbuchegg)


Lesenswert?

feg_zeven schrieb:

> des Timer E geht, in dem die Abfrage des Encoders stattfindet.  Es
> scheint als stimme die Abfrage der Pins nicht. Bin mir aber nicht
> sicher.

Na, ja
Wenn du das denkst, was hindert dich daran, das mal in einem eigenen 
Testprogramm zu verifzieren?
1
...
2
3
int main(void)
4
{
5
  PORTD.DIR = 0x00;  
6
7
  while( 1 ) {
8
9
    lass dir PORTD_IN wo ausgeben
10
  }
11
}

du musst ja nicht gleich in die vollen gehen. Genau dieses (Port 
Initialisieren und PORTD_IN) abfragen ist ja auch der Kern der Encoder 
Auswertung. Wenn du da ein Problem vermutest, dann nimm dir diese 
Kernfunktionalität getrennt vor.

Zb kommt mir komisch vor, dass in dem Code die Wörter Pullup bzw. 
Pulldown nirgends vorkommen.

(Das Testprogramm wirst du wahrscheinlich noch um eine UART ergänzen, 
damit du auch irgendwo Output generieren kannst)

: Bearbeitet durch User
von Christian K. (the_kirsch)


Lesenswert?

feg_zeven schrieb:
> ISR(TCE0_OVF_vect)
> {
>     int8_t new, diff;
>         new = 0;
>     if( PHASE_A )
>     new = 3;
>     if( PHASE_B )
>     new ^= 1;                   // convert gray to binary
>     diff = last - new;                // difference last - new
>     if( diff & 1 ){               // bit 0 = value (1)
>       last = new;                 // store new as next last
>       enc_delta += (diff & 2) - 1;        // bit 1 = direction (+/-)
>     }
>
>    TCE0.PER=TETW;
>
> }

Ich verstehe gerade nicht warum du den Drehencoder in einer Timer_ISR 
bearbeitest, wäre es nicht besser das per PinChange-Interrupt zu machen?

Du fragst Regelmäßig die Pins ab, du must diese aber auch mit dem 
letzten Wert der Pins vergleichen.

Und zwar so:

00 => 01 => +1
00 => 10 => -1

01 => 11 => +1
01 => 00 => -1

10 => 00 => +1
10 => 11 => -1

11 => 10 => +1
11 => 01 => -1

Alle andern Übergänge sind invalid, da wurde dann ein Schritt nicht 
erkannt

Entweder du pollst das weiterhin in einer TimerISR und besser über einen 
pinInterrupt.

: Bearbeitet durch User
von Falk B. (falk)


Lesenswert?

@ Lothar Miller (lkmiller) (Moderator) Benutzerseite

>Dieser "atomare" Zugriff ist unnötig,

Nein!

>weil enc_delta eh' nur 8 Bit hat
>und sowieso unterbrechungsfrei ausgelesen wird:

Das reicht nicht. Es muss auch unterbrechungsfrei gelöscht werden, damit 
nicht zufällig ein Interrupt GENAU zwischen Auslesen und Löschen 
reinspuckt und dadurch 1 Zählerstand verloren geht! Das ist ein SEHR 
schwer lokalisierbarer Fehler! Siehe

https://www.mikrocontroller.net/articles/Interrupt#Atomarer_Datenzugriff

von Falk B. (falk)


Lesenswert?

@ Christian K. (the_kirsch)

>Ich verstehe gerade nicht warum du den Drehencoder in einer Timer_ISR
>bearbeitest,

Weil das der normale, bewährte Weg ist.

> wäre es nicht besser das per PinChange-Interrupt zu machen?

Nein. Und nein, das will ich nicht wieder endlos diskutieren.

>Du fragst Regelmäßig die Pins ab, du must diese aber auch mit dem
>letzten Wert der Pins vergleichen.

Das macht die Routine schon, steht alles im Artikel Drehgeber, von 
dort stammt auch der Code!

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Falk Brunner schrieb:
> Das reicht nicht. Es muss auch unterbrechungsfrei gelöscht werden
Ach schlags kaputt. Das ist ja ein Read-Modify-Write...
Gut. Das Zeug muss weiterhin drin bleiben.

Christian K. schrieb:
> wäre es nicht besser das per PinChange-Interrupt zu machen?
Ich stimme Falk zu: wäre es nicht.
Wozu unnötig viele Interrupts? Wenn man aus jedem "Furz" einen Interrupt 
macht, dann kanns durchaus mal hecktisch werden.
Das siehst du sicher an dir selber auch: wenn jede Minute einer kommt 
und dich unterbricht, ist das lästig. Wenn dann mal gleichzeitig sogar 2 
oder 3 kommen, dann kann es schon mal passieren, dass du was 
durcheinander bringst.
Ich spreche jetzt dem uC keine Nachlässigkeit oder Vergesslichkeit zu, 
aber das Hauptproblem ist meist, dass dein Programm (das tadellos läuft, 
solange alle Interrupts hübsch nacheinander kommen) auf einmal 
"abstürzt", wenn 3 Interrupts auf einmal anstehen. Und das ganz einfach, 
weil du solche Unterbrechungen nicht einkalkuliert hast...

: Bearbeitet durch Moderator
von feg_zeven (Gast)


Angehängte Dateien:

Lesenswert?

Also der Encoder funktioniert eigentlich. Hab den letztes Jahr (klingt 
komisch, aber ist ja erst 9 Tage her :D) mal mit einem Oszi geprüft und 
der macht was er soll.
Zum Thema PullUp: Eigentlich habe ich zwei PullUp am Encoder an den 
beiden Kanälen angelötet. Auf dem Foto könnt ihr euch das mal angucken. 
Das schwarz/braune Twistet Pair sind die Signalleitungen. Das zweite 
braune ist +, weis ist GND. Muss ich denn dann trotzdem noch für die 
Pins PullUps/-downs konfigurieren?

von Falk B. (falk)


Lesenswert?

@ feg_zeven (Gast)

>komisch, aber ist ja erst 9 Tage her :D) mal mit einem Oszi geprüft und
>der macht was er soll.

Gut.

>braune ist +, weis ist GND. Muss ich denn dann trotzdem noch für die
>Pins PullUps/-downs konfigurieren?

Nein.

Zu deinem Programm.


>#define PHASE_A     (PORTD_IN & 0x01)
>#define PHASE_B     (PORTD_IN & 0x02)

Ist dein Encoder WIRKLICH dort angeschlossen?

>  encval=encode_read1();

Das ist eher nicht das, was du willst, eher so.

encval += encode_read1();

Kleiner, aber feiner Unterschied ;-)


>  CCP = CCP_IOREG_gc;
>  CLK.CTRL = >CLK_SCLKSEL_RC32M_gc;                                          // 
Clock >auf 32MHz stellen

Das geht bisweilen schief, wenn die Optimierung nicht eingeschaltet ist. 
Besser mit einem ASM-Makro arbeiten, das ist sicher. Kann man als 
einfache Funktion nutzen.

Beitrag "Re: PLL Startprobleme beim ATXmega128A1U"

>void impulsgeber_init (void)

Wir bei dir beim Start nicht aufgerufen, ist aber unkritisch.

>ISR(TCC0_OVF_vect)
>{
>  Send_UART(encval);
>  TCC0.PER = TCTW;

Das braucht man nicht, der Timer wird per Hardware automatich 
nachgeladen.
Im Gegenteil, durch dein Nachladen stimmt das Timing nicht mehr exakt.


>   TCE0.PER=TETW;

Hier ebenso.

Funktioniert denn der Rest? Kannst du per UART was senden bzw. auf dem 
PC empfangen?

von feg_zeven (Gast)


Lesenswert?

Ich denke schon, dass ich alles richtig angeschlossen habe. Pin0 und 
Pin1 des PortD, oder?

Ok, dann werde ich mal die von dir gefundenen Fehler ausmerzen. Das mit 
dem encval+=.... hätte mir selbst auffallen müssen. Danke.

Der Rest funktioniert eigentlich problemlos. Der Wert wird sekündlich 
gesendet bzw am PC empfangen. Bis jetzt ist es halt nur 0x00. Wie schon 
gesagt - die Zwei Timer-ISR scheint er zu zyklisch zu durchlaufen. Wenn 
ich in die ISR des Encoders die SendUart-Funktion aufrufen lasse bekomme 
ich auch alle paar ms einen Wert gesendet.

Ich probiere erstmal die Hinweise von dir aus und danach erstatte ich 
nochmal bericht :)

von Falk B. (falk)


Lesenswert?

ein

volatile int8_t encval = 0;

wäre auch nicht schlecht.

von feg_zeven (Gast)


Lesenswert?

Es geht. Danke nochmal an euch :)

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.