Forum: Mikrocontroller und Digitale Elektronik STM32 USART mit falscher Baudrate


von Thorsten E. (bluescreen)


Lesenswert?

Hallo,

ich versuche nun seit Tagen ein STM32 Board zum Laufen zu bekommen. Nach 
ewigem Gefummel mit den Loaderfiles habe ich immerhin nun erste 
Ergebnisse. Es blinkt und gibt etwas auf dem USART aus. Letzteres 
allerdings mit 9600Bd anstelle der eingestellt 19200Bd. Und genau darum 
geht es: warum ist die USART Geschwindigkeit falsch.

Das Board hat einen STM32F103VET Prozessor mit 8MHz Quarz.
Als Entwicklungsumgebung nutze ich Code::Blocks mit ARM-GCC 
(arm-none-eabi-gcc).

Ich habe mal die Funktion RCC_GetClocksFreq genutzt um die 
Taktfrequenzen anzuzeigen:
1
SYSCLK: 48000000
2
HCLK  : 48000000
3
PCLK1 : 48000000
4
PCLK2 : 48000000
5
ADCCLK: 24000000

Eigenlich sollte SYSCLK ja auf 72MHz stehen. Warum nur tut es das nicht?

Um zu verstehen was ich so treibe füge ich im Folgenden mal meine 
Quelltexte an. Ich hoffe, das sprengt hier nicht den Rahmen.
Vielleicht kann mir jemand die Nase auf die Stelle stossen wo ich einen 
Fehler mache.

Meine Compileroptionen sehen so aus:
1
arm-none-eabi-gcc.exe 
2
  -mcpu=cortex-m3 -mthumb 
3
  -O2 -Wall -g -fno-common 
4
  -D__NO_CTOR_DTOR_SUPPORT__ 
5
  -DUSE_IRQ -DUSE_STDPERIPH_DRIVER 
6
  -DHSE_VALUE=8000000 
7
  -DSTM32F10X_HD_VL 
8
  -g -ID:\Entwickl\WinARM\Projects\Blink_STM32F1\ 
9
  -I..\..\lib\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\STM32F10x_StdPeriph_Driver\inc 
10
  -I..\..\lib\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\Include 
11
  -I..\..\lib\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x 
12
  -I..\..\lib\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\CoreSupport 
13
  -c ..\..\lib\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\system_stm32f10x.c 
14
  -o default\lib\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\system_stm32f10x.o

Sowas natürlich für alle beteiligten C-Dateien. In meinem Programm lasse 
ich auch per SYSTICK eine LED im Sekundentakt blinken. Das klappt mit 
korrekter Geschwindigkeit.
Meine stm32f10x_conf.h:
1
#ifndef STM32F10X_CONF_H
2
#define STM32F10X_CONF_H
3
4
// define the target processor type
5
#define STM32F103xE
6
7
// include the needed device drivers
8
#include "stm32f10x_gpio.h"
9
#include "stm32f10x_rcc.h"
10
#include "stm32f10x_usart.h"
11
12
/* Exported types ------------------------------------------------------------*/
13
/* Exported constants --------------------------------------------------------*/
14
/* Uncomment the line below to expanse the "assert_param" macro in the
15
   Standard Peripheral Library drivers code */
16
/* #define USE_FULL_ASSERT    1 */
17
18
#endif // STM32F10X_CONF_H

Meine system_stm32f1xx.c:
1
#include "stm32f1xx.h"
2
#if !defined  (HSE_VALUE) 
3
  #define HSE_VALUE    ((uint32_t)8000000) /*!< Default value of the External oscillator in Hz.
4
                                                This value can be provided and adapted by the user application. */
5
#endif /* HSE_VALUE */
6
7
#if !defined  (HSI_VALUE)
8
  #define HSI_VALUE    ((uint32_t)8000000) /*!< Default value of the Internal oscillator in Hz.
9
                                                This value can be provided and adapted by the user application. */
10
#endif /* HSI_VALUE */
11
12
/*!< Uncomment the following line if you need to use external SRAM  */ 
13
#if defined(STM32F100xE) || defined(STM32F101xE) || defined(STM32F101xG) || defined(STM32F103xE) || defined(STM32F103xG)
14
/* #define DATA_IN_ExtSRAM */
15
#endif /* STM32F100xE || STM32F101xE || STM32F101xG || STM32F103xE || STM32F103xG */
16
17
/*!< Uncomment the following line if you need to relocate your vector Table in
18
     Internal SRAM. */ 
19
/* #define VECT_TAB_SRAM */
20
#define VECT_TAB_OFFSET  0x0 /*!< Vector Table base offset field. 
21
                                  This value must be a multiple of 0x200. */
22
23
/*******************************************************************************
24
*  Clock Definitions
25
*******************************************************************************/
26
#if defined(STM32F100xB) ||defined(STM32F100xE)
27
  uint32_t SystemCoreClock         = 24000000;        /*!< System Clock Frequency (Core Clock) */
28
#else /*!< HSI Selected as System Clock source */
29
  uint32_t SystemCoreClock         = 72000000;        /*!< System Clock Frequency (Core Clock) */
30
#endif
31
32
const uint8_t AHBPrescTable[16] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 6, 7, 8, 9};
33
const uint8_t APBPrescTable[8] =  {0, 0, 0, 0, 1, 2, 3, 4};
34
35
#if defined(STM32F100xE) || defined(STM32F101xE) || defined(STM32F101xG) || defined(STM32F103xE) || defined(STM32F103xG)
36
#ifdef DATA_IN_ExtSRAM
37
  static void SystemInit_ExtMemCtl(void); 
