Tag Archives: RISC-V

Installing Apache NuttX POSIX embedded OS on RISC-V based ESP32-C3 with 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)
* GPIO (UPDATE)
* SPIflash, SMARTFS and file fsystems (UPDATE)
* TMPFS (UPDATE)
* NSH scripting (UPDATE)

Introduction

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++
do
  if [ ! -e /usr/bin/riscv64-unknown-elf-${tool} ]; then
    ln -s riscv64-elf-${tool} /usr/bin/riscv64-unknown-elf-${tool}
  fi
done

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} ]
  then
    ln -s ${bin} riscv64-unknown-elf-${tool}
  fi
done

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:

wget https://github.com/espressif/esp-nuttx-bootloader/releases/download/latest/bootloader-esp32c3.bin
wget https://github.com/espressif/esp-nuttx-bootloader/releases/download/latest/partition-table-esp32c3.bin

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:

wget https://github.com/espressif/esp-nuttx-bootloader/releases/download/latest/mcuboot-esp32.bin

NuttX sources are also needed:

git clone https://github.com/apache/incubator-nuttx nuttx
git clone https://github.com/apache/incubator-nuttx-apps 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 https://github.com/apache/incubator-nuttx/archive/refs/tags/nuttx-10.2.0.tar.gz
wget -O incubator-nuttx-apps-nuttx-10.2.0.tar.gz https://github.com/apache/incubator-nuttx-apps/archive/refs/tags/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/configure.sh -L | grep esp32c3
  esp32c3-devkit:adc
  esp32c3-devkit:autopm
  esp32c3-devkit:ble
  esp32c3-devkit:bmp180
  esp32c3-devkit:efuse
  esp32c3-devkit:elf
  esp32c3-devkit:gpio
  esp32c3-devkit:lvgl
  esp32c3-devkit:mcuboot_confirm
  esp32c3-devkit:module
  esp32c3-devkit:nsh
  esp32c3-devkit:oneshot
  esp32c3-devkit:ostest
  esp32c3-devkit:pm
  esp32c3-devkit:pwm
  esp32c3-devkit:random
  esp32c3-devkit:romfs
  esp32c3-devkit:rtc
  esp32c3-devkit:sotest
  esp32c3-devkit:spiflash
  esp32c3-devkit:sta_softap
  esp32c3-devkit:tickless
  esp32c3-devkit:timer
  esp32c3-devkit:uid
  esp32c3-devkit:usbconsole
  esp32c3-devkit:wapi
  esp32c3-devkit:watchdog
  esp32c3-devkit:watcher

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/configure.sh -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:

make

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
esptool.py -c esp32c3 elf2image -fs 4MB -fm dio -ff 40m -o nuttx.bin nuttx
esptool.py 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 esptool.py 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:

esptool.py --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.

esptool.py v3.2
Serial port /dev/ttyUSB0
Connecting....
Chip is ESP32-C3 (revision 3)
Features: Wi-Fi
Crystal is 40MHz
MAC: XX:XX:XX:XX:XX:XX
Uploading stub...
Running stub...
Stub running...
Changing baud rate to 921600
Changed.
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.

Leaving...
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 esptool.py 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
/:
 dev/
 proc/
nsh> ls -l /dev
/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
/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/configure.sh -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:

esptool.py --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:
VARIABLE  BEFORE   AFTER
======== ======== ========
arena       5d2e0    5d2e0
ordblks         8        8
mxordblk    56710    53ab0
uordblks     5de0     9640
fordblks    57500    53ca0

Final memory usage:
VARIABLE  BEFORE   AFTER
======== ======== ========
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/
/var:
 mqueue/

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 LVGL.academy 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".

GPIO (UPDATE)

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:

CONFIG_ESP32C3_GPIO_IRQ=y
# IO Expander/GPIO Support
CONFIG_DEV_GPIO=y
CONFIG_DEV_GPIO_NSIGNALS=1

To have the command example:

CONFIG_EXAMPLES_GPIO=y
CONFIG_EXAMPLES_GPIO_PROGNAME="gpio"
CONFIG_EXAMPLES_GPIO_PRIORITY=100
CONFIG_EXAMPLES_GPIO_STACKSIZE=2048

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
Where:
	: 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
/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
0nsh> 

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

CONFIG_ESP32C3_SPIFLASH_SMARTFS=y
CONFIG_FS_SMARTFS=y
CONFIG_SMARTFS_ERASEDSTATE=0xff
CONFIG_SMARTFS_MAXNAMLEN=48
# CONFIG_SMARTFS_MULTI_ROOT_DIRS is not set
# CONFIG_SMARTFS_ALIGNED_ACCESS is not set
# CONFIG_FS_PROCFS_EXCLUDE_SMARTFS is not set

And for command utilities:

CONFIG_FSUTILS_MKSMARTFS=y
# CONFIG_NSH_DISABLE_MKSMARTFS is not set

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:

fstest

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
Directory:
 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:
VARIABLE  BEFORE   AFTER
======== ======== ========
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:

# CONFIG_ARCH_CHIP_LITEX is not set
# CONFIG_ESP32C3_SPIFLASH_LITTLEFS is not set

or if set: CONFIG_ESP32C3_SPIFLASH_LITTLEFS=y

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:

CONFIG_FS_LITTLEFS=y
CONFIG_FS_LITTLEFS_BLOCK_FACTOR=4
CONFIG_FS_LITTLEFS_BLOCK_CYCLE=200

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

