• Система автоматизации с открытым исходным кодом на базе esp8266/esp32 микроконтроллеров и приложения IoT Manager. Наша группа в Telegram

Нужна помощь Light Sleep Mode - стал срабатывать wdt ...

Slacky

Member
Контролирую внешнее питание по пину. Если пропало напряжение, вызываем вот такую функцию

Код:
void ICACHE_FLASH_ATTR sleepOnNow() {
    apModeNow=staModeNow=false;
    wifi_station_disconnect();
    wifi_set_opmode_current(NULL_MODE);
    wifi_fpm_set_sleep_type(LIGHT_SLEEP_T); //light sleep mode
    gpio_pin_wakeup_enable(GPIO_ID_PIN(pin_num[HOT_PIN]),
            GPIO_PIN_INTR_LOLEVEL); /* Set the interrupt to look for LOW pulses on HOT_PIN  */
    gpio_pin_wakeup_enable(GPIO_ID_PIN(pin_num[COLD_PIN]),
            GPIO_PIN_INTR_LOLEVEL); /* Set the interrupt to look for LOW pulses on COLD_PIN */
    wifi_fpm_open();
    os_delay_us(50000);
    wifi_fpm_set_wakeup_cb(wakeupFromMotion); //wakeup callback
    wifi_fpm_do_sleep(0xFFFFFFF);
    os_delay_us(50000);
}
Пока не оброс кодом, все работало правильно. А сейчас, после вызова этой функции модуль перегружается по wdt ...

Есть идеи, куда копать?
 

Slacky

Member
1) уменьшите задержки раз в 100 вместо os_delay_us(50000); os_delay_us(500);
Действительно, совсем забыл. Этот код я перетащил из Ардуины, там было что-то типа
Код:
delay(100);
И тут было изначально
Код:
os_delay_us(100);
А потом я поменял на большее значение, так как это микросекунды. Но у меня отложилось в памяти, что вроде и так работало.

Еще раз спасибо. Буду пробовать ...
 

Slacky

Member
Не помогает.

Код:
void ICACHE_FLASH_ATTR sleepOnNow() {
    apModeNow(false);
    staModeNow(false);
    system_soft_wdt_stop();
    wifi_station_disconnect();
    wifi_set_opmode_current(NULL_MODE);
    wifi_fpm_set_sleep_type(LIGHT_SLEEP_T); //light sleep mode
    gpio_pin_wakeup_enable(GPIO_ID_PIN(pin_num[HOT_PIN]),
            GPIO_PIN_INTR_LOLEVEL); /* Set the interrupt to look for LOW pulses on HOT_PIN  */
    gpio_pin_wakeup_enable(GPIO_ID_PIN(pin_num[COLD_PIN]),
            GPIO_PIN_INTR_LOLEVEL); /* Set the interrupt to look for LOW pulses on COLD_PIN */
    wifi_fpm_open();
    wifi_fpm_set_wakeup_cb(wakeupFromMotion); //wakeup callback
    wifi_fpm_do_sleep(0xFFFFFFF);
}
На выходе

Код:
Power low. Light sleep now ...
force slp enable,type: 1
pm close 7
fpm open,type:1 0
fpm 758

ets Jan  8 2013,rst cause:4, boot mode:(1,6)

wdt reset
 

Slacky

Member
Я попробую, но уже после праздников, уезжаю на выходные.

А вот облегченный код дал интересные результаты. Если убрать таймер и процедуру опроса двух пинов (buttonTimer и проч), то все работает. А вот так, если позамыкать пины на массу, заставив счетчик считать, то вот и срабатывает wdt, а если из кода убрать опрос пинов - то все нормально ...

Код:
#include <ets_sys.h>
#include <osapi.h>
#include <user_interface.h>
#include <os_type.h>
#include "driver/uart.h"
#include "driver/gpio16.h"
#include "user_config.h"
#include "wemos.h"