38
#endif /* DATA_IN_ExtSRAM */
39
#endif /* STM32F100xE || STM32F101xE || STM32F101xG || STM32F103xE || STM32F103xG */
40
41
void SystemInit (void)
42
{
43
  /* Reset the RCC clock configuration to the default reset state(for debug purpose) */
44
  /* Set HSION bit */
45
  RCC->CR |= (uint32_t)0x00000001;
46
47
  /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
48
#if !defined(STM32F105xC) && !defined(STM32F107xC)
49
  RCC->CFGR &= (uint32_t)0xF8FF0000;
50
#else
51
  RCC->CFGR &= (uint32_t)0xF0FF0000;
52
#endif /* STM32F105xC */   
53
  
54
  /* Reset HSEON, CSSON and PLLON bits */
55
  RCC->CR &= (uint32_t)0xFEF6FFFF;
56
57
  /* Reset HSEBYP bit */
58
  RCC->CR &= (uint32_t)0xFFFBFFFF;
59
60
  /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
61
  RCC->CFGR &= (uint32_t)0xFF80FFFF;
62
63
#if defined(STM32F105xC) || defined(STM32F107xC)
64
  /* Reset PLL2ON and PLL3ON bits */
65
  RCC->CR &= (uint32_t)0xEBFFFFFF;
66
67
  /* Disable all interrupts and clear pending bits  */
68
  RCC->CIR = 0x00FF0000;
69
70
  /* Reset CFGR2 register */
71
  RCC->CFGR2 = 0x00000000;
72
#elif defined(STM32F100xB) || defined(STM32F100xE)
73
  /* Disable all interrupts and clear pending bits  */
74
  RCC->CIR = 0x009F0000;
75
76
  /* Reset CFGR2 register */
77
  RCC->CFGR2 = 0x00000000;      
78
#else
79
  /* Disable all interrupts and clear pending bits  */
80
  RCC->CIR = 0x009F0000;
81
#endif /* STM32F105xC */
82
    
83
#if defined(STM32F100xE) || defined(STM32F101xE) || defined(STM32F101xG) || defined(STM32F103xE) || defined(STM32F103xG)
84
  #ifdef DATA_IN_ExtSRAM
85
    SystemInit_ExtMemCtl(); 
86
  #endif /* DATA_IN_ExtSRAM */
87
#endif 
88
89
#ifdef VECT_TAB_SRAM
90
  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
91
#else
92
  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
93
#endif 
94
}
95
96
void SystemCoreClockUpdate (void)
97
{
98
  uint32_t tmp = 0, pllmull = 0, pllsource = 0;
99
100
#if defined(STM32F105xC) || defined(STM32F107xC)
101
  uint32_t prediv1source = 0, prediv1factor = 0, prediv2factor = 0, pll2mull = 0;
102
#endif /* STM32F105xC */
103
104
#if defined(STM32F100xB) || defined(STM32F100xE)
105
  uint32_t prediv1factor = 0;
106
#endif /* STM32F100xB or STM32F100xE */
107
    
108
  /* Get SYSCLK source -------------------------------------------------------*/
109
  tmp = RCC->CFGR & RCC_CFGR_SWS;
110
  
111
  switch (tmp)
112
  {
113
    case 0x00:  /* HSI used as system clock */
114
      SystemCoreClock = HSI_VALUE;
115
      break;
116
    case 0x04:  /* HSE used as system clock */
117
      SystemCoreClock = HSE_VALUE;
118
      break;
119
    case 0x08:  /* PLL used as system clock */
120
121
      /* Get PLL clock source and multiplication factor ----------------------*/
122
      pllmull = RCC->CFGR & RCC_CFGR_PLLMULL;
123
      pllsource = RCC->CFGR & RCC_CFGR_PLLSRC;
124
      
125
#if !defined(STM32F105xC) && !defined(STM32F107xC)      
126
      pllmull = ( pllmull >> 18) + 2;
127
      
128
      if (pllsource == 0x00)
129
      {
130
        /* HSI oscillator clock divided by 2 selected as PLL clock entry */
131
        SystemCoreClock = (HSI_VALUE >> 1) * pllmull;
132
      }
133
      else
134
      {
135
 #if defined(STM32F100xB) || defined(STM32F100xE)
136
       prediv1factor = (RCC->CFGR2 & RCC_CFGR2_PREDIV1) + 1;
137
       /* HSE oscillator clock selected as PREDIV1 clock entry */
138
       SystemCoreClock = (HSE_VALUE / prediv1factor) * pllmull; 
139
 #else
140
        /* HSE selected as PLL clock entry */
141
        if ((RCC->CFGR & RCC_CFGR_PLLXTPRE) != (uint32_t)RESET)
142
        {/* HSE oscillator clock divided by 2 */
143
          SystemCoreClock = (HSE_VALUE >> 1) * pllmull;
144
        }
145
        else
146
        {
147
          SystemCoreClock = HSE_VALUE * pllmull;
148
        }
149
 #endif
150
      }
151
#else
152
      pllmull = pllmull >> 18;
153
      
154
      if (pllmull != 0x0D)
155
      {
156
         pllmull += 2;
157
      }
158
      else
159
      { /* PLL multiplication factor = PLL input clock * 6.5 */
160
        pllmull = 13 / 2; 
161
      }
162
            
163
      if (pllsource == 0x00)
164
      {
165
        /* HSI oscillator clock divided by 2 selected as PLL clock entry */
166
        SystemCoreClock = (HSI_VALUE >> 1) * pllmull;
167
      }
168
      else
169
      {/* PREDIV1 selected as PLL clock entry */
170
        
171
        /* Get PREDIV1 clock source and division factor */
172
        prediv1source = RCC->CFGR2 & RCC_CFGR2_PREDIV1SRC;
173
        prediv1factor = (RCC->CFGR2 & RCC_CFGR2_PREDIV1) + 1;
174
        
175
        if (prediv1source == 0)
176
        { 
177
          /* HSE oscillator clock selected as PREDIV1 clock entry */
178
          SystemCoreClock = (HSE_VALUE / prediv1factor) * pllmull;          
179
        }
180
        else
181
        {/* PLL2 clock selected as PREDIV1 clock entry */
182
          
183
          /* Get PREDIV2 division factor and PLL2 multiplication factor */
184
          prediv2factor = ((RCC->CFGR2 & RCC_CFGR2_PREDIV2) >> 4) + 1;
185
          pll2mull = ((RCC->CFGR2 & RCC_CFGR2_PLL2MUL) >> 8 ) + 2; 
186
          SystemCoreClock = (((HSE_VALUE / prediv2factor) * pll2mull) / prediv1factor) * pllmull;                         
187
        }
188
      }
189
#endif /* STM32F105xC */ 
190
      break;
191
192
    default:
193
      SystemCoreClock = HSI_VALUE;
194
      break;
195
  }
196
  
197
  /* Compute HCLK clock frequency ----------------*/
198
  /* Get HCLK prescaler */
199
  tmp = AHBPrescTable[((RCC->CFGR & RCC_CFGR_HPRE) >> 4)];
200
  /* HCLK clock frequency */
201
  SystemCoreClock >>= tmp;  
202
}
203
204
#ifdef DATA_IN_ExtSRAM
205
void SystemInit_ExtMemCtl(void) 
206
{
207
  __IO uint32_t tmpreg;
208
  /*!< FSMC Bank1 NOR/SRAM3 is used for the STM3210E-EVAL, if another Bank is 
209
    required, then adjust the Register Addresses */
210
211
  /* Enable FSMC clock */
212
  RCC->AHBENR = 0x00000114;
213
214
  /* Delay after an RCC peripheral clock enabling */
215
  tmpreg = READ_BIT(RCC->AHBENR, RCC_AHBENR_FSMCEN);
216
  
217
  /* Enable GPIOD, GPIOE, GPIOF and GPIOG clocks */
218
  RCC->APB2ENR = 0x000001E0;

Zu guter Letzt mein Hauptprogramm:
1
int main (void)
2
{
3
    GPIO_InitTypeDef GPIO_InitStructure;
4
5
    SystemInit() ;
6
7
    // setup systick timer
8
    if (SysTick_Config(SystemCoreClock/1000)) assert_param("SYSTICK init failed");
9
10
    // setup LED Pin (GPIOB, Pin 5)
11
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
12
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;
13
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_5;
14
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
15
    GPIO_Init(GPIOB, &GPIO_InitStructure);
16
17
    // setup USART1
18
    USART_InitTypeDef USART_InitStructure;
19
    // Enable USART and GPIO clock
20
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO | RCC_APB2Periph_USART1, ENABLE);
21
22
    // Configure USART1 Rx as input floating
23
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_10;
24
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
25
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_IN_FLOATING;
26
    GPIO_Init(GPIOA, &GPIO_InitStructure);
27
28
    // Configure USART1 Tx as alternate function push-pull
29
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;
30
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
31
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
32
    GPIO_Init(GPIOA, &GPIO_InitStructure);
33
34
    // Enable the USART1
35
    USART_Cmd(USART1, ENABLE);
36
37
    // Configure USART1
38
    USART_InitStructure.USART_BaudRate   = 19200;
39
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
40
    USART_InitStructure.USART_StopBits   = USART_StopBits_1;
41
    USART_InitStructure.USART_Parity     = USART_Parity_No;
42
    USART_InitStructure.USART_Mode       = USART_Mode_Rx | USART_Mode_Tx;
43
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
44
    USART_Init(USART1, &USART_InitStructure);
45
46
47
    char s_abcd[70];
48
    itoa(SystemCoreClock, s_abcd, 10);
49
50
  while(1)
51
    {
52
        USART_puts(USART1, "Clock: ");
53
        USART_puts(USART1, s_abcd);
54
        USART_puts(USART1, "\r\n");
55
        GPIOB->ODR^=GPIO_Pin_5;
56
        _delay_ms(500);
57
    }
58
}

Leider kann ich nicht durch den Code debuggen, da ich den OpenOCD mit 
STLINK-V2 unter Codeblocks nicht ordentlich zum Laufen bekomme. GDB 
startet das Target immer sofort nach dem Download des Binaries und 
stellt dann erst die Breakpoints ein. Mit dem Erfolg, dass die 
Breakpoints erst gesetzt werden wenn das Programm bereits in der 
Hauptschleife läuft.

Vielleicht dazu noch meine Debugger Settings:
1
monitor halt
2
load ./default/Blink_STM32F1.bin
3
file ./bin/default/Blink_STM32F1.bin
4
monitor sleep 1000
5
monitor reset
6
monitor halt

von Schnupftabak (Gast)


Lesenswert?

>Eigenlich sollte SYSCLK ja auf 72MHz stehen. Warum nur tut es das nicht?

CubeMX zeigt ua. den Clocktree an.
Sollte beim Durchblicken helfen...

>Vielleicht dazu noch meine Debugger Settings:

>load ./default/Blink_STM32F1.bin
>file ./bin/default/Blink_STM32F1.bin

'load' kann direkt ein elf File laden. Dann hast Du auch die
Debuginformationen im GDB zur Verfügung.

'load' und 'file' zusammen ist sinnlos.

>monitor sleep 1000

Warum?
Angstschlaf?

>GDB startet das Target immer sofort nach dem Download des Binaries

Mit der gezeigten GDB Initialisierung nicht...
Das Target startet erst mit einem 'continue'
Mglw. sendet Dein Codeblocks das implizit...
Vielleicht kannst Du die Breakpoint Kommandos den Debugger Settings
hinzufügen.
Es muss aber auch einen "offiziellen" Weg geben. Ich kenne CB aber 
nicht.

>monitor reset

Bei OOCD

mon reset halt oder
mon reset init benutzen.

Siehe OOCD Manual.

von Thorsten E. (bluescreen)


Lesenswert?

