Forum: Compiler & IDEs CH32V003 gcc und Codegenerierung


von Vanye R. (vanye_rijan)


Lesenswert?

Moin Leute,

Ich hab da gerade ein kleines Problem mit meiner Testumgebung
fuer einen CH32V003.

Mein System ist im Prinzip ein Eigenbau der zu 90%
hierauf basiert:

https://github.com/cnlohr/ch32v003fun

Ich hab z.b ein kleines Testprogramm das eine LED an einem Pin
blinken laesst und es funktioniert.

Ich linke nun ein weiteres Testprogramm hinzu OHNE das ich daraus
irgendwas benutzte! Sofort haengt sich mein Testprogramm weg.

Grund dafuer ist folgendes:
1
void SysTick_Handler(void) __attribute__((interrupt));
2
void SysTick_Handler(void)
3
{
4
  msTicks++;
5
6
  SysTick->SR = 0;
7
}

Alleine die deklaration dieses Systick_Handler sorgt dafuer
das mein Programm nicht laeuft. Der Grund wird auch klar wenn man
ins mapfile schaut:

Ohne Systick:
1
.text.startup.main
2
                0x00000000000000ce      0x100 /tmp/ccRxtkAe.ltrans0.ltrans.o
3
                0x00000000000000ce                main
4
 .text.vector_handler
5
                0x00000000000001ce        0x2 /tmp/ccRxtkAe.ltrans0.ltrans.o
6
                0x00000000000001ce                TIM1_CC_IRQHandler
7
                0x00000000000001ce                HardFault_Handler
8
                0x00000000000001ce                SysTick_Handler
9
                0x00000000000001ce                PVD_IRQHandler
10
                0x00000000000001ce                NMI_Handler
11
                0x00000000000001ce                SPI1_IRQHandler

Und so mit Systick:
1
.text.startup.main
2
                0x00000000000000ce       0xe8 /tmp/ccHcEMIq.ltrans0.ltrans.o
3
                0x00000000000000ce                main
4
 .text.SysTick_Handler
5
                0x00000000000001b6       0x26 /tmp/ccHcEMIq.ltrans0.ltrans.o
6
 .text.vector_handler
7
                0x00000000000001dc        0x2 /tmp/ccHcEMIq.ltrans0.ltrans.o
8
                0x00000000000001dc                TIM1_CC_IRQHandler
9
                0x00000000000001dc                HardFault_Handler
10
                0x00000000000001dc                PVD_IRQHandler
11
                0x00000000000001dc                NMI_Handler
12
                0x00000000000001dc                SPI1_IRQHandler

Der Linker fummelt mir also wohl die Interrupttabelle kaputt.

So sieht die uebersetztung aus:

make
riscv-none-embed-gcc --specs=nosys.specs -g -Os -flto 
-ffunction-sections  -static-libgcc -march=rv32ec -mabi=ilp32e -nostdlib 
-I./riscv_core/extralibs  -I./riscv_core -I./riscv_core/inc 
-I./riscv_core/attic -T ./riscv_core/inc/ch32v003fun.ld 
-Wl,-Map=mapfile.txt  -lc -lm -Wl,--gc-sections -L./riscv_core/misc 
-lgcc  riscv_core/attic/startup_ch32v003.c timer.c main.c -o 
template.elf
riscv-none-embed-objcopy -O ihex template.elf template.hex
riscv-none-embed-objcopy -O binary template.elf template.bin

Ich vermute mal das ich da noch irgendeine Option vergessen habe?
Oder woran kann das sonst liegen?

Der Handler selbst wird im StartUp-Code weak definiert:
1
void SysTick_Handler( void )             __attribute__((section(".text.vector_handler"))) __attribute((weak,alias("DefaultIRQHandler"))) __attribute__((used));

Vanye

von Steve van de Grens (roehrmond)


Lesenswert?

Vanye R. schrieb:
> Ich linke nun ein weiteres Testprogramm hinzu

Was soll das werden?

Zwei Programme auf diese Art zu kombinieren kann nicht funktionieren, 
egal wie du es dir vorgestellt hast.

von Harald K. (kirnbichler)


Lesenswert?

Vanye R. schrieb:
> Ich linke nun ein weiteres Testprogramm hinzu

Zeige bitte den vollständigen Quelltext.

Was ist in timer.c drin, was ist in main.c drin?

Vanye R. schrieb:
> Mein System ist im Prinzip ein Eigenbau der zu 90%
> hierauf basiert:

