Ваттметр 3-х канальный с перспективой.

mikelari
Offline
Зарегистрирован: 14.05.2015

В минимальном варианте это ваттметр трёхканальный (каналы независимые с  гальванической изоляцией между собой). Но также платформа для надстройки чего только нужно или можно, что в дальнейшем и планируется. На данном этапе собрано на DUE от Iduino, самодельный шилд для ili9341,  2.4 дюйма   ili9341 с тачскрином и SD. Датчики тока и напряжения это I2C  оптоизолированые  ina-226 купленные на https://strawberry-linux.com. Датчики могут измерять напряжение до 36 вольт и ток по каналам соответственно 3.2А , 20А, 100А. При проектировании прибора не брал за образец какой-либо готовый. Просто что вижу, то и пою. 

Немного фото:

На фото выше видно 100 А шунт.

//Трех канальный ваттметр на датчиках INA 226 с дальнейшим расширением функционала
//Канал1 ток до 3.2А, Канал2 ток до 20.А, Канал3 ток до 100 А

String Ver="Ver.     pm_5";        //версия прошивки 

#include <Wire.h>
#include <SPI.h>
#include <ILI9341_due_config.h>    //библиотека LCD ILI9341_due ( marekburiak-ILI9341_due )
#include <ILI9341_due.h>
#include <DueTimer.h>

#include "Tahoma_20.h"            // шрифт  14x20
#include "Courier_20.h"           // шрифт  14x20
#include "Courier_22.h"           // шрифт  14x20
#include "Courier_26.h"           // шрифт  14x26

#define TFT_CS 10                 //SPI config
#define TFT_DC 9                  //SPI config
#define TFT_RST 8                 //SPI config

byte       INA226_ADDR  = B0000000; // переменная для адресации нескольких ina_226
const byte INA226_ADDR1 = B1000000; // 40h  64d Канал1
const byte INA226_ADDR2 = B1000001; // 41h  65d Канал2 
const byte INA226_ADDR3 = B1000010; // 42h  66d Канал3

const byte INA226_CONFIG  = 0x00;   // наименование регистров ina 226
const byte INA226_SHUNTV  = 0x01;
const byte INA226_BUSV    = 0x02;
const byte INA226_POWER   = 0x03;
const byte INA226_CURRENT = 0x04;
const byte INA226_CALIB   = 0x05;
const byte INA226_MASK    = 0x06;
const byte INA226_ALERTL  = 0x07;
const byte INA226_DIE_ID  = 0xff;

float Shunt  = 0;        
float Shunt1 = 25.35940;  // mOhm Канал1
float Shunt2 = 2.02046;   // mOhm Канал2
float Shunt3 = 0.74856;   // mOhm Канал3

//              140uS      204uS      332uS      588uS      1100uS     2116uS     4156uS     8244uS
// AVERAGES 1   4007       404F       4097       40DF       4127       416F       41B7       41FF
// AVERAGES 4   4207       424F       4297       42DF       4327       436F       43B7       43FF 
// AVERAGES 16  4407       444F       4497       44DF       4527       456F       45B7       45FF
// AVERAGES 64  4607       464F       4697       46DF       4727       476F       47B7       47FF

unsigned long allTimeSec = 0;               // для расчета полного времени работы  грубо  

unsigned long lastFixAhTime  = millis();    // для расчета накопительной Ah по каждому каналу
unsigned long lastFixAhTime1 = millis();    
unsigned long lastFixAhTime2 = millis();
unsigned long lastFixAhTime3 = millis();

byte  needClear=1;                          //  для организации смены показаний 1-2 и 2-3 абсолют/проценты

float powerAh;                              // для расчета накопительной Ah по каждому каналу
float powerAh1;
float powerAh2;
float powerAh3;


//Описание массивов data и strdata с числоывми данными
//1=U1,2=U2,3=U3,4=I1,5=I2,6=I3,7=P1,8=P2,9=P3,10=Ah1,11=Ah2,12=Ah3,13=dU12,14=dU23,15=dI12,16=dI23,17=dP12,18=dP23,19=dAh12,20=dAh23
#define Myarray_size 21            //назначаем кол-во элементов 20+1 в массивах data[] с числоывми данными float и дублир. строками strdata[]

//Описание массивов datapr и strdatapr с процентными данными
//1=dU12,2=dU23,3=dI12,4=dI23,5=dP12,6=dP23,7=dAh12,8=dAh23
#define Myarraypr_size 9           //назначаем кол-во элементов 8+1 в массивах datapr[] с процентными данными int и дублир. строками strdatapr[]

//Описание массивов data и strdata с числоывми данными 
//1=U1,2=U2,3=U3,4=I1,5=I2,6=I3,7=P1,8=P2,9=P3,10=Ah1,11=Ah2,12=Ah3,13=dU12,14=dU23,15=dI12,16=dI23,17=dP12,18=dP23,19=dAh12,20=dAh23;
float data[Myarray_size];               //массив всех числовых данных 21шт. в формате float
char  strdata[Myarray_size][9];         //массив этих же данных в формате string 21шт. по 9 символов (формат 1234.6 + 2пробела +1);

