Forum: Mikrocontroller und Digitale Elektronik STM32F303 - CPU Reset ohne Hardware-Reset


von Thomas E. (picalic)


Lesenswert?

Servus zusammen,

vielleich kann mir jemand einen Tipp geben:
Beim STM32F303 möchte vom Applikationsprogramm auf Anforderung von außen 
den Bootloader starten und natürlich später auch vom Bootloader aus 
wieder in die App starten. Soweit ist das kein Problem - bislang wird 
das wie üblich durch Auslösen eines System-Resets bewerkstelligt. Nun 
hat diese Methode einen Nebeneffekt, den ich gerne vermeiden würde: Port 
B4 wird hier als Ausgang verwendet, wird aber beim Reset per Hardware 
erstmal als JTAG Reset-Eingang mit internem Pull-Up Widerstand 
konfiguriert. Die angeschlossene Schaltung sieht deshalb in dem Moment 
einen kurzen (> 2ms), unerwünschten High-Impuls.
Um das zu vermeiden, wollte ich nun keinen Hardware-Reset benutzen, 
sondern einen reinen Software-Restart ausführen, d.h. einen Sprung zur 
Adresse des Reset-Vektors, nachdem alle Interrupts deaktiviert wurden 
und der Stackpointer mit dem korrekten Wert geladen wurde. Auch das wäre 
eigentlich kein Problem, aber der Start des Bootloaders wird aus einem 
Interrupt-Handler der Applikation ausgeführt. Der Bootloader-Code wird 
dadurch im "Handler"-Mode, statt im "Tread"-Mode gestartet. Wie bekomme 
ich die CPU am Besten in den "Thread"-Mode umgeschaltet? Ich bräuchte 
also im Prinzip einen "CPU Reset", ohne die Hardware zurückzusetzen. 
(Programmiere in C, Keil µVision)
Danke!

von Nop (Gast)


Lesenswert?

Eine Möglichkeit wäre, im Interrupt nur ein Flag aufzusetzen für die 
Applikation und den Sprung aus der Applikation zu machen.

Eine andere wäre, sich das Stack-Layout anzusehen und (in Assembler) 
kurzerhand die Return-Adresse umzubiegen, so daß er beim Return statt an 
die Stelle des Aufrufs eben in den Bootloader springt. Dann sollte man 
aber auch das LR mit passendem EXC_RETURN zurechtpfriemeln, weil der 
Plot sonst nicht mehr klappt, falls der Interrupt während eines anderen 
Interrupts aufgerufen wird. Das Programming Manual kann hier die Details 
liefern.

von Christopher J. (christopher_j23)


Lesenswert?

Ich hätte - ohne Anspruch auf Vollständigkeit - zwei Möglichkeiten 
anzubieten:

1. Du setzt in deinem Interrupt-Handler ein Flag und behandelst dieses 
dann in deiner Main-Loop, wenn der Controller wieder im Thread-Modus 
ist. Das ist meiner Meinung nach die gängigste Variante, weil ich auch 
davon ausgehe, dass der Reset nicht sonderlich zeitkritisch ist (auch 
wenn durch ISR ausgelöst).

2. Du manipulierst direkt den Stack z.B. mittels Inline-Assembler und 
setzt die Rücksprungadresse auf den Bootloader. Aus dem Handbuch 
(http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/Babefdjc.html) 
geht hervor, dass während einer ISR grundsätzlich "EXC_RETURN" im 
Link-Register steht. Die eigentliche Rücksprungadresse holt sich der 
Controller dann vom Stack und die kannst du dann ändern. Außerdem 
solltest du dann den richtigen Modus im LR festlegen, falls EXC_RETURN 
auf den Handler-Mode verweisen sollte, weil deine ISR die den Bootloader 
anspringen soll eine andere ISR unterbrochen hat. Das klingt zwar alles 
ein bisschen kompliziert, sollte sich aber in < 10 Zeilen Assembler 
lösen lassen.

PS: Nop war schneller aber schön das wir die gleiche Idee hatten ;)