TMPFS (UPDATE)

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

Default TMPFS option in .config

CONFIG_FS_TMPFS=y
CONFIG_FS_TMPFS_BLOCKSIZE=512
CONFIG_FS_TMPFS_DIRECTORY_ALLOCGUARD=64
CONFIG_FS_TMPFS_DIRECTORY_FREEGUARD=128
CONFIG_FS_TMPFS_FILE_ALLOCGUARD=512
CONFIG_FS_TMPFS_FILE_FREEGUARD=1024

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
bar

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/script.sh 
echo then >>/tmp/script.sh
echo "  echo ram0 exists" >>/tmp/script.sh
echo else >>/tmp/script.sh
echo "  echo ram0 doesn't exist" >>/tmp/script.sh
echo fi >>/tmp/script.sh

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/script.sh
if [ -e /dev/ram0 ]
then
  echo ram0 exists
else
  echo ram0 doesn't exist
fi

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

nsh> sh /tmp/script.sh
ram0 doesn't exist

Now, if we create ram0 by using mkrd:

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

Using ESP-IDF with RISC-V ESP32-C3 nodeMCU module on a Linux system

>NodeMCU ESP32-C3S”></a></p>
<h2 id=Table of Content

* Introduction
* Step 1, install dependencies
* Step 2, install ESP-IDF
* Step 3, setup working environment
* Step 4, test “Hello World” example
* Step 5, tune the target, if needed
* Step 6, flash the project
* Step 7, viewing the output
* Troubleshooting
** Invalid value for ‘{esp32|esp32s2}’: ‘esp32c3′ is not one of ‘esp32′, ‘esp32s2′
** libsodium/crypto_box/curve25519xchacha20poly1305/box_seal_curve25519xchacha20poly1305.c
** Flashing errors
*** Detection, power, and device that disappear
*** usbfs: interface 0 claimed by ch341 while ‘brltty’ sets config #1
** Problem with CA certificates bundle

Introduction

The list of existing ESP32-C3 SoC modules (with their specs) is very interesting, This one RISC-V core SoC has lower computing power than the ESP32-S3 (dual-core Xtensa ISA based), but RISC-V architecture is also far more efficient and more will for sure have longer lifetime support and evolution due to its openness.

I bought for my test a ESP-C3-32S-Kit on AliExpress (for ~3.4€). There is also an interesting option, where you can use really tiny 3~4 € ESP-C3-01M-Kit programmer interface/mother board, with a SoC included alone on a daughter (ESP32-C3M with an on PCB antenna). This allows, to flash several pads one only one programmer board. This reduce size of the needed system for final application to just the SoC and it’s power supply and peripherals. Pads of the SoC daughter board are wide enough to be easily soldered.

Sadly the official documentation for installing it is incomplete with the current state (November 2021) of GIT repository. Here is the mean to install a working ESP-IDF for this SoC series on Linux. This will be more compact and straight forward that the official with Linux+Windows installation.

About RISC-V more generally, I created this week a list of available Open Source RISC-V implementations, and open sources tools to build them or work with them.

Step 1, install dependencies

Instruction for Arch Linux, CentOS and Debian, based Linux systems:

* Debian/Ubuntu/…

sudo apt-get install git wget flex bison gperf python3 python3-pip python3-setuptools cmake \
  ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0

* CentOS

sudo yum -y update && sudo yum install git wget flex bison gperf python3 python3-pip python3-setuptools \
  cmake ninja-build ccache dfu-util libusbx

* Arch Linux/Manjaro/…

sudo pacman -S --needed gcc git make flex bison gperf python-pip cmake ninja ccache dfu-util libusb

Step 2, install ESP-IDF

This will take at least 1.2GB of disk space (size of the git clone at the time I write this article).

mkdir -p ~/esp
cd ~/esp
git clone --recursive https://github.com/espressif/esp-idf.git

They didn’t mention, that 4.3 is required to use ESP32-C3. At the time I tried it, version 4.2 is currently (November 2021) the default branch, you can see the current version by this commands. Warning: This start the environment, so you will probably need to log again or simply start another shell to avoid problems when finishing the installation.:

cd ~/esp/esp-idf
. ./export.sh
idf.py --version

So you need to checkout the 4.3 branch to have a working version with ESP32-C3, and then update submodule to match the new version:

cd ~/esp/esp-idf
git checkout release/v4.3
git submodule update --init --recursive

Then you need to install some binaries/libs. Warning, it will create a ~/.expressif directory to place all downloaded files inside, don’t forget to create the directory + a link to a partition with enough place (about 1GB needed for all versions).

cd ~/esp/esp-idf
./install.sh esp32c3

After the installation, the following command is suggested to enter in the working (python pyenv) environment (don’t forget the first . and space)

Step 3, setup working environment

. ./export.sh

A message will be displayed that explain to you how to compile a program:

Done! You can now compile ESP-IDF projects.
Go to the project directory and run:

  idf.py build

They suggest in the documentation to add an alias to your .bashrc:

alias get_idf='. $HOME/esp/esp-idf/export.sh'

So you will only have to type get_idf to get ready to use it.

Step 4, test “Hello World” example

I would personally make it this way:

cd ~/esp/
mkdir tests
cd tests
cp -a ~/esp/esp-idf/examples/get-started/hello_world .
cd hello_world
idf.py set-target esp32c3

