ESP 32 спящий режим

Всем добрый день.

Решил сделать программу на ESP32 которая бы уходила в спящий режим с минимальным энергопотреблением и выходила бы из него по таймеру, кнопке или какому то сигналу на контакте. Однако во всех найденных мной примерах в интернете написано что цикл loop должен быть пустым:

void loop(){
  //This will never be reached
}

Как же тогда быть? Допустим у нас вебсервер с датчиком температуры. Просыпается по таймеру, сравнивает значение температуры с предыдущим, отправляет его куда то и вновь засыпает.

Как это реализовать без цикла loop или я что-то не понимаю?

С уважением.

в ESP, насколько я помню, выход из свящего режима эквивалентен сбросу/включению МК.
Соотвественно ваша программа должна думать, что каждый раз она включилась заново.

Если хотите, чтобы ESP32 работала от батареек, то в таком режиме что вы описали, она проживет максимум месяц, на тех батарейках(максимально ёмких), что сейчас имееются в продажах. Слишком большой ток при выходе из сна кушает.

Может тебе чуть чуть мозги напрячь и понять смысл кода?

Чушь какая

1 лайк

обоснуете?

У меня ESP8266 просыпается раз в час, что бы отправить данные, пары параллельно установленных аккумуляторов 18650 хватает примерно на месяц.

протри очки

типа ESP32 меньше жрет чем ESP8266?
не буду спорить, как нибудь проверю.

Это, мать его, фундаментально другой процессор, и сранивать цифры в лоб совершенно БЕСПОЛЕЗНО.

Проверено практикой. Минимальный измеренный ток старта при выходе из сна 140-170 мА. Коннект при идеальных условиях 2-5 секунды. rkit математикой владеете? можете сами подсчиать через сколько времени батарейка (не аккумулятор) снизит своё напряжение ниже напряжения работы еэспэшки.

А причём сдесь процессор. Передатчик у 8266 и 32 не думаю что сильно отличаются. А именно он является основеым потребителем энергии. Да и коннект по времени примерно одинаков.

Это лишь говорит о том, что ты хреновый практик.

Не могу, даже близко недостаточно информации (тут уже качество теоретика наложилось). Могу сказать про реальный прибор - градусник с журналом раз в минуту и отправкой по необходимости - год. От двух щелочных АА.

Чудо, аж не верится, надо будет поэкспериментировать…

Попробую я “повладеть математикой”…

Яндекс говорит что типичная щелочная батарейка это 2500мАч

Если взять ток 170мА продолжительностью 5 сек и запускать это 1 раз в час, как у @andycat - получается что батарейки должно хватить на 10600 часов, или 440 дней или примерно 15 месяцев

У меня конечно не совсем честная/чистая esp, плата, повышайка до 5 вольт, эти 5 в на плату, на ней родная понижайка на 3.3… + bme280 свою копеечку кушает. Но я особо не парюсь, раз в месяц аккумулятор меняю.

Оно то да, можно брать это число, если устраивает разряд до 0.8В

2 лайка

Вас обманули. Даже крест на ESP-шке красноречиво это подчёркивает. :slight_smile:

1 лайк

То есть 1117, то есть влёт конские 5мА, против ~20мкА esp32 во сне, и никаким низкопотребляющим прибором и не пахнет.

Устраивает разряд до 1.25, и разница тут около 15%.

Видимо кто-то по ночам пробирается ко мне на участок и меняет батарейки. Во всех градусниках.

А проектом можете поделиться?

Держи, но написано на esp-idf, третьей версии, если память не изменяет.

main.cpp - основная логика. Библиотеки не прикладываю - их много и ничего интересного там нет - вайфай и отправка по http.
#include "freertos/FreeRTOS.h"
#include "esp_system.h"
#include "esp_event.h"
#include "nvs_flash.h"
#include "driver/gpio.h"
#include "string.h"
#include "esp_log.h"
#include "wifi.h"
#include "esp_sleep.h"
#include "driver/adc_common.h"
#include "driver/adc.h"
#include "driver/rtc_io.h"
#include "driver/i2c.h"
#include <tmp1075.hpp>
#include "http_communication.hpp"
#include <vector>
#include "esp_pm.h"
#include "esp_sntp.h"
#include "esp32/ulp.h"
#include "ulp_main.h"
#include "ulp/ulp_config.h"
#include "esp32/clk.h"
#include "esp_system.h"
#include "soc/adc_channel.h"



