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?
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?
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.
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...
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 | }
|
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...
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...
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 ;)
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.
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 ;)
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.
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!!!!
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.