Forum: Mikrocontroller und Digitale Elektronik ARM7: ADC Wandler 16 Bit ?


von Christian J. (elektroniker1968)


Lesenswert?

Hallo,

bei der Spielerei mit dem AD Wandler des LPC2138 fiel mir was 
Sonderbares auf: Das 32 Bit Register AD0DR0 (=Kanal von Pin AD0.0) 
enthält ganz oben 11 und muss daher maskiert werden und weiterhin spuckt 
er 16 Bit Wert raus! Dabei ist die Auflösung doch nur 10 Bit, also bis 
1023.

Was weiterhin auffiel ist, dass die Taktfrequenz von 4,5 Mhz bei 11 
Cycles Sampling viel zu hoch ist, die Werte stimmen nicht. Erst wenn ich 
wesentlich weiter runter gehe, also den APB Takt / 5 teile (~ 2,7 Mhz) 
stimmen die Zahle und vor allem "flattern" sie nicht mehr. Dreht man 
höher hat man immer wieder Ausreisser und der maximale Wert wird nicht 
erreicht.

Hat da schonmal jemand Erfahrungen gesammelt?

Hier die Configuration:

1
unsigned int ADC_Init(unsigned int channel_mask)
2
{
3
    // Pin konfigurieren
4
    PINSEL0 |= (0x01<<22);
5
    PINSEL1 |= (0x01<<22);
6
7
    // Kanal 0 v AD0, CLKDIV = 1:5; Burst Mode, CLKS = 11, PDN = ON
8
    AD0CR = (channel_mask) | (4<<AD0CR_CLKDIV_BIT) | (0<<AD0CR_CLKS_BIT) | (1<<AD0CR_BURST_BIT)
9
            | (1<<AD0CR_PDN_BIT);
10
   
11
12
}

von Michael G. (linuxgeek) Benutzerseite


Lesenswert?

Was haeltst Du davon, mal ins Datenblatt zu schauen, da steht das mit 
Sicherheit alles drinnen.

von Christian J. (elektroniker1968)


Lesenswert?

das habe ich natürlich vorher studiert, BEVOR ich diese Frage hier 
stellte. Und da steht nichts drin, da stehen auch keine Sample Zeiten 
drin, da steht eigentlich nichts drin was da rein gehört, denn ein AD 
Wandler ist etwas komplizierter, der braucht Sample & Hold Zeiten. Das 
Datenblatt des ARM7 von Phlips ist wirklich "arm".

Und wenn man den GCC verwendet, dann wundert man sich auch, warum eine
1
 for (i=0;i<10000000;i++)

Schleife als Zeitsteuerung plötzlich rasend schnell durchlaufen wird, 
egal welcher Wert, wenn man den Optimization Level auf 3 setzt.  Ein 
Blick in den Assembercode: Die Schleife ist weg, existiert nicht mehr. 
Der optimiert dann nämlich einfach die ganze Schleife weg und setzt i 
nur noch auf den grössten Wert, was ich schlichtweg krank finde.


Gruss,
Christian

von Michael U. (amiga)


Lesenswert?

Hallo,

was erwartest Du?

Du sagst dem Compiler, er soll optimieren auf minimale Laufzeit oder 
Codegröße, er macht genau das und Du maulst...
Ein solche Schleife macht nichts, außer Platz im Code kosten und unnütz 
Rechenzeit zu verbraten, das Ergebnis ist eindeutig absehbar, also setzt 
er es gleich.

Solche Busy-Loops waren schon zu meinen Z80 oder 6502-ASm-Zeiten sehr 
unbeliebt, weil in dieser Zeit nichts anderes gemacht werden konnte und 
vor allem keine User-Aktionen beachtet werden konnten.

Du kannst natürlich auch dort Deinen Willen durchsetzen und den Compiler 
austricksen, das wird mindestens 1x die Woche hier erklärt.

Gruß aus Berlin
Michael

von Michael U. (amiga)


Lesenswert?

Hallo,

Nachtrag:

Ich hab mal interessehalber in das User-Manual geschaut,
logisch da evtl. Bits gesetzt, Bit31 DONE, Bit30 OVERRUN, 26:24 CHN 
können/müssen natürlich gesetzt sein.

Außerdem sind bei mir Bit15:6 sehr wohl 10 Bit und keine 16 für das 
Ergebnis V/Vref.

