Forum: Mikrocontroller und Digitale Elektronik mega644 uart problem


von HansFranz (Gast)


Lesenswert?

Hallo,
ich habe ein Problem mit dem uart des Mega644PU20.
In meiner Schaltung sollen 2 AD wandler abgefragt und deren Messwerte + 
eine Messwertnummer per uart zu einem pc übertragen werden. Die AD 
wandler werden alle 4 ms abgefragt und dann das 7 byte Packet (3 byte 
messwertnummer, 2 * 2 Byte für die Messwerte via UART 38400bps 
übertragen. Das ist eigentlich keine große Sache, habs schon zig mal 
gemacht, aber diesmal ist der Wurm drin. Es sieht so aus als würden die 
byte Packete nicht richtig übertragen, in ca. 7000 Packete ist eines 
oder 2 komplett mit 0en gefüllt oder die 3 byte Messwertnummer ist alles 
0. oder es fehlen einfasch ein paar bytes. 38400 ist bei 20MHz nicht die 
beste wahl aber andere uart geschwindigkeiten bringen keine veränderung.
Bevor ich den code poste, hat evtl. schonmal einer so ein ähnliches 
problem gehabt?

von HansFranz (Gast)


Lesenswert?

noch zur info, die ad wandler sind mit den externen interrupts 0 und 1 
verbunden in den isrs setze ich ein flag und in der main wird dann der 
entdprechende adwandler per spi abgefragt und danach die daten per uart 
gesendet. Damit das mit den Interrupts funktioniert muss ich diese immer 
wieder ein und ausschalten, könnte das ein Problem mit dem uart 
verursachen?
Das komische ist echt das die Übertragungsfehler nur sporadisch 
auftreten. An rx und tx des avr ist über 6cm kabel ein usb seriell 
wandler angeschlossen, sowie auch eine Masseverbindng. Den usb seriell 
wandler hatte ich auch schon durch ein normesl max232 modul ersetzt, 
selbes Problem. Nahe bei den rx und tx anschlüssen des avr befindent 
sich die stromversorgung der platine Spannungswandler, dioden etc. Das 
könnte doch auch zu einem Problem werden, leider habe ich kein oszi um 
mir das genauer anzuschauen. Welche Möglichkeiten gäbe es denn die rx 
und tx leitung zu schirmen oder zu stabilisieren evtl. die flanken der 
signale zu verbessern?

von Krapao (Gast)


Lesenswert?

Bevor ich an der "Schirmung" arbeiten würde, würde ich im AVR 
feststellen lassen, ob bereits illegale Pakete versendet werden.

Dazu würde ich am Ausgang des Datenproduzenten (SPI) die Realdaten durh 
leicht erkennbare Prüfdaten ersetzen und am tiefsten Punkt des 
Datensenders (dort wo UDR gefüllt wird) die Prüfdaten abfragen und eine 
Error-LED bedienen.

Wenn so ein Fehler erkannt wird, kann eine Debugfunktion aufgerufen 
werden, die das Anwendungsprogramm unterbricht und alle relevanten 
Variablen im Programm fürs Debugging an den PC sendet.

Bei dem Verdacht "hat was mit den IRQs zu tun" würde ich als Zweites 
untersuchen, ob ein Zusammenhang der Fehler mit der Aufruffrequenz der 
externen IRQs besteht.

Das kann man bestimmt auch praktisch machen, wenn man mit einem zweiten 
System (analoge Timerschaltung, µC, Funktionsgenerator) die IRQs von 
außen von 0-x Hz triggert.

von Thomas B. (nichtessbar)


Lesenswert?

Hm, wenn die AD-Wandler alle 4ms abgefragt werden, warum dann externe 
Interrupts und nicht ein interner Timer?
Wie schnell fährst du über SPI? Das Versenden von 7 Byte (8-N-1) dauert 
schon ~1.8ms, wenn dann der SPI auch noch langsam ist oder die ADCs lang 
brauchen zum konvertieren (was ich eher nicht glaube..) kanns schon in 
blöden Fällen zu Überschneidungen kommen, was aber dann ein Ändern der 
Baudrate "beheben" hätte müssen. Werden die ADCs "gleichzeitig" gelesen, 
oder zeitlich versetzt?

Code+Schaltung bitte, sonst is das Rätselraten denk ich...

von HansFranz (Gast)


Lesenswert?

Die AD wandler laufen im free running mode haben sozusagen damit ihren 
eigenen timer(250Hz), über den interrupt signalisieren sie conversion 
ready, aber nur wenn sie auch vom SPI her ausgewählt wurden. Ich 
aktiviere also INT0, wähle wandler 1 per SPI an, der Interrupt setzt ein 
flag, ich deaktiviere INT0, lese per SPI den Messwert aus, deselektiere 
wandler1, aktiviere INT1, wähle wandler 2 per SPI aus, INT1 setzt dann 
wieder ein flag, deaktiviere INT1, lese per SPI den Messwert aus, 
deselektiere wandler 2, sende die daten per uart, aktiviere INT0 und 
selektiere wandler1.
Ich habe hier im Forum gelesen das es bei einer dauerübertragung per 
uart sein kann das der empfänger irgendwann das startbit nicht mehr 
richtig erkennt und daher die kommunikation nicht mehr funktioniert bis 
er sich wieder "einschwingt" durch eine kleine pause oder so. Zwischen 
meinen übertragunggen ist ja nur 4ms - 1.8ms = 2.2ms pause, ich werde 
das mal ändern das sich die pause verlängert, also sowas wie 2 x 2 
messwerte einlesen und dann beide messwerte aufeinmal verschicken (11 
byte) alle 8ms,
müsste dann 8ms - ~2.9ms = ~5.1ms pause zwischen den übertragungen.

code :
1
#include <stdlib.h>
2
#include <avr/io.h>
3
#include <avr/interrupt.h>
4
#include <inttypes.h>
5
#include <string.h>
6
#include <util/delay.h>
7
8
#define F_CPU 20000000UL
9
#define UART_BAUD_CALC(UART_BAUD_RATE,F_OSC) ((F_CPU)/((UART_BAUD_RATE)*16L)-1)
10
#define DDR_SPI DDRB
11
#define DDR_SPI1 DDRD
12
#define DD_SS0 DDB4
13
#define DD_SS1 DDD7
14
#define DD_MOSI DDB5
15
#define DD_MISO DDB6
16
#define DD_SCK DDB7
17
#define SPI_PORT PORTB
18
#define SPI_PORT1 PORTD
19
#define SPI_SS0 PB4
20
#define SPI_SS1 PD7
21
22
void UART_init(uint16_t UART_BAUD_RATE);
23
void UART_send_byte(uint8_t data);
24
void UART_send_string(const char *s);
25
void INT0_enable(uint8_t enable);
26
void INT1_enable(uint8_t enable);
27
void SPI_init(void);
28
uint8_t SPI_send_byte(uint8_t data);
29
void SPI_select0(uint8_t enable);
30
void SPI_select1(uint8_t enable);
31
32
void StartMeasurement(void);
33
void StopMeasurement(void);
34
35
void ADC_init(void);
36
37
static uint32_t MesswertNo = 0;
38
volatile uint16_t AD_value0 = 0;
39
volatile uint16_t AD_value1 = 0;
40
volatile uint8_t AD_readData = 0;
41
static uint8_t measure_active = 0;
42
43
// Externer Interrupt 0 handler
44
ISR (INT0_vect)
45
{
46
  AD_readData = 1;
47
}
48
49
// Externer Interrupt 1 handler
50
ISR (INT1_vect)
51
{
52
  AD_readData = 2;
53
}
54
55
56
ISR(USART0_RX_vect)      
57
{
58
  volatile uint8_t data = UDR0;
59
  switch (data)
60
  {
61
    case 0x42: // (B)egin measurement
62
    {
63
      if (measure_active != 0) break;
64
      StartMeasurement();
65
      break;
66
    }
67
    case 0x45: // (E)nd measurement
68
    {
69
      if (measure_active == 0) break;
70
      StopMeasurement();
71
      break;
72
    }
73
    default:
74
    {
75
      break;
76
    }
77
  }
78
}
79
80
81
// UART initialisieren
82
void UART_init(uint16_t UART_BAUD_RATE)
83
{
84
  UBRR0H=(uint8_t)(UART_BAUD_CALC(UART_BAUD_RATE,F_CPU)>>8); //baudrate (highbyte)
85
  UBRR0L=(uint8_t)UART_BAUD_CALC(UART_BAUD_RATE,F_CPU);  //baudrate (lowbyte)
86
  UCSR0B = (1<<RXEN0) | (1<<TXEN0) | (1<<RXCIE0); //aktiviere RxD & TxD & RxInt & deaktiviere TxInt
87
  UCSR0C = (0<<UMSEL00) | (0<<USBS0) | (1<<UCSZ00)| (1<<UCSZ01);  // 8 Data Bits, 1 Stop Bit, No Parity
88
}
89
90
// Ein Byte per UART senden
91
void UART_send_byte(uint8_t data)
92
{
93
  while (!(UCSR0A & (1<<UDRE0)));    //warten bis Senden möglich
94
    UDR0 = data;  
95
}
96
97
// Einen String per UART senden
98
void UART_send_string(const char *s)
99
{
100
  do
101
    {
102
        UART_send_byte (*s);
103
    }
104
    while (*s++);
105
}
106
107
108
// SPI initialisieren
109
void SPI_init(void)
110
{
111
  // MOSI, SCK, SS = output
112
  DDR_SPI = (1<<DD_MOSI) | (1<<DD_SCK) | (1<<DD_SS0);
113
  DDR_SPI1 = (1<<DD_SS1);
114
  // MISO = input
115
  DDR_SPI &= ~(1<<DD_MISO);
116
  // SPI = Master, SCKrate = Clock / 16 = 1.25MHz
117
  SPCR = (1<<SPE)|(1<<MSTR) | (1<<SPR0);
118
}
119
120
// SPI slave0 selektieren
121
void SPI_select0(uint8_t enable)
122
{
123
  if (enable == 0) SPI_PORT |= (1<<SPI_SS0);
124
  else SPI_PORT &= ~(1<<SPI_SS0);
125
}
126
127
// SPI slave1 selektieren
128
void SPI_select1(uint8_t enable)
129
{
130
  if (enable == 0) SPI_PORT1 |= (1<<SPI_SS1);
131
  else SPI_PORT1 &= ~(1<<SPI_SS1);
132
}
133
134
135
// SPI byte senden
136
uint8_t SPI_send_byte(uint8_t data)
137
{
138
  // Start transmission 
139
  SPDR = data;
140
  // Wait for transmission complete 
141
  while(!(SPSR & (1<<SPIF))) ;
142
  return SPDR;
143
}
144
145
146
// INT0 ein- ausschalten
147
void INT0_enable(uint8_t enable)
148
{
149
  if (enable == 0)
150
  {
151
    // diable int0
152
    EIMSK &= ~(1<<INT0);
153
    EIFR &= ~(1 << INT0); 
154
  }
155
  else
156
  {
157
    // diable int0
158
    EIMSK &= ~(1<<INT0);
159
    // set falling edge
160
    EICRA |= (1<<ISC01);
161
    // enable int0
162
    EIMSK |= (1<<INT0);
163
  }
164
}
165
166
// INT1 ein- ausschalten
167
void INT1_enable(uint8_t enable)
168
{
169
  if (enable == 0)
170
  {
171
    // diable int1
172
    EIMSK &= ~(1<<INT1);
173
    EIFR &= ~(1 << INT1); 
174
  }
175
  else
176
  {
177
    // diable int1
178
    EIMSK &= ~(1<<INT1);
179
    // set falling edge
180
    EICRA |= (1<<ISC11);
181
    // enable int1
182
    EIMSK |= (1<<INT1);
183
  }
184
}
185
186
187
// externen AD Wandler initialisieren
188
void ADC_init(void)
189
{
190
  // select spi slave 0
191
  SPI_select0(1);
192
  // write to com register > select chan AN1+, AN1- and select next write to clock register
193
  SPI_send_byte(0x20);
194
  // select internal clock, 2.4576MHz, CLKDIV = 1, CLKOUT = disabled, 250HZ data output rate
195
  SPI_send_byte(0xb6);
196
  // write to com register > select chan AN1+, AN1- and select next write to setup register
197
  SPI_send_byte(0x10);
198
  // select normal mode, gain = 128, bipolar, no internal buffer, FSYNC = run
199
  SPI_send_byte(0x38);
200
  _delay_ms(50);
201
  SPI_send_byte(0x10);
202
  // select selfcalibration, gain = 128, bipolar, no internal buffer, FSYNC = run
203
  SPI_send_byte(0x78);
204
  _delay_ms(50);
205
  SPI_send_byte(0x10);
206
  // select normal mode, gain = 128, bipolar, no internal buffer, FSYNC = run
207
  SPI_send_byte(0x38);
208
  // deselect spi slave 0
209
  SPI_select0(0);
210
  // select spi slave 1
211
  SPI_select1(1);
212
  // write to com register > select chan AN1+, AN1- and select next write to clock register
213
  SPI_send_byte(0x20);
214
  // select internal clock, 2.4576MHz, CLKDIV = 1, CLKOUT = disabled, 250HZ data output rate
215
  SPI_send_byte(0xb6);
216
  // write to com register > select chan AN1+, AN1- and select next write to setup register
217
  SPI_send_byte(0x10);
218
  // select normal mode, gain = 1, unipolar, no internal buffer, FSYNC = run
219
  SPI_send_byte(0x4);
220
  _delay_ms(50);
221
  SPI_send_byte(0x10);
222
  // select selfcalibration, gain = 1, unipolar, no internal buffer, FSYNC = run
223
  SPI_send_byte(0x44);
224
  _delay_ms(50);
225
  SPI_send_byte(0x10);
226
  // select normal mode, gain = 1, unipolar, no internal buffer, FSYNC = run
227
  SPI_send_byte(0x4);
228
  // deselect spi slave 1
229
  SPI_select1(0);
230
  AD_readData = 0;
231
}
232
233
234
void StartMeasurement(void)
235
{
236
  SPI_select0(0);
237
  SPI_select1(0);
238
  MesswertNo = 0;
239
  AD_value0 = 32768;
240
  AD_value1 = 0;
241
  measure_active = 1;
242
  AD_readData = 0;
243
  SPI_select0(1);
244
  // enable int0
245
  INT0_enable(1);
246
  // disable int1
247
  INT1_enable(0);
248
}
249
250
void StopMeasurement(void)
251
{
252
  measure_active = 0;
253
  // disable int0
254
  INT0_enable(0);
255
  // disable int1
256
  INT1_enable(0);
257
  SPI_select0(0);
258
  SPI_select1(0);
259
  AD_readData = 0;
260
}
261
262
// Hauptprogramm
263
int main(void)
264
{
265
  measure_active = 0;
266
267
  UART_init(38400);
268
269
  SPI_init();
270
271
  sei();
272
  
273
  SPI_select0(0);
274
  SPI_select1(0);
275
  ADC_init();
276
  AD_value0 = 32768;
277
  AD_value1 = 0;
278
  uint16_t _v0 = 0;
279
  uint16_t _v1 = 0;
280
  while(1)
281
  {
282
    switch(AD_readData)
283
    {
284
      case 1:
285
      {
286
        INT0_enable(0);
287
        // select chan AN1+, AN1-, next read from data register
288
        SPI_send_byte(0x38);
289
        // read from data register
290
        _v0 = (uint16_t)(SPI_send_byte(0)<<8);
291
        _v0 += SPI_send_byte(0);
292
        SPI_select0(0);
293
        AD_value0 = _v0;
294
        AD_readData = 0;
295
        SPI_select1(1);
296
        INT1_enable(1);
297
        break;
298
      }
299
      case 2:
300
      {
301
        INT1_enable(0);
302
        // select chan AN1+, AN1-, next read from data register
303
        SPI_send_byte(0x38);
304
        // read from data register
305
        _v1 = (uint16_t)(SPI_send_byte(0)<<8);
306
        _v1 += SPI_send_byte(0);
307
        SPI_select1(0);
308
        AD_value1 = _v1;
309
        AD_readData = 3;
310
        break;
311
      }
312
      case 3:
313
      {
314
        UART_send_byte((uint8_t)(MesswertNo >> 16));
315
        UART_send_byte((uint8_t)(MesswertNo >> 8));
316
        UART_send_byte((uint8_t)(MesswertNo & 0xFFFF));
317
        UART_send_byte((uint8_t)(AD_value0 >> 8));
318
        UART_send_byte((uint8_t)(AD_value0 & 0xFF));
319
        UART_send_byte((uint8_t)(AD_value1 >> 8));
320
        UART_send_byte((uint8_t)(AD_value1 & 0xFF));
321
        MesswertNo++;
322
        AD_readData = 0;
323
        SPI_select0(1);
324
        INT0_enable(1);
325
        break;
326
      }
327
    }
328
  }
329
330
  return 0;
331
}

von Thomas B. (nichtessbar)


Lesenswert?

HansFranz schrieb:
> UART_send_byte((uint8_t)(MesswertNo >> 16));
>         UART_send_byte((uint8_t)(MesswertNo >> 8));
>         UART_send_byte((uint8_t)(MesswertNo & 0xFFFF));

Das ist falsch bzw. vermutlich nicht so gewollt (und die &0xffff ist 
auch eher wirkungslos, wird sowieso nur das niederwertigste Byte durch 
den Cast genommen..)... MesswerteNo ist uint 32 bit, Code sollte eigtl. 
so aussehen:

UART_send_byte((uint8_t)(MesswertNo >> 24));
UART_send_byte((uint8_t)(MesswertNo >> 16));
UART_send_byte((uint8_t)(MesswertNo >> 8));
UART_send_byte((uint8_t)(MesswertNo & 0xff));

Außer du willst natürlich nur die 24 Bit haben wegen deiner 3 Byte 
Zähler die übertragen werden...


Hast halt so einen Zählerüberlauf drinnen (das passiert bei 2^32-1) und 
dann hat der nächste Messwert die Nummer 0. Weiß nicht ob das vl. evtl. 
Probleme verursachen könnte, versuch mal die Überläufe wegzulassen indem 
du bei 2^24-1 MesswertNo wieder auf 0 setzt.

Versteh ich das richtig, die ADCs sind extern und über SPI anzusteuern 
und signalisieren (solange SS auf low) über eine Leitung die direkt mit 
den Interruptpins verbunden ist, wenn fertige Daten da sind die 
ausgelesen werden können?

Kann es sein dass deine Methode mal einen deadlock produziert? Sprich es 
wird auf Signal von ADC0 gewartet aber ADC1 ist gerade über die SS 
Leitung aktiviert, somit kann der 0er gar nicht signalisieren weil er 
nicht arbeitet? Könntest mal einen Watchdog einbauen und schaun ob dein 
Controller sich resettet weil er irgendwo hängenbleibt... (einfach immer 
am Programmstart vor while(1) irgendwo einen Port auf low ziehen oder 
ähnliches und das mitm Oszi verfolgen..) Kanns zwar jetzt aus dem Code 
heraus grad nicht nachvollziehen dass es zu so einer Situation kommen 
könnte, aber der Aufbau wie du ihn hast is anfällig für deadlocks wenn 
irgendwas mit den Interrupts nicht nach Plan verläuft...

von Thomas B. (nichtessbar)


Lesenswert?

Welce ADCs sinds denn  (Datenblatt)?

Könnte sein dass zwischen Interrupt und disablen des Interrupts noch ein 
zweiter kommt (durch Störung oder sonstiges?) Versuch mal gleich in der 
ISR den Interrupt auszuschalten, dann passiert da nix unkontrolliertes 
und erst wenn du mit der Verarbeitung des ersten in der Main fertig bist 
wieder den nächsten Interrupt setzen.

Sonst hat kann der Interrupt zwischen erstmaligem Auftreten und setzen 
des Flags und erreichen der Position in der Switch jederzeit nochmal 
feuern. Was jedoch zugegebenermaßen keine Auswirkung haben sollte...

von Thomas B. (nichtessbar)


Lesenswert?

HansFranz schrieb:
> // INT0 ein- ausschalten
> void INT0_enable(uint8_t enable)
> {
>   if (enable == 0)
>   {
>     // diable int0
>     EIMSK &= ~(1<<INT0);
>     EIFR &= ~(1 << INT0);
>   }
>   else
>   {
>     // diable int0
>     EIMSK &= ~(1<<INT0);
>     // set falling edge
>     EICRA |= (1<<ISC01);
>     // enable int0
>     EIMSK |= (1<<INT0);
>   }
> }



Noch kurz dazu

Das Flag brauchst du nicht extra zu löschen (EIFR &= ...), das wird beim 
Aufruf des Interruptvektors automatisch gelöscht. Sollte es zum Eintritt 
in diese Funktion noch gesetzt sein läuft irgendwas schief...

Im else wird das ganze enabled oder? Warum erst disablen und dann wieder 
enablen? Wenn du vorsichtig sein willst dann musst du ein cli() vor und 
ein sei() hinter dem Block setzen => Atomar


Würds generel auf eine int0_enable() und int0_disable() aufsplitten... 
Braucht weniger Rechenzeit und Code is besser lesbar ;)

