www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Ich suche eine schnelle ADC / DAC Berechnungs Routine für eine 32 Bit Plattform


Autor: Sebastian B. (mircobolle)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

ich habe hier eine DAC-Routine, die mir eine "bit-codierte" Spannung am 
Ausgang ausgeben soll. Die Auflösung ist 1 mV / Bit.

Die Plattform auf der diese Routine läuft ist ein 32 Bit Controller.
Der DAC sowie der ADC arbeiten mit 12 Bit.

Ich möchte nun erreichen, dass beide Routinen möglichst schnell diese 
Umrechnung vornehmen, damit ich eine höhere Abtastrate / Ausgabe Rate 
erreichen kann.

Als Beispiel poste ich jetzt den Code für meinen externen DAC (via SPI). 
Kernpunkt sind hier die Umrechnungen der Spannung. Falls jemand 
Verbesserungsmöglichkeiten sieht, wie die Routine schneller laufen kann 
wäre ich sehr dankbar!!
//-----------------------------------------------------------------------------
// Funktionsname: DAC_Update_A
// Beschreibung:  Setze den Ausgang A des DAC 
// Uebergabe:     u16 u16_output_a 
// Rueckgabe:     -
// Zeit (Zyklen): -
//-----------------------------------------------------------------------------
void       DAC_Update_A        (u16 u16_output_a){
  u16 u16_dac       = 0;
  u8  u8_dac_msb;
  u8  u8_dac_lsb;
  u32 u32_dac_code;
  u16 u16_dac_code;
  
  /*
      CODE = Soll_Spannung * 0xFFF /(2 * REF)
  */
  
  if (u16_output_a <= u16_Reference){
    u32_dac_code = (u32) (u16_output_a * (u32) 0x0FFF);
    u16_dac_code = (u16) (u32_dac_code / u16_Reference);
    
    u16_DAC_Output_A = u16_output_a; 
    
    u16_dac = u16_dac | (e_Speed      << SPD_SHIFT);   /* Setze Speed Bit     */
  
    u16_dac = u16_dac | R1_MASK;                       /* Setze R1 Bit */
    
    u8_dac_msb = (u8) ((u16_dac & 0xF000)>>8);
    u8_dac_msb = (u8) (u8_dac_msb | ((u16_dac_code & 0x0F00)>>8));
    
    u8_dac_lsb = (u8) (u16_dac_code & 0x0FF);

    SPI_WriteData(2,u8_dac_msb,false,false,0);    /* Write to Control Register   */
    SPI_WriteData(2,u8_dac_lsb,true,false,0);     /* Set Output value      */    
        
  } else {
    /* OUT OF RANGE */
  }
  
}

DANKE

PS:
Aktuell benötigt die Routine für die Ausführung zwischen 58 -  98 
Mikrosekunden.

Autor: Sebastian B. (mircobolle)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Die Typ-Definitionen habe ich vergessen:
/* Globale Typedefinitionen--------------------------------------------------*/
typedef unsigned char u8;                  // unsigned 8 bit type definition 
typedef signed char s8;                    // signed 8 bit type definition   
typedef unsigned short u16;                  // unsigned 16 bit type definition
typedef signed short s16;                    // signed 16 bit type definition  
typedef unsigned int u32;                 // unsigned 32 bit type definition
typedef signed int s32;                   // signed 32 bit type definition  
typedef float fl;                          // floating point, length 4 byte  
typedef double db;                         // floating point, length 8 byte  


ausserdem
u16_Reference = k_DAC_Reference_External * 2; 

und
const u16 k_DAC_Reference_External  = 5477; /* 5.477 Volt */

Autor: Matthias Lipinsky (lippy)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Die Auflösung ist 1 mV / Bit.
> 5477; /* 5.477 Volt */
> 12bit

Hm.. Passt irgendwie nicht..


Achja, Zur Berechnung immer 2^N Werte nehmen, nicht 2^N-1.

Autor: Matthias Lipinsky (lippy)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>      CODE = Soll_Spannung * 0xFFF /(2 * REF)
..
>    u32_dac_code = (u32) (u16_output_a * (u32) 0x0FFF);
>    u16_dac_code = (u16) (u32_dac_code / u16_Reference);


Hm..
Was ist mit folgendem Ansatz:

         0x1000       2^16