: Bearbeitet durch User
von Nop (Gast)


Lesenswert?

Christopher J. schrieb:

> PS: Nop war schneller aber schön das wir die gleiche Idee hatten ;)

Und sogar in derselben Reihenfolge. ^^

von Thomas E. (picalic)


Lesenswert?

Servus,

super - danke Euch für die Hinweise!
Die CPU per Software aus dem Handler zu holen, ist dann ja anscheinend 
nur etwas aufwändiger durch Stack-Manipulationen, die dem Prozessor 
einen regulären Rücksprung vorspielen, möglich. Schade, ich hatte 
gehofft, es gibt da vielleicht sowas wie ein intrinsic, daß ich nur noch 
nicht gefunden hatte, a la "__setcpumode" oder so ähnlich, mit dem man 
entsprechende Bits im CPU-Status rücksetzen kann. Aber anscheinend gibt 
es hier keine solchen Bits, auf die das Programm auch noch direkten 
Schreibzugriff hat. Dann werde ich wohl eher die erste Möglichkeit 
wählen und die Softreset-Funktion irgendwie auf den Hauptthread hieven - 
das dürfte das kleinere Übel sein.

von Christopher J. (christopher_j23)


Lesenswert?

Thomas E. schrieb:
> Schade, ich hatte
> gehofft, es gibt da vielleicht sowas wie ein intrinsic, daß ich nur noch
> nicht gefunden hatte, a la "__setcpumode" oder so ähnlich, mit dem man
> entsprechende Bits im CPU-Status rücksetzen kann.

So etwas ist mir bisher nicht bekannt.

Das ganze ist vom Prinzip eigentlich relativ simple, auch wenn es ein 
bisschen wie ein Hack wirkt. Angenommen dein Bootloader liegt bei 
0xDEADBEEF:
1
void irgendein_IRQHandler(void)
2
{
3
    // hier besser keinen C-Code, da sich sonst der SP verändern kann
4
5
    asm("ldr lr, =0xFFFFFFF9  \n\t" // EXC_RETURN für Thread Mode und MSP
6
         "ldr r0, =0xDEADBEEF \n\t" // BL-Adresse in r0 laden
7
         "str r0, [sp,#24]    \n\t" // r0 in PC auf dem Stack speichern (Offset 24 Byte von SP)
8
    );
9
10
    // hier könnte noch weiterer C-Code stehen
11
}

Ich habe es nicht getestet, also ohne Garantie aber wie gesagt 
eigentlich nicht so wild.

von Stefan F. (Gast)


Lesenswert?

Man könnte an B4 auch einfach einen "stärkeren" Pull-Down Widerstand 
hängen. Dann stört der interne Pull-Up nicht.

von Thomas E. (picalic)


Lesenswert?

Christopher J. schrieb:
> Das ganze ist vom Prinzip eigentlich relativ simple

Ja, Danke, das Prinzip habe ich (denke ich) verstanden und versuche auch 
schon den ganzen Tag, den Compiler dazu zu bewegen, irgendwelche 
Assembler-Befehle von mir zu akzeptieren.
Leider ohne Erfolg - ich bekomme es nicht hin, auch nur einen einzigen 
Assembler-Befehl per Inline-Assembler in den Code einzuschleusen. Die 
Register kennt der Inline-Assembler offenbar nicht (nicht mal r0 oder 
R0) und laut Handbuch gibt es da auch jede Menge Restriktionen.
Dann gibt es da auch einen "Embedded Assembler", der lt. Handbuch und 
meinem Verständnis davon tatsächlich einfach den ASM-Code einfügen 
sollte, wie er geschrieben wurde in der Form:
__asm return-type function-name(parameter-list)
{
// ARM/Thumb assembly code
instruction{;comment is optional}
...
instruction
}
Das scheint der Compiler aber auch anders zu sehen, als sein Handbuch.
Dann habe ich ein paar ASM-Befehle als "echte" Funktion in mein 
startup.s eingefügt. Durch EXPORT sollte dem Linker ja das Symbol dann 
bekannt sein - tut es aber leider nicht! Hat dazu jemand vielleicht 
einen Tipp und weiß wie es geht?
(Keil µVision 5.24, Compiler Version 5.06, auf Compiler-Version 6 kann 
ich ohne größere Änderungen der Dateien nicht umsteigen).
Danke!

