STM32, ADC & Interrupts

Gast #2455526
Lesenswert?

Hallo,
ich bin blutiger Cortex M3 Anfänger.
Ich habe bisher den SYSTICK Timer benutzt. Dessen Interrupt 
funktionierte einwandfrei. Ich habe lediglich die Register SYSTICKCSR 
und SYSTICKRVR initialisiert, und schon rennt das Ding. Super.
Jetzt möchte ich den ADC Interrupt benutzen.
Ich habe den ADC1 also initialisiert, und das Bit EOCIE im CR1 gesetzt. 
Leider ist nix passiert und meine ISR wurde nie aufgerufen. Der Fehler 
war auch schnell gefunden: der Interrupt muss im NVIC erst noch 
freigegeben werden! Also habe ich im Handbuch nachgeschaut zum 
STM32F103VC. Dort gibt es eine Tabelle, wo die einzelnen Interrupts 
aufgeführt sind. Der ADC ist an Position 18. Somit habe ich im SETENA0 
Register vom VIC BIT18 auf 1 gesetzt. Nun kommt zwar der ADC interrupt, 
aber dafür landet die CPU nachher sofort im NMI-handler?? was soll das? 
Warum macht die das?
Könnt ihr mir weiterhelfen?

Gruss carl
Gast #2455612
Lesenswert?

ich habe es herausgefunden.
Ich habe mich beim Beschreiben eines Arrays bei der Überprüfung der 
Grenzen geirrt und übers Array hinaus geschrieben und damit wohl den 
Stack zerschossen. Kaum macht man es richtig, funktioniert es ;-) 
Jedenfalls stürzt die Kiste jetzt nicht mehr ab.
Dafür habe ich ein anderes Problem.
Ich möchte 4 analoge Eingänge sampeln. Dazu benutze ich den continuous 
scan mode (CONT = 1, SCAN = 1 im ADC_CR1 bzw. ADC_CR2).
Im Handbuch heisst es allerdings zum SCAN mode:

"Bit 8 SCAN: Scan mode
This bit is set and cleared by software to enable/disable Scan mode. In 
Scan mode, the
inputs selected through the ADC_SQRx or ADC_JSQRx registers are 
converted.
0: Scan mode disabled
1: Scan mode enabled
Note: An EOC or JEOC interrupt is generated only on the end of 
conversion of the last
channel if the corresponding EOCIE or JEOCIE bit is set."

Das bedeutet doch: der ADC wandelt die 4 Kanäle zwar, so wie ich es in 
den SQRx-Registern sage, und schreibt das auch schön brav ins ADC_DR, 
aber was bringt mir das, wenn er nur nach dem letzten Kanal einen IRQ 
auslöst? Da es m.W. nur ein ADC-Datenregister gibt, wird doch dann da 
immer nur der Wert des letzten Kanals drin stehen, wenn der ADC 
Interrupt kommt. Oder?
(Firma: EleLa - www.elela.de) #2455653
Lesenswert?

Mache das mit einem DMA. Du stellt den AD Wandler auf die 4 Kanäle ein 
und der DMA schreibt alle Werte in einen Buffer.

Definiere den Buffer auf z.B. 16 Elemente, dann steht dort jeder Wert 4 
mal drin:
1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4
also jeder vierte ist der Kanal 1, immer abwechselnd.

Zum Schluss kommt der DMA Ready Interrupt und du kannst aus den 4 
Kanälen einen Mittelwert rechnen.

Ist viel einfacher und die CPU muss deutlich weniger Interrupts machen.

Demos dazu gibt es in der FW-Lib.
Gast #2455798
Lesenswert?

Markus Müller,
danke soweit. Mit dem DMA werde ich das morgen gleich ausprobieren.
Ohne DMA geht es wohl nicht? Ist zwar schon sehr sexy, wenn der DMA die 
Messwerte allesamt selbständig im Memory ablegt, aber ich möchte für 
eine so triviale Aufgabe nicht unbedingt einen kostbaren DMA-Kanal 
anbrauchen, wenns geht :-)
(Firma: EleLa - www.elela.de) #2455820
Lesenswert?

Besser einen DMA verwenden als so viele Interrupts.
Mein Buffer ist meist gleich 50 oder mehr Messungen dick (je Kanal), das 
spart dann schon viele Interrupts.

