|
| 1 | +/* |
| 2 | + This example smothly blinks GPIO_2 using different frequencies changed after Deep Sleep Time |
| 3 | + The PWM and control of blink frequency is done by ULP exclusively |
| 4 | + This is an example about how to program the ULP using Arduino |
| 5 | + It also demonstrates use of RTM MEMORY to persist data and states |
| 6 | +*/ |
| 7 | + |
| 8 | +#include <Arduino.h> |
| 9 | +#include "esp32/ulp.h" |
| 10 | +#include "driver/rtc_io.h" |
| 11 | + |
| 12 | +// RTC Memory used for ULP internal variable and Sketch interfacing |
| 13 | +#define RTC_dutyMeter 0 |
| 14 | +#define RTC_dir 4 |
| 15 | +#define RTC_fadeDelay 12 |
| 16 | +// *fadeCycleDelay is used to pass values to ULP and change its behaviour |
| 17 | +uint32_t *fadeCycleDelay = &RTC_SLOW_MEM[RTC_fadeDelay]; |
| 18 | +#define ULP_START_OFFSET 32 |
| 19 | + |
| 20 | +// For ESP32 Arduino, it is usually at offeset 512, defined in sdkconfig |
| 21 | +RTC_DATA_ATTR uint32_t ULP_Started = 0; // 0 or 1 |
| 22 | + |
| 23 | +//Time-to-Sleep |
| 24 | +#define uS_TO_S_FACTOR 1000000ULL /* Conversion factor for micro seconds to seconds */ |
| 25 | +#define TIME_TO_SLEEP 5 /* Time ESP32 will go to sleep (in microseconds); multiplied by above conversion to achieve seconds*/ |
| 26 | + |
| 27 | + |
| 28 | +void ulp_setup() { |
| 29 | + if (ULP_Started) { |
| 30 | + return; |
| 31 | + } |
| 32 | + *fadeCycleDelay = 5; // 5..200 works fine for a full Fade In + Out cycle |
| 33 | + ULP_Started = 1; |
| 34 | + |
| 35 | + // GPIO2 initialization (set to output and initial value is 0) |
| 36 | + const gpio_num_t MeterPWMPin = GPIO_NUM_2; |
| 37 | + rtc_gpio_init(MeterPWMPin); |
| 38 | + rtc_gpio_set_direction(MeterPWMPin, RTC_GPIO_MODE_OUTPUT_ONLY); |
| 39 | + rtc_gpio_set_level(MeterPWMPin, 0); |
| 40 | + |
| 41 | + // if LED is connected to GPIO2 (specify by +RTC_GPIO_OUT_DATA_S : ESP32 is 14, S2/S3 is 10) |
| 42 | + const uint32_t MeterPWMBit = rtc_io_number_get(MeterPWMPin) + RTC_GPIO_OUT_DATA_S; |
| 43 | + |
| 44 | + enum labels { |
| 45 | + INIFINITE_LOOP, |
| 46 | + RUN_PWM, |
| 47 | + NEXT_PWM_CYCLE, |
| 48 | + PWM_ON, |
| 49 | + PWM_OFF, |
| 50 | + END_PWM_CYCLE, |
| 51 | + POSITIVE_DIR, |
| 52 | + DEC_DUTY, |
| 53 | + INC_DUTY, |
| 54 | + }; |
| 55 | + |
| 56 | + // Define ULP program |
| 57 | + const ulp_insn_t ulp_prog[] = { |
| 58 | + // Initial Value setup |
| 59 | + I_MOVI(R0, 0), // R0 = 0 |
| 60 | + I_ST(R0, R0, RTC_dutyMeter), // RTC_SLOW_MEM[RTC_dutyMeter] = 0 |
| 61 | + I_MOVI(R1, 1), // R1 = 1 |
| 62 | + I_ST(R1, R0, RTC_dir), // RTC_SLOW_MEM[RTC_dir] = 1 |
| 63 | + |
| 64 | + M_LABEL(INIFINITE_LOOP), // while(1) { |
| 65 | + |
| 66 | + // run certain PWM Duty for about (RTC_fadeDelay x 100) microseconds |
| 67 | + I_MOVI(R3, 0), // R3 = 0 |
| 68 | + I_LD(R3, R3, RTC_fadeDelay), // R3 = RTC_SLOW_MEM[RTC_fadeDelay] |
| 69 | + M_LABEL(RUN_PWM), // do { // repeat RTC_fadeDelay times: |
| 70 | + |
| 71 | + // execute about 10KHz PWM on GPIO2 using as duty cycle = RTC_SLOW_MEM[RTC_dutyMeter] |
| 72 | + I_MOVI(R0, 0), // R0 = 0 |
| 73 | + I_LD(R0, R0, RTC_dutyMeter), // R0 = RTC_SLOW_MEM[RTC_dutyMeter] |
| 74 | + M_BL(NEXT_PWM_CYCLE, 1), // if (R0 > 0) turn on LED |
| 75 | + I_WR_REG(RTC_GPIO_OUT_W1TS_REG, MeterPWMBit, MeterPWMBit, 1), // W1TS set bit to clear GPIO - GPIO2 on |
| 76 | + M_LABEL(PWM_ON), // while (R0 > 0) // repeat RTC_dutyMeter times: |
| 77 | + M_BL(NEXT_PWM_CYCLE, 1), // { |
| 78 | + //I_DELAY(8), // // 8 is about 1 microsecond based on 8MHz |
| 79 | + I_SUBI(R0, R0, 1), // R0 = R0 - 1 |
| 80 | + M_BX(PWM_ON), // } |
| 81 | + M_LABEL(NEXT_PWM_CYCLE), // // toggle GPIO_2 |
| 82 | + I_MOVI(R0, 0), // R0 = 0 |
| 83 | + I_LD(R0, R0, RTC_dutyMeter), // R0 = RTC_SLOW_MEM[RTC_dutyMeter] |
| 84 | + I_MOVI(R1, 100), // R1 = 100 |
| 85 | + I_SUBR(R0, R1, R0), // R0 = 100 - dutyMeter |
| 86 | + M_BL(END_PWM_CYCLE, 1), // if (R0 > 0) turn off LED |
| 87 | + I_WR_REG(RTC_GPIO_OUT_W1TC_REG, MeterPWMBit, MeterPWMBit, 1), // W1TC set bit to clear GPIO - GPIO2 off |
| 88 | + M_LABEL(PWM_OFF), // while (R0 > 0) // repeat (100 - RTC_dutyMeter) times: |
| 89 | + M_BL(END_PWM_CYCLE, 1), // { |
| 90 | + //I_DELAY(8), // // 8 is about 1us: ULP fetch+execution time |
| 91 | + I_SUBI(R0, R0, 1), // R0 = R0 - 1 |
| 92 | + M_BX(PWM_OFF), // } |
| 93 | + M_LABEL(END_PWM_CYCLE), // |
| 94 | + |
| 95 | + I_SUBI(R3, R3, 1), // R3 = R3 - 1 // RTC_fadeDelay |
| 96 | + I_MOVR(R0, R3), // R0 = R3 // only R0 can be used to compare and branch |
| 97 | + M_BGE(RUN_PWM, 1), // } while (R3 > 0) // ESP32 repeatinf RTC_fadeDelay times |
| 98 | + |
| 99 | + // increase/decrease DutyMeter to apply Fade In/Out loop |
| 100 | + I_MOVI(R1, 0), // R1 = 0 |
| 101 | + I_LD(R1, R1, RTC_dutyMeter), // R1 = RTC_SLOW_MEM[RTC_dutyMeter] |
| 102 | + I_MOVI(R0, 0), // R0 = 0 |
| 103 | + I_LD(R0, R0, RTC_dir), // R0 = RTC_SLOW_MEM[RTC_dir] |
| 104 | + |
| 105 | + M_BGE(POSITIVE_DIR, 1), // if(dir == 0) { // decrease duty by 2 |
| 106 | + // Dir is 0, means decrease Duty by 2 |
| 107 | + I_MOVR(R0, R1), // R0 = Duty |
| 108 | + M_BGE(DEC_DUTY, 1), // if (duty == 0) { // change direction and increase duty |
| 109 | + I_MOVI(R3, 0), // R3 = 0 |
| 110 | + I_MOVI(R2, 1), // R2 = 1 |
| 111 | + I_ST(R2, R3, RTC_dir), // RTC_SLOW_MEM[RTC_dir] = 1 // increasing direction |
| 112 | + M_BX(INC_DUTY), // goto "increase Duty" |
| 113 | + M_LABEL(DEC_DUTY), // } "decrease Duty": |
| 114 | + I_SUBI(R0, R0, 2), // Duty -= 2 |
| 115 | + I_MOVI(R2, 0), // R2 = 0 |
| 116 | + I_ST(R0, R2, RTC_dutyMeter), // RTC_SLOW_MEM[RTC_dutyMeter] += 2 |
| 117 | + M_BX(INIFINITE_LOOP), // } |
| 118 | + |
| 119 | + M_LABEL(POSITIVE_DIR), // else { // dir == 1 // increase duty by 2 |
| 120 | + // Dir is 1, means increase Duty by 2 |
| 121 | + I_MOVR(R0, R1), // R0 = Duty |
| 122 | + M_BL(INC_DUTY, 100), // if (duty == 100) { // change direction and decrease duty |
| 123 | + I_MOVI(R2, 0), // R2 = 0 |
| 124 | + I_ST(R2, R2, RTC_dir), // RTC_SLOW_MEM[RTC_dir] = 0 // decreasing direction |
| 125 | + M_BX(DEC_DUTY), // goto "decrease Duty" |
| 126 | + M_LABEL(INC_DUTY), // } "increase Duty": |
| 127 | + I_ADDI(R0, R0, 2), // Duty += 2 |
| 128 | + I_MOVI(R2, 0), // R2 = 0 |
| 129 | + I_ST(R0, R2, RTC_dutyMeter), // RTC_SLOW_MEM[RTC_dutyMeter] -= 2 |
| 130 | + // } // if (dir == 0) |
| 131 | + M_BX(INIFINITE_LOOP), // } // while(1) |
| 132 | + }; |
| 133 | + // Run ULP program |
| 134 | + size_t size = sizeof(ulp_prog) / sizeof(ulp_insn_t); |
| 135 | + ulp_process_macros_and_load(ULP_START_OFFSET, ulp_prog, &size); |
| 136 | + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); |
| 137 | + ulp_run(ULP_START_OFFSET); |
| 138 | +} |
| 139 | + |
| 140 | + |
| 141 | +void setup() { |
| 142 | + Serial.begin(115200); |
| 143 | + while (!Serial) {} // wait for Serial to start |
| 144 | + |
| 145 | + ulp_setup(); // it really only runs on the first ESP32 boot |
| 146 | + Serial.printf("\nStarted smooth blink with delay %d\n", *fadeCycleDelay); |
| 147 | + |
| 148 | + // *fadeCycleDelay resides in RTC_SLOW_MEM and persists along deep sleep waking up |
| 149 | + // it is used as a delay time parameter for smooth blinking, in the ULP processing code |
| 150 | + if (*fadeCycleDelay < 195) { |
| 151 | + *fadeCycleDelay += 10; |
| 152 | + } else { |
| 153 | + *fadeCycleDelay = 5; // 5..200 works fine for a full Fade In + Out cycle |
| 154 | + } |
| 155 | + Serial.println("Entering in Deep Sleep"); |
| 156 | + esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR /*/ 4*/); // time set with variable above |
| 157 | + esp_deep_sleep_start(); |
| 158 | + // From this point on, no code is executed in DEEP SLEEP mode |
| 159 | +} |
| 160 | + |
| 161 | + |
| 162 | +void loop() { |
| 163 | + // It never reaches this code because it enters in Deep Sleep mode at the end of setup() |
| 164 | +} |
| 165 | + |
0 commit comments