diff --git a/README.rst b/README.rst
index 49e722e..a869e01 100644
--- a/README.rst
+++ b/README.rst
@@ -1,63 +1,76 @@
-What is py-esp32-ulp?
----------------------
+.. start-badges
-It is an assembler toolchain for the ESP32 ULP (Ultra Low-Power) Co-Processor,
-written in MicroPython.
+.. image:: ../../actions/workflows/run_tests.yaml/badge.svg
+ :height: 20px
+ :target: ../../actions/workflows/run_tests.yaml
+ :alt: Build Status
-It is able to translate small, simple assembler language programs to a
-loadable/executable machine code binary, at runtime, on the ESP32
-microcontroller, from projects implemented in MicroPython.
+.. end-badges
-This is intended as an alternative approach to assembling such programs on a
-development machine using the binutils-esp32ulp toolchain from Espressif.
+=====================
+py-esp32-ulp
+=====================
+py-esp32-ulp is an assembler toolchain for the ESP32 ULP (Ultra Low-Power)
+Co-Processor, written in MicroPython.
-Status
-------
+It can translate small assembly language programs to a loadable/executable
+ULP machine code binary, directly on the ESP32 microcontroller.
-The most commonly used stuff should work. Many ULP code examples found on
-the web will work unmodified. Notably, assembler macros and #include processing
-are not supported.
+This is intended as an alternative approach to assembling such programs using
+the binutils-esp32ulp toolchain from Espressif on a development machine.
-Expressions in assembly source code are supported and get evaluated during
-assembling. Only expressions evaluating to a single integer are supported.
-Constants defined with ``.set`` are supported in expressions.
+It can also be useful in cases where binutils-esp32ulp is not available.
-We have some unit tests and also compatibility tests that compare the output
-whether it is identical with binutils-esp32ulp output.
-There is a simple preprocessor that understands just enough to allow assembling
-ULP source files containing convenience macros such as WRITE_RTC_REG. The
-preprocessor and how to use it is documented here:
-`Preprocessor support `_.
+Features
+--------
-The minimum supported version of MicroPython is v1.12. py-esp32-ulp has been
-tested with MicroPython v1.12 and v1.17. It has been tested on real ESP32
-devices with the chip type ESP32D0WDQ6 (revision 1) without SPIRAM. It has
-also been tested on the Unix port.
+The following features are supported:
-There might be some stuff missing, some bugs and other symptoms of beta
-software. Also, error and exception handling is rather rough yet.
+* the entire `ESP32 ULP instruction set `_
+* constants defined with ``.set``
+* constants defined with ``#define``
+* expressions in assembly code and constant definitions
+* RTC convenience macros (e.g. ``WRITE_RTC_REG``)
+* many ESP32 ULP code examples found on the web will work unmodified
-Please be patient or contribute missing parts or fixes.
-See the issue tracker for known bugs and todo items.
+Quick start
+-----------
+To get going run the following directly on the ESP32:
-Links
------
+.. code-block:: python
-We are NOT (fully) compatible with "as", but we try to be close for the stuff
-that is actually implemented:
+ # Step 1: Install py-esp32-ulp
+ # IMPORTANT: Ensure the ESP32 is connected to a network with internet connectivity.
+ import upip
+ upip.install('micropython-py-esp32-ulp')
-https://sourceware.org/binutils/docs/as/index.html
+ # Step 2: Run an example
+ # First, upload examples/counter.py to the ESP32.
+ import counter
-Espressif docs:
+The `examples/counter.py `_ example shows how to assemble code, load
+and run the resulting binary and exchange data between the ULP and the main CPU.
-https://esp-idf.readthedocs.io/en/latest/api-guides/ulp_instruction_set.html
-https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf
+Documentation
+-------------
+See `docs/index.rst `_.
-Espressif ULP examples:
-https://github.com/espressif/esp-iot-solution/tree/master/examples/ulp_examples
+Requirements
+------------
+
+The minimum supported version of MicroPython is v1.12.
+
+An ESP32 is required to run the ULP machine code binary produced by py-esp32-ulp
+(the ESP32-S2 will not work as it is not binary compatible with the ESP32).
+
+
+License
+-------
+
+This project is released under the `MIT License `_.
diff --git a/docs/index.rst b/docs/index.rst
new file mode 100644
index 0000000..ba06dff
--- /dev/null
+++ b/docs/index.rst
@@ -0,0 +1,173 @@
+py-esp32-ulp Documentation
+==========================
+
+.. contents:: Table of Contents
+
+
+Overview
+--------
+
+`README.rst `_ gives a general overview of this project.
+
+
+Installation
+------------
+
+On the ESP32, install using upip:
+
+.. code-block:: python
+
+ # ensure the ESP32 is connected to a network with internet connectivity
+ import upip
+ upip.install('micropython-py-esp32-ulp')
+
+On a PC, simply ``git clone`` this repo.
+
+
+Getting started
+---------------
+
+On the ESP32
+++++++++++++
+
+The simplest example to try on the ESP32 is `counter.py `_.
+It shows how to assemble code, load and run the resulting binary and exchange
+data between the ULP and the main CPU.
+
+Run the ``counter.py`` example:
+
+1. Install py-esp32-ulp onto the ESP32 as shown above
+2. Upload the `examples/counter.py `_ file to the ESP32
+3. Run with ``import counter``
+
+You can also try the `blink.py `_ example, which shows how to
+let the ULP blink an LED.
+
+Look inside each example for a more detailed description.
+
+
+On a PC
++++++++
+
+On a PC with the unix port of MicroPython, you can assemble source code as
+follows:
+
+.. code-block:: shell
+
+ git clone https://github.com/ThomasWaldmann/py-esp32-ulp.git
+ cd py-esp32-ulp
+ micropython -m esp32_ulp path/to/code.S # this results in path/to/code.ulp
+
+
+More examples
++++++++++++++
+
+Other ULP examples from around the web:
+
+* https://github.com/espressif/esp-iot-solution/tree/master/examples/ulp_examples
+* https://github.com/duff2013/ulptool
+* https://github.com/joba-1/Blink-ULP/blob/master/main/ulp/
+
+
+Advanced usage
+--------------
+
+In some applications you might want to separate the assembly stage from the
+loading/running stage, to avoid having to assemble the code on every startup.
+This can be useful in battery-powered applications where every second of sleep
+time matters.
+
+Splitting the assembly and load stage can be combined with other techniques,
+for example to implement a caching mechansim for the ULP binary that
+automatically updates the binary every time the assembly source code changes.
+
+The ``esp32_ulp.assemble_file`` function can be used to assemble and link an
+assembly source file into a machine code binary file with a ``.ulp`` extension.
+That file can then be loaded directly without assembling the source again.
+
+1. Create/upload an assembly source file and run the following to get a
+ loadable ULP binary as a ``.ulp`` file:
+
+ .. code-block:: python
+
+ import esp32_ulp
+ esp32_ulp.assemble_file('code.S') # this results in code.ulp
+
+2. The above prints out the offsets of all global symbols/labels. For the next
+ step, you will need to note down the offset of the label, which represents
+ the entry point to your code.
+
+3. Now load and run the resulting binary as follows:
+
+ .. code-block:: python
+
+ from esp32 import ULP
+
+ ulp = ULP()
+ with open('test.ulp', 'r') as f:
+ # load the binary into RTC memory
+ ulp.load_binary(0, f.read())
+
+ # configure how often the ULP should wake up
+ ulp.set_wakeup_period(0, 500000) # 500k usec == 0.5 sec
+
+ # start the ULP
+ # assemble_file printed offsets in number of 32-bit words.
+ # ulp.run() expects an offset in number of bytes.
+ # Thus, multiply the offset to our entry point by 4.
+ # e.g. for an offset of 2:
+ # 2 words * 4 = 8 bytes
+ ulp.run(2*4) # specify the offset of the entry point label
+
+To update the binary every time the source code changes, you would need a
+mechanism to detect that the source code changed. This could trigger a re-run
+of the ``assemble_file`` function to update the binary. Manually re-running
+this function as needed would also work.
+
+
+Preprocessor
+------------
+
+There is a simple preprocessor that understands just enough to allow assembling
+ULP source files containing convenience macros such as WRITE_RTC_REG. This is
+especially useful for assembling ULP examples from Espressif or other ULP code
+found as part of Arduino/ESP-IDF projects.
+
+The preprocessor and how to use it is documented here: `Preprocessor support `_.
+
+
+Limitations
+-----------
+
+Currently the following are not supported:
+
+* assembler macros using ``.macro``
+* preprocessor macros using ``#define A(x,y) ...``
+* including files using ``#include``
+* ESP32-S2 (not binary compatible with the ESP32)
+
+
+Testing
+-------
+
+There are unit tests and also compatibility tests that check whether the binary
+output is identical with what binutils-esp32ulp produces.
+
+py-esp32-ulp has been tested on the Unix port of MicroPython and on real ESP32
+devices with the chip type ESP32D0WDQ6 (revision 1) without SPIRAM.
+
+Consult the Github Actions `workflow definition file `_
+for how to run the different tests.
+
+
+Links
+-----
+
+Espressif documentation:
+
+* `ESP32 ULP coprocessor instruction set `_
+* `ESP32 Technical Reference Manual `_
+
+GNU Assembler "as" documentation (we try to be compatible for all features that are implemented)
+
+* `GNU Assembler manual `_
diff --git a/docs/preprocess.rst b/docs/preprocess.rst
index 0716e69..a3bba20 100644
--- a/docs/preprocess.rst
+++ b/docs/preprocess.rst
@@ -1,5 +1,6 @@
+=====================
Preprocessor
----------------------
+=====================
py-esp32-ulp contains a small preprocessor, which aims to fulfill one goal:
facilitate assembling of ULP code from Espressif and other open-source
diff --git a/examples/README b/examples/README
deleted file mode 100644
index b758d49..0000000
--- a/examples/README
+++ /dev/null
@@ -1,3 +0,0 @@
-To run the micropython examples which load and run binaries on the ULP,
-you need a ESP32 MicroPython build that was made after 2018-05-01.
-
diff --git a/setup.py b/setup.py
index 00093a7..e16ceda 100644
--- a/setup.py
+++ b/setup.py
@@ -1,18 +1,28 @@
-from pathlib import Path
-from setuptools import setup
+import re
import sdist_upip
+from setuptools import setup
+VERSION = "1.0.0"
-HERE = Path(__file__).parent
-README = (HERE / 'README.rst').read_text()
-VERSION = "1.0.0"
+def long_desc_from_readme():
+ with open('README.rst', 'r') as fd:
+ long_description = fd.read()
+
+ # remove badges
+ long_description = re.compile(r'^\.\. start-badges.*^\.\. end-badges', re.M | re.S).sub('', long_description)
+
+ # strip links. keep link name and use literal text formatting
+ long_description = re.sub(r'`([^<`]+) [^>]+>`_', '``\\1``', long_description)
+
+ return long_description
+
setup(
name="micropython-py-esp32-ulp",
version=VERSION,
description="Assembler toolchain for the ESP32 ULP co-processor, written in MicroPython",
- long_description=README,
+ long_description=long_desc_from_readme(),
long_description_content_type='text/x-rst',
url="https://github.com/ThomasWaldmann/py-esp32-ulp",
license="MIT",