EICRA solltest du um sicherzugehen jedoch erst löschen und dann 
beschreiben. also EICHRA &= (3<<ISC00) und dann EICRA |= (1<<ISC01). 
Sollte zwar eigtl. wenn im restlichen Programm nicht damit rumgespielt 
wird nix passieren weil das eine Bit eh nie geschrieben wird aber sicher 
ist sicher ;)

von HansFranz (Gast)


Lesenswert?

also das mit der MesswertNo ist so gewollt nur die letzten 3 byte werden 
benötigt, sind knapp 18std bis zum überlauf, das ist ok.

> Versteh ich das richtig, die ADCs sind extern und über SPI anzusteuern
> und signalisieren (solange SS auf low) über eine Leitung die direkt mit
> den Interruptpins verbunden ist, wenn fertige Daten da sind die
> ausgelesen werden können?
ja das ist richtig, DRDY des ADC ist mit externem INT des avr verbunden.
DRDY -> H wenn conversion ready, also INT an steigender Flanke.

> Kann es sein dass deine Methode mal einen deadlock produziert? Sprich es
> wird auf Signal von ADC0 gewartet aber ADC1 ist gerade über die SS
> Leitung aktiviert, somit kann der 0er gar nicht signalisieren weil er
> nicht arbeitet?
einen deadlock wird es meiner Meinung nach nicht geben, es mag nach dem 
den ersten blick aufs programm so aussehen als könnte das passieren, 
aber ich denke eher nicht. Aber werds im hinterkopf behalten.
Ich glaube wirklich nicht das es an den Interrupts liegt.
Die ADCs sind MAX1416.

