RISCV (CH32V003): GNU Assembler übersetzt "CALL" nicht korrekt (oder bin ich nur zu doof?)

#7888559
Lesenswert?

Hallo zusammen,

ich versuche mir seit ein paar Tagen ein kleines HelloWorld-Blink-Programm direkt in RISCV-Assembler für den CH32V003 zu bauen und bekam es erst zum Laufen, als ich Verzögerungsschleifen, die mit dem Pseudo-Befehl "CALL" aufgerufen wurden, direkt ins Hauptprogramm eingebaut hatte und somit Unterprogramme vermieden hatte.

Ich habe inzwischen das Problem auf den folgenden "Zwei Zeilen-Quelltext" reduzieren können, welcher bei mir falsch übersetzt wird:

1
Start:        CALL Subroutine
2
Subroutine:   RET

Vorweg: Zum Übersetzen benutze ich das Paket "xpack-riscv-none-elf-gcc-14.2.0-3", die Version des im Paket enthaltenen Assemblers ist "GNU assembler (xPack GNU RISC-V Embedded GCC x86_64) 2.43.1".

Wenn man die beiden Zeilen mittels "riscv-none-elf-as.exe myfile.s" übersetzt und sich danach das Ergebnis mittels "riscv-none-elf-objdump.exe -d a.out" anschaut, dann sieht man folgendes:

1
a.out:     file format elf32-littleriscv
2

3
Disassembly of section .text:
4

5
00000000 <Start>:
6
   0:   00000097                auipc   ra,0x0
7
   4:   000080e7                jalr    ra # 0 <Start>
8

9
00000008 <Subroutine>:
10
   8:   00008067                ret

Der CALL-Befehl wird hier in zwei Befehle aufgeteilt (das ist auch noch in Ordnung), es fehlt jedoch im zweiten Befehl (jalr) der notwendige Offset, damit auch zum Unterprogramm gesprungen wird.

Richtigerweise sollte meiner Ansicht nach diese Zeile so aussehen:

1
   4:   008080e7                jalr    8(ra) # 8 <Subroutine>

Wenn ich in meinem HelloWorld-Blink-Programm die übersetzten CALL-Befehle derart patche (also die jalr-Befehle mit dem notwendigen Offset versehe), dass sie auf das Unterprogramm mit der Verzögerungsschleife zeigen, dann funktioniert auch alles, so wie es soll...

Es ist also offensichtlich der CALL-Befehl, der nicht korrekt übersetzt wird. Aber kann das sein? Ich habe auch ältere Versionen des Assemblers getestet, es ist überall dasselbe Verhalten.

Fehlt vielleicht hier nur eine Option, die ich dem Assembler mitgeben muss? Oder habe ich hier wirklich einen Fehler entdeckt? Das kann ich mir aber bei so einem grundlegenden Befehl nicht vorstellen...

Habt ihr irgendwelche Ideen?

Gruß Thomas

#7888687
Lesenswert?

Manuel H. schrieb:

Probier mal den Befehl:

1
riscv-none-elf-objdump.exe -d -r a.out

Dann siehst du, dass die erzeugte Objektdatei noch eine Relokation enthält, die auf das Unterprogramm verweist. Die richtige Adresse setzt dann der Linker ein.

DANKE! Das ist ein wertvoller Hinweis! Ich hatte gedacht, ich komme ohne einen Linker klar und der Assembler macht schon alles für mich. Da lag also mein Denkfehler.

Vielleicht sind folgende Befehle ja eine Hilfe für Gleichgesinnte, die ohne eine große IDE auf der Kommandozeile RISCV-Programme für den CH32V003 assemblieren wollen:

Assemblieren mittels:

1
riscv-none-elf-as.exe -agl blinken.s

Dann Linken mittels:

1
riscv-none-elf-ld.exe -o a.o -Ttext 0 -e Coldstart a.out

Danach die Binärdatei bauen mittels:

1
riscv-none-elf-objcopy.exe -O binary a.o a.bin

Und Anschauen, was der Assembler gebaut hat, geht mittels:

1
riscv-none-elf-objdump.exe -d -r a.o