Den Rest habe ich mir nicht angeschaut, wird aber wohl auch 
drinstehen...

Gruß aus Berlin
Michael

von Michael G. (linuxgeek) Benutzerseite


Lesenswert?

Christian J. wrote:
> Und wenn man den GCC verwendet, dann wundert man sich auch, warum eine
>
>
1
 for (i=0;i<10000000;i++)
>
> Schleife als Zeitsteuerung plötzlich rasend schnell durchlaufen wird,
> egal welcher Wert, wenn man den Optimization Level auf 3 setzt.

Vollkommen klar, da es ein nutzloses Konstrukt ist wenn der 
Schleifenkoerper leer ist.

von let (Gast)


Lesenswert?

hihi, womit wir wieder beim ominösen 'volatile' wären ;)

Aber den Punkt mit dem ADC kann ich nicht nachvollziehen. Wie kommst
du auf 16Bit?

von Christian J. (elektroniker1968)


Lesenswert?

Hallo,

der User oben hat das falsch verstanden, er meint das AD0GDR Register, 
wo unter 15:6 das RESULT steht. Das Ergebnis einer Operation wird in 
zwei Registern abgelegt, in jenem ist es zwischen anderen Bits 
eingemauert.

Es gibt aber noch die ADxRx Register, die sind übrigens nicht im LPC 
Headerfile mit drin (warum weiss ich nicht). Dort stehen die Ergebnisse 
jedes ADC einzeln drin. Im Manual von Philips ist das auf der Seite 212, 
ADDR0 bis ADDR7, jeweils für beide Wandler.

Den ADC einstellen reduziert sich auf das Bitweise Setzen des ADCFG 
Registers im Burst Mode, dann rennt das Viech los und scannt fortlaufend 
in einer Schleife alle gesetzten Pins, die im PINSEL als AD deklariert 
wurden. Man muss nur noch aus den besagten Registern auslesen und 
braucht sich um nichts weiter zu kümmern. Der Burst Mode kostet ca 2mA 
mehr an Strom, die die Platine zieht.

Hier der Code für einen Pin am AD0.0
1
// Das AD Poti an AD0.0 auslesen
2
void AD0_0_Init(void)
3
{
4
    unsigned int apb,teiler;
5
6
    // Pin AD0.0 konfigurieren
7
    PINSEL0 |= (0x01<<22);
8
    PINSEL1 |= (0x01<<22);
9
    
10
    // richtige APB Frequenz ermitteln < als 4.5 Mhz
11
    apb = PLL_GetAPBClock();
12
    teiler = 1;
13
    while ((apb/teiler > 4500000) && (teiler<10))
14
      teiler++;
15
16
    // Kanal 0 v AD0, CLKDIV = 4; Burst Mode, CLKS = 11, PDN = ON
17
    AD0CR = 1 | ((teiler-1)<<AD0CR_CLKDIV_BIT) | (0<<AD0CR_CLKS_BIT) | (1<<AD0CR_BURST_BIT)
18
            | (1<<AD0CR_PDN_BIT);
19
}

wird ausgelesen mit
1
debug_printf("Kanal 0 = %u \n",(AD0DR0 & 0xffff)>>6);

Dort, im AD0DR0 (32 Bit) stehen 16 Bit Werte drin (obere 2 Bits sind 
auch gesetzt). Nachdem ich damit etwas gespielt habe, habe ich bemerkt, 
dass die letzten 4 Bit für die Tonne sind, die floaten nur herum. 
Maximal 12 Bit aber auch nur so eben. Das Floaten wird weniger, wenn man 
den uC ohne PLL laufen lässt und die APB Taktrate weit runter dreht, 
klar, EMV lässt grüssen, 3.3V / 4096 sind nur noch 800uV.

Zum GCC Compiler: Plötzlich läuft mein "Flash Release", vorher kamen 
immer seltsame Fehlermeldungen des Assemblers, die undeutbar waren. Und 
mit Optimization Level 3 reduziert sich der Code von 3400 Bytes auf 1600 
Bytes runter. Das Wörtchen volatile vorsichtshalber mal etwas öfter 
verwendet und die Funktionen auf static gesetzt.

