Внешние прерывания Esp32 иногда отсутствуют
Короче говоря. ESP32, два счетчика воды (горячей и холодной). Будка, горячая и холодная, некоторые прерывания пропущены. Когда я подключаю ESP32 к последовательному монитору и смотрю, то обнаруживаются все прерывания (около 5-10 минут). Но если я оставлю питание только на 5 В, то некоторые прерывания будут пропущены (за один день их может быть до 50 или даже больше).
О счетчике воды - один импульс на литр. Правильная информация о датчике содержится в оригинальной инструкции по эксплуатации в формате PDF.
Подключения датчиков. Коричневый провод к GND, белый к контакту 32(горячий),33(холодный).
Плата. Olimex ESP32-POE Rev C.
- Питание от зарядного устройства Samsung 5 В (стандартное зарядное устройство 5 В без 9 В)
- Включен Wi-Fi (для NTP и просмотра показаний счетчика)
- Включено OTA
- Запущенный веб-сервер (обычный html, ESPAsyncWebServer)
- Bluetooth отключен
- Подключена SD-карта (хранит только базу данных SQLite)
- Использует библиотеку siara-cc/esp32_arduino_sqlite3_lib
Потребление воды в прошлом месяце (май)
- горячая - 4777 литров (подсчитано с помощью датчика - 2675)
- холод - 9289, с датчиком - 5528
Как мы видим, разница огромна. Может быть, время выполнения процедуры ISR слишком велико, и некоторые прерывания пропущены? Я знаю, что Wi-Fi тоже использует прерывания... Мой код прерывания неверен? Показания счетчика воды сохраняются в базе данных SQLite при использовании 10 литров. Заранее спасибо всем, кто уделил вам время!
Вот изображение счетчика воды и датчика
Вот некоторый код, связанный с прерываниями (отладка отключена).
struct WaterCounter
{
const uint8_t PIN; // microcontroller pin...
volatile uint32_t liters_total; // max 4294967295
volatile uint16_t liters_in_session; // max 65535
volatile uint32_t session_started; // unix timestamp
volatile uint32_t session_last_pulse; // unix timestamp
const char display_name[5];
volatile unsigned long pulse_start;
volatile unsigned long pulse_stop;
volatile uint32_t isr_prev_time;
volatile uint32_t liters_last_written;
volatile uint32_t liters_used_today;
};
WaterCounter wc_hot = { PIN_WC_HOT, 0, 0, 0, 0, "Hott", 0, 0, 0, 0, 0 };
WaterCounter wc_cold = {PIN_WC_COLD, 0, 0, 0, 0, "Cold", 0, 0, 0, 0, 0 };
void IRAM_ATTR handle_wc(WaterCounter *wc)
{
uint32_t t = micros();
uint32_t dt = t - wc->isr_prev_time;
if (dt<1500) return;
wc->isr_prev_time = t;
if (wc->pulse_start == 0)
{
wc->pulse_start = millis();
#ifdef DEBUG_ISR
//Serial.printf("Started %s pulse!\n", wc->display_name);
ets_printf("Started %s pulse!\n", wc->display_name);
#endif
}else
{
wc->pulse_stop = millis();
unsigned long pulsew = wc->pulse_stop - wc->pulse_start;
wc->pulse_start = 0;
if (pulsew > 40 && pulsew < 55)
{
wc->liters_total++;
wc->liters_in_session++;
wc->liters_used_today++;
if (wc->session_started == 0)
{
wc->session_started = now;
}
wc->session_last_pulse = now;
}
#ifdef DEBUG_ISR
//Serial.printf("%s: pulse_width: %ld\n", wc->display_name, pulsew);
ets_printf("%s: pulse_width: %ld\n", wc->display_name, pulsew);
#endif
}
}
void IRAM_ATTR isr_wc_cold()
{
handle_wc(&wc_cold);
}
void IRAM_ATTR isr_wc_hot()
{
handle_wc(&wc_hot);
}
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.println("Boot...");
// allow some time to pull up pin, interrupt is attached below
pinMode(wc_cold.PIN, INPUT_PULLUP);
pinMode(wc_hot.PIN, INPUT_PULLUP);
Serial.printf("Watchdog timeout: %dms\n", WATCHDOG_TIMEOUT);
timerWD = timerBegin(0, 80, true); //timer 0, div 80
timerAttachInterrupt(timerWD, &resetModule, true); //attach callback
timerAlarmWrite(timerWD, WATCHDOG_TIMEOUT * 1000, false); //set time in us
timerAlarmEnable(timerWD); //enable interrupt
timerWrite(timerWD, 0); //reset timer (feed watchdog)
SD_MMC.begin();
enqRC = sqlite3_initialize();
if (enqRC == SQLITE_OK)
{
openDb();
initSensorDatabase();
timerWrite(timerWD, 0); //reset timer (feed watchdog)
SetupWiFi();
closeDb();
}
timerWrite(timerWD, 0); //reset timer (feed watchdog)
pollNtp();
timerWrite(timerWD, 0); //reset timer (feed watchdog)
openDb();
initTotalValues(&wc_cold);
timerWrite(timerWD, 0); //reset timer (feed watchdog)
initTotalValues(&wc_hot);
closeDb();
timerWrite(timerWD, 0); //reset timer (feed watchdog)
attachInterrupt(digitalPinToInterrupt(wc_cold.PIN), &isr_wc_cold, FALLING);
attachInterrupt(digitalPinToInterrupt(wc_hot.PIN), &isr_wc_hot, FALLING);
timer_session = timerBegin(1, 80, true);
timerAttachInterrupt(timer_session, &onSessionTimer, true);
timerAlarmWrite(timer_session, 1000000, true); // every 1 second
timerAlarmEnable(timer_session);
EnableOTA();
//* async web
webServer.on("/", HTTP_GET, handleRootAsync);
webServer.on("/heap", HTTP_GET, heapInfoAsync);
webServer.on("/post", HTTP_POST, handlePostAsync);
webServer.on("/params", HTTP_GET, handleParameterPage);
webServer.onNotFound(onNotFoundAsync);
webServer.begin();
time(&now);
}
void loop() {
// put your main code here, to run repeatedly:
loop_task();
}
void loop_task()
{
if (time_every_second <= now)
{
writeWcTotalToDb(&wc_cold);
writeWcTotalToDb(&wc_hot);
time_every_second = now + 1;
}
if (time_every_30_seconds <= now)
{
pollNtp();
time_every_30_seconds = now + 30;
}
}
void IRAM_ATTR finishSession(WaterCounter *wc, time_t tt)
{
if (wc->session_started == 0)
{
return;
}
if (tt - wc->session_last_pulse > SESSION_LENGHT)
{
//finish session
#ifdef DEBUG_ISR
//Serial.printf("# %s liters in session: %d, duration %d(s)\n", wc->display_name, wc->liters_in_session, wc->session_last_pulse - wc->session_started);
ets_printf("# %s liters in session: %d, duration %d(s)\n", wc->display_name, wc->liters_in_session, wc->session_last_pulse - wc->session_started);
#endif
wc->session_started = 0;
wc->liters_in_session = 0;
}
}
/* every 1s */
void IRAM_ATTR onSessionTimer()
{
timerWrite(timerWD, 0); //reset timer (feed watchdog)
//ets_printf("\nonSessionTimer()\n");
time(&now);
getLocalTime(&timeinfo);
strftime(timeStringBuff, sizeof(timeStringBuff), "%Y-%m-%d %H:%M:%S", &timeinfo);
if (now % 86400 == 75600) /* works if this function is called every second */
{
ets_printf("\n!!!midnight!!\n");
wc_cold.liters_used_today = 0;
wc_hot.liters_used_today = 0;
}
finishSession(&wc_cold, now);
finishSession(&wc_hot, now);
}
void writeWcTotalToDb(WaterCounter *wc)
{
#ifdef DEBUG_WRITE
Serial.printf("writeWcTotalToDb(%s): begin\n", wc->display_name);
#endif
uint8_t ldiff = wc->liters_total - wc->liters_last_written;
if (ldiff == 0)
{
#ifdef DEBUG_WRITE
Serial.println("writeWcTotalToDb(): diff zero");
#endif
return;
}
if (ldiff <= LITERS_BEFORE_WRITE && wc->session_started > 0)
{
#ifdef DEBUG_WRITE
Serial.println("writeWcTotalToDb(): ldiff <= LITERS_BEFORE_WRITE && wc->session_started > 0");
#endif
return;
}
#ifdef DEBUG_WRITE
Serial.println("writeWcTotalToDb(): start writting procedure!");
#endif
if (!openDb())
{
char *sql = "INSERT INTO `water_counter` VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
sqliteResultCode = sqlite3_prepare_v2(sqliteDb, sql, strlen(sql), &sqliteRes, NULL);
if (sqliteResultCode != SQLITE_OK)
{
#ifdef DEBUG
Serial.printf("writeWcTotalToDb(): ERROR preparing sql: %d -> %s\n", sqliteResultCode, sqlite3_errmsg(sqliteDb));
#endif
}else
{
#ifdef DEBUG
Serial.print("writeWcTotalToDb(): Prepare OK\n");
#endif
//getLocalTime(&timeinfo); // inside every 1 sec
//strftime(timeStringBuff, sizeof(timeStringBuff), "%Y-%m-%d %H:%M:%S", &timeinfo);
sqlite3_bind_int(sqliteRes, 1, now); // datums
sqlite3_bind_int(sqliteRes, 2, wc->liters_total);
sqlite3_bind_text(sqliteRes, 3, timeStringBuff, strlen(timeStringBuff), SQLITE_STATIC);
sqlite3_bind_text(sqliteRes, 4, wc->display_name, strlen(wc->display_name), SQLITE_STATIC);
sqlite3_bind_int(sqliteRes, 5, 0);
sqlite3_bind_int(sqliteRes, 6, wc->liters_in_session);
sqlite3_bind_int(sqliteRes, 7, wc->session_started);
sqlite3_bind_int(sqliteRes, 8, 0); /* currently not used */
if (sqliteResultCode = sqlite3_step(sqliteRes) != SQLITE_DONE)
{
#ifdef DEBUG
Serial.printf("writeWcTotalToDb(): ERROR executing stmt: %d -> %s\n", sqliteResultCode, sqlite3_errmsg(sqliteDb));
#endif
}else
{
#ifdef DEBUG
Serial.print("writeWcTotalToDb(): data saved!\n");
#endif
wc->liters_last_written = wc->liters_total;
sqlite3_clear_bindings(sqliteRes);
sqliteResultCode = sqlite3_reset(sqliteRes);
if (sqliteResultCode != SQLITE_OK)
{
#ifdef DEBUG
Serial.printf("writeWcTotalToDb(): sqlite3_reset(res) result code = [%s] %s\n", sqliteResultCode, sqlite3_errmsg(sqliteDb));
#endif
}else
{
#ifdef DEBUG
Serial.print("writeWcTotalToDb(): sqlite3_reset(res) OK\n");
#endif
}
sqlite3_finalize(sqliteRes);
}
}
closeDb();
}else
{
#ifdef DEBUG
Serial.println("writeWcTotalToDb(): Failed to open database!");
#endif
}
#ifdef DEBUG_WRITE
Serial.printf("writeWcTotalToDb(%s): finished!\n", wc->display_name);
#endif
} /* writeWcTotalToDb() */
@Guntis, 👍0
Обсуждение2 ответа
Лучший ответ:
Как и предполагал @Majenko, я создал решение со счетчиком импульсов.
Вот рабочий код, основанный на примере espressif PCNT. Студия Arduino, плата Olimex ESP32-POE.
Каждый счетчик воды использует отдельный блок счетчика импульсов. Сейчас я тестирую этот код и через несколько дней приму ответ, если все будет работать так, как я хочу :)
#include "freertos/queue.h"
#include "driver/pcnt.h"
#include "driver/periph_ctrl.h"
#include "driver/gpio.h"
#include "esp_attr.h"
#define PCNT_H_LIM_VAL 1
#define PCNT_L_LIM_VAL -1
#define PCNT_THRESH1_VAL 0
#define PCNT_THRESH0_VAL -0
#define PCNT_INPUT_SIG_WC_HOT 32 // hot water counter
#define PCNT_INPUT_SIG_WC_COLD 33 // cold water counter
/*
Cold meter uses PCNT_UNIT_1
hot meter uses PCNT_UNIT_0
*/
xQueueHandle pcnt_evt_queue; // A queue to handle pulse counter events
pcnt_isr_handle_t user_isr_handle = NULL; //user's ISR service handle
/* A sample structure to pass events from the PCNT
* interrupt handler to the main program.
*/
typedef struct {
pcnt_unit_t unit; // the PCNT unit that originated an interrupt
uint32_t status; // information on the event type that caused the interrupt
} pcnt_evt_t;
/* Decode what PCNT's unit originated an interrupt
* and pass this information together with the event type
* the main program using a queue.
*/
static void IRAM_ATTR pcnt_example_intr_handler(void *arg)
{
uint32_t intr_status = PCNT.int_st.val;
int i;
pcnt_evt_t evt;
portBASE_TYPE HPTaskAwoken = pdFALSE;
for (i = 0; i < PCNT_UNIT_MAX; i++) {
if (intr_status & (BIT(i))) {
evt.unit = (pcnt_unit_t)i;
/* Save the PCNT event type that caused an interrupt
to pass it to the main program */
evt.status = PCNT.status_unit[i].val;
PCNT.int_clr.val = BIT(i);
xQueueSendFromISR(pcnt_evt_queue, &evt, &HPTaskAwoken);
if (HPTaskAwoken == pdTRUE) {
portYIELD_FROM_ISR();
}
}
}
}
/* Initialize PCNT functions:
* - configure and initialize PCNT
* - set up the input filter
* - set up the counter events to watch
*/
static void pcnt_example_init()
{
//Serial.printf("pcnt_example_init(%d, %d)\n", _unit, _pin);
/* Prepare configuration for the PCNT unit */
pcnt_config_t pcnt_config = {};
pcnt_config.pulse_gpio_num = PCNT_INPUT_SIG_WC_HOT;
pcnt_config.ctrl_gpio_num = PCNT_PIN_NOT_USED;
pcnt_config.channel = PCNT_CHANNEL_0;
pcnt_config.pos_mode = PCNT_COUNT_INC;
pcnt_config.neg_mode = PCNT_COUNT_DIS;
pcnt_config.lctrl_mode = PCNT_MODE_KEEP;//PCNT_MODE_REVERSE;
pcnt_config.hctrl_mode = PCNT_MODE_KEEP;
pcnt_config.counter_h_lim = PCNT_H_LIM_VAL;
pcnt_config.counter_l_lim = PCNT_L_LIM_VAL;
pcnt_config.unit = PCNT_UNIT_0;
/* Initialize PCNT unit */
ESP_ERROR_CHECK(pcnt_unit_config(&pcnt_config));
/* cold water meter */
pcnt_config.pulse_gpio_num = PCNT_INPUT_SIG_WC_COLD;
pcnt_config.ctrl_gpio_num = PCNT_PIN_NOT_USED;
pcnt_config.channel = PCNT_CHANNEL_0;
pcnt_config.pos_mode = PCNT_COUNT_INC;
pcnt_config.neg_mode = PCNT_COUNT_DIS;
pcnt_config.lctrl_mode = PCNT_MODE_KEEP;//PCNT_MODE_REVERSE;
pcnt_config.hctrl_mode = PCNT_MODE_KEEP;
pcnt_config.counter_h_lim = PCNT_H_LIM_VAL;
pcnt_config.counter_l_lim = PCNT_L_LIM_VAL;
pcnt_config.unit = PCNT_UNIT_1;
ESP_ERROR_CHECK(pcnt_unit_config(&pcnt_config));
/* Configure and enable the input filter */
ESP_ERROR_CHECK(pcnt_set_filter_value(PCNT_UNIT_0, 1000)); // filter_val is a 10-bit value, so the maximum filter_val should be limited to 1023.
ESP_ERROR_CHECK(pcnt_filter_enable(PCNT_UNIT_0));
/* Set threshold 0 and 1 values and enable events to watch */
ESP_ERROR_CHECK(pcnt_set_event_value(PCNT_UNIT_0, PCNT_EVT_THRES_1, PCNT_THRESH1_VAL));
ESP_ERROR_CHECK(pcnt_event_enable(PCNT_UNIT_0, PCNT_EVT_THRES_1));
ESP_ERROR_CHECK(pcnt_set_event_value(PCNT_UNIT_0, PCNT_EVT_THRES_0, PCNT_THRESH0_VAL));
ESP_ERROR_CHECK(pcnt_event_enable(PCNT_UNIT_0, PCNT_EVT_THRES_0));
/* Enable events on zero, maximum and minimum limit values */
ESP_ERROR_CHECK(pcnt_event_enable(PCNT_UNIT_0, PCNT_EVT_ZERO));
ESP_ERROR_CHECK(pcnt_event_enable(PCNT_UNIT_0, PCNT_EVT_H_LIM));
ESP_ERROR_CHECK(pcnt_event_enable(PCNT_UNIT_0, PCNT_EVT_L_LIM));
/* Initialize PCNT's counter */
ESP_ERROR_CHECK(pcnt_counter_pause(PCNT_UNIT_0));
ESP_ERROR_CHECK(pcnt_counter_clear(PCNT_UNIT_0));
/* Register ISR handler and enable interrupts for PCNT unit */
ESP_ERROR_CHECK(pcnt_isr_register(pcnt_example_intr_handler, NULL, 0, &user_isr_handle));
ESP_ERROR_CHECK(pcnt_intr_enable(PCNT_UNIT_0));
/* Everything is set up, now go to counting */
ESP_ERROR_CHECK(pcnt_counter_resume(PCNT_UNIT_0));
/* PCNT_UNIT_1 configuration */
/* Configure and enable the input filter */
ESP_ERROR_CHECK(pcnt_set_filter_value(PCNT_UNIT_1, 1000)); // filter_val is a 10-bit value, so the maximum filter_val should be limited to 1023.
ESP_ERROR_CHECK(pcnt_filter_enable(PCNT_UNIT_1));
/* Set threshold 0 and 1 values and enable events to watch */
ESP_ERROR_CHECK(pcnt_set_event_value(PCNT_UNIT_1, PCNT_EVT_THRES_1, PCNT_THRESH1_VAL));
ESP_ERROR_CHECK(pcnt_event_enable(PCNT_UNIT_1, PCNT_EVT_THRES_1));
ESP_ERROR_CHECK(pcnt_set_event_value(PCNT_UNIT_1, PCNT_EVT_THRES_0, PCNT_THRESH0_VAL));
ESP_ERROR_CHECK(pcnt_event_enable(PCNT_UNIT_1, PCNT_EVT_THRES_0));
/* Enable events on zero, maximum and minimum limit values */
ESP_ERROR_CHECK(pcnt_event_enable(PCNT_UNIT_1, PCNT_EVT_ZERO));
ESP_ERROR_CHECK(pcnt_event_enable(PCNT_UNIT_1, PCNT_EVT_H_LIM));
ESP_ERROR_CHECK(pcnt_event_enable(PCNT_UNIT_1, PCNT_EVT_L_LIM));
/* Initialize PCNT's counter */
ESP_ERROR_CHECK(pcnt_counter_pause(PCNT_UNIT_1));
ESP_ERROR_CHECK(pcnt_counter_clear(PCNT_UNIT_1));
ESP_ERROR_CHECK(pcnt_intr_enable(PCNT_UNIT_1));
/* Everything is set up, now go to counting */
ESP_ERROR_CHECK(pcnt_counter_resume(PCNT_UNIT_1));
}
void setup() {
Serial.begin(115200);
Serial.println("Boot...");
// put your setup code here, to run once:
pinMode(PCNT_INPUT_SIG_WC_HOT, INPUT_PULLUP);
pinMode(PCNT_INPUT_SIG_WC_COLD, INPUT_PULLUP);
/* Initialize PCNT event queue and PCNT functions */
pcnt_evt_queue = xQueueCreate(10, sizeof(pcnt_evt_t));
pcnt_example_init();
// pcnt_example_init(PCNT_UNIT_WC_HOT, PCNT_INPUT_SIG_WC_HOT);
//pcnt_example_init(PCNT_UNIT_WC_COLD, PCNT_INPUT_SIG_WC_COLD);
Serial.printf("Setup done\n");
}
int16_t count = 0;
pcnt_evt_t evt;
portBASE_TYPE res;
char * chToName(int ch)
{
if (ch == 1) return "Cold";
if (ch == 0) return "Hot";
return "Unc";
}
void loop() {
// put your main code here, to run repeatedly:
/* Wait for the event information passed from PCNT's interrupt handler.
* Once received, decode the event type and print it on the serial monitor.
*/
res = xQueueReceive(pcnt_evt_queue, &evt, 1000 / portTICK_PERIOD_MS);
if (res == pdTRUE) {
pcnt_get_counter_value(evt.unit, &count);
Serial.printf("Event PCNT unit[%d=%s]; cnt: %d, status: %d\n", evt.unit, chToName(evt.unit), count, evt.status);
if (evt.status & PCNT_EVT_THRES_1) {
Serial.printf("THRES1 EVT\n");
}
if (evt.status & PCNT_EVT_THRES_0) {
Serial.printf("THRES0 EVT\n");
}
if (evt.status & PCNT_EVT_L_LIM) {
Serial.printf("L_LIM EVT\n");
}
if (evt.status & PCNT_EVT_H_LIM) {
Serial.printf("H_LIM EVT\n");
}
if (evt.status & PCNT_EVT_ZERO) {
Serial.printf("ZERO EVT\n");
// here we are increment liter count
}
} else {
pcnt_get_counter_value(PCNT_UNIT_0, &count);
Serial.printf("Current counter value: %s=%d,", chToName(PCNT_UNIT_0), count);
pcnt_get_counter_value(PCNT_UNIT_1, &count);
Serial.printf(" %s=%d\n", chToName(PCNT_UNIT_1), count);
//pcnt_get_counter_value(PCNT_UNIT_WC_COLD, &count);
//Serial.printf(", %d on unit: %d\n", count, PCNT_UNIT_WC_COLD);
}
/*
if(user_isr_handle) {
Serial.printf("Clearing user_isr_handle\n");
//Free the ISR service handle.
esp_intr_free(user_isr_handle);
user_isr_handle = NULL;
}*/
}
Вы можете использовать функцию счетчика импульсов(PCNT) в ESP32 для подсчета количества импульсов в фоновом режиме, также можно настроить событие,когда количество отсчетов достигло определенного порога и имело множество опций,
Для получения дополнительной информации и доступных интерфейсов и API для счетчика импульсов(PCNT), пожалуйста, перейдите по ссылке ниже, https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/pcnt.html
Первоначально я столкнулся с большой проблемой, чтобы заставить счетчик импульсов(PCNT) работать в IDE Adrino для ESP-32, После нескольких попыток я заставил его работать, И тот же пример кода загружен в GitHub для справки. Я не использовал все API в официальной документации, но использовал несколько из них и работаю..
Я создал простую примерную программу для расходомера воды, там же мы используем для получения импульса, который необходимо подсчитать для измерения расхода воды.
Путь к образцу кода на GitHub:- https://github.com/Embedded-Linux-Developement/Arduino_Sample_Programs/tree/main/ESP_32/Water_Flow_Pulse_counter_WithOut_Interrupt_Using_PCNT
Я не размещал код здесь, потому что он есть в GitHub и является сравнением, и я могу его использовать. Это рабочий код, который я тестировал в ESP32 HW.
Надеется, что Это поможет, С уважением, Джерри Джеймс
- Как реализовать детектор brown out arduino ESP32?
- ESP32 в Arduino-IDE с FS.h и SPIFFS
- Программаторы для этой платы отсутствуют - Программирование ESP32 Cam с помощью Ardunio IDE
- Установка значения float до двух знаков после запятой
- ESP32-CAM первый: 0x8 TG1WDT_SYS_RESET загрузочный цикл
- esp32 Stack canary watchpoint срабатывает
- Проверка размера во флэш-памяти Esp32
- Постоянная частота дискретизации АЦП на ESP8266 или ESP32
Вы должны переключиться на использование периферийного устройства "счетчик импульсов" в ESP32. Подробнее читайте здесь: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/pcnt.html, @Majenko
Попробуем это завтра, @Guntis
@Majenko: можно ли это использовать с Arduino?, @dandavis
двоичный счетчик IC сделал бы надежность простой, но если есть встроенный счетчик,который мы можем использовать, это еще лучше., @dandavis
ДА. Arduino использовал IDF для управления чипом. Вы просто пропускаете слой Arduino и вызываете IDF напрямую., @Majenko
Я попробовал pcnt (с arduino). Он работает с одним входным контактом, но пока нет, я не могу работать с двумя разными входами (для каждого счетчика воды). Как я понял, я должен запустить два экземпляра pcnt.., @Guntis
Может быть, кто-то знает, как получить номер канала в обработчике pcnt_isr_register?, @Guntis