Gegebenenfalls müssen natürlich die Dateinamen angepasst werden.

Gruß Thomas

Persönliche Seite #7888713
Lesenswert?

as erzeugt aus der Assembly-Quelle eine Object Datei, was aber kein ausführbarer Code ist. Dazu muss mindestens Gelinkt werden, und evtl. braucht's noch zusätzlichen Code (Startup Code, CRT, Device Initialisierung, Vektor Tabellen, Linker Script, etc).

Am einfachsten bekommt man das, wenn man gcc als Treiber verwendet (für.S oder.sx oder mit -x assembler-with-cpp).

Weiterer Vorteil ist, dass man dann auch den C-Präprozessor im asm verwenden kann.

#7889087
Lesenswert?

Thomas T. (knibbel) 08.06.2025 13:01

ich versuche mir seit ein paar Tagen ein kleines HelloWorld-Blink-Programm direkt in RISCV-Assembler für den CH32V003 zu bauen und bekam es erst zum Laufen, als ich Verzögerungsschleifen, die mit dem Pseudo-Befehl "CALL" aufgerufen wurden, direkt ins Hauptprogramm eingebaut hatte und somit Unterprogramme vermieden hatte.

Eine sehr interessante Idee, sich bei diesem Prozessor auf die Assemblerebene zu begeben.
Kannst du ein einfaches Blink-Programm für den Einstieg posten?

#7889328
Lesenswert?

Christoph M. schrieb:

Eine sehr interessante Idee, sich bei diesem Prozessor auf die Assemblerebene zu begeben. Kannst du ein einfaches Blink-Programm für den Einstieg posten?

Da der Thread noch nicht zerredet wurde, kann ich das gerne machen. Hier kommt also ein sehr einfaches Programm, was bei der Adresse 0 startet und auch keinerlei Rücksicht auf irgendwelche Vektortabellen für Interrupts nimmt. Normalerweise erfolgt wohl zuallererst bei Adresse 0 ein Jump-Befehl zum Einsprungspunkt und dann folgen erst mal eine Reihe von Vektoren, falls man mit Interrupts arbeitet. Und den Stack-Pointer habe ich auch nicht gesetzt, das Speichern der Rücksprungadressen beim CALL-Befehl funktioniert hier etwas anders und auch nur für eine Ebene. Bei mehreren Unterprogramm-Ebenen muss man sich dann was eigenes basteln...

1
# Assemblieren:     riscv-none-elf-as -agl blink.s
2
# Linken:           riscv-none-elf-ld -o blink.o -Ttext 0 -e Coldstart a.out
3
# Anschauen:        riscv-none-elf-objdump -d -r blink.o
4
# Binary bauen:     riscv-none-elf-objcopy -O binary blink.o blink.bin
5

6
                        .file    "blinken.s"
7
                        .section .text
8
                        .balign  4
9

10
                        .global  Coldstart
11

12
R32_RCC_APB2PCENR       =       0x40021018
13
R32_GPIOD_CFGLR         =       0x40011400
14
R32_GPIOD_BSHR          =       0x40011410
15

16
                        .org    0x00000000
17

18
Coldstart:              nop
19

20
Port_Setup_Clock:       li      x10,R32_RCC_APB2PCENR
21
                        lw      x11,0(x10)
22
                        li      x7,(1<<5)
23
                        or      x11,x11,x7
24
                        sw      x11,0(x10)
25

26
Port_Setup_Direction:   li      x10,R32_GPIOD_CFGLR
27
                        lw      x11,0(x10)
28
                        li      x7,0xfff0ffff
29
                        and     x11,x11,x7
30
                        li      x7,0x00030000
31
                        or      x11,x11,x7
32
                        sw      x11,0(x10)
33

34
                        li      x10,R32_GPIOD_BSHR
35

36
Led_PD4_ON:             li      x11,0b00000000000000000000000000010000
37
                        sw      x11,0(x10)
38

39
                        call    Delay
40

41
Led_PD4_OFF:            li      x11,0b00000000000100000000000000000000
42
                        sw      x11,0(x10)
43

44
                        call    Delay
45

46
                        j       Led_PD4_ON
47

48

