Open GregoryGost opened 4 years ago
Is it possible to program ULP co-processor with esphome somehow? It seems like a good thing for battery powered devices.
It would be especially interesting if new ULP-RISC-V co-processor in ESP32-S2 could be used for some tasks in esphome.
Hello. I could be probably best just to compile program for ULP in Arduino IDE and have a way how to upload it to ESPHome RTC memory. It is rather independent code running on independent CPU, so using Arduino IDE one could have his own code running and if needed, then start into full-blown ESPHome system.
Maybe generic custom component can be used to upload ULP code into ESPHome RTC memory?
it seems you can... https://www.esp32.com/viewtopic.php?t=4899#p21215
I'll bump this issue since I end up here when searching for ULP stuff with ESPHome.
I just set up an external component that basically injects the ULP code linked in the previous reply and it works on a NodeMCU-32S, so interacting with the ULP from an ESPHome context is definitely possible! It ended up being rather straight-forward in fact.
based off @Buzzzsaw's suggestion this example sort of works: (blinks the onboard LED using ULP processor)
file: custom_compoents/ulptest.h
/*
* ESP32 Sketch shows that you can program the ulp co-processor using
* Arduino using the ulp assembly MACRO's. This sketch just blinks the
* on board LED on for x microseconds and OFF x microseconds using the ulp.
* This sketch was inspired from https://github.com/espressif/esp-idf/blob/98e5c475b31ce704e08bd1a48f5d2de3f0b216b0/components/ulp/test/test_ulp.c
* and https://www.esp32.com/viewtopic.php?t=4899#p21215
*/
#include "esp32/ulp.h"
#include "driver/rtc_io.h"
#include "esphome.h"
void ULP_BLINK_RUN(uint32_t us);
class ulptest : public Component {
public:
void setup() override {
// microseconds to delay between halt and wake states
ESP_LOGD("custom", "begin custom ulp component setup");
ULP_BLINK_RUN(1000000); //1 sec
}
void loop() override {
// put your main code here, to run repeatedly:
}
// ------------------------------------------------------------------------
void ULP_BLINK_RUN(uint32_t us) {
RTC_SLOW_MEM[12] = 0;
ulp_set_wakeup_period(0, us);
const ulp_insn_t ulp_blink[] = {
I_MOVI(R3, 12), // #12 -> R3
I_LD(R0, R3, 0), // R0 = RTC_SLOW_MEM[R3(#12)]
M_BL(1, 1), // GOTO M_LABEL(1) IF R0 < 1
I_WR_REG(RTC_GPIO_OUT_REG, 26, 27, 1), // RTC_GPIO12 = 1
I_SUBI(R0, R0, 1), // R0 = R0 - 1, R0 = 1, R0 = 0
I_ST(R0, R3, 0), // RTC_SLOW_MEM[R3(#12)] = R0
M_BX(2), // GOTO M_LABEL(2)
M_LABEL(1), // M_LABEL(1)
I_WR_REG(RTC_GPIO_OUT_REG, 26, 27, 0),// RTC_GPIO12 = 0
I_ADDI(R0, R0, 1), // R0 = R0 + 1, R0 = 0, R0 = 1
I_ST(R0, R3, 0), // RTC_SLOW_MEM[R3(#12)] = R0
M_LABEL(2), // M_LABEL(2)
I_HALT() // HALT COPROCESSOR
};
const gpio_num_t led_gpios[] = {
GPIO_NUM_2,
GPIO_NUM_0,
GPIO_NUM_4
};
for (size_t i = 0; i < sizeof(led_gpios) / sizeof(led_gpios[0]); ++i) {
rtc_gpio_init(led_gpios[i]);
rtc_gpio_set_direction(led_gpios[i], RTC_GPIO_MODE_OUTPUT_ONLY);
rtc_gpio_set_level(led_gpios[i], 0);
}
size_t size = sizeof(ulp_blink) / sizeof(ulp_insn_t);
ulp_process_macros_and_load(0, ulp_blink, &size);
ulp_run(0);
}
};
file: ulptest.yaml
esphome:
name: ulptest
includes:
- custom_components/ulptest.h
esp32:
board: esp32dev
framework:
type: arduino
# Enable logging
logger:
level: debug
# Enable Home Assistant API
api:
encryption:
key: !secret api_key
ota:
password: !secret ota_password
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Ulp-Test Fallback Hotspot"
password: !secret fallback_password
captive_portal:
custom_component:
- lambda: |-
auto my_custom = new ulptest();
App.register_component(my_custom);
return {0,0}; //{my_custom}; //warning if we dont return anything
deep_sleep: #use deep sleep to prove that ulp runs even when in deep sleep
run_duration: 10s
sleep_duration: 10s
one thing I'm not sure why: the light flashes much more dimly and for a much shorter time when in deep sleep
Further thoughts welcome
The challenge I'm facing now is having assembly code that actually does something useful and can interact with component code.
The above example uses assembly code macros defined by (I believe?) the espressif library, but these are said to be legacy so in good conscience I'm trying to use the preferred way. Most of the examples I found on their repository use a .S
file for assembly code that's part of the compile chain, but I have not found a way to easily integrate this in ESPHome.
I'm definitely open to suggestions for the .S
files approach, but I think I will go next with implementing the pulse count example with the macros (we know these are working).
Hi @Buzzzsaw, could you produce anything useful? I am also lost with the above example... I also only have the .S files from the pulse counter example wich detects the pulses and raises a counter. If the counter is more than 10 pulses it wakes up the esp from deepsleep... thats sounds really nice, but I also dont know how to use esphome with that code.
I have also already successfully compiled it in vs.code with the ESP-IDF extension... hope to hear something from you....
Tasmota converts that compiled code to a base64 encoded text and loads thats like:
python.exe binS2Berry.py
Parsing /build folder ...
build\ulp_main.bin.S build\ulp_example.map
Parsing asm file ...
Parsing map file ...
### Global vars (including function labels):
# changed = 0x50000054 -> ULP.get_mem( 21 )
# debounce_counter = 0x500000d8 -> ULP.get_mem( 54 )
# debounce_max_count = 0x500000dc -> ULP.get_mem( 55 )
# edge_count = 0x500000e0 -> ULP.get_mem( 56 )
# edge_count_to_wake_up = 0x500000e4 -> ULP.get_mem( 57 )
# edge_detected = 0x50000070 -> ULP.get_mem( 28 )
# !!! Make sure, that the next line is a jump to the global entry point or the entry point itself !!!
# entry = 0x50000000 -> ULP.get_mem( 0 )
# io_number = 0x500000e8 -> ULP.get_mem( 58 )
# next_edge = 0x500000d4 -> ULP.get_mem( 53 )
# wake_up = 0x500000b8 -> ULP.get_mem( 46 )
# ULP-Coprocessor-type: FSM
# You can paste the following snippet into Tasmotas Berry console:
import ULP
ULP.wake_period(0,1000 * 1000)
var c = bytes().fromb64("dWxwAAwA1AAAABgAowOAcg8AANA8AIBwEAAJggkBuC4wAMBwKAAAgAkB+C8PASByMADAcBAAQHJTA4ByDwAA0DMAAHAfAEByVABAgHMDgHJiA4ByDwAA0AsAAGgAAACwYwOAcg4AANAKAABycABAgBoAIHIOAABoAAAAsHMDgHJiA4ByDwAA0AsAAGhTA4ByDgAA0BoAAHIaAEByDgAAaIMDgHIOAADQGgAAcg4AAGiTA4ByDwAA0C8AIHC4AECAAAAAsDAA7C0BAIBwMADMKRAAYHC4AECAAQAAkAAAALA=")
# Length in bytes: 224
ULP.load(c)
ULP.run()
Describe the problem you have/What new integration you would like
Possibility to set the counter of impulses on the coprocessor. And the main core of ESP32 is sleeping.
Please describe your use case for this integration and alternatives you've tried:
Pulse counter for metering water consumption. Less battery consumption!
Additional context
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/ulp.html https://github.com/tanakamasayuki/UlpDebug Real Project Water Counter: https://github.com/OloloevReal/Waterius32/tree/master/src