Und worin unterscheiden sich die restlichen 10%?

von Vanye R. (vanye_rijan)


Lesenswert?

> Zeige bitte den vollständigen Quelltext.

Koennte ich tun, aber zu lang. Ausserdem habt ihr meine Frage
nicht verstanden.

Darum noch mal ein Versuch.
1
SRCS = ./riscv_core/attic/startup_ch32v003.c
2
#SRCS +=timer.c 
3
SRCS +=main.c

Das obige ist ein Ausschnitt aus dem Makefile welches zu einem
funktionierendem Programm fuehrt, nehme ich timer.c mit ins
Programm so funktioniert das Programm nicht. Ich entferne
also nur "#" und mache ein make clean;make

Mit anderen Worten es wird KEINE einzige Programmzeile aus
dieser Datei genutzt. Sie ist dann einfach nur vorhanden,
nichts daraus wird aufgerufen.

Lediglich die Codegenerierung vom Linker aendert sich.
Also die Position oder Eintraege von Interrupttabellen.
Aber es wird NICHTS davon genutzt! Kein Eintrag wird
aufgerufen, nichts ist aktiviert.
Und mein eigener Code laeuft auch garnicht erst! Selbst
ein Initalisieren eines GPIO und high setzen in der
ersten Zeile funktioniert dann nicht mehr. main
wird also nicht mehr erreicht.

Vanye

von Oliver S. (oliverso)


Lesenswert?

Neben Compiler und Linker braucht es für einen funktionierende toolchain 
auch noch startup code und vor allem ein linkerscript, das dem linker 
sagt, wie das alles zusammenzubauen ist.

In linkerscript findest du alle Antworten zu deiner Frage.

Oliver

von Harald K. (kirnbichler)


Lesenswert?

Vanye R. schrieb:
> Ausserdem habt ihr meine Frage nicht verstanden.

Du hast Dich nun auch sehr merkwürdig ausgedrückt:

Vanye R. schrieb:
> Ich linke nun ein weiteres Testprogramm hinzu OHNE das ich daraus
> irgendwas benutzte!

Vanye R. schrieb:
> Aber es wird NICHTS davon genutzt!

Du scheinst einen Interrupthandler angelegt zu haben. Was soll den der 
Linker damit Deiner Ansicht nach machen? Ob Du die nötige Peripherie 
initialisierst, die den Interrupt schlussendlich auslöst, das kann der 
Linker nicht wissen. Also muss er den Interrupthandler wie einen 
Interrupthandler behandeln.

Und dabei kommt er wohl aus den von Oliver beschriebenen Gründen 
durcheinander.

Worin bestehen denn die 10% Unterschied zum Code von cnlohr? Vielleicht 
liegen die in genau diesem Linkerskript und dem Drumherum?

von Markus F. (mfro)


Lesenswert?

Vanye R. schrieb:
> Der Linker fummelt mir also wohl die Interrupttabelle kaputt.

Die RISC-V Toolchain ist mir fremd, aber ich nehme an, was da passiert 
ist analog zu ARM.

Der Startup-Code enthält weak Stubs für die Interrupt-Service Routinen.
Sobald Du gleichnamige "echte" Routinen mit gleichem Namen dazulinkst, 
werden die aktiv.

Das ist nicht "Kaputtfummeln", sondern ein Dienst am Programmierer.

von Harald K. (kirnbichler)


Lesenswert?

Markus F. schrieb:
> Sobald Du gleichnamige "echte" Routinen mit gleichem Namen dazulinkst,
> werden die aktiv.

Dann müsste allerdings die Reihenfolge der Einträge für vector_handler 
und systick_handler andersherum sein, sofern man davon ausgehen kann, 
daß die beiden zu Beginn gezeigten Tabellen in ihrer Reihenfolge 
aufsteigend nach Vektor sortiert sind.
"vector_handler" sollte daher in beiden Fällen an der gleichen Stelle 
stehen, und das tuts nicht.

Da kann ich vanyes Irritation nachvollziehen.

von Vanye R. (vanye_rijan)


Lesenswert?

> Der Startup-Code enthält weak Stubs für die Interrupt-Service Routinen.
> Sobald Du gleichnamige "echte" Routinen mit gleichem Namen dazulinkst,
> werden die aktiv.

DAs weiss ich. Ich habs ja im Ausgangspost selber zitiert. Hier die 
Datei aus der ich zitiert habe und die 1:1 bei mir verwendet wird:

https://github.com/cnlohr/ch32v003fun/blob/master/attic/ch32v003evt/startup_ch32v003.c

Und im uebrigen sollte da nichts aktiv werden. Der Linker sollte die 
Adresse meines Codes dann anstelle des weak-defaults einsetzen. Das 
passiert nicht korrekt, aber selbst das sollte noch egal sein weil man 
den Interrupt dann ja auch noch aktivieren muss. Das mache ich aber 
nicht wie ich schon zweimal betont habe.
Aber kann sein das ich da was verwechselt habe (Tabelle und Position der 
Firmware). Ich muss mir das am Wochenende nochmal genauer anschauen.



Vanye

von Christopher B. (chrimbo) Benutzerseite


Lesenswert?

Hau mal die ganzen
1
 __attribute__((section(".text.vector_handler")))

im Startup Code raus und lege einfach ein array an die Stelle 
.text.vector_handler:
1
__attribute__((section(".text.vector_handler")))
2
void (* const gISR_VECTORS[])(void) = {
3
  NMI_Handler,
4
  HardFault_Handler,
5
  SW_Handler,
6
  ...
7
};

von Oliver S. (oliverso)


Lesenswert?

Vanye R. schrieb:
> Das
> passiert nicht korrekt, aber selbst das sollte noch egal sein weil man
> den Interrupt dann ja auch noch aktivieren muss. Das mache ich aber
> nicht wie ich schon zweimal betont habe.

Der linker analysiert keine Programme, um herauszufinden, ob du da 
irgendwo einen Interrupt aktivierst oder nicht. Selbst der Compiler 
macht und kann das nicht.

Oliver

von Vanye R. (vanye_rijan)


Lesenswert?

> Selbst der Compiler macht und kann das nicht.

Eben, deshalb ja mein hinweis das mein Programm uebersetzt wird
wenn timer.c includiert wird aber auch wenn nicht! Da sind also schon
Funktionen drin, genauer gesagt die Initialisierung fuer den
Timer und die Interruptfunktion. Aber die werden eben nicht 
aufgerufen/genutzt! Sonst wuerde der Linker ja sofort mit einem Fehler 
aussteigen sobald ich die Funktion entferne.

> im Startup Code raus und lege einfach ein array an die Stelle

Ja, ich fand das auch etwas komisch und kenne das sonst etwas anders,
allerdings ist der Source ja nicht von mir und scheint in anderen
Projekten zu funktionieren.

Vanye

von Harald K. (kirnbichler)


Lesenswert?

Vanye R. schrieb:
> Da sind also schon
> Funktionen drin, genauer gesagt die Initialisierung fuer den
> Timer und die Interruptfunktion. Aber die werden eben nicht
> aufgerufen/genutzt!

Ja, aber das ist völlig irrelevant. Du hast einen Interrupthandler 
definiert, der wird in die Interrupttabelle eingetragen, egal, ob Du 
irgendeine Initialisierung aufrufst oder nicht.

von Vanye R. (vanye_rijan)


Lesenswert?

> Ja, aber das ist völlig irrelevant. Du hast einen Interrupthandler
> definiert, der wird in die Interrupttabelle eingetragen, egal, ob Du
> irgendeine Initialisierung aufrufst oder nicht.

Dessen bin ich mir bewusst, aber das ist ja irrelevant solange der
Interrupt nicht eingeschaltet wird, was nicht der Fall ist.

Wenn ich keine Interruptfunktion im Programm habe wird ja folgendes
in der Tabelle eingetragen:

void DefaultIRQHandler( void )
{
  // Infinite Loop
  asm volatile( "1: j 1b" );
}

Damit funktioniert mein Programm weil auch das natuerlich niemals
aufgerufen wird. Es ist ja schliesslich nicht aktiviert.

Vanye

von Vanye R. (vanye_rijan)


Lesenswert?

Irgendwie wird das gerade verwirrender....

Hier seht ihr ja den Startcode:

https://github.com/cnlohr/ch32v003fun/blob/master/attic/ch32v003evt/startup_ch32v003.c


Default ist also
1
void DefaultIRQHandler( void )
2
{
3
  // Infinite Loop                                                                         
4
  asm volatile( "1: j 1b" );
5
}

des weiteren wird  das der Systick_Handler so vordefiniert:
1
void SysTick_Handler( void )
2
__attribute__((section(".text.vector_handler")))
3
__attribute((weak,alias("DefaultIRQHandler"))) __attribute__((used));

