Category Archives: RISC-V

32/64/128bits RISC-V processor ISA

RISC-V based ESP32c3 with ESP-IDF part 3, OLED screen, and potentiometer

Table of Content

* Introduction
* ADC Limitations on some ESP32 SoCs
* Potentiometer
* OLED I²C Screen
* Building the project and flashing


This piece of software was done for new year 2022, but procrastination helped me to delay the release of the tutorial, it continue the traditionnal (but with detailed explanations) LED blinking introduction tutorial. The goal of this tutorial is to learn to use potentiometer and little I²C screens (4 pins are I²C only, SPI versions use more pins) in EPE-IDF, with ESP32 microcontroller SoC based. I use here a really cheap (<5€) but powerful AI thinker ESP-C3-32S, that use an efficient low power RISC-V microcontroller.

Full schema with part 2 and 3

You can find the complete sources files and prebuild RISC-V firmware for ESP32-C3 on my files repository.

This example contain two main parts in the single file adc/esp32c3/adc/main/adc_dma_example_main.c, called in app_main(void), at the end of the file :
* One simple example single_read(NULL);, that make only one read of the state of the ADC, it uses ADC 1, channels 2,3,4) and ADC 2 (channel 0) and display datas on terminal.
* One more complex example continuous_read(NULL);, that reads 256 times the state of the channels and display them in the console, and then make continuous reading and change the onboard RGB Led blue colour light intensity.

The official documentation of the ADC.

ADC Limitations on some ESP32 SoCs

Limitations differs depending on ESP SoC, they have both 2 ADC, and one can’t be used when using WiFi:
* ESP32, based on Xtensa LX6 has 10 channels on ADC1 and 8 channels on ADC2, and ADC2 is used when Wifi is on.
* ESP32-S2 (no WiFi/BT) and ESP32-S3 (Wifi/BT), based on Xtensa LX7 (this last one has a RISC-V coprocessor for a more efficient ULP deep sleep mode), has 10 channels on both ADC, and ADC2 can’t be used when WiFi is on.
* ESP32-C3, based on RISC-V, ADC1 can’t be used with WiFi on, both ADC1 and ADC2 can’t be read simultaneously, you must read them alternately. ADC1 have 6 channels (6 pins) and ADC2 only one.

NodeMCU-series ESP-C3-32S-kit pinoutESP-C3-32S kit Pinout schema from JC François, with ADC pins in pink.

Full schema with part 2 and 3
Whole Breadboard montage with previous part tutorial and this one.

Potentiometer and OLED screen connexions
Connexions of potentiometer and OLED screen.


* The first top-left pin (ADC1_CH0 / ADC_CHECK in pink) is connected to the middle pin of the potentiometer (et right on the picture) using the white wire.
* The  3.3V , here 5th pin starting from the bottom left, but other 3.3V can be choosen, is connected to the left pin of the potentiometer (at right on the picture).
* The  GND , here 6th pin starting from the bottom left, but any ground pin can be used is connected using black wire to the right pin of the potentiometer.

V Red and GND black-blue breadboard lanes
Red lane and blue/black lane of the bread board.

Both  Vcc  and Ground are transiting by dedicated lane of the breadboard, on the top of the picture painted with red (meaning Vcc) and blue (meaning for black/Ground) lines. It is very important to keep black and red wire to these roles to avoid to burn components, any other colour can be used for data links. There is another lane at bottom. This is not clear on the picture, but the screen is connected on but on the first row of the inner part.

We need to include the adc.h headers, and we also add esp_log.h header here for debug purpose.

#include "esp_log.h"
#include "driver/adc.h"

Here are the presets used for potentiometer ADC (Analog-Digital Converter) in the source code.

/* ADC vars */

esp_err_t ret;
int adc1_reading[1] = {0xcc};
int adc2_reading[1] = {0xcc};
const char TAG_CH[][9] = {"ADC1_CH0", "ADC2_CH0"};

void init_adc()
  adc1_config_channel_atten(ADC1_CHANNEL_0, ADC_ATTEN_DB_0);
  adc2_config_channel_atten(ADC2_CHANNEL_0, ADC_ATTEN_DB_0);

OLED I²C Screen

An author made an interesting list of available colour display managed by ESP32 on Instructables.

I used the driver esp-idf-ssd1306 by nopnop2002 available on github (local archive)

There are 4 connector pins on the I²C only version:
*  GND , I use black wire and connect it to GND lane.
*  VCC , I use red wire and connect it to Vcc lane.
*  VCL  (sometimes VCK, VCLK as V clock), is for the clock signal, I choose green colour wire here, I connect it to  GPIO9 , at 4th pin starting from top right.
*  VDA  (VDA as V data), I choose white colour wire here, I connect it to  GPIO10 , at 6th pin starting from top right.

The SDA/SCL GPIO can be set by two way:

By editing the sdkconfig file at the root of the project and changing the following values to the values you want:


Or in the menu using:

make menuconfig

Then go to submenu SSD1306 Configuration ---> Then defining the number in (9) SCL GPIO number and (10) SDA GPIO number field.

By default, this application print the current settings in the monitor console via these functions:

        ESP_LOGI(tag, "INTERFACE is i2c");
        i2c_master_init(&dev, CONFIG_SDA_GPIO, CONFIG_SCL_GPIO, CONFIG_RESET_GPIO);

The example of the driver is used for the screen. It pre-include the necessary headers files. ssd1306.h is the driver itself, font8x8_basic.h is a 8×8 pixels ASCII font set, and driver/i2c.h is the i²C protocol header, used to communicate with the screen microcontroller.

#include "ssd1306.h"
#include "font8x8_basic.h"
#include "driver/i2c.h"

I removed the demo, and set all the specific code in the #if CONFIG_SSD1306_128x64 section, as this is the model of my screen.

#if CONFIG_SSD1306_128x64
        top = 2;
        center = 3;
        bottom = 8; // 8 lines
        int n=200;
        int pos=0; // initial position = 0

Le main loop (while(n) {}):
I read ADC1 channel 0 (first pin) of the potentiometer and print the current value into the console

  adc1_reading[0] = adc1_get_raw(ADC1_CHANNEL_0);
  printf("chan[%d] 0x%x = %d\n", 0, adc1_reading[0],adc1_reading[0]);

Then I compute the current p position after a constant I predetermined, after test I seen that the specific potentiometer I use, as values in range [20 ; 2920]. And I have 8 text lines on screen, so I rounded to 3000/8 = 375. Output value / 8 compute the current line on screen.

  pos=adc1_reading[0]/375; // 20~2920  => need to calibrate 3000/8=375

