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 | int led_init(void) |
2 | void LED_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
|
22 | |
23 | // Mode and Conf Bits
|
24 | #define MODE0 (unsigned int)0
|
25 | #define MODE1 (unsigned int)1
|
26 | #define CONF0 (unsigned int)2
|
27 | #define CONF1 (unsigned int)3
|
28 | |
29 | // Port Mode
|
30 | #define GPIO_MODE_INPUT (((unsigned int)0<<MODE0) | ((unsigned int)0<<MODE1)) // GPIO is input
|
31 | #define GPIO_SPEED_2MHZ (((unsigned int)0<<MODE0) | ((unsigned int)1<<MODE1)) // Max output Speed 2MHz
|
32 | #define GPIO_SPEED_10MHZ (((unsigned int)1<<MODE0) | ((unsigned int)0<<MODE1)) // Max output Speed 10MHz
|
33 | #define GPIO_SPEED_50MHZ (((unsigned int)1<<MODE0) | ((unsigned int)1<<MODE1)) // Max output Speed 50MHz
|
34 | |
35 | // Port Conf
|
36 | #define GPIO_OUT_PUSH_PULL (((unsigned int)0<<CONF0) | ((unsigned int)0<<CONF1)) // general purpose output push-pull
|
37 | |
38 | #define GPIO_AF_PUSHPULL (((unsigned int)0<<CONF0) | ((unsigned int)1<<CONF1)) // alternate function push-pull
|
39 | |
40 | #define GPIO_IN_FLOATING (((unsigned int)1<<CONF0) | ((unsigned int)0<<CONF1)) // input floating
|
41 | #define GPIO_IN_ANALOG (((unsigned int)0<<CONF0) | ((unsigned int)0<<CONF1)) // input analog
|
42 | |
43 | #define GPIO_IN_PULL_DOWN (((unsigned int)0<<CONF0) | ((unsigned int)1<<CONF1)) // alternate function push-pull
|
44 | #define GPIO_IN_PULL_UP (((unsigned int)0<<CONF0) | ((unsigned int)1<<CONF1)) // alternate function push-pull
|
45 | |
46 | #endif
|
led.h:
1 | #ifndef __led_h__
|
2 | #define __led_h__
|
3 | |
4 | |
5 | #define LED_PORT GPIOB->ODR
|
6 | #define LED_PORT_MASK 0xFFFF00FF
|
7 | #define LED_PORT_SHIFT 8
|
8 | #define LED_PORT_SHIFT_MAX 15
|
9 | |
10 | int led_init(void); |
11 | void led_out(char out); |
12 | void running_light(void); |
13 | |
14 | #endif
|
main.h:
1 | #ifndef __MAIN_H__
|
2 | #define __MAIN_H__
|
3 | |
4 | // Bit_on, bit_off definitions
|
5 | #define bit_on(BYTE, BIT) { BYTE |= 1 << BIT; }
|
6 | #define bit_off(BYTE, BIT) { BYTE &= ~(1 << BIT); }
|
7 | |
8 | #endif
|
led.c:
1 | #include "stm32.h" |
2 | #include "main.h" |
3 | #include "gpio.h" |
4 | #include "led.h" |
5 | |
6 | int ledCnt=0; |
7 | |
8 | int led_init(void) |
9 | {
|
10 | bit_on(RCC->APB2ENR, IOPB_EN); // enable PORT B |
11 | |
12 | GPIOB->CRH = |
13 | ((GPIO_OUT_PUSH_PULL | GPIO_SPEED_2MHZ) << GPIO_CONF_BIT(8)) | \ |
14 | ((GPIO_OUT_PUSH_PULL | GPIO_SPEED_2MHZ) << GPIO_CONF_BIT(9)) | \ |
15 | ((GPIO_OUT_PUSH_PULL | GPIO_SPEED_2MHZ) << GPIO_CONF_BIT(10)) | \ |
16 | ((GPIO_OUT_PUSH_PULL | GPIO_SPEED_2MHZ) << GPIO_CONF_BIT(11)) | \ |
17 | ((GPIO_OUT_PUSH_PULL | GPIO_SPEED_2MHZ) << GPIO_CONF_BIT(12)) | \ |
18 | ((GPIO_OUT_PUSH_PULL | GPIO_SPEED_2MHZ) << GPIO_CONF_BIT(13)) | \ |
19 | ((GPIO_OUT_PUSH_PULL | GPIO_SPEED_2MHZ) << GPIO_CONF_BIT(14)) | \ |
20 | ((GPIO_OUT_PUSH_PULL | GPIO_SPEED_2MHZ) << GPIO_CONF_BIT(15)); |
21 | |
22 | return(0); |
23 | }
|
24 | |
25 | |
26 | void led_out(char out){ |
27 | LED_PORT = (LED_PORT & LED_PORT_MASK) | (out << LED_PORT_SHIFT); |
28 | }
|
29 | |
30 | |
31 | void running_light(void) |
32 | {
|
33 | static int count=0; |
34 | count++; |
35 | |
36 | if(count > 0xffff){ |
37 | count=0; |
38 | 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.
Random ... schrieb: > oder wenn, dann nur als Nachschlagewerk. Damit hat sie schon ihren Zweck erfüllt.
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.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.