DEV Community

Masatoshi Nishiguchi
Masatoshi Nishiguchi

Posted on

Running Elixir on a 64KB Microcontroller with AtomVM and UIAPduino

日本語版

Introduction

Recently, I attended SWEST28, a summer workshop focused on embedded systems technologies.

https://swest.toppers.jp/phx/event

At the event, I joined a hands-on embedded development workshop using UIAPduino, led by Yuuki Umeta, the developer of UIAP.

During the workshop, we used the UIAPduino Pro Micro CH32V003. I also received a UIAPduino Pro Micro CH32V006, which is still under development.

After SWEST28, I first built a small game using the CH32V003 and shared it with the UIAP and Elixir communities, as well as other SWEST participants.

During one of our casual conversations, someone said:

It would be interesting if AtomVM could run on UIAPduino.

I use Elixir and AtomVM regularly, but I am not deeply familiar with AtomVM internals or the low-level details of the CH32V006.

Still, I was curious, so I decided to experiment with it while asking ChatGPT and Codex to help with research and implementation.

After some trial and error, I was able to run Elixir code on the CH32V006 and blink an LED.

It is still experimental, but it worked.


What is UIAP?

UIAPduino is a small and inexpensive Arduino-compatible development board.

https://www.uiap.jp/uiapduino/

For comparison, here are two UIAPduino boards alongside the XIAO ESP32S3.

Board Flash SRAM
UIAPduino Pro Micro CH32V003 16KB 2KB
UIAPduino Pro Micro CH32V006 64KB 8KB
XIAO ESP32S3 8MB 512KB

Looking at the numbers makes it clear that the CH32V006 is still a very small microcontroller.

However, it has more room than the CH32V003.

That made me wonder whether AtomVM might fit if we reduced its functionality to the bare minimum.


What is AtomVM?

AtomVM is a lightweight virtual machine designed to run Elixir and Erlang code on microcontrollers.

It can run in embedded environments such as ESP32 and is designed to work with limited Flash and RAM.

https://atomvm.org/


Making AtomVM Even Smaller for the CH32V006

The CH32V006 has only 64KB of Flash and 8KB of SRAM.

That is a very constrained environment for running AtomVM.

Instead of trying to run a normal AtomVM build unchanged, I created a configuration where the available functionality is reduced to the minimum required for this experiment.

The initial goal was simply:

Boot AtomVM
↓
Run Elixir code
↓
Blink an LED using GPIO
Enter fullscreen mode Exit fullscreen mode

For this reason, the AtomVM build used in this experiment has many features disabled.


Developing Together with LLMs

Because I am not deeply familiar with AtomVM internals or the CH32V006, I worked with ChatGPT and Codex while investigating and implementing the port.

I repeatedly went through:

Research
↓
Implementation
↓
Build
↓
Measurement
↓
Hardware testing
Enter fullscreen mode Exit fullscreen mode

Along the way, I also kept notes and ADRs describing what I tried and why I made certain decisions.

By checking each step individually, I was able to gradually make progress in an area that was initially unfamiliar to me.


Current Results

The current implementation is available on this working branch:

https://github.com/mnishiguchi/AtomVM/tree/feature/ch32v006/src/platforms/ch32v006

So far, I have confirmed the following on real hardware:

Boot AtomVM
↓
Run Elixir code
↓
Control GPIO
↓
Blink an LED
Enter fullscreen mode Exit fullscreen mode

It is still experimental, but I have confirmed that a minimal AtomVM configuration can run on the CH32V006.


Blinking an LED

From here, I will walk through how I ran AtomVM on the CH32V006 and blinked an LED using Elixir.

The following steps assume a Linux environment.

1. Prepare the Development Environment

The main tools I used were:

Erlang/OTP 28
Elixir 1.19
GNU Make
RISC-V GCC
RISC-V binutils
newlib
Enter fullscreen mode Exit fullscreen mode

On my machine, I checked them with:

erl -noshell \
  -eval 'io:format("OTP ~s~n", [erlang:system_info(otp_release)]), halt().'

elixir --version
make --version
riscv64-unknown-elf-gcc --version
riscv64-unknown-elf-readelf --version
Enter fullscreen mode Exit fullscreen mode

2. Prepare ch32fun and minichlink

For the CH32V006 platform layer, I use ch32fun.

For this experiment, I pinned the revision that I had tested successfully.

git clone https://github.com/cnlohr/ch32fun.git /tmp/ch32fun

git -C /tmp/ch32fun checkout \
  618bba58c615ed29dc99e6ea92d869c914b6a8c0