CODE = -----------  ------   SollSpannung
         2*UREF       2^16

...

         1      4096 * 2^16
CODE = -----  -------------  SollSpannung
        2^16    2 * UREF
         1
     = -----  KONSTANTE      Sollspannung
        2^16
CODE = (u16) ( (KONSTANTE * (u32)Sollspannung) >> 16 );

mit UREF=5477 folgt: KONSTANTE = 24506

Setzt man die Formel 5477(mV) ein, so folgt: CODE=2048
Ein u32-Überlauf findet erst bei Sollspannung>=175261(mV) statt.

Damit umgehst du dir die komplizierte Division.

Autor: Sebastian B. (mircobolle)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Matthias Lipinsky wrote:

>
> CODE = (u16) ( (KONSTANTE * (u32)Sollspannung) >> 16 );
> 
>
> mit UREF=5477 folgt: KONSTANTE = 24506
>
> Setzt man die Formel 5477(mV) ein, so folgt: CODE=2048
> Ein u32-Überlauf findet erst bei Sollspannung>=175261(mV) statt.
>
> Damit umgehst du dir die komplizierte Division.

Vielen Dank für die schnelle Antwort.
Hätte nicht gedacht, dass man da noch so viel rausholen kann. Du hast 
eine komplette Division eingesparrt. Das macht Performanz-technisch 
einiges aus !!!
Werde das ganze nochmal durchrechnen mit Bitbreiten, Überlaufen etc.

Die Herleitung hast du wirklich sehr schön verdeutlicht.
Nochmals vielen Dank.

Autor: Matthias Lipinsky (lippy)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Das macht Performanz-technisch einiges aus !!!

Ob und wieviel das ausmacht, musst du mal prüfen. Auch die Überläufe. 
Kann ja sein, dass ich einen Schusselfehler gemacht habe. Das Prinzip 
ist aber offenslichtlich rübergekommen.

Autor: Sebastian B. (mircobolle)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Matthias Lipinsky wrote:
>>Das macht Performanz-technisch einiges aus !!!
>
> Ob und wieviel das ausmacht, musst du mal prüfen. Auch die Überläufe.
> Kann ja sein, dass ich einen Schusselfehler gemacht habe. Das Prinzip
> ist aber offenslichtlich rübergekommen.

Ergebnisse sind da:

die routine rechnet nun nach [Matthias Lipinsky] so:
    u32_dac_code = (u32) (u32_Reference * u16_output_a);
    u16_dac_code = (u16) (u32_dac_code >> 16); 

Die Routine braucht jetzt konstant 54 Mikrosekunden !
Im Vergleich zu vorher 98 Mikrosekunden (Spitze) ist das eine 
Verbesserung um fast Faktor 2 !!!

Besten Dank für die hervorragende Unterstützung.

Autor: Matthias Lipinsky (lippy)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Die Routine braucht jetzt konstant 54 Mikrosekunden !

Wie hast du das ermittelt?

Das klassische Debugging-Verfahren?
..Portpin togglen..?

Autor: Sebastian B. (mircobolle)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Matthias Lipinsky wrote:
>>Die Routine braucht jetzt konstant 54 Mikrosekunden !
>
> Wie hast du das ermittelt?
>
> Das klassische Debugging-Verfahren?
> ..Portpin togglen..?

Genau.
Hier setzte ich die "Messpunkte" mit dem Macro:
mBASICDEF_Set_MeasurePoint()
           case PROTOCOL_AOUTPUT_1:
                       if (e_access == REMOTE_CONTROL_ACCESS_WRITE){
                          mBASICDEF_Set_MeasurePoint();
                          DAC_Update_A(u16_val);
                          mBASICDEF_ReSet_MeasurePoint();
                          RS232_PROTOCOL_SendResponse(PROTOCOL_STAT_SET_OK,&s_u8_current_pmt,1);                          
                       }else{
                          RS232_PROTOCOL_SendResponse(PROTOCOL_STAT_GET_OK,&u16_DAC_Output_A,2); 
                       }
                       break;  

das Makro selbst sieht so aus:
#define mBASICDEF_Set_MeasurePoint() (BASICDEF_BREAKPOINT=0)
#define mBASICDEF_ReSet_MeasurePoint() (BASICDEF_BREAKPOINT=1)