Insgesamt muss ich eines sagen: Der ARM mit Crossworks hat einen 
gewissen Suchtfaktor entwickelt :-) Mit dem bunten Editor macht es 
einfach Spass sauberen Code zu schreiben.

Gruss,
Christian

von let (Gast)


Lesenswert?

Nun, ich habe gerade kein aktuelles Datenblatt für den 2138.
Beim 2368 stehen die Daten an Position 15:6 in den Datenregistern.

Die separaten Register von die ADC Kanäle sind soweit ich weiß
erst mit dem 2138/01 eingeführt worden. Wenn du also so einen
hast, kannst du auch die FIO-Register der GPIO benutzen.
(F)IOPIN kann dann auch beschrieben werden.

Ich nehme an das du ein Headerfile vom 2138 verwendest. Die
zusätzlichen ADC- und FIO-Register fehlen dort.
FIO muß vor Verwendung aber aktiviert werden. Beim 2368 geht das
mit 'SCS |= GPIOM;'

>Mit dem bunten Editor macht es
>einfach Spass sauberen Code zu schreiben.
Du kennst also Eclipse noch nicht? Dann aber ran ;)

von let (Gast)


Lesenswert?

Nachtrag: FIO enable beim 2138/01 mit
1
SCS = (1<<0) | (1<<1);

Bin gerade zu faul um nachzusehen warum das anders ist.

von Christian J. (elektroniker1968)


Lesenswert?

Hmm....

Was seltsam ist, dass ich einen Fehler "Warning: Writeback of base 
register ist UNPREDICTABLE" bekomme, wenn ich die LED Funktion aus dem 
Timer IRQ heraus aufrufe... komisch. Muss man das was beachten, wenn man 
aus einem ISR eine andere Routine aufruft? Anm: Die LED Routine wird 
nirgendwo sonst aufgerufen, damit es keine Rekursion gibt.
1
// LED schalten (1=ein, 0=aus)
2
void LED_ctrl(char led_rot, char led_grn)
3
{
4
  // Anm: FIOSET ist R/W, FIOCLR ist RO 
5
6
  // Rote LED
7
  if (led_rot) // Ein
8
    FIO0CLR = LED_ROT_MASK; 
9
  else         // Aus
10
    FIO0SET = LED_ROT_MASK;
11
12
  // Grüne LED...
13
  if (led_grn) // Ein
14
    FIO0CLR = LED_GRN_MASK; 
15
  else        // Aus
16
    FIO0SET = LED_GRN_MASK;
17
}

Der IRQ
1
void __attribute__ ((interrupt("IRQ"))) timer0ISR(void)
2
{
3
  static unsigned char counter;
4
  
5
  if (++counter %2 ==0)
6
      LED_ctrl(1,0);
7
  else
8
    LED_ctrl(0,1);
9
10
   // IR Flag des M0 Match Registers löschen (=setzen)
11
   T0IR |= T0IR_MR0_MASK;
12
 
13
  VICVectAddr = 0;       // Acknowledge Interrupt
14
}

Klicke ich auf die Fehlermeldung zeigt er mir die Stelle im Asm Code an:
1
timer0ISR:
2
.LFB12:
3
  .loc 1 234 0
4
  @ Interrupt Service Routine.
5
  @ args = 0, pretend = 0, frame = 0
6
  @ frame_needed = 0, uses_anonymous_args = 0
7
  sub  lr, lr, #4
8
  stmfd  sp!, {r0, r1, r2, r3, ip, lr}
9
.LCFI4:
10
  .loc 1 237 0
11
  ldr  r3, .L64
