Forum: Mikrocontroller und Digitale Elektronik STM32F446: GPIO-Register läßt sich nicht beschreiben


von Walter T. (nicolas)


Lesenswert?

Hallo zusammen,

ich sitze hier seit mehr als einer Stunde mit dem typischen Brett vorm 
Kopf. Ich habe einen STM32F446 auf dem entsprechenden Nucleo-Board. Was 
ich machen will, ist noch nicht wirklich kompliziert: Einfach einen Pin 
wackeln.
1
#include "stm32f4xx_conf.h"
2
#include "stm32f4xx_gpio.h"
3
#include <stdio.h>
4
#include <stdlib.h>
5
#include "stm32f4xx.h"
6
7
// LED-Pin kollidiert mit SPI1
8
#define LED2_GPIO GPIOA
9
#define LED2_Pin  GPIO_Pin_5
10
11
12
13
#ifdef STM32F10x
14
    #error "This is STM32F4XX project";
15
#endif
16
17
18
19
static inline __attribute__((always_inline))  uint32_t stretch2bitmask16(const uint16_t word)
20
{
21
    uint32_t out = 0;
22
    out |= word & 0x0001U ?  0x00000003U : 0;
23
    out |= word & 0x0002U ?  0x0000000CU : 0;
24
    out |= word & 0x0004U ?  0x00000030U : 0;
25
    out |= word & 0x0008U ?  0x000000C0U : 0;
26
    out |= word & 0x0010U ?  0x00000300U : 0;
27
    out |= word & 0x0020U ?  0x00000C00U : 0;
28
    out |= word & 0x0040U ?  0x00003000U : 0;
29
    out |= word & 0x0080U ?  0x0000C000U : 0;
30
    out |= word & 0x0100U ?  0x00030000U : 0;
31
    out |= word & 0x0200U ?  0x000C0000U : 0;
32
    out |= word & 0x0400U ?  0x00300000U : 0;
33
    out |= word & 0x0800U ?  0x00C00000U : 0;
34
    out |= word & 0x1000U ?  0x03000000U : 0;
35
    out |= word & 0x2000U ?  0x0C000000U : 0;
36
    out |= word & 0x4000U ?  0x30000000U : 0;
37
    out |= word & 0x8000U ?  0xC0000000U : 0;
38
    return out;
39
}
40
41
42
43
44
typedef  GPIO_TypeDef GPIO_t;
45
typedef  uint16_t IOPin_t;
46
47
static inline __attribute__((always_inline))
48
    void io_setOutput(GPIO_t * GPIOx, const IOPin_t Pin)
49
{
50
    // RM0390 S. 186
51
    // General Purpose output, nach Reset push-/pull
52
    uint32_t mask = stretch2bitmask16(Pin); // laesst sich so besser durchsteppen
53
    mask &= 0x55555555;
54
    GPIOx->MODER |= mask;
55
    GPIOx->OSPEEDR |= stretch2bitmask16(Pin) & 0xFFFFFFFF; // Max frequ.
56
57
}
58
59
60
static inline __attribute__((always_inline))
61
    void io_toggleBit(GPIO_t * const GPIOx, const IOPin_t Pin)
62
{
63
    GPIOx->ODR ^= (uint32_t) Pin;
64
}
65
66
67
68
int main(void)
69
{
70
    SystemInit();
71
    SysTick_Config(SystemCoreClock/1000); // 1ms
72
73
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
74
    RCC_APB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
75
    RCC_APB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
76
    RCC_APB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
77
    RCC_APB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
78
    //RCC_APB1PeriphClockCmd(RCC_APB2Periph_AFIO  |  RCC_APB2ENR_AFIOEN, ENABLE); // TODO: AFIO-Taktquelle suchen
79
80
    GPIO_t *GPIOc = GPIOC;
81
82
    io_setOutput(LED2_GPIO, LED2_Pin);
83
84
    io_setOutput(GPIOC, GPIO_Pin_1);
85
    while(1) {
86
        io_toggleBit(GPIOC, GPIO_Pin_1);
87
    }
88
89
    return 0;
90
}
91
92
93
94
void SysTick_Handler(void)
95
{
96
}

Mein Problem: Bei Durchsteppen mit dem Debugger will sich das 
MODER-Register partout nicht beschreiben lassen. Folglich wackelt auch 
Pin PC1 nicht. PA5 läßt sich dagegen wackeln, da schon im Reset-Zustand 
Output.