Das mit dem CubeMX hatte ich tatsächlich schon gemacht. Insofern war mir 
theoretisch klar wie es gehen sollte. Problem sind die mehrfach 
ineinander geschachtelten Includes der ST Standard Libraries die alle 
möglichen Defines erzeugen und nutzen um den Taktpfad zu konfigurieren. 
Wahrscheinlich wäre es einfacher, die Standard Peripheral Library NICHT 
zu nutzen, sondern die paar Bits einfach so zu setzen.

Das Geheimnis war am Ende ganz einfach. Der STM32F103VET ist KEIN "Value 
Line" Prozessor. Daher gehört in den Compileraufruf statt 
-DSTM32F10X_HD_VL einfach nur -DSTM32F10X_HD. Warum das nun Auswirkungen 
auf den USART aber nicht auf den Systick hat, habe ich nicht weiter 
untersucht.

Nun zum Debug. Die Anregungen von "Schnupftabak" waren augenöffnend. Die 
Halt-Befehle heissen nicht nur "halt", sonder "reset halt". Nun habe ich 
es so eingestellt:
1
monitor reset halt
2
load ./default/Blink_STM32F1.elf
3
monitor reset halt

Der Sleep-Befehl aus der Ursprungskonfig sollte dazu dienen dem Debugger 
eine Chance zu geben, die Breakpoints zu setzen bevor der Prozessor 
losrennt. Ist aber nicht nötig.

Nun startet zwar der Prozessor sofort los (warum bloß) wird aber sofort 
bei einem Breakpoint auf dem ResetHandler angehalten. Dies zeigt 
Code::Blocks allerdings nicht an. Nun kann man irgendwohin einen 
Breakpoint setzen und "Start/Continue" drücken und er läuft wie geplant 
bis dorthin.

Also alles gut. Danke!

Schade, dass es weder im Code::Blocks noch auf meiner Platine einen 
Resetknopf gibt.

Nun muss ich sehen, dass ich malloc und Co zum Laufen bekomme. Im 
Gegensatz zur antiken WinAVR Installation muß man ja offensichtlich beim 
modernen ARM Zeugs alles selber machen. :-(

von W.S. (Gast)


Lesenswert?

Thorsten E. schrieb:
> Wahrscheinlich wäre es einfacher, die Standard Peripheral Library NICHT
> zu nutzen, sondern die paar Bits einfach so zu setzen.
>
> Das Geheimnis war am Ende ganz einfach. Der STM32F103VET ist KEIN "Value
> Line" Prozessor. Daher gehört in den Compileraufruf statt
> -DSTM32F10X_HD_VL einfach nur -DSTM32F10X_HD. Warum das nun Auswirkungen
> auf den USART aber nicht auf den Systick hat, habe ich nicht weiter
> untersucht.

Also mit anderen Worten: Es läuft zwar mittlerweile, aber du weißt noch 
immer nicht, was du da eigentlich getan hast und wie das Ganze 
funktioniert. Obendrein kommen mir die genannten Kommandozeilenparameter 
suspekt vor. Kann da nicht einfach sowas wie --cpu=Cortex-M3 stehen (wie 
beim Keil)? Falls ich mich recht erinnern sollte, wäre das beim Gcc etwa 
so: -mcpu=cortexm3 oder so ähnlich.

Ansonsten ist dein gepostetes Zeugs dramatisch zu viel, als daß sich da 
jemand dort durcharbeiten wollte. Und mit deiner Erkenntnis, daß man 
diese ST-Lib lieber außen vor lassen sollte, hast du Recht. Sag ich 
schon seit langem. Die Unbedarften glauben, mit Cube, ST-Lib und anderem 
einschlägigen Zeugs die Bürde des Lesens in den Referenzmanuals und das 
Verstehenlernen der zugrundeliegenden Hardware umgehen zu können, aber 
in Wirklichkeit bleiben sie bloß ahnungslose Dilettanten, die sich in 
ihrer IDE verlieren, anstatt sich um den µC zu kümmern.

W.S.

von Thorsten E. (bluescreen)


Lesenswert?

> Also mit anderen Worten: Es läuft zwar mittlerweile, aber du weißt noch
> immer nicht, was du da eigentlich getan hast und wie das Ganze
> funktioniert.
Stimmt so halb. Ich habe mir die StdPeriphLib nicht im Detail angesehen. 
Muß ich aber eigentlich aber auch nicht. Ich schau mir ja auch nicht den 
Quelltext meines Windows an. Bei einigen Dingen sollte man auch glauben, 
dass sich der Entwickler etwas dabei gedacht hat. :-)

> suspekt vor. Kann da nicht einfach sowas wie --cpu=Cortex-M3 stehen (wie
> beim Keil)? Falls ich mich recht erinnern sollte, wäre das beim Gcc etwa
> so: -mcpu=cortexm3 oder so ähnlich.
Genau das steht ja auch drin (s.o.). Allerdings fehlte es in der Tat 
beim Linkeraufruf. Es ist mir aber schleierhaft wozu der Linker wissen 
muss, welche CPU genutzt wird. Der Compiler erzeugt den Code, der Linker 
fügt ihn nur zusammen. Aber man lernt halt nie aus. Achja, der Linker 
will auch wissen, dass er Thumb-Code erzeugen soll staun.

> Die Unbedarften glauben, mit Cube, ST-Lib und anderem
> einschlägigen Zeugs die Bürde des Lesens in den Referenzmanuals und das
> Verstehenlernen der zugrundeliegenden Hardware umgehen zu können, aber
> in Wirklichkeit bleiben sie bloß ahnungslose Dilettanten, die sich in
> ihrer IDE verlieren, anstatt sich um den µC zu kümmern.
Naja, da kann ich Dir nur halb zustimmen. Wie oben schon angedeutet will 
ich mich ja eher um meine Applikation kümmern und nicht erst das 
"Betriebssystem" entwickeln. Das ist Ressourcenverschwendung wenn das 
jeder für sich tun muss.
Insofern finde ich es auch traurig, dass man so viel "frickeln" muss um 
in Gang zu kommen.
Warum wird nicht eine vernünftige clib samt den hardwarenahen Routinen 
(_sbrk, _write, ...) zur Verfügung gestellt, die man einfach benutzen 
kann. Ab besten als (quelloffene) Library und nicht als Haufen 
Quellfiles die man umständlich und unübersichtlich in sein Projekt 
kopieren muss.

Ein Musterbeispiel war da m.E. der WinAVR. Einmal installieren, ein 
Headerfile einbinden und ein mitgeliefertes Makefile anpassen und 
loslegen. WinAVR hatte ich ohne AVR Vorkenntnisse in ca einer Stunde am 
Laufen, mit malloc, printf und Co. Beim ARM bin ich nun schon seit 1,5 
Wochen dabei und habs immer noch nicht vollständig im Gange.

Falls jemand eine funktionierende und zuverlässige Implementierung von 
_sbrk und Co kennt, bitte mal zeigen.

So, nun habe ich vermutlich einen Flamewar zwischen Puristen und 
"Mausschubsern" ausgelöst. Ich lass mich überraschen.

>
> W.S.

von W.S. (Gast)


Lesenswert?

Thorsten E. schrieb:
> So, nun habe ich vermutlich einen Flamewar zwischen Puristen und
> "Mausschubsern" ausgelöst. Ich lass mich überraschen.

Nö.

Ein jeder kann es halt so treiben wie er will. Aber manche kriegen 
mangels eigener Fähigkeiten oder zu großer Faulheit keinen Fuß auf den 
Teppich und dann wird behauptet, daß es ja so unsäglich schwer sei. Und: 
Das ist es auch. Wenn ich mir das Quellcode-Gestrüpp so ansehe, was da 
mancher mit dem Benutzen der ST-Lib erzeugt und wo man sich mit 
Sicherheit darin verheddert, dann wundert mich nix mehr.

Und dein "Ich habe mir die StdPeriphLib nicht im Detail angesehen. Muß 
ich aber eigentlich aber auch nicht....  Bei einigen Dingen sollte man 
auch glauben, dass sich der Entwickler etwas dabei gedacht hat" ist ein 
Denkweise in die falsche Richtung.

Ich kann dir sagen, WOZU die Firma ST ihre unsägliche Lib unter die 
Leute gebracht hat: Um die Naiven fest an sich zu binden. Wer nicht 
anders kann, als sowas zu benutzen und die HW nicht mehr kennt oder 
kennen WILL, der muß halt zum Chip von ST greifen und obendrein auch 
noch nen größeren nehmen als eigentlich nötig, da er einen Haufen 
Geschwulst damit am Bein hat.

Verstehe mal: es gibt da nen Interessenkonflikt zwischen µC-Hersteller 
und Programmierer.