#define DELAY 1000 /* milliseconds */
#define HOT_PIN D1                                /* Pin of hot water                     */
#define COLD_PIN D2                               /* Pin of cold water                    */
#define EXT_POWER_PIN D0                          /* Pin of monitoring external power     */


LOCAL os_timer_t mainTimer, buttonTimer;

_waterCounter waterCounter;

LOCAL uint8_t ICACHE_FLASH_ATTR debounce(uint8 sample) {

  static uint8 state = 0xFF, cnt0, cnt1;
  uint8 delta;

  delta = sample ^ state;
  cnt1 = (cnt1 ^ cnt0) & delta;
  cnt0 = ~cnt0 & delta;
  state ^= (cnt0 & cnt1);

  return state;
}

LOCAL void ICACHE_FLASH_ATTR buttonTimer_cb(void *arg) {

    uint8 bitHot, bitCold, bitStatus, counter = 0;

    bitHot = gpio_read(HOT_PIN);
    bitCold = gpio_read(COLD_PIN) << 1;

    counter |= bitHot;
    counter |= bitCold;

    waterCounter.status = debounce(counter);

    if (waterCounter.status & 0x01) {
        if (!(waterCounter.status_old & 0x01)) {
            waterCounter.status_old |= 0x01;
            waterCounter.counterHotWater++;
            os_printf("hotCounter  = %d\r\n", waterCounter.counterHotWater);
        }
    } else {
        if (waterCounter.status_old & 0x01) {
            waterCounter.status_old &= ~0x01;
        }
    }

    if (waterCounter.status & 0x02) {
        if (!(waterCounter.status_old & 0x02)) {
            waterCounter.status_old |= 0x02;
            waterCounter.counterColdWater++;
            os_printf("coldCounter  = %d\r\n", waterCounter.counterColdWater);
        }
    } else {
        if (waterCounter.status_old & 0x02) {
            waterCounter.status_old &= ~0x02;
        }
    }
}



LOCAL void ICACHE_FLASH_ATTR wakeupFromMotion(void) {
    wifi_fpm_close();
    wifi_set_opmode(STATION_MODE);
    os_printf("Wake up from sleep.\r\n");
}

void ICACHE_FLASH_ATTR sleepOnNow() {
    wifi_station_disconnect();
    wifi_set_opmode(NULL_MODE);
    wifi_fpm_set_sleep_type(LIGHT_SLEEP_T); //light sleep mode
    gpio_pin_wakeup_enable(GPIO_ID_PIN(pin_num[HOT_PIN]),
            GPIO_PIN_INTR_LOLEVEL); /* Set the interrupt to look for LOW pulses on HOT_PIN  */
    gpio_pin_wakeup_enable(GPIO_ID_PIN(pin_num[COLD_PIN]),
            GPIO_PIN_INTR_LOLEVEL); /* Set the interrupt to look for LOW pulses on COLD_PIN */
    wifi_fpm_open();
    wifi_fpm_set_wakeup_cb(wakeupFromMotion); //wakeup callback
    wifi_fpm_do_sleep(0xFFFFFFF);
}


LOCAL void ICACHE_FLASH_ATTR mainTimer_cb(void *arg)
{
    uint8 bit;

    bit = gpio_read(EXT_POWER_PIN);

    if (!bit) {
        os_printf("Power low. Light sleep now ...\r\n");
        os_delay_us(50000);
        sleepOnNow();
    }
}

