Forum: Mikrocontroller und Digitale Elektronik ATMega168 @ 20Mhz ADC dauert zu lange DCC NMRA


von MOBA 2. (Gast)


Lesenswert?

Hi,

ich habe ein nicht lösbares Problem (sry schonmal für den langen Text, 
muss aber sein da das zum Verständnis gebraucht wird):

Ich nutze einen ATMega168PA mit 20Mhz. Die C's sind 22pf am Quarz.

Dieser ist ein NMRA-DCC Decoder/Bremsgenerator. Sprich er decodiert das 
DCC Signal (~20khz) mittels 1x Timer an einem Pin, wertet die Daten aus 
etc... Das ist recht viel.

Kurzer Verständniseinschub:
Der Bremsgenerator arbeitet so:
Fährt eine Lok auf den galvanisch getrennten Bremsabschnitt (versorgt 
mit meinem Modul), erzeugt dies einen Kurzschluss den ich aber so früh 
erkenne und handel, noch bevor die Zentrale die Lasterhöhung bemerkt 
(oder gar selbst Überstrom meldet). Wenn mein Modul merkt, dass keine 
Spannung mehr anliegt (durch Überbrückung der Lok), dann speist dieses 
den Abschnitt mit einem Halt-Befehl oder Langsamfahrtbefehl. Vorteil: 
Man braucht keine Booster, Schalter oder sonstiges Zubehör und alle 
Funktionen der Lok bleiben erhalten, da das so schnell geht, bevor der 
Lokdecoder sich resettet. Außerdem verschleißarm, da nichtmal 
Funkensprühen durch kurzschluss sichtbar war (arbeitet mit 24V, Zentrale 
kann bis 15A).



Dann erzeugt er nochmals das DCC Signal mittels H-Brücke auf 2 anderen 
Pinnen (wieder ~20kHz) und der 2. Timer wird genutzt.

Außerdem für die Zeiten hat er einen ISR-Timer mit OVF-INT mit 1,25khz 
und da kommt der 3. TImer zum Einsatz. Außerdem wird mit diesem an 8 
Pinnen Soft-PWM erzeugt (sofern Ausgang aktiviert).

Das klappt alles 1a und erstaunlich gut. Jetzt aber das Problem.
Da er ein Bremsgenerator ist, misst er den Stromverbrauch der H-Brücke 
(L6201PS). Hier habe ich einen 0,05R Widerstand in Serie zum Source-Pin 
der H-Brücke als Sensor. Diese Spannung greife ich (mit 100nF und 
Z-Diode für alle Fälle) ab und leite sie auf einen ADC-Pin.


