Skip to content

nonlinearbranch/STC_Arduino_Core_Cpp

 
 

Repository files navigation

STC8 Arduino Core

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.

Installation

  1. Arduino IDE → File → Preferences
  2. Add to Additional Boards Manager URLs:
    https://raw.githubusercontent.com/thevien257/STC_Arduino_Core/main/package_stc8051_index.json
    
  3. Tools → Board → Boards Manager → search "STC" → Install

Requirements

  • Arduino IDE 2.x (or 1.8.x)
  • Python 3.6+ with hidapi (pip install hidapi)
  • Linux/Windows

Supported Boards

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

Flashing

First-Time Flash (Boot Mode)

The chip ships blank — no firmware to auto-reset. You must enter the USB bootloader manually once:

  1. Unplug the board from USB
  2. Press and hold the BOOT button on the board
  3. Plug the USB cable in (while still holding BOOT)
  4. Release the BOOT button
  5. Click Upload in Arduino IDE

Subsequent Uploads (Auto-Reset)

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.

Upload Tool — stc8usb.py

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

Clock Speed

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.

Pin Naming Convention

Pins are named P<port>_<bit> and numbered as port × 8 + bit:

Port Pins Arduino # Notes
P0 P0_0P0_7 0–7
P1 P1_0P1_7 8–15 ADC (A0–A1, A3–A7), I2C (SDA=P1.4, SCL=P1.5)
P2 P2_0P2_7 16–23
P3 P3_0P3_7 24–31 USB (P3.0/P3.1), SPI (P3.2–P3.5), LED_BUILTIN (P3.4), UART1
P4 P4_0P4_7 32–39
P5 P5_0P5_5 40–45 ADC (A2=P5.4)
P6 P6_0P6_7 48–55
P7 P7_0P7_7 56–63

Special Pin Mappings

Function Pin Arduino #
LED_BUILTIN P3.4 28
A0A1 P1.0–P1.1 8–9
A2 P5.4 44
A3A7 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 as LED_BUILTIN.

SPI Pin Alternatives

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 Pin Alternatives

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

Serial Pin Alternatives

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);

AVR Uno-Compatible Aliases

For library compatibility, standard Arduino Uno pin names are also mapped:

  • D0D7P3_0P3_7
  • D8D13P1_0P1_5
  • D14D19P0_0P0_5

rv51 — The RISC-V Emulator

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.

Bundled Libraries

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.

Resources


Contributing

Development Setup

  1. Clone the repo into your Arduino packages directory:
    ~/.arduino15/packages/stc8051/hardware/stc8/1.0.1/
    
  2. Install the RISC-V toolchain: riscv64-unknown-elf-gcc, riscv64-unknown-elf-g++
  3. Install SDCC (for building rv51 emulator): sdcc
  4. Install Python deps: pip install hidapi

Project Structure

├── 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

Build Pipeline

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

Architecture: Two Worlds

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

How to Add a Native Ecall

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):

  1. main.S — Add dispatch entry after ecall_not_4:
    cjne a, #5, ecall_not_5
    ljmp syscall_your_function
    ecall_not_5:
  2. main.S — Implement the handler before emulator_size label. Read inputs from RISC-V registers (rv_x0 + 4*10 = a0, rv_x0 + 4*11 = a1). Write results back to a0.
  3. 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));

How to Add a Native Timer ISR

The pattern used by tone() (Timer3) and SoftwareSerial (Timer4):

  1. Reserve XDATA — Add addresses in linker.ld comment, shrink RAM LENGTH
  2. Write ISR in main.S — ISR reads config from shared XDATA, does the work, reti
  3. 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.

XDATA Shared Memory Map

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

Testing

Build a sketch from the command line:

arduino-cli compile --fqbn stc8051:stc8:stc8h8k64u:clock=24MHz path/to/sketch.ino

Run the library test suite:

cd tools && bash test_all_libraries.sh

Rebuild the rv51 emulator after modifying main.S:

cd cores/stc8/rv51 && make clean && make

Key Files to Know

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

Conventions

  • 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.ld comment block, use #define named constants
  • FwLib macros: Prefer TIM_Timer4_SetRunState() over raw SFR bit manipulation
  • Test in loop(): Always put test output in loop() with delay(1000) — USB CDC may not be ready during setup()

About

Arduino core for STC8 microcontrollers

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • C++ 86.6%
  • C 12.2%
  • Assembly 0.7%
  • Python 0.4%
  • Shell 0.1%
  • Batchfile 0.0%