49
Delay:                  li      x6,200000
50
DelayLoop:              addi    x6,x6,-1
51
                        bne     x6,zero,DelayLoop
52
                        ret

Viel Spaß damit.

Gruß Thomas

#7889804
Lesenswert?

Thomas T. (knibbel) 09.06.2025 14:51

Da der Thread noch nicht zerredet wurde, kann ich das gerne machen.
Wieso ist doch super. Auf die Art lernt man einen Prozessor in der Tiefe kennen (inclusive der Toolchains).

Johann L. (gjlayde)

weil es eben nicht die
absolute Adresse des nächsten Kommandos setzt.

Kannst du das mal genauer ausführen?

#7890313
Lesenswert?

Johann L. schrieb:

Auf .org würde ich komplett verzichten, weil es eben nicht die absolute Adresse des nächsten Kommandos setzt.

Ich denke, man sollte hier einen Mittelweg finden...

.org setzt nicht immer die absolute Adresse des nächsten Befehls. Wenn man allerdings die Einschränkungen berücksichtigt, dann sollte man .org nicht komplett ablehnen.

Ein Grund wäre beispielsweise folgender:

Sobald ich für Interrupts eine Vektortabelle am Anfang des Flash hinterlege, bin ich gezwungen diese zu umgehen, da der Programmstart immer bei 0 beginnt und dort dann ein Jump-Befehl stehen muss. Auch wenn man gewöhnlich dem Linker den Programmstart vorgeben kann (oder er selbst _start nimmt), funktioniert dies beim Mikrocontroller nicht mehr.

Ich will die Led an PD4 als nächsten Schritt mittels eines Timer-Interrupts blinken lassen und bereite den Quelltext so vor, damit die Adresse der Interrupt-Routine auch korrekt hinterlegt ist:

1
                        .org 0x00000000
2
Coldstart:              j       Warmstart
3

4
                        .org 0x00000090
5
TIM1TRG_Vector:         .dc.l   Irq_Routine
6

7
                        .org 0x00000100
8
Warmstart:              nop

Ich werde am nächsten Wochenende mal versuchen, die Interrupt-Routine auszuformulieren und die Werte für den Timer zu setzen.

Was alternativ allerdings funktionieren könnte, wäre folgendes: Ich baue mir eine separate Vektortabelle, in der ich zu Anfang einen Jump-Befehl auf das Ende der Vektortabelle setze. Genauer gesagt, direkt hinter dem letzten Eintrag der Vektortabelle. Wenn ich diese Datei dann assembliere und vor meine eigentliche Objektdatei linke, dann könnte es auch ohne .org funktionieren...

Gruß Thomas

#7890456
Lesenswert?

Warum lässt du den Linker nicht einfach /seine/ Arbeit machen?

Die Grösse der Vektortabelle ist bekannt, und gehört neben ihrer Adresse einfach in eine /Section/ der Linkdefinitionen. Dazu dann eine Assemblerquelle, die den Inhalt aufdröselt und genau dieser Section zugeordnet wird. Die braucht dann eventuell auch eher Aligns statt Org.

Genauso wie der Main- und der Interruptcode in eine Section gehören. Die richtigen Adressen setzt dann schon der Linker ein.

Die Sections wiederum liegen in /Regions/ der Linkertabelle.

Deine Beispiele werden nicht immer so einfach bleuben, und willst du ernsthaft in grösseren Projekten immer mit Absolutadressen hantieren?

Also mach es gleich richtig.

#7899234
Lesenswert?

Die Compiler Toolchain zu installieren war für mich relativ umständlich. Einfacher ist es, die Arduino IDE mit der passenden Toolchain zu installieren und dann einfach den vorhandenen Assembler mit einem Script zu nutzen.
Den Pfad zur Toolchain kann man einfach aus der Arduino-IDE heraus kopieren, wenn man einmal ein leeres Programm compiliert hat.

Das folgende Script assembliert und flashed den Mikrocontroller in einem Rutsch:

1
#!/bin/bash
2

3
# CH32V003 on Ubuntu
4
# To assemble programms for the CH32V003 microcontroller, you can use
5
# the assembler toolchain provide by this Arduino Framework:
6
# https://github.com/AlexanderMandera/arduino-wch32v003/tree/master
7
# Just follow the installation instructions and then run the script.
8

