Helligkeit mit mbed auswerten

Schon kurz nachdem ich meinen mbed vor etwa einem Monat in den Händen hielt, bastelte ich die ersten kleinen Dinge zusammen, war nur nicht dazu gekommen, auch ein paar Beispiele zu veröffentlichen. Das hole ich jetzt nach. Zwar habe ich arbeitsbedingt inzwischen seit etwa einem halben Jahr mit den Cortex-M3-Controllern von NXP zu tun, aber bisher hatte ich nur verschiedene komplexe Problemlösungen programmiert und die ganz einfachen Dinge wie PWM, ADC usw. blieben auf der Strecke. Aber genau diese einfachen Dinge wie das Auswerten von Sensoren am Analog-Digital-Converter gehören nun mal zu den grundlegenden Aufgaben von Mikrocontrollern.

Zum Nachvollziehen des folgenden Beispiels benötigt man einen mbed (oder ein PCB mit LPC1768 und entsprechend angebundenen LEDs + sonstigen Pins) und einige wenige zusätzliche Bauteile. Die meisten davon dürften sich in jeder Elektronikbastelkiste finden oder schnell irgendwo auslöten lassen. Einen Fotowiderstand bekommt man für etwa 0.50-2.50€, es sind auch beliebige andere LDRs möglich - mit entsprechender Anpassung des Serienwiderstands. Der hier verwendete VT93N1 kostet bei Pollin 0.50€ und genügt den einfachen Anforderungen vollkommen.

Hier die genauen Bauteile:

  • 1x LDR VT93N1 (alias: Fotosensor, Helligkeitssensor, Lichtsensor, Lichtwiderstand)
  • 1x Kondensator im Bereich 1nF - 100nF
  • 2x Widerstand im Bereich 100-150 Ohm
  • 1x Widerstand ca. 10kOhm
  • 1x Widerstand ca. 17kOhm (Serienwiderstand zum LDR, andere Werte erfordern Anpassung der Vergleichswerte im Quelltext)
  • 1x Duo-LED oder 2 einzelne LEDs
  • 1x Taster
  • einige Steckkabel
  • 1x Steckbrett (Breadboard)

Hier die Schaltung:
Vorschau: mbed mit LDR auf Steckbrett Vorschau: Schaltung mit mbed, LDR, I²C Vorschau: Platine mit mbed, LDR, I²C, LCD-Port
Bitte nicht davon verwirren lassen, dass noch ein paar mehr Sachen (I²C, Display-Port, sonstiger Datenport) in der Schaltung drin sind. Auf der Platine ist noch eine Pinleiste mehr zu sehen, als in der Schaltung. Mit Hilfe des Steckbrettfotos sollte es problemlos möglich sein, die einfache Schaltung ohne die Zusatzdinge nachzubauen - als Orientierung kann man dann zusätzlich in die Schaltung sehen. Die Platine habe ich mir übrigens zusammen mit einigen anderen Schaltungen anfertigen lassen und erwarte die Lieferung aus Fernost in den nächsten 2 Wochen. Ich habe hierbei absichtlich nur 2 SMD-Bauteile verwendet und den Rest mit herkömmlichen Füßchen - ich habe einfach zu viele “alte” Bauteile, die man für derartige Testboards ruhig mal verbrauchen kann ;) Wer so eine Platine (bleihaltig verzinnt) für 5€ inkl. Versand haben will, eMail an mich. Ich bekomme davon 10 Stück (Mindestabnahmemenge) und brauche eigentlich nur 1-2 Stück.

Als Umgebung verwende ich wie im mbed-Artikel erwähnt die Yagarto-GCC-Toolchain und Tollos als Bibliothekssammlung. Letztlich verwende ich von Tollos allerdings nur die Initialisierung, das Ticksystem und den Debug über den virtuellen COM-Port. Auf GPIOs und andere Funktionen wie hier z.B. ADC greife ich direkt zu. Der Verzicht der vordefinierten Abstraktionen vereinfacht die Zugriffe, man muss sich nicht extra in das Bibliothekssystem einarbeiten und vor allem sind die Zugriffe meist deutlich schneller. Die definierten Pointer gelten allgemein für die LPC17xx-Reihe, vermutlich auch für die meisten anderen Cortex-M3-Controller von NXP und für die NXP Cortex M0 ist nur ein kurzer Blick ins Datenblatt mit Anpassung der Register nötig. Die beiden Dateien kommen einfach in das sample-Verzeichnis von Tollos.

Hier nun eine meiner kleinen Grundlagenbibliotheken, die smalltools.h:

// unit with some variables and often used functions
// designed for ARM Cortex-M3 NXP1768 (mbed)
// 2011 Norbert Schröder, Licence: GPL
 