Nochwas: dein "Ich schau mir ja auch nicht den Quelltext meines Windows 
an." ist auch falsche Denke. Natürlich tust du das nicht, denn du bist 
kein PC-Mainboard-Hersteller und auch keiner von Bills Programmierern. 
Aber wenn du Firmware für nen µC schreiben willst, dann bist du sehr 
wohl µC-Entwickler und damit eigentlich in der Pflicht, deine HW zu 
kennen. Du bist kein 'Benutzer' oder 'Verbraucher', sondern einer der im 
Herstellungsprozeß angesiedelt ist, also im weiten Sinn bist du 
Hersteller.

Und was dein "..will ich mich ja eher um meine Applikation kümmern und 
nicht erst das "Betriebssystem" entwickeln. Das ist 
Ressourcenverschwendung wenn das jeder für sich tun muss." betrifft, da 
sag ich dir, daß das durchaus keine Ressourcenverschwendung ist. Gerade 
das Gebiet der µC-Anwendungen ist IMMER eine Mischung aus Gerätetechnik, 
Schaltungstechnik, qualifizierter Kenntnis des betreffenden µC und 
Programmieren. Da ist das Schreiben oder Anpassen von Lowlevel-treibern 
für dies und das ein wirklich notwendiger Teil des Kennenlernens des µC. 
Wer das ignoriert, bleibt mit den Füßen in der Luft hängen. Und 
abgesehen davon ist das Ganze ja auch nicht wirklich schwer - jedenfalls 
nicht für einen Ingenieur.

W.S.

von Thorsten E. (bluescreen)


Lesenswert?

Ich glaube da haben wir uns etwas falsch verstanden. Ich WILL durchaus 
den Chip verstehen (sonst würde ich Arduino nehmen). Und ich habe ja 
auch selbst schon bemerkt, das die ST Lib alles andere als ein 
Musterbeispiel der Programmierung ist. Ich muß ja zum Beispiel den Chip 
kennen lernen um Dinge wie einen DMA-Transfer zum LCD zu bauen, oder die 
Ansteuerung eine LED Matrix. Da will ich dann gerne lernen wie DMA, 
USART und Co zu konfigurieren sind. Das musste ich beim AVR auch und das 
ist ja auch gerade der Spaß an der Sache.

Aber ein funktionsfähiges Grundgerüst erleichtert den Einstieg und 
schafft nicht schon Frustration bevor es losgeht. Dazu gehört meines 
Erachtens die Prozessorinitialisierung und vor allem eine halbwegs 
vollständige Implementierung der C Library. Warum soll jeder für sich 
wieder neu die Speicherverwaltung erfinden.

Und die Prozessorinitialisierung mit seinen vielen Taktmöglichkeiten ist 
ja nun nicht gerade einsteigerfreundlich. Da hilft durchaus ein 
grafisches Tool wie CubeMX. Nicht um Code zu erzeugen, sondern einfach 
nur die richtigen Bits in den richtigen Registern anzuzeigen.

Ich denke, in der Tat werde ich die StdPeriphLib auf Dauer nicht 
verwenden, da sie viel zu viel Unützes mitbringt und unübersichtlich 
ist.
Allein schon diese unsäglichen Initialisierungsorgien über Structs. 
Warum nicht einfach:
1
UsartInit(uint8_t usartno, uint8_t pintx, uint8_t pinrx, uint32_t baudrate);
2
UsartInit(USART1, 9, 10, 19200);
Nun da ich einen laufenden Prozessor habe, kann ich ja auch damit 
experimentieren und mir eine für mich ausreichende übersichtlicher Lib 
bauen. Dann vielleicht auch lieber einfach eine für jeden verwendeten 
Prozessortyp anstatt dutzender unübersichtlicher Defines. Was brauch die 
denn schon groß? Prozessorinit, die Basic Funktionen für die CLib und 
einige übersichtliche Initfunktionen für USART, Portpins und Timer. 
Alles andere wird eher selten genutzt und kann bei Bedarf dazugebaut 
werden.

von Shlumolps (Gast)


Lesenswert?

An W.S: Du hast 'mal wieder den Hinweis auf die "Lernbetty" vergseen.

von gnugnu (Gast)


Lesenswert?

Zurück zum Thema: Wenn Du eine Möglichkeit in deinem Debugsystem hast, 
die SFRs anzuzueigen, ist es ja relativ einfach, das Problem zu finden: 
Du weisst dann ja, welche Sollwerte in die USART und SYSCFG Register 
hineinmüssen und kannst durch schrittweises Step over herausfinden, 
welche Routinen wann und wie die Registerinhalte verdrehen...

von W.S. (Gast)


Lesenswert?

Thorsten E. schrieb:
> Warum nicht einfach:UsartInit(uint8_t usartno, uint8_t pintx, uint8_t
> pinrx, uint32_t baudrate);

Nö, lieber nicht sowas. Denk doch mal nach: den USART (als UART) zu 
initialisieren ist was ganz anderes, als die Pins zu verteilen. 
Abgesehen davon solltest du recht genau wissen, welchen U(S)ART du 
initialisieren willst. Also bleibt eigentlich nur die Baudrate und wenn 
du willst die Kommunikationseinstellung wie z.B 8N1 oder so.

Was die Grundinitialisierung betrifft, so ist die eigentlich bei den 
STM32F10x relativ leicht. Sie ist nicht wirklich gut, sowas können 
andere besser, aber sie geht. Zumindest ist das Taktschema 
leichtverständlich und bei den Ports kann man erstmal zwischen GPIO und 
Alternativfunktion wählen. Das nicht wirklich Gute (um nicht zu sagen as 
Blöde daran) ist das nötige Procedere, wenn man mehr als nur eine 
Alternativfunktion auf dem Pin hat. Dann muß man bei diesen µC nämlich 
alle anderen irgendwie aus dem Wege schaffen und das bedeutet, in die 
generelle Taktversorgung und in die jeweiligen Register der 
ungewünschten Funktion hineinzugrätschen. Geht, ist aber unschön. Bei 
anderen hat man ein dediziertes Register pro Pin, wo man die gewünschte 
Alternativfunktion direkt eintragen kann und das ist viel angenehmer.

So. Ich guck mal, ob ich für dich ein bissel Beispielcode habe.

W.S.

von Thorsten E. (bluescreen)


Lesenswert?

In meinem Vorschlag der UART Initialisierung war ja die Uartnummer mit 
drin. Ok, Datenformat nicht. Brauch ich in aller Regel auch nicht.
Mich stört aber, das jede Konfiguration sich über zig Zeilen erstreckt. 
Aber das ist vielleicht Geschmacksache.

Ich habe inzwischen funktionsfähige Grundfunktionen für die Nanolib 
gefunden. Nur eines funktioniert nicht.

Es wird die Funktion __MFP_Sp oder so ähnlich (sorry, hab den genauen 
Namen grad unterwegs nicht parat) genutzt um den Stackpointer 
auszulesen. Diese gibt es aber in meiner StdPeriphLib nicht. Wie kriegt 
man das hin? Assembler?

von Nop (Gast)


Lesenswert?

W.S. schrieb:
> Kann da nicht einfach sowas wie --cpu=Cortex-M3 stehen (wie
> beim Keil)? Falls ich mich recht erinnern sollte, wäre das beim Gcc etwa
> so: -mcpu=cortexm3 oder so ähnlich.

Ja für den GCC schon. Diese Defines werden aber "gebraucht", weil man 
darüber in diesem ST-Gestrüpp irgendwelche Sachen ein- und ausblendet. 
Also werden sie in dem Fall über Kommandozeile beim Compileraufruf 
definiert.

> Ich kann dir sagen, WOZU die Firma ST ihre unsägliche Lib unter die
> Leute gebracht hat: Um die Naiven fest an sich zu binden.

Richtig. Das ist keine "Hardware Abstraction Layer", weil es nichts 
abstrahiert, sondern das sind nur Wrapperfunktionen für die Register. 
Eigentlich ein HOL, Hardware Obfuscation Layer, und um die Parameter zu 
verstehen, die 1:1 aus dem reference manual abkopiert sind, muß man 
selbiges dann trotzdem lesen.

Thorsten E. schrieb:
> Bei einigen Dingen sollte man auch glauben,
> dass sich der Entwickler etwas dabei gedacht hat. :-)

Ich schätze, wenn Du den ST-Library-Krams noch weiter nutzt, wird dieser 
Glaube sich bei Dir auch noch erledigen.

Thorsten E. schrieb:
> Aber ein funktionsfähiges Grundgerüst erleichtert den Einstieg und
> schafft nicht schon Frustration bevor es losgeht. Dazu gehört meines
> Erachtens die Prozessorinitialisierung

Nein, denn bei ARM ist das Grundprinzip, daß Du vom Energieverbrauch her 
nur das zahlst, was Du auch benutzt, und per Default ist nichts 
aktiviert. Man muß also (fast) alles selber einschalten, und das ist 
auch gut so, weil man nur das einschaltet, was man braucht.