// Отправка лога
// Прочистка основного цикла

extern const uint8_t ulp_main_bin_start[] asm("_binary_ulp_main_bin_start");
extern const uint8_t ulp_main_bin_end[]   asm("_binary_ulp_main_bin_end");

static constexpr auto TAG = "main";

//RTC_SLOW_ATTR time_t last_time_sync = 1606641588;
//time_t time_sync_interval = 1000 * 60 * 60 * 24 * 3;

static constexpr int b1 = 25;
static constexpr int b2 = 26;
static constexpr int temp_alert = 14;

struct ulp_temp_log_entry {
    uint32_t time;
    uint32_t temp;
    float f() const {
        return tmp1075::tmp1075::raw_to_float((uint16_t)temp);
    }
    uint32_t t() const {
        return time << 16;
    }

    uint16_t temp_raw() const {
        return (uint16_t)temp;
    }
};

static void nvs_init() {
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK( err );
}

/*static void i2c_master_init() {
    int i2c_master_port = I2C_NUM_0;
    i2c_config_t conf;
    conf.mode = I2C_MODE_MASTER;
    conf.sda_io_num = 15;
    conf.sda_pullup_en = GPIO_PULLUP_DISABLE;
    conf.scl_io_num = 13;
    conf.scl_pullup_en = GPIO_PULLUP_DISABLE;
    conf.master.clk_speed = 400000;
    ESP_ERROR_CHECK(i2c_param_config(i2c_master_port, &conf));
    ESP_ERROR_CHECK(i2c_driver_install(i2c_master_port, conf.mode, 0, 0, 0));
}*/

static int read_battery() {
    constexpr auto channel = ADC2_CHANNEL_8;// ADC2_GPIO25_CHANNEL;
    gpio_num_t adc_gpio_num;
    int result;
    int sum = 0;
    ESP_ERROR_CHECK(adc2_pad_get_io_num(channel, &adc_gpio_num));
    ESP_ERROR_CHECK(adc_vref_to_gpio(ADC_UNIT_2, adc_gpio_num));
    ESP_ERROR_CHECK(adc2_config_channel_atten(channel, ADC_ATTEN_DB_6));
    for (int i = 0; i < 10; i++) {
        ESP_ERROR_CHECK(adc2_get_raw(channel, ADC_WIDTH_12Bit, &result));
        sum += result;
    }
    return sum / 10;
}

static void power_config() {
    esp_pm_config_esp32_t cfg = {};
    cfg.max_freq_mhz = 240;
    cfg.min_freq_mhz = 40;
    cfg.light_sleep_enable = true;
    esp_pm_configure(&cfg);
    //esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
    //esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); // ULP
    //esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF); // нужно для fast boot
    //esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF); // нужно для работы ULP
    esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF);
}

// static void time_sync() {
//     auto t = time(nullptr);
//     if (esp_sleep_get_wakeup_cause() == 0 || t < last_time_sync || t - time_sync_interval > last_time_sync) {
//         setenv("TZ", "Europe/Moscow", true);
//         tzset();
//         sntp_setoperatingmode(SNTP_OPMODE_POLL);
//         sntp_setservername(0, "pool.ntp.org");
//         sntp_init();
//         int retry = 0;
//         const int retry_count = 200;
//         while (sntp_get_sync_status() == SNTP_SYNC_STATUS_RESET && ++retry < retry_count) {
//             vTaskDelay(100 / portTICK_PERIOD_MS);
//         }
//         if (retry < retry_count) {
//             ESP_LOGI(TAG, "Time sync success (%d/%d)", retry, retry_count);
//             last_time_sync = time(nullptr);
//         } else {
//             ESP_LOGI(TAG, "Time sync fail");
//         }
//     }
// }