/******************************************************************************
* FunctionName : user_rf_cal_sector_set
* Description  : SDK just reversed 4 sectors, used for rf init data and paramters.
*                We add this function to force users to set rf cal sector, since
*                we don't know which sector is free in user's application.
*                sector map for last several sectors : ABBBCDDD
*                A : rf cal
*                B : at parameters
*                C : rf init data
*                D : sdk parameters
* Parameters   : none
* Returns      : rf cal sector
*******************************************************************************/
uint32 ICACHE_FLASH_ATTR user_rf_cal_sector_set(void)
{
    enum flash_size_map size_map = system_get_flash_size_map();
    uint32 rf_cal_sec = 0;

    switch (size_map) {
        case FLASH_SIZE_4M_MAP_256_256:
            rf_cal_sec = 128 - 8;
            break;

        case FLASH_SIZE_8M_MAP_512_512:
            rf_cal_sec = 256 - 5;
            break;

        case FLASH_SIZE_16M_MAP_512_512:
        case FLASH_SIZE_16M_MAP_1024_1024:
            rf_cal_sec = 512 - 5;
            break;

        case FLASH_SIZE_32M_MAP_512_512:
        case FLASH_SIZE_32M_MAP_1024_1024:
            rf_cal_sec = 1024 - 5;
            break;

        default:
            rf_cal_sec = 0;
            break;
    }

    return rf_cal_sec;
}

void ICACHE_FLASH_ATTR user_rf_pre_init(void)
{
}

void ICACHE_FLASH_ATTR user_init(void)
{

    // Configure the UART
//    uart_init(BIT_RATE_115200, BIT_RATE_115200);
    uart_div_modify(0, UART_CLK_FREQ / 115200);

    if (set_gpio_mode(HOT_PIN, GPIO_INPUT, GPIO_PULLUP)) {
        os_printf("GPIO%d set input mode\r\n", pin_num[HOT_PIN]);
    }

    if (set_gpio_mode(COLD_PIN, GPIO_INPUT, GPIO_PULLUP)) {
        os_printf("GPIO%d set input mode\r\n", pin_num[COLD_PIN]);
    }

    if (set_gpio_mode(EXT_POWER_PIN, GPIO_INPUT, GPIO_PULLDOWN)) {
        os_printf("GPIO%d set input mode\r\n", pin_num[EXT_POWER_PIN]);
    }

    wifi_set_opmode(STATION_MODE);

    os_timer_disarm(&buttonTimer);
    os_timer_setfn(&buttonTimer, (os_timer_func_t *)buttonTimer_cb, (void *)0);
    os_timer_arm(&buttonTimer, 50, true);

    os_timer_disarm(&mainTimer);
    os_timer_setfn(&mainTimer, (os_timer_func_t *)mainTimer_cb, (void *)0);
    os_timer_arm(&mainTimer, DELAY, 1);
}
 

Slacky

Member
относительно колбека таймера поспешил.
Чтобы сказать определеннее надо поставить в колбеки принты и посмотреть результат.
Хм, ну я могу конечно поставить вывод некой инфы, но мне кажется это тут ни при чем ...
Не видно, где вы включили режим пинов на ввод.
В user_init
Код:
    if (set_gpio_mode(HOT_PIN, GPIO_INPUT, GPIO_PULLUP)) {
        os_printf("GPIO%d set input mode\r\n", pin_num[HOT_PIN]);
    }

    if (set_gpio_mode(COLD_PIN, GPIO_INPUT, GPIO_PULLUP)) {
        os_printf("GPIO%d set input mode\r\n", pin_num[COLD_PIN]);
    }
Ну и добавление в wakeupFromMotion(void)
Код:
    PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4);
    PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO5_U, FUNC_GPIO5);
положительных результатов не дало ...
еще замечу что в этом режиме таймеры все останавливаются.
Не знаю логику вашей проги, но вы это учитываете.
Не уверен, что таймеры остаются считать, но можно проверить, наверно, с помощью статической переменной ...
 

Slacky

Member
и еще надо учитывать, что после выхода в активный режим время установления соединения WIFI составит порядка 3 секунд
а у вас таймер работает с интервалом 50 мс.
Так задумано? Не получится что ESP никогда не установит соединение?
Не, 50 мс - именно только на опрос пинов.