@Stefanus: das möchte ich nicht, um die Flexibilität zu behalten, den 
Port bei Bedarf auch als Eingang zu nutzen.

: Bearbeitet durch User
von Nop (Gast)


Lesenswert?

Wenn ich mir das hier ansehe:

http://www.keil.com/support/man/docs/armcc/armcc_chr1359124249383.htm

"The inline assembler provides no direct access to the physical 
registers of an ARM processor. If an ARM register name is used as an 
operand in an inline assembler instruction it becomes a reference to a 
variable of the same name, and not the physical ARM register."

Ein embedded-Compiler mit verkrüppeltem Inline-Assembler, unfaßbar. Also 
mit GCC ist das überhaupt kein Problem, aber der meint auch nicht, die 
Programmierer vor sich selbst schützen zu sollen.

von Thomas E. (picalic)


Angehängte Dateien:

Lesenswert?

Ha! Kurze Zwischeninfo: der "Embedded Assembler" funktioniert doch!
Ich habe mich von der Fehleranzeige im Editor ins Boxhorn jagen lassen.
Da sieht es aus, wie im Bild, bei Maus über dem roten Kreuz zeigt er 
"expected '(' after asm" an, aber es kompiliert fehlerfrei!
Und ich bekomme tatsächlich eine Funktion, die aus dem "BX lr"-Befehl 
besteht (und nur aus diesem!) im Speicher! Das war jetzt nur ein Test, 
jetzt schreibe ich die richtige Funktion...

Trotzdem wäre vielleicht nett, wenn mir evtl. noch jemand verraten 
könnte, wie man "richtigen" Assemblercode in einer xxx.s Datei schreiben 
kann, dessen Adresse der Linker dann auch kennt.

: Bearbeitet durch User
von Gerd E. (robberknight)


Lesenswert?

Thomas E. schrieb:
> Port
> B4 wird hier als Ausgang verwendet, wird aber beim Reset per Hardware
> erstmal als JTAG Reset-Eingang mit internem Pull-Up Widerstand
> konfiguriert. Die angeschlossene Schaltung sieht deshalb in dem Moment
> einen kurzen (> 2ms), unerwünschten High-Impuls.

Dieses Problem hast Du doch auch beim Einschalten Deines Geräts. Wie 
löst Du das denn da?

Hast Du da irgendeine Inhibit- oder Reset-Schaltung die dafür sorgt daß 
der Rest der Schaltung durch den Impuls nicht gestört wird?

Kannst Du diese Schaltung nicht auch für das Springen in den Bootloader 
verwenden?

von Thomas E. (picalic)


Lesenswert?

Gerd E. schrieb:
> Dieses Problem hast Du doch auch beim Einschalten Deines Geräts.

Das ist richtig, aber das gesamte System wird in der Regel gemeinsam 
eingeschaltet, da haben die falschen Impulse normalerweise noch keine 
negativen Auswirkungen. Kritischer sind eher die evtl. im Betrieb 
ausgelösten Warmstarts z.B. wegen einer Konfigurationsänderung, 
Firmware-Update o.ä.. Um es zu konkretisieren: es geht um die 
Ansteuerung von Servos, meist welche mit digitaler Elektronik, schnell 
und kräftig. Wenn die nach etlichen 1,5 ms Impulsen plötzlich mal einen 
von 2,5ms sehen, dann kann es schon sein, daß die erstmal mit 
ordentlicher Wucht gegen den Poller laufen, bis sie merken, daß kein 
neuer Impuls mehr kommt.

: Bearbeitet durch User
von Gerd E. (robberknight)


Lesenswert?