static void init_ulp_program(void) {
    esp_err_t err = ulp_load_binary(0, ulp_main_bin_start,
            (ulp_main_bin_end - ulp_main_bin_start) / sizeof(uint32_t));
    ESP_ERROR_CHECK(err);

    rtc_gpio_init((gpio_num_t)SDA_GPIO);
    rtc_gpio_init((gpio_num_t)SCL_GPIO);
    rtc_gpio_set_direction((gpio_num_t)SDA_GPIO, RTC_GPIO_MODE_INPUT_ONLY);
    rtc_gpio_set_direction((gpio_num_t)SCL_GPIO, RTC_GPIO_MODE_INPUT_ONLY);

#ifdef ULP_GPIO_TRACE
    rtc_gpio_init((gpio_num_t)RUN_GPIO);
    rtc_gpio_set_level((gpio_num_t)RUN_GPIO, 0);
    rtc_gpio_set_direction((gpio_num_t)RUN_GPIO, RTC_GPIO_MODE_OUTPUT_ONLY);
#endif
    ulp_set_wakeup_period(0, 1000 * 1000 * 30);
} 


gpio_num_t disabled_gpio[] = {
    GPIO_NUM_12, GPIO_NUM_32, GPIO_NUM_33, GPIO_NUM_34, GPIO_NUM_15, GPIO_NUM_13
};



extern "C" void app_main() {
    esp_log_level_set("*", ESP_LOG_WARN);
    esp_log_level_set(TAG, ESP_LOG_INFO);

    power_config();
    auto slowclk_period = esp_clk_slowclk_cal_get();
    ESP_LOGI(TAG, "slow clock period %d.%06dus", slowclk_period >> 19, slowclk_period & ((1 << 19) - 1));
     

    std::vector<Variable_for_post> vars;

    auto cause = esp_sleep_get_wakeup_cause();
    if (cause == 0) {
        init_ulp_program();
        ulp_low_thr = tmp1075::tmp1075::float_to_raw(120);
        ulp_high_thr = tmp1075::tmp1075::float_to_raw(-50);
    }
    ESP_LOGI(TAG, "wakeup cause: %d; error_cnt: %d", 
        cause, 
        (int16_t)ulp_error_cnt
    );
    if (cause == 6) {
        ESP_LOGI(TAG, "ulp wakeup cause is %d", (int16_t)ulp_wakeup_cause);
        if ((int16_t)ulp_wakeup_cause == 2 || (int16_t)ulp_wakeup_cause == 1 || (int16_t)ulp_wakeup_cause == 3) {
            ulp_temp_log_entry* log = reinterpret_cast<ulp_temp_log_entry*>(&ulp_temp_log);
            uint16_t temp_log_start = ((&ulp_temp_log) - RTC_SLOW_MEM);
            uint16_t temp_log_count = ((uint16_t)ulp_temp_log_pos - temp_log_start) / 2;
            //ESP_LOGI(TAG, "start: %d; pos: %d; count: %d ", temp_log_start, (uint16_t)ulp_temp_log_pos, temp_log_count);
            uint32_t time = RTCCNTL.time0;
            for (size_t i = 0; i < temp_log_count; i ++) {
                auto time_passed_us = ((uint64_t)((time - log[i].t())) * (uint64_t)slowclk_period) >> RTC_CLK_CAL_FRACT;
                int32_t time_passed_s = (int64_t)time_passed_us / -1000000ll;
                ESP_LOGI(TAG, "%d %f", time_passed_s, log[i].f());
                vars.emplace_back("temp", log[i].f(), time_passed_s);
            }
            if (temp_log_count > 0) {
                auto temp = log[temp_log_count - 1].temp_raw();
                constexpr auto step = tmp1075::tmp1075::float_to_raw(1);
                ulp_low_thr = temp - step;
                ulp_high_thr = temp + step;
            } else {
                ulp_low_thr = tmp1075::tmp1075::float_to_raw(120);
                ulp_high_thr = tmp1075::tmp1075::float_to_raw(-50);
            }
            ulp_temp_log_pos = 0;
        }
        adc_power_acquire();
        vars.emplace_back("battery", read_battery());
        adc_power_release();

        nvs_init();
        wifi_init();
    //    time_sync();
        HTTP_communication::post_variables(vars, 1000);
        wifi_stop();
        nvs_flash_deinit();
    }

    ESP_LOGI(TAG, "going to sleep");
    for (auto g: disabled_gpio) {
        rtc_gpio_isolate(g);
    }

    //esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_TIMER);
    esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
    ESP_ERROR_CHECK(esp_sleep_enable_ulp_wakeup());
    esp_deep_sleep_disable_rom_logging();
    if (cause == 0) ESP_ERROR_CHECK(ulp_run(&ulp_entry - RTC_SLOW_MEM));
    esp_deep_sleep_start();
    //esp_deep_sleep(1000ull * 1000 * 60);
    //esp_deep_sleep(1000ull * 1000 * 60 * 60);
}
main.S - основная программа ULP проца
#include "soc/rtc_cntl_reg.h"
#include "soc/rtc_io_reg.h"
#include "soc/soc_ulp.h"
#include "stack.S"
#include "rtc.S"
#include "ulp_config.h"
#include "util.S"