Errors possible, see:
* Invalid value for ‘{esp32|esp32s2}’: ‘esp32c3′ is not one of ‘esp32′, ‘esp32s2′
* libsodium/crypto_box/curve25519xchacha20poly1305/box_seal_curve25519xchacha20poly1305.c

Step 5, tune the target, if needed

You need now to use menuconfig if you want to setup some parameters, default one should just work fine.

idf.py menuconfig

This show you an interactive cli menu interface like for the Linux kernel, that allow you to tune some parameters. You don’t need to tune them for this test.

You can navigate with cursor keys, validate or enter submenus with enter, and quit them without modification with esc key. When you quit it, it will say you if you didn’t done any modification:

No changes to save (for '~/esp/test/hello_world/sdkconfig')

Step 5 build the project

Now as everything is setup, you only need to build the project, this could take some time, as it, at first time, compile, the whole embedded system:

idf.py build

It will end with a message displaying the location of the binary to be flashed and the instruction to type to flash it, the long explicit way and the short implicit way (I coloured them here):

Generated >/data/arc/esp/test/hello_world/build/hello-world.bin

Project build complete. To flash, run this command:
/home/popolon/.espressif/python_env/idf4.3_py3.9_env/bin/python ../../esp-idf/components/esptool_py/esptool/esptool.py -p (PORT) -b 460800 --before default_reset --after hard_reset --chip esp32c3  write_flash --flash_mode dio --flash_size detect --flash_freq 80m 0x0 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0x10000 build/hello-world.bin
or run 'idf.py -p (PORT) flash'

* Problem with CA certificates

Step 6, flash the project

