Arduino core for STC8H8K64U — runs a RISC-V emulator (rv51) on the native 8051, giving you full C++ Arduino compatibility with 64 KB flash and 8 KB RAM over USB.
- Arduino IDE → File → Preferences
- Add to Additional Boards Manager URLs:
https://raw.githubusercontent.com/thevien257/STC_Arduino_Core/main/package_stc8051_index.json - Tools → Board → Boards Manager → search "STC" → Install
- Arduino IDE 2.x (or 1.8.x)
- Python 3.6+ with
hidapi(pip install hidapi) - Linux/Windows
| Board | MCU | Flash | RAM | Upload | Compiler |
|---|---|---|---|---|---|
| STC8H8K64U-USB | STC8H8K64U | 64 KB | 8 KB XDATA | USB HID (stc8usb.py) |
riscv64-unknown-elf-gcc (rv32em) via rv51 |
The chip ships blank — no firmware to auto-reset. You must enter the USB bootloader manually once:
- Unplug the board from USB
- Press and hold the BOOT button on the board
- Plug the USB cable in (while still holding BOOT)
- Release the BOOT button
- Click Upload in Arduino IDE
After the first flash, the firmware includes a USB reset handler. The upload tool sends a "BOOT" command over USB HID/CDC which triggers IAP_CONTR = 0x60 (software reset to bootloader). No button press needed — just click Upload.
If auto-reset fails for any reason, repeat the manual boot mode procedure above.
Flashing is handled by tools/stc8usb/stc8usb.py, a Python script that communicates with the STC8H USB HID bootloader. It is called automatically by Arduino IDE on upload. Features:
- USB HID protocol (no serial adapter needed)
- Auto-reset via HID or CDC
"BOOT"command - IRC frequency trimming to match the selected clock speed
- Supports STC8H8K16U/32U/48U/60U/64U
Requires hidapi: pip install hidapi
Select via Tools → Clock Speed. Default is 24 MHz.
Available: 11.0592, 12, 16, 20, 22.1184, 24, 30, 35, 40 MHz (40 MHz is risky).
The IRC oscillator is auto-trimmed to the selected frequency during upload.
Pins are named P<port>_<bit> and numbered as port × 8 + bit:
| Port | Pins | Arduino # | Notes |
|---|---|---|---|
| P0 | P0_0–P0_7 |
0–7 | |
| P1 | P1_0–P1_7 |
8–15 | ADC (A0–A1, A3–A7), I2C (SDA=P1.4, SCL=P1.5) |
| P2 | P2_0–P2_7 |
16–23 | |
| P3 | P3_0–P3_7 |
24–31 | USB (P3.0/P3.1), SPI (P3.2–P3.5), LED_BUILTIN (P3.4), UART1 |
| P4 | P4_0–P4_7 |
32–39 | |
| P5 | P5_0–P5_5 |
40–45 | ADC (A2=P5.4) |
| P6 | P6_0–P6_7 |
48–55 | |
| P7 | P7_0–P7_7 |
56–63 |
| Function | Pin | Arduino # |
|---|---|---|
LED_BUILTIN |
P3.4 | 28 |
A0–A1 |
P1.0–P1.1 | 8–9 |
A2 |
P5.4 | 44 |
A3–A7 |
P1.3–P1.7 | 11–15 |
SS |
P3.5 | 29 |
MOSI |
P3.4 | 28 |
MISO |
P3.3 | 27 |
SCK |
P3.2 | 26 |
SDA |
P1.4 | 12 |
SCL |
P1.5 | 13 |
| USB D- / D+ | P3.0 / P3.1 | 24 / 25 |
Serial (CDC) |
USB | — |
Serial1 (UART1) |
P3.7 TX, P3.6 RX (default) | 31, 30 |
Serial2 (UART2) |
P1.1 TX, P1.0 RX (default) | 9, 8 |
Note: SPI uses port group 3 (
SPI_S[1:0]=11). MOSI (P3.4) shares the same pin asLED_BUILTIN.
SPI supports 4 pin groups via SPI_S[1:0] in P_SW1 (selected in SPI.begin()):
| SPI_S | SS | MOSI | MISO | SCLK | Notes |
|---|---|---|---|---|---|
| 00 | P1.2/P5.4 | P1.3 | P1.4 | P1.5 | |
| 01 | P2.2 | P2.3 | P2.4 | P2.5 | |
| 10 | P5.4 | P4.0 | P4.1 | P4.3 | |
| 11 | P3.5 | P3.4 | P3.3 | P3.2 | Default |
I2C supports 4 pin groups via I2C_S[1:0] in P_SW2:
| I2C_S | SCL | SDA | Notes |
|---|---|---|---|
| 00 | P1.5 | P1.4 | Default |
| 01 | P2.5 | P2.4 | |
| 10 | P7.7 | P7.6 | |
| 11 | P3.2 | P3.3 | Conflicts with SPI SCK/MISO |
Serial1 and Serial2 support runtime pin selection via begin():
| UART | Option | RxD | TxD | Notes |
|---|---|---|---|---|
| Serial1 | UART1_PINS_P36_P37 |
P3.6 | P3.7 | Default |
| Serial1 | UART1_PINS_P30_P31 |
P3.0 | P3.1 | |
| Serial1 | UART1_PINS_P16_P17 |
P1.6 | P1.7 | |
| Serial1 | UART1_PINS_P43_P44 |
P4.3 | P4.4 | |
| Serial2 | UART2_PINS_P10_P11 |
P1.0 | P1.1 | Default |
| Serial2 | UART2_PINS_P46_P47 |
P4.6 | P4.7 |
Example: Serial1.begin(115200, SERIAL_8N1, UART1_PINS_P16_P17);
For library compatibility, standard Arduino Uno pin names are also mapped:
D0–D7→P3_0–P3_7D8–D13→P1_0–P1_5D14–D19→P0_0–P0_5
The STC8H8K64U board uses rv51, a RISC-V RV32EM emulator written in 8051 assembly (~6 KB). Your Arduino sketch is compiled with riscv64-unknown-elf-g++ and runs on top of rv51.
What works: Full C++, classes, templates, String, Serial, Wire, SPI, EEPROM, SoftwareSerial, tone(), millis(), micros(), digitalRead(), digitalWrite(), analogRead(), pulseIn(), attachInterrupt(), and most Arduino libraries.
Native ISR acceleration: Time-critical functions (tone(), SoftwareSerial, millis/micros counters) run as native 8051 Timer ISRs — zero emulator overhead. They communicate with RISC-V code via shared XDATA memory.
Performance: Emulated code runs ~100–1000× slower than native 8051, but native ISRs (timers, USB, serial) run at full 24 MHz speed.
| Library | Description |
|---|---|
| EEPROM | IAP-based EEPROM emulation |
| Wire | I2C master/slave (100/400 kHz) |
| SPI | Hardware SPI master (SS=P3.5, MOSI=P3.4, MISO=P3.3, SCK=P3.2) |
| SoftwareSerial | Bit-banged serial on any pin via native Timer4 ISR (up to 115200 baud) |
| TinyI2CMaster | Lightweight I2C master |
| FreeRTOS | RTOS with task/queue/semaphore support |
| STC8 Examples | Blink, Serial, PWM, Tone, EEPROM, Interrupt, SoftwareSerial, etc. |
- GitHub: https://github.com/thevien257/STC_Arduino_Core
- rv51 emulator:
cores/stc8/rv51/— based on cyrozap/rv51 - STC8H datasheet: https://www.stcmicro.com/stc/stc8h8k64u.html
- Issues: https://github.com/thevien257/STC_Arduino_Core/issues
- Clone the repo into your Arduino packages directory:
~/.arduino15/packages/stc8051/hardware/stc8/1.0.1/ - Install the RISC-V toolchain:
riscv64-unknown-elf-gcc,riscv64-unknown-elf-g++ - Install SDCC (for building rv51 emulator):
sdcc - Install Python deps:
pip install hidapi
├── boards.txt # Board definitions (FQBN, clock, upload tool)
├── platform.txt # Compile/link/upload recipes
├── package_stc8051_index.json # Arduino Board Manager package index
│
├── cores/stc8/ # Arduino core implementation
│ ├── Arduino.h # Main include (pins, types, SFR bridge)
│ ├── main.c # Arduino main() → setup() + loop()
│ ├── wiring.c # delay(), init()
│ ├── wiring_digital.c # pinMode(), digitalWrite(), digitalRead()
│ ├── wiring_analog.c # analogRead(), analogWrite()
│ ├── wiring_pulse.c # pulseIn(), pulseInLong()
│ ├── tone.c # tone()/noTone() — writes XDATA, native Timer3 ISR does the work
│ ├── interrupt.c # attachInterrupt()/detachInterrupt()
│ ├── eeprom_iap.c # EEPROM via STC8 IAP flash
│ ├── HardwareSerial.cpp # Serial (CDC USB), Serial1 (UART1), Serial2 (UART2)
│ ├── Print.cpp / Stream.cpp # Arduino Print/Stream base classes
│ ├── WString.cpp # Arduino String class
│ ├── USB_Core.c / USB_CDC.c # USB HID + CDC driver (auto-reset handler)
│ ├── avr/ # AVR compatibility shims (io.h, interrupt.h, pgmspace.h, etc.)
│ ├── avr_compat.cpp # AVR register emulation for library compatibility
│ ├── avr_timer_shim.c # RAM-backed AVR timer registers + weak ISR stubs
│ ├── pio_engine.h # PIO bytecode engine (NeoPixel, DHT11, etc.)
│ │
│ └── rv51/ # RISC-V emulator (8051 assembly)
│ ├── main.S # rv51 emulator source (~5300 lines of 8051 asm)
│ ├── linker.ld # RISC-V linker script (RAM/ROM layout, XDATA reservation)
│ ├── init.asm.rv51 # RISC-V CRT0 startup (_start, BSS clear, call main)
│ ├── Makefile # Build rv51.bin from main.S via SDCC
│ └── rv51.bin # Pre-built emulator binary (~6 KB)
│
├── variants/stc8h8k64u/
│ ├── pins_arduino.h # Pin mapping (Px_y defines, analog pins, SPI/I2C)
│ ├── variant.h # Variant-specific config
│ └── drivers/
│ ├── FwLib_STC8/include/ # STC8 FwLib headers (SFR macros, timer, EXTI, etc.)
│ ├── src/ # FwLib driver sources (timer.c, fw_*.c)
│ └── inc/ # Additional driver headers
│
├── libraries/
│ ├── EEPROM/ # EEPROM library
│ ├── Wire/ # I2C (Wire) library
│ ├── SPI/ # SPI library
│ ├── SoftwareSerial/ # Bit-banged serial via native Timer4 ISR
│ ├── TinyI2CMaster/ # Lightweight I2C
│ ├── FreeRTOS/ # FreeRTOS port
│ └── STC8 Examples/ # Built-in example sketches
│
└── tools/
├── wrapper/
│ ├── sdcc.sh # Compiler wrapper: intercepts SDCC calls → riscv64-unknown-elf-g++
│ ├── sdcc-link.sh # Linker wrapper: links RISC-V app, cats rv51.bin + app.bin
│ ├── sdar.sh # Archiver wrapper: SDCC ar → riscv64-unknown-elf-ar
│ └── packihx.sh # Intel HEX converter wrapper
├── stc8usb/
│ └── stc8usb.py # USB HID flash tool
└── libstdcxx/include/ # Minimal C++ standard library headers for rv51
Understanding the build flow is critical for contributing:
Arduino sketch (.ino)
│
▼
sdcc.sh wrapper ─── intercepts Arduino IDE's SDCC calls
│
▼
riscv64-unknown-elf-g++ ─── compiles as RISC-V RV32EM
│ ├── -march=rv32em_zicsr -mabi=ilp32e
│ ├── -Os -flto -ffunction-sections
│ └── -nostdlib -nostartfiles
▼
sdcc-link.sh wrapper
│
├── Links RISC-V objects with linker.ld → app.elf → app.bin
│
├── Concatenates: rv51.bin (emulator) + app.bin (sketch)
│
▼
firmware.hex ─── uploaded via stc8usb.py over USB HID
Code runs in two distinct contexts — understanding this is key:
| Native 8051 (24 MHz) | Emulated RISC-V (~24 KHz effective) | |
|---|---|---|
| Language | 8051 assembly (main.S) | C/C++ (riscv64-unknown-elf-g++) |
| Runs | ISRs, USB, timers | Arduino sketch, libraries |
| SFR access | Direct (mov P1, a) |
Via bridge (0xC0000000 + sfr_addr) |
| XDATA access | Direct (movx @dptr, a) |
Memory-mapped (0x00000000 + addr) |
| Speed | Full 24 MHz | ~100–1000× slower |
| Use for | Timing-critical ISRs | Application logic |
Ecalls let RISC-V code invoke native 8051 functions at full speed. Current ecalls:
| # | Function | Input | Output |
|---|---|---|---|
| 1 | micros() |
— | a0 = microseconds |
| 2 | millis() |
— | a0 = milliseconds |
| 3 | digitalRead(pin) |
a0 = pin | a0 = 0 or 1 |
| 4 | digitalWrite(pin, val) |
a0 = pin, a1 = val | — |
| 93 | exit(code) |
a0 = exit code | — |
To add a new ecall (e.g., #5):
- main.S — Add dispatch entry after
ecall_not_4:cjne a, #5, ecall_not_5 ljmp syscall_your_function ecall_not_5:
- main.S — Implement the handler before
emulator_sizelabel. Read inputs from RISC-V registers (rv_x0 + 4*10= a0,rv_x0 + 4*11= a1). Write results back to a0. - C wrapper — Use inline assembly to trigger:
register uint32_t a0 asm("a0") = arg; register uint32_t a5 asm("a5") = 5; // ecall number asm volatile("ecall" : "+r"(a0) : "r"(a5));
The pattern used by tone() (Timer3) and SoftwareSerial (Timer4):
- Reserve XDATA — Add addresses in
linker.ldcomment, shrink RAM LENGTH - Write ISR in main.S — ISR reads config from shared XDATA, does the work,
reti - C side — Write config to XDATA via
volatile uint8_t*pointers, configure timer via FwLib macros, enable interrupt. The ISR runs natively at 24 MHz.
Reserved area at top of 8 KB XDATA (application RAM ends at 0x1FAF):
0x1FB0-0x1FBF SoftwareSerial TX ring buffer (16 bytes)
0x1FC0-0x1FCF SoftwareSerial control (port, mask, state, mode, head/tail)
0x1FD0-0x1FDF SoftwareSerial RX ring buffer (16 bytes)
0x1FE0-0x1FE7 tone() state (port, mask, mode, toggle counter)
0x1FE8-0x1FEF (reserved for future use)
0x1FF0-0x1FFF millis/micros counters + Timer2 config
Build a sketch from the command line:
arduino-cli compile --fqbn stc8051:stc8:stc8h8k64u:clock=24MHz path/to/sketch.inoRun the library test suite:
cd tools && bash test_all_libraries.shRebuild the rv51 emulator after modifying main.S:
cd cores/stc8/rv51 && make clean && make| If you want to... | Edit |
|---|---|
| Add an Arduino API function | cores/stc8/wiring_*.c or cores/stc8/*.cpp |
| Add a native ecall | cores/stc8/rv51/main.S (ecall dispatch + handler) |
| Add a native timer ISR | cores/stc8/rv51/main.S + cores/stc8/rv51/linker.ld |
| Change pin mappings | variants/stc8h8k64u/pins_arduino.h |
| Add an SFR definition | variants/stc8h8k64u/drivers/FwLib_STC8/include/fw_reg_stc8h.h |
| Add a bundled library | libraries/<LibName>/src/ + library.properties |
| Change compile flags | tools/wrapper/sdcc.sh (compiler) or tools/wrapper/sdcc-link.sh (linker) |
| Change upload behavior | tools/stc8usb/stc8usb.py |
| Add AVR library compat | cores/stc8/avr/ shim headers |
| Fix USB CDC/HID | cores/stc8/USB_Core.c / cores/stc8/USB_CDC.c |
- Pin names: Always use
P<port>_<bit>(e.g.,P1_0), never raw numbers in user-facing code - SFR access from RISC-V: Use
SFR_REG(addr)macro →*(volatile uint8_t*)(0xC0000000 + addr) - XSFR access: Use FwLib
SFRX_ON()/SFRX_OFF()wrappers (sets P_SW2 EAXFR bit) - Timer ISR XDATA: Document addresses in
linker.ldcomment block, use#definenamed constants - FwLib macros: Prefer
TIM_Timer4_SetRunState()over raw SFR bit manipulation - Test in loop(): Always put test output in
loop()withdelay(1000)— USB CDC may not be ready duringsetup()