.macro my_halt continuation
        move r3, startup_addr
        move r2, \continuation
        st r2, r3, 0
#ifdef ULP_GPIO_TRACE
        clear_gpio(RUN_RTC_GPIO)
#endif
        halt
.endm

        .data
        .global error_cnt
error_cnt: .long 0

        .global wakeup_cause
wakeup_cause: .long 9999

        .bss
stack:
	.skip 200
.global stackEnd
stackEnd:
	.long 0

        .global low_thr
low_thr: .long 0

        .global high_thr
high_thr: .long 0

        .global temp_log
temp_log: .space TEMP_LOG_SIZE * 8 // значение + таймстамп

        .global temp_log_pos
temp_log_pos: .long 0

startup_addr: .long 0


        .text
        .global entry
entry:
#ifdef ULP_GPIO_TRACE
        set_gpio(RUN_RTC_GPIO)
#endif
        move r3, startup_addr
        ld r0, r3, 0
        jumpr first_run, 1, lt
        jump r0


first_run:
        move r3, temp_log_pos
        move r2, temp_log
        st r2, r3, 0

start_conversion_stage:
        move r3,stackEnd     // Инициируем стек вызовов
        psr
        jump tmp1075_trigger_OC // Запускаем датчик температуры
        move r0,r2 // test for error
        jumpr tmp_fail, 1, ge

        my_halt read_temp_stage

read_temp_stage:
        update_rtc_time
        move r3,stackEnd        // Инициируем стек вызовов
        psr
        jump tmp1075_read_temp_reg // Запускаем датчик температуры
        move r0,r2 // test for error
        jumpr tmp_fail, 1, ge

        move r3, tmp1075_temp 
        ld r1, r3, 0  // Температура в r1

        move r3, temp_log_pos
        ld r2, r3, 0

        st r1, r2, 4  // Пишем температуру
        check_rtc_time_valid
        read_rtc_reg1 // Читаем время в r0
        st r0, r2, 0  // Пишем время

        add r2, r2, 2 // temp_log_pos += 2
        st r2, r3, 0

        sub r0, r3, r2 // В r3 адрес temp_log_pos, следующей строго за temp_log - конец лога
        move r3, 3
        jumpr wakeup_tagged, 1, lt // По заполнению лога просыпаемся

        move r3, low_thr
        ld r0, r3, 0  // Нижний порог в r0
        sub r0, r1, r0  // r0 = r1 - r0
        rsh r0, r0, 15  // r0 >>= 15
        move r3, 1
        jumpr wakeup_tagged, 0, gt

        move r3, high_thr
        ld r0, r3, 0  // Верхний порог в r0
        sub r0, r1, r0  // r0 = r1 - r0
        rsh r0, r0, 15  // r0 >>= 15
        move r3, 2
        jumpr wakeup_tagged, 0, le

        my_halt start_conversion_stage


tmp_fail:
        move r1, error_cnt // Загружаемся и рапортуем фейл?
        ld r0, r1, 0
        add r0, r0, 1
        st r0, r1, 0
        move r3, 99
        //jump wakeup_tagged

wakeup_tagged:
        move r2, wakeup_cause
        st r3, r2, 0
wakeup:                   // Read RTC_CNTL_RDY_FOR_WAKEUP bit
        READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)
        AND r0, r0, 1
        JUMP wakeup, eq    // Retry until the bit is set
        WAKE                          // Trigger wake up
        //REG_WR 0x006, 24, 24, 0       // Stop ULP timer (clear RTC_CNTL_ULP_CP_SLP_TIMER_EN)
        my_halt first_run

        .global waitMs
waitMs:
	wait 8000
	sub r2,r2,1
	jump doneWaitMs,eq
	jump waitMs
doneWaitMs:
	ret


