Git Product home page Git Product logo

micropython-esp32-ulp's Introduction

Build Status

micropython-esp32-ulp

micropython-esp32-ulp is an assembler toolchain for the ESP32 ULP (Ultra Low-Power) Co-Processor, written in MicroPython.

It can translate small assembly language programs to a loadable/executable ULP-FSM (not RISC-V) machine code binary, directly on a ESP32 microcontroller.

This is intended as an alternative approach to assembling such programs using the binutils-gdb toolchain (esp32-elf-as) from Espressif on a development machine.

It can also be useful in cases where esp32-elf-as is not available.

Features

The following features are supported:

  • the entire ESP32 ULP instruction set
  • the entire ESP32-S2 ULP instruction set (this also covers the ESP32-S3) [1] [2]
  • 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
  • a simple disassembler is also provided
[1]Note: the ESP32-S2 and ESP32-S3 have the same ULP binary format between each other but the binary format is different than that of the original ESP32 ULP. You need to select the esp32s2 cpu (see docs) when assembling code for use on an ESP32-S2/S3.
[2]Note: The ESP32-S2 and ESP32-S3 have the same ULP binary format, but the peripheral register addresses (those accessed with REG_RD and REG_WR) are different. For best results, use the correct peripheral register addresses for the specific variant you are working with. The assembler (when used with cpu=esp32s2) will accept addresses for any of the 3 variants, because they are translated into relative offsets anyway and many registers live at the same relative offset on all 3 variants. This conveniently means that the same assembly code can assembled unmodified for each variant and produce a correctly working binary - as long as only peripheral registers are used, which have the same relative offset across the variants. Use with care!

Quick start

To get going run the following directly on the ESP32:

# IMPORTANT: Ensure the ESP32 is connected to a network with internet connectivity.

# Step 1: Install micropython-esp32-ulp (for MicroPython v1.20 or newer)
import mip
mip.install('github:micropython/micropython-esp32-ulp')

# Step 1: Install micropython-esp32-ulp (for MicroPython older than v1.20)
import upip
upip.install('micropython-esp32-ulp')

# Step 2: Run an example
# First, upload examples/counter.py to the ESP32.
import counter

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.

Documentation

See docs/index.rst.

Requirements

The minimum supported version of MicroPython is v1.12. (For ESP32-S2 and S3 devices, a version greater than v1.20 is required as versions before that did not enable the esp32.ULP class).

An ESP32 device is required to run the ULP machine code binary produced by micropython-esp32-ulp.

License

This project is released under the MIT License.

micropython-esp32-ulp's People

Contributors

ameeuw avatar igrr avatar plaque-fcc avatar thomaswaldmann avatar wnienhaus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

micropython-esp32-ulp's Issues

loadable file format

espressif's IDF ulp code loader expects some specific format for a loadable binary.

magic, sizes of text/data/bss, ...

we do not generate that yet.

make imports less ugly

In examples/counter.py, I had to import from __main__ which is a bit ugly.

So, move stuff so that importing from dunder modules is not needed.

improve argument handling

handling of opcode arguments (like giving registers, immediate numeric values, labels / addresses).

guess the opcode should say what it wants and the value actually given should be validated to comply with the expectation.

how to read ULP result data from micropython?

as a result of the assembler run, we have a symbol table (name -> offset).
what we do not have (yet?) is type information.

if we had that, we could build a uctypes.struct to comfortably deal with the result (like x = ulp.name).

any other ideas?

best practice: unit tests on micropython?

Usually I use pytest to run tests, but I guess that does not work on MicroPython?

So what is a sane, working test runner?

Currently I do testing using the UNIX port of MicroPython.

comments...

comment styles:

  • # until end of line, python style
  • // until end of line, C style
  • /* until reaching comment end, C style */

LED is not blinking

Hello there and thank you so much for your great library and work. I want to start playing around with the ULP copro and as a first step, I want to get the blinky example getting to run. I am working with micropython 1.18 on Heltec Wifi Kit32. On this board, the onboard LED is on GPIO25, so I have changed the definition to:

#define RTCIO_GPIO25_CHANNEL 6

If I start the program it will run without an error and I still can see the print output changing from 1 to 0 and vice versa, but the LED doesn´t light. Could you give me a hint, of what I am doing wrong?

Thanks in advance and best regards,
Werner

Trouble running example with micropython 1.11

Firstly, thanks for contributing this to Micropython. It seems very promising.

Unfortunately, I am unable to run the example code. I get the following error:

Traceback (most recent call last):
  File "<stdin>", line 37, in <module>
OSError: 260

This occurs at ulp.load_binary(load_addr, binary).

Googling the error it seems to be an issue with the binary.

The binary generated is:

>>> print(binary)
b'ulp\x00\x0c\x00\x18\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x80r\x0e\x00\x00\xd0\x1a\x00\x00r\x0e\x00\x00h\x00\x00\x00\xb0'

using not case-sensitive symbols?

Usually I am all for case-sensitivity, because it avoids a lot of issues.

But considering the symbol names from espressif have a lot of SYMBOLS_LIKE_THAT, I find that rather ugly, so I am considering just ignoring case, so typing the same stuff in lowercase does also match.

Opinions?

Update Actions runner image

Github has deprecated the ubuntu-18.04 image for running Github Actions with (see actions/runner-images#6002).

We will need to ensure our build works with newer Ubuntu versions.

(Last time I tried, the binutils-esp32 build process did not work on ubuntu versions later than 18.04, so this might be more effort than just changing the image name 😞).

add useful examples

we need some example code, like:

  • py: assemble, load and run some ulp code (done, see examples/counter.py)
  • py/asm: give data to / fetch data from ulp (done, see examples/counter.py)
  • asm: ulp blink
  • py/asm: implement framework for ULP data acquisition (collect data with ULP [while main CPU sleeps] into buffer, wake up main CPU, read/process buffer by main CPU, go to sleep again)

Maybe it is best to open a new ticket for each example we are working on.

Automate publish to PyPI

To make it easier to publish py-esp32-ulp to PyPI, we can automate it.
This would allow automatic publishing (by triggering a Github Action) every time a new Release is created in Github.

So far I have tested this approach in: https://github.com/wnienhaus/ujqtvms in the publish.yml workflow.

This could also make it easier for various maintainers to publish without needing to share PyPI credentials or documentation on how to manually do this.

Bug: Upper case expressions not supported

Current behaviour:

Expressions with hex characters in uppercase cause assembly to fail with the error ValueError: Unsupported expression.

Expected behaviour:

The expected behaviour is that uppercase hex characters are treated identically to lower case hex characters.

Example:

This code fails to assemble with "Unsupported expression"

entry:
  move r0, 0x7FE - 1

While this code is successfully assembled without error:

entry:
  move r0, 0x7fe - 1

The only difference in the two code snippets is the hex value 0x7FE being upper case in one example (the failing one) and lower case in the other (the successful one).

JUMPS instruction conditions

ESP32 TRM prior to v3.6 incorrectly described conditions used by JUMPS instruction.

TRM is now updated and this has been fixed in binutils-esp32ulp: diffs, docs.

Would be great to propagate these updates here as well.

optimizing memory usage

py-esp32-ulp is able to assemble about 3kB of source code (when it is running on a standard ESP32 "WROOM" chip) - more gives a MemoryError (out of memory). Bigger amounts of source code can be assembled when running on a device with more RAM (like on a development machine running the UNIX port of micropython).

PR #36 improved this a bit by calling the garbage collector now and then.

The limit looks rather low, but considering that the ULP has only a total of 4kiB of RAM and that we will likely only be allowed to use ~2kiB of it from micropython and that usually some of it is needed as a buffer (thus: not for code), the limit doesn't look too bad.

(moved from #35)

We can collect ideas here about how to improve memory usage in case we need to.

processing of labels (address symbols)

Looks like that for a simple implementation we need a 2-pass assembler:

  • pass 1: generating code (text segment), data segment, bss segment. but as we do not know all labels yet, some absolute addresses or relative offsets will be unknown (for code generation we'll just use 0) and thus the generated code will be wrong. But we are only interested in knowing all labels (offsets relative to some segment start) at the end of pass 1.
  • pass 2: really create text, data, bss segments as we now know correct addresses.

Symbol table would be like:

symbol/label ---> (global, segment, value)
global: flag set to True by .global
segment: TEXT, DATA, BSS or None if it is not some kind of address
value: relative offset from segment start

practical tests on ESP32

the code was developed / tested on the UNIX port of micropython on Linux/x64.

practically run the code on ESP32 hardware, assembling some ULP code.

run the tests on ESP32 hardware.

Thank you so much for great lib, I cannot figure out I2C RD/WR.

Greetings!
Thank you for the great lib for micropython.
I could have done many things with your lib, controlling lcd brightness during deepsleep,
communicate with between ULP and main core. It is so great!

I've asked in micropython forum, But I cannot still firgure out hot to get the I2C RD/WR.

I've got some sensors which doesn't have interrupts, I would like to wirte and read a single byte.
Referring the ESP32 technical reference manual,
, intializing I2C connection, but it doesn't work.
Could you please help me to get there?

#define DR_REG_RTCIO_BASE            0x3ff48400
#define DR_REG_RTC_I2C_BASE          0x3ff48C00
#define DR_REG_SENS_BASE             0x3ff48800                  
#define SENS_SAR_SLAVE_ADDR1_REG     0x3FF4883C 
#define SENS_I2C_SLAVE_ADDR0         0x000007FF                  
#define SENS_I2C_SLAVE_ADDR0_S       11
#define SENS_I2C_SLAVE_ADDR1_S       0
#define RTC_IO_SAR_I2C_IO_REG        0x3FF484C4
#define RTC_IO_SAR_I2C_SDA_SEL_S     30
#define RTC_IO_SAR_I2C_SCL_SEL_S     28
#define RTC_I2C_SCL_LOW_PERIOD_REG   0x3FF48C00 
#define RTC_I2C_SCL_HIGH_PERIOD_REG  0x3FF48C38
#define RTC_I2C_SCL_LOW_PERIOD_S     0
#define RTC_I2C_SCL_HIGH_PERIOD_S    0
#define RTC_I2C_SDA_DUTY_REG         0x3FF48C30
#define RTC_I2C_SDA_DUTY_S           0
#define RTC_I2C_SCL_START_PERIOD_REG 0x3FF48C40
#define RTC_I2C_SCL_START_PERIOD_S   0
#define RTC_I2C_SCL_STOP_PERIOD_REG  0x3FF48C44
#define RTC_I2C_SCL_STOP_PERIOD_S    0
#define RTC_I2C_TIMEOUT_REG          0x3FF48C0C
#define RTC_I2C_TIMEOUT_S            0
#define RTC_I2C_CTRL_REG             0x3FF48C04
#define RTC_I2C_MS_MODE_S            4 
#define DR_REG_RTCIO_BASE            0x3ff48400
#define RTC_IO_TOUCH_PAD0_REG        0x3ff48494 
#define RTC_IO_TOUCH_PAD1_REG        0x3ff48498 
#define RTC_IO_TOUCH_PAD2_REG        0x3ff4849c 
#define RTC_IO_TOUCH_PAD3_REG        0x3ff484a0 
#define RTC_IO_TOUCH_PAD0_FUN_SEL_S  17 
#define RTC_IO_TOUCH_PAD1_FUN_SEL_S  17 

data:        .long 0
value:       .long 0

.global entry
entry:   
# Select SDA/SCL pins to version 1, TOUCH2 and TOUCH3 (version 0 is TOUCH0 and TOUCH1)
WRITE_RTC_REG(RTC_IO_SAR_I2C_IO_REG, RTC_IO_SAR_I2C_SDA_SEL_S, 2, 0)
WRITE_RTC_REG(RTC_IO_SAR_I2C_IO_REG, RTC_IO_SAR_I2C_SCL_SEL_S, 2, 0)
WRITE_RTC_REG(RTC_IO_TOUCH_PAD0_REG, RTC_IO_TOUCH_PAD0_FUN_SEL_S, 2, 3) 
WRITE_RTC_REG(RTC_IO_TOUCH_PAD1_REG, RTC_IO_TOUCH_PAD1_FUN_SEL_S, 2, 3) 

# WRITE_RTC_REG(rtc_reg, low_bit, bit_width, value)
WRITE_RTC_REG(SENS_SAR_SLAVE_ADDR1_REG, SENS_I2C_SLAVE_ADDR0_S, 11, 0x76)

# Set SCL speed to 100khz
WRITE_RTC_REG(RTC_I2C_SCL_LOW_PERIOD_REG, RTC_I2C_SCL_LOW_PERIOD_S, 19, 40)
WRITE_RTC_REG(RTC_I2C_SCL_HIGH_PERIOD_REG, RTC_I2C_SCL_HIGH_PERIOD_S, 20, 40)

# SDA duty (delay) cycles from falling edge of SCL when SDA changes.
WRITE_RTC_REG(RTC_I2C_SDA_DUTY_REG, RTC_I2C_SDA_DUTY_S, 20, 16)

# Number of cycles after start/stop condition
WRITE_RTC_REG(RTC_I2C_SCL_START_PERIOD_REG, RTC_I2C_SCL_START_PERIOD_S, 20, 30)
WRITE_RTC_REG(RTC_I2C_SCL_STOP_PERIOD_REG, RTC_I2C_SCL_STOP_PERIOD_S, 20, 44)

# cycles before timeout
WRITE_RTC_REG(RTC_I2C_TIMEOUT_REG, RTC_I2C_TIMEOUT_S, 20, 200)

# Set mode to master
WRITE_RTC_REG(RTC_I2C_CTRL_REG, RTC_I2C_MS_MODE_S, 1, 1)

I2C_RD  0xa2, 7, 0, 0

Requesting a WAKE example

I would like an example of how to use WAKE because I'm not quite sure how it's supposed to work. I'll contribute one if I can, but right now it doesn't work. Should it?

#
# Example of putting the main processor to sleep then waking it up
# via the ULP after a while
#
from esp32 import ULP
from machine import mem32
import machine

from esp32_ulp import src_to_binary

source = """\
#define RTC_CNTL_LOW_POWER_ST_REG 0x3FF480c0
#define RTC_CNTL_RDY_FOR_WAKEUP BIT(19)

entry:
        MOVE r0, 2000

loop:
        WAIT 40000  // at 8 MHz this is 5 msec, if we do this 1000 times that's 5 seconds
        SUB r0, r0, 1
        jump is_rdy_for_wakeup, eq
        jump loop


is_rdy_for_wakeup:                   // Read RTC_CNTL_RDY_FOR_WAKEUP bit
#
# This test was described in https://github.com/espressif/esp-idf/issues/8341
# See reference manual 31.19.
#
# The main issue with this is that it will hang if the main CPU wasn't in deep sleep.
#
       READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)
       AND r0, r0, 1
       JUMP is_rdy_for_wakeup, eq    // Retry until the bit is set

       WAKE                          // Trigger wake up
       HALT                          // Stop the ULP program
"""

#
# Compile the ULP code
#
binary = src_to_binary(source)

load_addr, entry_addr = 0, 0
ULP_MEM_BASE = 0x50000000
ULP_DATA_MASK = 0xffff  # ULP data is only in lower 16 bits

ulp = ULP()
ulp.load_binary(load_addr, binary)

#
# The ULP starts out asleep (halted), so set a timer to wake it
# up the first time.
#
# ulp.set_wakeup_period(0, 50000)  # use timer0, wakeup after 50.000 cycles

mem32[ULP_MEM_BASE + load_addr] = 0x1000
ulp.run(entry_addr)

print("going to sleep")

# I think this should enable ULP wakeup
mem32[ 0x3ff48038 ] = 0x200 << 10
machine.deepsleep()


#
# We won't ever reach this, because WAKE is going to cause the main
# processor to reset and start over at the beginning.
#
print("I am awake again")

clarify "not implemented"-like comments in ulp.h

there are some such comments in espressif's ulp.h and while they say "not implemented", at least some of them have code in that file.

does it mean the functionality is not implemented in the hardware (ULP coprocessor) yet?

or is it just some leftover that is outdated and should be removed?

memory problem

I've got the ulp counter in a 14k .mpy file as a def. If the def is not called everything runs OK so the 14k file size is not a problem. There is in fact plenty of ram because the test I'm running is to download a new version of the 14k .mpy file, so there is at least 14k spare.

However if I call the counter def, urequests crashes with "MemoryError: memory allocation failed, allocating 1792 bytes". This is a problem I've struck before on other boards with different defs. For example on ESP32camera boards I have to deinit the camera after taking a pic in the camera def before using urequests or else a similar memory error from urequests.

I'm a bit worried this time though because I can't just deinit the ulp counter, it needs to keep running during deepsleep. Apart from the slow mem it uses for the count & state registers does the counter.py progrm set aside any ram that wouldn't be clawed back automatically when the counter def returns the count to the .mpy?

PS: unlike my trials with with the ESP camera boards, where any attempt to use urequest would crash, my problem with the ulp counter def on this DEVKIT1 board only occurs when I try to download a 14k file from the server, the other urequest tasks (like uploading the count) are working OK.

Can't get it to run

Originally posted by @kjm1102 in #61 (comment) :

I can't get it to run
Traceback (most recent call last): File "<stdin>", line 4, in <module> ImportError: no module named 'esp32_ulp'

1.0.0 milestone

considering the great work of @wnienhaus, this is much closer to be practically useful than previously.

so i create a milestone for first release - maybe only add very important features and bug fixes into there, so we can do a first release soon.

find a new and better home for this project

while i did create this project and did a lot of the ground work, i didn't work too much on it recently nor did i practically use it too much.

also, i generally think that a project that's not just for personal use but somehow generally useful should rather live at some "neutral" place (like in a github organisation) than in someone's personal github account.

@wnienhaus did a lot of work recently, so this is now much more practically usable than it was before, so i guess this is now ready to be used, enhanced and maintained by a community of interested people.

so, let's collect ideas about where this should live in future (github orgs)...

Questiona about GPIO Output (LED) example

WRITE_RTC_REG(RTC_GPIO_ENABLE_W1TS_REG, RTC_GPIO_ENABLE_W1TS_S + gpio, 1, 1)

Do I understand in this bit of code that you're turning the led on and off by enabling or disabling the GPIO output? I'm curious why you would have done that, rather than always initializing the pin as an output, then writing to RTCIO_RTC_GPIO_OUT_REG, RTCIO_RTC_GPIO_OUT_W1TS_REG, or RTCIO_RTC_GPIO_OUT_W1TC_REG?

add CI?

I can submit a PR to run some code ULP code through py-esp32-ulp and through binutils-esp32ulp, and compare the generated code, if you think this is useful.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.