├── .gitignore ├── CMakeLists.txt ├── Kconfig ├── LICENSE ├── README.md ├── component.mk ├── examples ├── ADC │ └── basic │ │ ├── CMakeLists.txt │ │ ├── basic.ino │ │ ├── main │ │ ├── CMakeLists.txt │ │ └── main.cpp │ │ └── sdkconfig.defaults ├── APA │ ├── Multi │ │ ├── CMakeLists.txt │ │ ├── main │ │ │ ├── CMakeLists.txt │ │ │ └── main.cpp │ │ └── sdkconfig.defaults │ └── Single │ │ ├── CMakeLists.txt │ │ ├── main │ │ ├── CMakeLists.txt │ │ └── main.cpp │ │ └── sdkconfig.defaults ├── Buttons │ ├── CMakeLists.txt │ ├── main │ │ ├── CMakeLists.txt │ │ ├── main.c │ │ ├── ulp_buttons.c │ │ └── ulp_buttons.h │ └── sdkconfig.defaults ├── Debugging │ ├── Modify │ │ ├── CMakeLists.txt │ │ ├── main │ │ │ ├── CMakeLists.txt │ │ │ └── main.cpp │ │ └── sdkconfig.defaults │ └── Simple │ │ ├── CMakeLists.txt │ │ ├── main │ │ ├── CMakeLists.txt │ │ └── main.cpp │ │ └── sdkconfig.defaults ├── GPIO_Wakeup │ ├── CMakeLists.txt │ ├── main │ │ ├── CMakeLists.txt │ │ └── main.cpp │ └── sdkconfig.defaults ├── HX711 │ └── calibrate_and_wake │ │ ├── CMakeLists.txt │ │ ├── main │ │ ├── CMakeLists.txt │ │ └── main.c │ │ └── sdkconfig.defaults ├── HallEffect │ └── ThresholdWake │ │ ├── CMakeLists.txt │ │ ├── main │ │ ├── CMakeLists.txt │ │ └── main.cpp │ │ └── sdkconfig.defaults ├── I2C │ ├── Bitbang │ │ ├── Cmd │ │ │ └── SHT3X │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── README.md │ │ │ │ ├── main │ │ │ │ ├── CMakeLists.txt │ │ │ │ └── main.cpp │ │ │ │ └── sdkconfig.defaults │ │ ├── MultiRead │ │ │ ├── CMakeLists.txt │ │ │ ├── main │ │ │ │ ├── CMakeLists.txt │ │ │ │ └── main.cpp │ │ │ └── sdkconfig.defaults │ │ └── Single │ │ │ ├── CMakeLists.txt │ │ │ ├── main │ │ │ ├── CMakeLists.txt │ │ │ └── main.cpp │ │ │ └── sdkconfig.defaults │ └── Hardware │ │ └── Read │ │ ├── CMakeLists.txt │ │ ├── main │ │ ├── CMakeLists.txt │ │ └── main.cpp │ │ └── sdkconfig.defaults ├── Interrupt │ ├── CMakeLists.txt │ ├── main │ │ ├── CMakeLists.txt │ │ └── main.cpp │ └── sdkconfig.defaults ├── Reg_Write │ └── Demo │ │ ├── CMakeLists.txt │ │ ├── main │ │ ├── CMakeLists.txt │ │ └── main.c │ │ └── sdkconfig.defaults ├── Temperature │ ├── CMakeLists.txt │ ├── main │ │ ├── CMakeLists.txt │ │ └── main.cpp │ └── sdkconfig.defaults ├── Timing │ └── LEDToggle │ │ ├── CMakeLists.txt │ │ ├── main │ │ ├── CMakeLists.txt │ │ └── main.cpp │ │ └── sdkconfig.defaults ├── Touch │ └── ThresholdWake │ │ ├── CMakeLists.txt │ │ ├── main │ │ ├── CMakeLists.txt │ │ └── main.cpp │ │ └── sdkconfig.defaults └── UART │ ├── Greeting │ ├── CMakeLists.txt │ ├── main │ │ ├── CMakeLists.txt │ │ └── main.cpp │ └── sdkconfig.defaults │ └── Printf_Touch │ ├── CMakeLists.txt │ ├── main │ ├── CMakeLists.txt │ └── main.cpp │ └── sdkconfig.defaults ├── keywords.txt ├── library.properties └── src ├── hulp.c ├── hulp.h ├── hulp_apa.h ├── hulp_arduino.h ├── hulp_compat.h ├── hulp_config.h ├── hulp_debug.c ├── hulp_debug.h ├── hulp_hall.h ├── hulp_hx711.h ├── hulp_i2cbb.h ├── hulp_macro_fix.h ├── hulp_macro_opt.h ├── hulp_macros.h ├── hulp_mutex.h ├── hulp_regwr.c ├── hulp_regwr.h ├── hulp_touch.c ├── hulp_touch.h ├── hulp_types.h ├── hulp_uart.c └── hulp_uart.h /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .vscode/ 3 | sdkconfig.old 4 | sdkconfig 5 | /.idea 6 | /cmake-build-* 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(include_dirs 2 | "src" 3 | ) 4 | 5 | set(srcs 6 | "src/hulp.c" 7 | "src/hulp_touch.c" 8 | "src/hulp_uart.c" 9 | "src/hulp_regwr.c" 10 | "src/hulp_debug.c" 11 | ) 12 | 13 | set(requires 14 | ulp 15 | driver 16 | soc 17 | hal 18 | ) 19 | 20 | idf_component_register( 21 | INCLUDE_DIRS "${include_dirs}" 22 | SRCS "${srcs}" 23 | REQUIRES "${requires}" 24 | ) 25 | -------------------------------------------------------------------------------- /Kconfig: -------------------------------------------------------------------------------- 1 | menu "HULP" 2 | 3 | config HULP_LABEL_AUTO_BASE 4 | int "Auto Label Base" 5 | default 60000 6 | range 0 65535 7 | help 8 | Starting number for automatic labels. If using HULP_LBLA(), label numbers between this number and 65535 are reserved and should not be used. 9 | 10 | config HULP_USE_APPROX_FAST_CLK 11 | bool "Use Approximate Fast Clock Frequency" 12 | default n 13 | help 14 | In calculating some timings, use hardcoded approximate fast clock frequency instead of querying accurate frequency at runtime. 15 | 16 | config HULP_FAST_CLK_CAL_CYCLES 17 | int "Fast Clock Frequency Cal Cycles" 18 | depends on !HULP_USE_APPROX_FAST_CLK 19 | default 100 20 | range 50 1000 21 | help 22 | Slow clock cycles to use to determine fast clock frequency. 23 | 24 | config HULP_UART_TX_OD 25 | bool "Open drain UART TX output" 26 | default n 27 | help 28 | UART transmit will use push-pull configuration by default. Enable this to use open drain instead. 29 | External pullups may be required for higher baud rates. 30 | 31 | config HULP_MACRO_OPTIMISATIONS 32 | bool "Enable macro optimisations" 33 | default n 34 | help 35 | After verifying that the ULP program loads and runs correctly, enable this option to reduce binary size and processing overhead 36 | by omitting some checks and allowing further constant folding by the compiler. 37 | 38 | endmenu 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Matt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HULP 2 | 3 | HULP is a helper library for the ESP32's Ultra Low Power Co-Processor (ULP). 4 | 5 | *** 6 | 7 | ## Features 8 | 9 | ### Memory Access 10 | 11 | Easily access variables to store and retrieve data, or share between the ULP and SoC 12 | ``` 13 | RTC_SLOW_ATTR ulp_var_t my_ulp_variable; 14 | 15 | // ULP: 16 | I_MOVI(R2, 0), 17 | I_GET(R1, R2, my_ulp_variable), // Load my_ulp_variable into R1 18 | I_PUT(R3, R2, my_ulp_variable), // Store R3 into my_ulp_variable 19 | 20 | // SoC: 21 | uint16_t temp = my_ulp_variable.val; 22 | my_ulp_variable.val = 123; 23 | ``` 24 | ### GPIO 25 | 26 | Functions to configure GPIOs in preparation for the ULP 27 | ``` 28 | hulp_configure_pin(GPIO_NUM_25, RTC_GPIO_MODE_INPUT_ONLY, GPIO_PULLUP_ONLY, 0); 29 | hulp_configure_pin(GPIO_NUM_26, RTC_GPIO_MODE_OUTPUT_ONLY, GPIO_FLOATING, 1); 30 | hulp_configure_analog_pin(GPIO_NUM_33, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12); 31 | ``` 32 | Countless macros to simplify ULP GPIO operations 33 | ``` 34 | I_GPIO_READ(GPIO_NUM_27), // Digital read 35 | I_GPIO_SET(GPIO_NUM_25, 1), // Set digital output level high 36 | I_ANALOG_READ(R0, GPIO_NUM_33), // Analog read 37 | I_GPIO_PULLUP(GPIO_NUM_26, 1), // Enable internal pullup 38 | ``` 39 | As well as advanced functionality, including interrupts 40 | ``` 41 | // Configure edge interrupt: 42 | hulp_configure_pin_int(GPIO_NUM_32, GPIO_INTR_ANYEDGE); 43 | // ULP: 44 | I_GPIO_INT_RD(GPIO_NUM_32), // Check if interrupt has triggered 45 | I_GPIO_INT_CLR(GPIO_NUM_32), // Clear interrupt 46 | ``` 47 | ### Timing 48 | 49 | Macros for basic delays 50 | ``` 51 | M_DELAY_US_10_100(72), // Delay 72uS 52 | M_DELAY_MS_20_1000(500), // Delay 500mS 53 | ``` 54 | HULP also introduces a concept that makes use of the RTC slow clock for complex, asynchronous timing operations, useful for very power efficient operation and multitasking without the need for blocking delays. See `Timing` example. 55 | 56 | ### Peripherals 57 | 58 | Helpers and drivers for internal peripherals as well as communication protocols, including: 59 | 60 | * Capacitive Touch 61 | 62 | * Hall Effect 63 | 64 | * Temperature 65 | 66 | * Hardware I2C 67 | 68 | * Software (bitbanged) I2C 69 | 70 | * APA RGB LED 71 | 72 | * UART 73 | 74 | * HX711 75 | 76 | 77 | ### Debugging 78 | 79 | * Insert breakpoints, track and manipulate ULP program flow, inspect or alter register contents via the SoC 80 | 81 | * Combine the UART driver with included ULP PRINTF subroutines to communicate debugging information independent of the SoC (even in deep sleep!) 82 | 83 | ### And much more... 84 | 85 | Check out the examples for some programs demonstrating the possibilities of the ULP with HULP. 86 | 87 | There's a lot more to HULP than what is mentioned above - in lieu of better documentation, please see relevant header files for more features and information. 88 | 89 | *** 90 | 91 | ## Compatibility 92 | 93 | HULP uses the C macro (legacy) programming method (https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/ulp_macros.html), however you are free to copy and convert any parts for use with the ULP binary toolchain. 94 | 95 | 96 | ESP-IDF >=4.2.0 is required, and there is partial support for Arduino-ESP32 >=2.0.0. 97 | 98 | 99 | Only ESP32 is currently supported. -------------------------------------------------------------------------------- /component.mk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boarchuz/HULP/b80e802178432d840e98d99d68cb433e6c3e0da4/component.mk -------------------------------------------------------------------------------- /examples/ADC/basic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_adc_example) -------------------------------------------------------------------------------- /examples/ADC/basic/basic.ino: -------------------------------------------------------------------------------- 1 | /* ADC Example 2 | 3 | This example will perform some basic ADC operations with the ULP, using 3 pins: 4 | PIN1: Simply read and store the value 5 | PIN2: Oversample and store the average value 6 | PIN3: Wake from deep sleep if value is below threshold 7 | */ 8 | 9 | #include "hulp_arduino.h" 10 | 11 | #define PIN_ADC_PIN1 GPIO_NUM_32 12 | #define PIN_ADC_PIN2 GPIO_NUM_33 13 | #define PIN_ADC_PIN3 GPIO_NUM_25 14 | 15 | // Pin 2 Settings 16 | // Set the log2 of the desired number of samples here. 17 | // In this example, it will be 2^3 = 8 samples. The ULP can then shift right 3 (ie. divide by 8) to find the average. 18 | // **Caution: Do not set higher than 4 (16 samples) else ULP arithmetic may overflow 19 | #define PIN2_OVERSAMPLE_SHIFT 3 20 | 21 | // Pin 3 settings 22 | // The internal pullup for ADC PIN3 is enabled in this example, so the idle value is ~4095 (using default 12 bit attenuation). 23 | // Set the threshold just a little lower, so even a small analog change will trigger a wakeup. (Try a resistor to GND, for example.) 24 | #define PIN3_WAKE_THRESHOLD (4090) 25 | 26 | #define ULP_WAKEUP_INTERVAL_MS (20) 27 | 28 | RTC_DATA_ATTR struct { 29 | ulp_var_t pin1; 30 | ulp_var_t pin2; 31 | struct { 32 | ulp_var_t debug; 33 | ulp_var_t armed; 34 | struct { 35 | ulp_var_t reason; 36 | ulp_var_t counter; 37 | } wakeup; 38 | } pin3; 39 | } ulp_vars; 40 | 41 | void ulp_init() 42 | { 43 | enum { 44 | LBL_PIN2_OVERSAMPLE_LOOP, 45 | LBL_PIN3_WAKEUP_TRIGGERED, 46 | LBL_HALT, 47 | }; 48 | 49 | const ulp_insn_t program[] = { 50 | // Set a register to 0 for use with I_GET and I_PUT 51 | I_MOVI(R3, 0), 52 | 53 | // #1 : Read and store 54 | I_ANALOG_READ(R1, PIN_ADC_PIN1), 55 | I_PUT(R1, R3, ulp_vars.pin1), 56 | 57 | // #2 : Oversample 58 | I_STAGE_RST(), 59 | I_MOVI(R0, 0), 60 | M_LABEL(LBL_PIN2_OVERSAMPLE_LOOP), 61 | I_ANALOG_READ(R1, PIN_ADC_PIN2), 62 | I_ADDR(R0, R0, R1), 63 | I_STAGE_INC(1), 64 | M_BSLT(LBL_PIN2_OVERSAMPLE_LOOP, (1 << PIN2_OVERSAMPLE_SHIFT)), 65 | I_RSHI(R0, R0, PIN2_OVERSAMPLE_SHIFT), 66 | I_PUT(R0, R3, ulp_vars.pin2), 67 | 68 | // #3 : Wake if below threshold 69 | I_ANALOG_READ(R1, PIN_ADC_PIN3), 70 | I_PUT(R1, R3, ulp_vars.pin3.debug), 71 | // Skip if the trigger isn't 'armed'. SoC enables this before going to sleep (see below). 72 | I_GET(R0, R3, ulp_vars.pin3.armed), 73 | M_BL(LBL_HALT, 1), 74 | // Subtract the threshold. If it overflows, the value must be < threshold so process the event. 75 | I_SUBI(R0, R1, PIN3_WAKE_THRESHOLD), 76 | M_BXF(LBL_PIN3_WAKEUP_TRIGGERED), 77 | M_BX(LBL_HALT), 78 | M_LABEL(LBL_PIN3_WAKEUP_TRIGGERED), 79 | // Store the value that caused the wakeup so SoC can inspect when it's ready. 80 | I_PUT(R1, R3, ulp_vars.pin3.wakeup.reason), 81 | // Increment the counter. 82 | I_GET(R0, R3, ulp_vars.pin3.wakeup.counter), 83 | I_ADDI(R0, R0, 1), 84 | I_PUT(R0, R3, ulp_vars.pin3.wakeup.counter), 85 | // Clear 'armed'. SoC can set it again when it goes to sleep. 86 | I_PUT(R3, R3, ulp_vars.pin3.armed), 87 | M_WAKE_WHEN_READY(), 88 | 89 | M_LABEL(LBL_HALT), 90 | I_HALT(), 91 | }; 92 | 93 | ESP_ERROR_CHECK(hulp_configure_analog_pin(PIN_ADC_PIN1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12)); 94 | ESP_ERROR_CHECK(hulp_configure_analog_pin(PIN_ADC_PIN2, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12)); 95 | ESP_ERROR_CHECK(hulp_configure_analog_pin(PIN_ADC_PIN3, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12)); 96 | ESP_ERROR_CHECK(rtc_gpio_pullup_en(PIN_ADC_PIN3)); 97 | 98 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), 1000UL * ULP_WAKEUP_INTERVAL_MS, 0)); 99 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 100 | } 101 | 102 | void setup() 103 | { 104 | Serial.begin(115200); 105 | if(hulp_is_deep_sleep_wakeup()) 106 | { 107 | Serial.print("Woken up! PIN3: Wakeup value: ");Serial.print(ulp_vars.pin3.wakeup.reason.val);Serial.print(", counter: ");Serial.println(ulp_vars.pin3.wakeup.counter.val); 108 | } 109 | else 110 | { 111 | ulp_init(); 112 | } 113 | 114 | // Print some values for a few seconds 115 | for(int i = 0; i < 5; ++i) 116 | { 117 | Serial.print("PIN1: Value: ");Serial.println(ulp_vars.pin1.val); 118 | Serial.print("PIN2: Value: ");Serial.println(ulp_vars.pin2.val); 119 | Serial.print("PIN3: Value: ");Serial.println(ulp_vars.pin3.debug.val); 120 | delay(1000); 121 | } 122 | 123 | // Don't go to sleep if PIN3 is still in a triggered state 124 | Serial.println("Waiting for PIN 3 idle..."); 125 | while(ulp_vars.pin3.debug.val < PIN3_WAKE_THRESHOLD) 126 | { 127 | delay(100); 128 | } 129 | 130 | // Tell ULP that PIN3 is now armed, and sleep 131 | Serial.println("Sleeping..."); 132 | ulp_vars.pin3.armed.val = 1; 133 | esp_sleep_enable_ulp_wakeup(); 134 | esp_deep_sleep_start(); 135 | } 136 | 137 | void loop(){} -------------------------------------------------------------------------------- /examples/ADC/basic/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/ADC/basic/main/main.cpp: -------------------------------------------------------------------------------- 1 | /* ADC Example 2 | 3 | This example will perform some basic ADC operations with the ULP, using 3 pins: 4 | PIN1: Simply read and store the value 5 | PIN2: Oversample and store the average value 6 | PIN3: Wake from deep sleep if value is below threshold 7 | */ 8 | 9 | #include "freertos/FreeRTOS.h" 10 | #include "freertos/task.h" 11 | #include "esp_log.h" 12 | #include "esp_sleep.h" 13 | 14 | #include "hulp.h" 15 | 16 | static const char *TAG = "HULP_ADC"; 17 | 18 | #define PIN_ADC_PIN1 GPIO_NUM_32 19 | #define PIN_ADC_PIN2 GPIO_NUM_33 20 | #define PIN_ADC_PIN3 GPIO_NUM_25 21 | 22 | // Pin 2 Settings 23 | // Set the log2 of the desired number of samples here. 24 | // In this example, it will be 2^3 = 8 samples. The ULP can then shift right 3 (ie. divide by 8) to find the average. 25 | // **Caution: Do not set higher than 4 (16 samples) else ULP arithmetic may overflow 26 | #define PIN2_OVERSAMPLE_SHIFT 3 27 | 28 | // Pin 3 settings 29 | // The internal pullup for ADC PIN3 is enabled in this example, so the idle value is ~4095 (using default 12 bit attenuation). 30 | // Set the threshold just a little lower, so even a small analog change will trigger a wakeup. (Try a resistor to GND, for example.) 31 | #define PIN3_WAKE_THRESHOLD (4090) 32 | 33 | #define ULP_WAKEUP_INTERVAL_MS (20) 34 | 35 | RTC_DATA_ATTR struct { 36 | ulp_var_t pin1; 37 | ulp_var_t pin2; 38 | struct { 39 | ulp_var_t debug; 40 | ulp_var_t armed; 41 | struct { 42 | ulp_var_t reason; 43 | ulp_var_t counter; 44 | } wakeup; 45 | } pin3; 46 | } ulp_vars; 47 | 48 | void ulp_init() 49 | { 50 | enum { 51 | LBL_PIN2_OVERSAMPLE_LOOP, 52 | LBL_PIN3_WAKEUP_TRIGGERED, 53 | LBL_HALT, 54 | }; 55 | 56 | const ulp_insn_t program[] = { 57 | // Set a register to 0 for use with I_GET and I_PUT 58 | I_MOVI(R3, 0), 59 | 60 | // #1 : Read and store 61 | I_ANALOG_READ(R1, PIN_ADC_PIN1), 62 | I_PUT(R1, R3, ulp_vars.pin1), 63 | 64 | // #2 : Oversample 65 | I_STAGE_RST(), 66 | I_MOVI(R0, 0), 67 | M_LABEL(LBL_PIN2_OVERSAMPLE_LOOP), 68 | I_ANALOG_READ(R1, PIN_ADC_PIN2), 69 | I_ADDR(R0, R0, R1), 70 | I_STAGE_INC(1), 71 | M_BSLT(LBL_PIN2_OVERSAMPLE_LOOP, (1 << PIN2_OVERSAMPLE_SHIFT)), 72 | I_RSHI(R0, R0, PIN2_OVERSAMPLE_SHIFT), 73 | I_PUT(R0, R3, ulp_vars.pin2), 74 | 75 | // #3 : Wake if below threshold 76 | I_ANALOG_READ(R1, PIN_ADC_PIN3), 77 | I_PUT(R1, R3, ulp_vars.pin3.debug), 78 | // Skip if the trigger isn't 'armed'. SoC enables this before going to sleep (see below). 79 | I_GET(R0, R3, ulp_vars.pin3.armed), 80 | M_BL(LBL_HALT, 1), 81 | // Subtract the threshold. If it overflows, the value must be < threshold so process the event. 82 | I_SUBI(R0, R1, PIN3_WAKE_THRESHOLD), 83 | M_BXF(LBL_PIN3_WAKEUP_TRIGGERED), 84 | M_BX(LBL_HALT), 85 | M_LABEL(LBL_PIN3_WAKEUP_TRIGGERED), 86 | // Store the value that caused the wakeup so SoC can inspect when it's ready. 87 | I_PUT(R1, R3, ulp_vars.pin3.wakeup.reason), 88 | // Increment the counter. 89 | I_GET(R0, R3, ulp_vars.pin3.wakeup.counter), 90 | I_ADDI(R0, R0, 1), 91 | I_PUT(R0, R3, ulp_vars.pin3.wakeup.counter), 92 | // Clear 'armed'. SoC can set it again when it goes to sleep. 93 | I_PUT(R3, R3, ulp_vars.pin3.armed), 94 | M_WAKE_WHEN_READY(), 95 | 96 | M_LABEL(LBL_HALT), 97 | I_HALT(), 98 | }; 99 | 100 | ESP_ERROR_CHECK(hulp_configure_analog_pin(PIN_ADC_PIN1, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12)); 101 | ESP_ERROR_CHECK(hulp_configure_analog_pin(PIN_ADC_PIN2, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12)); 102 | ESP_ERROR_CHECK(hulp_configure_analog_pin(PIN_ADC_PIN3, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12)); 103 | ESP_ERROR_CHECK(rtc_gpio_pullup_en(PIN_ADC_PIN3)); 104 | 105 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), 1000UL * ULP_WAKEUP_INTERVAL_MS, 0)); 106 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 107 | } 108 | 109 | extern "C" void app_main(void) 110 | { 111 | if(hulp_is_deep_sleep_wakeup()) 112 | { 113 | ESP_LOGI(TAG, "Woken up! Wakeup value: %u, counter: %u", ulp_vars.pin3.wakeup.reason.val, ulp_vars.pin3.wakeup.counter.val); 114 | } 115 | else 116 | { 117 | ulp_init(); 118 | } 119 | 120 | // Print some values for a few seconds 121 | for(int i = 0; i < 5; ++i) 122 | { 123 | ESP_LOGI(TAG, "GPIO %d: %5u", PIN_ADC_PIN1, ulp_vars.pin1.val); 124 | ESP_LOGI(TAG, "GPIO %d: %5u", PIN_ADC_PIN2, ulp_vars.pin2.val); 125 | ESP_LOGI(TAG, "GPIO %d: %5u", PIN_ADC_PIN3, ulp_vars.pin3.debug.val); 126 | vTaskDelay(1000 / portTICK_PERIOD_MS); 127 | } 128 | 129 | if(ulp_vars.pin3.debug.val < PIN3_WAKE_THRESHOLD) 130 | { 131 | // Don't go to sleep if PIN3 is still in a triggered state 132 | ESP_LOGI(TAG, "Waiting for GPIO %d idle...", PIN_ADC_PIN3); 133 | while(ulp_vars.pin3.debug.val < PIN3_WAKE_THRESHOLD) 134 | { 135 | vTaskDelay(100 / portTICK_PERIOD_MS); 136 | } 137 | } 138 | 139 | // Tell ULP that PIN3 is now armed, and sleep 140 | ESP_LOGI(TAG, "Sleeping..."); 141 | ulp_vars.pin3.armed.val = 1; 142 | esp_sleep_enable_ulp_wakeup(); 143 | esp_deep_sleep_start(); 144 | } 145 | -------------------------------------------------------------------------------- /examples/ADC/basic/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 -------------------------------------------------------------------------------- /examples/APA/Multi/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_apa_multi) -------------------------------------------------------------------------------- /examples/APA/Multi/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/APA/Multi/main/main.cpp: -------------------------------------------------------------------------------- 1 | #include "freertos/FreeRTOS.h" 2 | #include "freertos/task.h" 3 | #include "driver/rtc_io.h" 4 | #include "driver/gpio.h" 5 | #include "soc/rtc.h" 6 | #include "esp_sleep.h" 7 | 8 | #include "hulp.h" 9 | #include "hulp_apa.h" 10 | 11 | #define SCL_PIN GPIO_NUM_14 12 | #define SDA_PIN GPIO_NUM_13 13 | #define NUM_LEDS 5 14 | 15 | //The ULP will set the first LED to these values: 16 | #define ULP_SET_BRIGHTNESS 10 17 | #define ULP_SET_BLUE 0 18 | #define ULP_SET_RED 255 19 | #define ULP_SET_GREEN 255 20 | 21 | //Declare the array of ulp_apa_t in RTC Slow Memory 22 | RTC_DATA_ATTR ulp_apa_t leds[NUM_LEDS]; 23 | 24 | void init_ulp() 25 | { 26 | enum { 27 | LBL_APA_TX_RETURN, 28 | LBL_APA_ENTRY, 29 | }; 30 | 31 | const ulp_insn_t program[] = { 32 | I_MOVI(R1, 0), 33 | //Brightness and blue go in MSB 34 | I_MOVI(R0, (ULP_SET_BRIGHTNESS << 8 | ULP_SET_BLUE)), 35 | I_PUT(R0, R1, leds[0].msb), 36 | //Green and red go in LSB 37 | I_MOVI(R0, (ULP_SET_GREEN << 8 | ULP_SET_RED)), 38 | I_PUT(R0, R1, leds[0].lsb), 39 | 40 | //Ready to transmit, prepare R3 with the return address. 41 | M_MOVL(R3, LBL_APA_TX_RETURN), 42 | //Then branch to the APA TX subroutine entry 43 | M_BX(LBL_APA_ENTRY), 44 | //Returns here 45 | M_LABEL(LBL_APA_TX_RETURN), 46 | 47 | //Notify SoC that the TX is done, and sleep 48 | I_WAKE(), 49 | I_HALT(), 50 | 51 | //Subroutine: 52 | M_APA_TX(LBL_APA_ENTRY, SCL_PIN, SDA_PIN, leds, NUM_LEDS, R1, R3), 53 | }; 54 | 55 | ESP_ERROR_CHECK(hulp_configure_pin(SCL_PIN, RTC_GPIO_MODE_OUTPUT_ONLY, GPIO_FLOATING, 0)); 56 | ESP_ERROR_CHECK(hulp_configure_pin(SDA_PIN, RTC_GPIO_MODE_OUTPUT_ONLY, GPIO_FLOATING, 0)); 57 | 58 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), 250 * 1000, 0)); 59 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 60 | } 61 | 62 | void ulp_isr(void *task_handle_ptr) 63 | { 64 | xTaskNotifyFromISR(*(TaskHandle_t*)task_handle_ptr, 0, eNoAction, NULL); 65 | } 66 | 67 | extern "C" void app_main() 68 | { 69 | //ULP will trigger an interrupt when it finishes transmitting. Randomise the leds each time. 70 | TaskHandle_t main_handle = xTaskGetCurrentTaskHandle(); 71 | hulp_ulp_isr_register(&ulp_isr, &main_handle); 72 | hulp_ulp_interrupt_en(); 73 | 74 | init_ulp(); 75 | 76 | for(;;) 77 | { 78 | xTaskNotifyWait(0, 0, NULL, portMAX_DELAY); 79 | esp_fill_random(leds, sizeof(leds)); 80 | } 81 | } -------------------------------------------------------------------------------- /examples/APA/Multi/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 -------------------------------------------------------------------------------- /examples/APA/Single/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_apa_single) -------------------------------------------------------------------------------- /examples/APA/Single/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/APA/Single/main/main.cpp: -------------------------------------------------------------------------------- 1 | #include "freertos/FreeRTOS.h" 2 | #include "freertos/task.h" 3 | #include "driver/rtc_io.h" 4 | #include "driver/gpio.h" 5 | #include "soc/rtc.h" 6 | #include "esp_sleep.h" 7 | 8 | #include "hulp.h" 9 | #include "hulp_apa.h" 10 | 11 | #define SCL_PIN GPIO_NUM_14 12 | #define SDA_PIN GPIO_NUM_13 13 | 14 | //Brightness is 5 bit (0-31) 15 | #define BRIGHTNESS 5 16 | 17 | void init_ulp() 18 | { 19 | enum { 20 | LBL_RED_INTERVAL, 21 | LBL_RED_RETURN, 22 | LBL_BLUE_INTERVAL, 23 | LBL_BLUE_RETURN, 24 | LBL_GREEN_INTERVAL, 25 | LBL_GREEN_RETURN, 26 | LBL_YELLOW_INTERVAL, 27 | LBL_YELLOW_RETURN, 28 | LBL_WHITE_INTERVAL, 29 | LBL_WHITE_RETURN, 30 | 31 | LBL_HALT, 32 | 33 | LBL_APA_ENTRY, 34 | }; 35 | 36 | const ulp_insn_t program[] = { 37 | M_UPDATE_TICKS(), 38 | 39 | M_IF_MS_ELAPSED(LBL_RED_INTERVAL, 1000, LBL_GREEN_INTERVAL), 40 | M_APA1_SET(LBL_RED_RETURN, LBL_APA_ENTRY, BRIGHTNESS, 255, 0, 0), 41 | 42 | M_IF_MS_ELAPSED(LBL_GREEN_INTERVAL, 1050, LBL_YELLOW_INTERVAL), 43 | M_APA1_SET(LBL_GREEN_RETURN, LBL_APA_ENTRY, BRIGHTNESS, 0, 255, 0), 44 | 45 | M_IF_MS_ELAPSED(LBL_YELLOW_INTERVAL, 1100, LBL_BLUE_INTERVAL), 46 | M_APA1_SET(LBL_YELLOW_RETURN, LBL_APA_ENTRY, BRIGHTNESS, 255, 255, 0), 47 | 48 | M_IF_MS_ELAPSED(LBL_BLUE_INTERVAL, 1150, LBL_WHITE_INTERVAL), 49 | M_APA1_SET(LBL_BLUE_RETURN, LBL_APA_ENTRY, BRIGHTNESS, 0, 0, 255), 50 | 51 | M_IF_MS_ELAPSED(LBL_WHITE_INTERVAL, 1200, LBL_HALT), 52 | M_APA1_SET(LBL_WHITE_RETURN, LBL_APA_ENTRY, BRIGHTNESS, 255, 255, 255), 53 | 54 | M_LABEL(LBL_HALT), 55 | I_HALT(), 56 | 57 | M_INCLUDE_APA1(LBL_APA_ENTRY, SCL_PIN, SDA_PIN), 58 | }; 59 | 60 | ESP_ERROR_CHECK(hulp_configure_pin(SCL_PIN, RTC_GPIO_MODE_OUTPUT_ONLY, GPIO_FLOATING, 0)); 61 | ESP_ERROR_CHECK(hulp_configure_pin(SDA_PIN, RTC_GPIO_MODE_OUTPUT_ONLY, GPIO_FLOATING, 0)); 62 | 63 | hulp_peripherals_on(); 64 | 65 | vTaskDelay(1000 / portTICK_PERIOD_MS); 66 | 67 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), 1ULL * 50 * 1000, 0)); 68 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 69 | } 70 | 71 | extern "C" void app_main() 72 | { 73 | init_ulp(); 74 | esp_deep_sleep_start(); 75 | } -------------------------------------------------------------------------------- /examples/APA/Single/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 -------------------------------------------------------------------------------- /examples/Buttons/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_buttons) -------------------------------------------------------------------------------- /examples/Buttons/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.c" "ulp_buttons.c" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/Buttons/main/main.c: -------------------------------------------------------------------------------- 1 | /* HULP Buttons Example 2 | 3 | Example of using the ULP to handle more advanced button input, including multiple input events - single click, double click, and long hold - 4 | configurable interrupts, multiple buttons simultaneously, and debouncing of pin changes. 5 | 6 | Attach four buttons (or equivalent) across GND to GPIOs 0, 25, 26 and 27. 7 | Click, double click, and hold to log different events. Hold 0 to enter/exit deep sleep. 8 | */ 9 | 10 | #include "freertos/FreeRTOS.h" 11 | #include "freertos/task.h" 12 | #include "esp_log.h" 13 | #include "driver/gpio.h" 14 | #include "esp_sleep.h" 15 | 16 | #include "hulp.h" 17 | #include "ulp_buttons.h" 18 | 19 | #include "sdkconfig.h" 20 | 21 | static const char *TAG = "buttons"; 22 | 23 | // Array of RTC GPIOs with an attached button for ULP to monitor 24 | const gpio_num_t button_gpios[] = {GPIO_NUM_0, GPIO_NUM_25, GPIO_NUM_26, GPIO_NUM_27}; 25 | 26 | #define BUTTON_DEBOUNCE_MS 20 27 | #define BUTTON_DOUBLE_CLICK_TIMEOUT_MS 200 28 | #define BUTTON_HOLD_MS 5000 29 | // To ensure edge race conditions are handled, ULP will interrupt the SoC repeatedly at this interval until events are acknowledged: 30 | #define BUTTON_REINTERRUPT_MS 100 31 | #define ULP_WAKEUP_INTERVAL_US (5 * 1000) 32 | 33 | #define NUM_BUTTONS (sizeof(button_gpios)/sizeof(button_gpios[0])) 34 | 35 | // Button states (in RTC slow memory) 36 | static RTC_SLOW_ATTR ulp_button_t ulp_button_states[NUM_BUTTONS]; 37 | 38 | void ulp_isr(void *task_handle_ptr) 39 | { 40 | xTaskNotifyFromISR(*(TaskHandle_t*)task_handle_ptr, 0, eNoAction, NULL); 41 | } 42 | 43 | void app_main() 44 | { 45 | TaskHandle_t main_handle = xTaskGetCurrentTaskHandle(); 46 | ESP_ERROR_CHECK(hulp_ulp_isr_register(&ulp_isr, &main_handle)); 47 | hulp_ulp_interrupt_en(); 48 | 49 | if(!hulp_is_ulp_wakeup()) 50 | { 51 | ESP_ERROR_CHECK(ulp_buttons_init(button_gpios, ulp_button_states, NUM_BUTTONS, ULP_WAKEUP_INTERVAL_US, BUTTON_DEBOUNCE_MS, BUTTON_DOUBLE_CLICK_TIMEOUT_MS, BUTTON_HOLD_MS, BUTTON_REINTERRUPT_MS)); 52 | } 53 | else 54 | { 55 | ESP_LOGI(TAG, "Woken up! Sleep events:"); 56 | for(int i = 0; i < NUM_BUTTONS; ++i) 57 | { 58 | ESP_LOGI(TAG, "\tButton %d:", button_gpios[i]); 59 | ESP_LOGI(TAG, "\t\tClicks: %u", ulp_button_process_single_clicks(&ulp_button_states[i])); 60 | ESP_LOGI(TAG, "\t\tDouble clicks: %u", ulp_button_process_double_clicks(&ulp_button_states[i])); 61 | ESP_LOGI(TAG, "\t\tHolds: %u", ulp_button_process_holds(&ulp_button_states[i])); 62 | } 63 | } 64 | 65 | // Enable interrupts for all button events 66 | for(int i = 0; i < NUM_BUTTONS; ++i) 67 | { 68 | ulp_button_interrupt_config(&ulp_button_states[i], UINT16_MAX); 69 | } 70 | 71 | // Log events until 0 is held to go to sleep 72 | ESP_LOGI(TAG, "Hold %d to sleep.", button_gpios[0]); 73 | bool goto_sleep = false; 74 | for(;;) 75 | { 76 | xTaskNotifyWait(0, 0, NULL, portMAX_DELAY); 77 | for(int i = 0; i < NUM_BUTTONS; ++i) 78 | { 79 | if(ulp_button_process_single_clicks(&ulp_button_states[i])) 80 | { 81 | ESP_LOGI(TAG, "%d click", button_gpios[i]); 82 | } 83 | if(ulp_button_process_double_clicks(&ulp_button_states[i])) 84 | { 85 | ESP_LOGI(TAG, "%d double click", button_gpios[i]); 86 | } 87 | if(ulp_button_process_holds(&ulp_button_states[i])) 88 | { 89 | ESP_LOGI(TAG, "%d hold", button_gpios[i]); 90 | if(i == 0) 91 | { 92 | goto_sleep = true; 93 | } 94 | } 95 | } 96 | if(goto_sleep) 97 | { 98 | ESP_LOGI(TAG, "Going to sleep. Hold %d to wake up.", button_gpios[0]); 99 | // Enable only hold interrupt for 0 100 | ulp_button_interrupt_config(&ulp_button_states[0], ULP_BUTTON_INT_HOLD); 101 | // Disable all other interrupts 102 | for(int i = 1; i < NUM_BUTTONS; ++i) 103 | { 104 | ulp_button_interrupt_config(&ulp_button_states[i], 0); 105 | } 106 | esp_sleep_enable_ulp_wakeup(); 107 | esp_deep_sleep_start(); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /examples/Buttons/main/ulp_buttons.c: -------------------------------------------------------------------------------- 1 | #include "ulp_buttons.h" 2 | 3 | #include 4 | 5 | #include "hulp.h" 6 | 7 | #ifndef ARRAY_SIZE 8 | #define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) 9 | #endif 10 | 11 | static esp_err_t load_ulp_button_handler(uint32_t *load_addr, int debounce_ms, int double_click_timeout_ms, int hold_ms, int reinterrupt_ms) 12 | { 13 | // ULP Button FSM: 14 | enum { 15 | ULP_BUTTON_IDLE, // Idle, waiting for: press -> Pressed 16 | ULP_BUTTON_PRESSED, // Just pressed, debouncing -> Down 17 | ULP_BUTTON_DOWN, // Pressed down, waiting for: release -> Released /OR/ hold timer expiry -> Wait_Release 18 | ULP_BUTTON_RELEASED, // Just released, debouncing -> Up 19 | ULP_BUTTON_UP, // Click pending, waiting for: double click -> Wait_Release /OR/ double click timer expiry (ie. single click) -> Idle 20 | ULP_BUTTON_WAIT_RELEASE, // Double/hold already processed, waiting for: release -> Debounce 21 | ULP_BUTTON_DEBOUNCE, // Debouncing -> Idle 22 | }; 23 | 24 | enum { 25 | LBL_BUTTON_IDLE, 26 | LBL_BUTTON_PRESSED, 27 | LBL_BUTTON_DOWN, 28 | LBL_BUTTON_RELEASED, 29 | LBL_BUTTON_UP, 30 | LBL_BUTTON_WAIT_RELEASE, 31 | LBL_BUTTON_DEBOUNCE, 32 | 33 | LBL_BUTTON_HANDLE_RELEASE, 34 | LBL_BUTTON_HANDLE_DOUBLE_CLICK, 35 | 36 | LBL_BUTTON_CHECK_INT_INTERVAL, 37 | LBL_BUTTON_CHECK_INTS, 38 | LBL_BUTTON_CHECK_INTS_DOUBLE, 39 | LBL_BUTTON_CHECK_INTS_HOLD, 40 | LBL_BUTTON_INT, 41 | 42 | LBL_BUTTON_DONE, 43 | }; 44 | 45 | // The double click timeout and debounce timer use same timestamp, so ensure they use same range: 46 | uint8_t double_and_debounce_shift = hulp_ms_to_ulp_tick_shift(MAX(debounce_ms, double_click_timeout_ms)); 47 | uint16_t debounce_ticks = hulp_ms_to_ulp_ticks_with_shift(debounce_ms, double_and_debounce_shift); 48 | uint16_t double_click_timeout_ticks = hulp_ms_to_ulp_ticks_with_shift(double_click_timeout_ms, double_and_debounce_shift); 49 | 50 | uint8_t hold_shift = hulp_ms_to_ulp_tick_shift(hold_ms); 51 | uint16_t hold_ticks = hulp_ms_to_ulp_ticks_with_shift(hold_ms, hold_shift); 52 | 53 | uint8_t reint_shift = hulp_ms_to_ulp_tick_shift(reinterrupt_ms); 54 | uint16_t reint_ticks = hulp_ms_to_ulp_ticks_with_shift(reinterrupt_ms, reint_shift); 55 | 56 | const ulp_insn_t program[] = { 57 | // For clarity, this is deliberately not optimised 58 | 59 | // Subroutine entry 60 | // Expects current GPIO level in R0 (0/1). 61 | 62 | // Prepare ALU so that we can branch based on current pin state later. By subtracting 1, ALU overflow == pin low; ALU zero == pin high 63 | I_SUBI(R2, R0, 1), 64 | 65 | // FSM: Load current state and branch 66 | I_LD(R0, R1, offsetof(ulp_button_t, priv.state) / sizeof(ulp_var_t)), 67 | M_BL(LBL_BUTTON_IDLE, ULP_BUTTON_IDLE+1), 68 | M_BL(LBL_BUTTON_PRESSED, ULP_BUTTON_PRESSED+1), 69 | M_BL(LBL_BUTTON_DOWN, ULP_BUTTON_DOWN+1), 70 | M_BL(LBL_BUTTON_RELEASED, ULP_BUTTON_RELEASED+1), 71 | M_BL(LBL_BUTTON_UP, ULP_BUTTON_UP+1), 72 | M_BL(LBL_BUTTON_WAIT_RELEASE, ULP_BUTTON_WAIT_RELEASE+1), 73 | 74 | M_LABEL(LBL_BUTTON_DEBOUNCE), 75 | // Check debounce expiry 76 | I_RD_TICKS_REG(double_and_debounce_shift), 77 | I_LD(R2, R1, offsetof(ulp_button_t, priv.ts) / sizeof(ulp_var_t)), 78 | I_SUBR(R0, R0, R2), 79 | M_BL(LBL_BUTTON_CHECK_INT_INTERVAL, debounce_ticks), 80 | // Debounce expired, reset to idle 81 | I_MOVI(R2, ULP_BUTTON_IDLE), 82 | I_ST(R2, R1, offsetof(ulp_button_t, priv.state) / sizeof(ulp_var_t)), 83 | M_BX(LBL_BUTTON_CHECK_INT_INTERVAL), 84 | 85 | M_LABEL(LBL_BUTTON_WAIT_RELEASE), 86 | // If pin still low, do nothing 87 | M_BXF(LBL_BUTTON_CHECK_INT_INTERVAL), 88 | // Else released -> begin debounce timer 89 | I_RD_TICKS_REG(double_and_debounce_shift), 90 | I_ST(R0, R1, offsetof(ulp_button_t, priv.ts) / sizeof(ulp_var_t)), 91 | I_MOVI(R2, ULP_BUTTON_DEBOUNCE), 92 | I_ST(R2, R1, offsetof(ulp_button_t, priv.state) / sizeof(ulp_var_t)), 93 | M_BX(LBL_BUTTON_CHECK_INT_INTERVAL), 94 | 95 | M_LABEL(LBL_BUTTON_UP), 96 | // If pin is low again, process double click 97 | M_BXF(LBL_BUTTON_HANDLE_DOUBLE_CLICK), 98 | // Else check if double click timeout expired 99 | I_RD_TICKS_REG(double_and_debounce_shift), 100 | I_LD(R2, R1, offsetof(ulp_button_t, priv.ts) / sizeof(ulp_var_t)), 101 | I_SUBR(R0, R0, R2), 102 | M_BL(LBL_BUTTON_CHECK_INT_INTERVAL, double_click_timeout_ticks), 103 | // Double click timeout expired, process single click 104 | I_LD(R2, R1, offsetof(ulp_button_t, raw.single_clicks) / sizeof(ulp_var_t)), 105 | I_ADDI(R2, R2, 1), 106 | I_ST(R2, R1, offsetof(ulp_button_t, raw.single_clicks) / sizeof(ulp_var_t)), 107 | I_MOVI(R2, ULP_BUTTON_IDLE), 108 | I_ST(R2, R1, offsetof(ulp_button_t, priv.state) / sizeof(ulp_var_t)), 109 | M_BX(LBL_BUTTON_CHECK_INTS), 110 | 111 | M_LABEL(LBL_BUTTON_HANDLE_DOUBLE_CLICK), 112 | I_LD(R2, R1, offsetof(ulp_button_t, raw.double_clicks) / sizeof(ulp_var_t)), 113 | I_ADDI(R2, R2, 1), 114 | I_ST(R2, R1, offsetof(ulp_button_t, raw.double_clicks) / sizeof(ulp_var_t)), 115 | I_MOVI(R2, LBL_BUTTON_WAIT_RELEASE), 116 | I_ST(R2, R1, offsetof(ulp_button_t, priv.state) / sizeof(ulp_var_t)), 117 | M_BX(LBL_BUTTON_CHECK_INTS), 118 | 119 | M_LABEL(LBL_BUTTON_RELEASED), 120 | // Check debounce expiry 121 | I_RD_TICKS_REG(double_and_debounce_shift), 122 | I_LD(R2, R1, offsetof(ulp_button_t, priv.ts) / sizeof(ulp_var_t)), 123 | I_SUBR(R0, R0, R2), 124 | M_BL(LBL_BUTTON_CHECK_INT_INTERVAL, debounce_ticks), 125 | // Debounce expired, set to UP 126 | I_RD_TICKS_REG(double_and_debounce_shift), 127 | I_ST(R0, R1, offsetof(ulp_button_t, priv.ts) / sizeof(ulp_var_t)), 128 | I_MOVI(R2, ULP_BUTTON_UP), 129 | I_ST(R2, R1, offsetof(ulp_button_t, priv.state) / sizeof(ulp_var_t)), 130 | M_BX(LBL_BUTTON_CHECK_INT_INTERVAL), 131 | 132 | M_LABEL(LBL_BUTTON_DOWN), 133 | // If high, begin released debounce 134 | M_BXZ(LBL_BUTTON_HANDLE_RELEASE), 135 | // Else still low, check hold time 136 | I_RD_TICKS_REG(hold_shift), 137 | I_LD(R2, R1, offsetof(ulp_button_t, priv.ts) / sizeof(ulp_var_t)), 138 | I_SUBR(R0, R0, R2), 139 | M_BL(LBL_BUTTON_CHECK_INT_INTERVAL, hold_ticks), 140 | // Hold time expired, process hold 141 | I_LD(R2, R1, offsetof(ulp_button_t, raw.holds) / sizeof(ulp_var_t)), 142 | I_ADDI(R2, R2, 1), 143 | I_ST(R2, R1, offsetof(ulp_button_t, raw.holds) / sizeof(ulp_var_t)), 144 | I_MOVI(R2, LBL_BUTTON_WAIT_RELEASE), 145 | I_ST(R2, R1, offsetof(ulp_button_t, priv.state) / sizeof(ulp_var_t)), 146 | M_BX(LBL_BUTTON_CHECK_INTS), 147 | 148 | M_LABEL(LBL_BUTTON_HANDLE_RELEASE), 149 | I_RD_TICKS_REG(double_and_debounce_shift), 150 | I_ST(R0, R1, offsetof(ulp_button_t, priv.ts) / sizeof(ulp_var_t)), 151 | I_MOVI(R2, ULP_BUTTON_RELEASED), 152 | I_ST(R2, R1, offsetof(ulp_button_t, priv.state) / sizeof(ulp_var_t)), 153 | M_BX(LBL_BUTTON_CHECK_INT_INTERVAL), 154 | 155 | M_LABEL(LBL_BUTTON_PRESSED), 156 | I_RD_TICKS_REG(double_and_debounce_shift), 157 | I_LD(R2, R1, offsetof(ulp_button_t, priv.ts) / sizeof(ulp_var_t)), 158 | I_SUBR(R0, R0, R2), 159 | M_BL(LBL_BUTTON_CHECK_INT_INTERVAL, debounce_ticks), 160 | // Else debounce expired, set to UP 161 | I_RD_TICKS_REG(hold_shift), 162 | I_ST(R0, R1, offsetof(ulp_button_t, priv.ts) / sizeof(ulp_var_t)), 163 | I_MOVI(R2, ULP_BUTTON_DOWN), 164 | I_ST(R2, R1, offsetof(ulp_button_t, priv.state) / sizeof(ulp_var_t)), 165 | M_BX(LBL_BUTTON_CHECK_INT_INTERVAL), 166 | 167 | M_LABEL(LBL_BUTTON_IDLE), 168 | // If still high, do nothing 169 | M_BXZ(LBL_BUTTON_CHECK_INT_INTERVAL), 170 | // Else begin pressed debounce 171 | I_RD_TICKS_REG(double_and_debounce_shift), 172 | I_ST(R0, R1, offsetof(ulp_button_t, priv.ts) / sizeof(ulp_var_t)), 173 | I_MOVI(R2, ULP_BUTTON_PRESSED), 174 | I_ST(R2, R1, offsetof(ulp_button_t, priv.state) / sizeof(ulp_var_t)), 175 | 176 | // Check reinterrupt interval 177 | M_LABEL(LBL_BUTTON_CHECK_INT_INTERVAL), 178 | I_RD_TICKS_REG(reint_shift), 179 | I_LD(R2, R1, offsetof(ulp_button_t, priv.ts_int) / sizeof(ulp_var_t)), 180 | I_SUBR(R0, R0, R2), 181 | M_BL(LBL_BUTTON_DONE, reint_ticks), 182 | 183 | M_LABEL(LBL_BUTTON_CHECK_INTS), 184 | 185 | I_LD(R0, R1, offsetof(ulp_button_t, interrupts_en) / sizeof(ulp_var_t)), 186 | I_ANDI(R0, R0, ULP_BUTTON_INT_SINGLE), 187 | M_BXZ(LBL_BUTTON_CHECK_INTS_DOUBLE), 188 | I_LD(R0, R1, offsetof(ulp_button_t, processed.single_clicks) / sizeof(ulp_var_t)), 189 | I_LD(R2, R1, offsetof(ulp_button_t, raw.single_clicks) / sizeof(ulp_var_t)), 190 | I_SUBR(R0, R0, R2), 191 | M_BGE(LBL_BUTTON_INT, 1), 192 | 193 | M_LABEL(LBL_BUTTON_CHECK_INTS_DOUBLE), 194 | I_LD(R0, R1, offsetof(ulp_button_t, interrupts_en) / sizeof(ulp_var_t)), 195 | I_ANDI(R0, R0, ULP_BUTTON_INT_DOUBLE), 196 | M_BXZ(LBL_BUTTON_CHECK_INTS_HOLD), 197 | I_LD(R0, R1, offsetof(ulp_button_t, processed.double_clicks) / sizeof(ulp_var_t)), 198 | I_LD(R2, R1, offsetof(ulp_button_t, raw.double_clicks) / sizeof(ulp_var_t)), 199 | I_SUBR(R0, R0, R2), 200 | M_BGE(LBL_BUTTON_INT, 1), 201 | 202 | M_LABEL(LBL_BUTTON_CHECK_INTS_HOLD), 203 | I_LD(R0, R1, offsetof(ulp_button_t, interrupts_en) / sizeof(ulp_var_t)), 204 | I_ANDI(R0, R0, ULP_BUTTON_INT_HOLD), 205 | M_BXZ(LBL_BUTTON_DONE), 206 | I_LD(R0, R1, offsetof(ulp_button_t, processed.holds) / sizeof(ulp_var_t)), 207 | I_LD(R2, R1, offsetof(ulp_button_t, raw.holds) / sizeof(ulp_var_t)), 208 | I_SUBR(R0, R0, R2), 209 | M_BL(LBL_BUTTON_DONE, 1), 210 | 211 | M_LABEL(LBL_BUTTON_INT), 212 | I_WAKE(), 213 | I_RD_TICKS_REG(reint_shift), 214 | I_ST(R0, R1, offsetof(ulp_button_t, priv.ts_int) / sizeof(ulp_var_t)), 215 | 216 | M_LABEL(LBL_BUTTON_DONE), 217 | I_BXR(R3), 218 | }; 219 | 220 | uint32_t program_size = ARRAY_SIZE(program); 221 | ESP_ERROR_CHECK(ulp_process_macros_and_load(*load_addr, program, &program_size)); 222 | *load_addr += program_size; 223 | return ESP_OK; 224 | } 225 | 226 | static esp_err_t load_ulp_program_begin(uint32_t *load_addr) 227 | { 228 | const ulp_insn_t program[] = { 229 | I_FLAG_UPDATE_TICKS(), 230 | }; 231 | uint32_t program_size = ARRAY_SIZE(program); 232 | ESP_ERROR_CHECK(ulp_process_macros_and_load(*load_addr, program, &program_size)); 233 | *load_addr += program_size; 234 | return ESP_OK; 235 | } 236 | 237 | static esp_err_t load_ulp_button_block(gpio_num_t gpio, uint16_t state_rtc_offset, uint32_t *load_addr) 238 | { 239 | const ulp_insn_t program[] = { 240 | I_MOVI(R3, *load_addr + 4), 241 | I_MOVI(R1, state_rtc_offset), 242 | I_GPIO_READ(gpio), 243 | I_BXI(0), 244 | }; 245 | uint32_t program_size = ARRAY_SIZE(program); 246 | ESP_ERROR_CHECK(ulp_process_macros_and_load(*load_addr, program, &program_size)); 247 | *load_addr += program_size; 248 | return ESP_OK; 249 | } 250 | 251 | static esp_err_t load_ulp_program_end(uint32_t *load_addr) 252 | { 253 | const ulp_insn_t program[] = { 254 | I_HALT(), 255 | }; 256 | uint32_t program_size = ARRAY_SIZE(program); 257 | ESP_ERROR_CHECK(ulp_process_macros_and_load(*load_addr, program, &program_size)); 258 | *load_addr += program_size; 259 | return ESP_OK; 260 | } 261 | 262 | esp_err_t ulp_buttons_init(const gpio_num_t *button_gpios, ulp_button_t *buttons, size_t num_buttons, uint32_t period_us, int debounce_ms, int double_click_timeout_ms, int hold_ms, int reinterrupt_ms) 263 | { 264 | uint32_t program_size = 0; 265 | 266 | // Load the handler at offset 0 267 | ESP_ERROR_CHECK(load_ulp_button_handler(&program_size, debounce_ms, double_click_timeout_ms, hold_ms, reinterrupt_ms)); 268 | 269 | // Program entry will be at this point (ie. after handler routine) so keep a copy of current pc 270 | const uint32_t program_entry = program_size; 271 | 272 | ESP_ERROR_CHECK(load_ulp_program_begin(&program_size)); 273 | 274 | for(int i = 0; i < num_buttons; ++i) 275 | { 276 | ESP_ERROR_CHECK(load_ulp_button_block(button_gpios[i], RTC_WORD_OFFSET(buttons[i]), &program_size)); 277 | } 278 | 279 | ESP_ERROR_CHECK(load_ulp_program_end(&program_size)); 280 | 281 | for(int i = 0; i < num_buttons; ++i) 282 | { 283 | ESP_ERROR_CHECK(hulp_configure_pin(button_gpios[i], RTC_GPIO_MODE_INPUT_ONLY, GPIO_PULLUP_ONLY, 0)); 284 | } 285 | 286 | ESP_ERROR_CHECK(ulp_set_wakeup_period(0, period_us)); 287 | ESP_ERROR_CHECK(hulp_ulp_run(program_entry)); 288 | return ESP_OK; 289 | } 290 | 291 | static uint16_t process_button_evt(ulp_var_t* processed, ulp_var_t *raw) 292 | { 293 | uint16_t diff = raw->val - processed->val; 294 | if(diff) 295 | { 296 | processed->val = raw->val; 297 | } 298 | return diff; 299 | } 300 | uint16_t ulp_button_process_single_clicks(ulp_button_t *button) 301 | { 302 | return process_button_evt(&button->processed.single_clicks, &button->raw.single_clicks); 303 | } 304 | uint16_t ulp_button_process_double_clicks(ulp_button_t *button) 305 | { 306 | return process_button_evt(&button->processed.double_clicks, &button->raw.double_clicks); 307 | } 308 | uint16_t ulp_button_process_holds(ulp_button_t *button) 309 | { 310 | return process_button_evt(&button->processed.holds, &button->raw.holds); 311 | } 312 | 313 | void ulp_button_interrupt_config(ulp_button_t *button, uint16_t interrupt_en) 314 | { 315 | button->interrupts_en.val = interrupt_en; 316 | } -------------------------------------------------------------------------------- /examples/Buttons/main/ulp_buttons.h: -------------------------------------------------------------------------------- 1 | #ifndef ULP_BUTTONS_H 2 | #define ULP_BUTTONS_H 3 | 4 | #include "hulp.h" 5 | 6 | enum { 7 | ULP_BUTTON_INT_SINGLE = (1 << 0), 8 | ULP_BUTTON_INT_DOUBLE = (1 << 1), 9 | ULP_BUTTON_INT_HOLD = (1 << 2), 10 | }; 11 | 12 | typedef struct { 13 | ulp_var_t single_clicks; 14 | ulp_var_t double_clicks; 15 | ulp_var_t holds; 16 | } ulp_button_actions_t; 17 | 18 | // Struct containing config/state required for each button (in RTC slow memory) 19 | typedef struct { 20 | ulp_var_t interrupts_en; // interrupt enabled bits (ULP_BUTTON_INT_x) 21 | ulp_button_actions_t raw; // raw events queued by ULP (SoC: read only) 22 | ulp_button_actions_t processed; // events acknowledged by SoC (ULP: read only) 23 | struct { // private ULP data 24 | ulp_var_t state; 25 | ulp_var_t ts; 26 | ulp_var_t ts_int; 27 | } priv; 28 | } ulp_button_t; 29 | 30 | esp_err_t ulp_buttons_init(const gpio_num_t *button_gpios, ulp_button_t *buttons, size_t num_buttons, uint32_t period_us, int debounce_ms, int double_click_timeout_ms, int hold_ms, int reinterrupt_ms); 31 | 32 | uint16_t ulp_button_process_single_clicks(ulp_button_t *button); 33 | uint16_t ulp_button_process_double_clicks(ulp_button_t *button); 34 | uint16_t ulp_button_process_holds(ulp_button_t *button); 35 | void ulp_button_interrupt_config(ulp_button_t *button, uint16_t interrupt_en); 36 | 37 | #endif // ULP_BUTTONS_H -------------------------------------------------------------------------------- /examples/Buttons/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 -------------------------------------------------------------------------------- /examples/Debugging/Modify/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_debug_modify) -------------------------------------------------------------------------------- /examples/Debugging/Modify/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/Debugging/Modify/main/main.cpp: -------------------------------------------------------------------------------- 1 | #include "freertos/FreeRTOS.h" 2 | #include "freertos/task.h" 3 | #include "esp32/rom/ets_sys.h" 4 | #include "esp_log.h" 5 | 6 | #include "hulp.h" 7 | #include "hulp_debug.h" 8 | 9 | //Debugging requires a ulp_debug_bp_data_t in RTC memory: 10 | RTC_DATA_ATTR ulp_debug_bp_data_t ulp_debug_data; 11 | 12 | enum { 13 | LAB_BREAKPOINT_1 = 100, 14 | LAB_BREAKPOINT_2, 15 | LAB_BREAKPOINT_3, 16 | LAB_BREAKPOINT_4, 17 | LAB_SOMEWHERE_ELSE, 18 | LAB_FINISHED, 19 | LAB_DEBUG_ENTRY, 20 | }; 21 | 22 | void ulp_breakpoint_handler(hulp_debug_bp_cb_data_t* bp_data, void *ctx) 23 | { 24 | //This is an ISR. No lengthy operations, logging, printf, etc. 25 | 26 | //Use hulp_debug_bp_print_info for a simple dump of key breakpoint data: 27 | hulp_debug_bp_print_info(bp_data); 28 | 29 | //Some examples of things that may be checked/manipulated in breakpoint ISR: 30 | 31 | //Set R1 to 1234: 32 | // hulp_debug_bp_alter_reg(bp_data, R1, 1234); 33 | 34 | //Get and set register values. 35 | //eg. if R3 == 4 then R0++ 36 | if(bp_data->regs.r3 == 4) 37 | { 38 | hulp_debug_bp_alter_reg(bp_data, R0, bp_data->regs.r0 + 1); 39 | } 40 | 41 | //Change where the ULP will continue execution via a label. 42 | //eg. If it hit breakpoint with the label LAB_BREAKPOINT_1, change execution to LAB_SOMEWHERE_ELSE: 43 | if(bp_data->bp.label.valid && bp_data->bp.label.num == LAB_BREAKPOINT_1) 44 | { 45 | hulp_debug_bp_set_continue_label(bp_data, LAB_SOMEWHERE_ELSE); 46 | } 47 | 48 | //Enable a breakpoint by label number (this or another) 49 | //eg. if this breakpoint has label LAB_BREAKPOINT_3, enable the breakpoint with label LAB_BREAKPOINT_1 50 | if(bp_data->bp.label.valid && bp_data->bp.label.num == LAB_BREAKPOINT_3) 51 | { 52 | hulp_debug_bp_enable_by_label(bp_data->meta.handle, LAB_BREAKPOINT_1); 53 | } 54 | 55 | //Disable this breakpoint using its pc 56 | //eg. if R0==7, disable this breakpoint 57 | if(bp_data->regs.r0 == 7) 58 | { 59 | hulp_debug_bp_disable_by_pc(bp_data->bp.pc); 60 | } 61 | 62 | //If some condition is met, choose not to restart ULP 63 | //eg. if R0 < 1000 at LAB_BREAKPOINT_4 64 | if(bp_data->bp.label.valid && bp_data->bp.label.num == LAB_BREAKPOINT_4) 65 | { 66 | if(bp_data->regs.r0 < 1000) 67 | { 68 | ets_printf("Oh no, a low R0 value at breakpoint 4?! ULP will not be restarted.\n"); 69 | return; 70 | } 71 | } 72 | 73 | //eg. Stop after certain duration 74 | if(esp_log_timestamp() > (5 * 60 * 1000)) 75 | { 76 | ets_printf("Time expired, ULP will not be restarted\n"); 77 | return; 78 | } 79 | 80 | //When done, continue the ULP 81 | hulp_debug_bp_continue(bp_data); 82 | } 83 | 84 | void init_ulp() 85 | { 86 | const ulp_insn_t program[] = { 87 | I_ADDI(R3, R3, 1), 88 | I_MOVI(R1, 0), 89 | 90 | I_ADDI(R1, R1, 1), 91 | M_LABEL(LAB_BREAKPOINT_1), M_DEBUG_SET_BP(LAB_DEBUG_ENTRY, R2, ulp_debug_data), 92 | I_ADDI(R1, R1, 1), 93 | M_LABEL(LAB_SOMEWHERE_ELSE), 94 | I_ADDI(R1, R1, 1), 95 | M_LABEL(LAB_BREAKPOINT_2), M_DEBUG_SET_BP(LAB_DEBUG_ENTRY, R2, ulp_debug_data), 96 | I_ANDR(R0, R3, 1), 97 | M_BGE(LAB_FINISHED, 1), 98 | I_ADDI(R1, R1, 1), 99 | M_LABEL(LAB_BREAKPOINT_3), M_DEBUG_SET_BP(LAB_DEBUG_ENTRY, R2, ulp_debug_data), 100 | M_UPDATE_TICKS(), 101 | I_RD_TICKS_REG(1), 102 | I_ADDI(R1, R1, 1), 103 | M_LABEL(LAB_BREAKPOINT_4), M_DEBUG_SET_BP(LAB_DEBUG_ENTRY, R2, ulp_debug_data), 104 | 105 | M_LABEL(LAB_FINISHED), 106 | I_HALT(), 107 | 108 | M_INCLUDE_DEBUG_BP(LAB_DEBUG_ENTRY, R2, ulp_debug_data), 109 | }; 110 | 111 | hulp_debug_bp_config_t debug_cfg = { 112 | .data = &ulp_debug_data, 113 | .program = { 114 | .ptr = program, 115 | .num_words = (sizeof(program) / sizeof(program)[0]), 116 | }, 117 | .callback = { 118 | .fn = ulp_breakpoint_handler, 119 | .ctx = NULL, 120 | }, 121 | }; 122 | 123 | hulp_debug_bp_handle_t dbg_handle; 124 | ESP_ERROR_CHECK(hulp_debug_bp_init(&debug_cfg, &dbg_handle)); 125 | 126 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), 1ULL * 1000 * 1000, 0)); 127 | 128 | //Breakpoints may be disabled/enabled once the program is loaded (default: enabled) 129 | hulp_debug_bp_disable_by_label(dbg_handle, LAB_BREAKPOINT_1); 130 | 131 | //Everything is ready, start the ULP. 132 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 133 | } 134 | 135 | extern "C" void app_main() 136 | { 137 | init_ulp(); 138 | 139 | vTaskDelete(NULL); 140 | } 141 | -------------------------------------------------------------------------------- /examples/Debugging/Modify/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 -------------------------------------------------------------------------------- /examples/Debugging/Simple/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_debug_simple) -------------------------------------------------------------------------------- /examples/Debugging/Simple/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/Debugging/Simple/main/main.cpp: -------------------------------------------------------------------------------- 1 | #include "freertos/FreeRTOS.h" 2 | #include "freertos/task.h" 3 | 4 | #include "hulp.h" 5 | #include "hulp_debug.h" 6 | 7 | //A ulp_debug_bp_data_t struct in RTC memory: 8 | RTC_DATA_ATTR ulp_debug_bp_data_t ulp_debug_data; 9 | 10 | RTC_DATA_ATTR ulp_var_t ulp_some_counter; 11 | 12 | void init_ulp() 13 | { 14 | enum { 15 | LAB_BREAKPOINT_1 = 100, 16 | LAB_BREAKPOINT_2, 17 | LAB_DEBUG_ENTRY, 18 | }; 19 | 20 | const ulp_insn_t program[] = { 21 | 22 | I_ADDI(R3, R3, 1), 23 | 24 | //A breakpoint with a label: 25 | M_LABEL(LAB_BREAKPOINT_1), M_DEBUG_SET_BP(LAB_DEBUG_ENTRY, R2, ulp_debug_data), 26 | 27 | M_UPDATE_TICKS(), 28 | I_RD_TICKS_REG(1), 29 | 30 | //Another one: 31 | M_LABEL(LAB_BREAKPOINT_2), M_DEBUG_SET_BP(LAB_DEBUG_ENTRY, R2, ulp_debug_data), 32 | 33 | I_MOVI(R2, 0), 34 | I_GET(R1, R2, ulp_some_counter), 35 | I_SUBI(R1, R1, 3), 36 | I_PUT(R1, R2, ulp_some_counter), 37 | 38 | //A simple breakpoint without a label: 39 | M_DEBUG_SET_BP(LAB_DEBUG_ENTRY, R2, ulp_debug_data), 40 | 41 | I_HALT(), 42 | 43 | //Breakpoint dependency: 44 | M_INCLUDE_DEBUG_BP(LAB_DEBUG_ENTRY, R2, ulp_debug_data), 45 | }; 46 | 47 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), 1ULL * 1000 * 1000, 0)); 48 | 49 | //Basic debugging initialisation: 50 | hulp_debug_bp_config_t debug_config = HULP_DEBUG_BP_CONFIG_DEFAULT(ulp_debug_data, program, sizeof(program)); 51 | ESP_ERROR_CHECK(hulp_debug_bp_init(&debug_config, NULL)); 52 | 53 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 54 | } 55 | 56 | extern "C" void app_main() 57 | { 58 | init_ulp(); 59 | 60 | vTaskDelay(portMAX_DELAY); 61 | } -------------------------------------------------------------------------------- /examples/Debugging/Simple/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 -------------------------------------------------------------------------------- /examples/GPIO_Wakeup/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_gpio_wakeup) -------------------------------------------------------------------------------- /examples/GPIO_Wakeup/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/GPIO_Wakeup/main/main.cpp: -------------------------------------------------------------------------------- 1 | /* ULP GPIO Wakeup Example 2 | 3 | This uses the deep sleep wakeup stub to add GPIO wakeup functionality to ESP32 ULP. 4 | 5 | Upon powering on, the ULP program is loaded, an EXT1 wakeup trigger is configured, a custom deep 6 | sleep wakeup stub is set, and deep sleep begins. 7 | 8 | In a typical application, the EXT1 trigger GPIO might be an interrupt signal from an I2C slave, and the ULP 9 | might read some data, apply filtering, and determine whether or not to wake the SoC. 10 | In this example, GPIO 0 is used as the wakeup trigger as it is usually connected to a button on most dev boards, 11 | and the ULP simply increments a counter, waking the SoC when it reaches 5. 12 | 13 | For optimal power consumption, the stub strives to return to sleep asap. It disables EXT1, starts ULP, and immediately 14 | sleeps; the ULP handles processing, waits until the button is released, and then re-enables the EXT1 interrupt in 15 | preparation for the next trigger. 16 | 17 | Note that the ULP sleep timer is used here even though the ULP disables it anyway at the end of the program. This is 18 | because triggering a single ULP run in deep sleep proved difficult: it would often use the 150kHz clock (instead of 19 | 8MHz); and RTC memory would power down once the stub ended (often while the ULP is running) causing lockup. Using the 20 | timer forces the RTC controller to handle all of this which is much easier and more reliable. 21 | 22 | In practice, ULP runs ~1ms after trigger edge. When idle, expect nominal ESP32 deep sleep current (5-10uA). 23 | */ 24 | #include 25 | #include "freertos/FreeRTOS.h" 26 | #include "freertos/task.h" 27 | #include "esp_sleep.h" 28 | #include "esp32/rom/rtc.h" 29 | #include "esp32/rom/ets_sys.h" 30 | #include "soc/uart_reg.h" 31 | #include "soc/timer_group_reg.h" 32 | 33 | #include "hulp.h" 34 | 35 | //Uncomment for UART info and/or to scope STUB_DEBUG_GPIO and ULP_DEBUG_GPIO 36 | // #define DEBUG_MODE 37 | 38 | #define TRIGGER_GPIO GPIO_NUM_0 39 | 40 | RTC_DATA_ATTR ulp_var_t ulp_counter; 41 | RTC_DATA_ATTR ulp_var_t ulp_wake_signal; 42 | 43 | #ifdef DEBUG_MODE 44 | #define STUB_DEBUG_GPIO GPIO_NUM_26 45 | #define ULP_DEBUG_GPIO GPIO_NUM_27 46 | RTC_FAST_ATTR int stub_rtc_io; 47 | RTC_RODATA_ATTR static const char debug_fmt_str[] = "ULP Counter: %u, Wake Flag: %u\n"; 48 | 49 | static inline void RTC_IRAM_ATTR stub_uart_flush() 50 | { 51 | while (REG_GET_FIELD(UART_STATUS_REG(0), UART_ST_UTX_OUT)) { 52 | REG_WRITE(TIMG_WDTFEED_REG(0), 1); 53 | ; 54 | } 55 | } 56 | 57 | static inline void RTC_IRAM_ATTR stub_set_rtcio(uint32_t rtcio, uint32_t level) 58 | { 59 | if(level) 60 | { 61 | REG_SET_BIT(RTC_GPIO_OUT_W1TS_REG, BIT(RTC_GPIO_OUT_DATA_W1TS_S + rtcio)); 62 | } 63 | else 64 | { 65 | REG_SET_BIT(RTC_GPIO_OUT_W1TC_REG, BIT(RTC_GPIO_OUT_DATA_W1TC_S + rtcio)); 66 | } 67 | } 68 | #endif // DEBUG_MODE 69 | 70 | static inline void RTC_IRAM_ATTR stub_start_ulp() 71 | { 72 | REG_SET_BIT(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); 73 | } 74 | 75 | static inline void RTC_IRAM_ATTR stub_set(esp_deep_sleep_wake_stub_fn_t stub) 76 | { 77 | REG_WRITE(RTC_ENTRY_ADDR_REG, (uint32_t)stub); 78 | } 79 | 80 | static inline void RTC_IRAM_ATTR stub_return_to_sleep() 81 | { 82 | // See https://gist.github.com/igrr/54f7fbe0513ac14e1aea3fd7fbecfeab 83 | CLEAR_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_SLEEP_EN); 84 | SET_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_SLEEP_EN); 85 | // A few CPU cycles may be necessary for the sleep to start... 86 | for(;;); 87 | } 88 | 89 | static inline void RTC_IRAM_ATTR stub_disable_ext1() 90 | { 91 | REG_CLR_BIT(RTC_CNTL_WAKEUP_STATE_REG, BIT(RTC_CNTL_WAKEUP_ENA_S + 1)); 92 | } 93 | 94 | static void RTC_IRAM_ATTR wake_stub() 95 | { 96 | #ifdef DEBUG_MODE 97 | stub_set_rtcio(stub_rtc_io, 0); 98 | stub_set_rtcio(stub_rtc_io, 1); 99 | ets_printf(debug_fmt_str, ulp_counter.val, ulp_wake_signal.val); 100 | stub_uart_flush(); 101 | #endif 102 | 103 | stub_disable_ext1(); 104 | 105 | //If the ULP has set ulp_wake_signal then do full wakeup 106 | if(ulp_wake_signal.val != 0) 107 | { 108 | ulp_wake_signal.val = 0; 109 | esp_default_wake_deep_sleep(); 110 | return; 111 | } 112 | 113 | //Else start ULP and go back to sleep 114 | stub_start_ulp(); 115 | 116 | stub_set(&wake_stub); 117 | 118 | stub_return_to_sleep(); 119 | } 120 | 121 | void prepare_ulp() 122 | { 123 | enum { 124 | LBL_BELOW_THRESHOLD, 125 | LBL_FINISH_UP, 126 | }; 127 | 128 | const ulp_insn_t program[] = { 129 | #ifdef DEBUG_MODE 130 | //Toggle GPIO 131 | I_GPIO_SET(ULP_DEBUG_GPIO, 0), 132 | I_DELAY(65535), 133 | I_GPIO_SET(ULP_DEBUG_GPIO, 1), 134 | #endif 135 | 136 | //Increment counter 137 | I_MOVI(R2, 0), 138 | I_GET(R0, R2, ulp_counter), 139 | I_ADDI(R0, R0, 1), 140 | I_PUT(R0, R2, ulp_counter), 141 | 142 | //Check if counter has reached threshold 143 | M_BL(LBL_BELOW_THRESHOLD, 5), 144 | 145 | //Set ulp_wake_signal 146 | I_MOVI(R1, 1), 147 | I_PUT(R1, R2, ulp_wake_signal), 148 | //Use the EXT1 interrupt to wake the SoC again by driving the pin low 149 | //And loop until stub resets the flag to 0 to acknowledge 150 | I_GPIO_OUTPUT_EN(TRIGGER_GPIO), 151 | I_EXT1_EN(), 152 | I_GET(R0, R2, ulp_wake_signal), 153 | I_BGE(-3, 1), 154 | //Stop driving the pin low so button can interrupt again 155 | I_GPIO_OUTPUT_DIS(TRIGGER_GPIO), 156 | //Reset counter 157 | I_PUT(R2, R2, ulp_counter), 158 | M_BX(LBL_FINISH_UP), 159 | 160 | M_LABEL(LBL_BELOW_THRESHOLD), 161 | //Wait for button to be released 162 | I_GPIO_READ(TRIGGER_GPIO), 163 | I_BL(-1, 1), 164 | //Debounce a few ms 165 | I_DELAY(65535), 166 | 167 | M_LABEL(LBL_FINISH_UP), 168 | //Re-enable EXT1 so next button press will run wakeup stub 169 | I_EXT1_EN(), 170 | //Disable timer (ie. only run once) 171 | I_END(), 172 | I_HALT(), 173 | 174 | }; 175 | 176 | ESP_ERROR_CHECK(hulp_configure_pin(TRIGGER_GPIO, RTC_GPIO_MODE_INPUT_ONLY, GPIO_PULLUP_ONLY, 0)); 177 | 178 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), 0, 0)); 179 | 180 | //Prepare ULP, but do not enable timer so it won't run yet (see ulp_run()) 181 | REG_SET_FIELD(SENS_SAR_START_FORCE_REG, SENS_PC_INIT, 0); 182 | CLEAR_PERI_REG_MASK(SENS_SAR_START_FORCE_REG, SENS_ULP_CP_FORCE_START_TOP); 183 | SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_BIAS_I2C_FOLW_8M); 184 | SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_BIAS_CORE_FOLW_8M); 185 | SET_PERI_REG_MASK(RTC_CNTL_OPTIONS0_REG, RTC_CNTL_BIAS_SLEEP_FOLW_8M); 186 | } 187 | 188 | extern "C" void app_main(void) 189 | { 190 | if(hulp_is_deep_sleep_wakeup()) 191 | { 192 | printf("Woken up!\n"); 193 | } 194 | else 195 | { 196 | #ifdef DEBUG_MODE 197 | stub_rtc_io = rtc_io_number_get(STUB_DEBUG_GPIO); 198 | assert(stub_rtc_io != -1); 199 | hulp_configure_pin(STUB_DEBUG_GPIO, RTC_GPIO_MODE_OUTPUT_ONLY, GPIO_PULLUP_ONLY, 1); 200 | hulp_configure_pin(ULP_DEBUG_GPIO, RTC_GPIO_MODE_OUTPUT_ONLY, GPIO_PULLUP_ONLY, 1); 201 | #endif 202 | prepare_ulp(); 203 | } 204 | 205 | vTaskDelay(1000 / portTICK_PERIOD_MS); 206 | 207 | hulp_peripherals_on(); 208 | esp_set_deep_sleep_wake_stub(&wake_stub); 209 | ESP_ERROR_CHECK( esp_sleep_enable_ext1_wakeup(1ULL << TRIGGER_GPIO, ESP_EXT1_WAKEUP_ALL_LOW) ); 210 | esp_deep_sleep_start(); 211 | } 212 | -------------------------------------------------------------------------------- /examples/GPIO_Wakeup/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 -------------------------------------------------------------------------------- /examples/HX711/calibrate_and_wake/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_hx711) -------------------------------------------------------------------------------- /examples/HX711/calibrate_and_wake/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.c" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/HX711/calibrate_and_wake/main/main.c: -------------------------------------------------------------------------------- 1 | /* HX711 Example 2 | 3 | This example will read from a HX711 load cell amplifier, waking from deep sleep if the set weight threshold is met. 4 | 5 | Have a weight ready to use for calibration (set HX711_CALIBRATION_WEIGHT_G). Ensure there is nothing on the scales 6 | until prompted, then place the calibration weight down. 7 | Set HX711_THRESHOLD_WEIGHT_G to the minimum weight required to trigger a ULP deep sleep wakeup. 8 | */ 9 | 10 | #include 11 | #include 12 | #include "freertos/FreeRTOS.h" 13 | #include "freertos/task.h" 14 | #include "esp_log.h" 15 | #include "esp_sleep.h" 16 | 17 | #include "hulp.h" 18 | #include "hulp_hx711.h" 19 | 20 | static const char *TAG = "HULP_HX711"; 21 | 22 | #define PIN_HX711_SDA GPIO_NUM_25 23 | #define PIN_HX711_SCL GPIO_NUM_26 24 | 25 | /* Specify the calibration weight here */ 26 | #define HX711_CALIBRATION_WEIGHT_G 1000.0f 27 | 28 | /* Specify the threshold weight here. The ULP will wake the SoC when the weight exceeds this value. */ 29 | #define HX711_THRESHOLD_WEIGHT_G 500.0f 30 | 31 | // Helpers to pass structured HX711 values between ULP and SoC 32 | typedef struct { 33 | ulp_var_t high; 34 | ulp_var_t low; 35 | } ulp_hx711_data_t; 36 | 37 | static uint32_t hx711_ulp_to_val(const ulp_hx711_data_t *data) 38 | { 39 | return (((uint32_t)(data->high.val) << 8) | (data->low.val)); 40 | } 41 | 42 | static void hx711_val_to_ulp(ulp_hx711_data_t *data, const uint32_t value) 43 | { 44 | data->high.val = (value >> 8) & 0xFFFF; 45 | data->low.val = (value >> 0) & 0xFFFF; 46 | } 47 | 48 | // For the ULP to store readings 49 | RTC_DATA_ATTR ulp_hx711_data_t hx711_val; 50 | // For the SoC to set a wake threshold, accessible to ULP 51 | RTC_DATA_ATTR ulp_hx711_data_t hx711_threshold; 52 | // Keep these in RTC memory as they are required on deep sleep wakeup to convert values to weights. 53 | RTC_DATA_ATTR uint32_t idle_value; 54 | RTC_DATA_ATTR float cal_factor; 55 | 56 | static void ulp_isr(void *task_handle_ptr) 57 | { 58 | xTaskNotifyFromISR((TaskHandle_t)task_handle_ptr, 0, eNoAction, NULL); 59 | } 60 | 61 | static void init_ulp(void) 62 | { 63 | // Configure pin 64 | ESP_ERROR_CHECK(hulp_configure_pin(PIN_HX711_SDA, RTC_GPIO_MODE_INPUT_ONLY, GPIO_FLOATING, 0)); 65 | ESP_ERROR_CHECK(hulp_configure_pin(PIN_HX711_SCL, RTC_GPIO_MODE_OUTPUT_ONLY, GPIO_FLOATING, 0)); 66 | // For the calibration phase, the ULP will send interrupts when data is ready. Prepare ISR here. 67 | TaskHandle_t main_handle = xTaskGetCurrentTaskHandle(); 68 | hulp_ulp_isr_register(&ulp_isr, main_handle); 69 | hulp_ulp_interrupt_en(); 70 | } 71 | 72 | static void load_hx711_single_read_program(void) 73 | { 74 | // A simple program to read the value from the HX711 when ready, then interrupt the SoC 75 | const ulp_insn_t program[] = { 76 | // Read from HX711 77 | M_HX711_WAIT_READY(PIN_HX711_SDA), 78 | M_HX711_READ(R1, R2, PIN_HX711_SDA, PIN_HX711_SCL), 79 | // Store and alert SoC that a new value is ready 80 | I_MOVI(R3, 0), 81 | I_PUT(R1, R3, hx711_val.high), 82 | I_PUT(R2, R3, hx711_val.low), 83 | I_WAKE(), 84 | // Done 85 | I_HALT(), 86 | }; 87 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), 1000, 0)); 88 | } 89 | 90 | static void load_hx711_wake_above_threshold_program(void) 91 | { 92 | // A more advanced program to compare the current value to a set threshold, waking the SoC if exceeded. 93 | // The HX711 is powered down when not in use for lower power consumption. 94 | enum { 95 | LBL_WAKEUP, 96 | LBL_SLEEP, 97 | }; 98 | const ulp_insn_t program[] = { 99 | I_HX711_POWER_UP(PIN_HX711_SCL), 100 | 101 | M_HX711_WAIT_READY(PIN_HX711_SDA), 102 | M_HX711_READ(R1, R2, PIN_HX711_SDA, PIN_HX711_SCL), 103 | 104 | I_HX711_POWER_DOWN(PIN_HX711_SCL), 105 | 106 | I_MOVI(R3, 0), 107 | I_PUT(R1, R3, hx711_val.high), 108 | I_PUT(R2, R3, hx711_val.low), 109 | 110 | // Compare to threshold 111 | // 16-bit arithmetic, so first compare upper 16 bits 112 | I_GET(R0, R3, hx711_threshold.high), 113 | I_SUBR(R0, R1, R0), 114 | M_BXF(LBL_SLEEP), // if < threshold, sleep 115 | M_BGE(LBL_WAKEUP, 1), // if > threshold, wake 116 | // Else upper 16 bits equal, so need to compare lower 16 bits 117 | I_GET(R0, R3, hx711_threshold.low), 118 | I_SUBR(R0, R2, R0), 119 | M_BXF(LBL_SLEEP), // if < threshold, sleep 120 | // Else >= threshold, so wake 121 | M_LABEL(LBL_WAKEUP), 122 | M_WAKE_WHEN_READY(), 123 | M_LABEL(LBL_SLEEP), 124 | I_HALT(), 125 | }; 126 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), 250 * 1000, 0)); 127 | } 128 | 129 | static float hx711_val_to_g(uint32_t val, uint32_t idle_val, float calibration_factor) 130 | { 131 | return (((int64_t)val - idle_val) / calibration_factor); 132 | } 133 | 134 | static uint32_t hx711_ulp_read(int samples) 135 | { 136 | uint32_t total = 0; 137 | for(int i = 0; i < samples; ++i) 138 | { 139 | ESP_ERROR_CHECK(hulp_ulp_run_once(0)); 140 | xTaskNotifyWait(0, 0, NULL, portMAX_DELAY); 141 | uint32_t this_val = hx711_ulp_to_val(&hx711_val); 142 | ESP_LOGD(TAG, "Val %d/%d: %" PRIu32, i+1, samples, this_val); 143 | total += this_val; 144 | assert(total >= this_val); 145 | } 146 | return (uint32_t)((total + (samples / 2)) / samples); 147 | } 148 | 149 | void app_main(void) 150 | { 151 | if(!hulp_is_deep_sleep_wakeup()) 152 | { 153 | init_ulp(); 154 | 155 | // Load a simple program for the ULP to read values from HX711 156 | load_hx711_single_read_program(); 157 | 158 | // Take a few readings from the HX711 to set the 'idle' value when no load is on the scales 159 | idle_value = hx711_ulp_read(5); 160 | ESP_LOGI(TAG, "Tare Value: %" PRIu32, idle_value); 161 | 162 | // Now is the time to place the calibration weight 163 | ESP_LOGI(TAG, "Place the known weight (%.2fg) now. Calibrating in...", HX711_CALIBRATION_WEIGHT_G); 164 | for(int i = 5; i > 0; --i) 165 | { 166 | ESP_LOGI(TAG, "%d...", i); 167 | vTaskDelay(1000 / portTICK_PERIOD_MS); 168 | } 169 | 170 | // Get some readings from HX711 now that it is loaded with the calibration weight, and use the difference to calibrate. 171 | ESP_LOGI(TAG, "Calibrating..."); 172 | uint32_t loaded_value = hx711_ulp_read(5); 173 | ESP_LOGI(TAG, "Loaded Value (%.2fg): %" PRIu32, HX711_CALIBRATION_WEIGHT_G, loaded_value); 174 | cal_factor = ((int64_t)loaded_value - idle_value) / HX711_CALIBRATION_WEIGHT_G; 175 | ESP_LOGI(TAG, "Calibration complete: val = %.2fg + %" PRIu32, cal_factor, idle_value); 176 | vTaskDelay(2000 / portTICK_PERIOD_MS); 177 | 178 | // Print the current value for debugging/testing for a little while 179 | for(int i = 0; i < 10; ++i) 180 | { 181 | ESP_LOGI(TAG, "Current Weight: %.2fg", hx711_val_to_g(hx711_ulp_read(1), idle_value, cal_factor)); 182 | vTaskDelay(1000 / portTICK_PERIOD_MS); 183 | } 184 | 185 | // Set the ULP wake threshold to the threshold weight. 186 | uint32_t wake_threshold_value = idle_value + cal_factor * HX711_THRESHOLD_WEIGHT_G; 187 | ESP_LOGI(TAG, "Wake threshold (%.2fg): %" PRIu32, HX711_THRESHOLD_WEIGHT_G, wake_threshold_value); 188 | hx711_val_to_ulp(&hx711_threshold, wake_threshold_value); 189 | 190 | // Load deep sleep wakeup program 191 | load_hx711_wake_above_threshold_program(); 192 | } 193 | else 194 | { 195 | ESP_LOGI(TAG, "Woken up! Recent: %.2fg", hx711_val_to_g(hx711_ulp_to_val(&hx711_val), idle_value, cal_factor)); 196 | } 197 | 198 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 199 | vTaskDelay(1000 / portTICK_PERIOD_MS); 200 | 201 | while(hx711_ulp_to_val(&hx711_val) > hx711_ulp_to_val(&hx711_threshold)) 202 | { 203 | ESP_LOGI(TAG, "Waiting for weight to be released..."); 204 | vTaskDelay(1000 / portTICK_PERIOD_MS); 205 | } 206 | 207 | ESP_LOGI(TAG, "Sleeping"); 208 | hulp_peripherals_on(); 209 | esp_sleep_enable_ulp_wakeup(); 210 | esp_deep_sleep_start(); 211 | } 212 | -------------------------------------------------------------------------------- /examples/HX711/calibrate_and_wake/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 -------------------------------------------------------------------------------- /examples/HallEffect/ThresholdWake/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_hall_threshold) -------------------------------------------------------------------------------- /examples/HallEffect/ThresholdWake/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/HallEffect/ThresholdWake/main/main.cpp: -------------------------------------------------------------------------------- 1 | #include "freertos/FreeRTOS.h" 2 | #include "freertos/task.h" 3 | #include "driver/rtc_io.h" 4 | #include "driver/gpio.h" 5 | #include "soc/rtc.h" 6 | #include "esp_sleep.h" 7 | 8 | #include "hulp.h" 9 | #include "hulp_hall.h" 10 | 11 | #define HALL_THRESHOLD 100 12 | 13 | RTC_DATA_ATTR ulp_var_t ulp_adcvp0; 14 | RTC_DATA_ATTR ulp_var_t ulp_adcvn0; 15 | RTC_DATA_ATTR ulp_var_t ulp_adcvp1; 16 | RTC_DATA_ATTR ulp_var_t ulp_adcvn1; 17 | RTC_DATA_ATTR ulp_var_t ulp_adcvpdiff; 18 | RTC_DATA_ATTR ulp_var_t ulp_adcvndiff; 19 | RTC_DATA_ATTR ulp_var_t ulp_adcvdiff; 20 | 21 | RTC_DATA_ATTR ulp_var_t ulp_adcbaseline; 22 | RTC_DATA_ATTR ulp_var_t ulp_adcvbaselinediff; 23 | 24 | void init_ulp() 25 | { 26 | 27 | enum { 28 | LBL_HALT, 29 | }; 30 | 31 | const ulp_insn_t program[] = { 32 | I_MOVI(R2,0), 33 | 34 | I_ADC_POWER_ON(), 35 | 36 | I_HALL_POLARITY_FORWARD(), 37 | 38 | I_ADC(R0, 0, 0), 39 | I_PUT(R0, R2, ulp_adcvp0), 40 | I_ADC(R0, 0, 3), 41 | I_PUT(R0, R2, ulp_adcvn0), 42 | 43 | I_HALL_POLARITY_REVERSE(), 44 | 45 | I_ADC(R0, 0, 0), 46 | I_PUT(R0, R2, ulp_adcvp1), 47 | I_ADC(R0, 0, 3), 48 | I_PUT(R0, R2, ulp_adcvn1), 49 | 50 | I_ADC_POWER_OFF(), 51 | 52 | //+ diff 53 | I_GET(R0, R2, ulp_adcvp0), 54 | I_GET(R1, R2, ulp_adcvp1), 55 | I_SUBR(R0, R0, R1), 56 | I_PUT(R0, R2, ulp_adcvpdiff), 57 | 58 | //- diff 59 | I_GET(R0, R2, ulp_adcvn0), 60 | I_GET(R1, R2, ulp_adcvn1), 61 | I_SUBR(R0, R0, R1), 62 | I_PUT(R0, R2, ulp_adcvndiff), 63 | 64 | //diff 65 | I_GET(R0, R2, ulp_adcvpdiff), 66 | I_GET(R1, R2, ulp_adcvndiff), 67 | I_SUBR(R0, R0, R1), 68 | I_PUT(R0, R2, ulp_adcvdiff), 69 | 70 | I_GET(R1, R2, ulp_adcbaseline), 71 | I_SUBR(R0, R0, R1), 72 | I_SUBI(R1, R0, HALL_THRESHOLD), 73 | M_BXF(LBL_HALT), 74 | I_ADDI(R1, R0, HALL_THRESHOLD), 75 | M_BXF(LBL_HALT), 76 | I_PUT(R0, R2, ulp_adcvbaselinediff), 77 | I_WAKE(), 78 | 79 | M_LABEL(LBL_HALT), 80 | I_HALT(), 81 | }; 82 | 83 | hulp_peripherals_on(); 84 | 85 | hulp_configure_hall_effect_sensor(); 86 | 87 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), 1ULL * 50 * 1000, 0)); 88 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 89 | } 90 | 91 | extern "C" void app_main() 92 | { 93 | if(hulp_is_ulp_wakeup()) 94 | { 95 | printf("Woken: %d\n", (int16_t)ulp_adcvbaselinediff.val); 96 | printf("ADC: P: %u - %u (%d), N: %u - %u (%d), Diff: %d\n", ulp_adcvp0.val, ulp_adcvp1.val, (int16_t)ulp_adcvpdiff.val, ulp_adcvn0.val, ulp_adcvn1.val, (int16_t)ulp_adcvndiff.val, (int16_t)ulp_adcvdiff.val); 97 | } 98 | else 99 | { 100 | adc1_config_width( ADC_WIDTH_BIT_12); 101 | int baseline = hall_sensor_read(); 102 | printf("Baseline: %d\n", baseline); 103 | ulp_adcbaseline.val = baseline; 104 | 105 | init_ulp(); 106 | 107 | //Print some values for debugging 108 | TickType_t start_time = xTaskGetTickCount(); 109 | while(xTaskGetTickCount() - start_time < (1000 / portTICK_PERIOD_MS)) 110 | { 111 | printf("ADC: P: %u - %u (%d), N: %u - %u (%d), Diff: %d\n", ulp_adcvp0.val, ulp_adcvp1.val, (int16_t)ulp_adcvpdiff.val, ulp_adcvn0.val, ulp_adcvn1.val, (int16_t)ulp_adcvndiff.val, (int16_t)ulp_adcvdiff.val); 112 | vTaskDelay(50 / portTICK_PERIOD_MS); 113 | } 114 | } 115 | 116 | fflush(stdout); 117 | esp_sleep_enable_ulp_wakeup(); 118 | esp_deep_sleep_start(); 119 | } -------------------------------------------------------------------------------- /examples/HallEffect/ThresholdWake/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 -------------------------------------------------------------------------------- /examples/I2C/Bitbang/Cmd/SHT3X/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_sht3x) 9 | -------------------------------------------------------------------------------- /examples/I2C/Bitbang/Cmd/SHT3X/README.md: -------------------------------------------------------------------------------- 1 | An example of using i2c bit-banging to read an sht3x sensor every 10s but only waking up the ESP32 when temperature has changed by 0.1°C. 2 | 3 | Example output: 4 | ``` 5 | rst:0x5 (DEEPSLEEP_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) 6 | ... 7 | I (337) MAIN: temp: 19.8, last_temp: 19.9, difference: -0.11, hum: 54.6 8 | I (347) MAIN: Sleeping 9 | 10 | rst:0x5 (DEEPSLEEP_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT) 11 | ... 12 | I (337) MAIN: temp: 19.7, last_temp: 19.8, difference: -0.11, hum: 55.3 13 | I (347) MAIN: Sleeping 14 | ``` 15 | -------------------------------------------------------------------------------- /examples/I2C/Bitbang/Cmd/SHT3X/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/I2C/Bitbang/Cmd/SHT3X/main/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static const char *TAG = "MAIN"; 9 | 10 | static const uint8_t SLAVE_ADDR = 0x44; // should be either 0x44 or 0x45 11 | static const gpio_num_t SCL_PIN = GPIO_NUM_26, SDA_PIN = GPIO_NUM_25, LED_ERR = GPIO_NUM_13; 12 | static const uint16_t MAX_TEMP_DIFFERENCE_RAW = 37; // actually 37.4485 which is about 0.1C 13 | 14 | static RTC_SLOW_ATTR ulp_var_t ulp_write_cmd[] = { 15 | // write 0x24, 0x00 which is high repeatability with no clock stretching 16 | HULP_I2C_CMD_HDR(SLAVE_ADDR, 0x24, 1), 17 | HULP_I2C_CMD_1B(0x00), 18 | }; 19 | static RTC_SLOW_ATTR ulp_var_t ulp_read_cmd[HULP_I2C_CMD_BUF_SIZE(6)] = { 20 | HULP_I2C_CMD_HDR_NO_PTR(SLAVE_ADDR, 6), 21 | }; 22 | static RTC_DATA_ATTR ulp_var_t last_temp; 23 | 24 | void init_ulp() { 25 | enum { 26 | LABEL_I2C_READ, 27 | LABEL_I2C_READ_RETURN, 28 | LABEL_I2C_WRITE, 29 | LABEL_I2C_WRITE_RETURN, 30 | LABEL_I2C_ERROR, 31 | LABEL_NEGATIVE, 32 | LABEL_WAKE, 33 | }; 34 | 35 | const ulp_insn_t program[] = { 36 | // write i2c to request measurement 37 | I_MOVO(R1, ulp_write_cmd), 38 | M_MOVL(R3, LABEL_I2C_WRITE_RETURN), 39 | M_BX(LABEL_I2C_WRITE), 40 | M_LABEL(LABEL_I2C_WRITE_RETURN), 41 | M_BGE(LABEL_I2C_ERROR, 1), 42 | 43 | // delay 13ms to get result 44 | M_DELAY_US_5000_20000(13000), 45 | 46 | // read i2c 47 | I_MOVO(R1, ulp_read_cmd), 48 | M_MOVL(R3, LABEL_I2C_READ_RETURN), 49 | M_BX(LABEL_I2C_READ), 50 | M_LABEL(LABEL_I2C_READ_RETURN), 51 | M_BGE(LABEL_I2C_ERROR, 1), 52 | 53 | // read first 2 bytes (temperature) to R1 54 | I_MOVI(R1,0), 55 | I_GET(R1, R1, ulp_read_cmd[HULP_I2C_CMD_DATA_OFFSET]), 56 | 57 | // read last_temp to R2 58 | I_MOVI(R2,0), 59 | I_GET(R2, R2, last_temp), 60 | 61 | // get difference and store in R0 62 | I_SUBR(R0, R1, R2), 63 | 64 | // deal with negative 65 | M_BGE(LABEL_NEGATIVE, 0x8000), 66 | 67 | M_BGE(LABEL_WAKE, MAX_TEMP_DIFFERENCE_RAW), 68 | I_HALT(), 69 | 70 | M_LABEL(LABEL_NEGATIVE), 71 | M_BL(LABEL_WAKE, (uint16_t ) (1 - MAX_TEMP_DIFFERENCE_RAW)), 72 | I_HALT(), 73 | 74 | M_LABEL(LABEL_WAKE), 75 | I_WAKE(), 76 | I_HALT(), 77 | 78 | M_LABEL(LABEL_I2C_ERROR), 79 | I_GPIO_SET(LED_ERR, 1), // turn on led 80 | I_END(), // end ulp program so it won't run again 81 | I_HALT(), 82 | 83 | M_INCLUDE_I2CBB_CMD(LABEL_I2C_READ, LABEL_I2C_WRITE, SCL_PIN, SDA_PIN) 84 | }; 85 | 86 | ESP_ERROR_CHECK(hulp_configure_pin(SCL_PIN, RTC_GPIO_MODE_INPUT_ONLY, GPIO_FLOATING, 0)); 87 | ESP_ERROR_CHECK(hulp_configure_pin(SDA_PIN, RTC_GPIO_MODE_INPUT_ONLY, GPIO_FLOATING, 0)); 88 | ESP_ERROR_CHECK(hulp_configure_pin(LED_ERR, RTC_GPIO_MODE_OUTPUT_ONLY, GPIO_PULLDOWN_ONLY, 0)); 89 | 90 | hulp_peripherals_on(); 91 | 92 | vTaskDelay(1000 / portTICK_PERIOD_MS); 93 | 94 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), 10ULL * 1000 * 1000, 0)); 95 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 96 | } 97 | 98 | /** 99 | * formulas for conversion of the sensor signals, optimized for fixed point 100 | * algebra: 101 | * Temperature = 175 * S_T / 2^16 - 45 102 | * Relative Humidity = 100 * S_RH / 2^16 103 | */ 104 | int32_t word_to_temperature(uint16_t raw) { 105 | return ((21875 * (int32_t) raw) >> 13) - 45000; 106 | } 107 | 108 | int32_t word_to_humidity(uint16_t raw) { 109 | return ((12500 * (int32_t) raw) >> 13); 110 | } 111 | 112 | void print_result() { 113 | const uint16_t temp = ulp_read_cmd[HULP_I2C_CMD_DATA_OFFSET].val; 114 | const uint16_t hum_msb = ulp_read_cmd[HULP_I2C_CMD_DATA_OFFSET + 1].val; 115 | const uint16_t hum_lsb = ulp_read_cmd[HULP_I2C_CMD_DATA_OFFSET + 2].val; 116 | const uint16_t hum = (hum_msb << 8) | (hum_lsb >> 8); 117 | const double temp_c = word_to_temperature(temp) / 1000.0; 118 | const double last_temp_c = word_to_temperature(last_temp.val) / 1000.0; 119 | const double hum_rh = word_to_humidity(hum) / 1000.0; 120 | 121 | ESP_LOGI(TAG, "temp: %.1f, last_temp: %.1f, difference: %.2f, hum: %.1f", 122 | temp_c, last_temp_c, temp_c - last_temp_c, hum_rh); 123 | 124 | // would normally be done after sending e.g. using ESP-NOW 125 | last_temp.val = temp; 126 | } 127 | 128 | extern "C" void app_main(void) { 129 | 130 | if (hulp_is_deep_sleep_wakeup()) { 131 | print_result(); 132 | } else { 133 | init_ulp(); 134 | } 135 | 136 | ESP_LOGI(TAG, "Sleeping"); 137 | hulp_peripherals_on(); 138 | esp_sleep_enable_ulp_wakeup(); 139 | esp_deep_sleep_start(); 140 | } 141 | -------------------------------------------------------------------------------- /examples/I2C/Bitbang/Cmd/SHT3X/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 3 | -------------------------------------------------------------------------------- /examples/I2C/Bitbang/MultiRead/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_i2cbb_multi) -------------------------------------------------------------------------------- /examples/I2C/Bitbang/MultiRead/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/I2C/Bitbang/MultiRead/main/main.cpp: -------------------------------------------------------------------------------- 1 | #include "freertos/FreeRTOS.h" 2 | #include "freertos/task.h" 3 | #include "driver/rtc_io.h" 4 | #include "driver/gpio.h" 5 | #include "soc/rtc.h" 6 | 7 | #include "hulp.h" 8 | #include "hulp_i2cbb.h" 9 | 10 | #define SCL_PIN GPIO_NUM_14 11 | #define SDA_PIN GPIO_NUM_13 12 | 13 | #define SLAVE1_ADDR 0x12 14 | #define SLAVE1_SUBADDR 0x0 15 | #define SLAVE1_INTERVAL_MS 1000 16 | 17 | #define SLAVE2_ADDR 0x34 18 | #define SLAVE2_SUBADDR 0x0 19 | #define SLAVE2_INTERVAL_MS 2500 20 | 21 | RTC_DATA_ATTR ulp_var_t ulp_data1; 22 | RTC_DATA_ATTR ulp_var_t ulp_data2; 23 | RTC_DATA_ATTR ulp_var_t ulp_nacks; 24 | RTC_DATA_ATTR ulp_var_t ulp_buserrors; 25 | 26 | void init_ulp() 27 | { 28 | enum { 29 | LBL_SLAVE1_INTERVAL, 30 | LBL_SLAVE1_RETURN, 31 | 32 | LBL_SLAVE2_INTERVAL, 33 | LBL_SLAVE2_RETURN, 34 | 35 | LBL_HALT, 36 | 37 | LBL_I2C_READ_ENTRY, 38 | LBL_I2C_WRITE_ENTRY, 39 | LBL_I2C_NACK, 40 | LBL_I2C_ARBLOST, 41 | }; 42 | 43 | const ulp_insn_t program[] = { 44 | I_MOVI(R2,0), 45 | 46 | M_UPDATE_TICKS(), 47 | 48 | M_IF_MS_ELAPSED(LBL_SLAVE1_INTERVAL, SLAVE1_INTERVAL_MS, LBL_SLAVE2_INTERVAL), 49 | I_I2CBB_SET_SLAVE(SLAVE1_ADDR), 50 | M_I2CBB_RD(LBL_SLAVE1_RETURN, LBL_I2C_READ_ENTRY, SLAVE1_SUBADDR), 51 | I_PUT(R0, R2, ulp_data1), 52 | I_WAKE(), 53 | 54 | M_IF_MS_ELAPSED(LBL_SLAVE2_INTERVAL, SLAVE2_INTERVAL_MS, LBL_HALT), 55 | I_I2CBB_SET_SLAVE(SLAVE2_ADDR), 56 | M_I2CBB_RD(LBL_SLAVE2_RETURN, LBL_I2C_READ_ENTRY, SLAVE2_SUBADDR), 57 | I_PUT(R0, R2, ulp_data2), 58 | I_WAKE(), 59 | 60 | M_LABEL(LBL_HALT), 61 | I_HALT(), 62 | 63 | M_LABEL(LBL_I2C_NACK), 64 | I_GET(R0, R2, ulp_nacks), 65 | I_ADDI(R0, R0, 1), 66 | I_PUT(R0, R2, ulp_nacks), 67 | I_WAKE(), 68 | I_BXR(R3), 69 | 70 | M_LABEL(LBL_I2C_ARBLOST), 71 | I_GET(R0, R2, ulp_buserrors), 72 | I_ADDI(R0, R0, 1), 73 | I_PUT(R0, R2, ulp_buserrors), 74 | I_WAKE(), 75 | I_BXR(R3), 76 | 77 | M_INCLUDE_I2CBB_MULTI(LBL_I2C_READ_ENTRY, LBL_I2C_WRITE_ENTRY, LBL_I2C_ARBLOST, LBL_I2C_NACK, SCL_PIN, SDA_PIN), 78 | }; 79 | 80 | ESP_ERROR_CHECK(hulp_configure_pin(SCL_PIN, RTC_GPIO_MODE_INPUT_ONLY, GPIO_FLOATING, 0)); 81 | ESP_ERROR_CHECK(hulp_configure_pin(SDA_PIN, RTC_GPIO_MODE_INPUT_ONLY, GPIO_FLOATING, 0)); 82 | 83 | hulp_peripherals_on(); 84 | 85 | vTaskDelay(1000 / portTICK_PERIOD_MS); 86 | 87 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), 1ULL * 50 * 1000, 0)); 88 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 89 | } 90 | 91 | void ulp_isr(void *task_handle_ptr) 92 | { 93 | xTaskNotifyFromISR(*(TaskHandle_t*)task_handle_ptr, 0, eNoAction, NULL); 94 | } 95 | 96 | extern "C" void app_main() 97 | { 98 | // ULP will trigger an interrupt when there's new data or an error 99 | TaskHandle_t main_handle = xTaskGetCurrentTaskHandle(); 100 | hulp_ulp_isr_register(&ulp_isr, &main_handle); 101 | hulp_ulp_interrupt_en(); 102 | 103 | init_ulp(); 104 | 105 | for(;;) 106 | { 107 | // Wait for interrupt 108 | xTaskNotifyWait(0, 0, NULL, portMAX_DELAY); 109 | printf("Data1: %u, Data2: %u, NACK Errors: %u, Bus Errors: %u", ulp_data1.val, ulp_data2.val, ulp_nacks.val, ulp_buserrors.val); 110 | } 111 | } -------------------------------------------------------------------------------- /examples/I2C/Bitbang/MultiRead/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 -------------------------------------------------------------------------------- /examples/I2C/Bitbang/Single/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_i2cbb_single) -------------------------------------------------------------------------------- /examples/I2C/Bitbang/Single/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/I2C/Bitbang/Single/main/main.cpp: -------------------------------------------------------------------------------- 1 | #include "freertos/FreeRTOS.h" 2 | #include "freertos/task.h" 3 | #include "driver/rtc_io.h" 4 | #include "driver/gpio.h" 5 | #include "soc/rtc.h" 6 | 7 | #include "hulp.h" 8 | #include "hulp_i2cbb.h" 9 | 10 | #define SCL_PIN GPIO_NUM_14 11 | #define SDA_PIN GPIO_NUM_13 12 | 13 | #define SLAVE_ADDR 0x0 14 | 15 | // Set for 8-bit read: 16 | // #define SLAVE_READ8_SUBADDR 0x0 17 | 18 | // Set for 16-bit read: 19 | // #define SLAVE_READ16_SUBADDR 0x0 20 | 21 | // Set subaddress and value for write: 22 | // #define SLAVE_WRITE_SUBADDR 0x0 23 | // #define SLAVE_WRITE_VALUE 0x0 24 | 25 | RTC_DATA_ATTR ulp_var_t ulp_data8; 26 | RTC_DATA_ATTR ulp_var_t ulp_data16; 27 | RTC_DATA_ATTR ulp_var_t ulp_nacks; 28 | RTC_DATA_ATTR ulp_var_t ulp_buserrors; 29 | 30 | void init_ulp() 31 | { 32 | enum { 33 | LBL_READ8_RETURN, 34 | LBL_READ16_RETURN, 35 | LBL_WRITE_RETURN, 36 | 37 | LBL_HALT, 38 | 39 | LBL_I2C_READ_ENTRY, 40 | LBL_I2C_WRITE_ENTRY, 41 | LBL_I2C_NACK, 42 | LBL_I2C_ARBLOST, 43 | }; 44 | 45 | const ulp_insn_t program[] = { 46 | I_MOVI(R2,0), 47 | 48 | #ifdef SLAVE_READ8_SUBADDR 49 | M_I2CBB_RD(LBL_READ8_RETURN, LBL_I2C_READ_ENTRY, SLAVE_READ8_SUBADDR), 50 | I_PUT(R0, R2, ulp_data8), 51 | I_WAKE(), 52 | #endif 53 | 54 | #ifdef SLAVE_READ16_SUBADDR 55 | M_I2CBB_RD(LBL_READ16_RETURN, LBL_I2C_READ_ENTRY, SLAVE_READ16_SUBADDR), 56 | I_PUT(R0, R2, ulp_data16), 57 | I_WAKE(), 58 | #endif 59 | 60 | #ifdef SLAVE_WRITE_SUBADDR 61 | M_I2CBB_WR(LBL_WRITE_RETURN, LBL_I2C_WRITE_ENTRY, SLAVE_WRITE_SUBADDR, SLAVE_WRITE_VALUE), 62 | #endif 63 | 64 | I_HALT(), 65 | 66 | M_LABEL(LBL_I2C_NACK), 67 | I_GET(R0, R2, ulp_nacks), 68 | I_ADDI(R0,R0,1), 69 | I_PUT(R0,R2, ulp_nacks), 70 | I_WAKE(), 71 | I_BXR(R3), 72 | 73 | M_LABEL(LBL_I2C_ARBLOST), 74 | I_GET(R0, R2, ulp_buserrors), 75 | I_ADDI(R0,R0,1), 76 | I_PUT(R0,R2, ulp_buserrors), 77 | I_WAKE(), 78 | I_BXR(R3), 79 | 80 | M_INCLUDE_I2CBB(LBL_I2C_READ_ENTRY, LBL_I2C_WRITE_ENTRY, LBL_I2C_ARBLOST, LBL_I2C_NACK, SCL_PIN, SDA_PIN, SLAVE_ADDR), 81 | }; 82 | 83 | ESP_ERROR_CHECK(hulp_configure_pin(SCL_PIN, RTC_GPIO_MODE_INPUT_ONLY, GPIO_FLOATING, 0)); 84 | ESP_ERROR_CHECK(hulp_configure_pin(SDA_PIN, RTC_GPIO_MODE_INPUT_ONLY, GPIO_FLOATING, 0)); 85 | 86 | hulp_peripherals_on(); 87 | 88 | vTaskDelay(1000 / portTICK_PERIOD_MS); 89 | 90 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), 1ULL * 1000 * 1000, 0)); 91 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 92 | } 93 | 94 | void ulp_isr(void *task_handle_ptr) 95 | { 96 | xTaskNotifyFromISR(*(TaskHandle_t*)task_handle_ptr, 0, eNoAction, NULL); 97 | } 98 | 99 | extern "C" void app_main() 100 | { 101 | // ULP will trigger an interrupt when there's new data or an error 102 | TaskHandle_t main_handle = xTaskGetCurrentTaskHandle(); 103 | hulp_ulp_isr_register(&ulp_isr, &main_handle); 104 | hulp_ulp_interrupt_en(); 105 | 106 | init_ulp(); 107 | 108 | for(;;) 109 | { 110 | // Wait for interrupt 111 | xTaskNotifyWait(0, 0, NULL, portMAX_DELAY); 112 | printf("Read8: %u, Read16: %u, NACK Errors: %u, Bus Errors: %u\n", ulp_data8.val, ulp_data16.val, ulp_nacks.val, ulp_buserrors.val); 113 | } 114 | } -------------------------------------------------------------------------------- /examples/I2C/Bitbang/Single/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 -------------------------------------------------------------------------------- /examples/I2C/Hardware/Read/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_i2cbb_multi) -------------------------------------------------------------------------------- /examples/I2C/Hardware/Read/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/I2C/Hardware/Read/main/main.cpp: -------------------------------------------------------------------------------- 1 | #include "sdkconfig.h" 2 | 3 | #include "freertos/FreeRTOS.h" 4 | #include "freertos/task.h" 5 | #include "driver/rtc_io.h" 6 | #include "driver/gpio.h" 7 | #include "soc/rtc.h" 8 | 9 | #include "hulp.h" 10 | 11 | #define ULP_WAKEUP_INTERVAL_MS 1000 12 | 13 | //SCL = 2 or 4 14 | #define SCL_PIN GPIO_NUM_4 15 | //SDA = 0 or 15 16 | #define SDA_PIN GPIO_NUM_15 17 | 18 | #define SLAVE1_ADDR 0x0 19 | #define SLAVE1_SUBADDR 0x0 20 | 21 | // #define SLAVE2_ADDR 0x0 22 | // #define SLAVE2_SUBADDR 0x0 23 | 24 | RTC_DATA_ATTR ulp_var_t ulp_data1; 25 | RTC_DATA_ATTR ulp_var_t ulp_data2; 26 | 27 | void init_ulp() 28 | { 29 | const ulp_insn_t program[] = { 30 | I_MOVI(R2,0), 31 | 32 | I_I2C_READ(0, SLAVE1_SUBADDR), 33 | I_PUT(R0,R2,ulp_data1), 34 | 35 | #ifdef SLAVE2_ADDR 36 | I_I2C_READ(1, SLAVE2_SUBADDR), 37 | I_PUT(R0,R2,ulp_data2), 38 | #endif 39 | 40 | I_HALT(), 41 | }; 42 | 43 | 44 | hulp_register_i2c_slave(0, SLAVE1_ADDR); 45 | #ifdef SLAVE2_ADDR 46 | hulp_register_i2c_slave(1, SLAVE2_ADDR); 47 | #endif 48 | 49 | ESP_ERROR_CHECK(hulp_configure_i2c_pins(SCL_PIN, SDA_PIN, true, true)); 50 | 51 | const hulp_i2c_controller_config_t config = HULP_I2C_CONTROLLER_CONFIG_DEFAULT(); 52 | ESP_ERROR_CHECK(hulp_configure_i2c_controller(&config)); 53 | 54 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), ULP_WAKEUP_INTERVAL_MS * 1000, 0)); 55 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 56 | } 57 | 58 | extern "C" void app_main() 59 | { 60 | init_ulp(); 61 | 62 | for(;;) 63 | { 64 | vTaskDelay(ULP_WAKEUP_INTERVAL_MS / portTICK_PERIOD_MS); 65 | printf("Slave 1 Data: %u\n", ulp_data1.val); 66 | printf("Slave 2 Data: %u\n", ulp_data2.val); 67 | } 68 | } -------------------------------------------------------------------------------- /examples/I2C/Hardware/Read/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 -------------------------------------------------------------------------------- /examples/Interrupt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_interrupt) -------------------------------------------------------------------------------- /examples/Interrupt/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/Interrupt/main/main.cpp: -------------------------------------------------------------------------------- 1 | /* HULP RTCIO Interrupt Example 2 | 3 | Demonstrates the use of RTCIO interrupts to detect changes in pin state, even while the ULP is sleeping or busy. This also 4 | allows handling short pulses which would otherwise be difficult to detect using the ULP. 5 | 6 | The interrupt is processed the next time the ULP wakes (every 500ms here), toggling an LED if triggered in the previous 7 | interval. 8 | */ 9 | #include "esp_sleep.h" 10 | 11 | #include "hulp.h" 12 | 13 | #define BUTTON_PIN GPIO_NUM_0 14 | #define LED_PIN GPIO_NUM_26 15 | 16 | #define ULP_INTERVAL_MS 500 17 | 18 | void init_ulp() 19 | { 20 | enum { 21 | LAB_INTERRUPT_TRIGGERED, 22 | }; 23 | 24 | const ulp_insn_t program[] = { 25 | //Read the interrupt bit 26 | I_GPIO_INT_RD(BUTTON_PIN), 27 | //If it's set, goto LAB_INTERRUPT_TRIGGERED 28 | M_BGE(LAB_INTERRUPT_TRIGGERED, 1), 29 | //Else turn off the LED and HALT 30 | I_GPIO_SET(LED_PIN, 0), 31 | I_HALT(), 32 | M_LABEL(LAB_INTERRUPT_TRIGGERED), 33 | //Clear the interrupt bit 34 | I_GPIO_INT_CLR(BUTTON_PIN), 35 | //Turn on the LED and halt 36 | I_GPIO_SET(LED_PIN, 1), 37 | I_HALT(), 38 | }; 39 | 40 | ESP_ERROR_CHECK(hulp_configure_pin(BUTTON_PIN, RTC_GPIO_MODE_INPUT_ONLY, GPIO_PULLUP_ONLY, 0)); 41 | ESP_ERROR_CHECK(hulp_configure_pin(LED_PIN, RTC_GPIO_MODE_OUTPUT_ONLY, GPIO_FLOATING, 0)); 42 | ESP_ERROR_CHECK(hulp_configure_pin_int(BUTTON_PIN, GPIO_INTR_ANYEDGE)); 43 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), ULP_INTERVAL_MS * 1000, 0)); 44 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 45 | } 46 | 47 | extern "C" void app_main(void) 48 | { 49 | init_ulp(); 50 | hulp_peripherals_on(); 51 | esp_deep_sleep_start(); 52 | } 53 | -------------------------------------------------------------------------------- /examples/Interrupt/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 -------------------------------------------------------------------------------- /examples/Reg_Write/Demo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_regwr_demo) -------------------------------------------------------------------------------- /examples/Reg_Write/Demo/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.c" 3 | INCLUDE_DIRS "" 4 | ) -------------------------------------------------------------------------------- /examples/Reg_Write/Demo/main/main.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Demonstrates using the ULP to write registers using the value in R2 into any register, able to be changed at runtime. 3 | * 4 | * Limitations: 5 | * Up to 6 bits may be written from the MSB of R2 6 | * Valid low bits are 0, 8, 16, 24 7 | * If more than 6 bits are to be written, the instruction will pad with zeros the bits above the sixth bit 8 | * Some very specific instructions must be set at very specific places. 9 | * These are at very high offsets (~830 and ~1024). Ensure reserved ULP mem is >= 4108 bytes. 10 | * Each combination of high:low requires 3 instructions reserved at a particular place as its 'work area'. 11 | * These are all contained between HULP_REGWR_WORK_AREA_START and HULP_REGWR_WORK_AREA_END (approx words 128-256). 12 | * For example, if you write [3:1] of any register at any time then it will require HULP_REGWR_WORK_OFFSET(1,3) to HULP_REGWR_WORK_OFFSET(1,3)+2 13 | * If you do not use a combination then you are free to use that work area as normal. 14 | * It is up to you to ensure that nothing else is placed in work areas. 15 | * 16 | */ 17 | 18 | #include "freertos/FreeRTOS.h" 19 | #include "freertos/task.h" 20 | #include "esp_log.h" 21 | 22 | #include "hulp.h" 23 | #include "hulp_regwr.h" 24 | 25 | #include "sdkconfig.h" 26 | 27 | static const char* TAG = "HULP_REGWR"; 28 | 29 | // Pick a test register for this example. 30 | // SENS_ULP_CP_SLEEP_CYC2_REG is unused in this application so there's no harm writing anything to it. 31 | #define ULP_WR_TEST_REG SENS_ULP_CP_SLEEP_CYC2_REG 32 | 33 | static void init_ulp() 34 | { 35 | // Load special instructions to return to your program from any register write 36 | ESP_ERROR_CHECK(hulp_regwr_load_generate_ret()); 37 | // Load special instructions to generate variable register writes at run time 38 | ESP_ERROR_CHECK(hulp_regwr_load_generate_wr()); 39 | 40 | enum { 41 | LBL_INIT_DONE, 42 | LBL_REGWR_RETURN, 43 | }; 44 | 45 | const ulp_insn_t program[] = { 46 | // Initialise some values 47 | // R2 is used for the register, and the 6 bit value. 48 | // Initialise the value to 0 here, and set the register. Of course any of this can be changed at run time. 49 | I_MOVI(R2, HULP_REGWR_IMM_VAL(ULP_WR_TEST_REG, 0)), 50 | // The address of the work area for the particular combination of high:low must be set in R0 51 | // In this example, we're writing 6 bits [5:0] so it looks like this: 52 | I_MOVI(R0, HULP_REGWR_WORK_OFFSET(0, 5)), 53 | // Where to return to after the register write 54 | M_MOVL(R3, LBL_REGWR_RETURN), 55 | // Do that once, and now set the entry point for next run to here: 56 | M_LABEL(LBL_INIT_DONE), 57 | M_SET_ENTRY_LBL(LBL_INIT_DONE, program), 58 | 59 | // Loop: 60 | // The value that will be written to the register is in the upper 6 bits of R2. So to increment by one, add (1 << HULP_REGWR_VAL_SHIFT) 61 | I_ADDI(R2, R2, 1 << HULP_REGWR_VAL_SHIFT), 62 | // Branch to this address to do the configured register write. 63 | // It will ensure return instruction exists, generate register write instruction, execute the register write, and return to R3 64 | I_BXI(HULP_WR_REG_GEN_ENTRY), 65 | M_LABEL(LBL_REGWR_RETURN), 66 | // Returns here. 67 | I_HALT(), 68 | }; 69 | 70 | // Clear whatever is in our test register 71 | REG_WRITE(ULP_WR_TEST_REG, 0); 72 | 73 | ESP_ERROR_CHECK( hulp_ulp_load(program, sizeof(program), 1 * 1000 * 1000, 0) ); 74 | ESP_ERROR_CHECK( hulp_ulp_run(0) ); 75 | 76 | // Print something every time the ULP writes to the register 77 | uint32_t reg_current = 0; 78 | for(;;) 79 | { 80 | uint32_t reg_new = REG_READ(ULP_WR_TEST_REG); 81 | if(reg_new != reg_current) 82 | { 83 | reg_current = reg_new; 84 | ESP_LOGI(TAG, "ULP Wrote: %u", reg_current); 85 | } 86 | vTaskDelay(1); 87 | } 88 | } 89 | 90 | void app_main(void) 91 | { 92 | init_ulp(); 93 | vTaskDelete(NULL); 94 | } -------------------------------------------------------------------------------- /examples/Reg_Write/Demo/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=4108 -------------------------------------------------------------------------------- /examples/Temperature/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_temperature) -------------------------------------------------------------------------------- /examples/Temperature/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/Temperature/main/main.cpp: -------------------------------------------------------------------------------- 1 | #include "freertos/FreeRTOS.h" 2 | #include "freertos/task.h" 3 | #include "soc/rtc.h" 4 | 5 | #include "hulp.h" 6 | 7 | #define MEAS_INTERVAL_MS 1000 8 | 9 | RTC_DATA_ATTR ulp_var_t ulp_tsens_val; 10 | 11 | void init_ulp() 12 | { 13 | const ulp_insn_t program[] = { 14 | I_TSENS(R0, 1000), 15 | I_MOVI(R2,0), 16 | I_PUT(R0, R2, ulp_tsens_val), 17 | I_HALT(), 18 | }; 19 | 20 | hulp_tsens_configure(3); 21 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), MEAS_INTERVAL_MS * 1000, 0)); 22 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 23 | } 24 | 25 | extern "C" void app_main() 26 | { 27 | init_ulp(); 28 | 29 | for(;;) 30 | { 31 | vTaskDelay(MEAS_INTERVAL_MS / portTICK_PERIOD_MS); 32 | printf("TSENS: %u\n", ulp_tsens_val.val); 33 | } 34 | } -------------------------------------------------------------------------------- /examples/Temperature/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 -------------------------------------------------------------------------------- /examples/Timing/LEDToggle/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_timing_leds) -------------------------------------------------------------------------------- /examples/Timing/LEDToggle/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/Timing/LEDToggle/main/main.cpp: -------------------------------------------------------------------------------- 1 | #include "freertos/FreeRTOS.h" 2 | #include "freertos/task.h" 3 | #include "driver/rtc_io.h" 4 | #include "driver/gpio.h" 5 | #include "soc/rtc.h" 6 | #include "esp_sleep.h" 7 | 8 | #include "hulp.h" 9 | 10 | #define PIN_LED1 GPIO_NUM_12 11 | #define LED1_TOGGLE_MS 250 12 | #define PIN_LED2 GPIO_NUM_13 13 | #define LED2_TOGGLE_MS 500 14 | #define PIN_LED3 GPIO_NUM_14 15 | #define LED3_TOGGLE_MS 1000 16 | #define PIN_LED4 GPIO_NUM_15 17 | #define LED4_TOGGLE_MS 5000 18 | 19 | void init_ulp() 20 | { 21 | enum { 22 | LBL_INTERVAL1, 23 | LBL_INTERVAL2, 24 | LBL_INTERVAL3, 25 | LBL_INTERVAL4, 26 | LBL_HALT, 27 | }; 28 | 29 | const ulp_insn_t program[] = { 30 | I_MOVI(R2,0), 31 | 32 | M_UPDATE_TICKS(), 33 | 34 | M_IF_MS_ELAPSED(LBL_INTERVAL1,LED1_TOGGLE_MS,LBL_INTERVAL2), 35 | M_GPIO_TOGGLE(PIN_LED1), 36 | 37 | M_IF_MS_ELAPSED(LBL_INTERVAL2,LED2_TOGGLE_MS,LBL_INTERVAL3), 38 | M_GPIO_TOGGLE(PIN_LED2), 39 | 40 | M_IF_MS_ELAPSED(LBL_INTERVAL3,LED3_TOGGLE_MS,LBL_INTERVAL4), 41 | M_GPIO_TOGGLE(PIN_LED3), 42 | 43 | M_IF_MS_ELAPSED(LBL_INTERVAL4,LED4_TOGGLE_MS,LBL_HALT), 44 | M_GPIO_TOGGLE(PIN_LED4), 45 | 46 | M_LABEL(LBL_HALT), 47 | I_HALT(), 48 | }; 49 | 50 | ESP_ERROR_CHECK(hulp_configure_pin(PIN_LED1, RTC_GPIO_MODE_OUTPUT_ONLY, GPIO_FLOATING, 0)); 51 | ESP_ERROR_CHECK(hulp_configure_pin(PIN_LED2, RTC_GPIO_MODE_OUTPUT_ONLY, GPIO_FLOATING, 0)); 52 | ESP_ERROR_CHECK(hulp_configure_pin(PIN_LED3, RTC_GPIO_MODE_OUTPUT_ONLY, GPIO_FLOATING, 0)); 53 | ESP_ERROR_CHECK(hulp_configure_pin(PIN_LED4, RTC_GPIO_MODE_OUTPUT_ONLY, GPIO_FLOATING, 0)); 54 | 55 | hulp_peripherals_on(); 56 | 57 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), 1ULL * 10 * 1000, 0)); 58 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 59 | } 60 | 61 | extern "C" void app_main() 62 | { 63 | init_ulp(); 64 | esp_deep_sleep_start(); 65 | } -------------------------------------------------------------------------------- /examples/Timing/LEDToggle/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 -------------------------------------------------------------------------------- /examples/Touch/ThresholdWake/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_touch_wakeup) -------------------------------------------------------------------------------- /examples/Touch/ThresholdWake/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/Touch/ThresholdWake/main/main.cpp: -------------------------------------------------------------------------------- 1 | #include "freertos/FreeRTOS.h" 2 | #include "freertos/task.h" 3 | #include "driver/rtc_io.h" 4 | #include "driver/gpio.h" 5 | #include "soc/rtc.h" 6 | #include "esp_sleep.h" 7 | 8 | #include "hulp.h" 9 | #include "hulp_touch.h" 10 | 11 | #define PIN_TOUCH GPIO_NUM_32 12 | 13 | #define ULP_TOUCH_INTERVAL_MS 200 14 | //The read value will be printed out for a few seconds on reset/wake. Adjust this threshold accordingly: 15 | #define ULP_TOUCH_WAKEUP_THRESHOLD 250 16 | 17 | RTC_DATA_ATTR ulp_var_t ulp_touch_val; 18 | 19 | void init_ulp() 20 | { 21 | enum { 22 | LBL_TOUCH_INTERVAL, 23 | LBL_HALT, 24 | }; 25 | 26 | const ulp_insn_t program[] = { 27 | 28 | M_UPDATE_TICKS(), 29 | 30 | M_IF_MS_ELAPSED(LBL_TOUCH_INTERVAL, ULP_TOUCH_INTERVAL_MS, LBL_HALT), 31 | //Begin a new touch read 32 | M_TOUCH_BEGIN(), 33 | //Wait for it to complete 34 | M_TOUCH_WAIT_DONE(), 35 | //Get the value for selected pin 36 | I_TOUCH_GET_GPIO_VALUE(PIN_TOUCH), 37 | //Update the variable with the new value 38 | I_MOVI(R2, 0), 39 | I_PUT(R0, R2, ulp_touch_val), 40 | //If it's higher than threshold, go to halt; else trigger a wake 41 | M_BGE(LBL_HALT, ULP_TOUCH_WAKEUP_THRESHOLD), 42 | I_WAKE(), 43 | 44 | M_LABEL(LBL_HALT), 45 | I_HALT(), 46 | }; 47 | 48 | const hulp_touch_controller_config_t controller_config = HULP_TOUCH_CONTROLLER_CONFIG_DEFAULT(); 49 | ESP_ERROR_CHECK(hulp_configure_touch_controller(&controller_config)); 50 | 51 | const hulp_touch_pin_config_t pin_config = HULP_TOUCH_PIN_CONFIG_DEFAULT(); 52 | ESP_ERROR_CHECK(hulp_configure_touch_pin(PIN_TOUCH, &pin_config)); 53 | 54 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), 1ULL * 10 * 1000, 0)); 55 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 56 | } 57 | 58 | extern "C" void app_main() 59 | { 60 | 61 | if(!hulp_is_ulp_wakeup()) 62 | { 63 | init_ulp(); 64 | } 65 | 66 | TickType_t start_time = xTaskGetTickCount(); 67 | while(xTaskGetTickCount() - start_time < (5000 / portTICK_PERIOD_MS)) 68 | { 69 | printf("ulp_touch_val: %u\n", ulp_touch_val.val); 70 | vTaskDelay(ULP_TOUCH_INTERVAL_MS / portTICK_PERIOD_MS); 71 | } 72 | 73 | esp_sleep_enable_ulp_wakeup(); 74 | esp_deep_sleep_start(); 75 | } -------------------------------------------------------------------------------- /examples/Touch/ThresholdWake/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 -------------------------------------------------------------------------------- /examples/UART/Greeting/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_uart_greeting) -------------------------------------------------------------------------------- /examples/UART/Greeting/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/UART/Greeting/main/main.cpp: -------------------------------------------------------------------------------- 1 | #include "freertos/FreeRTOS.h" 2 | #include "freertos/task.h" 3 | #include "freertos/event_groups.h" 4 | #include "driver/rtc_io.h" 5 | #include "driver/gpio.h" 6 | #include "soc/rtc.h" 7 | #include "esp_log.h" 8 | 9 | #include "hulp.h" 10 | #include "hulp_uart.h" 11 | 12 | #include "sdkconfig.h" 13 | 14 | #define PIN_ULP_TX GPIO_NUM_25 15 | #define PIN_ULP_RX GPIO_NUM_26 16 | 17 | #define BAUD_RATE 115200 18 | 19 | #define ULP_STRING_GREETING "Hello, what's your name?\n" 20 | #define ULP_STRING_REPLY_START "Nice to meet you, " 21 | #define ULP_STRING_REPLY_END "! I'm a ULP Coprocessor.\n" 22 | #define ULP_RX_MAX_LEN 32 23 | 24 | RTC_DATA_ATTR ulp_var_t ulp_greeting HULP_UART_STRING_RESERVE(ULP_STRING_GREETING); 25 | RTC_DATA_ATTR ulp_var_t ulp_reply_start HULP_UART_STRING_RESERVE(ULP_STRING_REPLY_START); 26 | RTC_DATA_ATTR ulp_var_t ulp_reply_end HULP_UART_STRING_RESERVE(ULP_STRING_REPLY_END); 27 | 28 | RTC_DATA_ATTR ulp_var_t ulp_rx_buffer HULP_UART_STRING_BUFFER(ULP_RX_MAX_LEN); 29 | 30 | #ifndef ARRAY_SIZE 31 | #define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) 32 | #endif 33 | 34 | void init_ulp() 35 | { 36 | enum 37 | { 38 | LBL_TX_GREET, 39 | LBL_RX_RESPONSE, 40 | LBL_TX_REPLY_START, 41 | LBL_TX_RESPONSE, 42 | LBL_TX_REPLY_END, 43 | 44 | LBL_SUBROUTINE_TX_ENTRY, 45 | LBL_SUBROUTINE_RX_ENTRY, 46 | }; 47 | 48 | const ulp_insn_t program[] = { 49 | //Request name 50 | I_MOVO(R1, ulp_greeting), 51 | M_RETURN(LBL_TX_GREET, R3, LBL_SUBROUTINE_TX_ENTRY), 52 | //Read response (name) into buffer 53 | I_MOVO(R1, ulp_rx_buffer), 54 | M_RETURN(LBL_RX_RESPONSE, R3, LBL_SUBROUTINE_RX_ENTRY), 55 | //Start intro 56 | I_MOVO(R1, ulp_reply_start), 57 | M_RETURN(LBL_TX_REPLY_START, R3, LBL_SUBROUTINE_TX_ENTRY), 58 | //Echo the name that was just received 59 | I_MOVO(R1, ulp_rx_buffer), 60 | M_RETURN(LBL_TX_RESPONSE, R3, LBL_SUBROUTINE_TX_ENTRY), 61 | //End intro 62 | I_MOVO(R1, ulp_reply_end), 63 | M_RETURN(LBL_TX_REPLY_END, R3, LBL_SUBROUTINE_TX_ENTRY), 64 | //Sleep 65 | I_WAKE(), 66 | I_HALT(), 67 | 68 | //Dependencies for UART 69 | M_INCLUDE_UART_TX(LBL_SUBROUTINE_TX_ENTRY, BAUD_RATE, PIN_ULP_TX), 70 | M_INCLUDE_UART_RX(LBL_SUBROUTINE_RX_ENTRY, BAUD_RATE, PIN_ULP_RX, '\n'), 71 | }; 72 | 73 | // Set the contents of string buffers 74 | if( 75 | hulp_uart_string_set(ulp_greeting, ARRAY_SIZE(ulp_greeting), ULP_STRING_GREETING) < 0 || 76 | hulp_uart_string_set(ulp_reply_start, ARRAY_SIZE(ulp_reply_start), ULP_STRING_REPLY_START) < 0 || 77 | hulp_uart_string_set(ulp_reply_end, ARRAY_SIZE(ulp_reply_end), ULP_STRING_REPLY_END) < 0 78 | ) 79 | { 80 | abort(); 81 | } 82 | 83 | #if CONFIG_HULP_UART_TX_OD 84 | ESP_ERROR_CHECK(hulp_configure_pin(PIN_ULP_TX, RTC_GPIO_MODE_DISABLED, GPIO_PULLUP_ONLY, 0)); 85 | #else 86 | ESP_ERROR_CHECK(hulp_configure_pin(PIN_ULP_TX, RTC_GPIO_MODE_OUTPUT_ONLY, GPIO_FLOATING, 1)); 87 | #endif 88 | ESP_ERROR_CHECK(hulp_configure_pin(PIN_ULP_RX, RTC_GPIO_MODE_INPUT_ONLY, GPIO_PULLUP_ONLY, 0)); 89 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), 10ULL * 1000 * 1000, 0)); 90 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 91 | } 92 | 93 | void ulp_isr(void *task_handle_ptr) 94 | { 95 | xTaskNotifyFromISR(*(TaskHandle_t*)task_handle_ptr, 0, eNoAction, NULL); 96 | } 97 | 98 | extern "C" void app_main() 99 | { 100 | // ULP will trigger an interrupt when a new UART string is received. Set up here. 101 | TaskHandle_t main_handle = xTaskGetCurrentTaskHandle(); 102 | hulp_ulp_isr_register(&ulp_isr, &main_handle); 103 | hulp_ulp_interrupt_en(); 104 | 105 | // Load and start the program. 106 | init_ulp(); 107 | 108 | for (;;) 109 | { 110 | // Block until notified by ULP+ISR 111 | xTaskNotifyWait(0, 0, NULL, portMAX_DELAY); 112 | 113 | // Get the contents of the buffer and display 114 | char name[ULP_RX_MAX_LEN + 1]; 115 | int len = hulp_uart_string_get(ulp_rx_buffer, name, sizeof(name), false); 116 | if(len < 0) 117 | { 118 | abort(); 119 | } 120 | printf("ULP RX String (%d): %s\n", len, name); 121 | } 122 | } -------------------------------------------------------------------------------- /examples/UART/Greeting/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512 -------------------------------------------------------------------------------- /examples/UART/Printf_Touch/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # The following lines of boilerplate have to be in your project's 2 | # CMakeLists in this exact order for cmake to work correctly 3 | cmake_minimum_required(VERSION 3.5) 4 | 5 | set(EXTRA_COMPONENT_DIRS "../../../../hulp") 6 | 7 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 8 | project(hulp_example_uart_greeting) -------------------------------------------------------------------------------- /examples/UART/Printf_Touch/main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register( 2 | SRCS "main.cpp" 3 | INCLUDE_DIRS "" 4 | ) 5 | -------------------------------------------------------------------------------- /examples/UART/Printf_Touch/main/main.cpp: -------------------------------------------------------------------------------- 1 | #include "sdkconfig.h" 2 | 3 | #include "freertos/FreeRTOS.h" 4 | #include "freertos/task.h" 5 | #include "driver/rtc_io.h" 6 | #include "driver/gpio.h" 7 | #include "soc/rtc.h" 8 | #include "esp_sleep.h" 9 | 10 | #include "hulp.h" 11 | #include "hulp_touch.h" 12 | #include "hulp_uart.h" 13 | 14 | #ifndef ARRAY_SIZE 15 | #define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) 16 | #endif 17 | #ifndef MAX 18 | #define MAX(a,b) (((a)>(b))?(a):(b)) 19 | #endif 20 | 21 | #define PIN_TOUCH GPIO_NUM_32 22 | #define TOUCH_HYSTERESIS 1000 23 | 24 | #define PIN_UART_TX GPIO_NUM_25 25 | 26 | #define BAUD_RATE 9600 27 | 28 | #define HULP_PRINTF_X_MIN_CAP 4 29 | #define HULP_PRINTF_U_MIN_CAP 5 30 | 31 | // Need a buffer for formatting hex/dec to ASCII 32 | #define ULP_BUFFER_CAPACITY MAX(HULP_PRINTF_X_MIN_CAP, HULP_PRINTF_U_MIN_CAP) 33 | RTC_DATA_ATTR ulp_var_t ulp_buffer HULP_UART_STRING_BUFFER(ULP_BUFFER_CAPACITY); 34 | 35 | // And some strings to format the output, eg. "Pin 00032: 04779 (0x12AB)\n" 36 | #define ULP_STRING_FMT_1 "Pin " 37 | #define ULP_STRING_FMT_2 ": " 38 | #define ULP_STRING_FMT_3 " (0x" 39 | #define ULP_STRING_FMT_4 ")\n" 40 | #define ULP_STRING_DETECTION "Detection!\n" 41 | 42 | RTC_DATA_ATTR ulp_var_t ulp_str_fmt_1 HULP_UART_STRING_RESERVE(ULP_STRING_FMT_1); 43 | RTC_DATA_ATTR ulp_var_t ulp_str_fmt_2 HULP_UART_STRING_RESERVE(ULP_STRING_FMT_2); 44 | RTC_DATA_ATTR ulp_var_t ulp_str_fmt_3 HULP_UART_STRING_RESERVE(ULP_STRING_FMT_3); 45 | RTC_DATA_ATTR ulp_var_t ulp_str_fmt_4 HULP_UART_STRING_RESERVE(ULP_STRING_FMT_4); 46 | RTC_DATA_ATTR ulp_var_t ulp_str_detection HULP_UART_STRING_RESERVE(ULP_STRING_DETECTION); 47 | 48 | //Somewhere to store the previous touch val for comparison: 49 | RTC_DATA_ATTR ulp_var_t ulp_previous_touch_val; 50 | 51 | void init_ulp() 52 | { 53 | enum 54 | { 55 | LBL_FMT_1, 56 | LBL_PRINTF_PIN_RETURN, 57 | LBL_UART_PIN_RETURN, 58 | LBL_FMT_2, 59 | LBL_PRINTF_DEC_RETURN, 60 | LBL_UART_DEC_RETURN, 61 | LBL_FMT_3, 62 | LBL_PRINTF_HEX_RETURN, 63 | LBL_UART_HEX_RETURN, 64 | LBL_FMT_4, 65 | 66 | LBL_ALERT_OUT_RETURN, 67 | 68 | LBL_FINISHED, 69 | 70 | LBL_UART_TX_ENTRY, 71 | 72 | LBL_PRINTF_DEC_ENTRY, 73 | LBL_PRINTF_HEX_ENTRY, 74 | }; 75 | 76 | const ulp_insn_t program[] = { 77 | // Start a touch measurement and wait until it finishes 78 | M_TOUCH_BEGIN(), 79 | M_TOUCH_WAIT_DONE(), 80 | 81 | // TX the start of the output 82 | I_MOVO(R1, ulp_str_fmt_1), 83 | M_RETURN(LBL_FMT_1, R3, LBL_UART_TX_ENTRY), 84 | 85 | // Format and TX the pin number 86 | // printf needs value in R0 and buffer pointer in R1 87 | I_MOVI(R0, PIN_TOUCH), 88 | I_MOVO(R1, ulp_buffer), 89 | // Go to printf subroutine 90 | M_RETURN(LBL_PRINTF_PIN_RETURN, R3, LBL_PRINTF_DEC_ENTRY), 91 | // And now TX it out over UART (R1 will not be altered by printf, so the buffer pointer is still valid): 92 | M_RETURN(LBL_UART_PIN_RETURN, R3, LBL_UART_TX_ENTRY), 93 | 94 | // TX the next part of the output 95 | I_MOVO(R1, ulp_str_fmt_2), 96 | M_RETURN(LBL_FMT_2, R3, LBL_UART_TX_ENTRY), 97 | 98 | // Format and TX the touch measurement (decimal) 99 | I_TOUCH_GET_GPIO_VALUE(PIN_TOUCH), 100 | I_MOVO(R1, ulp_buffer), 101 | M_RETURN(LBL_PRINTF_DEC_RETURN, R3, LBL_PRINTF_DEC_ENTRY), 102 | M_RETURN(LBL_UART_DEC_RETURN, R3, LBL_UART_TX_ENTRY), 103 | 104 | // TX the next part of the output 105 | I_MOVO(R1, ulp_str_fmt_3), 106 | M_RETURN(LBL_FMT_3, R3, LBL_UART_TX_ENTRY), 107 | 108 | // Format and TX the touch measurement (hex) 109 | I_TOUCH_GET_GPIO_VALUE(PIN_TOUCH), 110 | I_MOVO(R1, ulp_buffer), 111 | M_RETURN(LBL_PRINTF_HEX_RETURN, R3, LBL_PRINTF_HEX_ENTRY), 112 | M_RETURN(LBL_UART_HEX_RETURN, R3, LBL_UART_TX_ENTRY), 113 | 114 | // TX the last part of the output 115 | I_MOVO(R1, ulp_str_fmt_4), 116 | M_RETURN(LBL_FMT_4, R3, LBL_UART_TX_ENTRY), 117 | 118 | 119 | // Now check if this touch < (previous - hysteresis), in which case TX an alert string 120 | I_TOUCH_GET_GPIO_VALUE(PIN_TOUCH), 121 | // Load the previous value into R1 122 | I_MOVI(R2, 0), 123 | I_GET(R1, R2, ulp_previous_touch_val), 124 | // Store it as the 'previous' value for next time 125 | I_PUT(R0, R2, ulp_previous_touch_val), 126 | I_SUBR(R0, R1, R0), // R0 = previous - current 127 | // If that overflowed then current > previous so halt: 128 | M_BXF(LBL_FINISHED), 129 | // Else current <= previous: 130 | I_SUBI(R0, R0, TOUCH_HYSTERESIS), // R0 = difference - hysteresis 131 | // If that overflowed then difference < hysteresis so halt: 132 | M_BXF(LBL_FINISHED), 133 | // Else difference >= hysteresis so alert! 134 | I_MOVO(R1, ulp_str_detection), 135 | M_RETURN(LBL_ALERT_OUT_RETURN, R3, LBL_UART_TX_ENTRY), 136 | 137 | // Sleep 138 | M_LABEL(LBL_FINISHED), 139 | I_HALT(), 140 | 141 | //Include subroutine for UART TX: 142 | M_INCLUDE_UART_TX(LBL_UART_TX_ENTRY, BAUD_RATE, PIN_UART_TX), 143 | 144 | // Include subroutines for printf: 145 | M_INCLUDE_PRINTF_X(LBL_PRINTF_HEX_ENTRY), 146 | // By default, the dec printf will put a newline in the final byte. Useful if you want to dump values without any formatting. 147 | // M_INCLUDE_PRINTF_U(LBL_PRINTF_DEC_ENTRY), 148 | // In this case, we'll leave the last byte unused instead with a custom declaration: 149 | M_INCLUDE_PRINTF_U_(LBL_PRINTF_DEC_ENTRY, R1, R2, R3, 0, '\0'), 150 | }; 151 | 152 | // Set the contents of string buffers 153 | if( 154 | hulp_uart_string_set(ulp_str_fmt_1, ARRAY_SIZE(ulp_str_fmt_1), ULP_STRING_FMT_1) < 0 || 155 | hulp_uart_string_set(ulp_str_fmt_2, ARRAY_SIZE(ulp_str_fmt_2), ULP_STRING_FMT_2) < 0 || 156 | hulp_uart_string_set(ulp_str_fmt_3, ARRAY_SIZE(ulp_str_fmt_3), ULP_STRING_FMT_3) < 0 || 157 | hulp_uart_string_set(ulp_str_fmt_4, ARRAY_SIZE(ulp_str_fmt_4), ULP_STRING_FMT_4) < 0 || 158 | hulp_uart_string_set(ulp_str_detection, ARRAY_SIZE(ulp_str_detection), ULP_STRING_DETECTION) < 0 159 | ) 160 | { 161 | abort(); 162 | } 163 | 164 | const hulp_touch_controller_config_t controller_config = HULP_TOUCH_CONTROLLER_CONFIG_DEFAULT(); 165 | ESP_ERROR_CHECK(hulp_configure_touch_controller(&controller_config)); 166 | 167 | const hulp_touch_pin_config_t pin_config = HULP_TOUCH_PIN_CONFIG_DEFAULT(); 168 | ESP_ERROR_CHECK(hulp_configure_touch_pin(PIN_TOUCH, &pin_config)); 169 | 170 | #if CONFIG_HULP_UART_TX_OD 171 | ESP_ERROR_CHECK(hulp_configure_pin(PIN_UART_TX, RTC_GPIO_MODE_DISABLED, GPIO_PULLUP_ONLY, 0)); 172 | #else 173 | ESP_ERROR_CHECK(hulp_configure_pin(PIN_UART_TX, RTC_GPIO_MODE_OUTPUT_ONLY, GPIO_FLOATING, 1)); 174 | #endif 175 | 176 | ESP_ERROR_CHECK(hulp_ulp_load(program, sizeof(program), 200ULL * 1000, 0)); 177 | ESP_ERROR_CHECK(hulp_ulp_run(0)); 178 | } 179 | 180 | extern "C" void app_main() 181 | { 182 | if(!hulp_is_ulp_wakeup()) 183 | { 184 | init_ulp(); 185 | } 186 | 187 | esp_deep_sleep_start(); 188 | } -------------------------------------------------------------------------------- /examples/UART/Printf_Touch/sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_ESP32_ULP_COPROC_ENABLED=y 2 | CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=1024 -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | # Arduino Syntax Highlighting 2 | 3 | RTC_DATA_ATTR LITERAL1 4 | ulp_var_t KEYWORD1 5 | ulp_insn_t KEYWORD1 6 | 7 | 8 | hulp_ulp_run KEYWORD2 9 | hulp_configure_pin KEYWORD2 10 | hulp_peripherals_on KEYWORD2 11 | hulp_ulp_load KEYWORD2 12 | hulp_ulp_isr_register KEYWORD2 13 | hulp_ulp_interrupt_en KEYWORD2 14 | hulp_configure_analog_pin KEYWORD2 15 | hulp_is_deep_sleep_wakeup KEYWORD2 16 | 17 | 18 | R0 LITERAL1 19 | R1 LITERAL1 20 | R2 LITERAL1 21 | R3 LITERAL1 22 | 23 | I_MOVI LITERAL1 24 | I_MOVR LITERAL1 25 | I_ADDI LITERAL1 26 | I_ADDR LITERAL1 27 | I_SUBI LITERAL1 28 | I_SUBR LITERAL1 29 | I_LSHI LITERAL1 30 | I_RSHI LITERAL1 31 | I_LSHR LITERAL1 32 | I_RSHR LITERAL1 33 | I_STAGE_RST LITERAL1 34 | I_STAGE_INC LITERAL1 35 | I_STAGE_DEC LITERAL1 36 | I_ST LITERAL1 37 | I_LD LITERAL1 38 | M_LABEL LITERAL1 39 | M_BX LITERAL1 40 | I_BGE LITERAL1 41 | M_BGE LITERAL1 42 | I_BL LITERAL1 43 | M_BL LITERAL1 44 | I_BXR LITERAL1 45 | I_BXF LITERAL1 46 | M_BXF LITERAL1 47 | M_BSLT LITERAL1 48 | I_WAKE LITERAL1 49 | I_HALT LITERAL1 50 | 51 | I_GET LITERAL1 52 | I_PUT LITERAL1 53 | I_ANALOG_READ LITERAL1 54 | M_WAKE_WHEN_READY LITERAL1 -------------------------------------------------------------------------------- /library.properties: -------------------------------------------------------------------------------- 1 | name=HULP 2 | version=2.0.1 3 | author=boarchuz 4 | maintainer=boarchuz 5 | sentence=ESP32 ULP Coprocessor Helper 6 | paragraph=Many drivers and helpers to do more with the ESP32's Ultra Low Power coprocessor. 7 | category=Other 8 | url=https://github.com/boarchuz/hulp 9 | architectures=esp32 10 | includes=hulp_arduino.h -------------------------------------------------------------------------------- /src/hulp.h: -------------------------------------------------------------------------------- 1 | #ifndef HULP_H 2 | #define HULP_H 3 | 4 | #include "driver/gpio.h" 5 | #include "driver/rtc_io.h" 6 | #include "driver/adc.h" 7 | 8 | #include "hulp_compat.h" 9 | #include "hulp_types.h" 10 | #include "hulp_macros.h" 11 | 12 | #include "hulp_config.h" 13 | 14 | #ifdef __cplusplus 15 | extern "C" { 16 | #endif 17 | 18 | typedef enum { 19 | ULP_STATE_IDLE, // Default state. 20 | ULP_STATE_WAKING, // Sleep timer expired. Starting ULP. 21 | ULP_STATE_RUNNING, // ULP is running. 22 | ULP_STATE_HALTED, // ULP just halted. Starting sleep timer. 23 | ULP_STATE_SLEEPING, // ULP is sleeping. Sleep timer is running. 24 | ULP_STATE_DONE, // ULP has finished and sleep timer is not running. 25 | ULP_STATE_UNKNOWN, // Unable to determine. 26 | } ulp_state_t; 27 | 28 | /** 29 | * Initialise and configure a pin for ULP GPIO. 30 | * 31 | * pin: GPIO pin (eg. GPIO_NUM_4) 32 | * mode: RTC_GPIO_MODE_INPUT_ONLY, RTC_GPIO_MODE_OUTPUT_ONLY, RTC_GPIO_MODE_INPUT_OUTPUT or RTC_GPIO_MODE_DISABLED 33 | * pull_mode: GPIO_PULLUP_ONLY, GPIO_PULLDOWN_ONLY, GPIO_PULLUP_PULLDOWN, GPIO_FLOATING 34 | * level: initial output level (if enabled) 35 | */ 36 | esp_err_t hulp_configure_pin(gpio_num_t pin, rtc_gpio_mode_t mode, gpio_pull_mode_t pull_mode, uint32_t level); 37 | 38 | /** 39 | * Initialise and configure a pin for ULP analog input 40 | * 41 | * pin: GPIO pin (eg. GPIO_NUM_32) 42 | * attenuation: Channel attenuation, one of ADC_ATTEN_DB_0, ADC_ATTEN_DB_2_5, ADC_ATTEN_DB_6 or ADC_ATTEN_DB_11 43 | * width: Bit capture width, one of ADC_WIDTH_BIT_9, ADC_WIDTH_BIT_10, ADC_WIDTH_BIT_11 or ADC_WIDTH_BIT_12 44 | */ 45 | esp_err_t hulp_configure_analog_pin(gpio_num_t pin, adc_atten_t attenuation, adc_bits_width_t width); 46 | 47 | /** 48 | * Prepares GPIOs for use with ULP hardware I2C. 49 | * Do not use this for bitbanging I2C. See examples for bitbanging configuration. 50 | * 51 | * RTC I2C signals can be mapped onto the following pins only: 52 | * SCL: GPIO_NUM_2 or GPIO_NUM_4 53 | * SDA: GPIO_NUM_0 or GPIO_NUM_15 54 | */ 55 | esp_err_t hulp_configure_i2c_pins(gpio_num_t scl_pin, gpio_num_t sda_pin, bool scl_pullup, bool sda_pullup); 56 | 57 | typedef struct { 58 | uint32_t scl_low; /*!< FAST_CLK cycles for SCL to be low [19b] */ 59 | uint32_t scl_high; /*!< FAST_CLK cycles for SCL to be high [20b] */ 60 | uint32_t sda_duty; /*!< FAST_CLK cycles SDA will switch after falling edge of SCL [20b] */ 61 | uint32_t scl_start; /*!< FAST_CLK cycles to wait before generating start condition [20b] */ 62 | uint32_t scl_stop; /*!< FAST_CLK cycles to wait before generating stop condition [20b] */ 63 | uint32_t timeout; /*!< Maximum number of FAST_CLK cycles that the transmission can take [20b] */ 64 | bool scl_pushpull; /*!< SCL is push-pull (true) or open-drain (false) */ 65 | bool sda_pushpull; /*!< SDA is push-pull (true) or open-drain (false) */ 66 | bool rx_lsbfirst; /*!< Receive LSB first */ 67 | bool tx_lsbfirst; /*!< Send LSB first */ 68 | } hulp_i2c_controller_config_t; 69 | 70 | #define HULP_I2C_CONTROLLER_CONFIG_DEFAULT() { \ 71 | .scl_low = 40, \ 72 | .scl_high = 40, \ 73 | .sda_duty = 16, \ 74 | .scl_start = 30, \ 75 | .scl_stop = 44, \ 76 | .timeout = 200, \ 77 | .scl_pushpull = false, \ 78 | .sda_pushpull = false, \ 79 | .rx_lsbfirst = false, \ 80 | .tx_lsbfirst = false \ 81 | } 82 | 83 | /** 84 | * Prepares I2C control registers in order to use ULP hardware I2C. 85 | * Do not use this for bitbanging I2C. 86 | */ 87 | esp_err_t hulp_configure_i2c_controller(const hulp_i2c_controller_config_t *config); 88 | 89 | /** 90 | * Set the address of an I2C slave for use with ULP hardware I2C. 91 | * Do not use this for bitbanging I2C. 92 | * 93 | * index: the register index in which to store the address (0-7) (SENS_SAR_SLAVE_ADDRx_REG) 94 | * address: I2C address of the slave 95 | */ 96 | esp_err_t hulp_register_i2c_slave(uint8_t index, uint8_t address); 97 | 98 | /** 99 | * Force RTC peripherals power domain to remain on in sleep. 100 | * Necessary to maintain some pin states during sleep, internal PU/PD resistors, ULP wakeup interval switching, etc. 101 | */ 102 | void hulp_peripherals_on(void); 103 | 104 | /** 105 | * Prepare the hall effect sensor for the ULP. 106 | * Sensor uses ADC channels on GPIO_36 (SENS_VP) and GPIO_39 (SENS_VN). Nothing should be externally connected to these pins. 107 | */ 108 | void hulp_configure_hall_effect_sensor(void); 109 | 110 | /** 111 | * Configure the temperature sensor for the ULP 112 | * Default clk_div: 3 113 | */ 114 | void hulp_tsens_configure(uint8_t clk_div); 115 | 116 | /** 117 | * Convert a time (in milliseconds) to an optimally-shifted 16-bit RTC tick count. 118 | */ 119 | uint16_t hulp_ms_to_ulp_ticks(uint32_t time_ms); 120 | 121 | /** 122 | * Variant of hulp_ms_to_ulp_ticks that allows specifying a reference shift. 123 | * Useful if you have a changing time period or multiple time periods that must be referenced to the same range of ticks. 124 | * Otherwise, for example, a shorter time period may resolve to a larger 16-bit tick count using a *different* bit range. 125 | * Typically, you'd use the maximum possible time to calulcate the shift using hulp_ms_to_ulp_tick_shift() 126 | */ 127 | uint16_t hulp_ms_to_ulp_ticks_with_shift(uint32_t time_ms, uint8_t shift); 128 | 129 | /** 130 | * Get the current 16-bit shifted value of the RTC ticks register. 131 | * Use hulp_ms_to_ulp_tick_shift() to get the shift associated with a given time. 132 | */ 133 | uint16_t hulp_get_current_ulp_ticks(uint8_t shift); 134 | 135 | /** 136 | * Get the optimal bit shift for a given time (in milliseconds), to be used in ranged 16-bit reading of RTC ticks. 137 | */ 138 | uint8_t hulp_ms_to_ulp_tick_shift(uint32_t time_ms); 139 | 140 | /** 141 | * Returns the offset of a label (after processing) in an array of ULP macros. 142 | */ 143 | uint16_t hulp_get_label_pc(uint16_t label, const ulp_insn_t *program); 144 | 145 | /** 146 | * Start the ULP coprocessor. Upon a I_HALT() instruction, the ULP will power down for the interval period before restarting. 147 | */ 148 | esp_err_t hulp_ulp_run(uint32_t entry_point); 149 | 150 | /** 151 | * Run the ULP program once. It will not be restarted after a I_HALT() instruction. 152 | */ 153 | esp_err_t hulp_ulp_run_once(uint32_t entry_point); 154 | 155 | /** 156 | * Process program macros and load it into RTC memory, and set the wakeup interval. 157 | * This is typically followed by hulp_ulp_run or hulp_ulp_run_once to start the ULP coprocessor. 158 | * For simplicity, expects program_size in bytes (not words). 159 | */ 160 | esp_err_t hulp_ulp_load(const ulp_insn_t *program, size_t program_size, uint32_t period_us, uint32_t entry_point); 161 | 162 | /** 163 | * Disables the timer so that the ULP will not wake up again. Equivalent to I_END() 164 | * If it is currently running, the ULP will continue until the next I_HALT() instruction. 165 | * Use hulp_ulp_run/hulp_ulp_run_once to restart. 166 | */ 167 | void hulp_ulp_end(void); 168 | 169 | /** 170 | * True if most recent reset was a wake from deep sleep (to distinguish from power-on reset, for example), false if any other cause. 171 | */ 172 | bool hulp_is_deep_sleep_wakeup(void); 173 | 174 | /** 175 | * True if deep sleep wakeup was triggered by the ULP, false if any other cause. 176 | */ 177 | bool hulp_is_ulp_wakeup(void); 178 | 179 | /** 180 | * Dump the provided instruction (via printf) in macro form 181 | */ 182 | int hulp_print_instruction(const ulp_insn_t *instruction); 183 | 184 | /** 185 | * Dump the provided program (via printf) in macro form 186 | */ 187 | void hulp_print_program(const ulp_insn_t *program, size_t num_instructions); 188 | 189 | /** 190 | * Get the current ULP state. 191 | * Note: when using hulp_ulp_run_once, only IDLE or DONE may be returned 192 | */ 193 | ulp_state_t hulp_get_state(void); 194 | 195 | /** 196 | * Register an ISR for ULP interrupts. 197 | * ULP interrupts will not be enabled until hulp_ulp_interrupt_en() is called. 198 | */ 199 | esp_err_t hulp_ulp_isr_register(intr_handler_t handler, void* handler_arg); 200 | 201 | /** 202 | * Deregister a ULP ISR. 203 | * Simply calls rtc_isr_deregister. Included for consistency with hulp_ulp_isr_register. 204 | */ 205 | esp_err_t hulp_ulp_isr_deregister(intr_handler_t handler, void* handler_arg); 206 | 207 | /** 208 | * Enable ULP interrupts. 209 | * The ULP can trigger interrupts with the I_WAKE() instruction. 210 | */ 211 | void hulp_ulp_interrupt_en(void); 212 | 213 | /** 214 | * Disable ULP interrupts. 215 | */ 216 | void hulp_ulp_interrupt_dis(void); 217 | 218 | /** 219 | * Configure RTC pin interrupt. 220 | * The ESP32 ULP has no ISR functionality, however it can read a register to determine if this interrupt has been triggered. 221 | * Assuming latency is acceptable, this allows flexibility to sleep or do other work without missing activity on a pin that 222 | * would otherwise need to be polled, as well as enabling the handling of pulses that may be too short to practically detect 223 | * using the ULP. 224 | * intr_type: GPIO_INTR_DISABLE, GPIO_INTR_ANYEDGE, GPIO_INTR_LOW_LEVEL, or GPIO_INTR_HIGH_LEVEL 225 | * RTC peripherals domain may need to be forced on for this to function correctly: hulp_peripherals_on() 226 | * See: I_GPIO_INT_RD, I_GPIO_INT_CLR, I_GPIO_INT_SET_TYPE 227 | */ 228 | esp_err_t hulp_configure_pin_int(gpio_num_t gpio_num, gpio_int_type_t intr_type); 229 | 230 | /** 231 | * @brief Get the frequency of RTC Fast Clock 232 | */ 233 | uint32_t hulp_get_fast_clk_freq(void); 234 | 235 | /** 236 | * Internal. Do not use directly. 237 | */ 238 | int hulp_adc_get_periph_index(gpio_num_t pin); 239 | 240 | /** 241 | * Internal. Do not use directly. 242 | */ 243 | int hulp_adc_get_channel_num(gpio_num_t pin); 244 | 245 | #ifdef __cplusplus 246 | } 247 | #endif 248 | 249 | #endif /* HULP_H */ 250 | -------------------------------------------------------------------------------- /src/hulp_apa.h: -------------------------------------------------------------------------------- 1 | #ifndef HULP_APA_H 2 | #define HULP_APA_H 3 | 4 | /** 5 | * APA-style (serial clock/data) RGB LED driver. 6 | */ 7 | 8 | #include "driver/gpio.h" 9 | #include "driver/rtc_io.h" 10 | 11 | #include "hulp.h" 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | /** 18 | * Structured APA data for ULP/SoC access 19 | */ 20 | typedef struct { 21 | union { 22 | ulp_var_t msb; 23 | struct { 24 | uint32_t blue : 8; /*!< 8-bit blue */ 25 | uint32_t brightness : 5; /*!< 5-bit brightness (0-31) */ 26 | uint32_t unused1 : 3; 27 | uint32_t unused2 : 16; 28 | }; 29 | }; 30 | union { 31 | ulp_var_t lsb; 32 | struct { 33 | uint32_t red : 8; /*!< 8-bit red */ 34 | uint32_t green : 8; /*!< 8-bit green */ 35 | uint32_t unused3 : 16; 36 | }; 37 | }; 38 | } ulp_apa_t; 39 | 40 | _Static_assert(sizeof(ulp_apa_t) == (2 * sizeof(uint32_t)), "ulp_apa_t size should be 2 words" ); 41 | 42 | /** 43 | * @brief Transmit a buffer (array of ulp_apa_t) to a string of APA RGBs 44 | * @param label_entry A new unique label for this subroutine. Branch to this label to transmit. 45 | * @param clk_gpio Clock pin (GPIO_NUM_x). 46 | * @param data_gpio Data pin (GPIO_NUM_x). 47 | * @param ulp_apa_var The array of ``ulp_apa_t`` in RTC Slow Memory. 48 | * @param num_apas The number of APAs in the array. 49 | * @param reg_scr General purpose scratch register (R1-R3). 50 | * @param reg_return The general purpose scratch register containing the return address (R1-R3). 51 | * 52 | * @code{c} 53 | * #define NUM_APAS 10 54 | * RTC_DATA_ATTR ulp_apa_t apas[NUM_APAS]; 55 | * 56 | * M_MOVL(R3, LBL_RETURN_POINT), //Prepare return 57 | * M_BX(LBL_APA_ENTRY), //Branch to the label given as APA entry 58 | * M_LABEL(LBL_RETURN_POINT), //Will return here after TX 59 | * 60 | * M_APA_TX(LBL_APA_ENTRY, GPIO_NUM_26, GPIO_NUM_27, apas, NUM_APAS, R1, R3), 61 | * @endcode 62 | */ 63 | #define M_APA_TX(label_entry, clk_gpio, data_gpio, ulp_apa_var, num_apas, reg_scr, reg_return) \ 64 | M_LABEL(label_entry), \ 65 | I_STAGE_RST(), \ 66 | I_GPIO_SET(data_gpio, 0), \ 67 | I_GPIO_SET(clk_gpio, 1), \ 68 | I_STAGE_INC(1), \ 69 | I_GPIO_SET(clk_gpio, 0), \ 70 | I_JUMPS(-3, 32, JUMPS_LT), \ 71 | I_MOVI(reg_scr, 0), \ 72 | I_STAGE_RST(), \ 73 | I_LD(R0, reg_scr, RTC_WORD_OFFSET(ulp_apa_var)), \ 74 | I_ORI(R0, R0, 0xE000), \ 75 | I_GPIO_SET(data_gpio, 1), \ 76 | I_GPIO_SET(clk_gpio, 1), \ 77 | I_STAGE_INC(1), \ 78 | I_LSHI(R0, R0, 1), \ 79 | I_GPIO_SET(clk_gpio, 0), \ 80 | I_JUMPS(8, 32, JUMPS_GE), \ 81 | I_JUMPS(4, 16, JUMPS_GE), \ 82 | I_BGE(-7, 1 << 15), \ 83 | I_GPIO_SET(data_gpio, 0), \ 84 | I_BGE(-8, 0), \ 85 | I_JUMPS(-3, 17, JUMPS_GE), \ 86 | I_LD(R0, reg_scr, (uint16_t)(RTC_WORD_OFFSET(ulp_apa_var) + 1)), \ 87 | I_BGE(-5, 0), \ 88 | I_ADDI(reg_scr, reg_scr, 2), \ 89 | I_MOVR(R0, reg_scr), \ 90 | I_BL(-18, NUM_LEDS * 2), \ 91 | I_GPIO_SET(data_gpio, 1), \ 92 | I_GPIO_SET(clk_gpio, 1), \ 93 | I_STAGE_INC(1), \ 94 | I_GPIO_SET(clk_gpio, 0), \ 95 | I_JUMPS(-3, 64, JUMPS_LT), \ 96 | I_GPIO_SET(data_gpio, 0), \ 97 | I_BXR(reg_return) 98 | 99 | #define M_APA1_SET(label_return_point, label_apa1_entry, brightness, red, green, blue) \ 100 | I_MOVI(R1, ((brightness) & 0x1F) << 8 | (blue)), \ 101 | I_MOVI(R2, (green) << 8 | (red)), \ 102 | M_MOVL(R3, label_return_point), \ 103 | M_BX(label_apa1_entry), \ 104 | M_LABEL(label_return_point) 105 | 106 | #define M_INCLUDE_APA1(label_entry, clk_gpio, data_gpio) \ 107 | M_INCLUDE_APA1_(label_entry, clk_gpio, data_gpio, R1, R2, R3) 108 | 109 | #define M_INCLUDE_APA1_(label_entry, clk_gpio, data_gpio, reg_br_blue, reg_green_red, reg_return) \ 110 | M_LABEL(label_entry), \ 111 | I_STAGE_RST(), \ 112 | I_GPIO_SET(data_gpio, 0), \ 113 | I_GPIO_SET(clk_gpio, 1), \ 114 | I_STAGE_INC(1), \ 115 | I_LSHI(R0, R0, 1), \ 116 | I_GPIO_SET(clk_gpio, 0), \ 117 | I_JUMPS(7,48,JUMPS_GE), \ 118 | I_JUMPS(-5,32,JUMPS_LT), \ 119 | I_JUMPS(2,33,JUMPS_GE), \ 120 | I_ORI(R0, reg_br_blue, 0xE000), \ 121 | I_BL(-9, 32768), \ 122 | I_GPIO_SET(data_gpio, 1), \ 123 | I_BGE(-10,0), \ 124 | I_JUMPS(4,64,JUMPS_GE), \ 125 | I_JUMPS(-4,49,JUMPS_GE), \ 126 | I_MOVR(R0,reg_green_red), \ 127 | I_BGE(-6,0), \ 128 | I_JUMPS(3,96,JUMPS_GE), \ 129 | I_GPIO_SET(data_gpio, 1), \ 130 | I_BGE(-17,0), \ 131 | I_GPIO_SET(data_gpio, 0), \ 132 | I_BXR(reg_return) 133 | 134 | 135 | #ifdef __cplusplus 136 | } 137 | #endif 138 | 139 | #endif /* HULP_APA_H */ 140 | -------------------------------------------------------------------------------- /src/hulp_arduino.h: -------------------------------------------------------------------------------- 1 | #include "hulp.h" 2 | 3 | #include "hulp_apa.h" 4 | #include "hulp_debug.h" 5 | #include "hulp_hall.h" 6 | #include "hulp_hx711.h" 7 | #include "hulp_i2cbb.h" 8 | #include "hulp_mutex.h" 9 | #include "hulp_touch.h" 10 | #include "hulp_uart.h" 11 | -------------------------------------------------------------------------------- /src/hulp_compat.h: -------------------------------------------------------------------------------- 1 | #ifndef HULP_COMPAT_H 2 | #define HULP_COMPAT_H 3 | 4 | #include "esp_idf_version.h" 5 | #include "hulp_config.h" 6 | 7 | #if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,2,0) 8 | #error "Unsupported IDF version" 9 | #endif 10 | 11 | #if CONFIG_IDF_TARGET_ESP32 12 | #if ESP_IDF_VERSION_MAJOR > 4 13 | #include "esp_private/esp_clk.h" 14 | #else 15 | #include "esp32/clk.h" 16 | #endif 17 | #include "esp32/ulp.h" 18 | #define HULP_ULP_RESERVE_MEM CONFIG_ESP32_ULP_COPROC_RESERVE_MEM 19 | #else 20 | #error "Unsupported target" 21 | #endif 22 | 23 | #endif /* HULP_COMPAT_H */ 24 | -------------------------------------------------------------------------------- /src/hulp_config.h: -------------------------------------------------------------------------------- 1 | #ifndef HULP_CONFIG_H 2 | #define HULP_CONFIG_H 3 | 4 | #include "sdkconfig.h" 5 | 6 | // Arduino build does not generate sdkconfig. Set missing config items to default values. 7 | #ifndef CONFIG_HULP_LABEL_AUTO_BASE 8 | #define CONFIG_HULP_LABEL_AUTO_BASE 60000 9 | #endif 10 | #ifndef CONFIG_HULP_FAST_CLK_CAL_CYCLES 11 | #define CONFIG_HULP_FAST_CLK_CAL_CYCLES 100 12 | #endif 13 | 14 | #endif /* HULP_CONFIG_H */ 15 | -------------------------------------------------------------------------------- /src/hulp_debug.c: -------------------------------------------------------------------------------- 1 | #include "hulp_debug.h" 2 | 3 | #include "esp_log.h" 4 | #include "esp32/rom/ets_sys.h" 5 | #include "soc/rtc_cntl_reg.h" 6 | 7 | #include "hulp.h" 8 | 9 | #include "hulp_config.h" 10 | 11 | static const char* TAG = "HULP-DBG"; 12 | 13 | typedef struct { 14 | uint16_t label_num; 15 | uint16_t pc; 16 | } hulp_debug_label_pc_pair_t; 17 | 18 | struct hulp_debug_bp_state_t { 19 | ulp_debug_bp_data_t* data; 20 | struct { 21 | hulp_debug_label_pc_pair_t* pc_pairs; 22 | size_t num; 23 | } labels; 24 | struct { 25 | hulp_debug_bp_cb_t fn; 26 | void* ctx; 27 | } callback; 28 | }; 29 | 30 | /** 31 | * To reduce ULP/RTC_MEM overhead, there are only 3 ST instructions for the 4 registers (ie. scratch reg is excluded). 32 | * For debugging ease, this parses them into an array of 4 uint16_t, setting scratch reg to its index (eg. R2 = 2). 33 | */ 34 | static void hulp_debug_parse_data_regs_inserting_scratch_val(const ulp_debug_bp_data_t* data, uint16_t* dest) 35 | { 36 | const uint16_t scr_reg = data->scr.val; 37 | 38 | for(uint16_t cur_reg = R0; cur_reg <= R3; ++cur_reg) 39 | { 40 | if(cur_reg == scr_reg) 41 | { 42 | dest[cur_reg] = cur_reg; 43 | } 44 | else 45 | { 46 | dest[cur_reg] = data->reg[cur_reg - (cur_reg > scr_reg ? 1 : 0)].val; //if reg > scratch reg then move array index back one to compensate 47 | } 48 | } 49 | } 50 | 51 | void hulp_debug_bp_continue(hulp_debug_bp_cb_data_t* bp_data) 52 | { 53 | //Use a known I_ST in the M_INCLUDE... macro to set an anchor point. Other key addresses can then be located by their offset. 54 | const uint16_t inc_st_pc = bp_data->meta.handle->data->scr.pc; 55 | 56 | //There are 3 reserved instructions at the default re-entry point. 57 | //Two are WR_REG reserved for the ULP to restore its original entry point (in case user does a normal HALT and reasonably expects ULP to begin again at normal entry) 58 | //ULP can only do 8 bit reg writes but (very) fringe cases where user may have entry point at very high offset (11 bits), so need 2 instructions to be sure. 59 | ulp_insn_t* reserved_insn = (ulp_insn_t*)(RTC_SLOW_MEM + inc_st_pc + HULP_DEBUG_BP_INC_ST_REENTRY_DEFAULT_OFFSET); 60 | //Alter instruction for upper 3 bits 61 | reserved_insn->wr_reg.data = (uint8_t)(((bp_data->meta.config_backup.entry_point) >> 8) & 0x7); 62 | ++reserved_insn; 63 | //Alter next instruction for lower 8 bits 64 | reserved_insn->wr_reg.data = (uint8_t)((bp_data->meta.config_backup.entry_point) & 0xFF); 65 | ++reserved_insn; 66 | //The third reserved instruction is a branch to the address to continue execution from (typically after breakpoint (default) but may have been altered). 67 | reserved_insn->bx.addr = bp_data->meta.return_addr; 68 | 69 | //Reload the register values from ULP data and compare with BP info to see if altered. Faster for SoC to skip ULP a few instructions ahead than for ULP to load values again even if unaltered. 70 | uint16_t check_reg_vals[4]; 71 | hulp_debug_parse_data_regs_inserting_scratch_val(bp_data->meta.handle->data, check_reg_vals); 72 | bool regs_dirty = ( 73 | bp_data->regs.r0 != check_reg_vals[0] || 74 | bp_data->regs.r1 != check_reg_vals[1] || 75 | bp_data->regs.r2 != check_reg_vals[2] || 76 | bp_data->regs.r3 != check_reg_vals[3] ); 77 | 78 | // Calculate reentry address based on default (reset entry point then branch) or dirty (reload registers then default) 79 | uint32_t reentry_point = inc_st_pc; 80 | if(regs_dirty) 81 | { 82 | reentry_point += HULP_DEBUG_BP_INC_ST_REENTRY_DIRTY_OFFSET; 83 | } 84 | else 85 | { 86 | reentry_point += HULP_DEBUG_BP_INC_ST_REENTRY_DEFAULT_OFFSET; 87 | } 88 | 89 | // Restore ULP wakeup timer based on state before the breakpoint disabled it. It will be enabled again here via ulp_run. 90 | if(bp_data->meta.config_backup.timer_en) 91 | { 92 | hulp_ulp_run(reentry_point); 93 | } 94 | else 95 | { 96 | hulp_ulp_run_once(reentry_point); 97 | } 98 | } 99 | 100 | void hulp_debug_bp_print_info(hulp_debug_bp_cb_data_t* bp_data) 101 | { 102 | ets_printf("D (%u) %s: BP:\t%s%5u\t\t%s%5u\t\t%s%5u\t\t%s%5u\t(PC: %5u, Lab: %5u%s, Line: %5u)\n", 103 | esp_log_timestamp(), 104 | TAG, 105 | bp_data->meta.reg_scr == R0 ? "*" : "", bp_data->regs.r0, 106 | bp_data->meta.reg_scr == R1 ? "*" : "", bp_data->regs.r1, 107 | bp_data->meta.reg_scr == R2 ? "*" : "", bp_data->regs.r2, 108 | bp_data->meta.reg_scr == R3 ? "*" : "", bp_data->regs.r3, 109 | bp_data->bp.pc, 110 | bp_data->bp.label.num, bp_data->bp.label.valid ? "" : "*", 111 | bp_data->bp.line 112 | ); 113 | } 114 | 115 | esp_err_t hulp_debug_bp_alter_reg(hulp_debug_bp_cb_data_t* bp_data, uint8_t reg, uint16_t val) 116 | { 117 | if(reg > R3) 118 | { 119 | return ESP_ERR_INVALID_ARG; 120 | } 121 | 122 | uint8_t scr_reg = bp_data->meta.reg_scr; 123 | 124 | if(reg == scr_reg) 125 | { 126 | return ESP_ERR_INVALID_STATE; 127 | } 128 | 129 | uint8_t arr_ind = reg; 130 | if(reg > scr_reg) --arr_ind; 131 | 132 | bp_data->meta.handle->data->reg[arr_ind].val = val; 133 | return ESP_OK; 134 | } 135 | 136 | void hulp_debug_bp_callback_default(hulp_debug_bp_cb_data_t* bp_data, void *ctx) 137 | { 138 | hulp_debug_bp_print_info(bp_data); 139 | hulp_debug_bp_continue(bp_data); 140 | } 141 | 142 | /** 143 | * Intermediate isr to transpose data set by ulp into a hulp_debug_bp_cb_data_t using metadata and info from config. 144 | * This is then passed to the callback. 145 | */ 146 | static void hulp_debug_isr_handler(void* ctx) 147 | { 148 | hulp_debug_bp_handle_t handle = (hulp_debug_bp_handle_t)ctx; 149 | 150 | if(handle->data->marker.pc == 0) 151 | { 152 | //No breakpoint data pending. Must be non-breakpoint wake (ie. normal user program I_WAKE). Ignore. 153 | return; 154 | } 155 | 156 | hulp_debug_bp_cb_data_t bp_data = {}; 157 | 158 | //Backup current config then immediately disable ULP timer to prevent it waking again while this breakpoint is processed. 159 | // - If this isr is not serviced quickly enough for some reason, there is potential for the ULP to wake again before we are able to disable the timer here, creating a mess. Unlikely/impossible? 160 | //Other options: 161 | // - Leave timer setting / enable timer: Not practical. Would require a sufficiently long ULP wakeup interval, else ULP may wake again before debugging complete -> mess. 162 | // - Always disable timer: If user's ULP program executes a normal halt, the ULP will not wake again if debugger has disabled timer, causing unexpected behaviour. Would require altering halt to perform a timer enable or trigger interrupt so debugger can handle it -> messy, inconvenient 163 | // - ULP disables its wakeup timer before interrupting: This would require overwriting R0 in order to get the current config, or losing current config -> not ideal as R0 is most likely register to want debugged (though could be worked around) and adds ULP overhead. Best option if current method is unreliable. 164 | 165 | #if CONFIG_IDF_TARGET_ESP32 166 | bp_data.meta.config_backup.timer_en = GET_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); 167 | CLEAR_PERI_REG_MASK(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); 168 | bp_data.meta.config_backup.entry_point = GET_PERI_REG_MASK(SENS_SAR_START_FORCE_REG, SENS_PC_INIT_M); 169 | #elif CONFIG_IDF_TARGET_ESP32S2 170 | bp_data.meta.config_backup.timer_en = REG_GET_FIELD(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); 171 | REG_CLR_BIT(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN); 172 | bp_data.meta.config_backup.entry_point = REG_GET_FIELD(RTC_CNTL_ULP_CP_TIMER_REG, RTC_CNTL_ULP_CP_PC_INIT); 173 | #else 174 | #error "unsupported" 175 | #endif 176 | 177 | //Get which register is scratch 178 | bp_data.meta.reg_scr = handle->data->scr.val; 179 | 180 | //Get the general purpose registers (scratch reg will be set to a default value) 181 | hulp_debug_parse_data_regs_inserting_scratch_val(handle->data, (uint16_t*)&(bp_data.regs)); 182 | 183 | //Get line number to easily locate breakpoint in file 184 | bp_data.bp.line = handle->data->marker.val; 185 | 186 | //Get the pc of this breakpoint 187 | bp_data.bp.pc = handle->data->marker.pc - HULP_DEBUG_SET_BP_START_ST_OFFSET; 188 | //This marker pc can be reset now 189 | handle->data->marker.pc = 0; 190 | 191 | //Set default continue point based on the marker pc (done in this way as part of breakpoint struct so that it can be altered between breakpoint and continue if desired) 192 | bp_data.meta.return_addr = bp_data.bp.pc + HULP_DEBUG_SET_BP_INSN_NUM; 193 | 194 | // Get the label number for this pc, if available 195 | if(handle->labels.pc_pairs != NULL) 196 | { 197 | for(size_t i = 0; i < handle->labels.num; ++i) 198 | { 199 | if(handle->labels.pc_pairs[i].pc == bp_data.bp.pc) 200 | { 201 | bp_data.bp.label.num = handle->labels.pc_pairs[i].label_num; 202 | bp_data.bp.label.valid = true; 203 | break; 204 | } 205 | } 206 | } 207 | 208 | //Set handle so other data (eg. label list) can be accessed by callbacks and continue 209 | bp_data.meta.handle = handle; 210 | 211 | //Pass to handler 212 | handle->callback.fn(&bp_data, handle->callback.ctx); 213 | } 214 | 215 | esp_err_t hulp_debug_bp_deinit(hulp_debug_bp_handle_t handle) 216 | { 217 | hulp_ulp_isr_deregister(hulp_debug_isr_handler, handle); 218 | if(handle) 219 | { 220 | if(handle->labels.pc_pairs) 221 | { 222 | free(handle->labels.pc_pairs); 223 | } 224 | free(handle); 225 | handle = NULL; 226 | } 227 | return ESP_OK; 228 | } 229 | 230 | static size_t hulp_debug_count_ulp_program_labels(const ulp_insn_t* program, size_t num_words) 231 | { 232 | size_t num_labels = 0; 233 | const ulp_insn_t* program_end = program + num_words; 234 | while(program < program_end) 235 | { 236 | if(program->macro.opcode == OPCODE_MACRO && program->macro.sub_opcode == SUB_OPCODE_MACRO_LABEL) 237 | { 238 | ++num_labels; 239 | } 240 | ++program; 241 | } 242 | return num_labels; 243 | } 244 | 245 | static size_t hulp_debug_populate_ulp_program_label_pcs(hulp_debug_label_pc_pair_t* label_pairs, size_t max_label_pairs, const ulp_insn_t* program, size_t num_words) 246 | { 247 | size_t current_pc = 0; 248 | 249 | const ulp_insn_t* program_end = program + num_words; 250 | const hulp_debug_label_pc_pair_t* labelpairs_end = label_pairs + max_label_pairs; 251 | 252 | while(program < program_end && label_pairs < labelpairs_end) 253 | { 254 | if(program->macro.opcode == OPCODE_MACRO) 255 | { 256 | if(program->macro.sub_opcode == SUB_OPCODE_MACRO_LABEL) 257 | { 258 | label_pairs->label_num = program->macro.label; 259 | label_pairs->pc = current_pc; 260 | ++label_pairs; 261 | } 262 | } 263 | else 264 | { 265 | ++current_pc; 266 | } 267 | ++program; 268 | } 269 | 270 | size_t num_populated = max_label_pairs - (labelpairs_end - label_pairs); 271 | return num_populated; 272 | } 273 | 274 | static esp_err_t hulp_debug_get_pc_from_label_pairs(hulp_debug_bp_handle_t handle, uint16_t label_num, uint16_t* dest) 275 | { 276 | if(!handle->labels.pc_pairs) 277 | { 278 | return ESP_ERR_INVALID_STATE; 279 | } 280 | 281 | for(size_t i = 0; i < handle->labels.num; ++i) 282 | { 283 | if(handle->labels.pc_pairs[i].label_num == label_num) 284 | { 285 | *dest = handle->labels.pc_pairs[i].pc; 286 | return ESP_OK; 287 | } 288 | } 289 | 290 | return ESP_ERR_NOT_FOUND; 291 | } 292 | 293 | esp_err_t hulp_debug_bp_set_continue_label(hulp_debug_bp_cb_data_t* bp_data, uint16_t label_num) 294 | { 295 | if(!bp_data->meta.handle->labels.pc_pairs) 296 | { 297 | return ESP_ERR_INVALID_STATE; 298 | } 299 | 300 | return hulp_debug_get_pc_from_label_pairs(bp_data->meta.handle, label_num, &(bp_data->meta.return_addr)); 301 | 302 | } 303 | 304 | /** 305 | * Each BP begins with a offset branch to allow easily enabling/disabling. 306 | * To enable, set offset to 1 (ie. branch next instruction) 307 | * To disable, set offset to skip over all instructions for this BP. 308 | */ 309 | static esp_err_t hulp_debug_bp_pc_set_branch_offset(uint16_t pc, uint16_t offset) 310 | { 311 | //A common misuse will be to try to disable a BP before the program is loaded into RTC memory. 312 | //For a rudimentary check, a ulp_insn_t array consisting of the M_DEBUG_SET_BP is declared and compared against the instruction at the provided pc. 313 | const ulp_insn_t template_insn_check = (ulp_insn_t[]){M_DEBUG_SET_BP(0,0,template_insn_check)}[0]; 314 | ulp_insn_t* bp_en_insn = (ulp_insn_t*)(RTC_SLOW_MEM + pc); 315 | 316 | if(bp_en_insn->b.opcode != template_insn_check.b.opcode || bp_en_insn->b.sub_opcode != template_insn_check.b.sub_opcode) 317 | { 318 | return ESP_ERR_INVALID_STATE; 319 | } 320 | 321 | bp_en_insn->b.offset = offset; 322 | return ESP_OK; 323 | } 324 | 325 | esp_err_t hulp_debug_bp_enable_by_pc(uint16_t pc) 326 | { 327 | //set to 'branch' ahead by 1 instruction (ie. continue) 328 | return hulp_debug_bp_pc_set_branch_offset(pc, 1); 329 | } 330 | 331 | esp_err_t hulp_debug_bp_disable_by_pc(uint16_t pc) 332 | { 333 | //set to branch forward HULP_DEBUG_BP_INSN_NUM instructions (ie. past this BP macro block) 334 | return hulp_debug_bp_pc_set_branch_offset(pc, HULP_DEBUG_SET_BP_INSN_NUM); 335 | } 336 | 337 | esp_err_t hulp_debug_bp_enable_by_label(hulp_debug_bp_handle_t handle, uint16_t label_num) 338 | { 339 | uint16_t pc; 340 | esp_err_t err = hulp_debug_get_pc_from_label_pairs(handle, label_num, &pc); 341 | if(err == ESP_OK) 342 | { 343 | err = hulp_debug_bp_enable_by_pc(pc); 344 | } 345 | return err; 346 | } 347 | 348 | esp_err_t hulp_debug_bp_disable_by_label(hulp_debug_bp_handle_t handle, uint16_t label_num) 349 | { 350 | uint16_t pc; 351 | esp_err_t err = hulp_debug_get_pc_from_label_pairs(handle, label_num, &pc); 352 | if(err == ESP_OK) 353 | { 354 | err = hulp_debug_bp_disable_by_pc(pc); 355 | } 356 | return err; 357 | } 358 | 359 | 360 | esp_err_t hulp_debug_bp_init(const hulp_debug_bp_config_t* config, hulp_debug_bp_handle_t* handle) 361 | { 362 | hulp_debug_bp_handle_t dbg_state = calloc(1, sizeof(struct hulp_debug_bp_state_t)); 363 | 364 | if(!dbg_state) 365 | { 366 | ESP_LOGE(TAG, "no memory for debug state"); 367 | return ESP_ERR_NO_MEM; 368 | } 369 | 370 | if(config->program.ptr) 371 | { 372 | size_t num_labels_in_program = hulp_debug_count_ulp_program_labels(config->program.ptr, config->program.num_words); 373 | if(num_labels_in_program > 0) 374 | { 375 | dbg_state->labels.pc_pairs = (hulp_debug_label_pc_pair_t*)malloc(num_labels_in_program * sizeof(hulp_debug_label_pc_pair_t)); 376 | if(!dbg_state->labels.pc_pairs) 377 | { 378 | ESP_LOGE(TAG, "no memory for debug labels"); 379 | hulp_debug_bp_deinit(dbg_state); 380 | return ESP_ERR_NO_MEM; 381 | } 382 | hulp_debug_populate_ulp_program_label_pcs(dbg_state->labels.pc_pairs, num_labels_in_program, config->program.ptr, config->program.num_words); 383 | dbg_state->labels.num = num_labels_in_program; 384 | 385 | } 386 | } 387 | 388 | dbg_state->data = config->data; 389 | dbg_state->callback.fn = config->callback.fn; 390 | dbg_state->callback.ctx = config->callback.ctx; 391 | 392 | esp_err_t err = hulp_ulp_isr_register(hulp_debug_isr_handler, dbg_state); 393 | if(err != ESP_OK) 394 | { 395 | ESP_LOGE(TAG, "failed to register isr (%d)", err); 396 | hulp_debug_bp_deinit(dbg_state); 397 | return err; 398 | } 399 | 400 | if(handle) 401 | { 402 | *handle = dbg_state; 403 | } 404 | 405 | hulp_ulp_interrupt_en(); 406 | 407 | return ESP_OK; 408 | } 409 | -------------------------------------------------------------------------------- /src/hulp_debug.h: -------------------------------------------------------------------------------- 1 | #ifndef HULP_DEBUG_H 2 | #define HULP_DEBUG_H 3 | 4 | #include "hulp.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | typedef struct hulp_debug_bp_state_t *hulp_debug_bp_handle_t; 11 | 12 | /** 13 | * Type for ULP to communicate breakpoint data to SoC handler. Do not access this directly. 14 | * Object must be in RTC_SLOW_MEM for ULP access (declare with RTC_DATA_ATTR). 15 | * 16 | * Member order is fixed; ULP BP macros expect these offsets. 17 | */ 18 | typedef struct { 19 | ulp_var_t marker; 20 | ulp_var_t scr; 21 | ulp_var_t reg[3]; 22 | } ulp_debug_bp_data_t; 23 | 24 | /** 25 | * Structured ULP breakpoint information. 26 | * Generally, 'meta' should be left to hulp_debug internal functions. 27 | */ 28 | typedef struct { 29 | struct { 30 | uint16_t r0; //Value of R0 31 | uint16_t r1; //Value of R1 32 | uint16_t r2; //Value of R2 33 | uint16_t r3; //Value of R3 34 | } regs; 35 | struct { 36 | uint16_t pc; //PC of breakpoint 37 | uint16_t line; //File line number of breakpoint macro 38 | struct { 39 | bool valid; //Whether or not label.num is valid or should be ignored 40 | uint16_t num; //Label number of breakpoint 41 | } label; 42 | } bp; 43 | struct { 44 | hulp_debug_bp_handle_t handle; 45 | uint8_t reg_scr; //Which register is scratch for this breakpoint 46 | uint16_t return_addr; //Address to continue to after breakpoint is processed and ULP state restored 47 | struct { 48 | bool timer_en; //Whether or not ULP wakeup timer was enabled before being disabled by breakpoint. 49 | uint32_t entry_point; //Original ULP program entry point to be restored upon continue. 50 | } config_backup; 51 | } meta; 52 | } hulp_debug_bp_cb_data_t; 53 | 54 | /** 55 | * ULP breakpoint callback ISR function type. 56 | * Short, no printf style functions, etc. 57 | */ 58 | typedef void (*hulp_debug_bp_cb_t)(hulp_debug_bp_cb_data_t* info, void* ctx); 59 | 60 | /** 61 | * ULP breakpoint debugging config for initialisation. 62 | * 63 | * Providing an optional pointer to the program array will allow the debugger to associate breakpoints with label numbers for improved debugging information. 64 | */ 65 | typedef struct { 66 | ulp_debug_bp_data_t* data; 67 | struct { 68 | const ulp_insn_t* ptr; 69 | size_t num_words; 70 | } program; 71 | struct { 72 | hulp_debug_bp_cb_t fn; 73 | void* ctx; 74 | } callback; 75 | } hulp_debug_bp_config_t; 76 | 77 | #define HULP_DEBUG_BP_CONFIG_DEFAULT(bp_data, ulp_program, program_size) { \ 78 | .data = &(bp_data), \ 79 | .program = { \ 80 | .ptr = (ulp_program), \ 81 | .num_words = (program_size) / sizeof(ulp_insn_t), \ 82 | }, \ 83 | .callback = { \ 84 | .fn = hulp_debug_bp_callback_default, \ 85 | .ctx = NULL, \ 86 | }, \ 87 | } 88 | 89 | #define HULP_DEBUG_BP_CONFIG_DEFAULT_NO_LABELS(bp_data) \ 90 | HULP_DEBUG_BP_CONFIG_DEFAULT(bp_data, NULL, 0) 91 | 92 | /** 93 | * Initialise ULP breakpoint debugging with the provided configuration. 94 | * 95 | * A pointer to a handle is optional if deinitialisation is desired later to free resources. 96 | * The ULP interrupt must be enabled with hulp_ulp_interrupt_en() to begin. 97 | */ 98 | esp_err_t hulp_debug_bp_init(const hulp_debug_bp_config_t* config, hulp_debug_bp_handle_t* handle); 99 | 100 | /** 101 | * Free resources associated with the provided ULP breakpoint debugging handle. 102 | */ 103 | esp_err_t hulp_debug_bp_deinit(hulp_debug_bp_handle_t handle); 104 | 105 | /** 106 | * Prints basic breakpoint debug info and continues ULP execution. 107 | * May be used in callback ISR, or used directly as the callback in debug initialisation config. 108 | */ 109 | void hulp_debug_bp_callback_default(hulp_debug_bp_cb_data_t* bp_data, void *ctx); 110 | 111 | /** 112 | * Basic dump of breakpoint info. 113 | * May be used in callback ISR. 114 | */ 115 | void hulp_debug_bp_print_info(hulp_debug_bp_cb_data_t* bp_data); 116 | 117 | /** 118 | * Restores the ULP state from a breakpoint and continues execution. 119 | * May be used in callback ISR. 120 | */ 121 | void hulp_debug_bp_continue(hulp_debug_bp_cb_data_t* bp_data); 122 | 123 | /** 124 | * Enable the BP at the provided pc. 125 | * May be used in callback ISR. 126 | */ 127 | esp_err_t hulp_debug_bp_enable_by_pc(uint16_t pc); 128 | 129 | /** 130 | * Disable the BP at the provided pc. 131 | * May be used in callback ISR. 132 | */ 133 | esp_err_t hulp_debug_bp_disable_by_pc(uint16_t pc); 134 | 135 | /** 136 | * Enable the BP using the identifying label immediately before the M_DEBUG_SET_BP macro. 137 | * This expects the instruction in RTC_SLOW_MEM so the program must already be loaded. 138 | * May be used in callback ISR. 139 | */ 140 | esp_err_t hulp_debug_bp_enable_by_label(hulp_debug_bp_handle_t handle, uint16_t label_num); 141 | 142 | /** 143 | * Disable the BP using the identifying label immediately before the M_DEBUG_SET_BP macro. 144 | * This expects the instruction in RTC_SLOW_MEM so the program must already be loaded. 145 | * May be used in callback ISR. 146 | */ 147 | esp_err_t hulp_debug_bp_disable_by_label(hulp_debug_bp_handle_t handle, uint16_t label_num); 148 | 149 | /** 150 | * In a breakpoint, change where the ULP will continue from using the provided label. 151 | * When hulp_debug_bp_continue is called, the ULP will branch to this label instead of continuing from the breakpoint. 152 | * This is useful for conditionally controlling the flow of execution based on the current state. 153 | * May be used in callback ISR. 154 | */ 155 | esp_err_t hulp_debug_bp_set_continue_label(hulp_debug_bp_cb_data_t* bp_data, uint16_t label_num); 156 | 157 | /** 158 | * In a breakpoint, change the value of a general purpose register (R0-R3, excluding the scratch register). 159 | * The ULP will load this updated value into the specified register when it continues. 160 | */ 161 | esp_err_t hulp_debug_bp_alter_reg(hulp_debug_bp_cb_data_t* bp_data, uint8_t reg, uint16_t val); 162 | 163 | /** 164 | * Set a breakpoint in a ULP program. 165 | * 166 | * If breakpoints have been initialised in HULP, interrupts are enabled, and this BP is enabled, the ULP will log information and halt at this point. 167 | * The current state can then be debugged and manipulated via hulp_debug methods before continuing execution when ready. 168 | * 169 | * label_bp_dep_entry: The same label number provided to M_INCLUDE_DEBUG_BP 170 | * reg_scr: Unfortunately, 1 register must be used as a scratch register (its value will be overwritten before breaking). 171 | * bp_data: The same ulp_debug_bp_data_t provided to M_INCLUDE_DEBUG_BP 172 | */ 173 | #define M_DEBUG_SET_BP(label_bp_dep_entry, reg_scr, bp_data) \ 174 | I_BGE(1, 0), /* <- allows debugger to enable/disable breakpoint by skipping or continuing into bp instructions. default: enabled */ \ 175 | I_MOVI(reg_scr, __LINE__), \ 176 | I_PUTO(reg_scr, reg_scr, __LINE__, (bp_data)), /* <- store the scr_reg and current PC for debugger */ \ 177 | M_BX(label_bp_dep_entry) 178 | 179 | //These must be kept in sync with above macro for offset calculations. Used internally. Do not use. 180 | // #define HULP_DEBUG_SET_BP_INSN_CHECK_FIRST I_BGE(1,0) //for rudimentary verification of M_DEBUG_SET_BP 181 | #define HULP_DEBUG_SET_BP_START_ST_OFFSET 2 //num instructions (excl macros) from start of M_DEBUG_SET_BP to I_ST 182 | #define HULP_DEBUG_SET_BP_INSN_NUM 4 //num instructions (excl macros) in a M_DEBUG_SET_BP block 183 | 184 | 185 | /** 186 | * Include subroutine necessary for ULP breakpoints. 187 | * Typically this macro is at the end of your program (eg. after I_HALT). 188 | * 189 | * label_entry: A unique label number for this subroutine. This label should be provided to every M_DEBUG_SET_BP so the breakpoint knows where to find this subroutine. 190 | * reg_scr: Unfortunately, 1 register must be used as a scratch register (its value will be overwritten before breaking). 191 | * bp_data: A ulp_debug_bp_data_t in RTC_SLOW_MEM 192 | */ 193 | #define M_INCLUDE_DEBUG_BP(label_entry, reg_scr, bp_data) \ 194 | M_LABEL(label_entry), \ 195 | I_MOVI(reg_scr, reg_scr), \ 196 | I_PUTO(reg_scr, reg_scr, (reg_scr) - 1, ((bp_data))), \ 197 | I_PUTO( ((reg_scr) == R0 ? R1 : R0), reg_scr, (reg_scr) - 2, ((bp_data)) ), \ 198 | I_PUTO( ((reg_scr) == R1 ? R2 : R1), reg_scr, (reg_scr) - 3, ((bp_data)) ), \ 199 | I_PUTO( ((reg_scr) == R2 ? R3 : R2), reg_scr, (reg_scr) - 4, ((bp_data)) ), \ 200 | I_WAKE(), \ 201 | I_HALT(), \ 202 | I_GETO( ((reg_scr) == R0 ? R1 : R0), reg_scr, (reg_scr) - 2, ((bp_data)) ), /* <- dirty reentry point */ \ 203 | I_GETO( ((reg_scr) == R1 ? R2 : R1), reg_scr, (reg_scr) - 3, ((bp_data)) ), \ 204 | I_GETO( ((reg_scr) == R2 ? R3 : R2), reg_scr, (reg_scr) - 4, ((bp_data)) ), \ 205 | M_SET_ENTRY(0), /* <- default reentry point */ \ 206 | I_BXI(0) 207 | 208 | #define HULP_DEBUG_BP_INC_ST_REENTRY_DIRTY_OFFSET 6 // num instructions (excl macros) from reg_scr I_ST to the dirty reentry (I_GET) 209 | #define HULP_DEBUG_BP_INC_ST_REENTRY_DEFAULT_OFFSET 9 // num instructions (excl macros) from reg_scr I_ST to default reentry (M_SET_ENTRY) 210 | 211 | #ifdef __cplusplus 212 | } 213 | #endif 214 | 215 | #endif /* HULP_DEBUG_H */ 216 | -------------------------------------------------------------------------------- /src/hulp_hall.h: -------------------------------------------------------------------------------- 1 | #ifndef HULP_HALL_H 2 | #define HULP_HALL_H 3 | 4 | #include "soc/rtc_io_reg.h" 5 | 6 | #include "hulp.h" 7 | 8 | #define I_HALL_CONNECT() \ 9 | I_WR_REG_BIT(RTC_IO_HALL_SENS_REG, RTC_IO_XPD_HALL_S, 1) 10 | 11 | #define I_HALL_DISCONNECT() \ 12 | I_WR_REG_BIT(RTC_IO_HALL_SENS_REG, RTC_IO_XPD_HALL_S, 0) 13 | 14 | #define I_HALL_POLARITY_FORWARD() \ 15 | I_WR_REG_BIT(RTC_IO_HALL_SENS_REG, RTC_IO_HALL_PHASE_S, 0) 16 | 17 | #define I_HALL_POLARITY_REVERSE() \ 18 | I_WR_REG_BIT(RTC_IO_HALL_SENS_REG, RTC_IO_HALL_PHASE_S, 1) 19 | 20 | 21 | #endif /* HULP_HALL_H */ 22 | -------------------------------------------------------------------------------- /src/hulp_hx711.h: -------------------------------------------------------------------------------- 1 | #ifndef HULP_HX711_H 2 | #define HULP_HX711_H 3 | 4 | #include "hulp.h" 5 | 6 | /** 7 | * Busy wait until data is ready (ie. until HX711 sets data low) 8 | */ 9 | #define M_HX711_WAIT_READY(gpio_data) \ 10 | I_GPIO_READ(gpio_data), \ 11 | I_BGE(-1, 1) 12 | 13 | /** 14 | * Read 24-bit value from a HX711 15 | * 16 | * Two destination registers are required for the output - high16 and low16. 17 | * As the name implies, these will overlap: 18 | * high16: [23:8] 19 | * low16: [15:0] 20 | * 21 | * The SoC may simply OR the outputs together for the full value if required: uint32_t val = ((uint32_t)high16 << 8) | (low16); 22 | * 23 | * reg_high16: Destination reg (R1-R3) to hold the upper 16 bits of the 24-bit value 24 | * reg_low16: Destination reg (R1-R3) to hold the lower 16 bits of the 24-bit value 25 | * 26 | * For example, to read the upper 16 bits into R1, lower 16 bits into R2: 27 | * M_HX711_READ(R1, R2, GPIO_NUM_25, GPIO_NUM_26), 28 | */ 29 | #define M_HX711_READ(reg_high16, reg_low16, gpio_data, gpio_clock) \ 30 | I_STAGE_RST(), \ 31 | I_MOVR(reg_high16, reg_low16), \ 32 | I_GPIO_SET(gpio_clock, 1), \ 33 | I_STAGE_INC(1), \ 34 | I_GPIO_SET(gpio_clock, 0), \ 35 | I_JUMPS(6, 25, JUMPS_GE), \ 36 | I_LSHI(reg_low16, reg_low16, 1), \ 37 | I_GPIO_READ(gpio_data), \ 38 | I_ORR(reg_low16, reg_low16, R0), \ 39 | I_JUMPS(-8, 17, JUMPS_LT), \ 40 | I_BGE(-8, 0) 41 | 42 | /** 43 | * HX711 read, setting gain to 64 */ 44 | #define M_HX711_READ_G64(reg_high16, reg_low16, gpio_data, gpio_clock) \ 45 | M_HX711_READ(reg_high16, reg_low16, gpio_data, gpio_clock), \ 46 | I_JUMPS(-9, 27, JUMPS_LT) /* jump back twice more for total 27 clock pulses */ 47 | 48 | /** 49 | * Power down the HX711 50 | * 51 | * The HX711 will enter power down mode when the clock pin is held high for 60uS. 52 | */ 53 | #define I_HX711_POWER_DOWN(gpio_clock) \ 54 | I_GPIO_SET(gpio_clock, 1) 55 | 56 | /** 57 | * Power up the HX711 58 | * 59 | * Return the clock pin to low. The HX711 resets and resumes normal operation. 60 | */ 61 | #define I_HX711_POWER_UP(gpio_clock) \ 62 | I_GPIO_SET(gpio_clock, 0) 63 | 64 | 65 | /** 66 | * Read upper 16 bits from a HX711 (lower bits will be clocked but ignored) 67 | * 68 | * reg_high16: Destination reg (R1-R3) to hold the upper 16 bits [23:8] of the 24-bit value 69 | * 70 | * For example, to read the upper 16 bits into R1: 71 | * M_HX711_READ(R1, GPIO_NUM_25, GPIO_NUM_26), 72 | */ 73 | #define M_HX711_READ16(reg_high16, gpio_data, gpio_clock) \ 74 | I_STAGE_RST(), \ 75 | I_GPIO_SET(gpio_clock, 1), \ 76 | I_STAGE_INC(1), \ 77 | I_GPIO_SET(gpio_clock, 0), \ 78 | I_JUMPS(6, 25, JUMPS_GE), \ 79 | I_GPIO_READ(gpio_data), \ 80 | I_JUMPS(-5, 17, JUMPS_GE), \ 81 | I_LSHI(reg_high16, reg_high16, 1), \ 82 | I_ORR(reg_high16, reg_high16, R0), \ 83 | I_BGE(-8, 0) 84 | 85 | 86 | #endif /* HULP_HX711_H */ 87 | -------------------------------------------------------------------------------- /src/hulp_macro_fix.h: -------------------------------------------------------------------------------- 1 | #ifndef HULP_MACRO_FIX_H 2 | #define HULP_MACRO_FIX_H 3 | 4 | #include "hulp_compat.h" 5 | 6 | // Fix for register macros bitmask (see https://github.com/espressif/esp-idf/pull/11652) 7 | #if !defined(I_WR_REG) 8 | # error "!I_WR_REG" 9 | #endif 10 | #undef I_WR_REG 11 | #define I_WR_REG(reg, low_bit, high_bit, val) {.wr_reg = {\ 12 | .addr = ((reg) / sizeof(uint32_t)) & 0xff, \ 13 | .periph_sel = SOC_REG_TO_ULP_PERIPH_SEL(reg), \ 14 | .data = val, \ 15 | .low = low_bit, \ 16 | .high = high_bit, \ 17 | .opcode = OPCODE_WR_REG } } 18 | 19 | #if !defined(I_RD_REG) 20 | # error "!I_RD_REG" 21 | #endif 22 | #undef I_RD_REG 23 | #define I_RD_REG(reg, low_bit, high_bit) {.rd_reg = {\ 24 | .addr = ((reg) / sizeof(uint32_t)) & 0xff, \ 25 | .periph_sel = SOC_REG_TO_ULP_PERIPH_SEL(reg), \ 26 | .unused = 0, \ 27 | .low = low_bit, \ 28 | .high = high_bit, \ 29 | .opcode = OPCODE_RD_REG } } 30 | 31 | #endif /* HULP_MACRO_FIX_H */ 32 | -------------------------------------------------------------------------------- /src/hulp_macro_opt.h: -------------------------------------------------------------------------------- 1 | #ifndef HULP_MACRO_OPT_H 2 | #define HULP_MACRO_OPT_H 3 | 4 | #include "soc/soc.h" 5 | #include "soc/soc_caps.h" 6 | #include "soc/rtc_io_periph.h" 7 | #include "soc/rtc_io_channel.h" 8 | #include "soc/rtc_io_reg.h" 9 | #include "soc/rtc_cntl_reg.h" 10 | #include "driver/rtc_io.h" 11 | 12 | #include "hulp.h" 13 | 14 | #include "sdkconfig.h" 15 | 16 | #ifdef CONFIG_HULP_MACRO_OPTIMISATIONS 17 | 18 | #define SOC_REG_TO_ULP_PERIPH_SEL(reg) (uint32_t)(((reg) - DR_REG_RTCCNTL_BASE) / 0x400) 19 | 20 | static const int s_hulp_rtc_io_num_map[SOC_GPIO_PIN_COUNT] = { 21 | RTCIO_GPIO0_CHANNEL, //GPIO0 22 | -1,//GPIO1 23 | RTCIO_GPIO2_CHANNEL, //GPIO2 24 | -1,//GPIO3 25 | RTCIO_GPIO4_CHANNEL, //GPIO4 26 | -1,//GPIO5 27 | -1,//GPIO6 28 | -1,//GPIO7 29 | -1,//GPIO8 30 | -1,//GPIO9 31 | -1,//GPIO10 32 | -1,//GPIO11 33 | RTCIO_GPIO12_CHANNEL, //GPIO12 34 | RTCIO_GPIO13_CHANNEL, //GPIO13 35 | RTCIO_GPIO14_CHANNEL, //GPIO14 36 | RTCIO_GPIO15_CHANNEL, //GPIO15 37 | -1,//GPIO16 38 | -1,//GPIO17 39 | -1,//GPIO18 40 | -1,//GPIO19 41 | -1,//GPIO20 42 | -1,//GPIO21 43 | -1,//GPIO22 44 | -1,//GPIO23 45 | -1,//GPIO24 46 | RTCIO_GPIO25_CHANNEL, //GPIO25 47 | RTCIO_GPIO26_CHANNEL, //GPIO26 48 | RTCIO_GPIO27_CHANNEL, //GPIO27 49 | -1,//GPIO28 50 | -1,//GPIO29 51 | -1,//GPIO30 52 | -1,//GPIO31 53 | RTCIO_GPIO32_CHANNEL, //GPIO32 54 | RTCIO_GPIO33_CHANNEL, //GPIO33 55 | RTCIO_GPIO34_CHANNEL, //GPIO34 56 | RTCIO_GPIO35_CHANNEL, //GPIO35 57 | RTCIO_GPIO36_CHANNEL, //GPIO36 58 | RTCIO_GPIO37_CHANNEL, //GPIO37 59 | RTCIO_GPIO38_CHANNEL, //GPIO38 60 | RTCIO_GPIO39_CHANNEL, //GPIO39 61 | }; 62 | 63 | #define hulp_gtr(gpio_num) ((uint8_t)s_hulp_rtc_io_num_map[gpio_num]) 64 | 65 | #define RTC_WORD_OFFSET(x) ((uint16_t)((uint32_t*)(&(x)) - RTC_SLOW_MEM)) 66 | 67 | // See rtc_io_desc 68 | static const rtc_io_desc_t s_hulp_rtc_io_desc[SOC_RTCIO_PIN_COUNT] = { 69 | /*REG MUX select function select Input enable Pullup Pulldown Sleep select Sleep input enable PAD hold Pad force hold Mask of drive capability Offset gpio number */ 70 | {RTC_IO_SENSOR_PADS_REG, RTC_IO_SENSE1_MUX_SEL_M, RTC_IO_SENSE1_FUN_SEL_S, RTC_IO_SENSE1_FUN_IE_M, 0, 0, RTC_IO_SENSE1_SLP_SEL_M, RTC_IO_SENSE1_SLP_IE_M, 0, RTC_IO_SENSE1_HOLD_M, RTC_CNTL_SENSE1_HOLD_FORCE_M, 0, 0, RTCIO_CHANNEL_0_GPIO_NUM}, //36 71 | {RTC_IO_SENSOR_PADS_REG, RTC_IO_SENSE2_MUX_SEL_M, RTC_IO_SENSE2_FUN_SEL_S, RTC_IO_SENSE2_FUN_IE_M, 0, 0, RTC_IO_SENSE2_SLP_SEL_M, RTC_IO_SENSE2_SLP_IE_M, 0, RTC_IO_SENSE2_HOLD_M, RTC_CNTL_SENSE2_HOLD_FORCE_M, 0, 0, RTCIO_CHANNEL_1_GPIO_NUM}, //37 72 | {RTC_IO_SENSOR_PADS_REG, RTC_IO_SENSE3_MUX_SEL_M, RTC_IO_SENSE3_FUN_SEL_S, RTC_IO_SENSE3_FUN_IE_M, 0, 0, RTC_IO_SENSE3_SLP_SEL_M, RTC_IO_SENSE3_SLP_IE_M, 0, RTC_IO_SENSE3_HOLD_M, RTC_CNTL_SENSE3_HOLD_FORCE_M, 0, 0, RTCIO_CHANNEL_2_GPIO_NUM}, //38 73 | {RTC_IO_SENSOR_PADS_REG, RTC_IO_SENSE4_MUX_SEL_M, RTC_IO_SENSE4_FUN_SEL_S, RTC_IO_SENSE4_FUN_IE_M, 0, 0, RTC_IO_SENSE4_SLP_SEL_M, RTC_IO_SENSE4_SLP_IE_M, 0, RTC_IO_SENSE4_HOLD_M, RTC_CNTL_SENSE4_HOLD_FORCE_M, 0, 0, RTCIO_CHANNEL_3_GPIO_NUM}, //39 74 | {RTC_IO_ADC_PAD_REG, RTC_IO_ADC1_MUX_SEL_M, RTC_IO_ADC1_FUN_SEL_S, RTC_IO_ADC1_FUN_IE_M, 0, 0, RTC_IO_ADC1_SLP_SEL_M, RTC_IO_ADC1_SLP_IE_M, 0, RTC_IO_ADC1_HOLD_M, RTC_CNTL_ADC1_HOLD_FORCE_M, 0, 0, RTCIO_CHANNEL_4_GPIO_NUM}, //34 75 | {RTC_IO_ADC_PAD_REG, RTC_IO_ADC2_MUX_SEL_M, RTC_IO_ADC2_FUN_SEL_S, RTC_IO_ADC2_FUN_IE_M, 0, 0, RTC_IO_ADC2_SLP_SEL_M, RTC_IO_ADC2_SLP_IE_M, 0, RTC_IO_ADC2_HOLD_M, RTC_CNTL_ADC2_HOLD_FORCE_M, 0, 0, RTCIO_CHANNEL_5_GPIO_NUM}, //35 76 | {RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_MUX_SEL_M, RTC_IO_PDAC1_FUN_SEL_S, RTC_IO_PDAC1_FUN_IE_M, RTC_IO_PDAC1_RUE_M, RTC_IO_PDAC1_RDE_M, RTC_IO_PDAC1_SLP_SEL_M, RTC_IO_PDAC1_SLP_IE_M, 0, RTC_IO_PDAC1_HOLD_M, RTC_CNTL_PDAC1_HOLD_FORCE_M, RTC_IO_PDAC1_DRV_V, RTC_IO_PDAC1_DRV_S, RTCIO_CHANNEL_6_GPIO_NUM}, //25 77 | {RTC_IO_PAD_DAC2_REG, RTC_IO_PDAC2_MUX_SEL_M, RTC_IO_PDAC2_FUN_SEL_S, RTC_IO_PDAC2_FUN_IE_M, RTC_IO_PDAC2_RUE_M, RTC_IO_PDAC2_RDE_M, RTC_IO_PDAC2_SLP_SEL_M, RTC_IO_PDAC2_SLP_IE_M, 0, RTC_IO_PDAC2_HOLD_M, RTC_CNTL_PDAC2_HOLD_FORCE_M, RTC_IO_PDAC2_DRV_V, RTC_IO_PDAC2_DRV_S, RTCIO_CHANNEL_7_GPIO_NUM}, //26 78 | {RTC_IO_XTAL_32K_PAD_REG, RTC_IO_X32N_MUX_SEL_M, RTC_IO_X32N_FUN_SEL_S, RTC_IO_X32N_FUN_IE_M, RTC_IO_X32N_RUE_M, RTC_IO_X32N_RDE_M, RTC_IO_X32N_SLP_SEL_M, RTC_IO_X32N_SLP_IE_M, 0, RTC_IO_X32N_HOLD_M, RTC_CNTL_X32N_HOLD_FORCE_M, RTC_IO_X32N_DRV_V, RTC_IO_X32N_DRV_S, RTCIO_CHANNEL_8_GPIO_NUM}, //33 79 | {RTC_IO_XTAL_32K_PAD_REG, RTC_IO_X32P_MUX_SEL_M, RTC_IO_X32P_FUN_SEL_S, RTC_IO_X32P_FUN_IE_M, RTC_IO_X32P_RUE_M, RTC_IO_X32P_RDE_M, RTC_IO_X32P_SLP_SEL_M, RTC_IO_X32P_SLP_IE_M, 0, RTC_IO_X32P_HOLD_M, RTC_CNTL_X32P_HOLD_FORCE_M, RTC_IO_X32P_DRV_V, RTC_IO_X32P_DRV_S, RTCIO_CHANNEL_9_GPIO_NUM}, //32 80 | {RTC_IO_TOUCH_PAD0_REG, RTC_IO_TOUCH_PAD0_MUX_SEL_M, RTC_IO_TOUCH_PAD0_FUN_SEL_S, RTC_IO_TOUCH_PAD0_FUN_IE_M, RTC_IO_TOUCH_PAD0_RUE_M, RTC_IO_TOUCH_PAD0_RDE_M, RTC_IO_TOUCH_PAD0_SLP_SEL_M, RTC_IO_TOUCH_PAD0_SLP_IE_M, 0, RTC_IO_TOUCH_PAD0_HOLD_M, RTC_CNTL_TOUCH_PAD0_HOLD_FORCE_M, RTC_IO_TOUCH_PAD0_DRV_V, RTC_IO_TOUCH_PAD0_DRV_S, RTCIO_CHANNEL_10_GPIO_NUM},// 4 81 | {RTC_IO_TOUCH_PAD1_REG, RTC_IO_TOUCH_PAD1_MUX_SEL_M, RTC_IO_TOUCH_PAD1_FUN_SEL_S, RTC_IO_TOUCH_PAD1_FUN_IE_M, RTC_IO_TOUCH_PAD1_RUE_M, RTC_IO_TOUCH_PAD1_RDE_M, RTC_IO_TOUCH_PAD1_SLP_SEL_M, RTC_IO_TOUCH_PAD1_SLP_IE_M, 0, RTC_IO_TOUCH_PAD1_HOLD_M, RTC_CNTL_TOUCH_PAD1_HOLD_FORCE_M, RTC_IO_TOUCH_PAD1_DRV_V, RTC_IO_TOUCH_PAD1_DRV_S, RTCIO_CHANNEL_11_GPIO_NUM},// 0 82 | {RTC_IO_TOUCH_PAD2_REG, RTC_IO_TOUCH_PAD2_MUX_SEL_M, RTC_IO_TOUCH_PAD2_FUN_SEL_S, RTC_IO_TOUCH_PAD2_FUN_IE_M, RTC_IO_TOUCH_PAD2_RUE_M, RTC_IO_TOUCH_PAD2_RDE_M, RTC_IO_TOUCH_PAD2_SLP_SEL_M, RTC_IO_TOUCH_PAD2_SLP_IE_M, 0, RTC_IO_TOUCH_PAD2_HOLD_M, RTC_CNTL_TOUCH_PAD2_HOLD_FORCE_M, RTC_IO_TOUCH_PAD2_DRV_V, RTC_IO_TOUCH_PAD2_DRV_S, RTCIO_CHANNEL_12_GPIO_NUM},// 2 83 | {RTC_IO_TOUCH_PAD3_REG, RTC_IO_TOUCH_PAD3_MUX_SEL_M, RTC_IO_TOUCH_PAD3_FUN_SEL_S, RTC_IO_TOUCH_PAD3_FUN_IE_M, RTC_IO_TOUCH_PAD3_RUE_M, RTC_IO_TOUCH_PAD3_RDE_M, RTC_IO_TOUCH_PAD3_SLP_SEL_M, RTC_IO_TOUCH_PAD3_SLP_IE_M, 0, RTC_IO_TOUCH_PAD3_HOLD_M, RTC_CNTL_TOUCH_PAD3_HOLD_FORCE_M, RTC_IO_TOUCH_PAD3_DRV_V, RTC_IO_TOUCH_PAD3_DRV_S, RTCIO_CHANNEL_13_GPIO_NUM},//15 84 | {RTC_IO_TOUCH_PAD4_REG, RTC_IO_TOUCH_PAD4_MUX_SEL_M, RTC_IO_TOUCH_PAD4_FUN_SEL_S, RTC_IO_TOUCH_PAD4_FUN_IE_M, RTC_IO_TOUCH_PAD4_RUE_M, RTC_IO_TOUCH_PAD4_RDE_M, RTC_IO_TOUCH_PAD4_SLP_SEL_M, RTC_IO_TOUCH_PAD4_SLP_IE_M, 0, RTC_IO_TOUCH_PAD4_HOLD_M, RTC_CNTL_TOUCH_PAD4_HOLD_FORCE_M, RTC_IO_TOUCH_PAD4_DRV_V, RTC_IO_TOUCH_PAD4_DRV_S, RTCIO_CHANNEL_14_GPIO_NUM},//13 85 | {RTC_IO_TOUCH_PAD5_REG, RTC_IO_TOUCH_PAD5_MUX_SEL_M, RTC_IO_TOUCH_PAD5_FUN_SEL_S, RTC_IO_TOUCH_PAD5_FUN_IE_M, RTC_IO_TOUCH_PAD5_RUE_M, RTC_IO_TOUCH_PAD5_RDE_M, RTC_IO_TOUCH_PAD5_SLP_SEL_M, RTC_IO_TOUCH_PAD5_SLP_IE_M, 0, RTC_IO_TOUCH_PAD5_HOLD_M, RTC_CNTL_TOUCH_PAD5_HOLD_FORCE_M, RTC_IO_TOUCH_PAD5_DRV_V, RTC_IO_TOUCH_PAD5_DRV_S, RTCIO_CHANNEL_15_GPIO_NUM},//12 86 | {RTC_IO_TOUCH_PAD6_REG, RTC_IO_TOUCH_PAD6_MUX_SEL_M, RTC_IO_TOUCH_PAD6_FUN_SEL_S, RTC_IO_TOUCH_PAD6_FUN_IE_M, RTC_IO_TOUCH_PAD6_RUE_M, RTC_IO_TOUCH_PAD6_RDE_M, RTC_IO_TOUCH_PAD6_SLP_SEL_M, RTC_IO_TOUCH_PAD6_SLP_IE_M, 0, RTC_IO_TOUCH_PAD6_HOLD_M, RTC_CNTL_TOUCH_PAD6_HOLD_FORCE_M, RTC_IO_TOUCH_PAD6_DRV_V, RTC_IO_TOUCH_PAD6_DRV_S, RTCIO_CHANNEL_16_GPIO_NUM},//14 87 | {RTC_IO_TOUCH_PAD7_REG, RTC_IO_TOUCH_PAD7_MUX_SEL_M, RTC_IO_TOUCH_PAD7_FUN_SEL_S, RTC_IO_TOUCH_PAD7_FUN_IE_M, RTC_IO_TOUCH_PAD7_RUE_M, RTC_IO_TOUCH_PAD7_RDE_M, RTC_IO_TOUCH_PAD7_SLP_SEL_M, RTC_IO_TOUCH_PAD7_SLP_IE_M, 0, RTC_IO_TOUCH_PAD7_HOLD_M, RTC_CNTL_TOUCH_PAD7_HOLD_FORCE_M, RTC_IO_TOUCH_PAD7_DRV_V, RTC_IO_TOUCH_PAD7_DRV_S, RTCIO_CHANNEL_17_GPIO_NUM},//27 88 | }; 89 | 90 | #define hulp_rtc_io_desc s_hulp_rtc_io_desc 91 | 92 | #else // CONFIG_HULP_MACRO_OPTIMISATIONS 93 | 94 | #define hulp_gtr(gpio_num) ((uint8_t)rtc_io_number_get(gpio_num)) 95 | 96 | #define RTC_WORD_OFFSET(x) ({ \ 97 | uint32_t* ptr_ = (uint32_t*)(&(x)); \ 98 | TRY_STATIC_ASSERT((uint32_t)(ptr_) % sizeof(uint32_t) == 0, (Not aligned)); \ 99 | TRY_STATIC_ASSERT((intptr_t)(ptr_) >= SOC_RTC_DATA_LOW && (intptr_t)(ptr_) < SOC_RTC_DATA_HIGH, (Not in RTC Slow Mem)); \ 100 | ((uint16_t)(ptr_ - RTC_SLOW_MEM)); \ 101 | }) 102 | 103 | #define hulp_rtc_io_desc rtc_io_desc 104 | 105 | #endif // CONFIG_HULP_MACRO_OPTIMISATIONS 106 | 107 | #endif /* HULP_MACRO_OPT_H */ 108 | -------------------------------------------------------------------------------- /src/hulp_mutex.h: -------------------------------------------------------------------------------- 1 | #ifndef HULP_MUTEX_H 2 | #define HULP_MUTEX_H 3 | 4 | /** 5 | * Mutex implementation based on Peterson's Algorithm, for managing access to resources shared between ULP and SoC 6 | */ 7 | 8 | #include "hulp.h" 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | /** 15 | * Mutex object for managing resource access between ULP and SoC 16 | * 17 | * ULP: 18 | * Take with M_MUTEX_TAKE (or M_MUTEX_TRY_TAKE), release with M_MUTEX_RELEASE 19 | * SoC: 20 | * Take with HULP_MUTEX_SOC_TAKE_WHILE, release with HULP_MUTEX_SOC_RELEASE 21 | */ 22 | typedef struct { 23 | ulp_var_t ulp_request; 24 | ulp_var_t soc_request; 25 | ulp_var_t soc_priority; 26 | } ulp_mutex_t; 27 | 28 | /** 29 | * ULP: Flag mutex wanted 30 | * 31 | * mutex: ulp_mutex_t object 32 | * reg_nonzero: R0-R3 which has any known value != 0 33 | * reg_nonzero_val: The known value in specified register 34 | */ 35 | #define M_MUTEX_REQUEST(_mutex, _reg_nonzero, _reg_nonzero_val) \ 36 | I_PUTO(_reg_nonzero, _reg_nonzero, _reg_nonzero_val, (_mutex).ulp_request), \ 37 | I_PUTO(_reg_nonzero, _reg_nonzero, _reg_nonzero_val, (_mutex).soc_priority) 38 | 39 | /** 40 | * ULP: Check if mutex held (after M_MUTEX_REQUEST) 41 | * R0 == 0 on success 42 | * 43 | * mutex: ulp_mutex_t object 44 | * reg_any: R1-R3 which has any known value 45 | * reg_any_val: The known value in specified register 46 | */ 47 | #define M_MUTEX_CHECK(_mutex, _reg_any, _reg_any_val) \ 48 | I_GETO(R0, _reg_any, _reg_any_val, (_mutex).soc_request), \ 49 | I_BL(2, 1), \ 50 | I_GETO(R0, _reg_any, _reg_any_val, (_mutex).soc_priority) 51 | 52 | /** 53 | * ULP: Take mutex (non-blocking) 54 | * R0 == 0 on success 55 | * Must release (M_MUTEX_RELEASE) regardless of result 56 | * 57 | * mutex: ulp_mutex_t object 58 | * reg_nonzero: R1-R3 which has any known value != 0 59 | * reg_nonzero_val: The known value in specified register 60 | */ 61 | #define M_MUTEX_TRY_TAKE(_mutex, _reg_nonzero, _reg_nonzero_val) \ 62 | M_MUTEX_REQUEST(_mutex, _reg_nonzero, _reg_nonzero_val), \ 63 | M_MUTEX_CHECK(_mutex, _reg_nonzero, _reg_nonzero_val) 64 | 65 | /** 66 | * ULP: Take mutex 67 | * 68 | * mutex: ulp_mutex_t object 69 | * reg_nonzero: R1-R3 which has any known value != 0 70 | * reg_nonzero_val: The known value in specified register 71 | */ 72 | #define M_MUTEX_TAKE(_mutex, _reg_nonzero, _reg_nonzero_val) \ 73 | M_MUTEX_TRY_TAKE(_mutex, _reg_nonzero, _reg_nonzero_val), \ 74 | I_BGE(-3, 1) 75 | 76 | /** 77 | * ULP: Release mutex 78 | * 79 | * mutex: ulp_mutex_t object previously taken 80 | * reg_zero: R0-R3 which has known value of 0 81 | */ 82 | #define M_MUTEX_RELEASE(_mutex, _reg_zero) \ 83 | I_PUT(_reg_zero, _reg_zero, (_mutex).ulp_request) 84 | 85 | /** 86 | * SoC: Flag mutex wanted 87 | */ 88 | static inline void hulp_mutex_soc_request(volatile ulp_mutex_t *mutex) 89 | { 90 | mutex->soc_request.val = 1; 91 | mutex->soc_priority.val = 0; 92 | } 93 | 94 | /** 95 | * SoC: Check if mutex held (after hulp_mutex_soc_request) 96 | */ 97 | static inline bool hulp_mutex_soc_check(const volatile ulp_mutex_t *mutex) 98 | { 99 | return mutex->ulp_request.val == 0 || mutex->soc_priority.val != 0; 100 | } 101 | 102 | /** 103 | * SoC: Release mutex 104 | */ 105 | static inline void hulp_mutex_soc_release(volatile ulp_mutex_t *mutex) 106 | { 107 | mutex->soc_request.val = 0; 108 | } 109 | 110 | /** 111 | * SoC: Take mutex 112 | * 113 | * mutex: pointer to ulp_mutex_t object 114 | * 115 | * eg. 116 | * HULP_MUTEX_SOC_TAKE_WHILE(&ulp_led_mutex) { 117 | * // Do this while waiting for ULP to release 118 | * vTaskDelay(1); // or ets_delay_us(10), etc 119 | * } 120 | * // Critical Section 121 | * // ... 122 | * HULP_MUTEX_SOC_RELEASE(&ulp_led_mutex); 123 | */ 124 | #define HULP_MUTEX_SOC_TAKE_WHILE(_mutex) \ 125 | hulp_mutex_soc_request(_mutex); \ 126 | while(!hulp_mutex_soc_check(_mutex)) 127 | 128 | #define HULP_MUTEX_SOC_RELEASE hulp_mutex_soc_release 129 | 130 | #ifdef __cplusplus 131 | } 132 | #endif 133 | 134 | #endif /* HULP_MUTEX_H */ 135 | -------------------------------------------------------------------------------- /src/hulp_regwr.c: -------------------------------------------------------------------------------- 1 | #include "hulp_regwr.h" 2 | 3 | #include "hulp.h" 4 | 5 | #include "hulp_config.h" 6 | 7 | // Layout of RTC Slow Memory if using regwr 8 | struct hulp_regwr_rtc_slow_map_check { 9 | // Normal usage 10 | ulp_insn_t normal1[HULP_REGWR_WORK_AREA_START]; 11 | // Each register write bit range will write to 3 particular (possibly overlapping) instructions in this region. 12 | // May be used as temporary storage between register writes (register write will modify the corresponding region with its required instructions) 13 | // Unused regions may be used as normal. 14 | // eg. if only one range [18:16] is used, then almost all of this is free for normal usage. If all ranges are used, all is reserved. 15 | ulp_insn_t regwr_work_area[HULP_REGWR_WORK_AREA_END - HULP_REGWR_WORK_AREA_START]; 16 | // Normal usage 17 | ulp_insn_t normal2[HULP_WR_REG_GEN_ENTRY_HAS_RET - HULP_REGWR_WORK_AREA_END]; 18 | // Reserved 19 | ulp_insn_t regwr_gen_wr[HULP_WR_REG_GEN_ENTRY_HAS_RET_COUNT]; 20 | // Normal usage 21 | ulp_insn_t normal3[(HULP_WR_REG_GEN_ENTRY) - (HULP_WR_REG_GEN_ENTRY_HAS_RET + HULP_WR_REG_GEN_ENTRY_HAS_RET_COUNT)]; 22 | // Reserved 23 | ulp_insn_t regwr_gen_ret[HULP_WR_REG_GEN_ENTRY_COUNT]; 24 | // Normal usage 25 | ulp_insn_t normal4[((CONFIG_ESP32_ULP_COPROC_RESERVE_MEM / 4) > (HULP_WR_REG_GEN_ENTRY + HULP_WR_REG_GEN_ENTRY_COUNT)) ? (CONFIG_ESP32_ULP_COPROC_RESERVE_MEM / sizeof(ulp_insn_t)) - (HULP_WR_REG_GEN_ENTRY + HULP_WR_REG_GEN_ENTRY_COUNT) : 0]; 26 | }; 27 | 28 | _Static_assert(offsetof(struct hulp_regwr_rtc_slow_map_check, regwr_work_area) == (HULP_REGWR_WORK_AREA_START * sizeof(ulp_insn_t)), "wrong offset: work area"); 29 | _Static_assert(offsetof(struct hulp_regwr_rtc_slow_map_check, regwr_gen_wr) == (HULP_WR_REG_GEN_ENTRY_HAS_RET * sizeof(ulp_insn_t)), "wrong offset: gen wr"); 30 | _Static_assert(offsetof(struct hulp_regwr_rtc_slow_map_check, regwr_gen_ret) == (HULP_WR_REG_GEN_ENTRY * sizeof(ulp_insn_t)), "wrong offset: gen ret"); 31 | 32 | esp_err_t hulp_regwr_load_generate_ret(void) 33 | { 34 | /** 35 | * Generates a I_BXR(R3) instruction at the end of the provided work area 36 | * 37 | * Executing at 1025 generates a I_BXR instruction 38 | * -> I_BXR(x) 39 | * [1:0] determines the register 40 | * -> I_BXR(R3) 41 | */ 42 | const ulp_insn_t program[] = { 43 | I_MOVI(R1, R3), // Constant == 3 44 | I_ST(R1, R0, (HULP_REGWR_WORK_COUNT - 1)), // MUST BE AT 1025 45 | I_BXI(HULP_WR_REG_GEN_ENTRY_HAS_RET), 46 | }; 47 | _Static_assert(sizeof(program) / sizeof(program[0]) == HULP_WR_REG_GEN_ENTRY_COUNT, "program size != reserved"); 48 | 49 | size_t program_size = sizeof(program) / sizeof(program[0]); 50 | return ulp_process_macros_and_load(HULP_WR_REG_GEN_ENTRY, program, &program_size); 51 | } 52 | 53 | esp_err_t hulp_regwr_load_generate_wr(void) 54 | { 55 | /** 56 | * Generates a ST instruction at the provided work area address in R0, then branches to it to execute 57 | * 58 | * Executing at 832 generates a ST instruction 59 | * -> I_ST(x, x, x) 60 | * [15:10] determine the offset value of the generated ST instruction, ie. (1 << 10) -> offset 1 61 | * -> I_ST(x, x, 1) 62 | * [3:2] is Rdest 63 | * -> I_ST(x, R0, 1) 64 | * [1:0] is Rsrc 65 | * -> I_ST(R2, R0, 1) 66 | * 67 | * Executing this generated ST at the correct PC produces a WR_REG with the required bit range, and R2's value determines its register/val 68 | */ 69 | const ulp_insn_t program[] = { 70 | I_MOVI(R1, (1 << 10) | (R0 << 2) | (R2 << 0)), 71 | I_ST(R1, R0, 0), // MUST BE AT 832 72 | I_BXR(R0), 73 | }; 74 | _Static_assert(sizeof(program) / sizeof(program[0]) == HULP_WR_REG_GEN_ENTRY_HAS_RET_COUNT, "program size != reserved"); 75 | 76 | size_t program_size = sizeof(program) / sizeof(program[0]); 77 | return ulp_process_macros_and_load(HULP_WR_REG_GEN_ENTRY_HAS_RET, program, &program_size); 78 | } 79 | 80 | esp_err_t hulp_regwr_prepare_offset(uint32_t offset) 81 | { 82 | const ulp_insn_t r3_return = I_BXR(R3); 83 | RTC_SLOW_MEM[offset + HULP_REGWR_WORK_COUNT - 1] = r3_return.instruction; 84 | return ESP_OK; 85 | } 86 | -------------------------------------------------------------------------------- /src/hulp_regwr.h: -------------------------------------------------------------------------------- 1 | #ifndef HULP_REGWR_H 2 | #define HULP_REGWR_H 3 | 4 | #include "hulp.h" 5 | 6 | /** 7 | * Each combination of high_bit and low_bit require a few words at a specific PC 8 | * 9 | * Due to limitations of this method, low_bit may only be 0, 8, 16, or 24 10 | */ 11 | #define HULP_REGWR_WORK_OFFSET(low_bit, high_bit) \ 12 | (128 + (high_bit) * 4 + (low_bit) / 8) 13 | 14 | /** 15 | * At each of these PCs, this is the number of instructions required to implement the register write 16 | */ 17 | #define HULP_REGWR_WORK_COUNT 3 18 | 19 | #define HULP_REGWR_WORK_AREA_START (HULP_REGWR_WORK_OFFSET(0,0)) 20 | #define HULP_REGWR_WORK_AREA_END (HULP_REGWR_WORK_OFFSET(24,31) + HULP_REGWR_WORK_COUNT) 21 | 22 | #define HULP_REGWR_VAL_SHIFT 10 23 | #define HULP_REGWR_IMM_VAL(register, value) \ 24 | (((value) << HULP_REGWR_VAL_SHIFT) | (SOC_REG_TO_ULP_PERIPH_SEL(register) << 8) | ((register & 0xFF) / sizeof(uint32_t))) 25 | 26 | /** 27 | * Branch here to write the register with the prepared parameters 28 | */ 29 | #define HULP_WR_REG_GEN_ENTRY 1024 30 | 31 | /** 32 | * Number of instructions to be reserved at above address 33 | */ 34 | #define HULP_WR_REG_GEN_ENTRY_COUNT 3 35 | 36 | /** 37 | * Branch here to write the register with the prepared parameters. 38 | * 39 | * Return instruction must exist. Generally, prefer HULP_WR_REG_GEN_ENTRY. 40 | */ 41 | #define HULP_WR_REG_GEN_ENTRY_HAS_RET 831 42 | 43 | /** 44 | * Number of instructions to be reserved at above address 45 | */ 46 | #define HULP_WR_REG_GEN_ENTRY_HAS_RET_COUNT 3 47 | 48 | /** 49 | * Sets instructions to generate a register write instruction 50 | */ 51 | esp_err_t hulp_regwr_load_generate_wr(void); 52 | 53 | /** 54 | * Sets instructions to generate a return instruction from any register write range 55 | */ 56 | esp_err_t hulp_regwr_load_generate_ret(void); 57 | 58 | /** 59 | * Prepares the area required by a particular [high:low] register write with a return instruction so it may be used 60 | */ 61 | esp_err_t hulp_regwr_prepare_offset(uint32_t offset); 62 | 63 | #endif /* HULP_REGWR_H */ 64 | -------------------------------------------------------------------------------- /src/hulp_touch.c: -------------------------------------------------------------------------------- 1 | #include "hulp_touch.h" 2 | 3 | #include "esp_log.h" 4 | #include "driver/touch_pad.h" 5 | #include "soc/touch_sensor_channel.h" 6 | #include "soc/touch_sensor_periph.h" 7 | #include "hal/touch_sensor_hal.h" 8 | 9 | #include "hulp_config.h" 10 | 11 | static const char* TAG = "HULP-TCH"; 12 | 13 | int hulp_touch_get_pad_num(gpio_num_t pin) 14 | { 15 | for(int i = 0; i < SOC_TOUCH_SENSOR_NUM; ++i) 16 | { 17 | if(touch_sensor_channel_io_map[i] == pin) return i; 18 | } 19 | ESP_LOGW(TAG, "no touch pad for gpio %d", pin); 20 | return -1; 21 | } 22 | 23 | esp_err_t hulp_configure_touch_controller(const hulp_touch_controller_config_t *config) 24 | { 25 | if(!config) 26 | { 27 | return ESP_ERR_INVALID_ARG; 28 | } 29 | 30 | esp_err_t err; 31 | if( 32 | ESP_OK != (err = touch_pad_init()) || 33 | ESP_OK != (err = touch_pad_set_fsm_mode(TOUCH_FSM_MODE_SW)) || 34 | ESP_OK != (err = touch_pad_set_voltage(config->high_voltage, config->low_voltage, config->attenuation)) 35 | ) 36 | { 37 | ESP_LOGE(TAG, "[%s] err (0x%x)", __func__, err); 38 | return err; 39 | } 40 | 41 | touch_hal_set_meas_time(config->fastclk_meas_cycles); 42 | return ESP_OK; 43 | } 44 | 45 | esp_err_t hulp_configure_touch_pin(gpio_num_t touch_gpio, const hulp_touch_pin_config_t *config) 46 | { 47 | if(!config) 48 | { 49 | return ESP_ERR_INVALID_ARG; 50 | } 51 | 52 | int touch_pad_num = hulp_touch_get_pad_num(touch_gpio); 53 | if(touch_pad_num < 0) 54 | { 55 | ESP_LOGE(TAG, "invalid touch pin (%d)", touch_gpio); 56 | return ESP_ERR_INVALID_ARG; 57 | } 58 | 59 | esp_err_t err; 60 | if( 61 | ESP_OK != (err = touch_pad_io_init(touch_pad_num)) || 62 | ESP_OK != (err = touch_pad_set_cnt_mode(touch_pad_num, config->slope, config->tie_opt)) || 63 | ESP_OK != (err = touch_pad_set_group_mask(0, 0, 1 << touch_pad_num)) 64 | ) 65 | { 66 | ESP_LOGE(TAG, "[%s] err (0x%x)", __func__, err); 67 | return err; 68 | } 69 | return ESP_OK; 70 | } 71 | -------------------------------------------------------------------------------- /src/hulp_touch.h: -------------------------------------------------------------------------------- 1 | #ifndef HULP_TOUCH_H 2 | #define HULP_TOUCH_H 3 | 4 | #include "driver/gpio.h" 5 | #include "driver/touch_pad.h" 6 | 7 | #include "hulp.h" 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | #define SWAPPED_TOUCH_INDEX(x) ((x) == TOUCH_PAD_NUM9 ? TOUCH_PAD_NUM8 : ((x) == TOUCH_PAD_NUM8 ? TOUCH_PAD_NUM9 : (x))) 14 | 15 | typedef struct { 16 | uint16_t fastclk_meas_cycles; 17 | touch_high_volt_t high_voltage; 18 | touch_low_volt_t low_voltage; 19 | touch_volt_atten_t attenuation; 20 | } hulp_touch_controller_config_t; 21 | 22 | #define HULP_TOUCH_CONTROLLER_CONFIG_DEFAULT() { \ 23 | .fastclk_meas_cycles = TOUCH_PAD_MEASURE_CYCLE_DEFAULT, \ 24 | .high_voltage = TOUCH_HVOLT_2V4, \ 25 | .low_voltage = TOUCH_LVOLT_0V8, \ 26 | .attenuation = TOUCH_HVOLT_ATTEN_1V5, \ 27 | } 28 | 29 | /** 30 | * Prepare touch controller for ULP control. 31 | * Do this once, then configure each pin using hulp_configure_touch_pin. 32 | * fastclk_meas_cycles: measurement time in fastclk (8MHz) cycles. (65535 = 8.19ms) 33 | * Shorter = lower power consumption; Longer = higher counts (better possible signal/noise filtering) 34 | */ 35 | esp_err_t hulp_configure_touch_controller(const hulp_touch_controller_config_t *config); 36 | 37 | typedef struct { 38 | touch_cnt_slope_t slope; 39 | touch_tie_opt_t tie_opt; 40 | } hulp_touch_pin_config_t; 41 | 42 | #define HULP_TOUCH_PIN_CONFIG_DEFAULT() { \ 43 | .slope = TOUCH_PAD_SLOPE_DEFAULT, \ 44 | .tie_opt = TOUCH_PAD_TIE_OPT_DEFAULT, \ 45 | } 46 | 47 | /** 48 | * Initialise and configure a pin for touch function. 49 | */ 50 | esp_err_t hulp_configure_touch_pin(gpio_num_t touch_gpio, const hulp_touch_pin_config_t *config); 51 | 52 | 53 | #define I_TOUCH_GET_PAD_THRESHOLD(touch_num) \ 54 | I_RD_REG((SENS_SAR_TOUCH_THRES1_REG + (4 * ((uint8_t)SWAPPED_TOUCH_INDEX(touch_num)/2))), (uint8_t)(SWAPPED_TOUCH_INDEX(touch_num)%2 ? 0 : 16), (uint8_t)(SWAPPED_TOUCH_INDEX(touch_num)%2 ? 15 : 31)) 55 | 56 | #define I_TOUCH_GET_GPIO_THRESHOLD(gpio_num) \ 57 | I_TOUCH_GET_PAD_THRESHOLD(hulp_touch_get_pad_num(gpio_num)) 58 | 59 | #define I_TOUCH_GET_PAD_VALUE(touch_num) \ 60 | I_RD_REG((SENS_SAR_TOUCH_OUT1_REG + (4 * ((uint8_t)SWAPPED_TOUCH_INDEX(touch_num)/2))), (uint8_t)((SWAPPED_TOUCH_INDEX(touch_num)%2) ? 0 : 16), (uint8_t)((SWAPPED_TOUCH_INDEX(touch_num)%2) ? 15 : 31)) 61 | 62 | #define I_TOUCH_GET_GPIO_VALUE(gpio_num) \ 63 | I_TOUCH_GET_PAD_VALUE(hulp_touch_get_pad_num(gpio_num)) 64 | 65 | #define I_TOUCH_GET_DONE_BIT() \ 66 | I_RD_REG_BIT(SENS_SAR_TOUCH_CTRL2_REG, SENS_TOUCH_MEAS_DONE_S) 67 | 68 | #define M_TOUCH_WAIT_DONE() \ 69 | I_TOUCH_GET_DONE_BIT(), \ 70 | I_BL(-1,1) 71 | 72 | #define M_TOUCH_BEGIN() \ 73 | I_WR_REG_BIT(SENS_SAR_TOUCH_CTRL2_REG, SENS_TOUCH_START_EN_S, 0), \ 74 | I_WR_REG_BIT(SENS_SAR_TOUCH_CTRL2_REG, SENS_TOUCH_START_EN_S, 1) 75 | 76 | //Junk: 77 | #define I_TOUCH_EN(gpio_num, enable) \ 78 | I_WR_REG_BIT(SENS_SAR_TOUCH_ENABLE_REG, (uint8_t)(SENS_TOUCH_PAD_WORKEN_S + SWAPPED_TOUCH_INDEX(hulp_touch_get_pad_num(gpio_num))), enable ? 1 : 0) 79 | 80 | #define I_TOUCH_INT_SET1_EN(gpio_num, enable) \ 81 | I_WR_REG_BIT(SENS_SAR_TOUCH_ENABLE_REG, (uint8_t)(SENS_TOUCH_PAD_OUTEN1_S + SWAPPED_TOUCH_INDEX(hulp_touch_get_pad_num(gpio_num))), enable ? 1 : 0) 82 | 83 | #define I_TOUCH_INT_SET2_EN(gpio_num, enable) \ 84 | I_WR_REG_BIT(SENS_SAR_TOUCH_ENABLE_REG, (uint8_t)(SENS_TOUCH_PAD_OUTEN2_S + SWAPPED_TOUCH_INDEX(hulp_touch_get_pad_num(gpio_num))), enable ? 1 : 0) 85 | 86 | #define I_TOUCH_INT_SET_SOURCE(touch_trigger_source) /*TOUCH_TRIGGER_SOURCE_BOTH / TOUCH_TRIGGER_SOURCE_SET1*/ \ 87 | I_WR_REG_BIT(SENS_SAR_TOUCH_CTRL1_REG, SENS_TOUCH_OUT_1EN_S, TOUCH_TRIGGER_SOURCE) 88 | 89 | #define M_TOUCH_SW_READ_PAD_END(touch_num) \ 90 | I_WR_REG_BIT(SENS_SAR_TOUCH_ENABLE_REG, (uint8_t)(SENS_TOUCH_PAD_OUTEN1_S + SWAPPED_TOUCH_INDEX(touch_num)), 0), \ 91 | I_WR_REG_BIT(SENS_SAR_TOUCH_ENABLE_REG, (uint8_t)(SENS_TOUCH_PAD_OUTEN2_S + SWAPPED_TOUCH_INDEX(touch_num)), 0), \ 92 | I_WR_REG_BIT(SENS_SAR_TOUCH_ENABLE_REG, (uint8_t)(SENS_TOUCH_PAD_WORKEN_S + SWAPPED_TOUCH_INDEX(touch_num)), 0) 93 | 94 | #define M_TOUCH_SW_READ_GPIO_END(gpio_num) \ 95 | M_TOUCH_SW_READ_PAD_END(hulp_touch_get_pad_num(gpio_num)) 96 | 97 | #define M_TOUCH_SW_READ_PAD_BEGIN_V(touch_num) \ 98 | I_WR_REG_BIT(SENS_SAR_TOUCH_ENABLE_REG, (uint8_t)(SENS_TOUCH_PAD_OUTEN1_S + SWAPPED_TOUCH_INDEX(touch_num)), 1), \ 99 | I_WR_REG_BIT(SENS_SAR_TOUCH_ENABLE_REG, (uint8_t)(SENS_TOUCH_PAD_OUTEN2_S + SWAPPED_TOUCH_INDEX(touch_num)), 1), \ 100 | I_WR_REG_BIT(SENS_SAR_TOUCH_ENABLE_REG, (uint8_t)(SENS_TOUCH_PAD_WORKEN_S + SWAPPED_TOUCH_INDEX(touch_num)), 1), \ 101 | I_WR_REG_BIT(SENS_SAR_TOUCH_CTRL2_REG, SENS_TOUCH_START_EN_S, 0), \ 102 | I_WR_REG_BIT(SENS_SAR_TOUCH_CTRL2_REG, SENS_TOUCH_START_EN_S, 1) 103 | 104 | #define M_TOUCH_SW_READ_GPIO_BEGIN_V(gpio_num) \ 105 | M_TOUCH_SW_READ_PAD_BEGIN_V(hulp_touch_get_pad_num(gpio_num)) 106 | 107 | /** 108 | * Internal. Do not use directly. 109 | */ 110 | int hulp_touch_get_pad_num(gpio_num_t pin); 111 | 112 | #ifdef __cplusplus 113 | } 114 | #endif 115 | 116 | #endif /* HULP_TOUCH_H */ 117 | -------------------------------------------------------------------------------- /src/hulp_types.h: -------------------------------------------------------------------------------- 1 | #ifndef HULP_TYPES_H 2 | #define HULP_TYPES_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | typedef union { 11 | struct { 12 | uint32_t data : 16; 13 | uint32_t reg_off : 2; 14 | uint32_t st : 3; 15 | uint32_t pc : 11; 16 | }; 17 | struct { 18 | uint16_t val; 19 | uint16_t meta; 20 | }; 21 | struct { 22 | uint8_t val_bytes[2]; 23 | uint8_t meta_bytes[2]; 24 | }; 25 | struct { 26 | uint8_t bytes[4]; 27 | }; 28 | ulp_insn_t insn; 29 | uint32_t word; 30 | } ulp_var_t; 31 | 32 | _Static_assert(sizeof(ulp_var_t) == 4, "ulp_var_t size should be 4 bytes"); 33 | 34 | #ifdef __cplusplus 35 | } 36 | #endif 37 | 38 | #endif /* HULP_TYPES_H */ 39 | -------------------------------------------------------------------------------- /src/hulp_uart.c: -------------------------------------------------------------------------------- 1 | #include "hulp_uart.h" 2 | 3 | #include "hulp_types.h" 4 | 5 | static void set_len(ulp_var_t *hulp_string, uint8_t len) 6 | { 7 | hulp_string[0].val = (hulp_string[0].val & 0xFF00) | len; 8 | } 9 | 10 | static uint8_t get_len(ulp_var_t *hulp_string) 11 | { 12 | return (hulp_string[0].val & 0x00FF); 13 | } 14 | 15 | static char char_get(ulp_var_t *hulp_string, uint8_t index) 16 | { 17 | return (char)((hulp_string[1 + index / 2].val >> ((index % 2) * 8)) & 0xFF); 18 | } 19 | static void char_set(ulp_var_t *hulp_string, uint8_t index, char c) 20 | { 21 | hulp_string[1 + index / 2].val = (hulp_string[1 + index / 2].val & ~(0xFF << ((index % 2) * 8))) | ((uint16_t)c << ((index % 2) * 8)); 22 | } 23 | 24 | int hulp_uart_string_set(ulp_var_t *hulp_string, size_t len, const char* str) 25 | { 26 | if(!hulp_string || len < 2 || !str) 27 | { 28 | return -1; 29 | } 30 | 31 | size_t capacity = (len - 1) * 2; 32 | uint8_t index = 0; 33 | while(*str != '\0' && index < capacity) 34 | { 35 | char_set(hulp_string, index, *str); 36 | ++str; 37 | ++index; 38 | } 39 | set_len(hulp_string, index); 40 | return index; 41 | } 42 | 43 | int hulp_uart_string_get(ulp_var_t *hulp_string, char* buffer, size_t buffer_size, bool clear) 44 | { 45 | if(!hulp_string || !buffer || !buffer_size) 46 | { 47 | return -1; 48 | } 49 | 50 | int string_len = get_len(hulp_string); 51 | if(buffer_size <= string_len) 52 | { 53 | return -1; 54 | } 55 | 56 | int index = 0; 57 | while(index < string_len) 58 | { 59 | *buffer = char_get(hulp_string, index); 60 | ++buffer; 61 | ++index; 62 | } 63 | *buffer = '\0'; 64 | if(clear) 65 | { 66 | set_len(hulp_string, 0); 67 | } 68 | return string_len; 69 | } -------------------------------------------------------------------------------- /src/hulp_uart.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef HULP_UART_H 3 | #define HULP_UART_H 4 | 5 | #include "driver/gpio.h" 6 | #include "driver/rtc_io.h" 7 | #include "soc/rtc.h" 8 | 9 | #include "hulp.h" 10 | 11 | #include "hulp_config.h" 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | #define HULP_UART_STRING_INIT_SIZE(capacity) {{.val = (capacity) << 8}} 18 | 19 | /** 20 | * Helper to declare an array of ulp_var_t to be used as a buffer with the given capacity 21 | * eg. RTC_SLOW_ATTR ulp_var_t my_buffer HULP_UART_STRING_BUFFER(8); // Can hold string of max length 8 22 | */ 23 | #define HULP_UART_STRING_BUFFER(required_capacity) [(1 + ((required_capacity) / 2))] = HULP_UART_STRING_INIT_SIZE(required_capacity) 24 | 25 | /** 26 | * Helper to declare an array of ulp_var_t of sufficient size to hold the given string literal. 27 | * hulp_uart_string_set must be used to then populate the array. 28 | * eg. 29 | * #define MY_ULP_STRING "Hello world" 30 | * RTC_SLOW_ATTR ulp_var_t my_ulp_string HULP_UART_STRING_RESERVE(MY_ULP_STRING); // Large enough to hold MY_ULP_STRING 31 | * hulp_uart_string_set(my_ulp_string, sizeof(my_ulp_string) / sizeof(ulp_var_t), MY_ULP_STRING); // Set MY_ULP_STRING 32 | */ 33 | #define HULP_UART_STRING_RESERVE(target_str) [(1 + (sizeof(target_str) / 2))] = HULP_UART_STRING_INIT_SIZE(sizeof(target_str)) 34 | 35 | /** 36 | * Set the array of ulp_var_t to the provided string, formatted for usage by the ULP. 37 | * len: Array length of ulp_var_t, ie. (sizeof(my_string) / sizeof(ulp_var_t)) 38 | * 39 | * Returns -1 if any errors 40 | */ 41 | int hulp_uart_string_set(ulp_var_t *hulp_string, size_t len, const char* str); 42 | 43 | /** 44 | * Get the string from the array of ulp_var_t into the provided buffer. 45 | * 46 | * Returns -1 if any errors 47 | */ 48 | int hulp_uart_string_get(ulp_var_t *hulp_string, char* buffer, size_t buffer_size, bool clear); 49 | 50 | /** 51 | * ULP subroutine to receive data over UART, until receiving the provided termination byte or the buffer is full. 52 | * 53 | * Prep: 54 | * Set R1 = offset of ULP string eg. I_MOVO(R1, ulp_receive_buffer) 55 | * Put return address in R3. eg. M_MOVL(R3, LABEL_RETURN_POINT) 56 | * Branch to label_entry eg. M_BX(LABEL_UART_RX) 57 | */ 58 | #define M_INCLUDE_UART_RX(label_entry, baud_rate, rx_gpio, termination_char) \ 59 | M_INCLUDE_UART_RX_(label_entry, baud_rate, rx_gpio, R1, R2, R3, termination_char) 60 | 61 | #define M_INCLUDE_UART_RX_(label_entry, baud_rate, rx_gpio, reg_string_ptr, reg_scr, reg_return, termination_char) \ 62 | M_LABEL(label_entry), \ 63 | M_MOVL(R0,label_entry), /*Need all the registers we can get for this one, so the return address */ \ 64 | I_ST(reg_return,R0,34), /* is saved temporarily*/ \ 65 | I_MOVI(reg_scr,0), /*reg_scr is used to count received bytes*/ \ 66 | I_LD(R0,reg_string_ptr,0), /*Begin loop for each byte: Load the metadata*/ \ 67 | I_RSHI(R0,R0,8), /* Isolate the buffer size from it*/ \ 68 | I_SUBR(R0,R0,reg_scr), /* Then compare buffer size with current length to check if full*/ \ 69 | I_BL(21, 1), \ 70 | I_GPIO_READ(rx_gpio), /*Wait here until pin goes low (start bit)*/ \ 71 | I_BGE(-1,1), \ 72 | I_STAGE_RST(), \ 73 | I_DELAY((uint16_t)(hulp_get_fast_clk_freq() / 2 / (baud_rate) + 34 - 36)), \ 74 | I_DELAY((uint16_t)(hulp_get_fast_clk_freq() / (baud_rate) - 34)), \ 75 | I_GPIO_READ(rx_gpio), /*Read the new bit, make room for it in another reg, and OR it in*/ \ 76 | I_RSHI(reg_return,reg_return,1), \ 77 | I_LSHI(R0,R0,15), \ 78 | I_ORR(reg_return,reg_return,R0), \ 79 | I_STAGE_INC(1), \ 80 | I_JUMPS(-6, 8, JUMPS_LT), /*Repeat 8 times*/ \ 81 | I_RSHI(R0,reg_scr,1), /*Store the byte. ulpstring =one word metadata, then 2 chars in every */ \ 82 | I_ADDR(R0,reg_string_ptr,R0), /* word thereafter, so offset = 1+length/2 (ie. length >> 1, */ \ 83 | I_ST(reg_return,R0,1), /* add that to string ptr, then I_ST with 1 offset) */ \ 84 | I_GPIO_READ(rx_gpio), /*Wait here until pin goes high to sync with stop bit*/ \ 85 | I_BL(-1,1), \ 86 | I_SUBI(R0,reg_return,(termination_char)<<8), /*Most recent byte is in upper 8 bits, so subtract (termination_char)<<8*/ \ 87 | I_BL(3,1<<8), /*If upper bits are 8b0 then byte matches termination_char so end */ \ 88 | I_ADDI(reg_scr,reg_scr,1), /* else increment length and loop back to beginning of new byte */ \ 89 | I_BGE(-23,0), \ 90 | I_LD(reg_return,reg_string_ptr,0), /*Load the metadata (termination char / buffer full branches here)*/ \ 91 | I_ANDI(reg_return,reg_return,0xFF<<8), /*Update metadata with received length*/ \ 92 | I_ORR(reg_return,reg_return,reg_scr), \ 93 | I_ST(reg_return,reg_string_ptr,0), /* This I_ST also sets updated flag on metadata var */ \ 94 | M_MOVL(reg_return,label_entry), /*Now need to load the return address saved at the beginning, and return */ \ 95 | I_LD(reg_return,reg_return,34), \ 96 | I_BXR(reg_return), \ 97 | I_HALT() /*reserved word (control should never reach here)*/ 98 | 99 | /** 100 | * ULP subroutine to transmit data over UART. 101 | * 102 | * Prep: 103 | * Set R1 = offset of ULP string (eg. I_MOVO(R1, ulp_receive_buffer),) 104 | * Put return address in R3. 105 | * Branch to label_entry 106 | */ 107 | #define M_INCLUDE_UART_TX(label_entry, baud_rate, tx_gpio) \ 108 | M_INCLUDE_UART_TX_(label_entry, baud_rate, tx_gpio, R1, R2, R3) 109 | 110 | #if CONFIG_HULP_UART_TX_OD 111 | #define I_HULP_UART_TX_HIGH(pin) I_GPIO_OUTPUT_DIS(pin) 112 | #define I_HULP_UART_TX_LOW(pin) I_GPIO_OUTPUT_EN(pin) 113 | #else 114 | #define I_HULP_UART_TX_HIGH(pin) I_GPIO_SET(pin, 1) 115 | #define I_HULP_UART_TX_LOW(pin) I_GPIO_SET(pin, 0) 116 | #endif 117 | 118 | #define M_INCLUDE_UART_TX_(label_entry, baud_rate, tx_gpio, reg_string_ptr, reg_scr, reg_return) \ 119 | M_LABEL(label_entry), \ 120 | M_MOVL(R0,label_entry),\ 121 | I_ST(reg_return,R0,31),\ 122 | I_LD(reg_return,reg_string_ptr,0),\ 123 | I_ANDI(reg_return,reg_return,255),\ 124 | I_ADDI(reg_string_ptr, reg_string_ptr, 1),\ 125 | I_LD(reg_scr,reg_string_ptr,0),\ 126 | I_STAGE_RST(),\ 127 | I_SUBI(reg_return,reg_return,1),\ 128 | I_MOVR(R0,reg_return),\ 129 | I_BGE(19, 65535),\ 130 | I_HULP_UART_TX_LOW((tx_gpio)),\ 131 | I_DELAY((uint16_t)(hulp_get_fast_clk_freq() / (baud_rate) - 19)), /* Start bit, minus a little overhead */ \ 132 | I_ANDI(R0,reg_scr,1),\ 133 | I_BL(12, 1),\ 134 | I_HULP_UART_TX_HIGH((tx_gpio)),\ 135 | I_DELAY((uint16_t)((hulp_get_fast_clk_freq() / (baud_rate)) - 42)),\ 136 | I_RSHI(reg_scr,reg_scr,1),\ 137 | I_STAGE_INC(1),\ 138 | I_JUMPS(2, 9, JUMPS_LT),\ 139 | I_JUMPS(-7, 16, JUMPS_LT),\ 140 | I_JUMPS(-8, 8, JUMPS_LT),\ 141 | I_HULP_UART_TX_HIGH((tx_gpio)),\ 142 | I_DELAY((uint16_t)(hulp_get_fast_clk_freq() / (baud_rate) - 0)),\ 143 | I_JUMPS(-16, 9, JUMPS_LT),\ 144 | I_BGE(-20, 0),\ 145 | I_HULP_UART_TX_LOW((tx_gpio)),\ 146 | I_DELAY((uint16_t)((hulp_get_fast_clk_freq() / (baud_rate)) - 48)),\ 147 | I_BGE(-11, 0),\ 148 | M_MOVL(reg_return,label_entry),\ 149 | I_LD(reg_return,reg_return,31),\ 150 | I_BXR(reg_return),\ 151 | I_HALT() 152 | 153 | /** 154 | * This will convert the value in R0 into an ASCII string (decimal). Very useful for debugging in combination with UART TX. 155 | * 156 | * As R1, R2, and R3 are also used in the subroutine, you should store the value beforehand and retrieve afterwards if still required. 157 | * The string is always 5 digits, padded with leading zeros where necessary (ie. "00000", "00001", ... "65535"). 158 | * A ulp_string_t initialised with a size of 6 is required for the destination buffer. eg. RTC_DATA_ATTR ulp_string_t<6> ulp_printf_buffer; 159 | * 160 | * A newline character will be appended automatically. The ULP may therefore pass the buffer directly to UART TX to print one value per line. 161 | * Use the customisable variant to override this behaviour. 162 | * 163 | * Prepare R1 with the offset of the ulp_string_t buffer. eg. I_MOVO(R1, ulp_printf_buffer) 164 | * Prepare R3 with the return address. eg. MOVL(R3, LABEL_SOME_PRINTF_RETURN_POINT) 165 | * Branch to the entry of the subroutine. 166 | */ 167 | #define M_INCLUDE_PRINTF_U(label_entry) \ 168 | M_INCLUDE_PRINTF_U_(label_entry, R1, R2, R3, 1, '\n') 169 | 170 | /** 171 | * Given that ulp_string_t rounds up odd initialisation sizes, a length of 5 for our 5-digit value produces a string with max length of 6 (one unused char). 172 | * May as well take advantage of it (eg. using '\n' allows outputting the buffer directly to UART TX with a linebreak between values, zero overhead). 173 | * If desired, use_final_char == 1 and specify final_char. To output the 5 digit ASCII only, use_final_char == 0. 174 | */ 175 | #define M_INCLUDE_PRINTF_U_(label_entry, reg_string_ptr, reg_scr, reg_return, use_final_char, final_char) \ 176 | M_LABEL(label_entry), \ 177 | I_ST(R0, reg_string_ptr, 3), \ 178 | I_MOVI(reg_scr, '0' << 8 | '0'), /* Reset counters for digits (10^4) (lower bits) and (10^3) (upper bits) with ASCII zero '0' */ \ 179 | I_BL(4, 10000), /* Loop incrementing (10^4) counter: '0' + 1 = '1' + 1 = '2' + 1 = '3' etc */ \ 180 | I_ADDI(reg_scr, reg_scr, 1), \ 181 | I_SUBI(R0, R0, 10000), \ 182 | I_BGE(-3, 0), \ 183 | I_BL(4, 1000), /* Loop incrementing (10^3) counter */ \ 184 | I_ADDI(reg_scr, reg_scr, 1 << 8), \ 185 | I_SUBI(R0, R0, 1000), \ 186 | I_BGE(-3, 0), \ 187 | I_ST(reg_scr, reg_string_ptr, 1), /* Store ASCII counters for digits (10^4) and (10^3) at offset metadata+1 */ \ 188 | I_MOVI(reg_scr, '0' << 8 | '0'), /* Do it all over again for (10^2) and (10^1), at metadata+2 */ \ 189 | I_BL(4, 100), \ 190 | I_ADDI(reg_scr, reg_scr, 1), \ 191 | I_SUBI(R0, R0, 100), \ 192 | I_BGE(-3, 0), \ 193 | I_BL(4, 10), \ 194 | I_ADDI(reg_scr, reg_scr, 1 << 8), \ 195 | I_SUBI(R0, R0, 10), \ 196 | I_BGE(-3, 0), \ 197 | I_ST(reg_scr, reg_string_ptr, 2), \ 198 | I_MOVI(reg_scr, ((use_final_char) ? (uint8_t)(final_char) : 0) << 8 | '0'), /* Lastly the units (10^0) at metadata+3 */ \ 199 | I_ADDR(reg_scr, R0, reg_scr), \ 200 | I_LD(R0, reg_string_ptr, 3), \ 201 | I_ST(reg_scr, reg_string_ptr, 3), \ 202 | I_LD(reg_scr, reg_string_ptr, 0), /* Load the metadata (ie. offset+0) and set length=5*/ \ 203 | I_ANDI(reg_scr, reg_scr, 0xFF << 8), \ 204 | I_ORI(reg_scr, reg_scr, 5 + ((use_final_char) ? 1 : 0)), \ 205 | I_ST(reg_scr, reg_string_ptr, 0), \ 206 | I_BXR(reg_return) 207 | 208 | /** 209 | * Format value as hex string. See M_INCLUDE_PRINTF_U 210 | */ 211 | #define M_INCLUDE_PRINTF_X(label_entry) \ 212 | M_INCLUDE_PRINTF_X_(label_entry, R1, R2, R3) 213 | 214 | #define M_INCLUDE_PRINTF_X_(label_entry, reg_string_ptr, reg_scr, reg_return) \ 215 | M_LABEL(label_entry), \ 216 | I_ST(R0, reg_string_ptr, 2), \ 217 | I_MOVI(reg_scr, '0' << 8 | '0'), \ 218 | I_BL(3, 40960), \ 219 | I_ADDI(reg_scr, reg_scr, ('A' - '0')), \ 220 | I_SUBI(R0, R0, 40960), \ 221 | I_BL(4, 4096), \ 222 | I_ADDI(reg_scr, reg_scr, 1), \ 223 | I_SUBI(R0, R0, 4096), \ 224 | I_BGE(-3, 0), \ 225 | I_BL(3, 2560), \ 226 | I_ADDI(reg_scr, reg_scr, ('A' - '0') << 8), \ 227 | I_SUBI(R0, R0, 2560), \ 228 | I_BL(4, 256), \ 229 | I_ADDI(reg_scr, reg_scr, 1 << 8), \ 230 | I_SUBI(R0, R0, 256), \ 231 | I_BGE(-3, 0), \ 232 | I_ST(reg_scr, reg_string_ptr, 1), \ 233 | I_MOVI(reg_scr, '0' << 8 | '0'), \ 234 | I_BL(3, 160), \ 235 | I_ADDI(reg_scr, reg_scr, 'A' - '0'), \ 236 | I_SUBI(R0, R0, 160), \ 237 | I_BL(4, 16), \ 238 | I_ADDI(reg_scr, reg_scr, 1), \ 239 | I_SUBI(R0, R0, 16), \ 240 | I_BGE(-3, 0), \ 241 | I_BL(3, 10), \ 242 | I_ADDI(reg_scr, reg_scr, ('A' - '0') << 8), \ 243 | I_SUBI(R0, R0, 10), \ 244 | I_LSHI(R0, R0, 8), \ 245 | I_ADDR(reg_scr, R0, reg_scr), \ 246 | I_LD(R0, reg_string_ptr, 2), \ 247 | I_ST(reg_scr, reg_string_ptr, 2), \ 248 | I_LD(reg_scr, reg_string_ptr, 0), /* Load the metadata (ie. offset+0) and set length=4*/ \ 249 | I_ANDI(reg_scr, reg_scr, 0xFF << 8), \ 250 | I_ORI(reg_scr, reg_scr, 4), \ 251 | I_ST(reg_scr, reg_string_ptr, 0), \ 252 | I_BXR(reg_return) 253 | 254 | #ifdef __cplusplus 255 | } 256 | #endif 257 | 258 | #endif /* HULP_UART_H */ 259 | --------------------------------------------------------------------------------