Заметил пока следующее - если сбросить ESP, не замыкать пины на землю, то после пропадания внешнего питания все прекрасно засыпает. В спящем режиме, если один (или оба) из пинов замкнуть на землю, то ESP просыпается, отрабатывает подсчет нажатия (замыкания пина на массу) и опять спокойно засыпает.

Но если после старта позамыкать пины, а потом отключить питание, тут и срабатывает wdt ...
 

Slacky

Member
Пока для анализа выяснил только следующее. Если срабатывает wdt, то в юарт успевает проскочить такая инфа
Код:
Power low. Light sleep now ...
force slp enable,type: 1
pm close 7
fpm open,type:1 0
fpm 758

ets Jan  8 2013,rst cause:4, boot mode:(1,7)

wdt reset
Если же засыпаем нормально, то инфа другая
Код:
Power low. Light sleep now ...
state: 5 -> 0 (0)
rm 0
del if0
usl
mode : null
force slp enable,type: 1
fpm open,type:1 0
 

Slacky

Member
А как Вы отключаете питание? И главное -зачем (т е нафига)?
У Вемоса есть battery шилд. Питание на него можно подать через микроUSB. Приходящие 5 вольт через резистор подаем на D0. Если питание по USB приходит, то считаем внешне питание есть. Если нет, значит нет.

Зачем - при пропадании внешнего питания, переходим на питание от батареи ну и засыпанием уменьшаем ток потребления, но при этом остается возможность контролировать замыкание пинов ...
 

Slacky

Member
ссылки не открываются.
Возможно я не понял Ваш алгоритм, либо Вы не выключаете питание.
Так как если выключить питание и потом включить то это будет состояние RST а не deep-sleep.
Странно, у меня даже с работы открылась ...
https://wiki.wemos.cc/_media/products:d1_mini_shields:sch_battery_v1.2.0.pdf
Ни про какой deep sleep речи не идет. Запитывается модуль через battery шилд, к которому подходит внешнее питание и АКБ. При пропадании внешнего питания с battery шилда, модуль продолжает запитываться от АКБ. И мы обсуждаем light sleep ...
 

Slacky

Member
теперь понял.
Вопросы:
1) зачем записывать этот сигнал.
О каком сигнале идет речь?
2) Возможно он вызывает прерывание.
3) Через D0 выводится сигнал на светодиод Вы это учитываете?
Ну дело в том, что та же логика, но написанная на Arduino - работает ...
 

Slacky

Member
Это вам так хочется думать.
Но если там работает а здесь нет, то там не то,что здесь.
------------------------------
Я вообще-то пишу на CИ и делал этот режим, проблем не было.
Но режим не очень хороший так как всегда включается при выходе WIFI и время соединения всегда большое.
по существу -----------------
1) Вы убрали задержки ?
os_delay_us(50000);
-----------------------------
Да
2) можете отключить WDT
Поставьте в user_init()
функцию:
ets_wdt_disable();
ее надо объявить
void ets_wdt_disable(void);
-----------------
Делал. Просто тупо все подвисает и на пины не реагирует. Секунд через 30 перегружается.
3) попробуйте убрать пока работу с D0 или переместите сигнал с него на другой пин
-----------------------------------
Убрал. Сделал засыпание по таймеру через 15 секунд. Все тоже самое.
4) поставьте запрет прерывания при обработке сигналов от пинов
Не помогло.
и сброс регистра статуса пинов после обработки и перед разрешением прерывания
А вот про сброс регистра можно поподробней?

ЗЫ. А может я куда-нибудь выложу этот пример, а Вы его у себя проверите? Там дел то на 5 минут :))
 

Slacky

Member
можете выложить здесь или в переписку,
но я сейчас экспериментирую с RTL и RDA не очень хотелось бы отвлекаться.
Поэтому обещать ничего не буду.
Ну вдруг найдете пару минут :))

Ну или кто-нибудь еще попробует ...

