Hallo erstmal,
ich hab mir vor kurzem so ein Discovery Board besorgt und bin ein wenig
am verzweifeln.
Wir haben in der Schule ein wenig mit µC's zu tun gehabt. Eigentlich
eher grundlegende Sachen durchgenommen.
Somit ist es für mich irgendwie klar, das man ein Register mit einem
bestimmten Wert beschreiben muss um z.B. auf einem Port dieses Bitmuster
zu schalten.
Nun hab ich mal eine Ewigkeit gebraucht um überhaupt die blöde LED zum
leuchten bringen. Grund dafür: Anscheinend ist es nicht mehr
"state-of-the-art", das mit einer Zuweisung zu machen, sondern man
definiert in 20 verschiedenen files verschiede Startup codes und
defninitionen, die eine Led zum leuchten bringen.
aus
RCC->APB2ENR=0xFFFD;
wird dann schnell RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |
RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOD |
RCC_APB2Periph_GPIOE, ENABLE);
, wobei das ja noch leicht herauszufinden ist was das macht.
also so wie ich das sehe nimmt man jetzt statt den 10 min, die es dauert
im datasheet nach dem richtigen Register suchen die 30 min
herauszufinden, in welchen files die vorgefertigten funktionen
geschrieben sind, sie zu verstehen und dann so ins eigene Programm zu
kopieren, dass keine Fehler entstehen. Wenn 30 min da reichen.
Aber jetzt genug von meiner Suderei über das System, ich hab ja noch
eine Frage:
Gibt es irgeneine Möglichkeit Interrupts so einfach zu schreiben wie
ichs gelernt habe, nämlich mit einer Interruptadresse und einem Stück
code, das ausgeführt wird wenn dieser aktiviert wird. also im Stil von
void HansPeter (void) interrupt 0xA4{
Code
}
Ich habe mich eine Zeit lang durch die Foren gewälzt. Sachen wie
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // Takt für
IO-Port
GPIO_InitSt.GPIO_Pin = GPIO_Pin_6; // Eingang RFID Takt Signal (PA6
bei EXT_IN_1)
GPIO_InitSt.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(GPIOA, &GPIO_InitSt);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // Takt für TIM3
TIM_TIxExternalClockConfig(TIM3, TIM_TIxExternalCLK1Source_TI1,
TIM_ICPolarity_Falling, 0); // Konfiguration Port
TIM_Cmd(TIM3, ENABLE); // Aktivieren
was zum Beispiel angeblich Flanken eines externen Signals zählt sind
zwar schön und gut, aber bei so einfachen Programmen sollte es ja auch
funktionieren ohne sich 10 mal nach unten zu verschachteln.
hoffentlich stoße ich mit dieser denkweise niemanden vor den Kopf, wenn
doch, sagt mir bitte wie man's richtig macht.
Der STM32 ist nunmal ein wenig Leistungsfähiger als das was du
kennengelernt hast und somit auch komplizierter zu programmieren. uC
Programmierung geht darüber hinaus einen Wert in ein Register zui
schreiben, du musst erstmal die Architektur verstehen. Es zwingt dich
niemand die Libs von ST zu nutzen, ich mache das auch nicht.
Allerdings glaube ich auch nicht, dass du mit dem Datasheet besser
fährst, da du noch keine grundlegenden uC-Kenntnisse zu haben scheinst.
Das Reference Manual von ST hat >1000 Seiten und dann brauchst du noch
das Reference Manual vom Cortex M3.
Also dank dem Inhaltsverzeichnis kann man sich schon zurechtfinden,
zumindest hab ich bis jetzt alles gefunden.
das eigentliche Problem ist ja Keil, ich würde gerne
void meininterrupt (void) interrupt 0xA4 {
//Code
}
schreiben, nur bekomme ich einen Fehler beim Compilen.
benutze ich die falsche syntax oder funktioniert das beim CortexM3 nicht
mehr
Du hast eben nicht alles gefunden, das funktioniert beim STM32 völlig
anders, eine Interrupt-Service Routine zu erstellen ist eben nicht ganz
so einfach wie du dir das vorstellst. Dafür musst du diverse
Einstellungen vornehmen. Du musst dich erstmal mit dem
Interrupt-Controller vom Cortex M3 auseinandersetzen.
neuling schrieb:> das eigentliche Problem ist ja Keil, ich würde gerne>> void meininterrupt (void) interrupt 0xA4 {> //Code> }>> schreiben, nur bekomme ich einen Fehler beim Compilen.> benutze ich die falsche syntax oder funktioniert das beim CortexM3 nicht> mehr
Na so ähnlich ist es schon, eher schon einfacher: Den Namen der ISR mußt
du exakt nutzen (wird z.B. im Startupcode definiert). Für Timer0 beim
LPC17xx heißt die ISR einfach
TIMER0_IRQHandler
Du schreibst einfach
void TIMER0_IRQHandler(void)
{
// code
}
Natürlich mußt du noch den ganzen Rest beachten; also den Interrupt
aktivieren, im NVIC priorisieren, den TIMER0 so konfigurieren, daß er
auch wie gewünscht ausgelöst werden etc.
Hallo Neuling,
was Du beim STM erlebst ist einerseits verwirrend, andererseits in der
Industrie gewünscht.
Im Prinzip kannst Du das mit Deiner Routine genauso machen, dann musst
Du aber alles selbst neu erfinden, deine Routinen in die entsprechenden
Tabellen einhängen, den Interruptcontroller und dess Vektortabellen
sebst aufbauen. Das kostet Dich bei einem Minimalprogramm nicht soviel
Zeit wenn du darin geübt bist und den Controller verstanden hast.
Wenn Du aber größere Umgebungen hast, den Controller häufig unter
verschiedenen Umgebungen einsetzen willst ist es ratsamer eine
Abstraktionsebene über die direkte HW zu legen: diese Funktion
bewerkstelligt die STM32 Bibliothek die Du verwendest. Sie erlaubt dass
die Programme schnell und portabel geschrieben werden können. Beispiel:
Die unetrschiedlichen Bitpositionen für das Einschalten und einstellen
der PLL sind in den STM32 Varianten unterschiedlich positioniert. Wenn
Du wie in deinem Beispiel hart und direkt auf die Register gehst und das
Programm dann von einer Variante auf die andere Varinate wechselst
müsstest Du alle solchen Zielen finden und auf deren Korrektheit pürüfe
- ob das sicher klappt kannst Du Dir selber überlegen.
Im übrigen werden die Funktionen shcon etwas Zeit fressen da sie zwar
relativ minimal angelegt sind aber die Calls dazu auch Zeit brauchen.
Dann kannst Du aber dort wo es zeiotkritisch ist immer noch über die
Strukturen hart uf die Register gehen.
Die STM32 Bibilothek ist sicherlich etwas aufwendiger zu verstehen, sie
gibt Dir aber die Möglichkeit, wenn Du mal drinnen bist UND den
Controller verstanden hast innerhalb kürzester Zeit alle Blöcke einfach
anszusteuern und felxibel damit umzugehen - und nachvollziehbarer Wird
dein Code auch.
Im übrigen nochmal: den STM32 kannst Du acuh in Deiner Umgebung OHNE
Bibliothek programmieren - ich halte es aber nicht für sinnvoll.
Grüße
Für dem Einstieg ist die STM FW Lib sehr gut geeignet. Wenn es sowas für
den AVR gäbe, wären hier viele Beiträge überflüssig.
Später kann man selber die Register-Variante wählen. Viele bleibe jedoch
bei der FW Lib auch im Paxiseinsatz. Den Overhead nimmt man in Kauf, die
STM32 habe für die meisten Einsatzfälle genug Leistungsreserven. Die Lib
dürfte in vielen Autos mit herumfahren...
Hi,
neuling schrieb:> das eigentliche Problem ist ja Keil, ich würde gerne>> void meininterrupt (void) interrupt 0xA4 {> //Code> }
Mit "Keil" meinst du sicher die µVision IDE (MDK-ARM), und das Problem
sitzt hier eher vor dem Computer.
Ich würde meine Programme auch gerne dem MS VC diktieren, aber so
funktioniert das nun mal nicht.
Die Interrupt-Vektoren für den Cortex-M3 sind in der MDK-ARM im
startup.s definiert. Dort findest du ein mal die Sprungtabelle, und
sämtliche Funktionen als [weak] vordefiniert. Das heisst, wenn du eine
Funktion mit einem Namen aus der Vector Table selbst schreibst, wird
immer deine Funktion eingelinkt.
Nun suchst du dir die richtige aus der Tabelle raus, schreibst "void"
davor und (void) { } dahinter. Fertig.
Ich schenk dir noch etwas für den STM32. Auf den ersten Blick sieht es
etwas viel aus, aber beim genaueren betrachten kann man das für alle
Portkonfigurationen sehr simpel verwenden. Ist auf meinem Mist
gewachsen.
Natürlich kann man auch schreiben:
1
intled_init(void)
2
voidLED_init(void){
3
RCC->APB2ENR|=(1UL<<3);/* Enable GPIOB clock */
4
5
GPIOB->ODR&=~0x0000FF00;/* switch off LEDs */
6
GPIOB->CRH&=~0xFFFFFFFF;/* Configure the GPIO for LEDs */
7
GPIOB->CRH|=0x33333333;
8
}
aber das ist schwiriger zu lesen.
gpio.h:
1
#ifndef __GPIO_H__
2
#define __GPIO_H__
3
4
5
// AF enable
6
#define AFIO_EN 0 // Alternate Function Enable
7
8
// GPIO enable
9
#define IOPA_EN 2 // GPIO A enable
10
#define IOPB_EN 3 // GPIO B enable
11
#define IOPC_EN 4 // GPIO C enable
12
#define IOPD_EN 5 // GPIO D enable
13
#define IOPE_EN 6 // GPIO E enable
14
#define IOPF_EN 7 // GPIO F enable
15
#define IOPG_EN 8 // GPIO G enable
16
17
// DAC Enable
18
#define DAC_EN 29
19
20
21
#define GPIO_CONF_BIT(BIT) ((BIT>7? BIT-8 : BIT) << 2) // 4Bits per port pin
led_out(1<<(ledCnt=++ledCnt<8?ledCnt:0));// feed the running light
39
}
40
}
Achtung:
Für die Port Config muss man noch wissen, ob man die unteren 8 Bit (CRL)
oder die oberen 8Bit (CRH) beschreiben will (oder beide).
Jedes der beiden CRx Register hat einen Block aus 4Bit (2 MODE, 2 CONF)
zur Portconfig.
Um Glitches beim Umschalten der Portconfig zu vermeiden, setzt man bei
STM im Gegensatz zu z.B. dem AVR, alle Port Config Bits in einem Rutsch.
VG,
/th.
Matthias K. schrieb:> Für dem Einstieg ist die STM FW Lib sehr gut geeignet. Wenn es sowas für> den AVR gäbe, wären hier viele Beiträge überflüssig.
Ich finde Librarys für die normalen Peripherals (abgesehen von ETH und
USB) eher überflüssig, oder wenn, dann nur als Nachschlagewerk.
Man holt sich unnötig viel Code rein, und wenn man eine sehr schnelle
Ausgabe(Eingabe braucht, muss man eh selbst ran und schreiben.
VG,
/th.
Genau so sehe ich die Libs eigentlich auch.
Die zumindest bei mir übliche Vorgehensweise ist so: Zu Anfang benutze
ich die jeweilige Lib, um parallel mit dem Datenblatt/Manual erstmal zum
Ziel zu kommen. Dann schaue ich Stück für Stück nach, was sich
eigentlich konkret im Libaufruf verbirgt und mische dann Lib und
Direktzugriffe auf Register, um zum Schluß fast nur noch auf
Registerebene zu arbeiten. Ausnahmen bleiben die echt komplexen
Peripherien. Das will ich wirklich nicht immer verstehen.
Wegen der Codegröße und Ausführungszeit muß man immer bedenken, daß in
der Lib üblicherweise idiotensicher jede mögliche Konstellation
berücksichtigt ist. Die eigene Implementierung bzw.
Registerdirektzugriffe kann dann im Einzelfall schon mal in dem einen
Projekt funktionieren und im anderen nicht.