i2c.S
/*
 * Demo of I2C ULP routines
 */

#include "soc/rtc_cntl_reg.h"
#include "soc/rtc_io_reg.h"
#include "soc/soc_ulp.h"

#include "stack.S"
#include "ulp_config.h"

/*
 * =============================== I2C code ==========================================
 * Implementation of pseudo code from
 * https://en.wikipedia.org/wiki/I%C2%B2C#Example_of_bit-banging_the_I.C2.B2C_master_protocol
 */

.bss
i2c_started:
	.long 0

i2c_didInit:
	.long 0

.text

.global i2c_start_cond
.global i2c_stop_cond
.global i2c_write_bit
.global i2c_read_bit
.global i2c_write_byte
.global i2c_read_byte

.macro I2C_delay
	wait 10 // 38 // minimal 4.7us
.endm

.macro read_SCL // Return current level of SCL line, 0 or 1
	READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + SCL_RTCIO, 1)
.endm

.macro read_SDA // Return current level of SDA line, 0 or 1
	READ_RTC_REG(RTC_GPIO_IN_REG, RTC_GPIO_IN_NEXT_S + SDA_RTCIO, 1)
.endm

.macro set_SCL // Do not drive SCL (set pin high-impedance)
	WRITE_RTC_REG(RTC_GPIO_ENABLE_W1TC_REG, RTC_GPIO_ENABLE_W1TC_S + SCL_RTCIO, 1, 1)
.endm

.macro clear_SCL // Actively drive SCL signal low
	// Output mode
	WRITE_RTC_REG(RTC_GPIO_ENABLE_W1TS_REG, RTC_GPIO_ENABLE_W1TS_S + SCL_RTCIO, 1, 1)
.endm

.macro set_SDA // Do not drive SDA (set pin high-impedance)
	WRITE_RTC_REG(RTC_GPIO_ENABLE_W1TC_REG, RTC_GPIO_ENABLE_W1TC_S + SDA_RTCIO, 1, 1)
.endm

.macro clear_SDA // Actively drive SDA signal low
	// Output mode
	WRITE_RTC_REG(RTC_GPIO_ENABLE_W1TS_REG, RTC_GPIO_ENABLE_W1TS_S + SDA_RTCIO, 1, 1)
.endm


i2c_start_cond:
	move r1,i2c_didInit
	ld r0,r1,0
	jumpr didInit,1,ge
	move r0,1
	st r0,r1,0
// set GPIO to pull low when activated
	WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + SCL_RTCIO, 1, 0)
	WRITE_RTC_REG(RTC_GPIO_OUT_REG, RTC_GPIO_OUT_DATA_S + SDA_RTCIO, 1, 0)
didInit:
	move r2,i2c_started
	ld r0,r2,0
	jumpr not_started,1,lt
// if started, do a restart condition
// set SDA to 1
	set_SDA
	I2C_delay
	set_SCL
clock_stretch: // TODO: Add timeout?
	read_SCL
	jumpr clock_stretch,1,lt

// Repeated start setup time, minimum 4.7us
	I2C_delay

not_started:
	// if (read_SDA() == 0) {
 	//		arbitration_lost();
 	// }

// SCL is high, set SDA from 1 to 0.
	clear_SDA
	I2C_delay
	clear_SCL
	move r0,1
	st r0,r2,0

	ret


i2c_stop_cond:
// set SDA to 0
	clear_SDA
	I2C_delay

	set_SCL
clock_stretch_stop:
	read_SCL
	jumpr clock_stretch_stop,1,lt

// Stop bit setup time, minimum 4us
	I2C_delay

// SCL is high, set SDA from 0 to 1
	set_SDA
	I2C_delay
	// if (read_SDA() == 0) {
 	//		arbitration_lost();
 	// }

 	move r2,i2c_started
 	move r0,0
 	st r0,r2,0

 	ret


// Write a bit to I2C bus
i2c_write_bit:
	jumpr bit0,1,lt
	set_SDA
	jump bit1
bit0:
	clear_SDA
bit1:

// SDA change propagation delay
	I2C_delay
// Set SCL high to indicate a new valid SDA value is available
	set_SCL
// Wait for SDA value to be read by slave, minimum of 4us for standard mode
	I2C_delay