von HansFranz (Gast)


Lesenswert?

sorry DRDY -> L wenn conversion ready, daher int auf fallender flanke.

von Thomas B. (nichtessbar)


Lesenswert?

unwahrscheinlich schon, würds aber zumindest trotzdem checken ob sich 
der Controller wo aufhängt, auch wenns nur ist dass man es dann mit 
Sicherheit ausschließen kann... ist ja jetzt nicht so viel Arbeit..

Sind halt so die Sachen die mir spontan mal einfallen würden zu 
überprüfen, wenn man dann Dinge ausschließen kann is die Fehlersuche 
schon eingegrenzter...

Wie stabil sind denn deine SPI-Leitungen? Kanns da wo Probleme geben 
dass dort schon nur 0er gelesen werden?

Hast du schon festgestellt ob der uC falsch versendet oder ob falsch 
empfangen wird? Wie sieht denn deine Empfängersoftware aus oder 
kontrollierst du das übers Terminal?

Kann ja auch sein dass wir am falschen Ende suchen ;)

von Thomas B. (nichtessbar)


Lesenswert?

Noch was um Interrupts auszuschließen: die INT-Pins zu pollen dürfte 
auch genügen von der Geschwindigkeit her, deine Main macht ja außer auf 
Flags warte nix... sparst dir sogar Flags und ist sogar schneller.

von HansFranz (Gast)


Lesenswert?

Oh man, es funktionierte die ganze zeit über zu 100%, das Problem lag in 
meiner Empfangssoftware auf der PC Seite!
Das war blöd, sorry euch damit genervt zu haben und danke Thomas für 
deine Hilfe!!!!

von Thomas B. (nichtessbar)


Lesenswert?

Passiert regelmäßig, liegt in der Natur der Sache..

Würd mir aber trotzdem die ExtInts noch sparen, ist schöner und 
schneller find ich :P

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.