ENG | Exploring the Milk V Duo S
Overview of the Milk V Duo S, a minimal RISC-V SBC: setup, features, challenges, and practical tips.
MilkV Duo S and 10000mAh powerbank for scale
I have to confess that the only reason I bought this was curiosity. As an enthusiast of single-board computers and emerging architectures, the Milk V Duo S presented an opportunity to explore very simple RISC-V computer and it’s limitations.
Introduction
The RISC-V architecture is gaining lot of attention today, because it’s supposed to be more open than ARM or x86/amd64 architectures. It sounds nice, but manufacturing microprocessor is not for everyone. However it everyone needs to mention it. While RISC-V is often touted as simple, it’s important to note that the architecture includes numerous extensions, including floating-point and vector instructions, adding complexity to its implementation and use.
The Milk-V Duo S is a unique single-board computer that serves primarily as a RISC-V development platform. Unlike more mainstream options such as Raspberry Pi or Radxa Rock, the Milk-V Duo S offers a minimalist approach to computing. It runs a stripped-down version of Linux, comprising only the kernel, busybox for basic utilities, dropbear as a lightweight SSH replacement, DHCP client and server, and wpa_supplicant for Wi-Fi connectivity. Notably, it includes strace and gdb, debugging and low-level development viable, although it does not have any compiler.
The Milk-V Duo S features a multi-core design:
- A “big” core that can be configured as either RISC-V 64-bit or ARM (though ARM support seems non-existent at present)
- A “small” core running FreeRTOS
- An 8051 core for additional low-level control
- An accelerator for computer vision or AI applications
This diverse set of computational resources provides a rich platform for exploring heterogeneous computing, real-time applications, and hardware-software co-design —though these features can be challenging to utilize effectively. (Be realistic: You have many options to play with and almost nothing works)
https://milkv.io/docs/duo/overview https://github.com/milkv-duo/duo-buildroot-sdk/releases/
Quick connection guide
Windows
- Copy image such as
milkv-duos-sd-v1.1.1-2024-0528.img.zip
from github to SD card - as of December 2024, there’s new repo - Connect USB-C to anything that can provide power
- Connect it by ethernet into your LAN
- Use whatever IP Scanner to find it
- ssh to it using
root
andmilkv
password.
Technically, solution described for Linux works, but it requires to download a driver.
Linux
- Copy image such as
milkv-duos-sd-v1.1.1-2024-0528.img.zip
from github to SD card - connect it to PC via USB-C, it creates network interface (USB Network card) with DHCP and your will see it as
192.168.42.1
so … - ssh to it using
root
andmilkv
password.
Serial connection (slow way)
Prerequisites
- Milk-V Duo S
- CoolTerm terminal emulator or your favorite one. PuTTY can be better, cause it handles special characters by default.
- Familiar tool for writing disc image to SD card: Raspberry Imager, Balena Etcher, …
- USB to Serial converter supporting 3.3V with USB cable
- USB-C cable for power
First attempt (boot with serial console)
My first step was reading MilkV Getting Started - Duo S
Interesting is GPIO part so I connected GND, UART0_TX, UART0_RX (pins 6,8,10) via USB to Serial converter to PC. Then I run CoolTerm and setup port as 115200 8N1. When I powered it from powerbank via USB-C port, I was welcomed by this
1
2
3
4
5
6
7
8
9
10
WD.C.SCS/0/0.WD.URPL.USBI.USBEF.BS/EMMC.EMI/25000000/12000000.PS. E:PARAM1 magic (0x0)
PS. E:PARAM1 magic (0x0)
PS. E:PARAM1 magic (0x0)
PS. E:PARAM1 magic (0x0)
PS. E:PARAM1 magic (0x0)
PS. E:PARAM1 magic (0x0)
PS. E:PARAM1 magic (0x0)
PS. E:PARAM1 magic (0x0)
E:Boot failed (8).
E:RESET:plat/mars/platform.c:114
and it kept reseting every few seconds. Hmmm. I expected it has Linux preinstalled on eMMC storage.
But documentation states:
eMMC version firmware burning
The DuoS eMMC version does not have firmware burned and needs to be burned using a PC through the USB interface.
Preparing SD card boot
Ok, the next chapter is about microSD card boot.
Vaguely following the instructions, I downloaded firmware from github and used Raspberry Imager to copy image into SD card (I had this installed already, because it’s needed for headless setup of Raspberry PI OS to enable SSH and set user and password).
It worked flawlessly and I got a serial console. Isn’t it nice to see boot messages without monitor?
Configuring WiFi on startup
I’ve done this using serial console, which is a bit harder.
Using Linux over serial console which does not handle special characters is a bit painful experience. Using arrows is impossible. Another cool feature is that right enter sends Ctrl+C
for some reason and commands do not work. I guess CoolTerm has cool in it’s name for reasons.
Manual is here, but editing files is “fun”. I’ve done it this way:
Replace SSIDs and passwords with your own. Also change country which may limit used channels and transmission power.
1
echo -e 'ctrl_interface=/var/run/wpa_supplicant\ncountry=CZ\nap_scan=1\n\nnetwork={\n ssid="SSID1"\n psk="PASS1"\n key_mgmt=WPA-PSK\n priority=1\n}\n\nnetwork={\n ssid="SSID2"\n psk="PASS2"\n key_mgmt=WPA-PSK\n priority=2\n}\n' > /etc/wpa_supplicant.conf
Actual file looks like this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ctrl_interface=/var/run/wpa_supplicant
country=CZ
ap_scan=1
network={
ssid="PavelP"
psk="qwerasdf"
key_mgmt=WPA-PSK
priority=1
}
network={
ssid="SSID2"
psk="PASS2"
key_mgmt=WPA-PSK
priority=2
}
I’ve used secondary configuration for my router and primary for my phone. It works even with WPA2-PSK in my case.
1
wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf
1
2
Successfully initialized wpa_supplicant
nl80211: kernel reports: Authentication algorithm number required
1
ip addr show wlan0
1
2
3
4
4: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
link/ether 88:00:33:77:27:99 brd ff:ff:ff:ff:ff:ff
inet 192.168.0.94/24 brd 192.168.0.255 scope global dynamic noprefixroute wlan0
valid_lft 3518sec preferred_lft 3068sec
Now let’s make this permanent (original file is basically placeholder, so overwrite it, just make sure it’s executable)
1
echo -e '#!/bin/sh\n\n# Put the program you want to run automatically here\n\nwpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf\n' > /mnt/system/auto.sh
For some reason it does not work, maybe there is something in wrong order:
1
2
3
4
5
6
7
8
9
10
11
12
Starting app...
Successfully initialized wpa_supplicant
[root@milkv-duo]~# Could not read interface wlan0 flags: No such device
nl80211: Driver does not support authentication/association or connect commands
nl80211: deinit ifname=wlan0 disabled_11b_rates=0
Could not read interface wlan0 flags: No such device
wlan0: Failed to initialize driver interface
[ 10.346825] aicbsp: sdio_err:<aicwf_sdio_bus_pwrctl,1432>: bus down
[ 11.082324] ieee80211 phy0:
[ 11.082324] *******************************************************
[ 11.082324] ** CAUTION: USING PERMISSIVE CUSTOM REGULATORY RULES **
[ 11.082324] *******************************************************
Indeed. Startup seqence is /etc/inittab
runs ::sysinit:/etc/init.d/rcS
which goes through /etc/initd.d/S??*
files and executes them in numerical order. However S99user
script runs /mnt/system/duo-init.sh
and after 30ms wait /mnt/system/auto.sh
. Now duo-init.sh
loads wifi module:
1
2
3
4
# WIFI/BT Module
insmod /mnt/system/ko/aic8800_bsp.ko
sleep 0.5
insmod /mnt/system/ko/aic8800_fdrv.ko
and /mnt/system/auto.sh
is our script that relies on wifi.
Let’s fix this by command that writes auto.sh
with 5s wait and sets fixed MAC address.
1
echo -e '#!/bin/sh\n\n# Put the program you want to run automatically here\n\nsleep 5\nip link set dev wlan0 down\nip link set dev wlan0 address 88:00:33:77:42:42\nip link set dev wlan0 up\n\nwpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf\n' > /mnt/system/auto.sh
Resulting file is:
1
2
3
4
5
6
7
8
9
10
#!/bin/sh
# Put the program you want to run automatically here
sleep 5
ip link set dev wlan0 down
ip link set dev wlan0 address 88:00:33:77:42:42
ip link set dev wlan0 up
wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant.conf
For some reason, not all devices can be easily found on network:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[root@marten -=- ~/bin]# ./netdiscover
Currently scanning: 192.168.40.0/16 | Screen View: Unique Hosts
3 Captured ARP Req/Rep packets, from 3 hosts. Total size: 180
_____________________________________________________________________________
IP At MAC Address Count Len MAC Vendor / Hostname
-----------------------------------------------------------------------------
192.168.0.1 38:43:7d:ee:63:f8 1 60 Compal Broadband Networks, Inc.
192.168.0.206 a8:a1:59:39:45:c6 1 60 ASRock Incorporation
192.168.0.73 08:3a:8d:c1:66:1d 1 60 Espressif Inc.
[root@marten -=- ~/bin]# arp -a
_gateway (192.168.0.1) at 38:43:7d:ee:63:f8 [ether] on enp0s31f6
? (192.168.0.73) at 08:3a:8d:c1:66:1d [ether] on enp0s31f6
? (192.168.0.206) at a8:a1:59:39:45:c6 [ether] on enp0s31f6
? (192.168.0.45) at fc:34:97:7f:54:31 [ether] on enp0s31f6
? (192.168.0.199) at 88:00:33:77:42:42 [ether] on enp0s31f6
[root@marten -=- ~/bin]# nmap -sn 192.168.0.0/24
Starting Nmap 7.95 ( https://nmap.org ) at 2024-06-22 16:42 CEST
Nmap scan report for _gateway (192.168.0.1)
Host is up (0.0012s latency).
MAC Address: 38:43:7D:EE:63:F8 (Compal Broadband Networks)
Nmap scan report for 192.168.0.206
Host is up (0.00022s latency).
MAC Address: A8:A1:59:39:45:C6 (ASRock Incorporation)
Nmap scan report for marten (192.168.0.143)
Host is up.
Nmap done: 256 IP addresses (3 hosts up) scanned in 3.34 seconds
[root@marten -=- ~/bin]#
With wifi and network address that will hopefully stay the same for some time (having avahi would be nice), we can finally connect to host using wifi as root
with password milkv
. Let’s log in and test a few commands.
Exploring available commands
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
[pavel@marten -=- /home/pavel/dev-blog]$ ssh [email protected]
The authenticity of host '192.168.0.199 (192.168.0.199)' can't be established.
ED25519 key fingerprint is SHA256:Bn2+WB5Crmm7pn3dtvlxC9xUlfzkdu3AbqY4IFFopDk.
This host key is known by the following other names/addresses:
~/.ssh/known_hosts:17: 192.168.0.10
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.0.199' (ED25519) to the list of known hosts.
[email protected]'s password:
[root@milkv-duo]~# uname -a
Linux milkv-duo 5.10.4-tag- #1 PREEMPT Tue May 28 21:08:24 CST 2024 riscv64 GNU/Linux
[root@milkv-duo]~# free
total used free shared buff/cache available
Mem: 330960 22788 295196 168 12976 300316
Swap: 0 0 0
[root@milkv-duo]~# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/root 752M 154M 556M 22% /
devtmpfs 162M 0 162M 0% /dev
tmpfs 162M 0 162M 0% /dev/shm
tmpfs 162M 128K 162M 1% /tmp
tmpfs 162M 40K 162M 1% /run
[root@milkv-duo]~# date
Sat Jun 22 18:14:51 UTC 2024
[root@milkv-duo]~# cat /proc/cpuinfo
processor : 0
hart : 0
isa : rv64imafdvcsu
mmu : sv39
[root@milkv-duo]~# cat /proc/cmdline
root=/dev/mmcblk0p3 rootwait rw console=ttyS0,115200 earlycon=sbi riscv.fwsz=0x80000 loglevel=9
[root@milkv-duo]~# python --version
Python 3.9.5
[root@milkv-duo]~# pip3 list
Package Version
----------- -------
cffi 1.14.2
evdev 1.6.1
freetype-py 0.0.0
lxml 4.6.3
modbus-tk 1.1.2
numpy 1.18.2
Pillow 8.2.0
pip 20.0.2
psutil 5.7.2
pycparser 2.20
pyserial 3.5
setuptools 44.0.0
smbus-cffi 0.5.1
spidev 3.5
[root@milkv-duo]~# curl wttr.in/Brno
Weather report: Brno
\ / Sunny
.-. +25(26) °C
― ( ) ― → 15 km/h
`-’ 10 km
/ \ 0.0 mm
┌─────────────┐
┌──────────────────────────────┬───────────────────────┤ Sat 22 Jun ├───────────────────────┬──────────────────────────────┐
│ Morning │ Noon └──────┬──────┘ Evening │ Night │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│ _`/"".-. Patchy rain ne…│ \ / Sunny │ \ / Sunny │ \ / Sunny │
│ ,\_( ). 21 °C │ .-. +24(25) °C │ .-. 22 °C │ .-. +15(14) °C │
│ /(___(__) → 16-19 km/h │ ― ( ) ― → 20-23 km/h │ ― ( ) ― ↘ 12-17 km/h │ ― ( ) ― ↓ 8-17 km/h │
│ ‘ ‘ ‘ ‘ 10 km │ `-’ 10 km │ `-’ 10 km │ `-’ 10 km │
│ ‘ ‘ ‘ ‘ 0.0 mm | 78% │ / \ 0.0 mm | 0% │ / \ 0.0 mm | 0% │ / \ 0.0 mm | 0% │
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
┌─────────────┐
┌──────────────────────────────┬───────────────────────┤ Sun 23 Jun ├───────────────────────┬──────────────────────────────┐
│ Morning │ Noon └──────┬──────┘ Evening │ Night │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│ \ / Sunny │ \ / Partly Cloudy │ \ / Partly Cloudy │ \ / Sunny │
│ .-. +22(24) °C │ _ /"".-. +24(25) °C │ _ /"".-. +22(25) °C │ .-. 16 °C │
│ ― ( ) ― ↓ 12-13 km/h │ \_( ). ↓ 12-13 km/h │ \_( ). ↓ 10-15 km/h │ ― ( ) ― ↓ 8-16 km/h │
│ `-’ 10 km │ /(___(__) 10 km │ /(___(__) 10 km │ `-’ 10 km │
│ / \ 0.0 mm | 0% │ 0.0 mm | 0% │ 0.0 mm | 0% │ / \ 0.0 mm | 0% │
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
┌─────────────┐
┌──────────────────────────────┬───────────────────────┤ Mon 24 Jun ├───────────────────────┬──────────────────────────────┐
│ Morning │ Noon └──────┬──────┘ Evening │ Night │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│ \ / Partly Cloudy │ \ / Partly Cloudy │ \ / Sunny │ \ / Sunny │
│ _ /"".-. 22 °C │ _ /"".-. +24(25) °C │ .-. 20 °C │ .-. +15(14) °C │
│ \_( ). ↙ 9-10 km/h │ \_( ). ↙ 11-12 km/h │ ― ( ) ― ↙ 14-24 km/h │ ― ( ) ― ↓ 13-26 km/h │
│ /(___(__) 10 km │ /(___(__) 10 km │ `-’ 10 km │ `-’ 10 km │
│ 0.0 mm | 0% │ 0.0 mm | 0% │ / \ 0.0 mm | 0% │ / \ 0.0 mm | 0% │
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
Location: Brno, okres Brno-město, Jihomoravský kraj, Jihovýchod, Česko [49.1922443,16.6113382]
Follow @igor_chubin for wttr.in updates
[root@milkv-duo]~# aplay -l
**** List of PLAYBACK Hardware Devices ****
card 1: cv182xadac [cv182xa_dac], device 0: cviteka-dac 300a000.dac-0 [cviteka-dac 300a000.dac-0]
Subdevices: 1/1
Subdevice #0: subdevice #0
[root@milkv-duo]~# arecord -l
**** List of CAPTURE Hardware Devices ****
card 0: cv182xaadc [cv182xa_adc], device 0: cviteka-adc 300a100.adc-0 [cviteka-adc 300a100.adc-0]
Subdevices: 1/1
Subdevice #0: subdevice #0
[root@milkv-duo]~# fdisk /dev/mmcblk0
Welcome to fdisk (util-linux 2.36.2).
Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.
Command (m for help): p
Disk /dev/mmcblk0: 14.84 GiB, 15931539456 bytes, 31116288 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0x2890ad13
Device Boot Start End Sectors Size Id Type
/dev/mmcblk0p1 * 1 262144 262144 128M c W95 FAT32 (LBA)
/dev/mmcblk0p2 262145 266240 4096 2M 0 Empty
/dev/mmcblk0p3 266241 1839104 1572864 768M 83 Linux
Command (m for help): q
Curiously 2nd partition is/seems empty.
Here it’s worth noting that Windows FAT32 partition contains two files:
1
2
3
4
5
6
[root@milkv-duo]/tmp/p1# ls -la
total 3458
drwxr-xr-x 3 root root 16384 Jan 1 1970 .
drwxrwxrwt 7 root root 220 Jul 16 20:36 ..
-rwxr-xr-x 1 root root 3083696 May 28 21:17 boot.sd
-rwxr-xr-x 1 root root 436736 May 28 21:17 fip.bin
boot.sd is identified as:
1
2
file boot.sd
boot.sd: Device Tree Blob version 17, size=3083696, boot CPU=0, string block size=100, DT structure block size=3082608
and contains interesting stuff. After playing with strings
, strings -n 12
and so on, I came to this, hopefully correct conclusion
- boot.sd contains kernel, possibly initrd and possibly some boot loader
- fip.bin contains OpenSBI, custom FreeRTOS and who knows what. Actually th
Shutdown
See last command. Sadly poweroff
or other commands are equal to reboot.
1
2
3
4
5
[root@milkv-duo]~# sync && /bin/umount -a -r
umount: can't remount sysfs read-only
umount: can't remount tmpfs read-only
umount: can't remount tmpfs read-only
umount: devtmpfs busy - remounted read-only
Running services
- ntpd
- dnsmasq (DNS cache and DHCP server - likely for USB-C network connection),
- DHCP client daemon to get IP address for ethernet and wifi interface
- dropbear (lightweight SSH server and client)
- shell script that blinks the LED
Further exploration
Transfer system to eMMC
Status of mainline kernel support
https://github.com/sophgo/linux/wiki
Lessons learned
- Default user is
root
and password ismilkv
- Importance of reading documentation before starting 😎 or purchase
- Connecting this device to ethernet to edit files is much easier than serial console. Although some terminal application may work better than others (depends on settings)
- I’m not familar with CoolTerm - I hope there are profiles for text and binary connection. Last time I used it was for reading EEPROM data from Arduino.
- There’s no Linux distribution. Only busybox (replacement of shell and many basic system utilities) and few utilities
- I have device running for about nine hours from power bank. Device is warm, but 10000mAh powerbank went from 85% to 60% capacity.
- Power consumption is rougly 0.2A (1 watt) all the time. It’s not much, but it’s not much lower than idling Raspberry Pi 3B or Radxa Rock 3C neither. Well, according to video, model without eMMC has much lower power consumption.
Conclusion
Setting up the Milk V Duo S was an educational journey filled with both challenges and rewarding moments. While the process wasn’t always smooth, and my first attempt was not the most straightforward and it provided valuable insights into good, old init scripts and very minimalistic Linux setup.
It seems a bit demanding to use. Application must be developed outside of this board, possibly using development docker containers with tools, Linux is almost as minimalistic as Linux distributed on 10 floppies in late 90s which resided on top of FAT filesystem and was launced from MS-DOS. However it might be decent RISC-V Development and Learning Platform to learn about RISC-V architecture, embedded Linux systems, and low-level programming. It’s a hands-on way to explore these technologies. Only if it had decent documentation. There are some examples, but they are lacking any information how they work, why they work, how to build them. FreeRTOS example has LED control and some test functions. It’s likely possible to cross-compile RTOS, your code, upload it and to make it work. But I came to conclusion that MilkV DuoS does not have support to run custom firmware (RTOS, Arduino code) on other core yer.
Is it better than Raspberry Pi 3B I own? Certainly not. RPI3B has way better support, can run Linux distributions with prebuilt packages and it’s faster.
Do I recommend it? No, unless you have too much free time. You can hardly find a device with more challenges per price. It’s cheap if your time is worthless.
Few videos
- Jeff Geerling -=- RISC-V isn’t killing Arm (yet) This video is about Milk-V Mars, comparing it to raspberries.
- Platima Tinkers -=- Milk-V Duo S - Dual Boot RISC-V or ARM with Dual Camera, Wi-Fi and RTOS Capabilities!
Resources
TODO: read when I have time
- Milk-v Duo Compilation Process Part II- Compiling Small Core FreeRTOS
- Use Opensbi to boot your own operating system
- Some tutorials
- Some videos
- Simply try running FreeRTOS on Milk-V Duo’s small core
- SSD1309 Display On the Milk-V Duo
- Some experiments
TODOs
- Separate serial port login and USB RDNIS login from Linux
For fun: Conclusion rephrased by Claude AI
Is it better than the Raspberry Pi 3B I own? Not by a long shot. The RPI3B boasts superior support, runs full-fledged Linux distributions with pre-built packages, and leaves the Milk V Duo S in the dust performance-wise.
Do I recommend it? Only if you have an abundance of time and a masochistic streak for technological challenges. You’d be hard-pressed to find a device that offers more frustrations per dollar. It’s a bargain if you consider your time worthless and enjoy the exquisite pain of wrestling with underdocumented hardware.
In short, the Milk V Duo S is perfect for those who find traditional computing too straightforward and yearn for the character-building experience of banging their head against a RISC-V wall.
If that sounds like your idea of fun, dive right in – the water’s fine, if by ‘fine’ you mean ‘full of sharks and with a strong undertow’.