Ist auch besser so herum: denn wenn man vergißt, etwas einzuschalten, 
merkt man es sehr schnell daran, daß etwas nicht geht. Wenn alles an 
wäre, und man vergißt, es auszuschalten, ist das viel schwieriger, weil 
man nur einen erhöhten Energieverbrauch hat, aber nirgendwohin konkret 
zeigen kann, woran er denn liegen muß.

> und vor allem eine halbwegs
> vollständige Implementierung der C Library.

Ist doch bei GCC dabei, oder nicht? Ich nutz die eh nicht. :-)

> Warum soll jeder für sich
> wieder neu die Speicherverwaltung erfinden.

Embedded mit malloc arbeiten ist ohnehin in 95% der Fälle schlichtweg 
die falsche Entscheidung und kommt normalerweise von Leuten, die sonst 
nur auf dem PC programmieren.

> Und die Prozessorinitialisierung mit seinen vielen Taktmöglichkeiten ist
> ja nun nicht gerade einsteigerfreundlich.

Sie bietet halt eine Menge Flexibilität, und wenn Dein einziges Anliegen 
ist "wie kriege ich den Takt voll aufgedreht", dann nutzt Du davon nur 
einen Bruchteil aus, und deswegen erscheint es umständlich.

Thorsten E. schrieb:
> Der Compiler erzeugt den Code, der Linker
> fügt ihn nur zusammen. Aber man lernt halt nie aus. Achja, der Linker
> will auch wissen, dass er Thumb-Code erzeugen soll staun.

Der Compiler erzeugt den Objektcode. Aber ich könnte mir vorstellen, 
warum der Linker Thumb wissen sollte. Weil nämlich beim Aufruf einer 
Thumbfunktion im LSB der Zieladresse immer eine 1 steht, wenn die 
angesprungene Funktion Thumb ist. Das ist bei ARM einfach so. Wenn die 
physikalische Zieladresse im Flash z.B. "0x40" wäre, aber die 
Zielfunktion in Thumb ist, dann geht der Jump im Binärfile nach 0x41. 
Das geht schon beim Resetvektor so los, wo das, was in der Tabelle 
steht, nicht ganz mit dem übereinstimmt, was im Mapfile steht, genau 
deswegen.

Thorsten E. schrieb:
> Ich denke, in der Tat werde ich die StdPeriphLib auf Dauer nicht
> verwenden, da sie viel zu viel Unützes mitbringt und unübersichtlich
> ist.

Eben. Die kannste nehmen als Ergänzung zum reference manual, d.h. als 
Samplecode, aber nicht als Realcode.

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Thorsten E. schrieb:
> ich versuche nun seit Tagen ein STM32 Board zum Laufen zu bekommen.

Hast Du mal versucht, in main() nach Aufruf von

   SystemInit ();
auch
   SystemCoreClockUpdate ();

aufzurufen? Du zeigst die Funktion zwar oben, aber Du rufst sie 
nirgendwo auf.

von Gnugnu (Gast)


Lesenswert?

Nop schrieb:
>
>> Warum soll jeder für sich
>> wieder neu die Speicherverwaltung erfinden.
>
> Embedded mit malloc arbeiten ist ohnehin in 95% der Fälle schlichtweg
> die falsche Entscheidung und kommt normalerweise von Leuten, die sonst
> nur auf dem PC programmieren.
>

So eine Aussage kommt in 95% der Fälle von Leuten, die vor 10-15 Jahren 
aufgehört haben, sich mit dem technologischen Fortschritt 
auseinanderzusetzen...

1. Wer heutzutage auf dem PC programmiert (Klugscheissermodus an: Du 
wolltest wahrscheinlich schreiben "FÜR den PC programmiert," denn 99% 
der Embedded Entwicklung findet auch auf PC hosts statt, aber das nur am 
Rande), weiß noch nicht mal mehr, was malloc() ist. Dynamische 
Speicherverwaltung heisst new, den Rest macht der Garbage Collector. 
Welcome to 2001 Technology!

2. Vermutlich arbeitest Du noch in der Embedded Welt der AVR Generation, 
in denen 512K Flash mehr war als Jemals ein Mensch brauchen würde, und 
mehr als Messfühlerdatenpumpen, die Nullen und Einsen von A nach B 
geschoben haben, hattest Du nie programmiert?

- Jede halbwegs anspruchsvolle Middleware, die heutzutage eingesetzt 
wird (RTOS,Filesystem,DB System, Netzwerkstack, Kryptierbibliotheken 
etc) setzt zwingend dynamisch verwalteten Speicher voraus

- Gerade Ressourcenarme Prozessoren profitieren enorm davon, dass nicht 
immer das Maximum an benötigtem Speicher vorgehalten wird (triviales 
Beispiel: Kommunikationsprotokolle mit variablen Payloadgrößen).

- Standard C Lib Implementationen von malloc() und free() sind in jedem, 
wirklich jedem Ökosystem zu finden

Ich dachte immer, ICH sei schon ein Fossil, weil ich mich mit Händen und 
Füßen gegen die Javaphilosophie bei Embedded wehre, aber obwohl ich z.T. 
mit sehr sehr ressourcenbeschränkten boards arbeite, kann ich mir 
heutzutage keine noch so primitive Anwendung vorstellen, die OHNE 
dynamische Speicherverwaltung auskommt.

von Nop (Gast)


Lesenswert?

Gnugnu schrieb:

> So eine Aussage kommt in 95% der Fälle von Leuten, die vor 10-15 Jahren
> aufgehört haben, sich mit dem technologischen Fortschritt
> auseinanderzusetzen...

Oder die ernsthafte Projekte haben, die z.B. auch Zulassungs-Regularien 
unterliegen.

> 2. Vermutlich arbeitest Du noch in der Embedded Welt der AVR Generation,

Deine Kristallkugel spinnt.

> - Jede halbwegs anspruchsvolle Middleware, die heutzutage eingesetzt
> wird (RTOS,Filesystem,DB System, Netzwerkstack, Kryptierbibliotheken
> etc) setzt zwingend dynamisch verwalteten Speicher voraus

Auch falsch. Bzw. nur richtig, wenn man von embedded Linux spricht, und 
da hat man natürlich ein so großes System, daß es praktisch wie ein PC 
ist.

> - Standard C Lib Implementationen von malloc() und free() sind in jedem,
> wirklich jedem Ökosystem zu finden

Das ändert nichts an issues wie Speicherfragmentierung, besonders wenn 
die CPU keine MMU hat. Es ändert auch nichts daran, daß so ein System 
nicht mehr testbar ist, weil die Fragmentierung sehr davon abhängt, was 
wie wo in welcher Reihenfolge vorher gemacht wurde.

> aber obwohl ich z.T.
> mit sehr sehr ressourcenbeschränkten boards arbeite, kann ich mir
> heutzutage keine noch so primitive Anwendung vorstellen, die OHNE
> dynamische Speicherverwaltung auskommt.

Dann bin ich froh, wenn Du keine kritischen Controller und dergleichen 
entwickelst. Wahrscheinlich sagt Dir aus dem Grund auch sowas wie MISRA 
nichts, und das ist ja noch harmlos.

Mit Deinem Ansatz würde Deine Software in solchen Branchen nie zum 
Kunden gehen, weil sie bereits im Codereview durchfallen würde.

Aber für Hobby ist schon OK.

von W.S. (Gast)


Lesenswert?

Gnugnu schrieb:
> Ich dachte immer, ICH sei schon ein Fossil, weil ich mich mit Händen und
> Füßen gegen die Javaphilosophie bei Embedded wehre, aber obwohl ich z.T.
> mit sehr sehr ressourcenbeschränkten boards arbeite

Ach, auch du kein Fan von Jamaica-VM ?

Also mal ganz im Ernst: bei 99.9% aller kleineren µC-Projekte, wo 
Controller wie die kleineren STM32F10x mit typisch 64K Flash und 8..20K 
RAM eingesetzt werden, ist sowas wie du es genannt hast, einfach nicht 
anzutreffen - weil es keinen Sinn ergibt. Und bei etwas größerem Zeugs 
nehmen die Leute mittlerweile nen Modul, z.B. Colibri, Armstone und so. 
Entweder mit nem WinCE drauf oder mit nem Linux.

So. und nun warte ich mal, ob der Thorsten sich nochmal meldet.

W.S.

von Thorsten E. (bluescreen)


Lesenswert?

Ich dachte, ich halte mich mal vornehm zurück und was aus der für und 
wider dyn. Speicherverwaltungsdiskussion so wird.

Vielleicht erstmal zu meinem Umfeld: ich betreibe das Mikrocontrollern 
als Hobby. Insofern werden keine Flugzeuge vom Himmel fallen oder 
Atomkraftwerke explodieren wenn mein Controller mal stehen bleibt. 
Derzeit habe ich zwei STM32 Projekte in Planung:

Einen LED Würfel (gähn, nicht schon wieder). Der Würfel liegt hier schon 
fünf Jahre ohne Controller rum. Übrigens mit einem Haufen TI's TLC5940 
als PWM Controller. Als µC ist auf der Platine ein LPC2148 vorgesehen, 
aber noch nicht bestückt. Es gibt aber auch eine Pfostenleiste an der 
man einen externen Controller anschliessen könnte. Übrigens liegt noch 
eine Leerplatine rum, falls jemand Lust hat.

Eine Heizungs"uhr", die vielleicht später eine richtige 
Heizungssteuerung wird. Dazu wollte ich das STM32 Board nehmen mit dem 
ich gerade übe. Ist Teil vom Chinesen mit 2,4" TFT dran.

Ich nutze malloc und Co selten, aber für arbeiten mit z.B. SD-Karten 
oder TCPIP Stacks ist es schon nützlich wenn man die riesigen Buffer die 
so ein Filesystemtreiber meist braucht auch mal freigeben kann wenn man 
ihn gerade nicht braucht. Oder wenn man Daten sammeln will deren Menge 
und Struktur nicht exakt vorhersehbar sind. Deswegen nehm ich ja gerade 
die größeren ARMs. Sonst könnte ich auch beim AVR bleiben.

Ausserdem funktionieren diverse Ausgabefunktionen nicht ohne _sbrk. 
Jetzt wird der eine oder andere sagen printf sei eh böse, weil riesig. 
Aber erstens hab ich gerade gelernt, dass es ein deutlich kleineres 
iprintf gibt und zweitens kratzt mich das bei 512kB Flash kaum. Und 
falls mal C++ kommen sollte, ist da dann vermutlich eh Schluss ohne dyn. 
Speicherverwaltung.

Inzwischen habe ich eine funktionsfähige Minimalbibliothek. Ich musste 
allerdings die Stackprüfung rauslassen, weil ich noch nicht weiß wie man 
den SP in C lesen kann. Ich wollte eine einfach Prüfung reinbauen:
1
char *stack = __get_MSP();
2
if (heap_end + incr > stack - securedistance)

Leider gibt es __get_MSP nicht.

Das ist also die derzeit verbleibende Frage. Ansonsten werde ich mir 
erstmal eine kleine "ARMlibc" bauen um das ST Monster loszuwerden.

Vielen Dank auf jeden Fall für die Hinweise und Tipps. Insbesondere auch 
zum Debugger, der jetzt halbwegs funktioniert.

von holger (Gast)


Lesenswert?

>Leider gibt es __get_MSP nicht.

Steht in core_cmFunc.h.

von W.S. (Gast)


Angehängte Dateien:

Lesenswert?

Thorsten E. schrieb:
> Ansonsten werde ich mir
> erstmal eine kleine "ARMlibc" bauen um das ST Monster loszuwerden.

Ja, tu das. Du wirst es irgendwann mal schätzen, ein eigenes Portfolio 
an Lowlevel-Treibern und kleinen Problemlösern zu haben.

Ich hänge dir hier mal ne kleine Anregung dran, da du ja mit einem 
"STM32F103VET" den Thread angefangen hast.

Das Ganze ist eigentlich nur ne Fingerübung meinerseits gewesen. Es 
basiert auf einem STM32F103C8T6, den die chinesischen Ebay-Händler wohl 
sehr zu lieben scheinen. Es gibt ihn solo oder im Fünferpack oder als 
ST-Link2 mit Gehäuse oder als Minimalst-Leiterplatte.

Ich habe die Prinzipschaltung des ST-Link2, die von ST veröffentlicht 
wurde, mal in eine kleine eigene LP umgesetzt. Da hat mich so einiges 
sehr geärgert, vor allem daß offenbar mehrere Pins einfach 
parallelgeschaltet sind. Macht man eigentlich nicht, wenigstens ein 
Entkoppel-R sollte dazwischen sein. Aber Augen zu und durch. Die ST-Link 
Firmware (V2.16.4 - STM32 + STM8 Debug), die es im Netz gibt, läuft 
drauf, die Entwurfsfehler mit den parallelgeschalteten Pins scheinen 
also OK zu sein - aber das war ja nicht mein Interesse.

Was also kann die gepostete Eigenkreation? Sie hat nen Startup in 
Assembler, eine Systemkonfiguration nach eigener Machart, einen USB-VCP 
über den man mit dem Teil kommunizieren kann, dito über USART1 und 2, 
eine Kommandoauswertung, eine Systemuhr, Ein- und 
Ausgabe-Konvertierungen, ein Event-System und serielle Treiber. stolpere 
nicht über gio.c, das dient zur Vereinheitlichung. Damit kann man alle 
installierten seriellen Ports einheitlich ansprechen (incl. USB) und man 
kann auch in einen String drucken, falls man sowas je brauche sollte. Du 
brauchst mit diesem Zeugs vermutlich nie wieder sprintf.

Und das ganze braucht nur 11.5 K im Flash - allerdings mit dem Keil 
übersetzt. Nach meiner Erfahrung braucht es beim Gcc etwa 20% mehr.

Natürlich habe ich das Ganze NICHT speziell für den kleinen 
STM32F103C8T6 geschrieben. Fast alle Teile bis auf config.c stammen aus 
anderen Projekten, der USB von einem NUC100-Projekt (Nuvoton), die 
seriellen Treiber aus ganz früheren Zeiten (frühe ARM7TDMI von Atmel), 
conv.c aus Motorola-Zeugs (ist steinalt) und so weiter. Das Neueste sind 
eigentlich die Events, die stammen aus der Lernbetty. (womit das viel 
weiter oben genannte Stichwort gefallen wäre...)

Wenn man erstmal eine Sache mit bedachtsam konzipierten Treibern 
erledigt hat, dann bleibt einem das für alle nachfolgenden Projekte 
erhalten. Lediglich in einigen Details der hardwareabhängigen Teile muß 
man dann noch ne Anpassung an den konkreten µC machen. Die Headerfiles 
bleiben dabei aber unverändert. So hat man für alle draufgesetzten 
Programmteile eine wirklich hardwareunabhängige Schnittstelle.

Also guck dir es an und verwende es nach deinem Gusto. Den USB-VCP hab 
ich auch für einige LPC-Typen (2478, 4088, u.a.). Man kann ihn in der 
vorliegenden Form auch für die STM32F30x benutzen.

Hau rein! Und viel Glück.

W.S.

von Thorsten E. (bluescreen)


Lesenswert?

Ja, im Header stehts, aber Linker meckert - weil ich vergessen habe 
core_cm3.c zum Projekt hunzuzufügen. Leider compiliert er das aber 
nicht:
1
||=== Build: default in Blink_STM32F1 (compiler: GNU GCC Compiler for ARM) ===|
2
C:\Users\Thorsten\AppData\Local\Temp\ccTtZLgW.s|528|Error: registers may not be the same -- `strexb r0,r0,[r1]'|
3
C:\Users\Thorsten\AppData\Local\Temp\ccTtZLgW.s|554|Error: registers may not be the same -- `strexh r0,r0,[r1]'|
4
||=== Build failed: 2 error(s), 0 warning(s) (0 minute(s), 0 second(s)) ===|

Geschickterweise sind die tmp Dateien die er anmeckert nach dem 
Compilieren schon weg.

von Nop (Gast)


Lesenswert?

Thorsten E. schrieb:
> char *stack = __get_MSP();
> if (heap_end + incr > stack - securedistance)

Autsch. Das ganze Speicherlayout, was dem zugrundeliegt, ist doch schon 
unglücklich. Hier liegt offensichtlich der Stack oben und wächst als 
descending stack nach unten auf den Heap zu. Ein Desaster, was nur 
darauf wartet zu passieren:

http://embeddedgurus.com/state-space/2014/02/are-we-shooting-ourselves-in-the-foot-with-stack-overflow/

Dieser Check ist sinnlos, weil er zwar kontrolliert, daß der Heap nicht 
zu arg nach oben wächst - aber das gilt ja nur in genau dem Moment der 
Allokation. Wenn das erstmal alloziert ist, hindert nichts den Stack 
daran, weiter nach unten zu wachsen.

Sinnvoller ist es, das Speicherlayout so zu wählen, daß man den Stack 
nach unten an den Anfang des RAM legt. Dann wächst er auf 0 zu, und bei 
einem Stack Overflow rauscht man in einen hard fault. Das ist immerhin 
ein klarer Absturz - viel schlimmer ist es, wenn es wie mit dem 
aktuellen Layout nicht abstürzt, sondern sich nur undefiniert verhält. 
Extrem schwer zu debuggen, sowas.