9
# This script compiles bink.s and flashes the microcontroller
10

11
#2025-06-28 mchris
12

13
# Define toolchain directory
14
DIR="/home/christoph/.arduino15/packages/WCH/tools/riscv-none-embed-gcc/8.2.0/bin"
15

16
# Add toolchain to PATH only if not already present
17
case ":$PATH:" in
18
    *":$DIR:"*) :;; # already in PATH, do nothing
19
    *) export PATH="$DIR:$PATH";;
20
esac
21

22
# Assemble, link, and create binary
23
echo "Assembling blink.s..."
24
riscv-none-embed-as -o blink.o blink.s
25

26
echo "Linking to create blink.elf..."
27
riscv-none-embed-ld -o blink.elf -Ttext=0 -e Coldstart blink.o
28

29
echo "Creating blink.bin..."
30
riscv-none-embed-objcopy -O binary blink.elf blink.bin
31

32
# OpenOCD variables
33
OPENOCD_BIN="$HOME/.arduino15/packages/WCH/tools/openocd/1.0.0/bin/openocd"
34
CFG_FILE="$HOME/.arduino15/packages/WCH/tools/openocd/1.0.0/bin/wch-riscv.cfg"
35

36
# Erase chip (optional, but recommended)
37
echo "Attempting to erase chip (this will fail if write protected)..."
38
"$OPENOCD_BIN" -f "$CFG_FILE" -c init -c halt -c "flash erase_address 0x0 0x400" -c exit 2>&1 | tee erase_output.log
39

40
if grep -q "write protected" erase_output.log; then
41
    echo "Chip is write protected. Aborting flash."
42
    exit 1
43
else
44
    echo "No write protection detected, or unable to determine."
45
fi
46

47
# Flash blink.bin to the microcontroller
48
echo "Flashing blink.bin to the microcontroller..."
49
"$OPENOCD_BIN" -f "$CFG_FILE" -c init -c halt -c "program blink.bin 0x0 verify reset exit"
50

51
echo "Done."
Persönliche Seite #7899262
Lesenswert?

Paar Anmerkungen:

OpenOCD versteht ELF. Der Zwischenschritt über iHex / Binary ist also überflüssig: https://openocd.org/doc/html/Flash-Commands.html Teilweise ist Binary auch kontraproduktiv, etwa wenn etwas Code ab 0x0 liegt und etwas Daten ab 0x1000000. Das ELF file ist dann klein um überschaubar, aber das Binary ist riesig weil es keine Section o.ä. darstellen kann.

Assemble und Link lassen sich mit einem Kommando ausführen, indem man riscv-none-embed-gcc als Treiber verwendet:

1
$ riscv-none-embed-gcc blink.s -o blink.elf -Ttext=0 -e Coldstart -nostartfiles

In reinen Asm Programmen will man i.d.R keinen C Startup Code, daher das -nostartfiles (Mit den RISCV Tools kenne ich mich nicht aus, und ich weiß nicht ob da überhaupt Startup Code anbei ist).

Um Argumente an den Linker zu übergeben wird normalerweise -Wl verwendet, also zum Beispiel -Wl,-e,Coldstart. Hier ist das nicht nötig weil gcc weiß, dass -e für den Linker ist. Dito für -Ttext. Ein Map-File wird man etwa mit -Wl,-Map,blink.map vom Linker erstellen lassen.