clock_stretch_write:
	read_SCL
	jumpr clock_stretch_write,1,lt

	// SCL is high, now data is valid
 	// If SDA is high, check that nobody else is driving SDA
 	// if (bit && (read_SDA() == 0)) {
 	// 		arbitration_lost();
 	// }

 	// Clear the SCL to low in preparation for next change
 	clear_SCL

 	ret


// Read a bit from I2C bus
i2c_read_bit:
// Let the slave drive data
	set_SDA
// Wait for SDA value to be written by slave, minimum of 4us for standard mode
	I2C_delay
// Set SCL high to indicate a new valid SDA value is available
 	set_SCL

clock_stretch_read:
	read_SCL
	jumpr clock_stretch_read,1,lt

// Wait for SDA value to be written by slave, minimum of 4us for standard mode
	I2C_delay
// SCL is high, read out bit
	read_SDA
// Set SCL low in preparation for next operation
	clear_SCL

	ret // bit in r0

// Write a byte to I2C bus. Return 0 if ack by the slave.
i2c_write_byte:
	stage_rst
next_bit:
	and r0,r2,0x80
	psr
	jump i2c_write_bit
	lsh r2,r2,1
	stage_inc 1
	jumps next_bit,8,lt

	psr
	jump i2c_read_bit
	ret // nack


// Read a byte from I2C bus
i2c_read_byte:
	push r2
	move r2,0
	stage_rst
next_bit_read:
	psr
	jump i2c_read_bit
	lsh r2,r2,1
	or r2,r2,r0
	stage_inc 1
	jumps next_bit_read,8,lt

	pop r0
	psr
	jump i2c_write_bit

	move r0,r2

	ret


i2c-util.S
/*
 * I2C ULP utility routines
 */

#include "soc/rtc_cntl_reg.h"
#include "soc/rtc_io_reg.h"
#include "soc/soc_ulp.h"

#include "stack.S"

.text

write_intro:
	psr
	jump i2c_start_cond

	ld r2,r3,20 // Address
	lsh r2,r2,1
	psr
	jump i2c_write_byte
	jumpr popfail,1,ge

	ld r2,r3,16 // Register
	psr
	jump i2c_write_byte
	jumpr popfail,1,ge
	ret


.global write8
write8:
	psr
	jump write_intro

write_b:
	ld r2,r3,8 // data byte
	psr
	jump i2c_write_byte
	jumpr fail,1,ge

	psr
	jump i2c_stop_cond

	move r2,0 // Ok
	ret


.global write16
write16:
	psr
	jump write_intro

	ld r2,r3,8 // data byte 1
	rsh r2,r2,8
	psr
	jump i2c_write_byte
	jumpr fail,1,ge

	jump write_b


read_intro:
	psr
	jump i2c_start_cond

	ld r2,r3,16 // Address
	lsh r2,r2,1
	psr
	jump i2c_write_byte
	jumpr popfail,1,ge

	ld r2,r3,12 // Register
	psr
	jump i2c_write_byte
	jumpr popfail,1,ge

	psr
	jump i2c_start_cond

	ld r2,r3,16
	lsh r2,r2,1
	or r2,r2,1 // Address Read
	psr
	jump i2c_write_byte
	jumpr popfail,1,ge

	ret
popfail:
	pop r1 // pop caller return address
	move r2,1
	ret

.global read8
read8:
	psr
	jump read_intro

	move r2,1 // last byte
	psr
	jump i2c_read_byte
	push r0

	psr
	jump i2c_stop_cond

	pop r0

	move r2,0 // OK
	ret
fail:
	move r2,1
	ret

.global read16
read16:
	psr
	jump read_intro

	move r2,0
	psr
	jump i2c_read_byte
	push r0

	move r2,1 // last byte
	psr
	jump i2c_read_byte
	push r0

	psr
	jump i2c_stop_cond

	pop r0
	pop r2 // first byte
	lsh r2,r2,8
	or r2,r2,r0
	move r0,r2

	move r2,0 // OK
	ret
rtc.S
#include "soc/rtc_cntl_reg.h"
#include "soc/soc_ulp.h"

.macro update_rtc_time 
    WRITE_RTC_REG(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_UPDATE_S, 1, 1)
.endm

.macro check_rtc_time_valid
    loop:
        READ_RTC_REG(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_VALID_S, 1)
        AND R0, R0, 1
        JUMP loop, EQ