Enter fullscreen mode Exit fullscreen mode

I also built minichlink, which is used for flashing the CH32V006 and viewing terminal output.

make -C /tmp/ch32fun/minichlink
Enter fullscreen mode Exit fullscreen mode

After connecting the UIAPduino over USB, it appeared like this on my machine:

lsusb | grep -i 1209:b806
Enter fullscreen mode Exit fullscreen mode
ID 1209:b806 Generic CNLohr RV003 RVSWDIO Programmer Customized for UIAPduino
Enter fullscreen mode Exit fullscreen mode

3. Build AtomVM First

Before running Elixir, I first built the minimal blink example included in the repository.

Run this from the root of the AtomVM repository:

make -C src/platforms/ch32v006 \
  CH32FUN=/tmp/ch32fun/ch32fun \
  clean image
Enter fullscreen mode Exit fullscreen mode

The firmware build roughly follows this flow:

Erlang
↓
BEAM
↓
Ahead-of-time compilation for RISC-V
↓
Link with AtomVM
↓
Firmware
Enter fullscreen mode Exit fullscreen mode

On my machine, the build produced:

Validated build/ch32v006_minimal.beam: 1096 bytes of rv32ec_zmmul code
Enter fullscreen mode Exit fullscreen mode

The Flash usage was:

Verified atomvm_ch32v006.bin: 56200 / 63488 bytes (7288 bytes free)
Enter fullscreen mode Exit fullscreen mode

Only about 62KB is available to the firmware, so this is already quite tight.

4. Flash the CH32V006

Next, flash the generated firmware to the board.

make -C src/platforms/ch32v006 \
  CH32FUN=/tmp/ch32fun/ch32fun \
  MINICHLINK=/tmp/ch32fun/minichlink \
  FLASH_IMAGE=build/images/AtomVM-uiapduino-pro-micro-ch32v006.bin \
  flash-uiap
Enter fullscreen mode Exit fullscreen mode

When the write succeeds, the output includes:

Detected CH32V006
Writing image

Image written.
Enter fullscreen mode Exit fullscreen mode

At this point, the orange LED on the board repeats this pattern:

100ms ON
900ms OFF
Enter fullscreen mode Exit fullscreen mode

This confirmed that AtomVM itself was running on the physical CH32V006.

5. Create an Elixir Blink Application

Next, I created an actual Elixir application.

Create a Mix project outside the AtomVM repository.

mix new uiap_blink --module UIAPBlink
cd uiap_blink
Enter fullscreen mode Exit fullscreen mode

Add ExAtomVM to mix.exs.

def project do
  [
    app: :uiap_blink,
    version: "0.1.0",
    elixir: "~> 1.19",
    deps: deps(),
    atomvm: [start: UIAPBlink]
  ]
end

defp deps do
  [
    {:exatomvm,
     git: "https://github.com/atomvm/ExAtomVM",
     branch: "main",
     runtime: false}
  ]
end
Enter fullscreen mode Exit fullscreen mode

I used the following lib/uiap_blink.ex.

defmodule UIAPBlink do
  @compile {:no_warn_undefined, [:gpio, :ch32v006]}

  @led 35

  def start do
    :gpio.init(@led)
    :gpio.set_pin_mode(@led, :output)
    loop(:high)
  end

  defp loop(level) do
    :gpio.digital_write(@led, level)
    :ch32v006.delay_ms(500)
    loop(toggle(level))
  end

  defp toggle(:high), do: :low
  defp toggle(:low), do: :high
end
Enter fullscreen mode Exit fullscreen mode

The logic is simple.

Turn the LED on
↓
Wait 500ms
↓
Turn the LED off
↓
Wait 500ms
↓
Repeat
Enter fullscreen mode Exit fullscreen mode

Compile the application.

mix deps.get
mix compile
Enter fullscreen mode Exit fullscreen mode

This creates:

_build/dev/lib/uiap_blink/ebin/Elixir.UIAPBlink.beam
Enter fullscreen mode Exit fullscreen mode

6. Build Firmware Containing the Elixir Application

For this CH32V006 port, I do not use mix atomvm.packbeam to create an .avm archive.

Instead, the BEAM produced by mix compile is embedded directly into the AtomVM firmware.

Elixir
↓
BEAM
↓
Ahead-of-time compilation for RISC-V
↓
Link with AtomVM
↓
Firmware
Enter fullscreen mode Exit fullscreen mode

Return to the AtomVM repository and build the firmware while specifying the BEAM file.