Wer sieht das, was ich nicht sehe?

Viele Grüße
W.T.

: Bearbeitet durch User
von Johnny B. (johnnyb)


Lesenswert?

Walter T. schrieb:
> Wer sieht das, was ich nicht sehe?

Habe keine Ahnung vom STM32, aber musst Du vielleicht noch die Pins als 
I/O definieren (vielleicht ist bei diesen Pins standardmässig die 
"Alternate Function" aktiviert)?

http://slemi.info/2017/04/16/configuring-io-pins/

: Bearbeitet durch User
von Mampf F. (mampf) Benutzerseite


Lesenswert?

Walter T. schrieb:
> Wer sieht das, was ich nicht sehe?

Du verwendest die StdPeriphLib nicht - ähm oder nur zum Teil?

Wieso schreibst du dir selbst obskure Funktionen wie io_setOutput?

Das wär das erste, was ich ändern würde.

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Achtung: Die folgenden Beispiele gelten für STM32F103.

Ich möchte Dir nützliche CMSIS Funktionen zeigen, die sicher auch bei 
deinem µC zur Verfügung stehen.

Zum Einen kann man I/O Pins so einstellen:

GPIOA->BSRR=GPIO_BSRR_BS5; // PA5=High
GPIOA->BSRR=GPIO_BSRR_BR5; // PA5=Low

Zweitens gibt es für das Ändern einzelner Bits die Hilfsfunktion:

SET_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);
CLEAR_BIT(RCC->APB2ENR, RCC_APB2ENR_IOPAEN);

Wobei die CMSIS für alle Register und Bitmasken entsprechende Konstanten 
definiert. Weiterhin gibt es noch eine Hilfsfunktion, um mehrere Bits 
gleichzeitig zu ändern. Sie ist für Register gedacht, wie 
zusammenhängende Blöcke von Bits irgendeine Bedeutung haben:

MODIFY_REG(AFIO->MAPR, AFIO_MAPR_SWJ_CFG, AFIO_MAPR_SWJ_CFG_DISABLE);

Der erste Parameter gibt das zu ändernde Register an. Der zweite 
Parameter ist eine Bitmaske die angibt, welche Bit-Gruppe du ändern 
willst. In diesem Fall sind es die Bits, die das SWJ Interface 
konfigurieren. Der dritte Parameter gibt an, welchen Wert die maskierten 
Bits bekommen sollen. Auch dafür gibt es in der CMSIS vordefinierte 
Konstanten die man ggf. zusammen addiert (oder ver-odert).

Der folgende Befehl ändert gleich zwei Bitgruppen auf einmal. Er 
konfiguriert PA5 als Ausgang:

MODIFY_REG(GPIOA->CRL, GPIO_CRL_CNF5 + GPIO_CRL_MODE5, 
GPIO_CRL_MODE5_0);

Schau Dir an, was es da sonst noch so gibt: 
https://github.com/ARMmbed/cmsis-core-stm32f4/blob/master/cmsis-core-stm32f4/stm32f4xx.h

Ich denke, so macht man das normalerweise. Vergiss die StdPeriphLib, die 
ist schon lange veraltet. Nimm lieber CMSIS Funktionen, denn die ändern 
sich nicht so schnell und sind außerdem auf allen ARM Controllern 
gleich. Durch Verwendung von CMSIS erzeugst besser lesbaren Code, da er 
näher am ARM Standard ist.

Um zu deiner eigentlichen Frage zu kommen: Ich sehe keinen Fehler, 
sorry. Ich wünschte, ich hätte dieses Board um es mal selbst 
auszuprobieren.

von A. B. (Gast)


Lesenswert?

Walter T. schrieb:
> Kopf. Ich habe einen STM32F446 auf dem entsprechenden Nucleo-Board. Was

Derer gibt's zwei: 64 oder 144?

> Mein Problem: Bei Durchsteppen mit dem Debugger will sich das
> MODER-Register partout nicht beschreiben lassen. Folglich wackelt auch

Was heißt das? Stimmt beim Auslesen des Registers der Inhalt nicht mit 
dem überein, was hineingeschrieben wurde?

> Pin PC1 nicht. PA5 läßt sich dagegen wackeln, da schon im Reset-Zustand
> Output.