Für "Dringende" Mesungen verwende ich den ADC2 oder ADC3 und lasse den 
einfach immer wandeln und hole die Werte so wie ich brauche, ohne DMA.

Beachte, es benötigt den DMA Kanal 1, steht auch so in der Doku.
Gast #2456636
Lesenswert?

Hallo,
ich hab das jetzt implementiert. Das ist ja genial! Der DMA haut das 
alles schön ordentlich in mein Array von Messwerten rein. Perfekt! 
Besser könnte es gar nicht sein :-) und es ist wirklich einfach.
Ich habe mir jetzt sogar eine Implementierung von memcpy gebaut, die den 
DMA verwendet. Damit wird das alles sauschnell und ohne einen einzigen 
wertvollen CPU Takt zu verbraten ;-)
Danke für den Tip mit dem DMA. Sonst hätte ich nämlich noch stundenlang 
an der Interruptlösung herumgedoktert ;-)
Gast #2465855
Lesenswert?

Hallo Markus,
hab doch noch mal eine Frage :-)
Ich habe den ADC so eingestellt, dass er mir 5 Kanäle periodisch misst 
(SCAN=1, CONT=1) und per DMA in ein Array der Grösse 50 von short ints 
transferiert. Jeder Messwert ist somit 10x vorhanden.
Jetzt möchte ich, so wie du das angedeutet hast, einen Mittelwert über 
die letzten 10 Messungen bilden. Das ist ja einfach, denn in dem Array 
gehört immer jeder zehnte Messwert dazu ;-) Nur frage ich mich, ob es 
eine gute Idee ist, einfach "blind" auf das Array zuzugreifen. Kann es 
dadurch bestimmt nicht passieren, dass ich inkonsistente Daten erhalte? 
Was passiert, wenn ich im Array gerade einen Messwert auslese, auf den 
der DMA gerade schreibend zugreift? Kriege ich immer den "alten", oder 
den "neuen" Messweert, oder kann es sein, dass ich ein zufälliges 
Gemisch erhalte von "alt" & "neu" ?

Gruss
(Firma: EleLa - www.elela.de) #2465883
Lesenswert?

Nein, das passiert eigentlich nicht.

Wenn man im DMA Interrupt eine Schleife ab dem ersten laufen lässt, dann 
wird innerhalb weniger ns (Prozessor-Takte) schon der erste Messwert für 
die Summe berechnet.
Wenn der dann beim 50sten angekommen ist, dann kann es schon sein dass 
die nächste AD Wandlung fertig ist, ist aber auch egal, da der erste 
schon berechnet wurde.

Messe doch einfach wie viele Prozessor-Takte die Routine benötigt und 
vergleiche das mit der AD Einstellung, wie schnell die wandelt.
Dann weißt Du das genau und braucht nicht raten oder vermuten.

Tipp:
1
// Sys-Tick Counter - Messen der Anzahl der Befehle des Prozessors:
2
#define CORE_SysTickEn()      (*((u32*)0xE0001000)) = 0x40000001
3
#define CORE_SysTickDis()      (*((u32*)0xE0001000)) = 0x40000000
4
#define CORE_GetSysTick()      (*((u32*)0xE0001004))
5
#define CORE_ClearSysTick()      (*((u32*)0xE0001004)) = 0
Gast #2465888
Lesenswert?

Hmm, das verstehe ich jetzt nicht ganz.
Den DMA Interrupt benutze ich auch gar nicht, sondern ich lasse den DMA 
schön brav im Hintergrund zyklisch alle Messwerte ins Array speichern, 
und dann kann ich je nach Bedarf die Messwerte da raus holen. Oder habe 
ich jetzt etwas nicht verstanden? :-/
(Firma: EleLa - www.elela.de) #2465971
Lesenswert?

Ja, geht natürlich auch.

Exakt zur gleichen Zeit eine Speicherstelle zu beschreiben (DMA) und zu 
lesen (CPU) wird ohnehin nicht gehen. Das wird sich gegenseitig 
verriegeln, bzw. der DMA / CPU wird dann ein Wait ausführen müssen.

Entweder greift der DMA in dem Takt auf das SRAM zu oder die CPU aber 
beides gleichzeitig wird nicht gehen.

Aus dem Grund hat der STM32F4xx 3 verschiedene integrierte SRAM Bänke, 
so dass die sich gegenseitig weniger blockieren.
Gast #2466048
Lesenswert?