Umgedreht kann dann der Heap oberhalb der globalen Variablen verbleiben, 
und seine Endbegrenzug ist dann einfach das Ende des RAM-Bereiches.

Wie dimensioniert man den Stack nun? Hoffen und beten? Die Pfuschmethode 
des Stack-Spraying (alles mit einem Muster initialisieren und gucken, 
bis wohin es mal eingerissen ist)?

Nein, bei GCC setzt man beim Compiler-Aufruf die Option -fstack-usage. 
Dann sieht man den Stackbedarf der einzelnen Funktion. Pro Call-Level 
muß man noch 8 Bytes hinzurechnen. Dann verfolgt man seinen Call-Tree 
der Applikation und schaut, welchen Bedarf man im Worst Case hat.

Hierzu addiert man dann noch den Stackbedarf aller Interruptfunktionen 
plus natürlich dem Sichern der Register (bei Exceptions werden 8 
Register gesichert). Also man nimmt an, daß sie allesamt zugleich genau 
dann anfallen, wenn man auch in der Applikation den schlimmsten Punkt 
erreicht hat. Dann schlägt man noch 10-20% drauf, damit man die ganze 
Rechnung nicht bei jeder Kleinänderung nochmal machen muß.

Und schon braucht man den Ansatz mit dem Stackregister gar nicht mehr.

von Nop (Gast)


Lesenswert?

Ach ja, und nochwas.. GCC hat mitunter eine merkwürdige Inline-Logik, da 
sollte man embedded sicherheitshalber mehr Kontrolle ausüben.

Wenn man zwei Funktionen hat, die beide beachtlich Stack verbrauchen, 
aber nie zugleich aufgerufen werden, dann sollte man sie mit dem 
Attribut noinline versehen. Ansonsten kann GCC auf die Idee kommen, sie 
zu inlinen, und ihr Stackbedarf würde sich dann bei einem ungünstigen 
Calltree trotzdem addiern.

Faustregel: Wenn es viel Stack verbraucht, dann unbedingt prüfen, wie 
der Stackverbrauchspfad im Falle von Inlining aussähe. Im Zweifel dann 
ein noinline setzen.

Nochmal zum Thema Speicherfragmentierung: Wenn man seine 
Allokationsmengen immer zur nächsten Zweierpotenz aufrundet, dann hat 
man keine Fragmentierung und verschwendet nie mehr als 50%. Alternativ 
kann man auch immer dieselbe Blockgröße allozieren, dann fragmentiert 
auch nichts.

von Nop (Gast)


Lesenswert?

Thorsten E. schrieb:
> Insofern werden keine Flugzeuge vom Himmel fallen oder
> Atomkraftwerke explodieren wenn mein Controller mal stehen bleibt.

Das ist auch nicht so der Punkt. Selbst wenn ich mal Bastelprojekte 
mache, gucke ich mir doch an, was in kritischen Systemen gemacht wird 
und welche Ideen ich übernehmen kann. Weil ich möchte, daß auch meine 
Bastelsachen zuverlässig laufen - ich find's halt befriedigend.

Denn die verbieten malloc ja nicht, weil es vom Teufel gemacht wurde, 
der hat bekanntlich stattdessen ohnehin ein struct mit drei 
void-Pointern.

von Thorsten E. (bluescreen)


Lesenswert?

Vor ca. 20 Jahren habe ich mal eine LED Matrix mittels Ethernut 
programmiert. Mit ganz vielen Mallocs und dem furchtbaren 
Speicherlayout. Und was soll ich sagen. Die Uptime steht derzeit auf 
11,2 Jahre. Der eine Absturz war ein Stromausfall.

Das ist mir zuverläsig genug.

Die spannende Frage, warum läßt sich die core_cm.c nicht compilieren und 
wie finde ich wo der Fehler ist, wenn der Compiler von irgendwelchen 
Temporärdateien redet, die man nicht anschauen kann, weil sie weg sind.

von Nop (Gast)


Lesenswert?

Thorsten E. schrieb:
> Das ist mir zuverläsig genug.

Eine Bestätigung, daß Erfahrung nicht alles ist - das kann auch nur 
heißen, daß man lange Zeit die immer selben Fehler macht.

Anyway, für die Fehlermeldung, das klingt mir doch sehr nach dem hier:

https://github.com/texane/stlink/issues/65

https://gist.github.com/timbrom/1942280

von Thorsten E. (bluescreen)


Lesenswert?

Das hat geholfen, danke.

von Thorsten E. (bluescreen)


Lesenswert?

Hi W.S.,

irgendwie hatte ich dein Mail mit dem ZIP File überlesen. 
Hochinteressante Bibliothek. Werd ich mir die Tage mal zu Gemüte führen. 
Jetzt bastele ich aber gerade erstmal eine kleine Testhardware für 
meinen LED Würfel (damit ich nicht immer den klobigen Würfel 
umherschleppen muss wegen doppelter Haushaltsführung)

Was mir auf die Schnelle an der Bibliothek aufgefallen ist, dass die 
Interruptroutinen mit __isr getaggt sind. Das scheint beim gcc nicht 
nötig zu sein. Was steckt denn da dahinter?
Mich hat es schon etwas gewundert, dass in einigen gcc Beispielen die 
ISRs ganz normale Funktionen sind. Eigentlich muss man doch bestimmt 
einige wichtige Register sichern.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Thorsten E. schrieb:
> Hi W.S.,
>
>
> Was mir auf die Schnelle an der Bibliothek aufgefallen ist, dass die
> Interruptroutinen mit __isr getaggt sind. Das scheint beim gcc nicht
> nötig zu sein. Was steckt denn da dahinter?
> Mich hat es schon etwas gewundert, dass in einigen gcc Beispielen die
> ISRs ganz normale Funktionen sind. Eigentlich muss man doch bestimmt
> einige wichtige Register sichern.

Das hat mit dem gcc nichts zu tun, sondern mit dem target. Manche 
Prozessoren benötigen speziellen Code für die ISRs, manche nicht (der 
ARM Cortex hat z.B. keinen von "normalen" Funktionsaufrufen 
unterscheidbaren stack frame, zumindestens was die Sicht der 
aufgerufenen Funktion betrifft, aber manche andere Prozessoren behandeln 
Exceptions anders (dort gibt es z.B. eine RTE statt einer RTS Andweisung 
zur Rückkehr)). Vermutlich übersetzt das __isr je nach konfiguriertem 
target anders.

M.M. nach ist es aber kein Fehler, eine Funktion als ISR Funktion 
zumindestens kenntlich zu machen, damit man beim Lesen des Codes weiss, 
dass ein anderer Prozessorkontext aktiv ist als bei einer "normalen" 
Funktion. Deswegen schreibe ich auch in jedem Funktionheader den 
Kommentar "wird im Kontext <der task xyz>|<des abc isr>" aufgerufen

: Bearbeitet durch User
von Thorsten E. (bluescreen)


Lesenswert?

Ja, das mit den unterschiedlichen Return Befehlen kenne ich ja auch vom 
AVR (und vom Z80). Oder es gibt gar unterschiedliche Stacks (Usermode, 
Systemmode oder so).

Aber selbst wenn es von daher nicht nötig wäre, müssen doch eigentlich 
innerhalb jeder ISR mindestens die vom Compiler genutzen Register 
gesichert werden, da man ja nicht vorhersehen kann, wann der Interrupt 
zuschlägt.

Deswegen müsste der Compiler (oder man selbst) ja in der ISR eine 
Registersicherung durchführen. Also muss er wissen, das die betreffende 
Routine eine ISR sein soll. Wie sagt man das dem gcc.
Oder macht der ARM sowas womöglich in Hardware?

Anscheinend geht das im gcc so:
1
__attribute__((interrupt("IRQ")))

Komischerweise finde ich nur wenig Beispiele, die das tatsächlich so 
machen. Ist es nun nötig oder nicht?

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Das hängt vom ABI ab. Beim Cortex M ist es so, dass R0-R4 lt. AAPCS 
destruktiv benutzt werden dürfen, d.h. es ist nicht garantiert, dass der 
Wert einer dieser Register bei Austritt aus der Funktion gleich dem bei 
Eintritt ist (das ABI benutzt R0 für den 1. Parameter und die Ausgabe 
und R1-R4 für weitere Parameter).

Der Cortex Kern sichert folgerichtig beim ISR Eintritt R0-R4 sowie 
weitere Register auf dem Stack (welcher geht hier zu weit), was aber für 
den Compiler komplett unsichtbar ist (glaubt der Compiler z.B. einen 
Rückgabewert ermitteln zu müssen, wird der in R0 geschrieben, aber da 
nach ISR Rückkehr R0 wieder restautiert wird, ist der "neue" Wert beim 
Aufruf als ISR weg). Andere Register, die benützt werden, müssen 
explizit gesichert und am Ende wieder restauriert werden, aber wenn der 
Compiler AAPCS kompatiblen Code generiert, passiert das sowieso.