Oder ist nur gemeint, dass PC1 seinen Zustand scheinbar nicht ändert? 
Und auch ist unklar, ob PC1 auf Input bleibt, oder schon auf Output 
geschaltet wird aber sein Zustand unverändert bleibt. 10k Pullup 
wechselweise an VCC bzw. GND, dann sieht man, was los ist.

Oben sehe ich außerdem nur etwas von PC1, PA5 kommt da gar nicht vor 
bzw. eben gerade nicht in exakt gleicher Weise wie PC1. Da werden 
sozusagen Äpfel mit Birnen verglichen.

Tipp: Die Register alle auch auslesen und mit den erwarteten Werten 
vergleichen. Am einfachsten geht das mit wohl mit OpenOCD. Die Register 
nach Reset wie oben im Code manuell beschreiben, auslesen und nachsehen, 
was an den Pins passiert ...

Es bleibt auch noch, dass der Compiler etwas wegoptimiert (was den 
Debugger  u. U. total in die Irre führt), deswegen auch mal mit "-O0" 
probieren und den Assembler-Code ansehen.

von Walter T. (nicolas)


Lesenswert?

Mampf F. schrieb:
> Du verwendest die StdPeriphLib nicht

Damit habe ich den gleichen Effekt (-kein Wunder: die GPIO_Init() macht 
ja genau das Gleiche): Im Debugger wird immer MODER=0 ausgelesen, und 
der Pin wackelt nicht.

So sieht es mit der StdPeriphLib aus (kleine Änderung: auch noch PC 13 
dazu):
1
#include "stm32f4xx_conf.h"
2
#include "stm32f4xx_gpio.h"
3
#include <stdio.h>
4
#include <stdlib.h>
5
#include "stm32f4xx.h"
6
7
// LED-Pin kollidiert mit SPI1
8
#define LED2_GPIO GPIOA
9
#define LED2_Pin  GPIO_Pin_5
10
11
12
int main(void)
13
{
14
    SystemInit();
15
    SysTick_Config(SystemCoreClock/1000); // 1ms
16
17
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
18
    RCC_APB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
19
    RCC_APB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
20
    RCC_APB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
21
    RCC_APB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
22
    //RCC_APB1PeriphClockCmd(RCC_APB2Periph_AFIO  |  RCC_APB2ENR_AFIOEN, ENABLE);
23
24
25
  /* Configure PG6 and PG8 in output pushpull mode */
26
    GPIO_InitTypeDef  GPIO_InitStructure;
27
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_13;
28
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
29
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
30
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
31
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
32
    GPIO_Init(GPIOC, &GPIO_InitStructure);
33
34
35
36
    GPIO_TypeDef *GPIOc = GPIOC;
37
38
    //io_setOutput(LED2_GPIO, LED2_Pin);
39
    //io_setOutput(GPIOC, GPIO_Pin_1);
40
    while(1) {
41
        //io_toggleBit(GPIOC, GPIO_Pin_1);
42
        //io_toggleBit(LED2_GPIO, LED2_Pin);
43
        GPIOC->BSRRL = GPIO_Pin_1 | GPIO_Pin_13;
44
        /* Reset PG6 and PG8 */
45
        GPIOC->BSRRH = GPIO_Pin_1 | GPIO_Pin_13;
46
    }
47
48
    return 0;
49
}
50
51
52
53
void SysTick_Handler(void)
54
{
55
}

A. B. schrieb:
> PA5 kommt da gar nicht vor
> bzw. eben gerade nicht in exakt gleicher Weise wie PC1. Da werden
> sozusagen Äpfel mit Birnen verglichen.

Ja, ich hatte die Set-Befehle für PA5 mit PC1 ersetzt.

A. B. schrieb:
> Es bleibt auch noch, dass der Compiler etwas wegoptimiert

Debug-Build extra ohne Optimierung - obwohl ein __IO-Registerzugriff 
ohnehin nicht wegoptimiert werden dürfte.

Bei keinem der Pins ist GPIO eine alternative Funktion.

von Walter T. (nicolas)


Lesenswert?

A. B. schrieb:
> Derer gibt's zwei: 64 und

Und genau das ist es. Das sollte für das Problem aber keinen Unterschied 
machen.

Das Datenblatt sagt: Alle GPIOs hängen auch wirklich am AHB1.

Der Startup-Code ist unverändert der, der von EMBlitz erzeugt wird.

OK, ich sehe: In der StdPeriphLibrary von 2016 sind im Vergleich zu der, 
die EmBlitz hineinpackt (September 2011), etliche Sachen für den 
STM32F446 hinzugekommen. Da werde ich mal ein manuelles Update 
vornehmen.