//Описание массивов datapr и strdatapr с процентными данными
//1=dU12,2=dU23,3=dI12,4=dI23,5=dP12,6=dP23,7=dAh12,8=dAh23; 
int  datapr[Myarraypr_size];           //массив данных 9шт. в формате int  для процентных величин для dU12 и т.д.
char strdatapr[Myarraypr_size][6];     //массив этих же данных 9шт. по 6 символов в формате string для процентных величин для dU12 и т.д. (формат 123_5 +1)

int str1  =45;   // Y для строки канала №1       !!! данные для LCD 320х240 для построения изображения на LCD !!!
int str12 =70;   // Y для строки канала 1/2 с процентами
int str2  =95;   // Y для строки канала №2
int str23 =120;  // Y для строки канала 2/3 с процентами
int str3  =145;  // Y для строки канала №3

int slb1 =20;    // X столбца №1 V
int slb2 =95;    // X столбца №2 A
int slb3 =175;   // X столбца №3 W
int slb4 =250;   // X столбца №4 Ah


#include <avr/dtostrf.h>

ILI9341_due tft = ILI9341_due(TFT_CS, TFT_DC, TFT_RST);

//----------------------------------------------------------------------------------
void setup()
{
  Timer3.attachInterrupt(everyOnesec).start(1000000);
	Serial.begin(9600);
  Wire.begin();
  
  INA226_ADDR = INA226_ADDR1;
  INA226_write(INA226_CONFIG, 0x41FF);   // выбрать в таблице шестнадцитиричное значение ,идем процедуру запись конфига в ina 226
  //INA226_write(INA226_CALIB,  2019);     // 3.2А=2048 можно не калибровать если не работаем с регистрами тока и мощности 

  INA226_ADDR = INA226_ADDR2;
  INA226_write(INA226_CONFIG, 0x41FF);   // выбрать в таблице шестнадцитиричное значение ,идем процедуру запись конфига в ina 226
  //INA226_write(INA226_CALIB,  3620);     // 20А=3567  можно не калибровать если не работаем с регистрами тока и мощности

  INA226_ADDR = INA226_ADDR3;
  INA226_write(INA226_CONFIG, 0x41FF);   // выбрать в таблице шестнадцитиричное значение ,идем процедуру запись конфига в ina 226
  //INA226_write(INA226_CALIB,  1710);     // 100А=1706 можно не калибровать если не работаем с регистрами тока и мощности

    
  tft.begin();                    // начинаем работу tft
  tft.setRotation(iliRotation270);// поворот  tft на 270 градусов
  tft.fillScreen(ILI9341_BLACK);  // залить tft черным

  delay (200);                    // без паузы может не стартануть экран
  tft.setFont(Courier_26);        // печать версии прошивки (скетча) на старте 
  tft.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);//желтым по черному
  tft.printAt(Ver,100,100);       // сначала X (0-320) затем Y(0-240)
  delay (3000);                   // показали версию прошивки 3 сек
  tft.fillScreen(ILI9341_BLACK);  // затерли экран в черный

 

  //tft.setFont(Courier_26);        // печать даты часов  пока нет для этого подпрограммы
  //tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);// белым по черному
  //tft.printAt(" 19.06.2016     16:13:54",0,0);// типа число / время /

  
  //tft.setFont(Tahoma_20);       // печать названий столбцов на tft
  tft.setFont(Courier_26);
  tft.setTextColor(ILI9341_GREEN, ILI9341_BLACK);// зеленым по черному
  tft.printAt("                     V                            A                             W                         Ah",0,23);


  tft.setFont(Courier_22);        // печать номеров каналов 1,2,3
  tft.setTextColor(ILI9341_GREEN, ILI9341_BLACK);//BLACK WHITE RED YELLOW GREEN BLUE DARKBLUE MAGENTA NAVY
  tft.printAt("1",0,str1+2);
  tft.printAt("2",0,str2+2);
  tft.printAt("3",0,str3+2);
 
}


//----------------------------------------------------------------------------------------------------
void loop()
{
  //long startmillis=millis();  
  //for(int i=1; i<100; i++) {

  //allTimeSec = (millis()/1000);     // счет секунд работы устройства в версии с таймером в процедуре everyOnesec
  INA226_getData();                   // идем на подпрограмму получить данные со всех каналов 
  floattostring();                    // идем на перегон всех float data[]  в string strdata[]
  inttostring();                      // идем на расчет процентных int datapr[] , перегон всех процентных int datapr[] в процентный  string strdatapr[]
  tolcd();                            // идем на подпрограмму вывода данных на lcd
   
  
  
  //}
  //float time =((millis()-startmillis));
  //Serial.print("time for 1 = ");
  //Serial.print(time/1000/100);
  //Serial.println("   sec");
  
}

