Milk-V Duo ist eine ultrakompakte Embedded-Entwicklungsplattform auf der Basis des CV1800B-Chips. Sie kann Linux und RTOS ausführen und bietet eine zuverlässige, kostengünstige und leistungsstarke Plattform für Profis, industrielle ODMs, IoT-Enthusiasten, Heimwerker und Entwickler.
Spezifikation
Milk-V Duo | Specification |
---|---|
Prozessor | CVITEK CV1800B (C906@1Ghz + C906@700MHz) |
RAM | DDR2 64MB |
Flash | 1x Mirco SD slot,1x SD NAND solder pad |
USB | 1x Type-C for data and Power,1x USB2 solder pad |
Kamera | 1x 16P FPC connector (MIPI CSI 2-lane) |
GPIO | up to 26 Pins available for general purpose I/O(GPIO) |
Größe | 21mm*51mm |
Details:
https://milkv.io/docs/duo/overview
Größe
Hier mal ein Größenvergleich des Milk-V & Cam mit meiner Logitech Maus.
Shop
https://arace.tech/search?q=Milk-V+Duo&type=product
Ich habe mal direkt zugeschlagen und mich mal eingedeckt.
Ein erster Test zeigt, dass der Energiebedarf mit ca. 0,5W sehr übersichtlich ist.
Die Installation ist denkbar übersichtlich. Einfach das Image unter https://milkv.io/docs/duo/getting-started/boot herunterladen und z.B. mit balenaEtcher auf einer SD-Karte installieren.
Besonders toll ist die Möglichkeit, den Milk-V Duo über ein USB-C Kabel an den PC anzuschließen und wie unter https://milkv.io/docs/duo/getting-started/setup beschrieben per SSH darauf zugreifen zu können. Das default root Passwort ist milkv.
Nach dem Start des Milk-V beginnt die blaue Onboard-LED direkt an, zu blinken. Das liegt daran, dass beim Start des Milk-V das /mnt/system/blink.sh Shell-Skript ausgeführt wird.
#!/bin/sh
LED_GPIO=/sys/class/gpio/gpio440
if test -d $LED_GPIO; then
echo "GPIO440 already exported"
else
echo 440 > /sys/class/gpio/export
fi
echo out > $LED_GPIO/direction
while true; do
echo 0 > $LED_GPIO/value
sleep 0.5
echo 1 > $LED_GPIO/value
sleep 0.5
done
Wie man sieht, kann man die GPIOs des Milk-V einfach aus einem Shell-Skript heraus ansprechen kann und der Milk-V hat viele davon.
- 26 GPIO Pins – MilkV-Duo 40-pin (SDIO, I2C, PWM, SPI, J-TAG, und UART)
- 3x I2C
- 5x UART
- 1x SDIO1
- 1x SPI
- 2x ADC
- 7x PWM
- 1x RUN
- 1x JTAG
Man kann die GPIO-Pins des Milk-V aber auch aus einem C-Programm ansprechen. Dazu muss man das milkv-duo/duo-app-sdk unter https://github.com/milkv-duo/duo-app-sdk/releases herunterladen und auf einem Linux PC installieren. Wer gerade kein Linux zur Hand hat, kann auch eine Ubuntu VM nutzen.
Die einfachste Art ist es, das https://github.com/milkv-duo/duo-examples Repo zu klonen und das enthaltene envsetup.sh Skript auszuführen.
#!/bin/bash
SDK_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &> /dev/null && pwd)
echo "SDK_DIR: ${SDK_DIR}"
MILKV_DUO_SDK=${SDK_DIR}/duo-sdk
TOOLCHAIN_DIR=${MILKV_DUO_SDK}/riscv64-linux-musl-x86_64
SDK_URL="https://github.com/milkv-duo/duo-app-sdk/releases/download/duo-app-sdk-v1.1/duo-sdk-v1.1.tar.gz"
function get_duo_sdk()
{
pushd ${SDK_DIR}
echo "SDK_URL: ${SDK_URL}"
sdk_file=${SDK_URL##*/}
echo "sdk_file: ${sdk_file}"
wget ${SDK_URL} -O ${sdk_file}
if [ $? -ne 0 ]; then
echo "Failed to download ${SDK_URL} !"
return 1
fi
if [ ! -f ${sdk_file} ]; then
echo "${sdk_file} not found!"
return 1
fi
echo "Extracting ${sdk_file}..."
tar -xf ${sdk_file}
if [ $? -ne 0 ]; then
echo "Extract ${sdk_file} failed!"
return 1
fi
[ -f ${sdk_file} ] && rm -rf ${sdk_file}
popd
}
if [ ! -d ${MILKV_DUO_SDK} ]; then
echo "SDK does not exist, download it now..."
get_duo_sdk
if [ $? -ne 0 ]; then
echo "Get SDK failed!"
return 1
fi
fi
export TOOLCHAIN_PREFIX=${TOOLCHAIN_DIR}/bin/riscv64-unknown-linux-musl-
export SYSROOT=${MILKV_DUO_SDK}/rootfs
export LDFLAGS="-mcpu=c906fdv -march=rv64imafdcv0p7xthead -mcmodel=medany -mabi=lp64d"
# -Os
export CFLAGS="-D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64"
echo "SDK environment is ready"
Das Skript installiert das duo-sdk und setzt die Umgebungsvariable TOOLCHAIN_PREFIX, die dann in den Makefiles der Beispiele verwendet wird, um den korrekten gcc zu verwenden.
Das Blink C-Programm kann man dann wie folgt kompilieren und per ssh auf den Milk-V Duo zu kopieren.
So weit so gut. Scheint alles zu funktionieren.
Allerdings ein Problem habe ich dann doch noch gefunden, als ich versucht habe den ADC des Milk-V zu nutzen. Für den Zugriff auf die GPIOs wird auf dem Milk-V die wiringX Library verwendet.
WiringX ist eine Library, mit der man die GPIOs verschiedener Plattformen mit generischen und einheitlichen Funktionen steuern kann. Durch die Verwendung von wiringX wird derselbe Code auf allen von wiringX unterstützten Plattformen nativ ausgeführt. Die wiringX Lib. hat auch eine analogRead Funktion, die aber leider in der Milk-V Version der wiringX Lib auskommentiert ist.
Schade, hätte gerne mal gesehen, was der ADC so kann… aber evtl. ist die analogRead Metode zur Verwendung des ADC in der nächsten Version des SDK erhalten. Ich habe jedenfalls mal ein GitHub Issue zu dem Thema erstellt (https://github.com/milkv-duo/duo-app-sdk/issues/1)
Alle Details zu den wiringX Funktionen des Milk-V Dou kann man unter https://milkv.io/zh/docs/duo/application-development/wiringx nachlesen.
Python
Da die Programmiersprache C nicht so einfach ist wie Python, ist auch ein Python Interpreter vorhanden. Das aktuelle Image unterstützt Python 3.9.5 und bringt für die Steuerung der GPIOs noch die pinpong Library mit.
Die pinpong-Lib ist eine Open-Source-Python-Bibliothek, die auf dem Firmata-Protokoll basiert und die Micropython-Syntax verwendet. Ihr Ziel ist es, ein Werkzeug zur Verfügung zu stellen, mit dem verschiedene Open-Source-Hardware-Steuerkarten direkt aus dem Python- Code heraus steuern kann.
Die Doku auf der Milk-V Seite https://milkv.io/docs/duo/application-development/pinpong ist noch recht dürftig und da wird hoffentlich in den nächsten Wochen noch etwas passieren.
Das blink Skript in Python sieht dann wie folgt aus:
import time
from pinpong.board import Board,Pin
Board("MILKV-DUO").begin()
led = Pin(Pin.D25, Pin.OUT)
while True:
led.write_digital(1) #Output high level method 2
print("1") #Terminal printing information.
time.sleep(1) #Wait for 1 second and keep the state
led.write_digital(0) #Output Low Level Method 1
print("0") #Terminal printing information.
time.sleep(1) #Wait for 1 second and keep the state
Ein Large language model auf dem Milk-V laufen lassen
Klingt erst mal etwas verrückt, aber es ist möglich, ein kleines LLM (Large language model) auf dem Milk-V laufen zu lassen. Dazu muss man das https://github.com/karpathy/llama2.c Repository von Andrej Karpathy klonen und mit dem gcc compiler für die RISC-Architektur übersetzen. Wichtig ist, dass man in der run.c Datei noch die Zeile „#include <stdint.h>“ ergänz werde, da sonst der Datentype int8_t unbekannt ist und der folgende Fehler auftritt:
run.c:453:39: error: unknown type name 'int8_t'; did you mean 'intptr_t'?
453 | void encode(Tokenizer* t, char *text, int8_t bos, int8_t eos, int *tokens, int *n_tokens) {
| ^~~~~~
| intptr_t
run.c:453:51: error: unknown type name 'int8_t'; did you mean 'intptr_t'?
453 | void encode(Tokenizer* t, char *text, int8_t bos, int8_t eos, int *tokens, int *n_tokens) {
| ^~~~~~
| intptr_t
run.c: In function 'generate':
run.c:737:5: warning: implicit declaration of function 'encode'; did you mean 'decode'? [-Wimplicit-function-declaration]
737 | encode(tokenizer, prompt, 1, 0, prompt_tokens, &num_prompt_tokens);
| ^~~~~~
| decode
run.c: In function 'chat':
run.c:816:5: error: unknown type name 'int8_t'; did you mean 'intptr_t'?
816 | int8_t user_turn = 1; // user starts
| ^~~~~~
| intptr_t
Wenn man alles korrekt eingerichtet hat, kann man ein RISC Binary mit dem Namen „run.rv64“ erzeugen.
Nachdem die Datei per scp auf den Milk-V kopiert wurde, kann man das run.rv64 ausführen.
Was noch fehlt ist ein Model. Eine Liste von kompatiblen Modellen findet man unter https://github.com/karpathy/llama2.c#models. Auf Grund des limitierten RAM von 64MB kann man max. das stories15M.bin Model verwenden, dass 15.000.000 Parameter besitzt und so gerade eben mit dem verfügbaren Speicher auskommt. Wenn man die Dateien stories15M.bin und tokenizer.bin aus dem GitHub Repository auf den Milk-V kopiert hat kann man von der KI eine kleine Geschichte schreiben lassen.
[root@milkv-duo]~/llm# ./run.rv64 stories15M.bin One day, a little boy named Tim went to play in the park. He saw a big pile of rocks. He wanted to make a big structure with the rocks. Tim started to add more and more rocks. He was having so much fun. Then, Tim met a new friend, Sam. Sam was not wealthy, but he was very smart. Sam said, "Let's build a big structure together!" Tim agreed, and they started to add more and more rocks. They worked hard all day. Soon, the structure was very tall. Tim and Sam were proud of their work. They learned that working together made their playtime more fun. The moral of the story is that teamwork makes everything better. achieved tok/s: 0.299230 [root@milkv-duo]~/llm#
Natürlich ist es nicht unbedingt sinnvoll, das stories15 Model auf einer so limitierten Hardware laufen zu lassen, aber es ist sehr beeindruckend zu sehen, was die der CV1800B Chip auf dem Milk-V Board drauf hat.
Kamera Test
Der Duo verfügt über einen 16P FPC connector (MIPI CSI 2-lane) an dem man eine Kamera anschließen kann. Dafür dass die Kamera gerade mal 4$ kostet sind die Ergebnisse eigentlich ganz OK.
Fazit:
Alles in allem ein recht beeindruckendes Stück Technik, wenn man bedenkt, dass man es für 5$ (Plus Zollgebühren) kaufen kann.
Als nächstes werde ich mal die anderen Möglichkeiten des Milk-V testen. Besonders gespannt bin ich auf die Qualität der Kamera, die auch nur 4$ kostet und auf die Fähigkeiten der Onboard-TPU (Tensor Processing Unit).