Datum:
Angehängte Dateien:Hallo, ich bin gerade dabei, auf ARM Cortex M0 Controller (als Ersatz für die 8-Bitter) umzusteigen und habe eine Frage zu CMSIS und Cortex M0. Ich verwende das Entwicklerboard LPC1114 mit LPCXpresso und stelle mir die Frage: Woher weiß ich, welche Headerdateien ich z.B. für die I/O-Ports einbinden muss usw. (Ich konnte hier keine "Anleitung" bei NXP oder ARM finden) Ich habe in mein Projekt von NXP die CMSIS-Dateien importiert (siehe Bild) und mein mainfile wie folgt erstellt:
#ifdef __USE_CMSIS #include "LPC11xx.h" #endif #include <cr_section_macros.h> #include <NXP/crp.h> __CRP const unsigned int CRP_WORD = CRP_NO_CRP ; //Enable Code Read Protect" // TODO: insert other include files here // TODO: insert other definitions and declarations here int main(void) { // Die Std. Peripherie nun Clocks, PLLs usw. einrichten SystemInit(); //GPIOInit(); while(1) { } return 0 ; } |
Jetzt möchte ich irgendwie die Ports ansprechen um z.B. eine LED blinken zu lassen. Welches Headerfile muss ich einbinden?
Datum:
Hallo, schau dir mal die Seite an, hier findest du einen Einstieg: LPC1100 Series http://ics.nxp.com/support/lpcxpresso/ Gruß G.G.
Datum:
G. G. schrieb: > Hallo, > > schau dir mal die Seite an, hier findest du einen Einstieg: > > LPC1100 Series > > http://ics.nxp.com/support/lpcxpresso/ > Da war ich schon. Konnte aber nichts finden, dass mir hier weiterhilft. Wie ich ein Projekt erstelle oder einbinde, das weiß ich. Wie im obigen Beispiel möchte ich auf die I/O-Ports zugreifen. Wenn ich z.B. die Funktion GPIOInit(); aufrufe, erhalte ich beim build die Fehlermeldung undefined reference to `GPIOInit' Welche Headerdatei muss ich hier einbinden?
Datum:
Angehängte Dateien:H. G. schrieb: > Wenn ich z.B. die Funktion > GPIOInit(); > Welche Headerdatei muss ich hier einbinden? #include "LPC11xx.h" #include "LPC11xx_GPIO.h" Wenn alles ordentlich installiert wurde, findet deine Software die "LPC11xx.h" usw.. Hier wiederum stehen alle alle weiteren Informationen, wo z.B. GPIOInit() definiert ist (Include.. und Haeder... ) Einfacher wäre es sicher, wenn deine Software selbstständig ein Projekt eröffnen könnte. Da sind dann die Objekt-Path vorgegeben. (CooCox CoIDE) Siehe Anlage CooCox CoIDE is a free software product. Visit: http://www.coocox.org/CooCox_CoIDE.htm MfG. G.G.
Datum:
Man muss nicht unbedingt die Funktionen verwenden, man kann auch klassisch "Register direkt" verwenden. Dafür sollte #include "LPC11xx.h" ausreichen. Im Falle von GPIO kann "Register direkt" sogar obligatorisch sein, wenn die entsprechenden GPIO-Funktionen nicht als "static inline" ausgelegt sind - sie sind dann je nach Anwendungsfall u. U. schlicht zu langsam. Beispiel:
void GPIO_SetValue(uint8_t portNum, uint32_t bitValue)
{
LPC_GPIO_TypeDef *pGPIO = GPIO_GetPointer(portNum);
if (pGPIO != NULL)
{
pGPIO->SET = bitValue;
}
}
|
Der Funktionsaufruf frisst Zeit, "Pointer holen" kommt dazu, dann kommt die Indirektion. D. h. folgendes hat das gleiche Resultat:
GPIO_SetValue(0, 1); LPC_GPIOA->SET = 1; |
aber die zweite Variante ist der Turbo. Die Entscheidung "Peripherals library" oder "Register direkt" würde ich mir drei Mal überlegen. Bei den stm32 setze ich die "standard peripherals library" ein, und es gibt m. E. drei große Probleme: Die semantische Lücke zwischen Datenblatt und der Library, die fehlende Abstraktion (die Funktionen sind oft nicht genug "high level") und schließlich ist das Zeugs nicht portabel zwischen stm32f1 und stm32f4. Muss nicht zwangsläufig für LPC gelten. Es ist natürlich verlockend, z. B. im Falle von CAN/UART auf bestehende Funktionen aufsetzen zu können, die die Baud rate settings korrekt machen. Die Kehrseite ist dann, dass z. B. im Falle des LPC177x_8xCMSIS_110506 z. B. uint64_t Operationen für UART-Einstellungen eingebunden werden (muss für den CM0 nicht gelten). Generellt gilt, dass Code-Größe und RAM-Bedarf ansteigen.
Datum:
Roland H. schrieb: > Die Entscheidung "Peripherals library" oder "Register direkt" würde ich > mir drei Mal überlegen. JA, das sollte man dreimal ganz dick unterstreichen! Und nochwas, so ein Grundgerüst gefällt mir überhaupt nicht: int main(void) { // Die Std. Peripherie nun Clocks, PLLs usw. einrichten SystemInit(); //GPIOInit(); while(1) { } return 0 ; } weil man eben nicht genau weiß, was da alles in SystemInit angelassen wird. Ich rate eher dazu, all diese tollen für ein bestimmtes Board geschriebenen XYZ_Init Routinen wegzulassen und sich sein System selbst aufzusetzen. Dazu kann auch gehören, daß man sich seine eigene CPU-Headerdatei zusammenstellt, um unnötigen Ballast erst gar nicht in das Projekt kommen zu lassen. W.S.
Datum:
Ok, das leuchtet ein. Aber wo finde ich die Funktionen und deren Parameter? Nach GPIO_SetValue(); habe ich z.B. in der Headerdatei, im Datenblatt und im user manual vergebens gesucht. Also ich bräuchte eine Art Start-up-Lektüre (am Besten mit Beispielen) damit ich einmal die Funktionen kennenlerne und weiß, was ich wo einbinden muss. Hast du da für mich eine Empfehlung?
Datum:
Genau das ist der Punkt bei diesen Libs. Es scheint nirgends einen Bezug zum Usermanual des mc's zu geben. Eine vernünftige Doku zu den Libs gibt es aber nicht. Und wenn man die Funktionen der Lib aus den Headern und dem Code rausklaubt, hat man den mc immer noch nicht verstanden. Also lies das Manual des mc und setze die Register. Sowas wie:
LPC_GPIO0->DIR |= (1<<PinNummer); LPC_GPIO0->DATA |= (1<<PinNummer); LPC_GPIO0->DATA &= ~(1<<PinNummer); |
ist auch nicht schwieriger als ein GPIOSetDir und GPIOSetValue. Besonders schön ist für den LPC11xx auch die Funktion:
void GPIOSetInterrupt( uint32_t portNum, uint32_t bitPosi, uint32_t sense, uint32_t single, uint32_t event ) { switch ( portNum ) { case PORT0: if ( sense == 0 ) { LPC_GPIO0->IS &= ~(0x1<<bitPosi); /* single or double only applies when sense is 0(edge trigger). */ if ( single == 0 ) LPC_GPIO0->IBE &= ~(0x1<<bitPosi); else LPC_GPIO0->IBE |= (0x1<<bitPosi); } else LPC_GPIO0->IS |= (0x1<<bitPosi); if ( event == 0 ) LPC_GPIO0->IEV &= ~(0x1<<bitPosi); else LPC_GPIO0->IEV |= (0x1<<bitPosi); break; case PORT1: if ( sense == 0 ) { LPC_GPIO1->IS &= ~(0x1<<bitPosi); /* single or double only applies when sense is 0(edge trigger). */ if ( single == 0 ) LPC_GPIO1->IBE &= ~(0x1<<bitPosi); else LPC_GPIO1->IBE |= (0x1<<bitPosi); } else LPC_GPIO1->IS |= (0x1<<bitPosi); if ( event == 0 ) LPC_GPIO1->IEV &= ~(0x1<<bitPosi); else LPC_GPIO1->IEV |= (0x1<<bitPosi); break; case PORT2: if ( sense == 0 ) { LPC_GPIO2->IS &= ~(0x1<<bitPosi); /* single or double only applies when sense is 0(edge trigger). */ if ( single == 0 ) LPC_GPIO2->IBE &= ~(0x1<<bitPosi); else LPC_GPIO2->IBE |= (0x1<<bitPosi); } else LPC_GPIO2->IS |= (0x1<<bitPosi); if ( event == 0 ) LPC_GPIO2->IEV &= ~(0x1<<bitPosi); else LPC_GPIO2->IEV |= (0x1<<bitPosi); break; case PORT3: if ( sense == 0 ) { LPC_GPIO3->IS &= ~(0x1<<bitPosi); /* single or double only applies when sense is 0(edge trigger). */ if ( single == 0 ) LPC_GPIO3->IBE &= ~(0x1<<bitPosi); else LPC_GPIO3->IBE |= (0x1<<bitPosi); } else LPC_GPIO3->IS |= (0x1<<bitPosi); if ( event == 0 ) LPC_GPIO3->IEV &= ~(0x1<<bitPosi); else LPC_GPIO3->IEV |= (0x1<<bitPosi); break; default: break; } return; } |
Der ganze Code muss dazugelinkt werden nur um eine einzelne Zeile Register setzen durch eine Funktion zu ersetzen. Was sense, single und event bedeuten, kann man auch nicht erahnen ohne das Usermanual gelesen zu haben. Soviel kann man gar nicht brechen. Mir kommt aber ein ganz anderer Verdacht auf, eventuell sind auch die Hersteller der tollen IDEs nicht unbeteiligt. So kriegt man schnell den Flash voll und grenzt die (codegrößenbeschränkten) Freeware-Versionen aus. Das trifft hier zwar für CodeRed und die LPC11xx nicht zu da die nur 32k haben, aber große Programme brauchen auch länger um in den Flash geladen zu werden. Und Warten nervt. Einen Vorteil haben die Libs aber. Es gibt sie. Und damit genügend Beispielcode wenn man mal Verständnisprobleme mit dem Usermanual hat. @ Roland H. Das gibt es bei LPC11xx nicht: LPC_GPIOA->SET = 1 Die LPC11xx haben die Besonderheit mit dem MASKED_ACCESS Speicherbereich, den ich sonst noch nicht gesehen haben. Setzen eines Pins z.B.
// Pin als Ausgang konfigurieren LPC_GPIO0->DIR |= (1<<PinNummer); // Pin auf 1 setzen LPC_GPIO0->MASKED_ACCESS[1<<PinNummer]=1; // Pin auf 0 setzen LPC_GPIO0->MASKED_ACCESS[1<<PinNummer]=0; |
Datum:
Ok, hab das mal mit einem Beispiel versucht und mittels Debugger durchgesteppt. Die LED (am Port0 Pin7) blinkt aber nicht! Was habe ich vergessen oder übersehen?
#ifdef __USE_CMSIS #include "LPC11xx.h" #endif #include <cr_section_macros.h> #include <NXP/crp.h> __CRP const unsigned int CRP_WORD = CRP_NO_CRP ; #define PinNummer 7 int main(void) { SystemInit(); // Pin als Ausgang konfigurieren LPC_GPIO0->DIR |= (1<<PinNummer); while(1) { // Pin auf 1 setzen LPC_GPIO0->MASKED_ACCESS[1<<PinNummer]=1; // Pin auf 0 setzen LPC_GPIO0->MASKED_ACCESS[1<<PinNummer]=0; } return 0 ; } |
Datum:
So schnell ist dein Auge nicht, das du etwas blinken sehen würdest. Mach mal soetwas wie delay zwischen die Toggelei.
Datum:
Children schrieb: > So schnell ist dein Auge nicht, das du etwas blinken sehen würdest. > > Mach mal soetwas wie delay zwischen die Toggelei. Nein, ich habe den Debugger händisch bedient! Also Zeile für Zeile (mit F5) ausführen lassen.
Datum:
Du hast vergessen den Clock für die GPIO's zu aktivieren. Weiss das genaue Register dazu nicht aus dem kopf, schau am besten im Manual nach.
Datum:
das entsprechende Bit steht nach dem Reset immer auf 1 laut Manual. Trotzdem zur Sicherheit:
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6); |
hier war noch ein Fehler:
// Set LPC_GPIO0->MASKED_ACCESS[(1<<PinNummer)] = (1<<PinNummer); / Clr LPC_GPIO0->MASKED_ACCESS[(1<<PinNummer)] = (0<<PinNummer); |
War und ist auch ungetestet aus dem Kopf....
Datum:
Albert ... schrieb: > Du hast vergessen den Clock für die GPIO's zu aktivieren. Weiss das > genaue Register dazu nicht aus dem kopf, schau am besten im Manual nach. Hab ich nun versucht mit:
LPC_SYSCON.SYSAHBCLKCTRL |= 0x8000;
|
Da erhalte ich aber die Fehlermeldung: ../src/main.c:26:12: error: request for member 'SYSAHBCLKCTRL' in something not a structure or union Hat jemand ein konkretes Beispiel, wie man den Clock für die GPIO´s setzt?
Datum:
Ah, ja Pointer auf Struktur! Danke!
Datum:
Es funktioniert!!! Hier der Code:
#ifdef __USE_CMSIS #include "LPC11xx.h" #endif #include <cr_section_macros.h> #include <NXP/crp.h> __CRP const unsigned int CRP_WORD = CRP_NO_CRP ; #define PinNummer 7 int main(void) { SystemInit(); LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6); // Pin als Ausgang konfigurieren LPC_GPIO0->DIR |= (1<<PinNummer); int i,j; while(1) { // Pin auf 1 setzen LPC_GPIO0->MASKED_ACCESS[1<<PinNummer]= (1 << 7); for (i=0; i<=1000; i++) { for (j=0; j<=1000; j++) { } } // Pin auf 0 setzen LPC_GPIO0->MASKED_ACCESS[1<<PinNummer]= (0 << 7); for (i=0; i<=1000; i++) { for (j=0; j<=1000; j++) { } } } return 0 ; } |
Datum:
Jürgen Liegner schrieb: > das entsprechende Bit steht nach dem Reset immer auf 1 laut Manual. > Trotzdem zur Sicherheit: > >
> LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6); > |
Warum muss ich eigentlich das Register SYSAHBCLKCTRL über eine Struktur ansprechen? Kann ich die Bits nicht irgendwie auch direkt setzen z.B. SYSAHBCLKCTRL |= (1<<6);
Datum:
H. G. schrieb: > Warum muss ich eigentlich das Register SYSAHBCLKCTRL über eine Struktur > ansprechen? Weil's im Header in Zeile 147 so definiert ist? Wenn du dem compiler bzw. linker die Adresse des Registers anders "beibringst", kannst du das natürlich auch auf deine gewünschte Art schreiben. Ist in der LPC11xx.h aber nun mal so definiert. Warum hast du eigentlich so eine verschachtelte Schleife für das delay gewählt? Der LPC11xx hat 32 bit; die kannst du ruhig voll ausnutzen. Ist ja einer der Vorteile.
Datum:
falls du man sowas wie das bekannt Delayus suchst:
// Microsecond delay loop- void DelayuS(uint32_t uS) { uint32_t CyclestoLoops; CyclestoLoops = SystemCoreClock; if (CyclestoLoops >= 2000000) { CyclestoLoops /= 1000000; CyclestoLoops *= uS; } else { CyclestoLoops *= uS; CyclestoLoops /= 1000000; } if (CyclestoLoops <= 100) return; CyclestoLoops -= 100; // cycle count for entry/exit 100? should be measured CyclestoLoops /= 4; // cycle count per iteration- should be 4 on Cortex M0/M3 if (!CyclestoLoops) return; // Delay loop for Cortex M3 thumb2 asm volatile ( // Load loop count to register " mov r3, %[loops]\n" // loop start- subtract 1 from r3 "loop: sub r3, #1\n" // " nop \n" // test for zero, loop if not " bne loop\n\n" : // No output registers : [loops] "r" (CyclestoLoops) // Input registers : "r3" // clobbered registers ); } |
Das geht bei meinem lpc11c24 ziemlich genau. Natürlich nur wenn keine langen Interrupts dazwischen kommen. Und die SystemCoreClock Variable muss auch richtig stehen. Die wird aber von der cr_startup_lpc11.c vor der main() schon richtig gesetzt und steht bei mir auf 48000000. Durch die assembler-Befehle kann auch die Compileroptimierung nicht dazwischen funken. Ich möchte noch betonen, dass diese Funktion nicht von mir stammt, sondern mal aus einem der vielen Codeschnipsel im Web. Leider weiss ich aber nicht mehr woher das kam. In realen Anwendeungen sollte sowas sowieso nur sehr begrenzt vorkommen, da wartet man besser mit dem SysTick oder einen anderen Timer anstelle die Rechenzeit zu verbraten.
Datum:
Jürgen Liegner schrieb: > Sowas wie: > LPC_GPIO0->DIR |= (1<<PinNummer); > LPC_GPIO0->DATA |= (1<<PinNummer); > LPC_GPIO0->DATA &= ~(1<<PinNummer); > > ist auch nicht schwieriger als ein GPIOSetDir und GPIOSetValue. Roland H. schrieb: > Beispiel: > void GPIO_SetValue(uint8_t portNum, uint32_t bitValue) > > { > > LPC_GPIO_TypeDef *pGPIO = GPIO_GetPointer(portNum); > > > > if (pGPIO != NULL) > > { > > pGPIO->SET = bitValue; > > } > > } > > Der Funktionsaufruf frisst Zeit, "Pointer holen" kommt dazu, dann kommt > die Indirektion. > > D. h. folgendes hat das gleiche Resultat: > GPIO_SetValue(0, 1); > > LPC_GPIOA->SET = 1; > > aber die zweite Variante ist der Turbo. > > Die Entscheidung "Peripherals library" oder "Register direkt" würde ich > mir drei Mal überlegen. Man muß dabei aber auch erwähnen, daß es mit den Libs viel universeller und auch gerade für Einsteiger einfacher ist. Mit der Lib hat man immer nur eine Funktion, in der man die Parameter setzen muß. Wie würdest du es denn machen, wenn du z.B. einen DS18B20 angeschlossen hast? Zum Ansprechen von DQ müßtest du an jeder Stelle des Sensor-Codes sowas wie LPC_GPIOx->FIODIR |= (1<<y); schreiben, wobei x und y in jedem Projekt anders sind. Das gleiche für das Setzen und Löschen. Man könnte auch defines in der zugehörigen headerdatei machen wie #define DQ_SET_DIR LPC_GPIO2->FIODIR|=(1<<7) und diese im c.file einfach einsetzen.Es ist halt ein Kompromiss. Ansonsten stimmt alles genannte. Aber man kann auch bedenken, daß bei den 32-bittern üblicherweise Speicher- und Laufzeitreserven da sind, die das dann egalisieren.
Datum:
Bär_Tram schrieb: > Man muß dabei aber auch erwähnen, daß es mit den Libs viel > universeller und auch gerade für Einsteiger einfacher ist. In welchem Sinne "universell"? Portabel auf einen anderen ARM des gleichen Herstellers? Mit viel Glück passt die Library noch für die Nachfolgegeneration (die nächste CPU-Linie). Bei STM32 geht das übrigens nicht - die Library zwischen stm32f1 und stm32f4 ist anders. Ich bezweifle, dass es einfacher ist. Da gehen die Meinungen allerdings auseinander. Deshalb hatte ich geschrieben, dass man es sich überlegen sollte. Bär_Tram schrieb: > Mit der Lib > hat man immer nur eine Funktion, in der man die Parameter setzen muß. Mag sein, dass ich STM32 library geschädigt bin. In jedem Fall bekommst Du dort mit einer Library-Funktion z. B. keine PWM hin. Da hilft die Library gar nicht. Auf allen anderen Plattformen habe ich es dann von Anfang an gar nicht mehr gemacht. > Wie würdest du es denn machen, wenn du z.B. einen DS18B20 angeschlossen > hast? Zum Ansprechen von DQ müßtest du an jeder Stelle des > Sensor-Codes sowas wie > LPC_GPIOx->FIODIR |= (1<<y); > schreiben, wobei x und y in jedem Projekt anders sind. Das gleiche für > das Setzen und Löschen. Man könnte auch defines in der zugehörigen > headerdatei machen wie > #define DQ_SET_DIR LPC_GPIO2->FIODIR|=(1<<7) > und diese im c.file einfach einsetzen. Ja, so ähnlich. Um den Begriff "universell" aufzugreifen ;-) Zunächst definiere ich den GPIO-Pin in einer config.hpp mittels #defines (port + pin - bei Dir das x und das y). Dann habe ich GPIO-Makros, welche in der Logik-Schicht verwendet werden, und je für AVR/diverse ARMs/MSP430/Renesas RX/PIC32 den optimalen GPIO-Aufruf einsetzen. Das ist portabel, schont den Flash/Stack und ist am schnellsten. Diesbezüglich keine Kompromisse - der liegt darin, diesen Umweg vorgesehen zu haben. Ausserdem giesst der Präprozessor die Konfiguration so fest in den Code, dass der Pin zur Laufzeit nicht geändert werden kann. Z. B. beim lpc1769 ungefähr so (alles etwas vereinfacht - hier wird das bit-banding ignoriert):
#define GPIO_MODE_OUT(port, pin) \ CONCAT2(LPC_GPIO, port)->FIODIR |= (1 << pin) |
und beispielsweise beim atxmega256a3
#define GPIO_MODE_OUT(port, pin) \ CONCAT2(PORT, port.DIRSET) = (1 << pin) |
In diesem Fall also ohne "read-modify-write". Immer wenn eine neue CPU auf dem Tisch landet, dann wird die GPIO-API einmalig und zentral ergänzt. Die eigentlichen Programme bleiben unberührt. Selbst wenn es die erste und auf längere Sicht die einzige Plattform ist, würde ich immer einen Mini-HAL-Layer einbauen, und sei es nur um dort die Möglichkeit zu haben, Ergänzungen/Verbesserungen/zusätzliche Einstellungen zentral vornehmen zu können.