: Bearbeitet durch User
von Mampf F. (mampf) Benutzerseite


Lesenswert?

Walter T. schrieb:
> RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
>     RCC_APB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
>     RCC_APB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
>     RCC_APB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
>     RCC_APB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);

Kuck mal ... APB und AHB passen nicht.

Scheiß Fehler, der mich auch schon zum Wahnsinn getrieben hat! :)

von A. B. (Gast)


Lesenswert?

Die entscheidende Fragen sind aber leider unbeantwortet, nämlich:

Werden die Register tatsächlich korrekt beschrieben? Man sollte da 
Glauben von Tatsachen trennen ...
Also im Zweifelsfall JEDES Register nach dem Schreiben überprüfen.
Und wie schon erwähnt am Pin mit den Widerständen prüfen, ob zumindest 
auf Ausgang geschaltet wird.

Es ist schlichtweg unrealistisch, dass sich die Register nicht 
beschreiben lassen (wenn sie nicht gerade gelockt, der Takt nicht 
eingeschaltet oder der Chip hinüber ist), wie in der Überschrift 
behauptet wird!

Weitere Idee: Kurzes Delay nach dem Einschalten der GPIO-Clocks. Es kann 
durchaus sein, dass die Register ein paar Takte "Anlaufzeit" benötigen.

von Walter T. (nicolas)


Lesenswert?

Mampf F. schrieb:
> Kuck mal ... APB und AHB passen nicht.

Ähem...das ist wohl das Äquivalent zu "Benzin vergessen!". Danke! Das 
war's.

von W.S. (Gast)


Lesenswert?

Mampf F. schrieb:
> Scheiß Fehler, der mich auch schon zum Wahnsinn getrieben hat! :)

Und da gibt es immer noch Leute, die behaupten, daß durch extensives 
Ersetzen von (1<<xyz) durch ominöse Identifer aus CMSIS der Code besser 
und lesbarer würde.

mit leisem Grinsen zum Tag der großen Vereinheitlichung

W.S.

von Mampf F. (mampf) Benutzerseite


Lesenswert?

W.S. schrieb:
> Mampf F. schrieb:
>> Scheiß Fehler, der mich auch schon zum Wahnsinn getrieben hat! :)
>
> Und da gibt es immer noch Leute, die behaupten, daß durch extensives
> Ersetzen von (1<<xyz) durch ominöse Identifer aus CMSIS der Code besser
> und lesbarer würde.

Ja oder noch geiler - hat mich einen Tag gekostet [1], den Fehler zu 
finden:

GPIO_PinAFConfig funktioniert nicht mit zB GPIO_Pin_4 sondern mit 
GPIO_PinSource_4

Ersteres ist 1<<4, zweiteres einfach nur 4. Aber vmtl hätte ich dann 
trotzdem 1<<4 als Parameter verwendet xD




[1]: Beitrag "[STM32F4] I2S macht nichts PLL-Clock-Problem?!"

: Bearbeitet durch User
von W.S. (Gast)


Lesenswert?

Tja, manchmal frag ich mich, warum denn die Leute sich selbst das Leben 
so schwer machen und jegliche Art klaren lesbaren Codes vermeiden wie 
der Teufel das Weihwasser. Ich kann's mir nur so erklären, daß die 
allermeisten keine entsprechende fachliche Ausbildung im Kreuz haben und 
eben genau deshalb sklavisch jeden Firlefanz nachmachen, den man ihnen 
irgendwann mal vorgemacht hat - ohne in der Lage zu sein, das Ganze nach 
Sinn zu hinterfragen.

Und bei Folgendem bin ich mir sicher, daß der gute Walter in 3 Monaten 
nicht mehr weiß, wozu er das überhaupt geschrieben hat:
1
static inline __attribute__((always_inline))  uint32_t  stretch2bitmask16(const uint16_t word)
2
3
oder
4
5
typedef  GPIO_TypeDef GPIO_t;
6
typedef  uint16_t IOPin_t;
7
8
static inline __attribute__((always_inline))
9
    void io_setOutput(GPIO_t * GPIOx, const IOPin_t Pin)

Ganz zu schweigen von
1
uint32_t out = 0;
2
    out |= word & 0x0001U ?  0x00000003U : 0;
3
    out |= word & 0x0002U ?  0x0000000CU : 0;