Ein Vorteil bei der Verwendung von gcc ist, dass man neben .macro von GAS noch einen zweiten Makro-Prozessor nutzen kann, nämlich den C Präprozessor (#include, #define, #if, #ifdef, ...). Dazu muss man allerdings .S oder .sx als Suffix verwenden, oder man kann auch mit -x die "Sprache" explizit angeben:

1
$ riscv-none-embed-gcc -x assembler-with-cpp blink.asm -o blink.elf -Ttext=0 -e Coldstart -nostartfiles

Liegt die Applikation in mehreren Modulen vor, dann kann man einfach alle Module in einem Rutsch übersetzen, anstatt für jedes Modul händisch einen GAS-Aufruf zu bemühen:

1
$ riscv-none-embed-gcc -x assembler-with-cpp blink.asm sound.asm grafik.asm -o blink.elf <options>

Um die Ausgaben / Ergebnisse des C Präprozessors zu behalten dient -save-temps. Die Zwischenergebnisse werden dann als blink.s, sound.s und grafik.s gespeichert.

Wenn man Assembler mit C kombinieren will, dann gibt man einfach die C-Quellen beim Aufruf mit an. Zu beachten ist, dass -x die Sprache für /alle/ folgenden Inputs setzt. Mit -x none erreicht man, dass die Sprache wieder vom Suffix abgeleitet wird:

1
$ riscv-none-embed-gcc modul1.c -x assembler-with-cpp blink.asm sound.asm grafik.asm -x none modul2.c -o blink.elf <options>

Bei vielen[tm] Modulen nimmt man üblicherweise ein Makefile anstatt eines Shell Skripts.

#7899474
Lesenswert?

Johann L. (gjlayde) Benutzerseite

Paar Anmerkungen

Danke für die vielen wertvollen Hinweise.
Für mich ist die Erstellung der Makefiles immer etwas mühsam, deshalb mach ich der Einfachheit halber Bash-Scripts.

Aber: Dank Perplexity sind Makefiles nun auch kein unlösbares Problem mehr.

Im Anhang das Makefile. Ich musste es ein wenig "tweaken" weil unter anderem "make clean" frecherweise gleich mein Sourcefile gelöscht hat.

Jetzt gehen die Kommandos ziemlich gut und der Pfad zur Toolchain im Arduino-Ordner ist schön separiert, sodass es jeder selbst leicht an seinen Pfad anpassen könnte.

Kommandos u.a. sind: make make clean make flash

Angehängte Dateien:
#7899475
Lesenswert?

Weiß eigentlich jemand, mit welcher Taktfrequenz die MCU im obigen Blinkbeispiel läuft? Ich habe das Assemblerfile auf einen Quarz-Losen CH32V003 geflasht und es blinkt recht schnell.
Eigentlich wäre es besser, als Referenz den Systemtimer statt der Delay-Loop zu verwenden.

Ich habe mal versucht, ein 1Hz Blinken mit dem obigen Assemblerprogramm zu erzeugen
Beitrag "Re: RISCV (CH32V003): GNU Assembler übersetzt "CALL" nicht korrekt (oder bin ich nur zu doof?)"

Dazu muss man x6 mit 1e6 laden:

1
 
2
Delay:                  li      x6,1000000

Tja, wie hoch ist jetzt FCPU?

#7899790
Lesenswert?

Christoph M. schrieb:

Weiß eigentlich jemand, mit welcher Taktfrequenz die MCU im obigen Blinkbeispiel läuft? Ich habe das Assemblerfile auf einen Quarz-Losen CH32V003 geflasht und es blinkt recht schnell. Eigentlich wäre es besser, als Referenz den Systemtimer statt der Delay-Loop zu verwenden.

Die Taktfrequenz sollte gemäß Datenblatt bei ca. 24 MHz liegen. Sie kommt vom "24 MHz HSI RC" und wird - ohne weiter geteilt zu werden - zu FCLK geleitet.

Ja, es wäre wesentlich besser einen Timer für die Erzeugung der Blink-Impulse zu verwenden. Ich habe dazu mal ein wenig mit Interrupts gespielt und das Umschalten des LED-Ausgangs in eine Interrupt-Routine verlegt. Das Hauptprogramm macht jetzt nur noch ein paar Initialisierungen und verweilt dann in einer Endlosschleife.

1
# Assemblieren:     riscv-none-elf-as -agl blink.s
2
# Linken:           riscv-none-elf-ld -o blink.o -Ttext 0 -e Reset_Entry a.out
3
# Anschauen:        riscv-none-elf-objdump -d -r blink.o
4
# Binary bauen:     riscv-none-elf-objcopy -O binary blink.o blink.bin
5

6

7
                        .file           "blinken.s"
8
                        .section        .text
9
                        .balign         4
10

11
                        .global         Reset_Entry
12

13
                        .list
14

15
                        .include        "CH32V003_reg1.asm"
16

17

18
                        .org 0x00000000
19
Reset_Entry:            j       Coldstart
20

21
                        .org 0x00000098
22
TIM2_Vector:            .dc.l   Interrupt_Routine
23

24
                        .org 0x00000100
25
Coldstart:              nop
26
                        li      t0,0x00000003                   # "Interrupt Vectortable Recognition Mode" und "Entry Address Mode" aktivieren
27
                        csrw    mtvec,t0
28
                        li      t0,0x00000008                   # Global Interrupts freigeben
29
                        csrw    mstatus,t0
30

31
Port_Setup_Clock:       li      x10,R32_RCC_APB2PCENR           # Takt für Port D einschalten
32
                        lw      x11,0(x10)
33
                        li      x7,(1<<5)
34
                        or      x11,x11,x7
35
                        sw      x11,0(x10)
36

37
Port_Setup_Direction:   li      x10,R32_GPIOD_CFGLR
38
                        lw      x11,0(x10)
39
                        li      x7,0xfff0ffff
40
                        and     x11,x11,x7
41
                        li      x7,0x00030000
42
                        or      x11,x11,x7
43
                        sw      x11,0(x10)
44

45
Port_Setup_LED_off:     li      x10,R32_GPIOD_OUTDR
46
                        lw      x11,0(x10)
47
                        ori     x11,x11,0x0010
48
                        sw      x11,0(x10)
49

50

51
Timer_Setup:            li      x10,R32_RCC_APB1PCENR           # Takt für Timer 2 einschalten
52
                        lw      x11,0(x10)
53
                        ori     x11,x11,0x00000001
54
                        sw      x11,0(x10)
55

56
                        li      x10,R16_TIM2_PSC                # Prescaler auf 7999 (0x1F3F) setzen: 24MHz/(3*8000) => Taktfrequenz=1000Hz
57
                        li      x11,7999
58
                        sw      x11,0(x10)
59

60
                        li      x10,R16_TIM2_BASE               # Reload-Value und Counter-Value setzen (bestimmt die Interrupt-Frequenz)
61
                        li      x11,250
62
                        sw      x11,44(x10)                     # speichern in R16_TIM2_ATRLR
63
                        sw      x11,36(x10)                     # speichern in R16_TIM2_CNT
64

65
                        li      x10,R16_TIM2_CTLR1              # Reload-Funktion einschalten und rückwärts zählen
66
                        lw      x11,0(x10)
67
                        ori     x11,x11,0x0090
68
                        sw      x11,0(x10)
69

70
                        li      x10,R16_TIM2_DMAINTENR          # Interrupts einrichten (1)
71
                        li      x11,0x0001
72
                        sw      x11,0(x10)
73
                        li      x10,R32_PFIC_IENR2              # Interrupts einrichten (2)
74
                        li      x11,0x00000040
75
                        sw      x11,0(x10)
76

77

78
                        li      x10,R16_TIM2_CTLR1              # Timer 2 aktivieren
79
                        lw      x11,0(x10)
80
                        ori     x11,x11,0x0001
81
                        sw      x11,0(x10)
82

83

84
Endless_Loop:           j       Endless_Loop
85

86

87

88
Interrupt_Routine:      li      x10,R32_GPIOD_OUTDR
89
                        lw      x11,0(x10)
90
                        xori    x11,x11,0x00000010
91
                        sw      x11,0(x10)
92

93
                        li      x10,R16_TIM2_INTFR
94
                        li      x11,0x00000000
95
                        sw      x11,0(x10)
96
                        mret

Die Adressen der einzelnen Register sind ausgelagert und finden sich in der beigefügten Include-Datei wieder.

Angehängte Dateien:
#7900085
Lesenswert?

Danke für das Code-Beispiel :-)