Daraus schliesse ich das der SystTick_Handler erstmal nicht aufgerufen 
wird, denn dann wuerde es ja sofort in obiger Endlosschleife enden. 
Zustimmung?

In meiner main schalte ich dann eine LED ein und aus und hab dazwischen 
eine brutale Wartefunktion die einfach Zyklen verschwendet. Ich sehe 
dann die LED wie erwartet blinken. Alles laeuft!

Jetzt binde ich meine timer.c im Makefile ein und sorge dafuer das in 
timer.c keine Funktion befindet die SysTick_Handler heisst. Alles 
laeuft, led blinkt.

Jetzt schreibe ich da folgendes rein:
1
void SysTick_Handler(void)
2
{
3
}

Alles laeuft. LED blinkt. Warum auch nicht. Sollte ja nicht aufgerufen 
werden oder? Allerdings wenn doch, hat das ja keine Auswirkungen.

Okay, jetzt folgendes
1
static volatile uint32_t msTicks;
2
3
void SysTick_Handler(void)
4
{
5
  //   msTicks++;                                                                              
6
7
  volatile int i;
8
  asm volatile( "nop");
9
  i++;
10
11
  //    SysTick->SR = 0;                                                                       
12
}

Meine LED blinkt, alles geht!

Dann kommentiere ich die msTicks Zeile aus und das Programm haengt sich 
weg!

Es gibt also zwei Merkwuerdigkeiten:

1. Wieso wird Systick_Handler ueberhaubt aufgerufen?
2. Wenn schon, wieso ist gerade der zugriff auf msTicks so giftig?

Ich seh schon kommen das ich RiscV Assembler lernen muss, seufz!

Vanye

von J. S. (jojos)


Lesenswert?

Vanye R. schrieb:
> void SysTick_Handler(void) __attribute__((interrupt));

wozu ist das Attribut interrupt? Stimmen die damit nicht überein und 
wird dann das weak DefaultHandler nicht überschrieben?

Beim Default Handler wird das auch nicht gemacht.

Edit:
Mr. Lohr schreibt das interrupt crucial wäre, aber beim Default Handler 
ist die ISR naked. Doku zu Attribut interrupt sagt das da noch ein 
Argument hingehört.
Bzw. bei RISC-V ist default machine.
https://gcc.gnu.org/onlinedocs/gcc/RISC-V-Function-Attributes.html

ok, naked ist beim DefaultHandler valid weil das nur eine Endlos asm 
Schleife ist.

: Bearbeitet durch User
von Christopher B. (chrimbo) Benutzerseite


Lesenswert?

Vanye R. schrieb:
> Ja, ich fand das auch etwas komisch und kenne das sonst etwas anders,
> allerdings ist der Source ja nicht von mir und scheint in anderen
> Projekten zu funktionieren.

Mein Fehler, hab erst jetzt gesehen dass besagtes Vektorarray im Startup 
Code angelegt ist.

Vielleicht gibt es einen Hardfault? Du kannst ja mal probieren deine LED 
im
1
void HardFault_Handler(void)
 einzuschalten

von Felix F. (wiesel8)


Lesenswert?

Laut WCH muss der Interrupt so deklariert werden:
https://github.com/openwch/ch32v003/blob/main/EVT/EXAM/SYSTICK/SYSTICK_Interrupt/User/main.c#L52

Ich mache das so mit der offiziellen SPL und es funktioniert 
einwandfrei.

von Vanye R. (vanye_rijan)


Lesenswert?

So, ich hab jetzt laufen. Ich hab mir sogar bereits ueber den Systick
meine delay funktion ans laufen bekommen.

Die Loesung bestand daran an den CFLAGS noch ein...

  -fdata-sections

...anzuhaengen. Aber ehrlich gesagt warum es damit laeuft ist
mir noch ein raetzel. Aber es scheint zu laufen.


Vanye

von Vanye R. (vanye_rijan)


Lesenswert?

Noch ein kleiner Tip fuer den Einsteiger. Wer bei den Teilen
einen Portleitung auf eine alternative Funktion umlegt,
der muss den Takt dafuer einschalten.

  RCC->APB2PCENR |= RCC_AFIOEN;

Ich glaube das hat mich bestimmt eine Stunde gekostet
da man irgendwie nicht erwartet das dafuer auch ein Takt
gebraucht wird!

Vanye

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.