Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ports/esp32 add support for the ulp #3578

Closed
wants to merge 10 commits into from
Closed
7 changes: 7 additions & 0 deletions ports/esp32/Makefile
Expand Up @@ -83,6 +83,7 @@ INC_ESPCOMP += -I$(ESPCOMP)/lwip/include/lwip/posix
INC_ESPCOMP += -I$(ESPCOMP)/mbedtls/include
INC_ESPCOMP += -I$(ESPCOMP)/mbedtls/port/include
INC_ESPCOMP += -I$(ESPCOMP)/spi_flash/include
INC_ESPCOMP += -I$(ESPCOMP)/ulp/include
INC_ESPCOMP += -I$(ESPCOMP)/vfs/include
INC_ESPCOMP += -I$(ESPCOMP)/newlib/platform_include
INC_ESPCOMP += -I$(ESPCOMP)/xtensa-debug-module/include
Expand Down Expand Up @@ -149,6 +150,7 @@ SRC_C = \
modsocket.c \
modesp.c \
modesp32.c \
esp32_ulp.c \
moduhashlib.c \
espneopixel.c \
machine_hw_spi.c \
Expand Down Expand Up @@ -417,6 +419,10 @@ ESPIDF_SPI_FLASH_O = $(addprefix $(ESPCOMP)/spi_flash/,\
flash_ops.o \
)

ESPIDF_ULP_O = $(addprefix $(ESPCOMP)/ulp/,\
ulp.o \
)

$(BUILD)/$(ESPCOMP)/lwip/%.o: CFLAGS += -Wno-address -Wno-unused-variable -Wno-unused-but-set-variable
ESPIDF_LWIP_O = $(addprefix $(ESPCOMP)/lwip/,\
api/pppapi.o \
Expand Down Expand Up @@ -605,6 +611,7 @@ OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_NGHTTP_O))
OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_NVS_FLASH_O))
OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_OPENSSL_O))
OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_SPI_FLASH_O))
OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_ULP_O))
OBJ_ESPIDF += $(addprefix $(BUILD)/, $(ESPIDF_WPA_SUPPLICANT_O))
################################################################################
# Main targets
Expand Down
126 changes: 126 additions & 0 deletions ports/esp32/README.ulp.md
@@ -0,0 +1,126 @@
# ULP

To compile binarys for the ulp you need the ulp toolkit. Download it from https://github.com/espressif/binutils-esp32ulp/wiki#downloads
Then extract it, then add ```esp32ulp-elf-binutils/bin``` to your PATH

## Example Makefile

```make
ULP_S_SOURCES := main.S
ULP_APP_NAME := test
ULP_LD_SCRIPT := esp32.ulp.ld

SRC_PATH := src
BUILD_PATH := build

include $(ESPIDF)/components/ulp/Makefile.projbuild

ULP_ELF := $(ULP_APP_NAME).elf
ULP_MAP := $(ULP_ELF:.elf=.map)
ULP_SYM := $(ULP_ELF:.elf=.sym)
ULP_BIN := $(ULP_ELF:.elf=.bin)
ULP_EXPORTS_LD := $(ULP_ELF:.elf=.ld)
ULP_EXPORTS_HEADER := $(ULP_ELF:.elf=.h)

ULP_OBJECTS := $(notdir $(ULP_S_SOURCES:.S=.ulp.o))
ULP_DEP := $(notdir $(ULP_S_SOURCES:.S=.ulp.d)) $(ULP_LD_SCRIPT:.ld=.d)
ULP_PREPROCESSED := $(notdir $(ULP_S_SOURCES:.S=.ulp.pS))
ULP_LISTINGS := $(notdir $(ULP_S_SOURCES:.S=.ulp.lst))

.PHONY: all clean

all: $(BUILD_PATH) $(BUILD_PATH)/$(ULP_BIN)

clean:
rm -rf $(BUILD_PATH)

$(BUILD_PATH):
mkdir $@

# Generate preprocessed linker file.
$(BUILD_PATH)/$(ULP_APP_NAME).ld: $(SRC_PATH)/$(ULP_LD_SCRIPT)
cpp -P $< -o $@

# Generate preprocessed assembly files.
# To inspect these preprocessed files, add a ".PRECIOUS: %.ulp.pS" rule.
$(BUILD_PATH)/%.ulp.pS: $(SRC_PATH)/%.S
cpp $< -o $@

# Compiled preprocessed files into object files.
$(BUILD_PATH)/%.ulp.o: $(BUILD_PATH)/%.ulp.pS
$(ULP_AS) -al=$(patsubst %.ulp.o,%.ulp.lst,$@) -o $@ $<

# Link object files and generate map file
$(BUILD_PATH)/$(ULP_ELF): $(BUILD_PATH)/$(ULP_OBJECTS) $(BUILD_PATH)/$(ULP_APP_NAME).ld
$(ULP_LD) -o $@ -A elf32-esp32ulp -Map=$(BUILD_PATH)/$(ULP_MAP) -T $(BUILD_PATH)/$(ULP_APP_NAME).ld $<

# Dump the list of global symbols in a convenient format.
$(ULP_SYM): $(ULP_ELF)
$(ULP_NM) -g -f posix $< > $@

# Dump the binary for inclusion into the project
$(BUILD_PATH)/$(ULP_BIN): $(BUILD_PATH)/$(ULP_ELF)
$(ULP_OBJCOPY) -O binary $< $@
```

## Example linker script for the ulp
```
#define ULP_BIN_MAGIC 0x00706c75
#define HEADER_SIZE 12
#define CONFIG_ULP_COPROC_RESERVE_MEM 4096

MEMORY
{
ram(RW) : ORIGIN = 0, LENGTH = CONFIG_ULP_COPROC_RESERVE_MEM
}

SECTIONS
{
.text : AT(HEADER_SIZE)
{
*(.text)
} >ram
.data :
{
. = ALIGN(4);
*(.data)
} >ram
.bss :
{
. = ALIGN(4);
*(.bss)
} >ram

.header : AT(0)
{
LONG(ULP_BIN_MAGIC)
SHORT(LOADADDR(.text))
SHORT(SIZEOF(.text))
SHORT(SIZEOF(.data))
SHORT(SIZEOF(.bss))
}
}
```

## Example ulp code
```asm
move R3, 99
move R0, 10

# mem[R0+0] = R3
st R3, R0, 0

HALT

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could there be a more practical example (without getting too long / too complex), where the python code feeds some data to the ulp program and also fetches from data back from it?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not easily. It is possible to access the slow mem from the upstream using magic values and normal memory access. We use a machine.RTC().slowmem[addr] construct internally, but it is not upstream and we do not plan to publish it. That is why i choose a very limited example.

```

## Example python code using the ulp
```python
import esp32
import time

u = esp32.ULP()
with open('test.bin', 'rb') as f:
b = f.read()
u.load_binary(0,b)
u.run(0)
```
98 changes: 98 additions & 0 deletions ports/esp32/esp32_ulp.c
@@ -0,0 +1,98 @@
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2018 "Andreas Valder" <andreas.valder@serioese.gmbh>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "py/obj.h"
#include "py/runtime.h"
#include "py/mperrno.h"

#include "esp32/ulp.h"
#include "esp_err.h"

typedef struct _esp32_ulp_obj_t {
mp_obj_base_t base;
} esp32_ulp_obj_t;

const mp_obj_type_t esp32_ulp_type;

// singleton ULP object
STATIC const esp32_ulp_obj_t esp32_ulp_obj = {{&esp32_ulp_type}};

STATIC mp_obj_t esp32_ulp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
// check arguments
mp_arg_check_num(n_args, n_kw, 0, 0, false);

// return constant object
return (mp_obj_t)&esp32_ulp_obj;
}

STATIC mp_obj_t esp32_ulp_set_wakeup_period(mp_obj_t self_in, mp_obj_t period_index_in, mp_obj_t period_us_in) {
mp_uint_t period_index = mp_obj_get_int(period_index_in);
mp_uint_t period_us = mp_obj_get_int(period_us_in);
int _errno = ulp_set_wakeup_period(period_index, period_us);
if (_errno != ESP_OK) {
mp_raise_OSError(_errno);
}
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_ulp_set_wakeup_period_obj, esp32_ulp_set_wakeup_period);

STATIC mp_obj_t esp32_ulp_load_binary(mp_obj_t self_in, mp_obj_t load_addr_in, mp_obj_t program_binary_in) {
mp_uint_t load_addr = mp_obj_get_int(load_addr_in);

mp_buffer_info_t bufinfo;
mp_get_buffer_raise(program_binary_in, &bufinfo, MP_BUFFER_READ);

int _errno = ulp_load_binary(load_addr, bufinfo.buf, bufinfo.len/sizeof(uint32_t));
if (_errno != ESP_OK) {
mp_raise_OSError(_errno);
}
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_ulp_load_binary_obj, esp32_ulp_load_binary);

STATIC mp_obj_t esp32_ulp_run(mp_obj_t self_in, mp_obj_t entry_point_in) {
mp_uint_t entry_point = mp_obj_get_int(entry_point_in);
int _errno = ulp_run(entry_point/sizeof(uint32_t));
if (_errno != ESP_OK) {
mp_raise_OSError(_errno);
}
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_ulp_run_obj, esp32_ulp_run);

STATIC const mp_map_elem_t esp32_ulp_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_set_wakeup_period), (mp_obj_t)&esp32_ulp_set_wakeup_period_obj },
{ MP_ROM_QSTR(MP_QSTR_load_binary), (mp_obj_t)&esp32_ulp_load_binary_obj },
{ MP_ROM_QSTR(MP_QSTR_run), (mp_obj_t)&esp32_ulp_run_obj },
{ MP_ROM_QSTR(MP_QSTR_RESERVE_MEM), MP_ROM_INT(CONFIG_ULP_COPROC_RESERVE_MEM) },
};
STATIC MP_DEFINE_CONST_DICT(esp32_ulp_locals_dict, esp32_ulp_locals_dict_table);

const mp_obj_type_t esp32_ulp_type = {
{ &mp_type_type },
.name = MP_QSTR_ULP,
.make_new = esp32_ulp_make_new,
.locals_dict = (mp_obj_t)&esp32_ulp_locals_dict,
};
1 change: 1 addition & 0 deletions ports/esp32/modesp32.c
Expand Up @@ -128,6 +128,7 @@ STATIC const mp_rom_map_elem_t esp32_module_globals_table[] = {
{ MP_OBJ_NEW_QSTR(MP_QSTR_wake_on_ext1), (mp_obj_t)&esp32_wake_on_ext1_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_WAKEUP_ALL_LOW), mp_const_false },
{ MP_OBJ_NEW_QSTR(MP_QSTR_WAKEUP_ANY_HIGH), mp_const_true },
{ MP_ROM_QSTR(MP_QSTR_ULP), MP_ROM_PTR(&esp32_ulp_type) },
};

STATIC MP_DEFINE_CONST_DICT(esp32_module_globals, esp32_module_globals_table);
Expand Down
2 changes: 2 additions & 0 deletions ports/esp32/modesp32.h
Expand Up @@ -26,4 +26,6 @@
#define RTC_LAST_EXT_PIN 39
#define RTC_IS_VALID_EXT_PIN(pin_id) ((1ll << (pin_id)) & RTC_VALID_EXT_PINS)

extern const mp_obj_type_t esp32_ulp_type;

#endif // MICROPY_INCLUDED_ESP32_MODESP32_H
2 changes: 1 addition & 1 deletion ports/esp32/sdkconfig.h
Expand Up @@ -5,7 +5,7 @@

#define CONFIG_TRACEMEM_RESERVE_DRAM 0x0
#define CONFIG_BT_RESERVE_DRAM 0x0
#define CONFIG_ULP_COPROC_RESERVE_MEM 0
#define CONFIG_ULP_COPROC_RESERVE_MEM 2040
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there a reason for the value 2040? Why not 2048? (The default value from the ESP IDF is 512.)

Also, isn't in necessary to define ULP_COPROC_ENABLED?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ULP_COPROC_ENABLED seems to be ignored by the current build system, but for consistency we should probably set it. I choose the largest posible value. 2048 is not posible because micropython it self uses a part of the rtc slow memory already. Sadly I found no way to calculate the remaining space there and had to hard code it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should set ULP_COPROC_ENABLED to 1 (it seems to be used by esp_sleep_enable_ulp_wakeup).

For the RESERVE_MEM value: ultimately I think the best solution is to overlap the ULP reserved mem with the machine.RTC().memory() feature so that the RTC class can be used to read/write into the ULP and they can talk to each other. So, for example, they'd both use the first 2048 bytes of RTC slow memory. Then it's up to the user to decide how much is used by each one. This can be done quite easily by forcing rtc_user_mem_data to be located at 0x50000000. But this can be done later. For now, 2040 is a good enough value for the RESERVE_MEM (but it may change in the future).

#define CONFIG_PHY_DATA_OFFSET 0xf000
#define CONFIG_APP_OFFSET 0x10000

Expand Down