Forum: Mikrocontroller und Digitale Elektronik Cortex M0-hardfault bei Zugriff auf Variable


von Thomas K. (thomaskroger)


Lesenswert?

Im Atmel Studio mit einem SAM21 Cortex M0 läuft das Programm in einen
hardfault wenn ein Zugriff auf eine Variable stattfindet.

Die Deklaration von xy_lcd_cursorPosition ist problemlos:
1
bla.c
2
#include "bla.h" 
3
4
//uint8_t  xy_lcd_cursorPosition __attribute__((aligned(4)))=0;
5
//uint32_t  xy_lcd_cursorPosition =0;
6
//uint8_t  xy_lcd_cursorPosition;
7
uint8_t  xy_lcd_cursorPosition=0;
8
9
static const __packed uint8_t ssd1306_font6x8 [] = 
10
{   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sp
11
    0x00, 0x00, 0x00, 0x2f, 0x00, 0x00, // !
12
...bla

Sobald aber ein Schreib(!)zugriff statt findet, läuft das Programm 
sofort beim Start in einen hardfault. Ohne Zugriff läuft das Programm:
1
status_code xy_lcd_init()
2
{   xy_lcd_cursorPosition=0;  //->HardFault
3
    //xy_lcd_cursorPosition=0;  //läuft

Ein Breakpoint bei
1
//startup_sam21.c
2
__libc_init_array();

geht. Danach im Disassembler weiter ist mühsam und bringt keine 
Erkenntnisse.
1
  __asm("BKPT #1\n") ; // Break into the debugger

R0 = 0x20000118
R1 = 0xfffffff9
R2 = 0x118
R3 = 0x9E9
R12 = 1
IPSRm = 3
APSR = 0x40000000
PSR = 0x40000003
AIRCR =0xfa050000
SCR = 0
CCR = 0x00000208

Übrigens, ein Breakpoint bei
1
status_code xy_lcd_init()
2
{   
3
-> xy_lcd_cursorPosition=0;  //->HardFault
wird nicht angelaufen.


Hat jemand eine Idee?
Besten Dank.

von Nop (Gast)


Lesenswert?

Mach mal Singlestep, und zwar im im Disassembly.

von Adam P. (adamap)


Lesenswert?

Kann es evtl. sein, dass da vom Aufbau etwas nicht passt...
weil wenn der Breakpoint nicht angesprungen wird, dann stimmt da was 
nicht.

Hast vllt. die ganze Datei? Das man den Aufbau erkennt.

Schau mal in die Aufrufliste im Debug-Mode von wo er in den Hard-Fault 
springt.

von Sebi (Gast)


Lesenswert?

Kannst du dir die Adresse deiner Variable anschauen und überprüfen ob 
die Adresse zum RAM deines Mikrocontrollers gehört? Vielleicht geht 
irgendwas mit deinem Linkerscript durcheinander. Funktionieren denn 
lokale Variablen oder die auch nicht?

von Schrumpfkopf (Gast)


Lesenswert?


von Dr. Sommer (Gast)


Lesenswert?

Thomas K. schrieb:
> __packed

Sehr verdächtig. Unaligned-Zugriffe gehen auf dem Cortex-M0 nicht 
(->Absturz). Benutzt du so etwas noch bei anderen Typen? Kompilierst du 
mit -munaligned-access?

Wie sehen die Register beim Hard Fault aus? Insbesondere das LR? Was 
steht im CFSR? Wie sieht der Assembler-Code an der Stelle, die im LR 
steht, aus?

Ansonsten Assembler Step-By-Step.

von Thomas K. (thomaskroger)


Lesenswert?

Vielen Dank für die vielen Anregungen!

__packed
ist natürlich obsolet.


Im Watch war der erste Hinweis:
1
xy_lcd_cursorPosition  0x10  uint8_t(static storage at address 0x0)

address bei 0x0 ist Unsinn und Inhalt 0x10 ist verdächtig.

Das zentrale Problem war offenbar ein Durcheinander in der Make-Datei 
bzw. Müll im SolutionExplorer. Ich arbeite mit Links auf Dateien da die 
Sources für mehrerer Projekte verfügbar sein müssen. Da hatte sich tief 
versteckt ein Link auf eine alte *.c im Backup-Ordner eingeschlichen und 
die *.d und *.o-Files wollten sich auch nicht finden lassen.

Nach dem gründlichen Aufräumen des SolutionExplorer und Neustart(!) 
gehts:
1
xy_lcd_cursorPosition  0x00  uint8_t(static storage at address 0x20001048)

Es war also so, dass unbemerkt eine alte *.c, die nicht in das Projekt 
gehört, mit dem gleichen Namen wie die aktuelle *.cpp den Compiler an 
die Wand gespielt hat.

von Jim M. (turboj)


Lesenswert?

Thomas K. schrieb:
> Ich arbeite mit Links auf Dateien da die
> Sources für mehrerer Projekte verfügbar sein müssen.

Damit zerschießt man sich make recht zuverlässig, da sich bei Links das 
Datum nicht ändert, wenn man das Original angefasst hat.

Du solltest also ein "make clean" in dein default Build Skript einbauen, 
denn so werden nur komplette rebuilds tun.

von Dr. Sommer (Gast)


Lesenswert?

Thomas K. schrieb:
> Ich arbeite mit Links auf Dateien da die
> Sources für mehrerer Projekte verfügbar sein müssen.

Oje, sowas macht man lieber mit Libraries...

von Thomas K. (thomaskroger)


Lesenswert?

Danke für die Tipps.

Auffällig ist, dass in der Funktion

syscalls.c
1
extern caddr_t _sbrk(int incr)
2
{  static unsigned char *heap = NULL;
3
  unsigned char *prev_heap;
4
5
  if (heap == NULL) {
6
    heap = (unsigned char *)&_end;
7
  }
8
9
  prev_heap = heap;
10
11
  heap += incr;
12
13
  return (caddr_t) prev_heap;
14
}

incr=0x9f8 und  heap=0x20007898

Dann ist nach
1
  heap += incr;
heap=0x20008290 und damit außerhalb des SRAM-Bereiches.

von Dr. Sommer (Gast)


Lesenswert?

Thomas K. schrieb:
> heap=0x20008290 und damit außerhalb des SRAM-Bereiches.

Dann fehlt in der _sbrk-Funktion dringend eine Bereichsprüfung. Und 
natürlich benutzt dein Programm zu viel malloc(). Das "extern" ist hier 
übrigens überflüssig. Das hat überhaupt nur eine Wirkung an globalen 
Variablen und keine an Funktionen.

von Vincent H. (vinci)


Lesenswert?

Bei der _sbrk Implementierung brennts auf einem M0 sowieso. Wenn "incr" 
kein vielfaches von 4x ist dann verliert man das notwendige Alignment.

von Dr. Sommer (Gast)


Lesenswert?

Vincent H. schrieb:
> Bei der _sbrk Implementierung brennts auf einem M0 sowieso. Wenn "incr"
> kein vielfaches von 4x ist dann verliert man das notwendige Alignment.

Sollte nicht malloc dafür sorgen? Im Zweifelfall so aufrunden:
1
incr = (incr + 3) & 0xFFFFFFFC;

von Vincent H. (vinci)


Lesenswert?

Dr. Sommer schrieb:
> Vincent H. schrieb:
>> Bei der _sbrk Implementierung brennts auf einem M0 sowieso. Wenn "incr"
>> kein vielfaches von 4x ist dann verliert man das notwendige Alignment.
>
> Sollte nicht malloc dafür sorgen? Im Zweifelfall so aufrunden:
>
1
incr = (incr + 3) & 0xFFFFFFFC;

Hm, da bin ich mir nicht sicher. GNU MCU Ecplise etwa liefert in seiner 
newlib Variante genau das was du grad vorgeschlagen hast (inkl. Limit 
Prüfung):
1
caddr_t
2
_sbrk(int incr)
3
{
4
  extern char _Heap_Begin; // Defined by the linker.
5
  extern char _Heap_Limit; // Defined by the linker.
6
7
  static char* current_heap_end;
8
  char* current_block_address;
9
10
  if (current_heap_end == 0)
11
    {
12
      current_heap_end = &_Heap_Begin;
13
    }
14
15
  current_block_address = current_heap_end;
16
17
  // Need to align heap to word boundary, else will get
18
  // hard faults on Cortex-M0. So we assume that heap starts on
19
  // word boundary, hence make sure we always add a multiple of
20
  // 4 to it.
21
  incr = (incr + 3) & (~3); // align value to 4
22
  if (current_heap_end + incr > &_Heap_Limit)
23
    {
24
      // Some of the libstdc++-v3 tests rely upon detecting
25
      // out of memory errors, so do not abort here.
26
#if 0
27
      extern void abort (void);
28
29
      _write (1, "_sbrk: Heap and stack collision\n", 32);
30
31
      abort ();
32
#else
33
      // Heap has overflowed
34
      errno = ENOMEM;
35
      return (caddr_t) - 1;
36
#endif
37
    }
38
39
  current_heap_end += incr;
40
41
  return (caddr_t) current_block_address;
42
}

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.