Clearing the 8 text lines of the buffer, but the current line

  for (int i=0;i<8;i++) {
    if ( i != pos) {
      ssd1306_clear_line(&dev, i,false);

Printing 2022!! at the current line. the two space, allow to center a bit the text.

  ssd1306_display_text(&dev, pos, "  2022!!", 11, false);

And finally, wait a delay of 50 milliseconds before refreshing to avoid uselessly saturating processor and overloading.

  vTaskDelay(50 / portTICK_PERIOD_MS);

That's all ! We just have to build the project and put in on the board now.

Building the project and flashing

Build the example for AI thinker ESP-C3-32S

Initialising esp-idf:


Then go the the project root:

cd myproject/ set-target esp32c3

If you have the following error:

Adding "set-target"'s dependency "fullclean" to list of commands with default set of options.
Executing action: fullclean
Directory '/data/arc/esp/esp-idf/test/adc/esp32c3/adc/build' doesn't seem to be a CMake build directory. Refusing to automatically delete files in this directory. Delete the directory manually to 'clean' it:

You simply need to clean build subdirectory if it exists

rm -R build
mkdir build

and in any case to create the CMake:

cd build
cmake ..
cd ..

Then configure the project for your SoC target, in ESP32-C3 case: set-target esp32c3

If you need to change some settings of your porject, like GPIO ports for screen driver, you can edit the sdkconfig file or use make menuconfig now.

you will not have to redo all this procedure at each rebuild now, you can play with source code and build or rebuild/flash with the following last command: build flash monitor
You can quit the monitor by making the CTRL + ] keys combination.

relatively full Debian desktop, server or both environnement on RISC-V based LicheeRV.

Table of Contents

* Introduction
* Installing the Image
* Connecting serial
* Booting
* Setting the WiFi
* Audio
* Some minors but useful tuning
* What is working

* Update 2022-03-12: Someone made a full tutorial to build a working image with own kernel and standard debian buildroot.
* Update 2022-04-06: Sehraf made RISC-V Arch Linux builder for Lichee-RV and D1

Image used in this tutorial use a kernel that doesn’t support firewall so don’t forget to use it only behind a well configured router (or box) connexion and don’t use confidential things on it.


I managed to have a working Debian desktop environment on RISC-V after previous test and some exchanges on different Sipeed/D1 channels. Most informations are today available on Wiki dedicated page.

See also the previous article Booting Ubuntu Linux on a LicheeRV.

This image seems to manage more of the SoC features, or at least it announce lot of flags (IMAFDCVU):

$ cat /proc/cpuinfo 
processor	: 0
hart		: 0
isa		: rv64imafdcvu
mmu		: sv39

* IMAF = base ISA, Mul/div, Atomic instruction, (single precision) Float
* D = Double precision float
* V = Vector processor extension
* C = Compressed instructions
* U = User mode hyperverisor

The main problem was to have a working image with Debian, AllWinner and Speed give only a Linux image that can be made on Windows using PhoenixCard tool.

Someone of a Sipeed chat that have access to a Windows installed computer, made the conversion and give it available here (my mirror copy) sha256sum of the image: cf73baf3ed67d480e7606c666ccb81fce21295ba8fbba10e0ad86939065be6ffw. You need an at least 16GB microSD card to use it with LicheeRV and it’s Dock..

As a video exemple of the working image, Glaxnimate animation suite (own made RISC-V version of Debian package) , goes-up quickly to 6 of load, as most applications, but it is still usable:

Installing the Image

To install it, you can follow the following steps:

Install aria2 (Debian based (Debian, Ubuntu, …) sudo apt install aria2, Archlinux based (Arch, Manjaro, …): pacman -S aria2)

Update: someone said me he had problems with aria2, as wrote at the top of this article, you can still download the image from here: give it available here (my mirror copy). Please, verify the sha256sum of the image at the end of the download (Aria2 does automatically): cf73baf3ed67d480e7606c666ccb81fce21295ba8fbba10e0ad86939065be6ffw.

For an USB microSD card reader (I use /dev/sdd for /dev/sdX in my case you can verify which one is your by sudo fdisk -l:


It is very important to wipefs to avoid any problems with detections, then write, the downloaded image:

sudo wipefs -a ${DEVICE}
xzcat 20211230_LicheeRV_debian_d1_hdmi_8723ds.ddimg.xz | sudo dd bs=1MB status=progress of=${DEVICE}

Then delete the partition 8:

sudo fdisk ${DEVICE}

Resize the partition 7 to use the remaining space:

sudo parted ${DEVICE}

You will see the exact size of your partition (here in bold) that will be used later:

Model: SD ACLCE (sd/mmc)
Disk /dev/mmcblk0: 63.9GB

Then reuse the same value here to use the whole end of the card:

(parted) resizepart 7
End?  [??.?GB]? 63.9GB
(parted) quit

Then now grow the fs itself.
* for an USB sdcard reader (/dev/sdX):

sudo resize2fs ${DEVICE}7

* for an internal sdcard reader (/dev/mmcblkX):

sudo resize2fs ${DEVICE}p7

Now sync (flush data in memory on disk) the card:


You can now extract the card from your reader and put it in the LicheeRV board.

Connecting serial

You should connect the way described on this picture. You can also connect the red wire on one of the 5V pin to power the board if you want:
picture of UART connectors pinout, upper row from left, 5V, 5V, GND, TX, RX

You can then connect using one of the methods I previously described here.

screen /dev/ttyUSB0 115200
Package             commande
busybox             busybox microcom -t 5000 -s 115200 /dev/ttyUSB0
minicom             minicom -D /dev/ttyUSB0 
gtkterm-git (AUR)   gtkterm -s 115200 -p /dev/ttyUSB0
python-pyserial     python -m /dev/ttyUSB0 115200
screen              screen /dev/ttyUSB0 115200
tinyserial          com /dev/ttyUSB0 115200
picocom             picocom --baud 115200 /dev/ttyUSB0

On the Login prompt, use:
* Login: sipeed
* Password: licheepi

Just for information about PinOut, used to know the serial pins:

You can find a Pineout of the board on the LicheeRV HDK Schematic PDF (local mirror):

And the pineout of the dock in the Dock Datasheet (local mirror)

LicheeRV Dock pineout


There has several problems at boot due to cgroup not enable in this kernel.

You can disable this problems by:

sudo dpkg -P rtkit
sudo systemctl disable e2scrub_reap
sudo systemctl disable systemd-hostnamed

The first line allow to have more HDMI (including sound) working and stop loop message on all consoles. The second one avoid 2+minutes of wait at booting time. The third one seems to have no effect, the message continue at boot.

Details of the problems:

H2MI to DVI and HDMI to USB dongles I used
Then boot it plugged on a 1080p HDMI screen. It doesn’t work with my HDMI->DVI (tried on a 1680×1050 DVI-A and a 1080p DVI-D) or with my 1080p HDMI->USB dongle. Someone else reported it worked with an HDMI-DVI dongle (reference: 6140063500G).

Update: This was resolved partially by removing rfkit, a watchdog daemon that tried to kill something, probably on a wrong test. The message that come in loop on the console disappear then, the HDMI output on the HDMI to USB dongle worked, this will allow me to record/stream video output, and audio output on HDMI now work too. It could be suggestive, but I feel like system also work a bit faster (testing/killing/restarting things can take a lot of resources) :

sudo dpkg -P rtkit

The error message loops like this in dmesg :

Jan 18 10:50:33 sipeed systemd[1]: Starting RealtimeKit Scheduling Policy Service...
Jan 18 10:50:33 sipeed kernel: Unable to handle kernel paging request at virtual address ffffffdf8099707e
Jan 18 10:50:33 sipeed kernel: Oops [#52]
Jan 18 10:50:33 sipeed kernel: Modules linked in: xt_time xt_multiport xt_mark xt_mac xt_limit xt_comment xt_TCPMSS xt_LOG uvcvideo videobuf2_vmallo>
Jan 18 10:50:33 sipeed systemd[1]: rtkit-daemon.service: Main process exited, code=killed, status=11/SEGV
Jan 18 10:50:58 sipeed systemd[1]: rtkit-daemon.service: Failed to get cgroup ID on cgroup /sys/fs/cgroup/system.slice/rtkit-daemon.service, ignorin>

The problem of missing cgroup management in kernel is also the source of long boot and messages:

[FAILED] Failed to start Remove Sta…ext4 Metadata Check Snapshots.
See 'systemctl status e2scrub_reap.service' for details.
[   ***] A start job is running for Raise ne…rk interfaces (1min 7s / 5min 14s)

Looking at journalctl:

journalctl -xeu e2scrub_reap.service

You will see the following message:

e2scrub_reap.service: Failed to get cgroup ID on cgroup /sys/fs/cgroup/system.slice/e2scrub_reap.service, ignoring: Function not implemented
journalctl -xeu systemd-hostnamed.service

systemd-hostnamed.service: Failed to get cgroup ID on cgroup /sys/fs/cgroup/system.slice/systemd-hostnamed.service, ignoring: Fu

If you disable it, the boot will now be 2 minutes faster:

sudo systemctl disable e2scrub_reap
sudo systemctl disable systemd-hostnamed

You can see the whole boot sequence by connecting to UART. See this ASCIInema record of the boot sequence (local copy of the cast).

LigthDM connexion prompt

At the LightDM Login and pass prompt use:
* Login: sipeed
* Password: licheepi

Then you will have after about less than 1 minutes (yes, that’s a bit slow) the desktop.

Setting the WiFi

You can set your WiFi connexion (and even BlueTooth) with connexion manager. It is accessible from the main menu (the most left-bottom gray icon) by Preferences > Connman Settings, see this picture

access to Connman from menu

Then choose Wireless at left of the new box.Activate the Wifi connexion in Connman
* Click on the gray button to start the wifi (1 in red on the picture).
* Select the network you want to connect to (2 in red)
* Click on connect (3 in red).
* A prompt will open, where you will need to enter the passphrase of the WiFi.
Authentication required

The connexion should be established now, with “Connected” wrote at top of the window and “Online” beside the name of the Wifi router name (as on the picture at right.

You can by pressing on the gear at the right of the router name (2) in previous right picture, have access to some control to have the connexion set automatically at each boot.

click on the greyed autoconnect button, it should become blue meaning autoconnect is activated

Click on the IPv4 at left and then on the Method (set to None) button, choose automatic in the menu as shown in the following picture, then apply at bottom right.

Don’t forget to check also that NameServers is set as you want (by DHCP or static).

I noticed it worked better if I uncommented the following lines in /etc/network/interfaces using

sudo vi /etc/network/interfaces

, it seems to work far better when it’s uncommented (there is a typo: wpa-deriver instead of wpa-driver, but works as is. to remove the # comments, just move the cursors to them and press x one time.

auto wlan0 #(wlp3s0 为网卡名)
iface wlan0 inet dhcp
	wpa-deriver wext
 	wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf

When finished, quit and save with the sequence of keys: “:“, “x!“, ["enter"] key

You can sync your card and reboot safely now:

sudo reboot

It should work fine the next time. you can verify the ip address on your router, or by connecting on the console or interface, and typing:

sipeed@sipeed:~$ ip address
1: lo:  mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: sit0@NONE:  mtu 1480 qdisc noop state DOWN group default qlen 1000
    link/sit brd
3: wlan0:  mtu 1500 qdisc mq state DOWN group default qlen 1000
    link/ether 74:ee:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 192.168.xx.xx/24 brd 192.168.xx.xx scope global dynamic wlan0
       valid_lft 41822sec preferred_lft 41822sec
4: wlan1:  mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 76:ee:xx:xx:xx:xx brd ff:ff:ff:ff:ff:ff
    inet 192.168.xx.xx/24 brd 192.168.xx.xx scope global wlan1
       valid_lft forever preferred_lft forever

or, like the former way:

sipeed@sipeed:~$ sudo ifconfig
[sudo] password for sipeed: 
lo: flags=73  mtu 65536
        inet  netmask
        inet6 ::1  prefixlen 128  scopeid 0x10
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 13  bytes 1793 (1.7 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 13  bytes 1793 (1.7 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

wlan0: flags=-28669  mtu 1500
        inet 192.168.xx.xx  netmask  broadcast 192.168.xx.xx
        ether 74:ee:xx:xx:xx:xx  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

wlan1: flags=-28605  mtu 1500
        inet 192.168.xx.xx  netmask  broadcast 192.168.xx.xx
        ether 76:ee:xx:xx:xx:xx  txqueuelen 1000  (Ethernet)
        RX packets 582  bytes 80736 (78.8 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 504  bytes 82357 (80.4 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

To be more comfortable I suggest to make a swap file on the microSD if you want to use some desktop application, because, 512MB is really short:

Create a swap file of 1GB (1024M) and format it:

sudo dd bs=1M count=1024 status=progress if=/dev/zero of=/swap
sudo mkswap /swap

Add this to /etc/fstab for automatic mount after reboot:

echo "/swap none swap defaults 0 0" |sudo tee -a /etc/fstab/

Then mount it immediately

sudo swapon -a

For more comfortable serial (UART) console usage, you could also install xterm package. It will give you the resize command. When you type resize from your serial connexion, the serial view will adapt to you local Xterm, VTE term, or whatever terminal you use.


On this default image to have audio working on HDMI I suggest ton install and use PAVUcontrol, the best tool I know to manage PulseAudio and PipeWire audio daemons.

sudo apt install pavucontrol

You can launch it in a term like the following line or in menu like on this picture:


With the GUI menu, choose Sound & Video > PulseAudio Volume Control
select Pavu control in menu

Then for HDMI default output in PulseAudio, that is wrapped on PipeWire, select, the Output Devices tab, as blue underlined on picture, then press the green rounded check (I added red square on this picture) beside Build-in Audio Stereo where Analog Output, the first Entry with Headphones is probably the speaker connector on the board (need to try it).
Select Analog Output for HDMI output

Some minors but useful tuning

Crontab installation is broken by default, group crontabs is missing:

apt reinstall cron

To use your language:

sudo vi locale.gen

Uncomment your corresponding line (ex: for french: fr_FR.UTF-8 The two first chars (here fr) are language and the second (here FR) are the country (here France).

You can then set the locale, and the keyboard
You can list available languages layout by:

localectl  list-x11-keymap-layouts
localectl set-locale fr_FR.UTF-8
set-keymap fr
set-x11-keymap fr_FR

To gain some KB you can replace lightdm by xdm (the first default display manager, or nodm that doesn’t prompt for login/password.

For nodm:

sudo apt install nodm

For xdm:

sudo apt install xdm

Anyway you can install both and during the installation, dpkg-configure will ask you in a menu the one you want to use.

By default with nodm, root user will be used, this is really not a good idea. You can change it by editing the nodm config file from root to sipeed:

sudo sed -i s/NODM_USER=root/NODM_USER=sipeed/ /etc/default/nodm

or by using debian dpkg-reconfigure that will ask you several question and change the user:

sudo dpkg-reconfigure nodm

Then reboot or stop LightDM and start another dm:

sudo systemctl stop lightdm
sudo systemctl start xdm

As LicheeRV is a very cheap card, the goal is to have an available board to test RISC-V integration, there is no dedicated Video RAM. You can gain lot of performances by disabling X. Stop lightdm will free the resources of X automatically. You will gain RAM and resources for compilation or other tasks.

sudo systemctl stop lightdm

To disable it permanently use systemctl disable, it will keep this state after reboot

sudo systemctl disable lightdm

You can still re-enable it by using systemctl enable:

sudo systemctl enable lightdm

What is working

MuseScore, Scribus and FontForge
Among application working well, I found:
* Graphics tools: GIMP, Krita work a bit slowly. The first time, need to wait long time, and then go to preferences to disable GL acceleration before creating an image, else it will be awfully slow. Everything will go far faster after that.
* Edition tools: MuseScore (see vidéo), FontForge, Scribus, Inkscape.
* Animation tool: Pencil2D, UPDATE: Glaxnimate (see videos below), I made a Debian package.
* Chat: IRC client Hexchat, and Telegram-desktop client (FOSS Android version)
* Blender work but is totally unusable
* Web browser:: They are generally unusable, the exception is Netsurf (package netsurf-gtk, see screenshot below), that is still slow but a minimum usable, a framebuffer version (netsurf-fb) is pre-installed, but should be used in terminal console view, that is not setup by default. Text browsers like w3m, eLinks, etc, can work. Firefox is unavailable (there is an unofficial method to patch it and compile it for RISC-V, need to test it, but I doubt it will be efficient, a 3 or 4 year old version, could be better. There is an official patch but seem to be no more available? As often, Firefox like to block progress on new technologies….
screenshot with Gimp, Pencil2D, Hexchat (irc client)
* Web server: HTTPd Apache and Nginx works You can test my installation of Nginx, when it is up, here. I let up for few days (it should consume 2,5W maximum (5V*0.5A power via serial or sometime USB), but could some times reboot for update, I wait for solar panels to plug it on.

I put checkers on name to not display private informations:
Screenshot with Blender and Telegram

Netsurf-GTK has little display bugs, but is relatively usable. Still not reactive for typing URL in URL bars.
Screenshot of Netsurf-GTK

Telegram rendering of the Glaxnimate animation:

Launching of MuseScore:

Booting Ubuntu Linux on a LicheeRV

Sipeed made a microSD card image to boot Ubuntu on RISC-V based Allwinner D1 SoC. with their LicheeRV SoM.

Boot sequence on ASCIInema (local copy)

I made a copy of the image in my own repository, that’s faster/easier to download.
* SHA256sum: 4a414a36ba5ae8000bd2f8ee088ea399b502527e1868662427bc00676d65ca79

Just download the archive, untar and follow the instruction in the README. There is an error, the primary partition should start at 80MB (163840), not 40MB (81920). The is limited to 4Go, so it should be grown with resize2fs to have more place to work.

The whole process so:

You must first set the SDCARD device name. For me it was /dev/sdd warning if you are wrong you can delete your system ^^. You can have it’s value by using dmesg just after plugging it:

sudo dmesg | tail

or by using fdisk -l. You need a microSD card with 16GB or more space.

export SDCARD=/dev/sdd

Then follow the following script:

tar xf licheerv_d1_1.14lcd_ub2004.tgz
cd licheerv_d1_ub2004_20220104a
sudo wipefs -a "${SDCARD}"
sudo fdisk "${SDCARD}"

In fdisk:


then continue the process:

sudo dd if=boot0_sdcard_sun20iw1p1.bin of="${SDCARD}" bs=512 seek=16
sudo dd if=d1-kernel.toc1 of="${SDCARD}" bs=512 seek=32800 status=progress

Then depending on the name of the device on your system, if its of kind /dev/mmcblk0 then you should use /dev/mmcblk0p1 for partition 1, if it’s a /dev/sdd then, it’s /dev/sdd1.

if you use a /dev/mmcblkX type of device (typically internal SDcard reader) use this:

sudo dd if=ub2004_rootfs.img of="${SDCARD}p1" bs=1M status=progress
sudo resize2fs "${SDCARD}p1"

if you use a /dev/sdX type of device (typically an USB SDcard reader) use this:

sudo dd if=ub2004_rootfs.img of="${SDCARD}1" bs=1M status=progress
sudo resize2fs "${SDCARD}1"

After the sync, you can unplug the microSD card, plug it in your Lichee RV turn on the SoM, wait few seconds, after OpenSBI have booted it will open the kernel and you will see the Ubuntu boot sequence on screen.

Here is the boot with the LicheeRV dock, allowing to plug USB keyboard or other device, and HDMI, still need to manage it. There are some Debian image with HDMI working, but need proprietary Windows tools to make the microSD card :(.

Update: Another interesting image with more features Debian Bulleseye 11 from RVBoards, but with very strange FS (Android ?) and only 4GB partition (the /opt could be hacked). On their mirror (can be slow), local copy
* Sha256sum: e4a042d3a7c0658ffa009185488164eb18bd49fd92928cdec190a80f15b0c86b
* Just need to unzip and dd to the microSDcard.
* ASCIInema record of the boot sequence (local copy). This image is for Allwinner Nezha, so lot of things will not work and throw error message. But on the other side, most peripherals are detected, could be interesting for tuning. Linux condig.gz of the kernel

(Français) Installer le système embarqué POSIX, Apache NuttX sur une carte basée sur un RISC-V ESP32-C3 avec Arch Linux

Table of content

Apache NuttX logo
* Introduction
* System packages
* NuttX sources and tools
* Configuration of devkit project, compilation and flash
* Connexion to NSH via (USB) serial
* The ostest and other basic sets (UPDATE)
* The apps examples (UPDATE)
* SPIflash, SMARTFS and file fsystems (UPDATE)
* NSH scripting (UPDATE)


Apache NuttX is a POSIX embedded system available on a lot of microcontrollers boards and architectures. After seeing some articles from Lup Yuen Lee, installing and working with NuttX on Bouffalo BL602 and BL604 RISC-V microcontroller boards, I discovered it can be installed on one of my boards. So I tried and managed to install it this evening on my recently acquired 3.5€ ESP32-C3S SoC nodeMCU board. ESP32-C3 is a SoC with RISC-V RV32IMC microcontroller, integrated 2.4GHz WiFi and Bluetooth LTE. The board contains a CH340 serial-USB converter, so it can easily be used/flashed/debugged from a computer. I already made a post about installing ESP-IDF tools and flashing examples on this RISC-V board.

For people that already know Espressif SoCs, here is a table of the power usage of some of their ESP models:

SoC        Modem sleep  Light sleep mode  Deep sleep mode
ESP8266          20 mA          2,000 µA            20 µA
ESP32            20 mA            800 µA            20 µA
ESP32-C3         20 mA            130 µA             5 µA

This article explains the procedure to prepare environment, on Arch Linux in November 2021. This is for x86_64, but should work on ARM too, only RISC-V toolchains are missing on ALARM, can be compiled, by using x86_64 versions of PKGBUILD (riscv32-elf-binutils, riscv64-elf-gcc). You can find the pricompiled binaries in my ArchLinux ARM archives including a little text about the order of compilation (binlib, gcc-bootstrap, newlib, gcc (and optionnaly, gcc and newlib again). Direct link to the three most usefull archives:
* riscv32-elf-binutils-2.36.1-2-armv7h.pkg.tar.xz
* riscv64-elf-gcc-11.1.0-1-armv7h.pkg.tar.xz
* riscv64-elf-newlib-4.1.0-1-any.pkg.tar.xz

Latest GIT version is needed In November 2021 for ESP32-C3, some other RISC-V architectures are already in stable releases. This is followed by an example of flashing and connect to the NSH shell, via serial on USB terminal. The dependencies for Debian based Linux on the official page, some parts could be incomplete. Some aspects of the NuttX, POSIX compatible Filesystem. OStest, GPIO and SPIflash included examples are also shortly described.

System packages

General system dependencies for NuttX:

sudo pacman -S --needed base-devel ncurses5-compat-libs gperf pkg-config \
   gmp libmpc mpfr libelf expat picocom uboot-tools util-linux git wget

Just press enter to select all packages on the base-devel packages group.

You also need some AUR packages, I still use obsolete Pacaur that some say is obsolete:

pacaur -S --needed  isl kconfig-frontends genromfs

Sadly for my case, there is currently a conflict between kendryte-toolchain-bin (used for Kendryte K210 RV64 SoC), that depend on isl19, currently conflicting with isl, I uninstalled kendryte-toolchain-bin package, hope it would still work from binary archive with last isl version (0.24 now), didn't managed to compile kendryte-toolchain from sources with it.

Specific RISC-V and ESP32 tools, riscv64 GCC is used to compile for both RV64 and RV32 architectures, but we need RV32 specific version of binutils here.:

sudo pacman -S --needed esptool riscv64-elf-gcc riscv32-elf-binutils

Specific RISC-V and ESP32 tools, AUR part (we don't use it in this article, but OpenOCD (Open On-Chip Debugger) can be useful for debugging):

pacaur -S openocd-esp32

You will need a special trick with GNU compilation toolchain, as the current version of NuttX search for riscv64-unknown-elf-* and Arch Linux call them riscv64-elf-* (without unknown-. I just created symlinks in /usr/bin/. Need root privileges for this (UPDATE: g++ was also needed by some optional examples applications):

sudo bash
for tool in gcc ar ld nm objcopy g++
  if [ ! -e /usr/bin/riscv64-unknown-elf-${tool} ]; then
    ln -s riscv64-elf-${tool} /usr/bin/riscv64-unknown-elf-${tool}

Or a more radical solution, to have a link for all existing riscv64-elf-* tools:

sudo bash
cd /usr/bin/
ls riscv64-elf-* | while read bin
do tool=${bin//riscv64-elf-}
  if [ ! -e riscv64-unknown-elf-${tool} ]
    ln -s ${bin} riscv64-unknown-elf-${tool}

NuttX sources and tools

Choose a directory where you will install the tools. I choose a directory called nuttx here:

mkdir nuttx
cd nuttx

Some binaries are needed to creating the file system. The booting partition and the partition table. You can compile them by yourself, but I here just chosen to download already compiled ones, I would maybe update this post with compilation process:


We will just use bootloader and partition-table binaries here, but there is also mcuboot binary in the repository, not sure I will need it later, but I downloaded it to have everything for working in my archives:


NuttX sources are also needed:

git clone nuttx
git clone apps

Update November 26, version 10.2.0 stable is out this week with ">esp32-c3 available by default, NuttX and NuttX-apps tarballs (list + download link for each version), Warning they are both called nuttx-version.tar.gz (Github is stupid), but they contain respectively incubator-nuttx-nuttx-version and incubator-nuttx-apps-nuttx-version files trees

So you can just download them this way to avoid problems:

wget -O incubator-nuttx-nuttx-10.2.0.tar.gz
wget -O incubator-nuttx-apps-nuttx-10.2.0.tar.gz

And unarchive them this way:

tar xf incubator-nuttx-nuttx-10.2.0.tar.gz
tar xf incubator-nuttx-apps-nuttx-10.2.0.tar.gz
ln -s incubator-nuttx-nuttx-10.2.0 nuttx
ln -s incubator-nuttx-apps-nuttx-10.2.0 apps

So they can be usable the standard way.

Configuration of devkit project, compilation and flash

We have now all necessaries tools to prepare and install a bootable and functional system. We now enter in the nuttx directory:

cd nuttx

This is time to choose a project. You can see a list of existing ones for ESP32-C3 by typing:

./tools/ -L | grep esp32c3

Description of some of them are available in the ESP32 (not C3) specific part of the doc

I choose "usbconsole" that contain NSH shell and allow to easily connect via USB using ttyUSB.

./tools/ -l esp32c3-devkit:usbconsole

Then you can tune some advanced parameters but I didn't used it, looks like ESP32-C3 is generic enough to not have to do anything special depending on board, so can be passed.

make menuconfig

You can quit it by pressing 2 times esc key.

Time to compile it now. We can compile it with only one CPU core of the computer:


Or to compile faster (if you don't have too low memory, else it could be slower), you can can add for example -j4 to use 4 cores or your computer:

make -j4

A long compilation sequence will be printed, ending by the names of the binaries (in colour here). This will be the system to be flashed.

CP: nuttx.hex
CP: nuttx.bin
MKIMAGE: ESP32-C3 binary -c esp32c3 elf2image -fs 4MB -fm dio -ff 40m -o nuttx.bin nuttx v3.2
Generated: nuttx.bin (ESP32-C3 compatible)

For flashing it, you need to first plug your ESP32-C3 based board on your computer, then use flashing command. Parameters needed are chip type, (here an esp32c3), serial port where board is connected to (here ttyUSB0) and transfer rate (921600). Following parameters are the memory address where the binaries will be flashed followed by their names (binary files names are coloured here). So bootloader start at 0, partition table start at 0x8000 and NuttX binary we just compiled start at 0x10000. Warning: bootloader and partition-table need to be flashed only when you install for the first time NuttX, the number of write cycle of a flash is limited so only flash what you need: --chip esp32c3 --port /dev/ttyUSB0 --baud 921600 write_flash \
   0x0 ../bootloader-esp32c3.bin \
   0x8000 ../partition-table-esp32c3.bin \
   0x10000 nuttx.bin

Here is the displayed upload, I coloured (and masked) the MAC address. It can be useful to keep it for later. also colored the flashing of the 3 binaries. v3.2
Serial port /dev/ttyUSB0
Chip is ESP32-C3 (revision 3)
Features: Wi-Fi
Crystal is 40MHz
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 921600
Configuring flash size...
Flash will be erased from 0x00000000 to 0x00004fff...
Flash will be erased from 0x00008000 to 0x00008fff...
Flash will be erased from 0x00010000 to 0x00030fff...
Compressed 19120 bytes to 11416...
Wrote 19120 bytes (11416 compressed) at 0x00000000 in 0.5 seconds (effective 324.4 kbit/s)...
Hash of data verified.
Compressed 3072 bytes to 69...
Wrote 3072 bytes (69 compressed) at 0x00008000 in 0.1 seconds (effective 433.2 kbit/s)...
Hash of data verified.
Compressed 131264 bytes to 51049...
Wrote 131264 bytes (51049 compressed) at 0x00010000 in 1.9 seconds (effective 542.5 kbit/s)...
Hash of data verified.

Hard resetting via RTS pin...

Et voilà! The board is flashed (and restarted). In case of errors here, I wrote few lines about most common problems when flashing microcontrollers or FPGA boards using USB on Linux.

Update: I found a new possible problem. If you are already connected to the board by a terminal, the fuse will fail with the following error:

serial.serialutil.SerialException: device reports readiness to read but returned no data (device disconnected or multiple access on port?)

Just have to quit the terminal application, and restart the previous command, everything should run fine now.

Connexion to NSH via (USB) serial

You can now connect with any serial terminal tool like screen (yes it has this functionality too), gtkterm (with a GUI), or lot of others. I used Picocom as proposed by NuttX documentation.
One of the interesting aspect of NuttX, is that you have an integrated shell called NSH (for NuttShell).

picocom -b 115200 /dev/ttyUSB0

To escape from Picocom you need to type: ctrl-a then ctrl-x. Picocom man page contains all the needed shortcuts.

After connecting to the terminal, just type on enter key to have a NSH prompt, you have an integrated help, with the help command. See the more detailed NSH commands description in the official documentation.

nsh> help
help usage:  help [-v] []

  .         cd        echo      hexdump   mv        set       truncate
  [         cp        exec      kill      printf    sleep     uname
  ?         cmp       exit      ls        ps        source    umount
  basename  dirname   false     mkdir     pwd       test      unset
  break     dd        free      mkrd      rm        time      usleep
  cat       df        help      mount     rmdir     true      xd

Builtin Apps:
  nsh  sh

NuttX is POSIX, so as any UniX environment, every device can be accessed via files. Like on Linux, you have the 2 important directories; /dev containing the devices files, and /proc containing system and devices parameters.

Update: If you are stuck for any reason, you can send a reset signal to the board by pressing two times the F7 function key in the terminal.

nsh> ls
nsh> ls -l /dev
 crw-rw-rw-       0 console
 crw-rw-rw-       0 null
 crw-rw-rw-       0 ttyS0
 crw-rw-rw-       0 zero
nsh> ls -l /proc
 dr--r--r--       0 0/
 dr--r--r--       0 1/
 -r--r--r--       0 meminfo
 dr--r--r--       0 fs/
 dr--r--r--       0 self/
 -r--r--r--       0 uptime
 -r--r--r--       0 version

In the included commands, mkrd allow to create a RAMDISK file system

Update: This was the NuttX version 10.2.0-RC0 c7e604b20b-dirty after cat /proc/version.

The ostest and other basic sets (UPDATE)

The sources of the demo programs can be found in boards/risc-v/esp32c3/esp32c3-devkit/src/ subdirectory and the .config settings, that can also be changed with make menuconfig, for each demo are located in the boards/risc-v/esp32c3/esp32c3-devkit/configs/ directory.

For my second test, I tried esp32c3-devkit:ostest receipe. The system include a command called ostest, that try different systems usage, including memory allocation, timers or multithreading.

If you have already compiled a project and try to compile another one you will have an alert:

Already configured!
Please 'make distclean' and try again.

You only need to follow the instruction and make a distclean to be able to compile another one:

make distclean
[...] <- lot of cleaning
./tools/ -l esp32c3-devkit:ostest

As we already flashed the boot en partition table and number of write cycle of flash are limited (to some thousand of times, look at the flash eprom specification), you can limit the flash to the nuttx.bin part itself: --chip esp32c3 --port /dev/ttyUSB0 --baud 921600 write_flash \
   0x10000 nuttx.bin

You will only be able to see the name of the command by typing help :

nsh> help
help usage:  help [-v] []
[...] <- standard commands
Builtin Apps:
  nsh     ostest  sh

When the ostest finish on this system image, you will see memory used. and the filesystem will have a new directory /var with a subdirectory /var/mqueue added.

nsh> ostest
[...] <= lot of tests
barrier_func: Thread 6 done
barrier_test: Thread 5 completed with result=0
barrier_test: Thread 6 completed with result=0
barrier_test: Thread 7 completed with result=0

End of test memory usage:
======== ======== ========
arena       5d2e0    5d2e0
ordblks         8        8
mxordblk    56710    53ab0
uordblks     5de0     9640
fordblks    57500    53ca0

Final memory usage:
======== ======== ========
arena       5d2e0    5d2e0
ordblks         2        8
mxordblk    58ae0    53ab0
uordblks     47f0     9640
fordblks    58af0    53ca0
user_main: Exiting
ostest_main: Exiting with status 0
nsh> ls var/

As with Linux, you can know the total, used and free memory by displaying the content of /proc/meminfo. The builtin command free do the same thing.

nsh> cat /proc/meminfo
                   total       used       free    largest  nused  nfree
        Umem:     382752       6128     376624     376624     32      1

Among the other tests, some demos that interest me are:
* esp32c3-devkit:gpio to access to GPIO (general purpose Input/Output) via command line.
* esp32c3-devkit:spiflash to access the flash disk using NuttX SMART Flash file system via SPI1. The command mksmartfs can be used to make the FS, and the FS can be mounted the POSIX way (like Linux for example) by typing mount -t smartfs /dev/... /mntpoint. It add mksmartfs command and flash_eraseall and fstest builtin apps.
* esp32c3-devkit:lvgl demo, LVGL is a small graphic and GUI library that help to make interfaces in small memory embedded board systems. Demos include drivers for Sitronix ST7735 (262K Color Single-Chip TFT Controller/Driver) and ST7789 SPI displays controllers in esp32c3_st7735.c and esp32c3_st7789.c in boards/risc-v/esp32c3/esp32c3-devkit/src/. I need to figure out how to connect one. Breadboards will help for this test. Another solution is probably to try it using Qemu?? With this demo the RGB led of the board is set to orange. There is a Youtube video of this demo on an ESP32 (Xtensa) version monothread and multithread, side by side. The ESP32-C3 has only one core. The command in the system is called lvgldemo. Nothing appear on terminal console, so I don't know at all what this demo do. There are some documentation about NuttX and LVGL on the LVGL documentation site. There is also tutorial site.

The apps examples (UPDATE)

We previously downloaded the git apps repository beside the nuttx repository. The apps, contains examples beyond the basic vital functionalities of the system. These resources can be set in the .config file (flags starting with CONFIG_EXAMPLES_) or by using make menuconfig.

In the menu config the apps are in the last entry. Go down with cursor keys and when you are on the last entry, just press enter:

Then the application configuration submenu will display. Warning, you have generally limited resources on the board, select options with parsimony. The best is to first choose the already prepared set for your platform and then to activate some if they work:
* Cryptographic Library Support contains LibTomCrypt and Mbed TLS Cryptography libraries support.
* The Examples subset contains interesting tools like, audio generator (need external dependencies), uIP web server (implementation on uIP TCP/IP stack, that include a DHCP client), tools to blink LEDs (warning PowerLED needs external dependencies), battery monitor, camera driver, the LVGdemo, pdcurses (a GUI text lib).
* The filesystem utilities contains mkgpt, mkbr (for managing fs), a password file support (need external dependencies) etc... :
* The network utilities contains, a chat tool, a sJSON and CODEC libraries, an FTP client, MQTT-C (a MQTT messages client), and a remote execution server and client.
* The NSH library allow you to tune NSH parameters, including MOTD management, a more heavy and complete Command Line Editor, instead of the minimal readline(), or backslash of characters.
* The System Libraries of NSH add-ons, allow to enable system() interface (and so call of NSH function from C), Terminal Curses control support an hexadecimal file editor, or a minimal CU terminal editor. There are also options to use Emacs, VIM or Ncurses mode interface. Zmodem and GPIO u-Block modem., SPI tools, etc...
* Wireless Libraries and NSH Add-ons, contains Bluetoot and IEEE 802.15.4 applications including both version of "Swiss army knife".


The esp32c3-devkit:gpio add a command to access to GPIO (general purpose Input/Output) via command line. It can be set in make menuconfig by Application configuration ---> Examples ---> [*] GPIO driver example, or by setting the .config file.

To have GPIO activated:

# IO Expander/GPIO Support

To have the command example:


With this a new gpio command appear, if you type it without arguments, it will help you (-h argument works too):

nsh> gpio
ERROR: Missing required arguments
USAGE: gpio [-w ] [-o ] 
       gpio -h
	: The full path to the GPIO pin driver.
	-w : Wait for an signal if this is an interrupt pin.
	-o :  Write this value (0 or 1) if this is an output pin.
	-h: Print this usage information and exit.

You can list the available devices (driver path in the help). Here is a long (-l) listing, and you can see they are character devices files with the initial c:

nsh> ls -l /dev
 crw-rw-rw-       0 console
 crw-rw-rw-       0 gpint0
 crw-rw-rw-       0 gpout0
 crw-rw-rw-       0 gpout1
 crw-rw-rw-       0 null
 crw-rw-rw-       0 ttyS0
 crw-rw-rw-       0 zero

In the example source boards/risc-v/esp32c3/esp32c3-devkit/src/esp32c3_gpio.c, we can see than the pins 1 and 2 are set to the two first gpout, here gpout0 and gpout1 and that only the pin 9 is used for the interrupts gpint0:

/* Pin 1 and 2 are used for this example as GPIO outputs. */

#define GPIO_OUT1  1
#define GPIO_OUT2  2
/* Interrupt pins.  GPIO9 is used as an example, any other inputs could be
 * used.

#define GPIO_IRQPIN  9

Here is a map of the pins on my board, thanks to J-C. François, licence CC-BY-SA:

The pins 1 and 2 are at the upper left, and the pin 9 at the upper right

The current state of a GPIO can be know by a cat. here for example 0 state (the result is without return carriage so just touch by nsh:

nsh> cat /dev/gpout0

Writing a value 1 to the first pin defined (so pin 1) the example display the current value, then change it and verify its state:

nsh> >gpio -o 1 /dev/gpout0
Driver: /dev/gpout0
  Output pin:    Value=0
  Writing:       Value=1
  Verify:        Value=1

SPIflash, SMARTFS and file systems (UPDATE)

The SPIflash recipe ( esp32c3-devkit:spiflash ), add the commands mksmartfs and the builtin apps flash_eraseall and fstest.

To use fstest, you need to prepare the fs and mount it.

In the default included commands, mkrd allow to create a RAMDISK file system. It can be used for tests and avoid to waste flash writing cycles.

The syntax is (help mkrd to display it):

mkrd usage:  mkrd [-m ] [-s ] 

Default secteur size is 512 bytes. We can simply create a 10KB RAM disk, so 512×20 = 10240 bytes = 10 kilobytes bytes, by typing:

mkrd 20

A ramdisk device will appear in /dev called ram0. Filesystems are blocks devices (shown by the initial b, c is character device):

nsh> ls -l /dev
 crw-rw-rw-       0 console
 crw-rw-rw-       0 null
 brw-rw-rw-   10240 ram0
 brw-rw-rw-  983040 smart0
 crw-rw-rw-       0 ttyS0
 crw-rw-rw-       0 zero

You can also see the smart0 device corresponding to the disk, is also available with the SPIflash recipe. sadly RAM disk isn't supported by smartFS so it can't be formatted as is. VFat is supported, but need to add it at configure time. You can activate TMPFS (see below) that also use RAM, as on Linux, for working on temporary RAM disk.

* SmartFS support can be activated by make menuconfig in File Systems ---> -*- SMART file system
* smartfs application can be selected by make menuconfig in Application Configuration ---> File System Utilities ---> mksmartfs.

Flags set in .config, for SMARTFS support:


And for command utilities:


smart0 need to be formated before mounting it. The data will be kept on it after reboot, or flashing a new code.

nsh> mksmartfs /dev/smart0

In POSIX systems, we need to create an empty directory used as a mount point, then mount the partition. The type of filesystem isn't auto-detected, so we need to pass it to mount command here:

mkdir /mnt
mount -t smartfs /dev/smart0 /mnt

It will make a test loop of 100 iterations, of writing and then deleting files in the mountpoint, I put the ouput. If you really want to test the fs you can use it, but you will waste write cycles of the flash :

The commande is:


Here is a sample of the output, I stopped it by resetting the card (2 times F7:

=== FILLING 4 =============================
168. Type[8]: File  Name: iJaUkEIwgSG4cAyl8J
169. Type[8]: File  Name: FNLd0XaTSYF8TV3YtO
170. Type[8]: File  Name: SV0Lrtmigq9Yg4SauoD4f
171. Type[8]: File  Name: GJfbQ0bFaowO0ep
Total file size: 427496

=== DELETING 4 ============================
Deleted some files
  Number of files: 215
  Number deleted:  108
 1. Type[8]: File  Name: PRMIRHz9ZwgHga
 2. Type[8]: File  Name: 9LH4Uf67RL4bFXVCykg3
Total file size: 427496
File System:
  Block Size:      1024
  No. Blocks:      960
  Free Blocks:     457
  Avail. Blocks:   457
  No. File Nodes:  0
  Free File Nodes: 457

End of loop memory usage:
======== ======== ========
arena       58fb0    58fb0
ordblks        41       57
mxordblk    510a0    510a0
uordblks     77d0     6f40
fordblks    517e0    52070[...]

As I stopped it during the loop the files are still on the fs. Wildcard are not supported by the rm command.

As we can see with the df (disk free) command, after deleting some file, this still uses lot of blocks:

nsh> df
  Block    Number
  Size     Blocks       Used   Available Mounted on
  1024        960        725         235 /mnt
     0          0          0           0 /proc

I used the flash_eraseall builtin app to erase evrything:

nsh> flash_eraseall /dev/smart0

This remove the files but don't free the blocks. I needed to force a mksmartfs with the option -f to free the blocks:

It removed the files but didn't cleaned the block. I need to format again the device using:

nsh> mksmartfs -f /dev/smart0

Now All the blocks are available again:

nsh> df
  Block    Number
  Size     Blocks       Used   Available Mounted on
  1024        960         10         950 /mnt
     0          0          0           0 /proc

For example, write a single file to be kept after reboot. we can write the content of the file with cat or echo commands for example. I write here a simple sentence this is kept datas in the test.txt file, using the file redirection character (>):

nsh> echo "this is kept datas" >/mnt/test.txt
nsh> df
  Block    Number
  Size     Blocks       Used   Available Mounted on
  1024        960         11         949 /mnt
     0          0          0           0 /proc

As you can see this took an entire 512 bytes block even if it's an only 18 bytes string file. Every file written use a full block, so try to choose the sector-size when you use mksmartfs.

After rebooting the device (2 times F7) or unplug/replug it. You can still see the data on the file. Note that after each reboot, you need to mount afain the filesystem:

nsh> mount -t smartfs /dev/smart0 /mnt
nsh> cat /mnt/test.txt
this is kept datas

Removing files by rm command effectively free the blocks:

nsh> rm test.txt
nsh> df
  Block    Number
  Size     Blocks       Used   Available Mounted on
  1024        960         10         950 /mnt

Among the other available file systems in NuttX, there is LittleFS, CNX-Software made an article about it. It is very compact and reliable, but there is no utilies to manage them in applications, need to use libs or code a shell compatible command. The Source Git repository shows an exemple usage in C

By make menuconfig select Board Selection ---> [*] Mount SPI Flash MTD on bring-up (LittleFS) ---> (X) LittleFS (Only one file system can be set at a time here), and

In the .config, if the file sustem is not set for the board:



For the more general LITTLEFS support, by make menuconfig, in File Systems ---> -*- LITTLEFS File System (several can be selected in this part).

Options in .config:


With this setting (after selecting SPIflash), there is a esp32c3flash device that replace smart0.

And The smartFS can't be mounted. The error message is a bit erroneous:

nsh> mount -t smartfs /dev/esp32c3flash /mnt
nsh: mount: mount failed: No such device


If you choose in make menuconfig the option File Systems -> [*] TMPFS file system

Default TMPFS option in .config


You will see an automounted /tmp, it is 512 blocks wide on my board (so 512×512/1024=512/2=256KB of RAM disk. 1 block is used for the file system root directory (mounted in /tmp):

nsh> df
  Block    Number
  Size     Blocks       Used   Available Mounted on
     0          0          0           0 /proc
   512          1          1           0 /tmp

TMPfs allow to use the free memory, but don't really use it until your write files inside:

nsh> cat /proc/meminfo
                   total       used       free    largest  nused  nfree
        Umem:     364464      10016

354448 354448 38 1

I cd (change directory) in /tmp here and write a file, it will take one more 512 bytes block and so, 10016 + 512 (block) + 176 (probably some references in filesystem dictionnary) = 10704 bytes of memory will be used. The size of the refs vary it took only 96 bytes on another test.

nsh> cd /tmp
nsh> echo "This is a test" >test
nsh> df
  Block    Number
  Size     Blocks       Used   Available Mounted on
     0          0          0           0 /proc
   512          2          0           2 /tmp
nsh> cat /proc/meminfo
                   total       used       free    largest  nused  nfree
        Umem:     364464      10704     353760     353744     42      2

NSH scripting (UPDATE)

You can make simple shell script in NSH. For exemple this one lie exemple test if /dev/ram0 exists and then print the result of the test.

if [ -e /dev/ram0 ]; then echo "ram0 exists"; else echo "no ram0"; fi

Variable can be set and unset

nsh> set foo bar
nsh> echo $foo

script can be put in files. I didn't found an easy way to put them. The cat function, seems to be limited to go from one file to another, it doesn't manage STDIN a standard way, and there is no editor. The only solution is to echo line by line, or to transfer files by network or serial (need to search more).
If you have activated the TMPFS as explained previously or formatted and mounted SMARTFS partition, you can write files inside. Here is an exempla with TMPFS, available in /tmp by default I removed the initial nsh> here, so it will be easier to copy paste:

echo if [ -e /dev/ram0 ] >/tmp/ 
echo then >>/tmp/
echo "  echo ram0 exists" >>/tmp/
echo else >>/tmp/
echo "  echo ram0 doesn't exist" >>/tmp/
echo fi >>/tmp/

The >> symbol means you append lines to an existing file instead of overwriting it.
you can now see the script by a cat:

nsh> cat /tmp/
if [ -e /dev/ram0 ]
  echo ram0 exists
  echo ram0 doesn't exist

And it can be executed by two means, sh or source, the difference,

nsh> sh /tmp/
ram0 doesn't exist

Now, if we create ram0 by using mkrd:

nsh> mkrd 20
nsh> sh /tmp/
ram0 exists