////////////// Register begin /////////////////////////////////////////////////
volatile uint32_t* GPIO0_DIR = (uint32_t)0x2009C000; 	// LPC_GPIO0+0x00
volatile uint32_t* GPIO0_MASK= (uint32_t)0x2009C010;	// LPC_GPIO0+0x10
volatile uint32_t* GPIO0_PIN = (uint32_t)0x2009C014;	// LPC_GPIO0+0x14
volatile uint32_t* GPIO0_SET = (uint32_t)0x2009C018;	// LPC_GPIO0+0x18
volatile uint32_t* GPIO0_CLR = (uint32_t)0x2009C01C;	// LPC_GPIO0+0x1C
volatile uint32_t* GPIO1_DIR = (uint32_t)0x2009C020;	// LPC_GPIO1+0x00
volatile uint32_t* GPIO1_MASK= (uint32_t)0x2009C030;	// LPC_GPIO1+0x10
volatile uint32_t* GPIO1_PIN = (uint32_t)0x2009C034;	// LPC_GPIO1+0x14
volatile uint32_t* GPIO1_SET = (uint32_t)0x2009C038;	// LPC_GPIO1+0x18
volatile uint32_t* GPIO1_CLR = (uint32_t)0x2009C03C;	// LPC_GPIO1+0x1C
volatile uint32_t* GPIO2_DIR = (uint32_t)0x2009C040;	// LPC_GPIO2+0x00
volatile uint32_t* GPIO2_MASK= (uint32_t)0x2009C050;	// LPC_GPIO2+0x10
volatile uint32_t* GPIO2_PIN = (uint32_t)0x2009C054;	// LPC_GPIO2+0x14
volatile uint32_t* GPIO2_SET = (uint32_t)0x2009C058;	// LPC_GPIO2+0x18
volatile uint32_t* GPIO2_CLR = (uint32_t)0x2009C05C;	// LPC_GPIO2+0x1C
volatile uint32_t* PINSEL4   = (uint32_t)0x4002C010;	// Pin Function Select Register 4 (Port2, Pins 0-14)
volatile uint32_t* AD0CR     = (uint32_t)0x40034000;	// A/D 0 Control Register (UserManual 29.5.1)
volatile uint32_t* AD0GDR    = (uint32_t)0x40034004;	// A/D Global Data Register (UserManual 29.5.2)
//volatile uint32_t* PCLKSEL0= (uint32_t)0x400FC1A8;	// Takt für diverse Komponenten wie ADC, UART, PWM, ...
////////////// Register end ///////////////////////////////////////////////////
 
 
////////////// global var begin ///////////////////////////////////////////////
int debLevel=2;			// DebugLevel: 0=nothing; 1=error; 2=warn/values; 3=debuginfo
////////////// global var end /////////////////////////////////////////////////
 
 
////////////// prototype begin ////////////////////////////////////////////////
void LEDflash(uint32_t a);								// blink the 4 mbed-LEDs
void LEDflash2(uint32_t a);								// blink pin2.1 + pin2.0 (mbed pin25/26)
uint SW_pressed(void);									// switch at p0.16 (mbed p14)
void ADC_init(void);									// init AD0.5 - LPC1768 p1.31 (mbed p20)
uint32_t ADC5_read(void);								// read AD0.5 - LPC1768 p1.31 (mbed p20)
void LDR5_read(void);									// reads LDR on AD0.5, stores it in an array
void LDR_interpret();									// is called if switch pressed within LDR5_read, output of interpreted light-value
////////////// prototype end //////////////////////////////////////////////////
 
 
////////////// LED-Flash begin ////////////////////////////////////////////////
void LEDflash(uint32_t a){				// a*100ms blinken - überschreibt Port1-Defs !!!
 uint32_t p1=(1<<18)|(1<<20)|(1<<21)|(1<<23);				// mbed-LEDs an Port1
 *GPIO1_DIR=p1;
 *GPIO1_MASK=~p1;
 for(uint32_t i=0;i<a;i++){			// Let LED1 blink for 100ms; Bsp. für PIN statt SET/CLR
  *GPIO1_PIN=p1;					// ledSet(1, LED_ON);
  sysPause(50);
  *GPIO1_PIN=~p1;					// ledSet(1, LED_INVERT);
  sysPause(50);
 }
}
 