12
  ldrb  r2, [r3, #0]  @ zero_extendqisi2
13
  .loc 1 238 0
14
  mov  r0, #1
15
  .loc 1 237 0
16
  add  r2, r2, r0
17
  ands  r1, r2, r0
18
  strb  r2, [r3, #0]
19
  .loc 1 240 0
20
  movne  r0, #0
21
  movne  r1, #1
22
  bl  LED_ctrl
23
  .loc 1 243 0
24
  ldr  r2, .L64+4
25
  ldr  r3, [r2, #0]
26
  orr  r3, r3, #1
27
  str  r3, [r2, #0]
28
  .loc 1 245 0
29
  mov  r2, #0
30
  mvn  r3, #0
31
  str  r2, [r3, #-4047]
32
  .loc 1 246 0
33
  ldmfd  sp!, {r0, r1, r2, r3, ip, lr}^
34
.L65:
35
  .align  2

ldmfd  sp!, {r0, r1, r2, r3, ip, lr}^

ist rot unterschlängelt.

von Robert Teufel (Gast)


Lesenswert?

Ein paar Worte zum ADC.

Natuerlich ist es ein 10-bit Wandler und man kann den verschieden 
"anlehnen", also das LSB auf die Stelle "0" oder das MSB auf die 
hoechste Stelle des 16-bit (halb)Wortes.
Es sollen ja auch noch 12-bit Wandler nachkommen, da laesst sich 
Kompatibilitaet nur erreichen mit MSB aligned. Deshalb wurde das so 
gemacht wie es ist. Natuerlich kannst Du die WErte auch als 16-bit Werte 
verwenden, allerdings werden sich die 6 LSB nie veraendern.

Zum Thema Geschwindigket des ADC, das haengt natuerlich auch von der 
Impedanz Deiner Quelle ab. Bei einer niederohmigen Quelle laesst sich 
der ADC sehr wohl bei der angegebenen Geschwindigkeit betrieben.

Robert

von Christian J. (elektroniker1968)


Lesenswert?

Hallo,

nur als Info: Es ändern sich alle 16 Bits aber die letzten sind nicht 
brauchbar.

Nochmal die Frage: Muss man etwas beachten wenn man aus einer ISR eine 
andere Routine aufruft? Ich krieg da nämlich eine Fehlermeldung, erst 
wenn ich die Inhalte der aufgerufenen Routine in die ISR kopiere klappt 
es.

von let (Gast)


Lesenswert?

Wüßte nicht das es da etwas zu beachten gäbe. Aber der GCC hat
ein Problem mit der Codeerzeugung für ISR-Routinen. Zumindest im
Thumb-Mode funktioniert das nicht und Dateien die einen
ISR-Handler enthalten müssen im ARM-Mode übersetzt werden.

Es gibt zwei Alternativen dazu:
1. Man deklariert die Funktion als 'naked' und kümmert sich
selbst per ISR_BEGIN/ISR_END Makros (inline-ASM) um die Register
und Rücksprungadressen (Holzhammer-Methode).

2. Man baut einen ISR-Handler in den Startup-Code ein der den
Handler in C als normale Funktion aufruft. So machen es einige
Beispiele in der Sammlung von Martin Thomas (die ich übernommen
habe).

Soweit ich weiß hat Rowley auch Makros für ISR-Handler.

von Christian J. (elektroniker1968)


Lesenswert?

Hallo,

also die Holzhammer Methode vergessen wir mal lieber :-) Bei einem PIC 
ja aber nicht bei einer derart komplexen CPU. Ich habe mal gesucht nach 
dem Umsschalter für ARM und THUMB Code aber leider nichts gefunden. 
Weiss nicht ob das ein #pragma ist oder sowas.

Vielleicht wäre eine Seite mit Codebeispielen ganz gut, bisher wurde ich 
da nicht fündig. Nicht die grossen Projekte sonder die kleinen, die 
bestimmte Funktionen demonstrieren.

Was sicherlich noch interessant sein dürfte wären Möglichkeiten Daten im 
Flash abzulegen, etwa Messwerte. libmem bietet sich hier an. Aber ich 
verlasse das Thema des Threads. Die Wissensneugier ist gross aber es 
fehlen einfach die Beispiele und die Doku kann man insgesamt vergessen.

Ach ja... ein gutes Beispiel, wie man eine Library erzeugt wäre noch 
toll, damit ich meine mühsam Universalfunktionen später einmal mit 
einbinden kann, so wie unter Visual Basic fertige .dll eingebunden 
werden können, wenn man deren Funktionssatz kennt. Bisher habe im im 
Linker von Rowley dazu noch keine Möglichkeit gefunden den Object Code 
einzubinden.

Gruss,
Christian

von Robert T. (robertteufel)


Lesenswert?

Codebeispiele (nicht fuer GCC aber portierbar)
Direkt von der NXP Webseite:
http://www.standardics.nxp.com/support/documents/microcontrollers/zip/code.bundle.lpc213x.lpc214x.uvision.zip
Das ist fuer Keil geschrieben aber immerhin Beispielcode.

Robert

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.