//-----------------------------------------------------------------------------------------------
void everyOnesec(){
  allTimeSec += 1;                        // счет секунд работы устройства в версс милисекунды по millis
}
//------------------------------------------------------------------------------------------------
void INA226_getData(){                    // подпрограмма получения  данных со всех каналов
  
  INA226_ADDR = INA226_ADDR1;
  Shunt  = Shunt1;
  lastFixAhTime = lastFixAhTime1;
  INA226_readData();                        // идем на подпрограмму чтения регистров канала 1 
  lastFixAhTime1 = millis();                // после чтения регистров запоминаем когда были сняты данные
  powerAh1+= powerAh;                       // Ah за последнее измерение прибавим к сумме Ah
  data[10]=powerAh1;                        // Ah1 3.2a
  //Serial.print(powerAh1,5);
  //Serial.println(" ");
  
  INA226_ADDR = INA226_ADDR2;
  Shunt  = Shunt2;
  lastFixAhTime = lastFixAhTime2;
  INA226_readData();
  lastFixAhTime2 = millis();
  powerAh2+= powerAh;                       // Ah за последнее измерение прибавим к сумме Ah
  data[11]=powerAh2;                        // Ah1 20.0a
  //Serial.print(powerAh2,5);
  //Serial.println(" ");
  
  INA226_ADDR = INA226_ADDR3;
  Shunt  = Shunt3;
  lastFixAhTime = lastFixAhTime3;
  INA226_readData();
  lastFixAhTime3 = millis();
  powerAh3+= powerAh;                       // Ah за последнее измерение прибавим к сумме Ah
  data[12]=powerAh3;                        // Ah1 100a
  //Serial.print(powerAh3,5);
  //Serial.println(" ");
 
  //Serial.println(" ");

 data[13]=data[2]-data[1];        //U1-U2  расчет разницы значенй между каналами если они нужны в абсолютном значении
 data[14]=data[3]-data[2];        //U2-U3

 data[15]=data[5]-data[4];        //I1-I2
 data[16]=data[6]-data[5];        //I2-I3

 data[17]=data[8]-data[7];        //P1-P2
 data[18]=data[9]-data[8];        //P2-P3;

 data[19]=data[11]-data[10];      //Ah1-Ah2
 data[20]=data[12]-data[11];      //Ah2-Ah3;
  
  //delay(1000);  
}

//-----------------------------------------------------------------------------------------
void INA226_readData()                // подпрограмма получения данных по напряжению, падению на шунте,
{                                     // для расчета тока, расчета мощности , расчета Ah
                                      // !!! не забыть про вариант расчета напряжения и мощности до/после шунта !!!
  
  float sv_, bv_, c_cal, p_cal;       // shunt volt, bus volt, current calcul, power calcul
       
  bv_  = INA226_read(INA226_BUSV);    // идем процедуру чтение напряжения шины
  sv_  = INA226_read(INA226_SHUNTV);  // идем процедуру чтение падения на шунте
    
  bv_ *= 1.25;                          // расчет по регистрам bus voltage   in mV
  sv_ *= 2.5;                           // расчет по регистрам shunt voltage in microV
  c_cal = sv_/Shunt;                    // расчет тока in mA по полученому падению на шунте и известному сопрот. шунта
  p_cal = (bv_-(sv_/1000))* c_cal /1000; // расчет мощности in mW с учетом падения на шунте

    powerAh  = millis() - lastFixAhTime;      // время прошло после последней фиксации Ah
    powerAh *= (sv_/Shunt);                   // ток в мА (sv_/Shunt)
    powerAh /= 1000;                          // ток перевод в А 
    powerAh /= 1000;                          // мСек в сек
    powerAh /= 3600;                          // сек в часы


  if (INA226_ADDR == INA226_ADDR1)  {;
  data[1]=bv_/1000;                         //U1 3.2a
  data[4]=c_cal/1000;                       //I1 3.2a
  data[7]=p_cal/1000;                       //P1 3.2a
  }
  if (INA226_ADDR == INA226_ADDR2)  {;
  data[2]=bv_/1000;                         //U2 20a
  data[5]=c_cal/1000;                       //I2 20a
  data[8]=p_cal/1000;                       //P2 20a
  }
  if (INA226_ADDR == INA226_ADDR3)  {;
  data[3]=bv_/1000;                         //U3 100a
  data[6]=c_cal/1000;                       //I3 100a
  data[9]=p_cal/1000;                       //P3 100a
  }
    
/*Serial.print("Ina_");
  Serial.print(INA226_ADDR);       
  
  Serial.print(" bus_V=");
  Serial.print(bv_/1000,3);               // bus voltage in V по регистрам 
    
  Serial.print(" curr_cal_A=");
  Serial.print(c_cal/1000,3);             // ток в А 
  
  Serial.print(" Load_V=");
  Serial.print((bv_-(sv_/1000))/1000,3);  //  с учетом падения U на шунте, расчет U на нагрузку
  
  Serial.print(" Load_W=");
  Serial.print(  p_cal /1000 ,3  );       // с учетом падения U на шунте, расчет W на нагрузку
  
  Serial.print(" allTimeSec=");
  Serial.print( (millis()- allTime)  );    // сколько времени работает программа всего
  
  Serial.print(" Ah=");                    // заголовок для Ah остальное там откуда вывов подпрограммы
*/    
     
}

//-----------------------------------------------------------------------------------------
void INA226_write(byte reg, unsigned short val) // процедура записи в регистры ina 226
{
  Wire.beginTransmission(INA226_ADDR);
  Wire.write(reg);
  Wire.write(val >> 8);
  Wire.write(val & 0xff);
  Wire.endTransmission();  
}

//-----------------------------------------------------------------------------------------
short INA226_read(byte reg) // процедура чтения из регистров ina 226
{
  short ret = 0;
  // request the registor
  Wire.beginTransmission(INA226_ADDR);
  Wire.write(reg);
  Wire.endTransmission();  

  // read
  Wire.requestFrom((int)INA226_ADDR, 2);
  
  while(Wire.available()) {
    ret = (ret << 8) | Wire.read();
  }
  
  return ret;
}

//---------------------------------------------------------------------------------------------------
void floattostring()                 // подпрограмма перегон всех числовых данных float data[]  в string strdata[]
{
  for (int x=1;x<Myarray_size;x++) { // цикл по всему массиву data[]  
  char buffer[10];                   // резервируем буфер в 10 символов  для перегона флоат в стринг 
  String stringVal = "";             // данные из буфера будут копироваться в стринг stringVal 
  dtostrf(data[x], 1, 4, buffer);    // функция перегона числа data[x] в буфер символов buffer (1 -минимальная длина, 4-округлить знаков после запятой)
     for(int i=0; i < 6; i++ )       // далее цикл длиной от  нуля до 6 (5 цифр и запятая) это длина данных для измерителя мощности 
    {
       stringVal += buffer[i];       // перегон по одному символу из буфера buffer добавляем к стрингу stringVal
    }
  stringVal+="  ";                    // 2пробела в конце для подтирания хвостов от цифр разной ширины
  stringVal.toCharArray(strdata[x],9); // полученый stringVal копируем в элементы массива strdata[] 
  
  //Serial.print("strdata ");Serial.print(x);Serial.print(" : ");Serial.print(strdata[x]);Serial.println("_"); //вывод на экран элементов  strdata[]
  }
}
//---------------------------------------------------------------------------------------------------
void inttostring()                   //подпрограмма расчет процентных int datapr[] и перегон всех процентных данных  int datapr[] в strintg strdatapr[]
{
  //Описание массивов dataper и strdatapr с процентными данными
  //1=dU12,2=dU23,3=dI12,4=dI23,5=dP12,6=dP23,7=dAh12,8=dAh23;

  //Описание массивов data и strdata с числоывми данными 
  //1=U1,2=U2,3=U3,4=I1,5=I2,6=I3,7=P1,8=P2,9=P3,10=Ah1,11=Ah2,12=Ah3,13=dU12,14=dU23,15=dI12,16=dI23,17=dP12,18=dP23,19=dAh12,20=dAh23; 
  
 datapr[1]=abs(data[2]/(data[1]/100));         //U1-U2 1-2
 datapr[2]=abs(data[3]/(data[2]/100));         //U2-U3 2-3
 datapr[3]=abs(data[5]/(data[4]/100));         //I1-I2 4-5
 datapr[4]=abs(data[6]/(data[5]/100));         //I2-I3 5-6
 datapr[5]=abs(data[8]/(data[7]/100));         //P1-P2 7-8
 datapr[6]=abs(data[9]/(data[8]/100));         //P2-P3 8-9
 datapr[7]=abs(data[11]/(data[10]/100));       //Ah1-Ah2 10-11
 datapr[8]=abs(data[12]/(data[11]/100));       //Ah2-Ah3 11-12
 

  for (int x=1;x<Myarraypr_size;x++) {   // цикл по всему массиву dataper[] 
    
  char buffer[10];                       // резервируем буфер в 10 символов  для перегона int в стринг 
  String stringVal = "";                // данные из буфера будут копироваться в стринг stringVal 
  itoa(datapr[x],buffer,10);             // функция перегона числа int datapr[x] в буфер символов buffer 
   for(int i=0; i < strlen(buffer); i++ )// далее цикл длиной от  нуля до длина буффера (кол-во цифр в буффере)  
    {
       stringVal += buffer[i];           // перегон по одному символу из буфера buffer добавляем к стрингу stringVal
    }
  
  //if (datapr[x] < 100) stringVal +="  ";  // если проценты < 100 то добавим пробел
  //if (datapr[x] < 10)  stringVal +="  ";  // если проценты < 10  то добавим пробел
  if (datapr[x] > 999 || datapr[x] < -999 ) stringVal="999";  // если проценты > 999 то нам не нужно показывать это
  stringVal+=" %";                      // добавляю отображение знака процентов
  stringVal.toCharArray(strdatapr[x],6);  // полученый stringVal копируем в элементы массива strdatapr[]
   
  
  //Serial.print("strdatapr ");Serial.print(x);Serial.print(" : ");Serial.print(strdatapr[x]);Serial.println("_"); //вывод на экран элементов strdatapr[]
  
  }
}
//--------------------------------------------------------------------------------------------------
void tolcd() {                                       // подпрограмма вывода данных на tft
  

  byte timer_sec = (allTimeSec)%60;                  // вывод таймера
  byte timer_min = (allTimeSec/60)%60;
  byte timer_hour  = (allTimeSec/3600)%24;
  String timerSTR = " ";
  if (timer_hour < 10) timerSTR +="0";
  timerSTR += String(timer_hour,DEC);
  timerSTR +=":";
  if (timer_min < 10) timerSTR +="0";
  timerSTR += String(timer_min,DEC);
  timerSTR +=":";
  if (timer_sec < 10) timerSTR +="0";
  timerSTR += String(timer_sec,DEC);
  timerSTR +=" ";
  
  tft.setFont(Courier_26);                        // печать даты часов счетчика пока нет для этого подпрограммы
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK); // белым по черному
  tft.printAt(timerSTR,230,0);                    // вывели таймер работы устройства


  
  tft.setFont(Courier_26);                          //Начали вывод U,I,P,Ah данных на LCD 
  tft.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);  //BLACK WHITE RED YELLOW GREEN BLUE DARKBLUE MAGENTA NAVY
  tft.printAt(strdata[1],slb1,str1);                //1 U, во все стринги нужно добавить по пробелу
  tft.printAt(strdata[4],slb2,str1);                //1 A
  tft.printAt(strdata[7],slb3,str1);                //1 W
  tft.printAt(strdata[10],slb4,str1);               //1 Ah
  
  tft.setFont(Courier_26);
  tft.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);
  tft.printAt(strdata[2],slb1,str2);                //2 U,  во все стринги нужно добавить по пробелу
  tft.printAt(strdata[5],slb2,str2);                //2 A
  tft.printAt(strdata[8],slb3,str2);                //2 W
  tft.printAt(strdata[11],slb4,str2);               //2 Ah
  
  tft.setFont(Courier_26);
  tft.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);
  tft.printAt(strdata[3],slb1,str3);                //3 U, во все стринги нужно добавить по пробелу
  tft.printAt(strdata[6],slb2,str3);                //3 A
  tft.printAt(strdata[9],slb3,str3);                //3 W
  tft.printAt(strdata[12],slb4,str3);               //3 Ah



  
                                                    
  byte whatSec =  (allTimeSec)%10;                 // начинаем вывод разницы между каналами измерения абсолюные/процентные
  if ( whatSec >= 3 && whatSec <= 6){              // между 3 и 6 секундами покажем  проценты
    
    tft.setFont(Courier_22);                             
    tft.setTextColor(ILI9341_BLACK, ILI9341_BLACK);//не красивый способ стереть числовые данные для печати процентов   
    if (needClear == 1){
    tft.printAt("0000000000000000000000000000",slb1,str12+2);
    tft.printAt("0000000000000000000000000000",slb1,str23+2);  
  
    tft.setFont(Courier_22);
  
    tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);     // и сам вывод процентов 
    tft.printAt(strdatapr[1],slb1+15,str12+2);          //1/2 U  +15 и +2 добавлены для выравнивания т.к. шрифт уменьшен
    tft.printAt(strdatapr[3],slb2+15,str12+2);          //1/2 A
    tft.printAt(strdatapr[5],slb3+15,str12+2);          //1/2 W
    tft.printAt(strdatapr[7],slb4+15,str12+2);          //1/2 Ah  
  
    tft.printAt(strdatapr[2],slb1+15,str23+2);          //2/3 U во все стринги нужно добавить по пробелу
    tft.printAt(strdatapr[4],slb2+15,str23+2);          //2/3 A +15 и +2 добавлены для выравнивания т.к. шрифт уменьшен
    tft.printAt(strdatapr[6],slb3+15,str23+2);          //2/3 W
    tft.printAt(strdatapr[8],slb4+15,str23+2);          //2/3 Ah 
  }  
  needClear = 0;
  }
  else {                                               // с 6 по 9 и далее с 0 по 3 сек  покажем цифры 
    tft.setFont(Courier_22);  
                             
    tft.setTextColor(ILI9341_BLUE, ILI9341_BLACK);   
    tft.printAt(strdata[13],slb1+3,str12+2);          //1/2 U  +3 и +2 добавлены для выравнивания т.к. шрифт уменьшен
    tft.printAt(strdata[15],slb2+3,str12+2);          //1/2 A
    tft.printAt(strdata[17],slb3+3,str12+2);          //1/2 W
    tft.printAt(strdata[19],slb4+3,str12+2);          //1/2 Ah

    tft.printAt(strdata[14],slb1+3,str23+2);          //2/3 U во все стринги нужно добавить по пробелу
    tft.printAt(strdata[16],slb2+3,str23+2);          //2/3 A +3 и +2 добавлены для выравнивания т.к. шрифт уменьшен
    tft.printAt(strdata[18],slb3+3,str23+2);          //2/3 W
    tft.printAt(strdata[20],slb4+3,str23+2);          //2/3 Ah
    needClear = 1;
  }


  
  /*  
  tft.setFont(Courier_26);                              // balancer пока нет платы балансира поэтому имитация
  tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
  tft.printAt("        4.200         3.200         1.200        3.888",0,172);
  tft.printAt("        4.200         3.200         1.200        2.777",0,194);
  tft.setFont(Courier_22);
  tft.setTextColor(ILI9341_MAGENTA, ILI9341_BLACK);
  tft.printAt("   min  4.200       max  3.200      bal    0.777 ",0,218);
*/
  
}
//--------------------------------------------------------------------------------------------------

На экран одновременно выводится:

- часы и календарь (еще не реализовано)

- таймер

- напряжение, ток, мощность, ампер/часы всех трёх каналов

- показания разницы всех величин между каналами, с периодическим переключением показаний из абсолютных значений в процентное и обратно

Результаты проверки пока очень радуют.

Далее планы и железо есть, времени мало. По мере реализации напишу.

Короткое видео работы https://www.youtube.com/watch?v=cisTjr0wLas&feature=youtu.be

 

ЮриБас
Offline
Зарегистрирован: 13.01.2012

Да, навороченный получается ваттметр..   Сколько в долларах (с доставкой) обошелся один модуль INA226iso (с ADM3260)?  Модуль конечно очень интересный..  Изолятор имеет  изолированный источник питания 0,15Вт (30мА)..  наверное можно еще прицепить какой нибудь МК типа тиньки к этому модулю, тогда не только мерить, но и управлять зарядом можно было бы - т.е. управлять балансировкой.  (мысли вслух)

mikelari
Offline
Зарегистрирован: 14.05.2015

Ну и задумал не как ваттметр. Уже можно применять для нескольких задач. С балансировкой  идет развитие. Дальше еще интересней и дороже, но пока не будем об этом. Модули на 3.2A http://strawberry-linux.com/catalog/items?code=10226 и на 20А http://strawberry-linux.com/catalog/items?code=11226 примерно по 1000 рублей, модуль  на 100 А http://strawberry-linux.com/catalog/items?code=13226 с шунтом примерно 1700 рублей, комиссия посредника zenmarket http://zenmarket.jp/default.aspx 300 рублей, пересыл по Японии 200 рублей, пересыл с Японии ко мне около 1000 рублей. Итого немного меньше 6000 рублей за три модуля. 

ЮриБас
Offline
Зарегистрирован: 13.01.2012

Спасибо за исходники! Цены в принципе адекватные, если задачи серьезные..  например я вижу применение при проектировании и анализе зарядных устройств, и как анализатор аккумуляторов, особенно в сборке.. правда из 4 аккумуляторов и выше, необходимо соответсвующее число модулей.

Посмотрел видео, хорошая точность..  даже на 100А отклонения не большие, получается до тысячной Ампера .   А какая скорость считывания по каждому каналу получилась? 

mikelari
Offline
Зарегистрирован: 14.05.2015

В Datasheet на инглише подробно описаны особенности конфигурирования. Про скорости считывания есть в коде.При конфигурировании можно выбрать время выборки (140uS 204uS 332uS 588uS 1100uS 2116uS 4156uS 8244uS). Чем дольше тем точнее, и количество выборок для усреднения от 1 до 64. Чем  больше, тем точнее. Я пока выбрал усреднение 1 выборка 8244uS. Можно по каждому каналу брать через 140uS с усреднением 1. С регистрами расчета тока и мощности в самом INA226  я разобрался и все нормально работает, но решил ардуиной посчитаю, и на скорость в целом это влияет мало. 

ЮриБас
Offline
Зарегистрирован: 13.01.2012

mikelari пишет:
..  Я пока выбрал усреднение 1 выборка 8244uS. 

Ага.. то-то я смотрю по видео, похоже как нет усреднения, с другой стороны цифры не так уж пляшут..  По коду еще не разбирался, не было времени, спасибо за разъяснение.   Значит у Вас получилось где-то около 120 выборок в секунду, при 8244uS.  Мне бы минимум 300 выборок..  это  чтобы можно смотреть модулированный сигнал заряда с несущей частотой до 40Гц.  

А если взять к примеру  140uS  и с усреднением = 64, точность на сколько будет хуже?

mikelari
Offline
Зарегистрирован: 14.05.2015

Я разницы между 140uS и 8244uS при усреднении 1 не заметил в своем применении. Выглядит одинаково, только еще быстрее работает при 140uS. Нужно в даташите посмотреть, не помню точно, кажется если  взять 140uS и усреднение 64, то время до получения результата будет 8960uS (простое умножение). Точность при этом не думаю что будет плохая. Интересен фильтр при аналоговых измерениях типа: делаем n измерений, сортируем их в порядке возростания, берем из них центральных j значений, и по ним считаем среднее. Например 100 измерений залил в массив, отсортировал, взял с 45 до 55 номера и посчитал по ним среднее. Я пробовал довольно интересно. Но в случае с INA226  при конфигурировании можно пробовать добиться хороших измерений без проведения дополнительных расчетов.

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

ЮриБас, АЦП сигма-дельта физически очень медленные.  В частности INA226 даёт 15- битное разрешение только с конвершн тайм 8.2 мс на семпл. Уменьшение времени в два раза отнимает один бит. Соответссно 140µS -это уже 8-битное разрешение. Короче не предназначены они для скоростных измерений в принципе. 

Кстати на видео цифры пляшут капец как сильно, в последних разрядах просто мусор выводится.

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

mikelari пишет:

 Интересен фильтр при аналоговых измерениях типа: делаем n измерений, сортируем их в порядке возростания, берем из них центральных j значений, и по ним считаем среднее. Например 100 измерений залил в массив, отсортировал, взял с 45 до 55 номера и посчитал по ним среднее. Я пробовал довольно интересно. Но в случае с INA226  при конфигурировании можно пробовать добиться хороших измерений без проведения дополнительных расчетов.

Разработчики INA226 специально сделали аппаратное усреднение, зачем приделывать костыль?

mikelari
Offline
Зарегистрирован: 14.05.2015

Кстати, на видео импульсный блок питания и электронная нагрузка. Ток маленький конечно, но... Нужно записать то же , только сливать с акуумулятора на активную нагрузку типа лампы и посмотреть как это будет.Через пару дней сделаю.

ЮриБас
Offline
Зарегистрирован: 13.01.2012
Что-то меня сбивает с толку такие фразы из даташита INA226 :
                                   7.4 Device Functional Modes - Это раздел в даташите
The internal ADC is based on a delta-sigma (ΔΣ) front-end with a 500 kHz (±30%) typical sampling rate.
Внутренний АЦП базируется на дельта-сигма (ΔΣ) фронт-конец с 500 кГц (± 30%) типичной частоты дискретизации. 
На сколько понимаю, говорится о частоте дискридитации т.е. 500кГц.. ? 
 
Потом, частота дискридетации, написано, влияет на шумы.. и там картинка
 
Но нигде не говорится, что время выборки влияет на разрешение в битах
 
Еще вот такая табличка в даташите:
tCT  ADC conversion           time
CT bit = 000            140 - 154μs
CT bit = 001            204 - 224μs
CT bit = 010            332 - 365μs
CT bit = 011            588 - 646μs
CT bit = 100            1.1 - 1.21 ms
CT bit = 101            2.116 - 2.328 ms
CT bit = 110            4.156 - 4.572 ms
CT bit = 111            8.244 -9.068 ms

"CT" - это что,  а "bit = 000" адрес?   Или как-то на разрешение в битах говорит?
 
Т.е. в моем понимании, у INA226 16 бит точность во всем временном диапозоне выборок.. а время выборки влияет только на шумы..  поэтому, чем меньше частота выборок, тем меньше шумов ну и как бы больше точность (но не битность)
 
Если что не то понял, ткните носом. 
 
 
dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

ЮриБас, 500кГц -частота модулирования, понятие из внутренней кухни дельта-сигма,конечным пользователям почти не о чём не говорящее. То, что время выборки влияет на разрешение -факт из принципа работы дельта-сигма. Для более старшего собрата -ina219 время/разрядность в табличке документировалось, но тут видимо решили не пугать пользователей. CT-биты -конвершн тайм биты. Соответссно по три бита на каждый конфиг. Да, по вашей последней фразе - всё неправильно поняли.  Я ведь вам уже написал в #7  Конвершн тайм прямо влияет на разрядность.  И кстати 16 -бит это дифф резолюшн, т.е. в обоих полярностях. А так оно 15 бит.

ЮриБас
Offline
Зарегистрирован: 13.01.2012

Спасибо  dimax   Да, с ina219 и с ADS1115 все понятно в даташите..  и да, действительно, увеличение скорости выборки в два раза уменьшает разрешение на один бит.

Таблица  INA219 

 

Странно, что у  INA226 нет подобной таблицы..  Значит получается, что 16 бит в диф режиме может выдать на скорости выборки 8.244 -9.068 ms.  т.е. 110 выборок в секунду ?  Или может 16 бит выдает на 140мксек?  Как определить?

Вот например у ADS1115 ясно написано, что 16 бит при 860 SPS (выборок в сек).. 

С другой стороны, логично предположить, что 860 SPS  будет на минимальном разрешении т.е. 8 бит ..  Такое впечатление, что действительно, специально запутывают.. рекламный трюк.
 

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

ЮриБас пишет:

 

Странно, что у  INA226 нет подобной таблицы..  Значит получается, что 16 бит в диф режиме может выдать на скорости выборки 8.244 -9.068 ms.  т.е. 110 выборок в секунду ?  Или может 16 бит выдает на 140мксек?  Как определить?

То ли  вы читаете через строчку, то ли прочитанное тот час забываете.  Перечитайте ещё раз сообщение #7

ЮриБас
Offline
Зарегистрирован: 13.01.2012

dimax пишет:
  Перечитайте ещё раз сообщение #7 
 

Мне просто было интересно, откуда Вы взяли эти цифры, т.е. каким образом вычитали из даташита. 
Но вроде дошло.. хотя получается, что ошибался, наивно доверяясь таблицам, которые приводил выше.. особенно на счет ADS1115.  А надо брать из даташита максимальную выборку и применять к минимальной битности..  которую еще надо выискивать, это как в рекламе, важное - мелкими буквами.

mikelari
Offline
Зарегистрирован: 14.05.2015

При измерениях с аккумулятора на активную нагрузку всё примерно так же. Третий разряд (милливольты и миллиамперы) нормально, а четвертый разряд бегает как мусор. Но это меня усстраивает. В первом канале дискретность должна быть 0.1мА, там еще посмотрим что можно сделать.

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

mikelari, что касается измерения напряжения -то ни при каких условиях там нет 4-го разряда. Девиация при идеальном источнике напряжения и максимальным конвершн тайм составляет 1LSB, то есть 1,25мв.  Сответссно нет смысла выводить информацию с разрешением менее 2,5 мв -там будет мусор.

mikelari
Offline
Зарегистрирован: 14.05.2015

ЮриБас пишет:

dimax пишет:
  Перечитайте ещё раз сообщение #7 
 

Мне просто было интересно, откуда Вы взяли эти цифры, т.е. каким образом вычитали из даташита. 
Но вроде дошло.. хотя получается, что ошибался, наивно доверяясь таблицам, которые приводил выше.. особенно на счет ADS1115.  А надо брать из даташита максимальную выборку и применять к минимальной битности..  которую еще надо выискивать, это как в рекламе, важное - мелкими буквами.

Ну да, есть какая то муть с ina226...

Предисловие:

В datasheet ads1115 сказано: 16bit , MAXIMUM 860 SPS. По дефолту настроено 128 SPS . Логично с фабрики иметь по дефолту  конфиг с АЦП разрешением 16bit. И тут получается время конвертации примерно 8mS.

В datasheet ina219 сказано: Min Conversion Time 84uS при 9bit что (считаем) равно примерно 12000 SPS. По дефолту настроено Conversion Time  532uS при 12bit что (считаем) равно примерно 1880 SPS . Тоже логично что с фабрики идет по дефолту на 12 bit т.е. максимум его АЦП.

В datasheet ina226 сказано: Min Conversion Time 140uS при ??? bit что равно примерно 7140 SPS. По дефолту настроено Conversion Time  1.1mS при ??? bit что равно примерно 900 SPS .

Вот  далее муть...

Какой смысл согласно datasheet ina226 иметь по дефолту настройку АЦП 12 bit если у него есть 16 bit т.е. менее его возможностей по разрядности? Так было бы вернее, если  указаное по дефолту CONVERSION TIME = 1.1mS  должно соответствовоть  16bit ( или там 15 без диффа) и это примерно 900 SPS ?

Мысли вслух...

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

mikelari, 12 бит уже достаточно для большинства задач, + часто имеется приоритет в область скорости. Например с максимальным разрешением режим усреднения становится слишким долгим, (до 8 секунд на 15 битах и 1024 семплах). А на 12 битах те же 1024 семпла отсчитает за 1 секунду. А режим усреднения -штука наиполезнейшая, например я с помощью ina219 измерял средний ток у мощного светодиода, работающего от шим-драйвера. Контрольное измерение tru-rms мультиметром показало ту же цифру что и усреднённый ток с ina. 

mikelari
Offline
Зарегистрирован: 14.05.2015

Продолжение. Добавлен термоскан MLX90614. И… плата. Преобразователь уровней логики, далее I2C мультиплексор TCA9548A, далее восемь гальванически изолированных I2C Isolator на AMD3260  http://strawberry-linux.com/catalog/items?code=23260 , далее восемь ADC1115. Проще говоря восемь изолированных диф-каналов измерения 5 вольт. Проверил на измерении с балансных разъемов батарей. Работает хорошо. Далее железо обдумывается. Пилим софт. А тут работы края не видно. Нужно запустить лог на SD, вывод в LogView,  продумать тач-меню с режимами: измерение мощности, измерение емкости батареи, измерения кпд преобразователей DC-DC, измерения внутреннего сопротивления банок и т.д. и т.п.. Короче, это надолго.

mikelari
Offline
Зарегистрирован: 14.05.2015

Софт пилим, функционал добавляется. Плохо читал про DUE в интернете, наступил на грабли. Подал 12В на разъем питания, и из за ошибки в разводе/схемотехнике DUE  эти 12 вольт попали всюду на шину 5В. К счастью, все сделано на гальваноразвязке от ADM 3260, и перенапряжение не попало на датчики. Умер только I2C мультиплексор. Железо о котором писал выше наконец то собрано в кучу и работает довольно хорошо. Завораживает смотреть на кучу бегающих цветных циферек, которые к тому же не бесполезны. Снял небольшое видео  https://youtu.be/yca17xm28f0 с добавленным функционалом, только на ваттетр и на балансное измерение подключены разные источники, поэтому напряжения не совпадают. Ну это сейчас и не важно, видно что и как работает. Работа продолжается.

mikelari
Offline
Зарегистрирован: 14.05.2015

Пока сделано всё, что запланировано. Выглядит так:

А также был сделан еще один ваттметр с автономным питанием на двух 18650, с 100А шунтом.

 

bwn
Offline
Зарегистрирован: 25.08.2014

Симпатичненько, вот только входы-выходы для канала №3 смутили.)))

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

Подпишусь на тему