make -C src/platforms/ch32v006 \
  CH32FUN=/tmp/ch32fun/ch32fun \
  START_BEAM_INPUT=/path/to/uiap_blink/_build/dev/lib/uiap_blink/ebin/Elixir.UIAPBlink.beam \
  IMAGE_BASENAME=AtomVM-uiapduino-pro-micro-ch32v006-uiap-blink \
  image
Enter fullscreen mode Exit fullscreen mode

On my machine, this produced:

Validated build/Elixir.UIAPBlink.beam: 2824 bytes of rv32ec_zmmul code
Enter fullscreen mode Exit fullscreen mode

The complete firmware size was:

Verified atomvm_ch32v006.bin: 58672 / 63488 bytes (4816 bytes free)
Enter fullscreen mode Exit fullscreen mode

Here is the comparison.

Application Flash usage
Minimal Erlang blink 56,200 bytes
Elixir blink 58,672 bytes

The Elixir version leaves only 4,816 bytes free.

It is a good reminder of just how small this environment is.

7. Flash the Elixir Firmware

Now flash the firmware containing the Elixir application.

make -C src/platforms/ch32v006 \
  CH32FUN=/tmp/ch32fun/ch32fun \
  MINICHLINK=/tmp/ch32fun/minichlink \
  FLASH_IMAGE=build/images/AtomVM-uiapduino-pro-micro-ch32v006-uiap-blink.bin \
  flash-uiap
Enter fullscreen mode Exit fullscreen mode

After flashing, the LED pattern changed from:

100ms ON
900ms OFF
Enter fullscreen mode Exit fullscreen mode

to:

500ms ON
500ms OFF
Enter fullscreen mode Exit fullscreen mode

This confirmed that my Elixir code was actually running on the CH32V006.

8. Confirm That AtomVM Boots

Finally, I checked the AtomVM boot message over SWIO.

make -C src/platforms/ch32v006 \
  CH32FUN=/tmp/ch32fun/ch32fun \
  MINICHLINK=/tmp/ch32fun/minichlink \
  monitor-uiap
Enter fullscreen mode Exit fullscreen mode

The board printed:

Terminal started

AVM CH32V006 boot
Enter fullscreen mode Exit fullscreen mode

At this point, the complete path had been verified on real hardware:

Elixir
↓
BEAM
↓
AtomVM
↓
Ahead-of-time compilation for RISC-V
↓
CH32V006
↓
GPIO
↓
Blinking LED
Enter fullscreen mode Exit fullscreen mode

What I Learned

This experiment confirmed that even on a very small microcontroller such as the CH32V006, Elixir code can run if AtomVM is reduced to the minimum functionality required.

For me, the most interesting result was confirming this entire path on real hardware:

Elixir
↓
BEAM
↓
AtomVM
↓
RISC-V
↓
CH32V006
Enter fullscreen mode Exit fullscreen mode

Of course, this is not yet the same experience as using a normal AtomVM environment.

Still, running AtomVM at all within only 64KB of Flash and 8KB of SRAM shows that there is room for further exploration.


Current Limitations

The CH32V006 AtomVM port is still experimental.

At the moment, there are several important limitations:

  • Very little Flash or RAM remains available
  • Many AtomVM features have been disabled
  • The startup BEAM is embedded directly into the firmware
  • Many features normally available in AtomVM are not yet supported
  • The entire firmware must be rebuilt whenever the application changes

For this reason, it cannot yet be used in the same way as AtomVM on ESP32.

At the moment, I am mainly exploring how small AtomVM can become while still remaining useful on the CH32V006.


What's Next

Next, I would like to gradually test more basic hardware features beyond blinking an LED.

For example:

  • GPIO input
  • Buttons
  • UART
  • ADC
  • I2C
  • SPI

I am also interested in seeing how much Elixir-style code can realistically fit within such limited Flash and RAM.

Rather than thinking of the CH32V006 as simply a smaller ESP32, I would like to explore AtomVM use cases that make sense specifically because the environment is this small.


Conclusion

In this experiment, I was able to run a minimal AtomVM configuration on the UIAPduino Pro Micro CH32V006 and confirm an Elixir-based LED blink on real hardware.

I am not an expert in AtomVM internals or the CH32V006, but by working with LLMs and going through research, implementation, measurement, and hardware verification one step at a time, I was able to get this far.

The experiment started from encountering UIAPduino at SWEST28 and from a casual conversation afterward in the community.

That small idea eventually led to running Elixir on the CH32V006.

It is still experimental, but now that I know AtomVM can run on the CH32V006, I would like to keep exploring what is possible in this very small environment.

Top comments (0)