.endm

.macro read_rtc_reg0 
    READ_RTC_REG(RTC_CNTL_TIME0_REG, 0, 16)
.endm

.macro read_rtc_reg1 
    READ_RTC_REG(RTC_CNTL_TIME0_REG, 16, 16)
.endm

.macro read_rtc_reg2 
    READ_RTC_REG(RTC_CNTL_TIME1_REG, 0, 16)
.endm

/*
    .bss

    .global time_reg_1
time_reg_1:
    .long 0

    .global time_reg_2
time_reg_2:
    .long 0

    .global time_reg_3
time_reg_3:
    .long 0


    .text
    .global entry
read_time:
    //Trigger update of register
    WRITE_RTC_REG(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_UPDATE_S, 1, 1)

check_time_valid:
    // Check if RTC_CNTL_TIME_VALID bit is 1, otherwise repeat
    READ_RTC_REG(RTC_CNTL_TIME_UPDATE_REG, RTC_CNTL_TIME_VALID_S, 1)
    AND R0, R0, 1
    JUMP check_time_valid, EQ
    
    // Read timer registers
    READ_RTC_REG(RTC_CNTL_TIME0_REG, 0, 16)
    MOVE R2, time_reg_1
    ST R0, R2, 0

    READ_RTC_REG(RTC_CNTL_TIME0_REG, 32, 16) ;REG_RD 0x3FF48010, 31, 16
    MOVE R2, time_reg_2
    ST R0, R2, 0

    READ_RTC_REG(RTC_CNTL_TIME1_REG, 0, 16) ;REG_RD 0x3FF48014, 15, 0
    MOVE R2, time_reg_3
    ST R0, R2, 0*/
stack.S
/*
 * ULP stack and subroutine macros
 */

.macro push rx
	st \rx,r3,0
	sub r3,r3,1
.endm

.macro pop rx
	add r3,r3,1
	ld \rx,r3,0
.endm

// Prepare subroutine jump, uses scratch register sr
.macro psr sr=r1 pos=.
	.set _next2,(\pos+16)
	move \sr,_next2
	push \sr
.endm

// Return from subroutine
.macro ret sr=r1
	pop \sr
	jump \sr
.endm
tmp1075.S
#include "stack.S"

.set tmp1075_addr,0b1001000
.set tmp1075_reg_temp,0
.set tmp1075_reg_CFG,1
.set cmd_OC,0b10000001

    .bss
    .global tmp1075_temp
tmp1075_temp: .long 0

    .text
    .global tmp1075_trigger_OC
tmp1075_trigger_OC:
    move r1, tmp1075_addr
    push r1
    move r1, tmp1075_reg_CFG
    push r1
    move r1, cmd_OC
    push r1
    psr
    jump write8
    add r3,r3,3
	move r0,r2 // test for error
    jumpr fail,1,ge
    ret

    .global tmp1075_read_temp_reg
tmp1075_read_temp_reg:
    move r1, tmp1075_addr
    push r1
    move r1, tmp1075_reg_temp
    push r1
    psr
    jump read16
    add r3,r3,2 // remove call parameters from stack
    move r1,r0 // save result
	move r0,r2 // test for error
    jumpr fail,1,ge
	move r2,tmp1075_temp // store result
	st r1,r2,0
    move r2, 0 // ok
    ret

fail:
    ret
ulp_config.h
//#define ULP_GPIO_TRACE
#define SCL_GPIO 4
#define SDA_GPIO 0
#define SCL_RTCIO 10
#define SDA_RTCIO 11
#define TEMP_LOG_SIZE 180
#define UPDATE_INTERVAL // Посчитать
#define RUN_RTC_GPIO 10
#define RUN_GPIO 4
util.S
#include "soc/rtc_cntl_reg.h"
#include "soc/rtc_io_reg.h"
#include "soc/soc_ulp.h"

.macro set_gpio num
    WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + \num, 1, 1)
.endm

.macro clear_gpio num
    WRITE_RTC_REG(RTC_GPIO_OUT_W1TC_REG, RTC_GPIO_OUT_DATA_W1TC_S + \num, 1, 1)
.endm

На плате esp32S от ai-thinker (низкий минимальный порог напряжения питания), tmp1075(дешевый на тот момент, простой в программировании), и батарейки.

1 лайк