und BASICDEF_BREAKPOINT ist wie richtig vermutet ein Port-Pin ;-)


Interrupts sind nicht deaktiviert muss ich dazu sagen, aber wie gesagt 
ist das Muster auf dem Oszi konstant.

Autor: Benedikt K. (benedikt) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Was ist das eigentlich für ein lahmer Controller?
Ich kenne jetzt nicht wirklich viele 32bit Controller, aber die die ich 
kenne, lasen sich alle mit etlichen 10MIPs takten, und die haben alle 
einen Hardaremultiplizierer, der nur wenige Takte braucht.
Daher kommen mir ein paar 10µs für die Berechnung sehr lange vor.
Ich würde daher darauf tippen, dass die SPI Schnittstelle die 
eigentliche Bremse ist.

Autor: Matthias Lipinsky (lippy)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Dazu müsste man sich mal die Funktion
SPI_WriteData
ansehen...

Autor: Sebastian B. (mircobolle)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Matthias Lipinsky wrote:
> Dazu müsste man sich mal die Funktion
>
> SPI_WriteData
> 
> ansehen...

kein Problem.

Aber an SPI_WriteData dürfte es meiner Meinung nach nicht liegen, da die 
Funktion den Schreibauftrag nur entgegenimmt und in einen Ring-Puffer 
schreibt. Das eigentliche Versenden wird von der SPI-Interrupt Service 
Routine erledigt.

>Was ist das eigentlich für ein lahmer Controller?
MCF51QE128 von Freescale. Low-Power Controller.
Ich habe das identische System auf dem MC9S08QE128 und auf dem 
MCF51QE128 laufen lassen und erstaunlicherweise zieht der 32 Bit 
Controller im Vergleich zum 8 Bit Controller 20 mA WENIGER Strom.

Die interne Clock ist auf 36 kHz getrimmt.
Das ergibt nach den Clock Einstellungen einen Bus-Takt von 4,608 MHz, 
die CPU läuft mit einem Takt von BUSCLK * 2, also 9,216 MHz.
//-----------------------------------------------------------------------------
// Funktionsname: SPI_WriteData
// Beschreibung:  Sollen Daten ueber SPI uebertragen werden, wird diese
//                Funktion aufgerufen.
// 
//                b_RiseCsAfterTransmit gibt an, ob die CS Leistung NACH der
//                Uebertragung des Bytes HIGH gesetzt werden soll.
//                
//                Das Flag b_RS gibt an ob der RS Pin VOR einer Uebertragung auf
//                HIGH oder LOW gesetzt werden soll.
//             
//                Die Wartezeit u8_wait_time gibt an wie lange NACH der Versendung
//                eines Bytes gewartet werden soll. 
//
// Uebergabe:     SPI_Channel_e e_Channel, u8 u8_Data, bool b_RiseCsAfterTransmit,bool b_RS, u8 u8_wait_time
// Rueckgabe:     -
// Zeit (Zyklen): -
//-----------------------------------------------------------------------------
void SPI_WriteData (SPI_Channel_e e_Channel, u8 u8_Data, bool b_RiseCsAfterTransmit,bool b_RS, u8 u8_wait_time){
  
  if (e_Channel == 1){ 
    v_s_TxBuf_1[v_u8_TxWriteIndex_1].u8_data      = u8_Data;
    v_s_TxBuf_1[v_u8_TxWriteIndex_1].b_rise_cs    = b_RiseCsAfterTransmit;
    v_s_TxBuf_1[v_u8_TxWriteIndex_1].b_rs         = b_RS;
    v_s_TxBuf_1[v_u8_TxWriteIndex_1].u8_wait_time = u8_wait_time;
    
    
    v_b_TxBufEmpty_1                              = false;
    
    v_u8_TxWriteIndex_1 += 1;
    v_u8_TxWriteIndex_1 = v_u8_TxWriteIndex_1 % MAX_BUFFER_COUNT_1;
    
    /* Wenn Transmit Buffer Empty Interrupt deaktiviert UND wenn Transmitfunktion gerade NICHT aktiv */
    if ((SPI1C1_SPTIE == 0)&&(v_u8_busy_1 == false)){       
         SPI1C1_SPTIE                      = 1;    /* Interrupts wieder aktivieren */
    }
  }
  if (e_Channel == 2){
    v_s_TxBuf_2[v_u8_TxWriteIndex_2].u8_data      = u8_Data;
    v_s_TxBuf_2[v_u8_TxWriteIndex_2].b_rise_cs    = b_RiseCsAfterTransmit;
    v_s_TxBuf_2[v_u8_TxWriteIndex_2].b_rs         = b_RS;
    v_s_TxBuf_2[v_u8_TxWriteIndex_2].u8_wait_time = u8_wait_time;
    
    
    v_b_TxBufEmpty_2                              = false;
    
    v_u8_TxWriteIndex_2 += 1;
    v_u8_TxWriteIndex_2 = v_u8_TxWriteIndex_2 % MAX_BUFFER_COUNT_2;
    
    /* Wenn Transmit Buffer Empty Interrupt deaktiviert UND wenn Transmitfunktion gerade NICHT aktiv */
    if ((SPI2C1_SPTIE == 0)&&(v_u8_busy_2 == false)){       
         SPI2C1_SPTIE                      = 1;    /* Interrupts wieder aktivieren */
    }
  }
}