Thomas E. schrieb:
> es geht um die
> Ansteuerung von Servos, meist welche mit digitaler Elektronik, schnell
> und kräftig. Wenn die nach etlichen 1,5 ms Impulsen plötzlich mal einen
> von 2,5ms sehen, dann kann es schon sein, daß die erstmal mit
> ordentlicher Wucht gegen den Poller laufen, bis sie merken, daß kein
> neuer Impuls mehr kommt.

Das wäre mir ehrlich gesagt zu gefährlich.

Wenn da - aus was für Gründen auch immer - ein Reset kommt, hast Du es 
gleich mit Maschinenschäden oder vielleicht sogar Personenschäden zu 
tun.

Nicht umsonst gibt es bei Maschinen normal ein ausgeklügeltes 
Schutzkonzept mit E-Stops, Schutzeinhausungen mit Öffnungsschaltern, 
Lichtschranken und speziell zertifizierten Sicherheitssteuerungen die 
das ganze überwachen.

Ich würde daher schaltungstechnisch dafür sorgen daß der Pin keine 
falschen Signale mehr auslösen kann.

Eine Variante: kräftigerer Pulldown an den Pin, vielleicht noch ein 
kleines Logikgatter zum Puffern dahinter, z.B. 74LVC1G17 oder 74LVC2G17.

Andere Variante: Du könntest die Ausgabe des Pins invertieren und dann 
einen 74LVC1G14 dahinterhängen, der invertiert auch wieder und Du hast 
dann die richtige Polarität. Der Pullup während dem Reset erzeugt damit 
genau die gewünschte Polarität am Ausgang Richtung Servo.

Noch eine andere Idee:
Der NRST-Pin des STM32 wirkt soweit ich weiß auch als Ausgang. Du 
könntest den mit z.B. einem RS-Flipflop nutzen und den NRST vom STM32 an 
das Set von dem Flipflop hängen. Der Ausgang des Flipflop bleibt solange 
high, bis Dein STM32 fertig initialisiert ist und Dein Programm korrekt 
läuft. Dann schaltest Du das Reset des Flipflops über einen anderen Pin.

Der Ausgang des Flipflops schaltet z.B. den /OE eines 74xx244 und damit 
die Ausgänge zu den kritischen Servos etc. frei.

Man könnte das auch noch leicht erweitern so daß regelmäßig ein 
Reset-Puls kommen muss damit das Flipflop den Kram nicht dichtmacht 
(Watchdog).

: Bearbeitet durch User
von Thomas E. (picalic)


Lesenswert?

Gerd E. schrieb:
> Wenn da - aus was für Gründen auch immer - ein Reset kommt, hast Du es
> gleich mit Maschinenschäden oder vielleicht sogar Personenschäden zu
> tun.

Wenn da im Betrieb unkontrolliert ein Reset auftritt, führt das sehr 
wahrscheinlich zum Absturz (und damit ist nicht Absturz des Programms 
gemeint! ;)) durch Kontrollverlust - in dem Fall ist dann ein kurz 
unkontrolliert zuckendes Servo das geringste Problem!

von Christopher J. (christopher_j23)


Lesenswert?

Das mit dem Keil hatte ich glatt überlesen aber habe auch nicht wirklich 
Ahnung was dessen Inline-Assembler kann und was nicht. Jedenfalls meine 
ich, dass du die Möglichkeit mit dem Flag setzen fokussieren solltest. 
Das mit dem Umbiegen des Program Counters ist ein dirty Hack, den man 
macht weil man es kann aber nicht weil es notwendig ist. In der 
main-loop kannst du dann noch deine sonstige Hardware in einen 
definierten Zustand versetzen, aus dem du dann wieder sicher starten 
kannst, etwa nach einem Reset nach einem Firmwareupdate.

von Nop (Gast)


Lesenswert?

Christopher J. schrieb:

> Das mit dem Umbiegen des Program Counters ist ein dirty Hack

Nö, das ist alles dokumentiert und spezifiziert, siehe Programming 
Manual.

von Thomas E. (picalic)


Lesenswert?

