Forum: Mikrocontroller und Digitale Elektronik Befehle hinter vorgefertigten Funktionen STM32


von neuling (Gast)


Lesenswert?

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.

von Steel (Gast)


Lesenswert?

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.

von neuling (Gast)


Lesenswert?

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

von Steel (Gast)


Lesenswert?

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.

von Lutz (Gast)


Lesenswert?

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.

von friedrich (Gast)


Lesenswert?

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

von Matthias K. (matthiask)


Lesenswert?

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...

von Random .. (thorstendb) Benutzerseite


Lesenswert?

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.

von Random .. (thorstendb) Benutzerseite


Lesenswert?

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.

von Matthias K. (matthiask)


Lesenswert?

Random ... schrieb:
> oder wenn, dann nur als Nachschlagewerk.

Damit hat sie schon ihren Zweck erfüllt.

von Lutz (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.