Autor: Matthias Lipinsky (lippy)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hier gibts doch Kanidaten für viel Zeit:
v_u8_TxWriteIndex_1 = v_u8_TxWriteIndex_1 % MAX_BUFFER_COUNT_1;
...
v_u8_TxWriteIndex_2 = v_u8_TxWriteIndex_2 % MAX_BUFFER_COUNT_2;

Wie groß sind denn di beiden Konstanten?
Das ist doch die Pufferlänge-1, stimmts?

Nimm da 2^N-1 Werte, dann kansnt du aus dem Modulo ein AND machen

Autor: Benedikt K. (benedikt) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sebastian B. wrote:
> MCF51QE128 von Freescale. Low-Power Controller.

OK, also eine CISC CPU. Das erklärt dann, wieso es etwas länger dauert.

Außer wenn SPI sehr langsam getaktet ist, kommt mir die Sende Routine 
ineffizient vor:
Ich gehe mal von 2 Takten pro C-Befehl aus (was eher wenig ist), selbst 
dann benötigt SPI_WriteData rund 30 Takte. Wenn die SPI Schnittstelle 
also mit mehr als 2,6MHz läuft, dann ist es schneller die Daten direkt 
zu Senden und auf das Ende zu warten (bzw. währenddessen andere 
Berechnungen zu machen, was man in C nicht immer wirklich gut festlegen 
kann, da der Compiler die Reihenfolge der Befehle öfters umsortiert).

Autor: Sebastian B. (mircobolle)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Matthias Lipinsky wrote:
> Hier gibts doch Kanidaten für viel Zeit:
>
>
> v_u8_TxWriteIndex_1 = v_u8_TxWriteIndex_1 % MAX_BUFFER_COUNT_1;
> ...
> v_u8_TxWriteIndex_2 = v_u8_TxWriteIndex_2 % MAX_BUFFER_COUNT_2;
> 
>
> Wie groß sind denn di beiden Konstanten?
> Das ist doch die Pufferlänge-1, stimmts?
>
> Nimm da 2^N-1 Werte, dann kansnt du aus dem Modulo ein AND machen
/* lokale Definitionen-------------------------------------------------------*/
#define MAX_BUFFER_COUNT_1 128

stimmt, bei "modulo" wird ja auch nochmal richtig gerechnet ...

das lasse ich mir nochmal durch den Kopf gehen.

>Außer wenn SPI sehr langsam getaktet ist, kommt mir die Sende Routine
>ineffizient vor:
>Ich gehe mal von 2 Takten pro C-Befehl aus (was eher wenig ist), selbst
>dann benötigt SPI_WriteData rund 30 Takte. Wenn die SPI Schnittstelle
>also mit mehr als 2,6MHz läuft, dann ist es schneller die Daten direkt
>zu Senden und auf das Ende zu warten (bzw. währenddessen andere
>Berechnungen zu machen, was man in C nicht immer wirklich gut festlegen
>kann, da der Compiler die Reihenfolge der Befehle öfters umsortiert).

Auch das werde ich mir mal überlegen und ob es in meinem System "direkt" 
zu realisieren wäre. Im Moment schreibe ich ja alle "Aufträge" in einen 
Puffer, der abgearbeitet wird. Jeder Auftrag setzt u.a. auch Port-Pins, 
welche spezielle Signale für die Hardware darstellen.