void LEDflash2(uint32_t a){			// pin2.1 + pin2.0 (mbed pin25/26)
 uint32_t p2=(1<<0)|(1<<1);
 *GPIO2_DIR=p2;
 *GPIO2_MASK=~p2;
 for(uint32_t i=0;i<a;i++){
  *GPIO2_PIN=(1<<0);				// LED an pin2.0
  sysPause(25);
  *GPIO2_PIN=(1<<1);				// LED an pin2.1
  sysPause(25);
  *GPIO2_PIN=(1<<1)|(1<<0);			// LEDs an pin2.0 + pin2.1
  sysPause(25);
  *GPIO2_PIN=0;						// LEDs aus
  sysPause(25);
 }
}
////////////// LED-Flash end //////////////////////////////////////////////////
 
 
////////////// Switch begin ///////////////////////////////////////////////////
uint SW_pressed(void){			// if switch pressed then return 1 else return 0
 uint p0=(1<<16);
 uint sw;
 *GPIO0_DIR&=~p0;			// switch at p0.16 (mbed p14)
 *GPIO0_MASK=~p0;
 sw=~*GPIO0_PIN;			// der Taster ist bei Standardeinstellungen lowaktiv und muss per Pull-Down auf Masse gezogen werden
 sw&=p0;
 sw>>=16;
// if(debLevel==3)debugf("%i\n",sw);
 return sw;
}
////////////// Switch end /////////////////////////////////////////////////////
 
 
////////////// ADC begin //////////////////////////////////////////////////////
// Quellen: UserManual Chapter 29 (S. 574)
// http://www.keil.com/forum/17115/
// http://www.embeddedrelated.com/groups/lpc2000/show/43628.php
int aLDR[10];								// aLDR[0] ist aktuelle Position im Helligkeits-Array, Werte [1..8] liegen zw. 0-4095, [9] ist Durchschnitt
 
void ADC5_init(void) {						// Aktivierung AD0.5 - LPC1768 p1.31 (mbed p20)
 LPC_SC->PCONP       |= (1<<12);			// Enable power to ADC block (PCADC-Bit)
 *AD0CR=(1<<5)|(4<<8)|(1<<21);//|(1<<24);	// S. 577: AD0.5; 4=0b100, also CLKDIV=5; PDN normal; Start conversion now auskommentiert
 LPC_PINCON->PINSEL3 |= (0x3<<30);			// PINSEL3 p1.31 in Bits 31:30 auf 0b11 für AD0.5 - wenn Fkt<>0b11 das Doppelbit zuvor per & löschen!
}
 
void ADC5_free(void){						// Gibt den AD0.5-Pin wieder frei
 *AD0CR&=~(1<<5);							// S. 577: AD0.5 aus der Liste der AD-Pins werfen
 LPC_PINCON->PINSEL3 &= ~(0x3<<30);			// PINSEL3 p1.31 in Bits 31:30 auf 0b00 für GPIO
}
 
void ADC_deactive(void){					// Save energy and deactivate the power for ADC
 *AD0CR&=~(1<<21);							// S. 574: PDN (Power Down)
 LPC_SC->PCONP       &= ~(1<<12);			// Disable power to ADC block (PCADC-Bit)
}
 
uint32_t ADC5_read(void){
 uint32_t regVal;
 *AD0CR |= (1<<24);							// Start conversion now
 while(1){									// wait until end of A/D convert
  regVal = *AD0GDR;
  if((regVal>>31)&1)break;
 }
 *AD0CR &= 0xF8FFFFFF;						// stop ADC now
 if((regVal>>30)&1)return(10000);			// Overrun
 regVal=(regVal>>4)&0xFFF;					// RESULT is on bits 15:4
 return(regVal); 
}
 
void LDR5_read(void){						// LDR on AD0.5
  uint32_t regVal;
  regVal=ADC5_read();
  if(regVal<4096){							// Overrun returns 10000, 3.3V gives about 4080
   if(debLevel==3)debugf("%i\n",regVal);
   aLDR[aLDR[0]]=regVal;
   if(aLDR[0]<8)aLDR[0]++;else aLDR[0]=1;	// aLDR-Zeiger hochschieben bzw. beim Ende des Arrays zurück auf 1
  }
 if(SW_pressed()==1)LDR_interpret();
}
 
void LDR_interpret(){						// LDR < 1024 dunkel, LDR < 2048 halbdunkel, LDR < 3072 hell, sonst sehr hell
 int x;
 aLDR[9]=0;									// Durchschnittswert der Helligkeit auf 0 setzen
 for(x=1;x<9;x++)aLDR[9]+=aLDR[x];			// ergibt also max 8*4096=2^15
 aLDR[9]>>=11;								// Auswertung auf Werte [0..15] = 2^4 skaliert, also SHR 11
 if(debLevel>=2){
  if(aLDR[9]==0)debugf("Helligkeit: Da sitzt was auf dem Sensor.\n");
  else if(aLDR[9]==1)debugf("Helligkeit: dunkel\n");
  else if(aLDR[9]<=3)debugf("Helligkeit: halbdunkel\n");
  else if(aLDR[9]<=8)debugf("Helligkeit: durschnittlich\n");
  else if(aLDR[9]<=13)debugf("Helligkeit: hell\n");
  else if(aLDR[9]==14)debugf("Helligkeit: sehr hell\n");
  else if(aLDR[9]==15)debugf("Helligkeit: Der Sensor fuehlt sich geblendet.\n");
 }
}
////////////// ADC end ////////////////////////////////////////////////////////