Also je nachdem, welche Annahmen das Toolset über die Registernutzung 
trifft, kann (und hoffentlich wird) so ein Schlüsselwort _interrupt() 
oder wie immer es heisst Alles tun, um den Teil der vollständigen 
Prozessorstatusrestaurierung, der nicht vom ABI abgedeckt ist, zu 
ergänzen.

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


Lesenswert?

Thorsten E. schrieb:
> Was mir auf die Schnelle an der Bibliothek aufgefallen ist, dass die
> Interruptroutinen mit __isr getaggt sind. Das scheint beim gcc nicht
> nötig zu sein. Was steckt denn da dahinter?

Interrupt-Service-Handler müssen beim ARM als solche gekennzeichnet 
sein, sonst geht die Sache schief. Das hängt mit den Aufruf-Konventionen 
und mit dem Sichern und Restaurieren von Registern und mit dem 
Rücksprung zusammen, also mit Funktionskopf und -ende.

Beim Gcc gibt's das ganz genauso, es wird nur völlig anders formuliert - 
die konkrete Bezeichnung dafür hab ich vergessen, war ziemlich länglich. 
Ich hatte mich zuletzt bei der Lernbetty mit dem Gcc befaßt, ist also 
schon ein paar Jahre her - normalerweise benutze ich den Keil und dort 
wird das wie oben mit __isr gemacht. Ich hatte aber bei der Lernbetty in 
irgend eine Headerdatei ein #define dafür hineingesetzt, um bei den 
C-Quellen möglichst Codegleichheit Keil<-->Gcc zu ermöglichen.

W.S.

von W.S. (Gast)


Lesenswert?

Thorsten E. schrieb:
> Anscheinend geht das im gcc so:
> __attribute__((interrupt("IRQ")))
> Komischerweise finde ich nur wenig Beispiele, die das tatsächlich so
> machen. Ist es nun nötig oder nicht?

Ja, jetzt hab ich's auch gelesen.
Also das war's.

Zumindest beim ARM7TDMI war das zwingend nötig, denn der hatte ja noch 
eine ganze Reihe unterschiedlicher Stacks. User, Szupervisor, FastInt 
und so weiter.

Ich würde das auch beim Cortex vor jede ISR schreiben, mehr als daß der 
Gcc meint, daß dies nicht nötig sei, kann dabei ja wohl nicht passieren.

W.S.

von rmu (Gast)


Lesenswert?

Anscheinend muss der Stackpointer ein vielfaches von 8 sein wenn man 
unterfunktionen nach AAPCS aufruft. Das muss nach einem Interrupt nicht 
automatisch der Fall sein, und _attribute_... erzeugt Code, der den 
Stackpointer richtet.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

rmu schrieb:
> Anscheinend muss der Stackpointer ein vielfaches von 8 sein wenn man
> unterfunktionen nach AAPCS aufruft.

Da ist AAPCS leider etwas unklar; in 5.2.1.1 ist als "Universal stack 
constraint" ein word alignment (also 4) aufgeführt, in 5.2.1.2 in Rahmen 
eines "public interfaces" (das leider nicht definiert ist) ein double 
word alignemnt (also 8). 4 muss also IMMER erfüllt sein, 8 nur in 
Spezialfällen.

> Das muss nach einem Interrupt nicht
> automatisch der Fall sein, und _attribute_... erzeugt Code, der den
> Stackpointer richtet.

? Wiese muss das NACH einem Interrupt nicht automatisch der Fall sein? 
Ein Interrupt muss den Prozessor im selben Zustand hinterlassen, bei dem 
der Interrupt aufgerufen wurde, d.h. nach dem Interrupt muss der SP 
identisch zu vorher sein, deswegen macht deine Aussage keinen Sinn - 
meintest Du "alignment bei Auftreten des Interrupts?"

In jedem Fall arbeite ich schon seit vielen Jahren mit Cortex M 
Prozessoren, und eine explizite Deklaration eines ISR durch ein 
Attribute ist sehr selten. Ein guter Indikator ist FreeRTOS, wo der 
SysTick Handler in den meisten ports einfach nur als void 
SysTickHandler(void) deklariert ist; da Richard Barry die meisten ports 
selber gesehen und abgesegnet hat und er sehr genau weiss was er tut UND 
FreeRTOS auf Cortex Systemen weltweit extrem häufig und fehlerfrei 
läuft, wäre ein Problem hier früher oder später aufgetaucht... 
allerdings sind in den Standard Cortex ports der Pending Service Handler 
und der Portservicehandler mit _attribute_ ((naked)) getaggt. Werde 
ich mir bei Gelegenheit mal ansehen, was dahinter steckt - das ist aber 
von der Diskussion hier unabhängig...

: Bearbeitet durch User
von rmu (Gast)


Lesenswert?

Ruediger A. schrieb:
> ? Wiese muss das NACH einem Interrupt nicht automatisch der Fall sein?
> Ein Interrupt muss den Prozessor im selben Zustand hinterlassen, bei dem
> der Interrupt aufgerufen wurde, d.h. nach dem Interrupt muss der SP
> identisch zu vorher sein, deswegen macht deine Aussage keinen Sinn -
> meintest Du "bei Auftreten des Interrupts?"

Hab mich vielleicht falsch ausgedrückt...

Interrupt unterbricht Prozessor, der sichert (ein paar) Register (inkl. 
aktuellen PC), und springt in die ISR. Dort angelangt ist der SP u.U. 
zwar ein vielfaches von 4, aber nicht von 8. Der ISR Eintrittscode, den 
der Compiler mit _attribute_ erzeugt sollte dann feststellen, ob er 
den SP um 4 kleiner machen muss.

Nachdem die ISR abgearbeitet ist wird dann im Falle des Falles der SP 
wieder erhöht und die ISR beendet, der Proz erledigt den Rest.

Ich hab in meinen ISRs eigentlich auch kaum noch wo so ein attribute 
angegeben, funktionieren trotzdem, allerdings rufe ich auch nicht 
unbedingt andere Funktionen in meinen ISRs auf...

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

ok, nach ein bisschen gockeln löst sich das Mysterium:

http://infocenter.arm.com/help/topic/com.arm.doc.ihi0046b/IHI0046B_ABI_Advisory_1.pdf

Hier ist in Abschnitt 2.3.3 aufgeführt, dass Cortex M3 > Rev0 den Stack 
NICHT explizit auf 8 alignen müssen (da das dort der Kernel macht), 
andere Cortex aber schon.

von Nop (Gast)


Lesenswert?

Ruediger A. schrieb:

> 4 muss also IMMER erfüllt sein, 8 nur in Spezialfällen.

Ich meine mich zu entsinnen, daß man letzteres für den Datentyp double 
braucht.

> allerdings sind in den Standard Cortex ports der Pending Service Handler
> und der Portservicehandler mit attribute ((naked)) getaggt.

Das bedeutet, daß die Register beim Aufrufen dieser Funktion nicht 
gesichert werden, sondern darum muß die Funktion sich selber kümmern.

Das kann man beispielsweise gebrauchen, wenn man einen stack overflow 
handler schreiben will, wo also der hard fault kommt, weil der SP in den 
Wald außerhalb des RAM zeigt. Würde man da die Register sichern, dann 
würde wieder in den Wald geschrieben.

Also muß man vor dem exception handling erstmal den SP prüfen und gff. 
wieder auf einen gültigen Wert setzen, bevor man mit einer C-Funktion 
(die aus dem exception handler aufgerufen wird) irgendwelche Sachen wie 
die Anzeige eines Bluescreens machen kann.

von Ruediger A. (Firma: keine) (rac)


Lesenswert?

Nop schrieb:
> Ruediger A. schrieb:
>
>> 4 muss also IMMER erfüllt sein, 8 nur in Spezialfällen.
>
> Ich meine mich zu entsinnen, daß man letzteres für den Datentyp double
> braucht.
>
>> allerdings sind in den Standard Cortex ports der Pending Service Handler
>> und der Portservicehandler mit attribute ((naked)) getaggt.
>
> Das bedeutet, daß die Register beim Aufrufen dieser Funktion nicht
> gesichert werden, sondern darum muß die Funktion sich selber kümmern.
>

ok, danke für die Auffrischung (wußte ich auch irgendwann mal). Macht 
auch komplett Sinn, da die beiden Handler den Context Switch 
implementieren, also momentanen Taskzustand einfrieren, einen anderen 
Taskzustand restaurieren und mit fake bx in einen völlig anderen Zustand 
springen als bei Eintritt... da wäre eine Stackmanipulation durch vom 
Compiler generierten Code natürlich tödlich.

Thx

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.