Man muss dazu sagen, dass das System ja eigentlich für einen 8 Bit 
Controller konzipiert war. Deshalb fand ich diese Entkopplung von 
Sende-Auftrag und eigentlicher Abarbeitung in der ISR ganz vernünftigt. 
Nun läuft das System allerdings auf einem 32 Bit Controller und ich 
versuche noch so viel Performanz wie möglich rauszukitzeln.

Vielen Dank für die Beiträge!!

Autor: Matthias Lipinsky (lippy)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
/* lokale Definitionen-------------------------------------------------------*/
#define MAX_BUFFER_COUNT_1 128
wird diese Konstante dann so verwendet:
StrukturFIFO   v_s_TxBuf_2[MAX_BUFFER_COUNT_1];

Wenn ja, dann hast du ja 0..128, also 129 Array-Elemente.

Mach da mal ne 127 draus, und aus dem % ein AND.

Das sollte nochmal einiges an Zeit sparen.

Autor: Benedikt K. (benedikt) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Matthias Lipinsky wrote:
>
> /* lokale
> Definitionen-------------------------------------------------------*/
> #define MAX_BUFFER_COUNT_1 128
> 
> wird diese Konstante dann so verwendet:
>
> StrukturFIFO   v_s_TxBuf_2[MAX_BUFFER_COUNT_1];
> 
>
> Wenn ja, dann hast du ja 0..128, also 129 Array-Elemente.

Nein!

Ein Array array[4] hat die Elemente array[0], array[1], array[2] und 
array[3] also 4 insgesamt.
Das passt schon. Wenn der Compiler intelligent ist, macht er dann auch 
automatisch ein &127 aus dem %128. Nur leider sind nicht alle Compiler 
so intelligent.

Autor: Matthias Lipinsky (lippy)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Nein!

>Ein Array array[4] hat die Elemente array[0], array[1], array[2] und
>array[3] also 4 insgesamt.

Ja. Schusselfehler.
(Ich war noch bei ST von Arbeit)


>Wenn der Compiler intelligent ist, macht er dann auch
>automatisch ein &127 aus dem %128.

Darauf verlasse ich mich nicht.

Autor: Sebastian B. (mircobolle)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Matthias Lipinsky wrote:
>
> /* lokale
> Definitionen-------------------------------------------------------*/
> #define MAX_BUFFER_COUNT_1 128
> 
> wird diese Konstante dann so verwendet:
>
> StrukturFIFO   v_s_TxBuf_2[MAX_BUFFER_COUNT_1];
> 

Guten Abend zusammen,

das "Verunden" mit 127 ist eine elegante Methoden den Überlauf von 127 
auf 0 zu bewerkstelligen.
v_u8_TxWriteIndex_1 += 1;
v_u8_TxWriteIndex_1 = v_u8_TxWriteIndex_1 & (MAX_BUFFER_COUNT_1-1);

Den einzigen Nachteil den ich hier sehe ist die "Eingeschränktheit" der 
Puffergroesse. Da diese immer das Format 2^N haben muss. Bei Modulo 
hingegen kann theoretisch jeder mögliche Wert des Datentyp angegeben 
werden.

Ich werde morgen mal messen was für eine Zeitersparnis der oben genannte 
Code bringt. ;-) Bin mal gespannt.

Viele Gruesse
Sebastian

Autor: Läubi .. (laeubi) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Sebastian B. wrote:
> Den einzigen Nachteil den ich hier sehe ist die "Eingeschränktheit" der
> Puffergroesse. Da diese immer das Format 2^N haben muss. Bei Modulo
> hingegen kann theoretisch jeder mögliche Wert des Datentyp angegeben
> werden.
Schnell und universell sind meist zwei dinge die sich nicht immer unter 
einen Hut bringen lassen... ;)

Autor: Uwissender (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Matthias Lipinsky (lippy):
> Achja, Zur Berechnung immer 2^N Werte nehmen, nicht 2^N-1.

wieso?

Autor: Uwissender (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
sry, plz ignore:

N=12 -> 2^12 -> 4096 -> 0x1000

Hex ist wohl noch nicht meine Stärke ...

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.