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:
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?
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.
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:
Der Funktionsaufruf frisst Zeit, "Pointer holen" kommt dazu, dann kommt
die Indirektion.
D. h. folgendes hat das gleiche Resultat:
1
GPIO_SetValue(0, 1);
2
3
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.
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.
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?
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:
1
LPC_GPIO0->DIR|=(1<<PinNummer);
2
LPC_GPIO0->DATA|=(1<<PinNummer);
3
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:
/* single or double only applies when sense is 0(edge trigger). */
11
if(single==0)
12
LPC_GPIO0->IBE&=~(0x1<<bitPosi);
13
else
14
LPC_GPIO0->IBE|=(0x1<<bitPosi);
15
}
16
else
17
LPC_GPIO0->IS|=(0x1<<bitPosi);
18
if(event==0)
19
LPC_GPIO0->IEV&=~(0x1<<bitPosi);
20
else
21
LPC_GPIO0->IEV|=(0x1<<bitPosi);
22
break;
23
casePORT1:
24
if(sense==0)
25
{
26
LPC_GPIO1->IS&=~(0x1<<bitPosi);
27
/* single or double only applies when sense is 0(edge trigger). */
28
if(single==0)
29
LPC_GPIO1->IBE&=~(0x1<<bitPosi);
30
else
31
LPC_GPIO1->IBE|=(0x1<<bitPosi);
32
}
33
else
34
LPC_GPIO1->IS|=(0x1<<bitPosi);
35
if(event==0)
36
LPC_GPIO1->IEV&=~(0x1<<bitPosi);
37
else
38
LPC_GPIO1->IEV|=(0x1<<bitPosi);
39
break;
40
casePORT2:
41
if(sense==0)
42
{
43
LPC_GPIO2->IS&=~(0x1<<bitPosi);
44
/* single or double only applies when sense is 0(edge trigger). */
45
if(single==0)
46
LPC_GPIO2->IBE&=~(0x1<<bitPosi);
47
else
48
LPC_GPIO2->IBE|=(0x1<<bitPosi);
49
}
50
else
51
LPC_GPIO2->IS|=(0x1<<bitPosi);
52
if(event==0)
53
LPC_GPIO2->IEV&=~(0x1<<bitPosi);
54
else
55
LPC_GPIO2->IEV|=(0x1<<bitPosi);
56
break;
57
casePORT3:
58
if(sense==0)
59
{
60
LPC_GPIO3->IS&=~(0x1<<bitPosi);
61
/* single or double only applies when sense is 0(edge trigger). */
62
if(single==0)
63
LPC_GPIO3->IBE&=~(0x1<<bitPosi);
64
else
65
LPC_GPIO3->IBE|=(0x1<<bitPosi);
66
}
67
else
68
LPC_GPIO3->IS|=(0x1<<bitPosi);
69
if(event==0)
70
LPC_GPIO3->IEV&=~(0x1<<bitPosi);
71
else
72
LPC_GPIO3->IEV|=(0x1<<bitPosi);
73
break;
74
default:
75
break;
76
}
77
return;
78
}
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.
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?
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.
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:
1
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?
Jürgen Liegner schrieb:> das entsprechende Bit steht nach dem Reset immer auf 1 laut Manual.> Trotzdem zur Sicherheit:>>
1
>LPC_SYSCON->SYSAHBCLKCTRL|=(1<<6);
2
>
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);
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.
falls du man sowas wie das bekannt Delayus suchst:
1
// Microsecond delay loop-
2
voidDelayuS(uint32_tuS)
3
{
4
uint32_tCyclestoLoops;
5
6
CyclestoLoops=SystemCoreClock;
7
if(CyclestoLoops>=2000000)
8
{
9
CyclestoLoops/=1000000;
10
CyclestoLoops*=uS;
11
}
12
else
13
{
14
CyclestoLoops*=uS;
15
CyclestoLoops/=1000000;
16
}
17
18
if(CyclestoLoops<=100)
19
return;
20
21
CyclestoLoops-=100;// cycle count for entry/exit 100? should be measured
22
CyclestoLoops/=4;// cycle count per iteration- should be 4 on Cortex M0/M3
23
24
if(!CyclestoLoops)
25
return;
26
27
// Delay loop for Cortex M3 thumb2
28
asmvolatile
29
(
30
// Load loop count to register
31
" mov r3, %[loops]\n"
32
// loop start- subtract 1 from r3
33
"loop: sub r3, #1\n"
34
// " nop \n"
35
// test for zero, loop if not
36
" bne loop\n\n"
37
38
:// No output registers
39
:[loops]"r"(CyclestoLoops)// Input registers
40
:"r3"// clobbered registers
41
);
42
}
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.
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.
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):
1
#define GPIO_MODE_OUT(port, pin) \
2
CONCAT2(LPC_GPIO, port)->FIODIR |= (1 << pin)
und beispielsweise beim atxmega256a3
1
#define GPIO_MODE_OUT(port, pin) \
2
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.