Hier kriegt "out" aber ne ganz dolle Null drübergeordert. Und 0x0001U 
ist natürlich viel genauer als 1, gelle?

Da fällt mir eine Anekdote über Heinrich Schlusnus ein: In irgendeiner 
Bühnenszene gibt's nen Zweikampf, der andere fällt regiegemäß getroffen 
zu Boden, liegt aber dabei eher unbequem. Also räkelt er sich so hin, 
daß er bequemer daliegt. Das Publikum grinst schon.. aber Schlusnus 
rettet die Situation per Stegreif: "Was denn Kerl, du röchelst noch? 
Soll ich dich noch töter töten?"

W.S.

von Walter T. (nicolas)


Lesenswert?

W.S. schrieb:
> Und 0x0001U
> ist natürlich viel genauer als 1, gelle?

Das 'U' kann mich keinen Cent gekostet.

Wenn Du hier:

Walter T. schrieb:
> out |= word & 0x8000U ?  0xC0000000U : 0;

auf den ersten Blick weißt, ob Dir irgendwelche integer propagation 
rules mit ihrem Vorzeichen unter irgendeiner Compilerversion irgendeinen 
Fehler an einem selten genutzten Pin 15 erzeugen können - Glückwunsch. 
Ich bin nicht vom Fach. Habe aber dafür vor Jahren 10-Finger-tippen 
gelernt.  Ein Buchstabe kostet mich weniger Zeit, als über irgendwelche 
propagation rules zu sinnieren.

Ja, die Funktion ist quick & dirty. Selbst der Compiler mag sie so 
wenig, daß er sie direkt zu einem konstanten Wert wegoptimiert.

von Mampf F. (mampf) Benutzerseite


Lesenswert?

W.S. schrieb:
> Hier kriegt "out" aber ne ganz dolle Null drübergeordert.

Falls das nicht zu einem konstanten Wert optimiert wird, würde der 
Compiler wohl die ARM-Assembler-Befehle conditional verwenden, dann 
würde da vermutlich keine 0 drübergeodert werden.

Aber ansonsten muss ich W.S. doch etwas Recht geben ...

Ich für mich schreibe meinen Code für ARM-µCs mittlerweile plain stupid 
einfach runter ohne mit mit Attributen, Inline, ... oder irgendwas 
Anderem zu verkünsteln - außer ich brauche sie wirklich (wie packed 
struct gelegentlich) oder wenn etwas im TCM landen muss.

Die Dinger sind mittlerweile so schnell und die Compiler so gut - 
jegliche Optimierung, die man sich selbst überlegt ist da fast sinnlos, 
außer sie reduzieren die Komplexität irgendwelcher Algorithmen von 
O(n^2) auf O(n*log(n)) oder so, aber das kommt eher selten vor und 
selbst dann kann man noch abschätzen, ob O(n^2) nicht gut genug ist.

Es heißt immer: Ein gutes Pferd springt nur so hoch es muss :)

Dazu gehört dann natürlich auch, dass man keine Library-Funktionen per 
Hand (schlechter) nachbaut.

Vlt sind viele hier 8Bit µC-geschädigt und denken immer noch in der 
Schiene, Peripherie direkt ansprechen, in Assembler programmieren, jedes 
Bit optimieren usw ...

: Bearbeitet durch User
von Jim M. (turboj)


Lesenswert?

Schau mal ganz scharf hin:
1
 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
2
//    ^                      ^
3
 RCC_APB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
4
//    ^                      ^
5
 RCC_APB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
6
//    ^                      ^

Da wird IMHO die flasche Funktion aufgerufen - und damit haben die GPIOs 
ab Port B keine Clock.

von Walter T. (nicolas)


Lesenswert?

Mampf F. schrieb:
> Dazu gehört dann natürlich auch, dass man keine Library-Funktionen per
> Hand (schlechter) nachbaut.

Das ist doch klar. Wenn man eine eigene Funktion baut, sollte sie für 
irgendeinen Zweck besser sein als das, was man sowieso schon hat.

In meinem Fall ist das das schnelle Umschalten zwischen Input und 
Output.

Und daß ich vom Umzug vom STM32F103 auf den STM32F446 und später wieder 
zurück möglichst wenig ändern muß.

von Stefan F. (Gast)


Lesenswert?

Oh, Jim hat es jetzt auch gemerkt. Spätzünder bzw. Nichtleser.

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.