• Stars
    star
    193
  • Rank 201,081 (Top 4 %)
  • Language
    C
  • License
    Apache License 2.0
  • Created about 4 years ago
  • Updated 4 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Program the ESP32 with Nim! Wrappers around ESP-IDF API's.

Nesper

Program the ESP32 using Nim! This library builds on the esp-idf. Nim now has support for FreeRTOS & LwIP. Combined with the new ARC garbage collector makes Nim an excellent language for programming the ESP32.

See Releases for updates.

Status

This project is fairly stable and even being used in shipping hardware project. The documentation is rough and primarily only includes this README. As such, it may still require understanding the underlying ESP-IDF SDK for various use cases. However, it is useable and several people unfamiliar with ESP-IDF and embedded programming have added wrappers for esp-idf modules.

Note: It's recommended to use the ESP-IDF.py v4.0 branch (as of 2020-11-24). Branch v4.1 has multiple serious bugs in I2C.

General Usage

  1. Install ESP-IDF
    • TLDR: git clone -b release/v4.0 --recursive https://github.com/espressif/esp-idf.git
    • esp-idf version 4.0 is recommended for now since its more stable
    • esp-idf version can be set using the defines: -d:ESP_IDF_V4_0 or -d:ESP_IDF_V4_1
  2. Install Nim 1.4+
  3. Use Nimble to install Nesper (nimble install https://github.com/elcritch/nesper or for the devel branch nimble install 'https://github.com/elcritch/nesper@#devel' )
  4. Create a new Nimble project nimble init --git esp32_nim_example
  5. In the new project directory edit the Nimble file and add the lines:
requires "nesper >= 0.6.1"
# includes nimble tasks for building Nim esp-idf projects
include nesper/build_utils/tasks
  • Make sure not to include a bin option like bin = @["src/esp32_nim_example"] as this will override the nimble esp_build and result in a broken idf.py build.
  1. Run nimble esp_setup to setup the correct files for building an esp32/esp-idf project

Compiling and Building

  1. Run nimble esp_build to build the esp-idf project
  2. Flash and monitor the esp32 board using: idf.py -p </dev/ttyUSB0> flash monitor

Notes:

  • Running nimble esp_build will both compile the Nim code and then build the esp-idf project
  • During development it's often handy just to run nimble esp_compile to check your Nim code works
  • Sometimes the Nim build cache gets out of sync, use nimble esp_build --clean to force a full Nim recompile
  • Sometimes the esp-idf build cache gets out of sync, use nimble esp_build --dist-clean to force a full Nim recompile

Example Code

This code shows a short example of setting up an http server to toggle a GPIO pin. It uses the default async HTTP server in Nim's standard library. It still requires the code to initialize the ESP32 and WiFi or ethernet.

import asynchttpserver, asyncdispatch, net
import nesper, nesper/consts, nesper/general, nesper/gpios

const
  MY_PIN_A* = gpio_num_t(4)
  MY_PIN_B* = gpio_num_t(5)

var 
  level = false
  
proc config_pins() =
    MOTOR1_PIN.setLevel(true)

proc http_cb*(req: Request) {.async.} =
    level = not level 
    echo "toggle my pin to: #", $level
    MY_PIN_A.setLevel(level)
    await req.respond(Http200, "Toggle MY_PIN_A: " & $level)

proc run_http_server*() {.exportc.} =
    echo "Configure pins"
    {MY_PIN_A, MY_PIN_B}.configure(MODE_OUTPUT) 
    MY_PIN_A.setLevel(lastLevel)
    
    echo "Starting http server on port 8181"
    var server = newAsyncHttpServer()
    waitFor server.serve(Port(8181), http_cb)

Why

TLDR; Real reason? It's a bit of fun in a sometimes tricky field.

I generally dislike programming C/C++ (despite C's elegance in the small). When you just want a hash table in C it's tedious and error prone. C++ is about 5 different languages and I have to idea how to use half of them anymore. Rust doesn't work on half of the boards I want to program. MicroPython? ... Nope - I need speed and efficiency.

Library

The library is currently a collection of random ESP-IDF libraries that I import using c2nim as needed. Sometimes there's a bit extra wrapping to provide a nicer Nim API.

Caveat: these features are tested as they're used for my use case. However, both Nim and the esp-idf seem designed well enough that they mostly "just work". PR's are welcome!

Supported ESP-IDF drivers with Nim'ified interfaces:

  • Nim stdandard library support for most basic POSIX network API's!
  • Most of the basic FreeRTOS.h header
  • NVS Flash
  • UART
  • SPI (don't mix half-duplex & duplex devices)
  • I2C

Other things:

  • Nim standard library wrapping of FreeRTOS semaphore's, mutexes, etc
    • include pthread in your CMakeLists.txt file and use Nim's POSIX lock API's
  • Nim support for xqueue and other "thread safe" data structures
  • Nim standard library support for FreeRTOS tasks using thread api's
    • include pthread in your CMakeLists.txt file and use Nim's POSIX Pthread API's

Things I'm not planning on (PR's welcome!)

  • I2S
  • PWM
  • LCDs
  • Built-in ADC

Manual Setup

This is the more manual setup approach:

  1. It's recommend to copy nesper/esp-idf-examples/simplewifi example project initially, to get the proper build steps.
    • git clone https://github.com/elcritch/nesper
    • cp -Rv nesper/esp-idf-examples/simplewifi/ ./nesper-simplewifi
    • cd ./nesper-simplewifi/
    • make build (also make esp_v40 or make esp_v41 )
  2. Nesper wrapper API names generally match the C names directly, usually in snake case
  • FreeRTOS functions usually are camel case and start with an x, e.g. xTaskDelay
  • These api's are found under nesper/esp/* or nesper/esp/net/*, e.g. nesper/esp/nvs
  1. Nesper Nim friendly api, usually in camel case
  • These api's are found under nesper/*, e.g. nesper/nvs

Example Async server on a ESP32-CAM (or other Esp32 Wifi board)

See SimpleWifi Example

The async code really is simple Nim code:

import asynchttpserver, asyncdispatch, net

var count = 0

proc cb*(req: Request) {.async.} =
    inc count
    echo "req #", count
    await req.respond(Http200, "Hello World from nim on ESP32\n")
    # GC_fullCollect()

proc run_http_server*() {.exportc.} =
    echo "starting http server on port 8181"
    var server = newAsyncHttpServer()

    waitFor server.serve(Port(8181), cb)

when isMainModule:
    echo "running server"
    run_http_server()

Nim-ified ESP32 APIs

GPIOs

import nesper, nesper/consts, nesper/general, nesper/gpios

const
  MOTOR1_PIN* = gpio_num_t(4)
  MOTOR2_PIN* = gpio_num_t(5)

proc config_pins() =
  # Inputs pins use Nim's set `{}` notation
  configure({MOTOR1_PIN, MOTOR2_PIN}, GPIO_MODE_INPUT)
  # or method call style:
  {MOTOR1_PIN, MOTOR2_PIN}.configure(MODE_INPUT)
  
  MOTOR1_PIN.setLevel(true)
  MOTOR2_PIN.setLevel(false) 

SPIs

import nesper, nesper/consts, nesper/general, nesper/spis

proc cs_adc_pre(trans: ptr spi_transaction_t) {.cdecl.} = ... 
proc cs_unselect(trans: ptr spi_transaction_t) {.cdecl.} = ...

proc config_spis() = 
  # Setup SPI example using custom Chip select pins using pre/post callbacks 
  let
    std_hz = 1_000_000.cint()
    fast_hz = 8_000_000.cint()
    
  var BUS1 = HSPI.newSpiBus(
        mosi = gpio_num_t(32),
        sclk = gpio_num_t(33),
        miso = gpio_num_t(34),
        dma_channel=0,
        flags={MASTER})

  logi(TAG, "cfg_spi: bus1: %s", repr(BUS1))

  var ADC_SPI = BUS1.addDevice(commandlen = bits(8),
                               addresslen = bits(0),
                               mode = 0,
                               cs_io = gpio_num_t(-1),
                               clock_speed_hz = fast_hz, 
                               queue_size = 1,
                               pre_cb=cs_adc_pre,
                               post_cb=cs_unselect,
                               flags={HALFDUPLEX})

Later these can be used like:

const 
  ADC_READ_MULTI_CMD =  0x80
  ADC_REG_CONFIG0 = 0x03

proc read_regs*(reg: byte, n: range[1..16]): SpiTrans =
  let read_cmd = reg or ADC_READ_MULTI_CMD # does bitwise or
  return ADC_SPI.readTrans(cmd=read_cmd, rxlength=bytes(n), )

proc adc_read_config*(): seq[byte] =
  var trn = read_regs(ADC_REG_CONFIG0, 2)
  trn.transmit() # preforms SPI transaction using transaction queue
  result = trn.getData()

See more in the test SPI Test or the read the wrapper (probably best docs for now): spis.nim.

Wear levelling / Virtual FAT filesystem

import nesper, nesper/esp_vfs_fat

var
  base_path : cstring = "/spiflash"
  s_wl_handle : wl_handle_t = WL_INVALID_HANDLE

mount_config = esp_vfs_fat_mount_config_t(format_if_mount_failed: true,
                                          max_files: 10, allocation_unit_size: 4096)
err = esp_vfs_fat_spiflash_mount(base_path, "storage", mount_config.addr, s_wl_handle.addr)

if err != ESP_OK:
  echo "Failed to mount FATFS."
else:
  echo "FATFS mounted successfully!"

writeFile("/spiflash/hello.txt", "Hello world!")
echo readFile("/spiflash/hello.txt") # Hello world!

Notice: file extension of files on FAT filesystem is limited to maximum of 3 characters.

Why Nim for Embedded?

Nim is a flexible language which compiles to a variety of backend "host" languages, including C and C++. Like many hosted languages, it has excellent facilities to interact with the host language natively. In the embedded world this means full compatability with pre-existing libraries and toolchains, which are often complex and difficult to interface with from an "external language" like Rust or even C++. They often also require oddball compilers, ruling out LLVM based lanugages for many projects (including the ESP32 which defaults to a variant of GCC).

Nim has a few nice features for embedded work:

Language:

  • High level language and semantics with low level bit fiddling and pointers
  • Flexible garbage collector or manual memory management
    • ARC GC allows using native-C debuggers, meaning any embedded debuggers should work too!
    • ARG GC doesn't use locks, and utilizies move semantics -- it's fast
  • Simple FFI's around to import and/or wrap C/C++ libraries
  • Async/Event support
  • Real hygenic language macros, and collections with generics!
  • Very flexible and hackable standard library!

Libraries:

  • Simplified network wrappers around native sockets (i.e. use select w/o a PhD)
  • Sane standard library, including JSON, datetime, crypto, ...
    • Efficient compiler that eliminates un-needed code (i.e. json support using a few extra kB's)
    • Package library manager

Compiler:

  • Fast compilation, generated C compiles fast
  • Deterministic exception handling using a non-malloc friendly goto technique
  • Object-oriented like programming that's not based on vtables

There are a few cons of Nim:

  • Lack of documentation for many parts of the standard library
  • Understanding the differences between stack/heap based objects is a bit tricky
  • Compiler options are often incompatible and can require some experimentation
  • Small community (e.g. lack of some libraries)
  • You likely won't get to use it at XYZ Megacorp
  • It will require some pioneering!

More Repositories

1

plotex

Elixir plotting utilities library
Elixir
90
star
2

figuro

Experimental UI Library for Nim
Nim
65
star
3

etrace

Update of original etrace program.
C
61
star
4

fidgetty

Widget library built on Fidget written in pure Nim and OpenGL rendered
Nim
56
star
5

bulma_widgets

Bulma Widgets using Phoenix LiveView Components
Elixir
23
star
6

cssgrid

Pure Nim CSS Grid layout engine
Nim
21
star
7

forthwith

Portable Forth clone using C, inline asm, and XMacros
C
19
star
8

keyx

Elixir implementation of Shamir's Secret Sharing
Elixir
14
star
9

keyxn

Pure Nim implementation of Shamir's Secret Sharing (SSS) algorithm
Nim
13
star
10

nerves_tier

Nerves (Elixir) wrapper for ZeroTierOne
Elixir
12
star
11

ants

Nim
11
star
12

cdecl

Nim helper for using C Macros
Nim
9
star
13

imkivy

imkivy
Nim
9
star
14

matrex_numerix

matrex_numerix
Elixir
9
star
15

platonic

Nim
6
star
16

nerves_system_orangepi_zero

Nerves System for Orange Pi Nano (Allwinner H2+ with xradio module)
Shell
6
star
17

pidex

pidex - Elixir PID Controller
Elixir
5
star
18

cimporter

Scaffolding to import C projects using c2nim
Nim
5
star
19

iex_ssh_shell

Simple shell using Erlang's SSH daemon functionality
Elixir
4
star
20

esp32_nim_net_example

esp32_nim_net_example
C
4
star
21

nerves_system_nanopi_core2

Nerves System Image for NanoPi Neo Core 2's
Batchfile
4
star
22

pjon-elixir-serial

C
3
star
23

einode

nimler_node -- experimental erlang/elixir c node library for Nim
Nim
3
star
24

autocomplete

textmate autocomplete module for C++ (possibly Java) using etag.
Ruby
2
star
25

eggsinstaller

Simple Installer for Python eggs including PyQt4 installation
2
star
26

wpe_kiosk

Simple Wrapper to run WebKit Embedded in Kiosk mode
Elixir
2
star
27

nerves_beaglebone_pru_compiler_example

Elixir
2
star
28

bopunk

BoPunk firmware management interface
Python
2
star
29

plotex_liveview_example

Plotex LiveView Example
Elixir
2
star
30

pipe

pipeline simulator
Python
2
star
31

cheerp

Nim wrapper for Cheerp (experimental)
Nim
2
star
32

Calendar

calendar project, porting from java to scala.
Java
2
star
33

bulma_widgets2

Elixir
2
star
34

nerves_rtc_modules

RTC Modules for NervesTime
Elixir
1
star
35

wpe_kiosk_rpi3

Elixir
1
star
36

c-nodes-example

Simple EI Connect Erlang / Elixir C Node
C
1
star
37

forthwith_ex

forthwith_ex -- forth shell via elixir
Elixir
1
star
38

einode_test

Nim
1
star
39

bulma_widgets_phx_test

CSS
1
star
40

wiki_for_nerves_docs

Q&A style answer for various Nerves related topics (unofficialy)
1
star
41

trans

translator
C
1
star
42

nim_embedded_os_test

Nim
1
star
43

CuteMark

Simple PyQt5 based Markdown Renderer.
Python
1
star
44

cloud_server

x86_cloud_server_example
Elixir
1
star
45

nim_make_aduino

Nim Test
Nim
1
star
46

Vala.tmbundle

Textmate Vala bundle
1
star
47

NanoBERT

Lightweight RPC client and server library modeled after BERT-RPC and using MsgPack.
C++
1
star
48

myEmacs

Personal Emacs configuration
Emacs Lisp
1
star
49

reactqt

Example Application Mixing Pyside and React
JavaScript
1
star
50

exblas

OpenBLAS for Nerves
Shell
1
star
51

open_on_dropbox

Simple utility to open Dropbox .web files in a browser
Shell
1
star
52

hsv5

HTML5 Based Alternative to CSV, TSV, JSONL, etc
Nim
1
star
53

surface_bulma_widgets

Elixir
1
star