Thomas T. (knibbel) 29.06.2025 15:09

Die Taktfrequenz sollte gemäß Datenblatt bei ca. 24 MHz liegen. Sie kommt vom "24 MHz HSI RC" und wird - ohne weiter geteilt zu werden - zu FCLK geleitet.

Die Delay-Loop mit dem Zählerwert 1 Million ergibt die Verzögerung von 500ms, die für das 1Hz Blinken gebraucht werden. Bei 24MHz ( 24 Millionen Zyklen/Sekunde) wären das 12 Taktzyklen für die beiden Befehle addi und bne.
Dann wäre diese RISC-V Implementierung definitv schnarchlangsam.

1
Delay:                  li      x6,1000000
2

3
DelayLoop:              addi    x6,x6,-1
4
                        bne     x6,zero,DelayLoop
5

6
                        ret
#7900091
Lesenswert?

Ich versuche mehr über den Prozessor herauszufinden. Die Firma QingKeV scheint ein wenig von den normalen RISC-V Konventionen abzuweichen. Laut Prozessor-Manal sollte das Ding einen Hardwaremultiplizierer haben:

1
QingKeV2_Processor_Manual
2

3
RISC-V Kern:
4
RV32ECZmmul 
5

6
RV32:   32-bit architecture
7
E:         16 registers
8
C:         16-bit compressed instructions
9
XW:      16-bit compression instruction for self-extending byte and half-word operations
10
m:         hardware multiplication, i.e., Zmmul extension.
11