http://slacky.ru/upload/files/WemosD1miniSleep.rar

И про сброс регистров можно более подробно?

Спасибо.
 

Slacky

Member
Я посмотрел Вашу программу.
Могу сказать следующее:
1) Не надо два раза определять колбеки таймеров. Если Вы их останавливаете то для запуска снова загрузите интервал.
Я делаю иначе.
Я пускаю без автозагрузки и пускаю снова в конце колбека таймера либо там где это мне нужно.
таким образом всегда гарантируется исполнение кода в колбеке до момента очередного вызова этого колбека.
Ну я и так и так пробовал. Просто в многочисленных примерах происходит переопределение. Ну я на всякий случай сделал так же ...
2) У Вас логика не верная.
Поясняю. У Вас просыпание происходит по внешнему сигналу. верно? Значит и обработку этого сигнала Вы должны делать в колбеке просыпания.
На мой взгляд правильная. Просыпание происходит по низкому уровню. А опрос кнопок выполнен на срабатывание от низкого в высокому. Поэтому эти два события никак не связаны между собой.
кроме того, после просыпания происходит восстановление соединения.
Вы нигде не контролируете это событие.
Надо использовать колбек WIFI
wifi_handle_event_cb
Это ж заготовка. Просто разорвать соединение - восстановить. В реальной программе так и сделано ...
-------------------
3) WDT не надо рестартовать.
---------------------
Это осталось от проверки на выключение wdt, нужно просто удалить ...
Вот пока примерно все, что можно сказать про вашу прогу
Меня все-таки интересует реальный запуск на другом модуле, другими руками и головой :))
 

Slacky

Member
Что произойдет, если у Вас о на 1 сменится до Вашей проверки?
или вы удерживаете ноль скажем секунду?
WDT не засвистит?
Большие задержки при работе WIFI не разрешаются.
А в момент установления соединения Вы можете прозевать смену 0 на 1.
----------------------
Это достаточно медленный счетчик, т.е. переход от нуля к единице может продолжаться 1 секунду. Я проверял, ничего не теряется. К тому же, еще раз хочу напомнить, это всего-лишь заготовка для проверки засыпания и не более ...
 

Slacky

Member
уберите ets_intr_lock(); и unlock() и повторное назначение колбека таймера из buttonTimer
если это заготовка то сделайте одну кнопку и упростите ее проверку
----------------
ets_intr_lock и ets_intr_unlock, а также переопределение таймеров - это уже в процессе поиска срабатывания wdt, изначально там их не было ...
и еще у Вас нет установки пинов на ввод в user_init()
Вот это разве не оно?

Код:
    if (set_gpio_mode(HOT_PIN, GPIO_INPUT, GPIO_PULLUP)) {
        os_printf("GPIO%d set input mode\r\n", pin_num[HOT_PIN]);
    }

    if (set_gpio_mode(COLD_PIN, GPIO_INPUT, GPIO_PULLUP)) {
        os_printf("GPIO%d set input mode\r\n", pin_num[COLD_PIN]);
    }
 

Slacky

Member
Если решать Вашу задачу правильно и красиво, то надо делать так:
Когда esp просыпается по нулю, вы меняете сигнал просыпания на 1 и снова идете спать.
Когда проснетесь по единице меняете на ноль и справляете нужду с WIFI и идете спать.
Это уже частности, но дело в том, что до каллбека не доходит, все падает на моменте засыпания ...
 

Slacky

Member
----------------
это уберите.
У вас есть закоментировано PIN_FUNC_SELECT... для GPIO4 и GPIO5 перенесите их в user_init
и после них добавьте
GPIO_DIS_OUTPUT(GPIO_ID_PIN(4));
GPIO_DIS_OUTPUT(GPIO_ID_PIN(5));
А в чем разница? Честно, не копал, просто предполагаю, что в принципе это одно и то же ...
 
Сверху Снизу