Das heisst also, wenn ich im dümmsten Fall grade denjenigen ADC-Messwert 
auslesen will, den der DMA gerade beschreibt, ist sicher gestellt, dass 
ich kein Datenchaos bekomme? Ob ich jetzt einen topaktuellen oder einen 
etwas älteren Messwert kriege ist nicht so wichtig. Aber ich hatte zu 
Beginn eben befürchtet, dass es vorkommen könnte, dass die CPU z.B. das 
Highbyte schon ausgelesen hat, und der DMA dann einen neuen Messwert 
hinschreibt, und die CU liest dann im nächsten Takt das Lowbyte des 
neuen Messwerts - das wäre natürlich sehr gruselig, denn dann hätte ich 
ja ein Datengemisch! Aber wenn du sagst, dass es sich verriegelt, dann 
ist ja alles gut :-)
über dies hätte ich es bis jetzt auch noch nicht gesehen, dass der Fall 
eingetreten wäre, meine Testsoftware werkelt schon einige Zeit vor sich 
hin. Aber ich wollte jetzt doch noch auf Nummer sicher gehen :-)
#2466065
Lesenswert?

Da das DMA vom ADC wohl 16-Bit Worte schreibt, kann es vorkommen, dass 
ein 32-Bit Ladebefehl ein altes und ein neues 16-Bit Wort liest. Es kann 
aber bei 16- oder 32-Bit Ladebefehlen nicht vorkommen, dass ein 16-Bit 
Wert bitweise gemischt ist. Dies kann jedoch sehr wohl geschehen, wenn 
du den Speicher byteweise ausliest.

Bei manchen Controllern ist jedoch bei solchen Zugriffen darauf zu 
achten, welche Kopie des SRAMs für den Zugriff verwendet wird, weil 
sonst Pufferung und Caching relevant werden. Die STM32 gehören m.W. 
nicht dazu, aber beispielsweise STR9.

In der umgekehrten Richtung (CPU => Speicher => DMA) kann es vmtl. auch 
beim STM32 dazu kommen, dass ein frisch nach einem Store ins SRAM 
gestarteter DMA alte Daten im RAM sieht, wenn dazwischen nicht ein 
entsprechender Barrier-Befehl für Ordnung sorgt.
#2466098
Lesenswert?

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0489c/CIHGHHIE.html

Ok, grad mal nachgesehen, bei CM3 passiert vermutlich noch nichts 
unerwartetes, weil Puffer nicht tief genug.

Generell gilt es aber, bei komplexen Architekturen und DMA das memory 
ordering model der Adressbereiche im Auge zu behalten. Die CM3 scheinen 
aber simpel genug zu sein, um hier wenig Überraschungen zu bieten. Nur, 
wie gesagt, bei den STR9 sieht das schon deutlich anders aus.
Gast #2466135
Lesenswert?

Ah, Stichwort "Barrier Befehl" ! kann ich da etwas mit der DMB 
Instruktion machen? Data memory barrier.

Mein Array von messwerten habe ich als unsigned short int gekennzeichnet 
und greife auch immer nur so darauf zu. Ich gehe mal davon aus, dass der 
Compiler dann selbst dafür schaut, dass die Zugriffe 16bit breit 
erfolgen. Auf jedenfall lese ich das Array sicher nicht byteweise aus 
:-)
(Firma: EleLa - www.elela.de) #2466214
Lesenswert?

Ja, 16 Bit breite ist nur ein Maschinenbefehl und somit braucht man sich 
keine Gedanken zu machen.

Mit einem 8-Bitter (AVR) hätte man schon Probleme.

Ich berechne die Werte-Reihe immer im DMA-Interrupt und schreibe das 
Ergebnis in eine Variable. Das Programm (Main-Loop) nimmt dann nur noch 
diese fertige Variable.
Damit hatte ich noch nie ein Problem und der aktuelle Wert steht immer 
bereit in der Variable.
Gast #2466355
Lesenswert?

carl schrieb:
> Mein Array von messwerten habe ich als unsigned short int gekennzeichnet

Wenn du unsigned 16bit meinst schreib das doch auch so hin: uint16_t. 
Ist deutlich klarer was gemeint ist und bei einigen Datentypen kann man 
auch nicht so ohne weiteres wissen wie groß sie auf verschiedenen 
Architekturen sind, die uint*_t / int*_t Schreibweise sorgt da für 
Klarheit und Portabilität.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren