Как известно, микросхема PCF8583 умеет считать внешние импульсы до 999 999, что очень мало (например, типовой счетчик электроэнергии выдает 3200 импульсов на кВт*ч, что позволило бы нам посчитать всего только 312 кВт до переполнения регистров PCF8583).
Для расширения счетных возможностей измерительной системы предлагается использовать дополнительный регистр, увеличивающий свое значение на единицу при каждом переполнении PCF8583. Затем можно значение данного регистра учитывать при подсчете итогового показания.
Второй не менее важной задачей является сохранение показаний при отключении электропитания и решается она запитыванием самого счетчика PCF8583 от батарейки (см. фото):
Для хранения переменных для промежуточных вычислений и контроля переполнения счетных регистров воспользуемся внутренними регистрами микросхемы, доступными для чтения/записи:
Прилагаю исходный код для конструктора кода:
uint32_t current_value; // Инициализация текущего значения счетчика
uint32_t previous_value; // Инициализация предыдущего значения счетчика
uint32_t counter_overflows; // Инициализация счетчика переполнений
uint32_t division_ratio; // Инициализация коэффициента деления
uint32_t initial_indication; // Инициализация начального показания счетчика
uint32_t div1; // Инициализация коэффициента деления
uint32_t init1; // Инициализация начального показания счетчика
uint32_t counter1_print; // Значение показаний счетчика 1
uint32_t counter2_print; // Значение показаний счетчика 2
uint8_t value_1,value_2, value_3; // Переменные value_1,value_2, value_3
void ICACHE_FLASH_ATTR
startfunc()
{
// выполняется один раз при старте модуля
}
PCF8583_read_my(uint8_t adr)
{
i2c_start(); // Устанавливаем на шине Start состояние
i2c_writeByte(adr*2); // отправляем байт 0b101000yх (отклик любой RTC)
if(i2c_getAck()){return(-1);} // slave не откликнулся
i2c_writeByte(01); // устанавливаем регистр для чтения
if(i2c_getAck()){return(-1);} // slave not ack
i2c_stop(); // Формируем на шине Stop состояние
// ------------ читаем текущее значение (current_value) ----------------
i2c_start(); // Устанавливаем на шине Start состояние
i2c_writeByte(adr*2+1);
if(i2c_getAck()){return(-1);} // slave не откликнулся
uint8_t Val=0; // Инициализация вспомогательной переменной
Val= i2c_readByte(); // ACK
current_value=(Val&15) + (Val/16)*10; // Поразрядное логическое "И" +
value_1=Val;
i2c_setAck(0);
Val= i2c_readByte(); //ACK
current_value=current_value+ (Val&15)*100 + (Val/16)*1000;
value_2=Val;
i2c_setAck(0);
Val= i2c_readByte(); //ACK
current_value=current_value+ (Val&15)*10000 + (Val/16)*100000;
value_3=Val;
i2c_setAck(0);
// -------- читаем предыдущее значение (previous_value) ------------
Val= i2c_readByte(); //ACK
previous_value=(Val&15) + (Val/16)*10;
i2c_setAck(0);
Val= i2c_readByte(); //ACK
previous_value=previous_value+ (Val&15)*100 + (Val/16)*1000;
i2c_setAck(0);
Val= i2c_readByte(); //ACK
previous_value=previous_value+ (Val&15)*10000 + (Val/16)*100000;
i2c_setAck(0);
//---- читаем содержимое счетчика переполнений (counter_overflows)----
Val = i2c_readByte(); //ACK
counter_overflows = Val;
i2c_setAck(0);
//---------------------------------------------------------------------------
i2c_setAck(1);
i2c_stop();
//return current_value;
}
// ------- Функция обновления предыдущего значения -------------------
PCF8583_upd_previous(uint8_t adr)
{
i2c_start(); // Устанавливаем на шине Start состояние
i2c_writeByte(adr*2); // отправляем байт 0b101000yх (откл любой RTC)
if(i2c_getAck()){return;} // slave не откликнулся
i2c_writeByte(04); // устанавливаем адрес регистра 0x00 для чтения
if(i2c_getAck()){return;} // slave не откликнулся
i2c_writeByte(value_1); // записываем по адресу 0x00 байт 0x20
if(i2c_getAck()){return;} // slave не откликнулся
i2c_writeByte(value_2); // записываем по адресу 0x00 байт 0x20
if(i2c_getAck()){return;} // slave не откликнулся
i2c_writeByte(value_3); // записываем по адресу 0x00 байт 0x20
if(i2c_getAck()){} // slave не откликнулся
i2c_stop(); // Формируем на шине Stop состояние
}
PCF8583_upd_counter_overflows(uint8_t adr)
{
i2c_start(); // Устанавливаем на шине Start состояние
i2c_writeByte(adr*2); // отправл байт на шину 0b101000yх (отк любой RTC)
if(i2c_getAck()){return;} // slave не откликнулся
i2c_writeByte(07); // устанавливаем адрес регистра 0x00 для чтения
if(i2c_getAck()){return;} // slave не откликнулся
i2c_writeByte(counter_overflows); // записываем по адресу 0x00 байт 0x20
if(i2c_getAck()){} // slave не откликнулся
i2c_stop(); // Формируем на шине Stop состояние
}
void ICACHE_FLASH_ATTR
timerfunc(uint32_t timersrc)
{
if(timersrc%10==0) // выполнение кода каждые 10 секунд
{
// ---------------- Логика работы счетчика A -------------------
PCF8583_read_my (0x50);
if (current_value>previous_value)
{
PCF8583_upd_previous(0x50); // Перезапись предыдущего значения счетчика текущим
}
else
{
if(current_value!=previous_value)
{
counter_overflows++;
PCF8583_upd_previous(0x50); // Перезапись предыдущего значения счетчика текущим
PCF8583_upd_counter_overflows(0x50); // Перезапись счетчика переполнений counter_overflows
}
}
init1=sensors_param.cfgdes[0]; // Читаем начальное значение из flash памяти esp
div1=sensors_param.cfgdes[1]; // Читаем значение коэффициента деления из flash памяти esp
if (div1==0) div1=1; // исключение деления на ноль
counter1_print= init1+((current_value*10+999999*10*counter_overflows)/div1);
valdes[0] = counter1_print;
// ------ Счетчик B ----------------------------------------
current_value=0; // Обнуляем переменные
previous_value=0; // Обнуляем переменные
counter_overflows=0; // Обнуляем переменные
PCF8583_read_my (0x51);
if (current_value>previous_value)
{
PCF8583_upd_previous(0x51); // Перезапись предыдущего значения счетчика текущим
}
else
{
if(current_value!=previous_value)
{
counter_overflows++;
PCF8583_upd_previous(0x51); // Перезапись предыдущего значения счетчика текущим
PCF8583_upd_counter_overflows(0x51); // Перезапись счетчика переполнений counter_overflows
}
}
init1=sensors_param.cfgdes[2]; // Читаем начальное значение из flash памяти esp
div1=sensors_param.cfgdes[3]; // Читаем значение коэффициента деления из flash памяти esp
if (div1==0) div1=1; // исключение деления на ноль
counter2_print= init1+((current_value*10+999999*10*counter_overflows)/div1);
valdes[1] = counter2_print;
}
}
void webfunc(char *pbuf) {
os_sprintf(HTTPBUFF,"Показания счетчика A: <b>%s</b> ,", fltostr(counter1_print));
os_sprintf(HTTPBUFF,"<br><hr>Показания счетчика B: <b>%s</b> ,", fltostr(counter2_print));
os_sprintf(HTTPBUFF,"<br><hr><form action='/i2cgo'><input type='hidden' name='adr' value='50' /><input type='hidden' name='set' value='002000000000000000' /><button type='submit'>Сброс счетчика A</button></form>");
os_sprintf(HTTPBUFF,"<br><hr><form action='/i2cgo'><input type='hidden' name='adr' value='51' /><input type='hidden' name='set' value='002000000000000000' /><button type='submit'>Сброс счетчика B</button></form>");
}
Также необходимо сделать дополнительные настройки, определив глобальные переменные и переменные для хранения пользовательских данных во flash памяти ESP:
Так выглядит окно главной страницы модуля
Как пользоваться таким счетчиком:
1. Во вкладке Designer code options в поле «Показания счетчика А:» вводим текущее значение Вашего счетчика (например электроэнергии), умноженное на 10. Например, для 1237,4кВт*ч, надо записать 12374).
2. В поле «Делитель счетчика А:» указываете число имп. на 1кВт*ч (типовое значение – 3200, ничего домножать не надо!).
3. Нажимаем Set и настройки сохранятся
4. В окне главной страницы модуля нажимаем кнопку «Сброс счетчика A». Будет выполнен GET-запрос вида IP адрес/i2cgo?adr=50&set=002000000000000000, микросхема перейдет в режим счетчика событий и регистры обнулятся.
Показания (переменные valuedes0, valuedes1) отправляются на сервера MQTT и MajorDomo.
Пример вывода IP адрес/sensors
hostname:ESPCounter;pcfcnt1:146;pcfcnt2:145;valuedes0:1214;valuedes1:645;