12
Zmmul extension
13
https://five-embeddev.com/riscv-user-isa-manual/Priv-v1.12/m.html  
14
XW: QingKeV vendor specific extension

Was mich vor allem interessiert, ist der Systick-Timer. Den verwendet man normalerweise in Echtzeitsystemen für alles Mögliche als Zeitbasis. Man kann ihn z.B. auch sehr gut für die 'delay' Funktion verwenden.

Hier ist die Initialisierung im Arduino-Core, was für die Assemblerimplementierung hilfreich sein könnte:
https://github.com/openwch/arduino_core_ch32/blob/main/cores/arduino/ch32/hw_config.c

Um im Reference-Manual fündig zu werden, muss man nach R32_STK suchen.

Angehängte Dateien:
#7900169
Lesenswert?

Manuel H. (Firma: Universität Tartu) (xenos1984) 08.06.2025 18:06

Wenn man ohne IDE arbeiten und den Kompiliervorgang etwas vereinfachen möchte, kann man auch PlatformIO Core benutzen. Da kann man über die Kommandozeile das Projekt erstellen, und statt einzeln die einzelnen Programme aufzurufen, tut es ein "pio run". https://github.com/Community-PIO-CH32V/platform-ch32v

Wie kriege ich Platform-IO dazu, nur mein Assembler-File zu verwenden?
Das Arduino-Framework kann ich selektieren und mit dem ini-File

1
[env:ch32v003f4p6_evt_r0]
2
platform = ch32v
3
board = ch32v003f4p6_evt_r0
4
framework = arduino
5
upload_protocol = wch-link

und einen rohen, auf einen TSSOP-Adapter aufgelöteten CH32V003F4P6 und sonst keine Bauteile außer der LED mit Vorwiderstand blinkt es auch schön:

1
#include <Arduino.h>
2

3
#define LED PD4 // TSSOP 20 Board
4

5
void setup() {
6
  pinMode(LED, OUTPUT);
7
}
8

9
void loop() {
10
  digitalWrite(LED, HIGH);   
11
  delay(1000);                    
12
  digitalWrite(LED, LOW);    
13
  delay(1000);                    
14
}

Ich will aber nicht das dicke Framework, sonder das rohe blink.s

#7901249
Lesenswert?

Bei diesem Blink-Beispiel nutze ich den systick-Timer als Zeitbasis. Die LED blinkt zu langsam, woraus ich schließe, dass der CH32V003 nicht mit 24Mhz, sondern mit 8Mhz startet und läuft.

Thomas T. (knibbel) 29.06.2025 15:09

Die Taktfrequenz sollte gemäß Datenblatt bei ca. 24 MHz liegen. Sie kommt vom "24 MHz HSI RC" und wird - ohne weiter geteilt zu werden - zu FCLK geleitet.

Das wird dann wohl nicht stimmen, 8Mhz ist wohl eher richtig.

Angehängte Dateien:

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren