esphome / feature-requests

ESPHome Feature Request Tracker
https://esphome.io/
419 stars 26 forks source link

ESP32 ULP Coprocessor #826

Open GregoryGost opened 4 years ago

GregoryGost commented 4 years ago

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

timotoots commented 3 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.

jbrepogmailcom commented 3 years ago

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.

rufik commented 3 years ago

Maybe generic custom component can be used to upload ULP code into ESPHome RTC memory?

burlizzi commented 2 years ago

it seems you can... https://www.esp32.com/viewtopic.php?t=4899#p21215

Buzzzsaw commented 1 year ago

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.

alexd321 commented 1 year ago

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

Buzzzsaw commented 1 year ago

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).

ThomasCr commented 7 months ago

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....

ThomasCr commented 7 months ago

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()

https://tasmota.github.io/docs/ULP/