If you didn’t have plugged your board in the USB port it’s the time to do. Then (by default white colour RGB led is enlighten. Type then the suggested command. I choose the short way here:

idf.py --port /dev/ttyUSB0 flash

It should now do the flashing sequence. I show only some important steps. In color you MAC address is given, it can be useful to detect if everything run fine.

Serial port /dev/ttyUSB0
Connecting....
Chip is ESP32-C3 (revision 3)
Features: Wi-Fi
Crystal is 40MHz
MAC: XX:XX:XX:XX:XX:XX
Uploading stub...
[...] <- lot of output
Writing at 0x00010000... (20 %)
[...] <- lot of output
Leaving...
Hard resetting via RTS pin...
Done

Read this if you have some troubles at flashing.

Step 7, viewing the output

You can connect to the board, still with its ttyUSB0 port, need to connect at speed 115200. I previously wrote an article about how to control a remote board, including serial

I use TinySerial here:

com /dev/ttyUSB0 115200

You can also use idf.py to monitor the output like this (idf.py monitor shortcuts and launching GDB with GDBStub:

idf.py -p /dev/ttyUSB0 monitor

In both case, you will see a booting sequence, an Hello World! some characteristics of your board, then a 10 seconds counter and a reboot. This is looping until you unplug or flash again the board.

Hello world!
This is esp32c3 chip with 1 CPU core(s), WiFi/BLE, silicon revision 3, 2MB external flash
Minimum free heap size: 327588 bytes
Restarting in 10 seconds...
Restarting in 9 seconds...
Restarting in 8 seconds...

You can quit by typing: the 2 keys ctrl-c followed then by a

Building, flashing and monitor can be combined by one command:
idf.py -p /dev/ttyUSB0 flash monitor

Troubleshooting

I don’t want to pollute too much the reading of the workflow and keep it as reference, so people can found answer when they encouter the same errors.

Invalid value for ‘{esp32|esp32s2}’: ‘esp32c3′ is not one of ‘esp32′, ‘esp32s2′

If you have the following error, you didn’t checkout the 4.3 branch at Step 2

Usage: /data/arc/esp/esp-idf/tools/idf.py set-target [OPTIONS] {esp32|esp32s2}
Try '/data/arc/esp/esp-idf/tools/idf.py set-target --help' for help.

Error: Invalid value for '{esp32|esp32s2}': 'esp32c3' is not one of 'esp32', 'esp32s2'.

libsodium/crypto_box/curve25519xchacha20poly1305/box_seal_curve25519xchacha20poly1305.c

If during the set-target you have the following error:

CMake Error at ~/esp/esp-idf/tools/cmake/component.cmake:475 (add_library):
  Cannot find source file:

    ~/esp/esp-idf/components/libsodium/libsodium/src/libsodium/crypto_box/curve25519xchacha20poly1305/box_seal_curve25519xchacha20poly1305.c

This mean you didn’t made the git submodules update in Step 2. So return in ~/esp/esp-idf and git submodule update.

You will then need to remove the hello_world directory and copy it again in tests: cd ~/esp/tests/; rm -R hello_word; cp -a ~/esp/esp-idf/examples/get-started/hello_world ..

Flashing errors

There are several problems possible for flashing part, most of them are about ttyUSB0 detection/usage.

Detection, power, and device that disappear

NodeMCU ESP32-C3S

First look if your board is detected by lsusb.

* If the device isn’t detected at all, verify if you USB wire is not a powering only wire. Try by connecting other USB devices, if all of them aren’t detected by the wire, this is probably the case, if you use a wire for your phone with the same kind of connector, try if it detected with it, then your board.

* Another case, a board that worked perfectly the first day, was not detected any more the second day. I looked at it with artisanal microscope using phone zoom + cheap lens, and seen a hair or wire, like on the detail picture at left, just above two pins of the USB connector. After removing it, the board work again.

* If the interface is displayed by lsusb but disappear when flashing it, try to disable power saving (autosuspend) for this device or all USB devices, lots of old mother boards have problems with USB devices power saving. Some mouses keep stuck, some flashable devices disappear, etc…

usbfs: interface 0 claimed by ch341 while ‘brltty’ sets config #1

If it fails with a long error message including around the 10th line:

FileNotFoundError: [Errno 2] No such file or directory: '/dev/ttyUSB0'

Then there is a problem with the USB detection in your system configuration.

You can dig in kernel message with dmesg | less then search for ttyUSB0 (just type /ttyUSB0 to found the problem.

In my case for example, the brtty (tty for braille) driver tried to get the serial device and then disconnected it:

[36767.028312] usb 1-1.6: ch341-uart converter now attached to ttyUSB0
[36767.252867] audit: type=1130 audit(1637441179.890:468): pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=brltty-device@sys-devices-pci0000:00-0000:00:1a.0-usb1-1\x2d1-1\x2d1.6 comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
[36767.878792] audit: type=1130 audit(1637441180.513:469): pid=1 uid=0 auid=4294967295 ses=4294967295 msg='unit=brltty@-sys-devices-pci0000:00-0000:00:1a.0-usb1-1\x2d1-1\x2d1.6 comm="systemd" exe="/usr/lib/systemd/systemd" hostname=? addr=? terminal=? res=success'
[36767.883002] usb 1-1.6: usbfs: interface 0 claimed by ch341 while 'brltty' sets config #1
[36767.883975] ch341-uart ttyUSB0: ch341-uart converter now disconnected from ttyUSB0
[36767.883998] ch341 1-1.6:1.0: device disconnected

You can in this case not the device id with the following command (the ID in the result is coloured here:

lsusb | grep CH340
Bus 001 Device 005: ID 1a86:7523 QinHeng Electronics CH340 serial converter

Then edit the 90-brltty-device.rules:

>code>sudo vi /usr/lib/udev/rules.d/90-brltty-device.rules

Search the line containing 1a86/7523 by typing /1a86/7523, then comment the line by adding a # at start:

#ENV{PRODUCT}=="1a86/7523/*", ENV{BRLTTY_BRAILLE_DRIVER}="bm", GOTO="brltty_usb_run"

save and quit by typing :x if you use vi/vim/nvim… and then reload udev rules by typing:

systemctl reload systemd-udevd

It should now be ok, by just unplugging and replugging the USB wire from your computer.

Generally all the kind of (tty)USB connection problem can be solved by tuning /etc/udev/rules.d/

Problem with CA certificates bundle

With the 4.2 version of ESP-IDF, some FAILED are thrown in relation with a x509_crt_bundle and cacrt_all.pem invalid certificate.

Updating the Certificate Bundle section has a link to himself as solution. ^^. Looks like the problem is on ESP-IF 4.2, 4.3 runs just fine, see install + upgrade process.

My system CA-certificates are already up to date but it want a specific version to put somewhere (not documented) to be able to push it on the board firmware. Ca-certificates, are certificates about CA (Certificate Authorities) that you need to be sure that this authorities are still be considered as valid. anyway ESP-IF 4.3 works just fine.

Linux syscall and RISC-V assembly

Sample of RISC-V assembly code

Syscall in Linux kernel, is an interface to access to kernel basic functions. They are described in section 2 of man pages. The introduction is in man 2 syscall (indirect system call), and the list of functions are described in man 2 syscalls. ”’Update:”’ System Calls in lectures of official Linux kernel documentation including “Linux system calls implementation”, “VDSO and virtual syscalls” and “Accessing user space from system calls”

This article follow previous one about RISC-V overall progress and available tools to play with, I will try to make a short article here about Linux syscall usage and the RISC-V assembly case.

Table of Content

* Description section of the man page
* Getting the list of function and how to access them
* Passing parameters
* Function number and registers of return values
* Return values and error code
* Compiling and executing on virtual environment
* Update: Bronzebeard assembler and its baremetal environment for real hardware

Description section of the man page

* syscall() is a small library function that invokes the system call whose assembly language interface has the specified number with the specified arguments. Employing
* syscall() is useful, for example, when invoking a system call that has no wrapper function in the C library.
* syscall() saves CPU registers before making the system call, restores the registers upon return from the system call, and stores any error returned by the system call in errno(3).
* Symbolic constants for system call numbers can be found in the header file .

You can find here function, like access to files open/close/read/write/flush, access to sockets, ioctl, uid, gid, pid, messages, ptrace, restart system, etc…

Getting the list of function and how to access them

As far I know, now only a part of syscall functions are accessible easily in assembly, they are defined in /usr/include/unistd.h, and function numbers assigned in ABI are defined in /usr/include/asm-generic/unistd.h.

The more practical match I found is using /usr/include/asm-generic/unistd.h to see which function are available and there respective manpage for the function header definition. For example:
* asm-generic: #define __NR_read 63
* man 2 read: ssize_t read(int fd, void *buf, size_t count);

The ABI with RISC_V as defined in man 2 syscall in section Architecture calling conventions use the registers following 2 tables rules.

Passing parameters

The second table in this section of the man page shows the registers used to pass the system call arguments.

Arch/ABI      arg1  arg2  arg3  arg4  arg5  arg6  arg7  Notes
──────────────────────────────────────────────────────────────
riscv         a0    a1    a2    a3    a4    a5    -

Here are the arguments in the order of the function definition, for example, in read (63) function:

ssize_t      read  ( int fd, void *buf, size_t count );
a0(result) = a7(63)( a0(fd),  a1(*buf),    a2(count) )

For remember, 3 standard I/O file descriptors are STDIN=0, STDOUT=1, STDERR=2, the other are used when opened a file with open and closed by close.

So we set the arguments as this. x0 is the always 0 register:

        addi  a0, x0, 0       # Set STDIN as sources
        la    a1, buffer_addr # load address of helloworld
        addi  a2, x0, 3       # reaad 3 bytes

Function number and registers of return values

And the first one give the register in which put the function number, that will receive return value and errno (error value)

Arch/ABI    Instruction           System  Ret  Ret  Error    Notes
                                  call #  val  val2
───────────────────────────────────────────────────────────────────
riscv       ecall                 a7      a0   a1   -

So for the read function, we need to put 63 (as found in /usr/include/asm-generic/unistd.h in register a7, and registers and following registers will receive the return values, a0 will receive system call result, and a1 an error message (the errno value).

ssize_t read(int fd, void *buf, size_t count);
a0    =  63 (    a0,       a1,         a2)

So can set them as this:

        addi  a7, x0, 63     # set called function as read()
        ecall                # call the function

Return values and error code

After the man page of read(2):
* On success, the number of bytes read is returned
* On error, -1 is returned, and errno is set to indicate the error.

So we can test first the return value of a0 and if a0 < 0 then we jump to part for display the error message, else we can simply display a OK message. So for the branching part, RISC-V in its super reduced set only have < (lt) and <= (le) comparators, you just need to swap registers to compute > (gt) and >= (ge), but this avoid lots more of transistors.

    addi a3,x0,0           # x3=0
    blt  a1,a3, error_seq  # if x1<0 branch to error_seq

We so use here the syscall write function (64) defined as:
* asm-generic: #define __NR_write 64
* man 2 write: ssize_t write(int fd, const void *buf, size_t count);
* So, registers: a0=1 (STDOUT), a1=*buf, a2=count, a7=64 (function number)

And we will finish with exit() syscall, defined as:
* asm-generic: #define __NR_exit 93
* man 2 exit: noreturn void _exit(int status);
* So, registers: a0=return code, a7=93 (function number)

    la    a1, ok           # load address (pseudo code) of ok string
    addi  a2, x0, 3        # set length of text to 3 (O + K + \n)
    addi  a7, x0, 64       # set ecall to write function
    ecall                  # Call the function

    addi  a0, x0, 0        # set return code to 0 (OK) for exit (93) function
    j     end              # unconditional jump to end before quit

error_seq:
    la    a1, error        # load address (pseudo code) of error string
    addi  a2, a2, 0x30     # add 0x30 (0 ASCII code) to the error code
    sb    a2, 7(a1)        # put the (byte) value at position 7 of Error string (before \n)
    addi  a2, x0, 10       # set now length of our string
    addi  a7, x0, 64       # set ecall to write function
    ecall                  # Call the function

    addi  a0, x0, -1       # set return code to -1 (error) for exit (93) function
end:
    addi    a7, x0, 93     # set ecall to exit (93) funciton
    ecall                  # Call linux to terminate the program

.data:
ok:     .ascii "OK\n"
error:  .ascii "Error:  \n"

RISC-V Longan nano

Compiling and executing on virtual environment

If you don't have a RISC-V hardware (can be found as low as 3€ now), you need to have a cross compiler and qemu for emulating instructions, or a whole system installed.

Packages needed for compiling on ArchLinux x86 or ARM for example.

sudo pacman -S riscv64-linux-gnu-gcc  riscv64-linux-gnu-glibc riscv64-elf-binutils riscv64-elf-binutils riscv64-elf-gcc riscv64-elf-gdb

Newlib is a lightweight RV32 (RISC-V 32bits) lightweight library for bare metal that can be used instead of a whole GNU system on embedded devices with low memory capacity (as Longan nano, less than 8€ with screen, see picture below, or 3€ Sipeed RV): riscv32-elf-newlib.

I made a simple shell script to don't have to remember the commands to assemble the code from an x86 platform (work also on ARM or RISC-V one) that take the .s as argument:

name=$1
riscv64-linux-gnu-as -march=rv64imac -o ${name}.o ${name}.s
riscv64-linux-gnu-ld -o ${name} ${name}.o

You can add a strip but better to avoid it if you need to debug it:

riscv64-elf-strip --strip-all ${name}

And it can be executed on non RISC-V platforms by using qemu-riscv64, if it doesn't depend on libraries or if you have them installed, it allow you to test it without having a full RISC-V system installed, qemu is so fantastic. On ArchLinux it is available in package qemu-arch-extra:

qemu-riscv64 ${name}

And can be disassembled (will probably use different instruction than your assembly code, due to RISC-V assembly pseudo-instructions:

riscv64-linux-gnu-objdump -d ${name}

Bronzebeard assembler and its baremetal environment for real hardware

RISC-V Longan nano

Update: Bronzebeard is an assembler with light baremetal environment builder for RISC-V, GD32VF103 as Longan Nano (about 8€ with a screen, as pictured on this article pictures) and Wio (similar board with an added ESP8266 SoC). I made an AUR package of Bronzebeard, and someone made a Mandelbrot set demo in 918 bytes pure RISC-V assembly. You can find some other example in the source of Bronzebeard. gd32vf103inator is a set of tools for GD32V, to manage from a simple random text editor.

RISC-V overall progress

WordPress is so cumbersome (brut long text in SQL DB and other misconception (WTF?), buggy (especially to multilingual content missing) and hard to maintain on long term that I didn’t posted for long time I want to migrate. You can test the new log engine (here specialised in TIC-80 256 bytes code on https://256b.popolon.org/.

I continued my travel toward RISC-V I started as said in a previous post in may 2018.

RISC-V Benefits

Among the benefit of RISC-V beside other implementations:
* Open source and without license fee availability, allowing everyone to participate, implement and have full specifications
* Highest modularity for a processor in specifications, You can reduce the core to only the set of functions you want for a specific tasks, allowing less transistor/more compact specialized cores, and then multiply cores with some specialized in some kind of tasks.
* Vector extension

As example of specific task oriented cores:
* You don’t need a SIMD if you have a more powerful DSP unit, and you can implement this DSP unit using a RISC-V core with Vector extension, that is more efficient that a simple SIMD for this kind of task.
* JIT extension could be useful for some dynamic web server using scripting language (as PHP, Ruby, JS), but less useful for static web server (PCRE-jit computation in servers like nginx is marginal a core could be dedicated on a processor with many cores without this extension). Alibaba Group (Chinese Amazon) developed it’s own RISC-V implementation for its web servers).
* The European processor Initiative started in 2018, with first goal to uses United Kingdom made ARM core for main processor (at this time ARM had solid build/compilation/system ecosystem) with several kind of computing accelerators based on RISC-V, called EPAC (European Processor Accelerators)(a.is). It is developed at Barcelone with several partners inside European Union(a.is), Sadly as most European union project it will help USA industry for manufacturing, (GlobalFoundries) instead of maintain, sustain and develop local knowledge, experience and workforce for independence, employment and smaller carbon footprint shipments (as China and United-States do). The first test version is a 22nm FPGA, the production one will be 12nm ASIC, and among the objectives, there will be to highest throughput while not going above the 1GHz top barrier, to achieve higher energy efficiency.

Operating System working on RISC-V in June 2021

First efforts with toward embedded operating systems as first available RISC-V chips was in this domain. As the RISC-V embedded market is mature, more effort are made toward mobile and desktop Operating Systems.

Several Linux distribution already work on RISC-V now. Next Debian Bulleye (release August 14 2021, already in beta), Fedora have a RISC-V flavor already working on the BeagleV Starlight open-source board and its StarFive JH7100 processor, and there is a port of Yocto.

For reference, here is instructions about making the RISCV64 virtual machine on Debian Wiki, and a more general guide to make Linux run on Qemu, RISC-V.

You can find the Debian RISC-V system image on this page, just click on to download (direct link). This image is made with Debian Quick Image Baker (dqib)

To run it, you also need OpenSBI (Open Source Supervisor Binary Interface) needed to load the bootloader (Documentation, Prebuild deb packages) I used deb package on Archlinux:

ar x ../opensbi_0.9-1_all.deb
SBIPATH=`pwd`
(cd /; sudo tar xf "$SBIPATH/data.tar.xz")

And the Uboot for Qemu. I personally just extract and the directory that also contain, the “artifacts” Debian image.

By default image is in Qcow format, that is more compact for unused blocks, but also really slower.
You can convert it easily by:

qemu-img convert -f qcow2 -O raw image.qcow2 image.raw

Here is the boot.sh to launch it (you can replace image.raw by image.qcow2, if you have a verypowerfull system and only place count: I use 2 cores and 512M of RAM. For huge compilation I restart it with more mem (1536M is generally enough, but more means more cache and faster compilation).

cores=2
mem=512M
qemu-system-riscv64 -machine virt -cpu rv64 -smp ${cores} -m ${mem} \
  -device virtio-blk-device,drive=hd -drive file=image.raw,if=none,id=hd \
  -device virtio-net-device,netdev=net -netdev user,id=net,hostfwd=tcp::2222-:22 \
  -bios /usr/riscv64-linux-gnu/lib/opensbi/generic/fw_jump.elf \
  -kernel ../qemu-riscv64_smode/uboot.elf \
  -object rng-random,filename=/dev/urandom,id=rng -device virtio-rng-device,rng=rng \
  -nographic -append "root=LABEL=rootfs console=ttyS0"

The login/pass are root/root.

you can connect by ssh:

ssh -p 2222 root@127.0.0.1

and rsync files via:

rsync -a --rsh="ssh -p 2222" root@127.0.0.1:path/to/rsync .

I already managed to compile TIC-80 on it and run it on Qemu:

.

There is also BareDOOM a port of DOOM that run on RISC-V emulated in TinyEmu (source code in the same page, there is also a fork with additional features on GitHub, a system emulator for the RISC-V and x86 architectures), JSLinux allow to run it in a browser (There is also a Quake port on RISC-V K210 microcontroller based Sipeed Maix Bit board). There is also a patched version of buildroot for K210 (no-mmu), for building Linux on this platform.

There is an important Note in system images from Fabrice Bellard site to make binary working with TinyEMU:


- TinyEMU only supports raw boot loader and kernel images. So after
  building riscv-pk or the Linux kernel, you must convert the ELF
  image to a raw image with:

  riscv64-unknown-linux-gnu-objcopy -O binary bbl bbl.bin
  
  riscv64-unknown-linux-gnu-objcopy -O binary vmlinux kernel.bin

A port of Haiku has already well progressed, also use TinyEMU for the port.

Update 2 July 2021: Another RISC-V (RV32 only) emulator is Nemu (github) it is a part of XiangShan (香山) RISC-V open processor from Chinese Academy of Sciences (OpenXiangShan (Gitee)) made by Chinese Academy of Sciences, the processors should have the performances of an ARM Cortex-A72/A73 in july 2021 with a 28nm process in first batch (Yanqi hu / Yanqi Lake / 雁栖湖), and performances of an Intel i9-10900K in second batch ( Nanhu / South Lake / 南湖) in fall 2021, with 14nm process, both manufactured by TSMC, and it already run GNU/Linux distribution Debian. See also(中文).

Another Virtual Machine emulator for RISC-V is JuiceVM supporting RV64IMASU. It is lightweight, support small UART serial I/O and MMU

Available RISC-V Hardware in June 2021

Several developers have received a first beta version of BeagleV RISC-V board. Here are demos (on youtube sadly) of first tries of different devs at beginning of may "BeagleV RISC-V Computer (Beta)" (unboxing), "BeagleV from BeagleBoard.org Fedora image Live Booting - 1" (booting), now BeagleV give a prepared Fedora with XFCE working on HDMI.


I have now 2 embedded microcontroller oriented RISC-V Board from Sipeed, one 32bits and one MaixDuino SipeedM1 (microPython (by default), dual-core 64bits+AI module) of the young hackers, Shenzhen based, SiPeed company. The last one support lot of embedded systems including default MicroPython based MaixDuino system (and cool plateforrm.io interface, Arduino, PlatformIO, microPython, OpenMV, FreeRTOS, et TensorFlow, etc..

The Nezha board of SiPeed, is a bit more expansive and with lower specs in first version, but very promising for the future Nezha: Your first RV64 Linux SBC for IoT~ (video Youtube).

Huawei Hi3861 is oriented toward their LiteOS kernel that is used in their new Android compatible HarmonyOS.

The BL602/BL604 de Bouffalo Lab (RISC-V WiFi & Bluetooth 5.0 LE) based on ESP32-C (RISC-V variant of ESP32, pin2pin compatible) is a 100 % open source BootLoader & driver (no blob needed), thanks to the work of Lup Yuen Lee (李立源). He wrote lot of interesting articles about the process of making the driver and a book about this chip, and several article around Ecosystem, including using Rust, How to simulate it in WebAssembly, etc.

China Science Academy is currently working on optimizing Firefox (SpiderMonkey) and Chrome/ium (Webkit) engines for RISC-V 2000 laptops with equivalent power of a Cartex-A78 should be available at then end of 2022

At the same time the port of true GNU/Linux on embedded devices like phones progress fast, on ARM architecture, with specialized distributions like PostMarket OS (and it's derivative Alpine Linux), or more general distributions, including Debian, Manjaro (ArchLinux derivative) etc. Port of KDE Plasma and GTK4/GNOME40 (especially GTK4.1+ with very efficient OpenGL acceleration, are of great help in this area. PinePhone from Pine64.org community is really an active and efficient hub for these ports. This phone (developer oriented in current state) is at the same time cheap (<200€) have hard switch for all network features, and every part can be easily changed. Those project are all ready to be ported on RISC-V versions of this kind of devices. Pine64 is already working on RISC-V opensource bl602 driver (see above) and a RISC-V (GD32VF103TB) running a smart/programmable iron solderer, and a future RISC-V based mother board to replace the current ARM one on the PinePhone could be totally realistic. See also LINux on MOBile for news on the port of Linux on Mobile devices.

An interesting post (and blog) about RISC-V assembly programming from Stephen Marz, another blog about RISC-V assembly from Daniel Mangun and one from ieee.org Build a RISC-V CPU From Scratch. A simple HelloWorld example, with deep explanations, including Linux RISC-V Application Binary Interface (ABI), to start with RISC-V assembly, based on GNU Assembler (support RV32 & RV64). There is also a Python made, limited, RISC-V assembler. A complete tutorial in French for starting with the Sipeed Longan Nano, (available for ~5 € with LCD screen).

HTML5 RISC-V Interpreter/simulator. There is also RARS a really slow/unusable Java RISC-V simulator (pacaur -S rars), jupiter (with link to other simulators) (pacaur -S jupiter) is another one, and spike (pacman -S spike) is a c made simulator. Ripes (pacaur -S ripes-git) is a very advanced one, with multiple level of pipe, data/instruction cache states, etc... Venus with online simulator in Kotlin language.

Tutorial serie about designing RISC-V processor in VHDL.

I hope that with progress I made in real-time programming and different new programming languages in last few years (Lua, some bit of RISC-V assembly, JavaScript), I will make quickly some RISC-V demo on embedded board.

Tester un système Linux RISC-V avec qemu sur ARM et ArchLinuxARM

There is also an english tutorial with more informations here

Pour cela, on peut utiliser la distribution Busybear-linux, très limitée, mais déjà bien pour un premier test de Qemu avec RISC-V (Site officiel, le CPU à sources ouvertes et sous license libre). il a l’avantage de fournir un système pré-compilé, prêt à l’emploi ici : github.com/michaeljclark/busybear-linux/releases

Les deux fichiers indispensables sont :
* bbl.bz2 = boot
* busybear.bin.bz2 = système

Récupérer les fichiers de busybear et les décompresser :

ver=1.0
wget https://github.com/michaeljclark/busybear-linux/releases/download/v$ver/bbl.bz2
wget https://github.com/michaeljclark/busybear-linux/releases/download/v$ver/busybear.bin.bz2
bzcat bbl.bz2 >bbl
bzcat busybear.bin.bz2 >busybear.bin

Les explications que j’ai récupéré viennent du le wiki d’arch pour le périphérique réseau : //wiki.archlinux.fr/Qemu car BusyBear donne des instruction pour des anciens systèmes Debian/Ubuntu (avant le passage à Netplan).

Sur archlinux(ARM) nous avons donc besoin d’uml_utilities qui se trouve dans la grande yaourtière.

wget https://aur.archlinux.org/cgit/aur.git/snapshot/uml_utilities.tar.gz
tar xf uml_utilities.tar.gz
cd uml_utilities
makepkg -A # ignore l'architecture définie dans le PKGBUILD

Il faut ensuite passer en root (ou avoir mis des droits sudo globaux à l’utilisateur pour installer ce paquet :

su - root
cd dossier_de_l'archive
pacman -U uml_utilities*.pkg.tar.xz

Profiter d’être en root pour préparer l’interface réseau pour qemu (remplacer alarm par l’utilisateur de votre choix :

tunctl -u alarm -t tap0
ifconfig tap0 192.168.0.1 netmask 255.255.255.0 up

Puis se deloguer de root par logout ou exit.

Dans le dossier où sont placer les deux fichiers préalablement décompressés :

qemu-system-riscv64 \
  -nographic -machine virt -kernel bbl \
  -append "root=/dev/vda ro console=ttyS0" \
  -drive file=busybear.bin,format=raw,id=hd0 \
  -device virtio-blk-device,drive=hd0 \
  -net nic -net tap,ifname=tap0,script=no

Le boot sur busybear/RiscV va alors commencer. Il se bloc pendant quelques secondes sur le logo RISC-V en ASCII-art :

qemu-system-riscv64: warning: hub port hub0port0 has no peer
qemu-system-riscv64: warning: vlan 0 with no nics
qemu-system-riscv64: warning: netdev hub0port0 has no peer
qemu-system-riscv64: warning: requested NIC (anonymous, model unspecified) was not created (not supported by this machine?)
              vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
                  vvvvvvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrr       vvvvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrr      vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrrrr    vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrrrr    vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrrrr    vvvvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrrrrr      vvvvvvvvvvvvvvvvvvvvvv
rrrrrrrrrrrrr       vvvvvvvvvvvvvvvvvvvvvv
rr                vvvvvvvvvvvvvvvvvvvvvv
rr            vvvvvvvvvvvvvvvvvvvvvvvv      rr
rrrr      vvvvvvvvvvvvvvvvvvvvvvvvvv      rrrr
rrrrrr      vvvvvvvvvvvvvvvvvvvvvv      rrrrrr
rrrrrrrr      vvvvvvvvvvvvvvvvvv      rrrrrrrr
rrrrrrrrrr      vvvvvvvvvvvvvv      rrrrrrrrrr
rrrrrrrrrrrr      vvvvvvvvvv      rrrrrrrrrrrr
rrrrrrrrrrrrrr      vvvvvv      rrrrrrrrrrrrrr
rrrrrrrrrrrrrrrr      vv      rrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrr          rrrrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrrrr      rrrrrrrrrrrrrrrrrrrr
rrrrrrrrrrrrrrrrrrrrrr  rrrrrrrrrrrrrrrrrrrrrr

       INSTRUCTION SETS WANT TO BE FREE

On voit aux message du kernel que c’est encore un noyau assez simple :

[...]
[   10.190000] This architecture does not have kernel memory protection.
[...]

On arrive enfin au prompt où pour se connecter il faut utiliser :
* Identifiant : root
* Mot de passe : busybear.

Et voilà !

ucbvax login: root
Password:
    ____                   ____                     __    _
   / __ )__  _________  __/ __ )___  ____ ______   / /   (_)___  __  ___  __
  / __  / / / / ___/ / / / __  / _ \/ __ `/ ___/  / /   / / __ \/ / / / |/_/
 / /_/ / /_/ (__  ) /_/ / /_/ /  __/ /_/ / /     / /___/ / / / / /_/ />  <
/_____/\__,_/____/\__, /_____/\___/\__,_/_/     /_____/_/_/ /_/\__,_/_/|_|
                 /____/
root@ucbvax:~# cat /proc/cpuinfo
hart    : 0
isa     : rv64imafdcsu
mmu     : sv48

Pour configurer le réseau :

ifconfig sit0 192.168.0.10 netmask 255.255.255.0 up

On peut aussi le modifier dans /etc/hosts/interfaces pour qu'il soit pris en compte au reboot :

auto lo
iface lo inet loopback

auto sit0
iface sit0 inet static
  address 192.168.0.10
  netmask 255.255.255.0
  broadcast 192.168.0.255
  gateway 192.168.0.1

!!! Attention, les cache/resolever DNS par défaut dans /etc/resolv.conf sont ceux de Google !!! Changer par un de votre réseau ou de votre fournisseur d'accès. Ceux de Google :

root@ucbvax:~# cat /etc/resolv.conf 
nameserver 8.8.8.8
nameserver 8.8.4.4