Christopher J. schrieb:
> Das mit dem Keil hatte ich glatt überlesen aber habe auch nicht wirklich
> Ahnung was dessen Inline-Assembler kann und was nicht.

Der Inline-Assembler ist offenbar ungeeignet (zumindest hierfür).
Aber der Embedded Assembler (mit dem man quasi eine komplette C-Funktion 
in ASM schreibt) dürfte gehen. Habe mich da nur von der Fehleranzeige im 
Editor-Fenster fehlleiten lassen.

Christopher J. schrieb:
> dass du die Möglichkeit mit dem Flag setzen fokussieren solltest.

Wollte ich ja dann erst auch, fand es aber schon eleganter, wenn sich 
die main nicht regelmäßig um dieses eigentlich nur in absoluten 
Ausnahmefällen (Konfiguration, Firmwareupdate) auftretende Ereignis 
kümmern muss. Zudem ist das irgendwie unbefriedigend, wenn man im 
Prinzip weiss, was man auf MPU-Seite implementieren muss, aber das 
Entwicklungswerkzeug dafür nicht im Griff hat. Man will sich die Lösung 
schließlich nicht von der IDE vorschreiben lassen!

Christopher J. schrieb:
> Das mit dem Umbiegen des Program Counters ist ein dirty Hack, den man
> macht weil man es kann aber nicht weil es notwendig ist.

Es soll eigentlich genau das gemacht werden, was bei einem Reset 
passiert, d.h. sowohl Stackpointer, als auch PC werden von ihren 
Reset-Vektoren geladen. Der alte Programm-Status incl. dessen Stack 
gehen den Prozessor nichts mehr an. Nur will ich einen "echten" Reset 
vermeiden, weil der einen ungewünschten Nebeneffekt hat. Dafür IST es 
notwendig.

von Mampf F. (mampf) Benutzerseite


Lesenswert?

Hmm, also bei mir hatte ich das so gelöst:
1
NVIC_SystemReset();

Ist das zu kompliziert? :)

Achso nochmal gelesen ... das Problem ist nicht der STM sondern quasi 
die restliche Schaltung, die dann einen Reset für 2ms sehen würde.

Okay, dann bringt meine Lösung natürlich nichts :)

Einen anderen Port-Pin verwenden, der keine JTAG-Funktion hat?

: Bearbeitet durch User
von Uwe B. (Firma: TU Darmstadt) (uwebonnes)


Lesenswert?

Der Cortex M Kern kann auch alleine zurueckgesetzt werden ohne Reset der 
Periphrerie. Libopencm3 macht das wie folgt:

void scb_reset_core(void)
{
        SCB_AIRCR = SCB_AIRCR_VECTKEY | SCB_AIRCR_VECTRESET;

        while (1);
}

von Thomas E. (picalic)


Lesenswert?

Uwe B. schrieb:
> Der Cortex M Kern kann auch alleine zurueckgesetzt werden ohne Reset der
> Periphrerie. Libopencm3 macht das wie folgt:

Habe ich mich nicht getraut. Das Programming Manual sagt dazu:
>Bit 0 VECTRESET
>      Reserved for Debug use. This bit reads as 0. When writing to the  register 
you must write 0 to
> this bit, otherwise behavior is unpredictable.

So geh't jetzt:
1
__asm void pop_PC (void) __attribute__((noreturn));
2
3
static void PerformSoftwareReset (void)
4
{
5
  uint32_t *vectaddr = (uint32_t *)0x8000000;
6
  uint32_t *stackframe;
7
8
(hier: Code für disablen von interrupts, reset Peripherie, Clocks etc...)
9
10
  SCB->VTOR = (uint32_t)vectaddr;              // set Vector Table for bootloader
11
  stackframe = (uint32_t *)(vectaddr[0]-36);  // vectaddr[0] is bootloader's top of stack
12
  __set_MSP((uint32_t)stackframe);            
13
  // build up stack frame for return from exception:
14
  stackframe[8] = 0x01000000;                  // xPSR 
15
  stackframe[7] = vectaddr[1];                // PC
16
  stackframe[6] = 0xFFFFFFFF;                  // LR
17
//  skip stackframe[5..1] -> no need to restore other registers (R0..R3, R12)
18
  stackframe[0] = 0xFFFFFFF9;                  // exception return code: ->Thread, no FPU, use MSP
19
  pop_PC (); 
20
}
21
22
__asm void pop_PC (void)
23
{
24
  POP   {PC}
25
}