Und im Folgenden die sample.c:

#include "tollos.h"           // Tollos library
 
#ifdef __BUILD_WITH_EXAMPLE__
#include "lpc17xx_libcfg.h"
#else
#include "lpc17xx_libcfg_default.h"
#endif /* __BUILD_WITH_EXAMPLE__ */
 
#include <stdlib.h>
#include "smalltools.h"
 
int init(void) {		// Tollos-Init, return 0 to start tick() calls, or 1 to halt (power down).
 uint r;
 debugBaud(9600);
 debugf("\n--- ARM booted ---\n--- %s; sample compiled on %s at %s\n", TOLLOS_VERSION,__DATE__, __TIME__);	// Send a string to the host computer terminal
 debugf("--- SYS_Clock        %d MHz\n", sysQuery(SYS_CLOCK));
 debugf("--- Sys_TickTime     %d\n", sysQuery(SYS_TICKTIME));
 debugf("--- Sys_TickMax      %d\n", sysQuery(SYS_TICKMAX));
 debugf("--- Sys_TickLast     %d\n", sysQuery(SYS_TICKLAST));
 debugf("--- Sys_Watchdog     %d\n", sysQuery(SYS_WATCHDOG));
 debugf("--- Sys_StackUsed    %d\n", sysQuery(SYS_STACKUSED));
 debugf("--- Sys_Stacksize    %d\n", sysQuery(SYS_STACKSIZE));
 debugf("--- --- --- --- --- --- --- --- --- --- --- --- --- --- ---\n");
 LEDflash(2);LEDflash2(10);
 ADC5_init();
 
 sysSet(SYS_WATCHDOG, 5000);	// Request that the watchdog timer reset the processor if a tick() call takes more than 5 seconds
 return 0;
} // init
 
  /* --------------------------------------------------------------- */
  /* tick -- repeating tick function                                 */
  /*                                                                 */
  /* This is called once a tick (default 25 Hz) after init() ends.   */
  /* Return 0 to continue tick() calls, or 1 to halt (power down).   */
  /* --------------------------------------------------------------- */
int tick(void) {
 static int ticks=0;         // counter
 
 if(ticks==100){	// Change a LED state every second
  ticks=0;
  LDR5_read();
  ledSet(1, LED_INVERT);
 }else{
  ticks++;
  if((ticks==25)|(ticks==50)|(ticks==75))LDR5_read();
 }
 
 return 0;
} // tick

Über das Ticksystem blinkt 1x pro Sekunde eine LED und außerdem wird alle 250 ms der ADC-Pin, an welchem der LDR hängt, mittels der Funktion LDR5_read ausgewertet. Am Ende der Routine wird geprüft, ob Vorschau: Ausgabe vom mbed mit LDR an Docklight und RealTermder Taster gerade betätigt ist. Wenn ja, dann wird ein Durchschnittswert über die letzten 8 Werte ermittelt und das Ergebnis in Form einer kleinen Helligkeitsansage per debugf ausgegeben. Der LDR wird laut seinem Datenblatt eigentlich nur mit einem Serienwiderstand versehen und zwischen beiden greift man den Wert mittels ADC ab. Merkwürdiger Weise hat der mbed allerdings regelmäßig falsche Werte ermittelt und das obwohl ich analog der Beschreibung aus dem LPC1768-Datenblatt den ADC-Wert erst auswerte, nachdem das “ADC-Fertig-Bit” im Register AD0GDR gesetzt wurde. Aber dennoch scheint irgendetwas manchmal zu Fehlern zu führen. Mittels Multimeter und Oszilloskop konnte ich keine Fehler wie Peaks erkennen. So in etwa jeder 5. Wert war viel zu hoch oder viel zu niedrig, oft nahe an der Grenze des Wertebereichs des integrierten ADC. Dies war die Ursache dafür, dass ich Mittelwerte verwende. Da aber auch die Mittelwertbildung nicht reichte, probierte ich, einen 10nF-Kondensator parallel zum LDR anzuschließen und siehe da: Es hilft.

Einen Kommentar schreiben