In der Software habe ich den ADC als "Free-Running" konfiguriert (wie 
ich das verstanden habe wird nun permanent gewandelt ohne das ich es 
immer aktivieren muss.
Den Wert nehme ich entgegen und berechne den Strom. Ich nutze 1,1V int. 
Ref.
1
if (!decoder.timer.timer_univ[brggen_adc_conversion_timer])
2
  {
3
    //get data and convert
4
    decoder.dcc_current = ADCW;    //get adc value ADCW
5
    decoder.dcc_current *= 1.1;    //ref voltage
6
    decoder.dcc_current /= 1024;  //convert to voltage
7
    decoder.dcc_current /= 0.05;  //this is R (I = U/R)
8
    
9
    decoder.timer.timer_univ[brggen_adc_conversion_timer] = TI_1ms;
10
  }


Jetzt das PROBLEM:

Wenn ich es so mache wie oben klappt alles super ABER ab und an wird ein 
Kurzschluss erzeugt, denn im ungünstigsten Moment ist noch der alte Wert 
vorhanden und nicht der echte Stromwert. Auf dem Tisch mit Lok hin und 
herschieben über die Trennstelle passierte das (bei der hohen Rate) alle 
20-30x. Ist nicht viel, aber im Betrieb, wenn es dann mal passiert, 
absolut nervig.


Lasse ich bspw. den Timer weg 
(decoder.timer.timer_univ[brggen_adc_conversion_timer] löschen und die 
Abfrage rausnehmen, sodass die Wandlung permanent passiert), dann ist 
die Lokerkennung genauso wie in der Theorie ausgemalt!!! ABER: Dann kann 
ich nichts mehr steuern (an dem Modul hängen noch die Signale da das 
eine Signalbeeinflussung geben sollte). Ich muss ungelogen 20x auf die 
Taste "Signal grün" tippen und wenn ich Glück habe kommt mal ein Befehl 
in der richtigen Zeit durch.


Ich bin komplett ratlos was ich machen soll. Es kann doch nicht so krass 
lange dauern, bis er die 3 float Befehle abgearbeitet hat, der taktet 
mit 20Mhz. Ich weiß, float, und uint32 und größer haben, zumindest bei 
mir, immer schon Probleme auf den Mega's gemacht, deswegen vermeide ich 
die wo ich kann, aber hier geht es leider nicht. Vll. ist es auch eine 
Einstellungssache beim AtmelStudio7?!

Ich würde mich über Hilfe echt freuen!

von Knut B. (Firma: TravelRec.) (travelrec) Benutzerseite


Lesenswert?

Nimm doch die ganze Konvertierung mal raus und arbeite mit realen 
ADC-Werten. Dem Controller ist es doch Wurscht, ob er mA oder ADC-Ticks 
von der H-Brücke liest. Wenn Du es irgendwo ausgeben musst, kannst Du 
zur Laufzeit in der Main konvertieren, das kann dann immer noch von 
kritischen Interrupten unterbrochen werden.

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


Lesenswert?

Marius D. schrieb:
> Ich nutze 1,1V int. Ref.
Dann rechne statt mit float in mV. Dadurch kannst du mit Integern 
rechnen und bist pfeilschnell. Denn ein kleiner Merkspruch für solche 
schmächtigen 8-Bit uC ohne FPU ist "Float = Langsam".

> Es kann doch nicht so krass lange dauern, bis er die 3 float Befehle
> abgearbeitet hat
Eins vorneweg: es sind 4 float-Befehle, denn allein das Einlesen des 
ADC-Integer-Werts und dessen Umwandeln in einen float ist auch nicht 
ohne.

> Es kann doch nicht so krass lange dauern
Aber der Witz ist: sowas kann man im Simulator mal kurz ausmessen 
lassen. Oder man misst es mit einem Portpin im echten Leben...

von Edi R. (edi_r)


Lesenswert?

Marius D. schrieb:
1
> //get data and convert
2
>     decoder.dcc_current = ADCW;    //get adc value ADCW
3
>     decoder.dcc_current *= 1.1;    //ref voltage
4
>     decoder.dcc_current /= 1024;  //convert to voltage
5
>     decoder.dcc_current /= 0.05;  //this is R (I = U/R)

Ich würde es so machen:
1
uint32_t current;
2
current = 5500UL * ADCW;
3
current += 128;
4
current >>= 8;

Das Ergebnis ist der Strom in mA als Ganzzahl. Wenn Dir die Auflösung in 
dA (ja, Dezi-Ampere, z. B. 54 dA = 5,4 A) reicht, kommst Du sogar mit 
16-bit-Variablen aus:
1
uint16_t current;
2
current = 55 * ADCW;
3
current += 128;
4
current >>= 8;

von Falk B. (falk)


Lesenswert?

@Edi R. (edi_r)

>Ich würde es so machen:

>uint32_t current;
>current = 5500UL * ADCW;
>current += 128;
>current >>= 8;

Was soll der Unsinn? Oder bist du BASCOM-geschädigt? Sowas kann man in C 
problemlos in EINE Zuweisung schreiben. Außerdem braucht der OP hier 
sicher KEINE 32 Bit Variable. Es reicht, wenn man per Cast oder Suffix 
eine 32 Bit RECHNUNG erzeugt. Siehe Festkommaarithmetik. Und wenn 
schon, dann bitte richtig rechnen.

Bei 0,05R = 50mOhm sind das 20A/V
1
U = ADCW * U_ref / 1024 = ADCW * 1,1V / 1024
2
I = U / R = U / 0,05R = U * 20A/V = ADCW * 1,1 * 20A/V / 1024 = ADCW * 22 / 1024
3
I_mA = I * 1000 = ADCW * 22000 / 1024 = (ADCW * 22000) >> 10

1
uint16_t current;  // mA
2
current = (22000UL * ADCW) >> 10;

von H.Joachim S. (crazyhorse)


Lesenswert?

Marius D. schrieb:
> Es kann doch nicht so krass
> lange dauern, bis er die 3 float Befehle abgearbeitet hat,

Das "kann nicht" stützt sich worauf? Erwartung, Erfahrung?
Ist auch ein bisschen unglücklich geschrieben, die 3 Befehle mit 3 
Konstanten kann man natürlich vorab berechnen, so dass nur eine 
Operation übrigbleibt. Aber ich denke, dass baut der Compiler eh für 
dich um, so dass da eher kein Einsparpotential besteht.

Ansonsten ist ja alles gesagt. Solange du nicht irgendeinen Wert 
menschlich ablesbar machen willst, brauchst du eigentlich gar keine 
Skalierung auf den tatsächlichen Stromwert. Rechne einfach mit dem 
nackten ADC-Wert weiter, der entspricht ja nach wie vor dem Strom, halt 
in einer sehr unüblichen Einheit.

von Edi R. (edi_r)


Lesenswert?

Falk B. schrieb:
> Was soll der Unsinn? Oder bist du BASCOM-geschädigt?

Nö, ich wollte es nur ähnlich schreiben wie der TO. Schließlich soll 
er es nachvollziehen können. Du hast aber Recht damit, dass für das 
Ergebnis eine 16-Bit-Variable reicht. Die Multiplikation wird aber 
trotzdem mit 32 Bit durchgeführt. Bei meinem zweiten Vorschlag mit den 
dA nur mit 16 Bit.

Falk B. schrieb:
> current = (22000UL * ADCW) >> 10;

Das halte wiederum ich für Unsinn. Das Verschieben der Bytes um 8 Bit 
erledigt der Compiler durch ein Verschieben der Bytes. Bei der 
Verschiebung um 10 Bit müssen zusätzlich zwei Shifts eingefügt werden.

von Peter D. (peda)


Lesenswert?

Marius D. schrieb:
> decoder.dcc_current = ADCW;    //get adc value ADCW
>     decoder.dcc_current *= 1.1;    //ref voltage
>     decoder.dcc_current /= 1024;  //convert to voltage
>     decoder.dcc_current /= 0.05;  //this is R (I = U/R)

Was willst Du mit dieser elendig langen Rechenorgie, noch dazu mit 2 
schnarchlahmen Divisionen.

Du willst doch bestimmt nur auf einen Schwellwert vergleichen. Da kann 
man ganz einfach die Schwelle einmalig in ADC-Schritte umrechnen und 
erreicht genau das gleiche, bloß 1000-mal schneller.

von Axel R. (Gast)


Lesenswert?

Willst Du auf die Schnelle
nur mit einer Schwelle
vergleichen,
so tu die Umrechnung
streichen.

frei nach baumann

StromTuner

von MOBA 2. (Gast)


Lesenswert?

Edi R. schrieb:

> Das Ergebnis ist der Strom in mA als Ganzzahl. Wenn Dir die Auflösung in
> dA (ja, Dezi-Ampere, z. B. 54 dA = 5,4 A) reicht, kommst Du sogar mit
> 16-bit-Variablen aus:
>
1
> uint16_t current;
2
> current = 55 * ADCW;
3
> current += 128;
4
> current >>= 8;
5
>

Viele Antworten in kurzer Zeit. Danke erstmal.

Ich finde die Idee mit den dA am Besten, irgendwie hatte ich ein Brett 
vor dem Kopf, vorallem weil der Schwellwert (ist veränderbar via 
CV-Programmierung) im EEPROM ebenfalls als dA abgelegt ist. Hätte man 
auch sofort so umrechnen können anstatt alles hin und herzuteilen.

Allerdings die Berechnung ist glaube nicht korrekt. Wenn ich einen 
ADC-Wert von 120 habe, dann entspricht dies 2,578A.

Mit der obigen Berechnung komme ich auf 26,28dA.
Ich verstehe auch die +128 nicht, wenn ich die nicht habe, dann habe ich 
25,78dA dass passt schon besser.

Die korrekte Berechnung müsste doch so lauten:
1
uint16_t current = ((55 * ADCW) / 256);

BTW: Macht es einen Unterschied ob ich um 8 bit schiebe oder teile? 
Müsste der Compiler doch eigentlich anpassen das ganze, oder?

von Falk B. (falk)


Lesenswert?

@ Marius Dege (2009marius15)

>Ich finde die Idee mit den dA am Besten, irgendwie hatte ich ein Brett
>vor dem Kopf, vorallem weil der Schwellwert (ist veränderbar via
>CV-Programmierung) im EEPROM ebenfalls als dA abgelegt ist.

Dann reicht sogar eine 8 Bit Variable als Ergebnis. Die Rechung

>Allerdings die Berechnung ist glaube nicht korrekt. Wenn ich einen
>ADC-Wert von 120 habe, dann entspricht dies 2,578A.

Stimmt.

>Mit der obigen Berechnung komme ich auf 26,28dA.

Stimmt auch, ist halt gerundet.

>Ich verstehe auch die +128 nicht,

Das ist zum Runden. 128/256 = 0,5. Wenn man die addiert, wird 
mathematisch korrekt gerundet. Ist aber akademisch.

>uint16_t current = ((55 * ADCW) / 256);

>BTW: Macht es einen Unterschied ob ich um 8 bit schiebe oder teile?
>Müsste der Compiler doch eigentlich anpassen das ganze, oder?

Wenn der Compiler was taugt, wird er aus /256 automatisch ein >> 8 
machen.

von Jonas B. (jibi)


Lesenswert?

>Was willst Du mit dieser elendig langen Rechenorgie, noch dazu mit 2
>schnarchlahmen Divisionen.

geteilt durch 2^10 wird doch bestimmt durch ein shift ersetzt...

GRuß J

von Falk B. (falk)


Lesenswert?

@ Jonas Biensack (jibi)

>>Was willst Du mit dieser elendig langen Rechenorgie, noch dazu mit 2
>>schnarchlahmen Divisionen.

>geteilt durch 2^10 wird doch bestimmt durch ein shift ersetzt...

Bei einer Fließkommadivision? Jaja, theoretisch wäre das möglich, da ja 
auch die Fließkommadarstellung zur Basis 2 erfolgt, aber praktisch glaub 
ich das eher nicht, schon gar nicht auf dem AVR, wo das alles per 
Software emuliert wird. Und wenn schon Fließkomma, dann wäre es eine 
Subtraktion des Exponenten um 10 und KEIN Shift . . .!

von Jonas B. (jibi)


Lesenswert?

>Bei einer Fließkommadivision?

Hab ich übersehen...

GRuß j

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.