von Uwe B. (Firma: TU Darmstadt) (uwebonnes)


Lesenswert?

Thomas E. schrieb:
> Uwe B. schrieb:
>> Der Cortex M Kern kann auch alleine zurueckgesetzt werden ohne Reset der
>> Periphrerie. Libopencm3 macht das wie folgt:
>
> Habe ich mich nicht getraut. Das Programming Manual sagt dazu:

Den Core Reset machen alle Blackmagic Debug Probes um in den DFU 
Bootloader zu kommen ohne Probleme.

von Thomas E. (picalic)


Lesenswert?

Ok, also so:
1
  __DSB();                                                     /* Ensure all outstanding memory accesses included
2
                                                                  buffered write are completed before reset */
3
  SCB->AIRCR  = ((0x5FA << SCB_AIRCR_VECTKEY_Pos)      |
4
                 (SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) |
5
                 SCB_AIRCR_VECTRESET_Msk);                     /* Keep priority group unchanged */
6
  while(1);
geht's auch (bzw. sogar besser!)
Mensch, hättest Du ja auch mal zwei Tage früher schreiben können... ;)

von Bauform B. (bauformb)


Lesenswert?

Uwe B. schrieb:
> Den Core Reset machen alle Blackmagic Debug Probes um in den DFU
> Bootloader zu kommen ohne Probleme.

Die machen das doch per SWD? Evt. halten die vorher die CPU an oder 
schalten sie in einen speziellen Debug-Modus und dann funktioniert der 
AIRCR_VECTRESET definiert. Der ist doch speziell für Debugger gedacht?

Nebenan gibt's ganz aktuell ein gutes Beispiel für "unpredictable 
behavior"
Beitrag "Mikrocontroller mit Code von der alten Revision"

von Christopher J. (christopher_j23)


Lesenswert?

Bauform B. schrieb:
> Nebenan gibt's ganz aktuell ein gutes Beispiel für "unpredictable
> behavior"

"unpredictable" ist vor allem das Problem des TO von nebenan, weil der 
ja gar nicht sagt wo es eigentlich hängt.


Bauform B. schrieb:
> Die machen das doch per SWD? Evt. halten die vorher die CPU an oder
> schalten sie in einen speziellen Debug-Modus und dann funktioniert der
> AIRCR_VECTRESET definiert. Der ist doch speziell für Debugger gedacht?

Der Uwe meinte wohl die BMP selber, also um deren Firmware (die der 
Black Magic Probe) zu updaten.


Nop schrieb:
> Christopher J. schrieb:
>
>> Das mit dem Umbiegen des Program Counters ist ein dirty Hack
>
> Nö, das ist alles dokumentiert und spezifiziert, siehe Programming
> Manual.

Ich habe mich ein bisschen uneindeutig ausgedrückt. Den PC müsste man ja 
bei beiden von uns vorgeschlagenen Lösungen verändern. Was ich für einen 
dirty Hack halte ist das verändern des PC auf dem Stack, damit dann nach 
einem exception return der Bootloader angesprungen wird.


Thomas E. schrieb:
> So geh't jetzt:

Ok, das ist auf jeden Fall schonmal sauberer sich erstmal den initialen 
Stackpointer zu schnappen und es dann darauf aufzubauen. Der Vorschlag 
von Uwe gefällt mir aber auch deutlich besser ;)

von Nop (Gast)


Lesenswert?

Christopher J. schrieb:
> Was ich für einen
> dirty Hack halte ist das verändern des PC auf dem Stack

Was ist daran dirty? Völlig normale Assembler-Programmierung. Was meinst 
Du, wie man unter der Haube z.B. ein OS mit Multitasking realisiert?

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.