Отстают millis() и micros() при совместном использовании I2C OLED дисплея и INA226

BigAleksey
Offline
Зарегистрирован: 16.08.2014

Пытаюсь сделать измеритель Ёмкости и мощности заряда/разряда АКБ и столкнулся с такой проблеммой:

При совметном использовании nano и I2C OLED дисплея и INA226 отставать millis() и micros()

За минуту счетчики время отстают ~7 секунд

#include <Wire.h>
#include <INA226.h>
#include <OLED_I2C.h>
INA226 ina;


OLED  myOLED(SDA, SCL, 8);

//extern uint8_t SmallFont[];
extern uint8_t MediumNumbers[];
extern uint8_t BigNumbers[];
float Amps_per_bit = 0.0166;
//float Amps_per_bit=0.01526;
uint16_t Amps_ADC_Zerro_Offset = 2;
uint16_t i = 0;
unsigned long Time_from_old = 0;
unsigned long ADC_MS = 0;
unsigned long kADC_MS = 0;
unsigned long Time_Count_priv = 0;
unsigned long Time_Display_priv = 0;
unsigned long Time = 0;
uint16_t Time_display = 0;





void loop()
{
  Time = micros();
  Time_from_old = Time - Time_Count_priv;
  if (Time_from_old >= 100000)
  { ADC_MS = ADC_MS + ((ina.readShuntADC() + Amps_ADC_Zerro_Offset) * Time_from_old);
    Time_Count_priv = Time;

  }
  if (ADC_MS >= 1000000)
  {
    kADC_MS++;
    ADC_MS = ADC_MS - 1000000;
  }
  Time_from_old = Time - Time_Display_priv;
  if (Time_from_old >= 1000000)
  {
    myOLED.printNumF(float (ina.readBusVoltage()), 3, LEFT, 0);
    myOLED.printNumF(float ((ina.readShuntADC() + Amps_ADC_Zerro_Offset)*Amps_per_bit), 2, LEFT, 20);
    myOLED.printNumF(float (micros()/1000 ), 0, LEFT, 40);
    myOLED.update();
    Time_Display_priv = Time;
  }

}




void setup()
{
  myOLED.begin();
  ina.begin();
  ina.configure(INA226_AVERAGES_64, INA226_BUS_CONV_TIME_1100US, INA226_SHUNT_CONV_TIME_8244US, INA226_MODE_SHUNT_BUS_CONT);
  ina.calibrate(0.015, 5);
  myOLED.setFont(MediumNumbers);
}

Сначала в голову пришла мысль что кварц на ардуинке шалит, но при запуске примера счетчики за 10 минут не разбегаются.

unsigned long time;
 
void setup(){
  Serial.begin(9600);
}
void loop(){
  Serial.print("Time: ");
  time = micros();
  //выводит количество микросекунд с момента начала выполнения программы
  Serial.println(time);
  // ждет секунду, перед следующей итерацией цикла.
  delay(1000);
}

 

Logik
Offline
Зарегистрирован: 05.08.2014

Наиболее вероятная причина - длительные запреты прерывания в библиотеках. Или же в них  вмешательство в работу таймера.

BigAleksey
Offline
Зарегистрирован: 16.08.2014

Скорее всего косяк сидит в IC2 которой пользуются обе библиотеки. Таймеры начинают врать при отдельном использовании и дисплея и INA226. Вопрос как избавится от этого косяка....

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

BigAleksey пишет:

Вопрос как избавится от этого косяка....

Посмотреть что не так с библиотеками и либо поправить их, либо (скорее всего это будет проще) вовсе отказаться от их использования.

BigAleksey
Offline
Зарегистрирован: 16.08.2014

ЕвгенийП пишет:

либо (скорее всего это будет проще) вовсе отказаться от их использования.

Только краеугольный камень проекта это читать ток/напряжение по I2C c ina226 и выводить на I2C дисплей. И при этом считать Aч и Втч (для чего и нужно точное время). 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

BigAleksey пишет:

ЕвгенийП пишет:

либо (скорее всего это будет проще) вовсе отказаться от их использования.

Только краеугольный камень проекта это читать ток/напряжение по I2C c ina226 и выводить на I2C дисплей. И при этом считать Aч и Втч (для чего и нужно точное время). 

И что? А причём тут кривые библиотеки? Всё это можно сделать руками, причём так. чтобы одно другому не мешало. Чаще всего именно так и приходится делать потому, что эти библиотеки писались без малейшеё мысли о совместимости друг с другом.

BigAleksey
Offline
Зарегистрирован: 16.08.2014

ЕвгенийП пишет:

И что? А причём тут кривые библиотеки? Всё это можно сделать руками, причём так. чтобы одно другому не мешало. Чаще всего именно так и приходится делать потому, что эти библиотеки писались без малейшеё мысли о совместимости друг с другом.

Если не будет простого решения, то прийдётся все руками писать. Но тогда пожалуй проще уйти на STM32, что-бы прокачивать скилы на более быстром камне с более вкусным набором переферии.

BigAleksey
Offline
Зарегистрирован: 16.08.2014

P.S. есть у кого проекты с плотной работой по I2C ? Просьба посмотреть там millis() и micros() корректно считают время?

P.P.S. посмотрел библиотеки INA226 и дисплея, запрета прерываний в них не нашел, общаются с I2C через  функции TWxx ...

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

BigAleksey, I2C не имеет к этой проблеме никакого отношения. -Ваш принцип счёта времени идеологически неверный. Загляните в эту ветку, читать можно  с #67 поста. Всё поймёте.

BigAleksey
Offline
Зарегистрирован: 16.08.2014

dimax пишет:

BigAleksey, I2C не имеет к этой проблеме никакого отношения. -Ваш принцип счёта времени идеологически неверный. Загляните в эту ветку, читать можно  с #67 поста. Всё поймёте.

Почитал ветку и позволю не согласится:

У меня в тестовом прогоне идёт прямое отображение micros();, без накопительных ошибок на промежуточной переменной

что в первом 

47     myOLED.printNumF(float (micros()/1000 ), 0, LEFT, 40);

 

 

то во втором случае

 
time = micros();
09   //выводит количество микросекунд с момента начала выполнения программы
10   Serial.println(time);

При этом в первом случае micros() в течении минуты отстаёт на ~7 секунд (если отключаю опрос INA226 то отставание сокращается примерно до 5 сек), а во втором случае за 10 минут идут +- 0,5 сек (измерял время секундомером на телефоне, и такой точности мне вполне будет достаточно)

 

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

BigAleksey, с чем не согласится?  Какой ещё первый случай, какой второй? Мораль той темы - что нужно интервалы аппаратным таймером делать через прерывание, а не сравнивать миллисы и прочую чушь. И пример там готовый.

BigAleksey
Offline
Зарегистрирован: 16.08.2014

dimax пишет:

BigAleksey, с чем не согласится?  Какой ещё первый случай, какой второй? Мораль той темы - что нужно интервалы аппаратным таймером делать через прерывание, а не сравнивать миллисы и прочую чушь. И пример там готовый.

Частично решил проблемму вашим куском кода:

 

TCNT1=0; TCNT0=0;  TCCR1A=0;
TCCR1B=(1<<CS12)|(1<<WGM12); //CTC Mode, DIV256
OCR1A=6249; //10 раз в секунду
TIMSK1=(1<<OCIE1A); // разрешить прерывание
}

ISR (TIMER1_COMPA_vect) { 
  tim1++;
  }


на 10 минутах сходится время, но есть нюанс -до 0,1 сек временного разрешения все хорошо, при попытке сделать разрешение 0,01с

OCR1A=625; //100 раз в секунду

время начинает отставать в 3-4 раза.

P.S. что-бы не было сюрпризом, использование этого таймера ломает какие-либо стандартные функции (PWM) ?

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

BigAleksey, желательно переключить прескалер на поменьше:

TCCR1B=(1<<CS11)|(1<<WGM12); //CTC Mode, DIV8
OCR1A=19999; //10 ms

 

BigAleksey
Offline
Зарегистрирован: 16.08.2014

dimax пишет:

BigAleksey, желательно переключить прескалер на поменьше:

TCCR1B=(1<<CS11)|(1<<WGM12); //CTC Mode, DIV8
OCR1A=19999; //10 ms

 

Так не пошло, время ускорилось в разы.

Я так понимаю можно оставлять и со старым прескалером, только шаг коррекции хода часов за счет OCR1A -+ будет более грубым (полученная точность хода меня пока вполне устраивает).

 

P.S. без заглядывания в даташиты ваш код напоминает BrainFuck ;-)

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

BigAleksey, что именно не пошло? Тут чистая математика. Вы хотели раз в 10mS - вот расчёт:    Div * 0.0625*(OCR1A+1) =  (uS) И вообще непонятно, зачем при измерении заряда/разряда может потребоваться таймер чаще раза в секунду.

BigAleksey
Offline
Зарегистрирован: 16.08.2014

dimax пишет:

BigAleksey, что именно не пошло? Тут чистая математика. 

время ускорилось в ~4,5 раза , точно для DIV8 1<<CS11 а для DIV256 1<<CS12 ?

dimax пишет:

И вообще непонятно, зачем при измерении заряда/разряда может потребоваться таймер чаще раза в секунду.

Тут просто - основа девайса 500А 75mV шунт c подключенной платкой INA226, одной из задумок было измерение потребляемой ёмкости Ач (и Вт*ч)  при запуске авто (тут можно выйти из положения встроенным в INA226 усреднителем), с измерением пикового тока. Изначально хотел интегрировать показания с квантом 0,01 сек; но ардуинка не тянет такое быстродействие, а AVR как-то мне не заходит - изучать С меня тянет на STM32. 

P.S. INA226 вещь, при 500А шунте уже сейчас железка измеряет ток с точностью +- 0,02А

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

BigAleksey пишет:

время ускорилось в ~4,5 раза , точно для DIV8 1<<CS11 а для DIV256 1<<CS12 ?

совершенно точно. даташит, стр 134 Table 16-5.

BigAleksey пишет:

 (тут можно выйти из положения встроенным в INA226 усреднителем), с измерением пикового тока.

Именно так и нужно делать. А данные из чипа снимать 1 раз в секунду.

BigAleksey
Offline
Зарегистрирован: 16.08.2014

dimax пишет:

Именно так и нужно делать. А данные из чипа снимать 1 раз в секунду.

Пиковый максимальный ток (и пиковая мощность) остаётся в таком случае не пойманый, а как я изначально задумывал настроить INA226 на вычисление с усреднением 4 и выдачей импульса на ноге по окончанию раз в ~64ms, с ноги по прерыванию считывать показания  по IC2. Но быстродействия на Wiring хронически не хватает, усредняю пока в  INA226 по 64 семплам.

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

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

BigAleksey
Offline
Зарегистрирован: 16.08.2014

dimax пишет:

BigAleksey, ina226 не предназначен для снятия пиковых токов,  вы будете считывать нечто, не имеющее никакого отношения к пикам.  

На осцилограф он конечно не тянет, но с минимальным временем семплирования 140 us и частотой I2C до 2.94Mhz вполне спокойно может выдавать раз в 140 us актуальные данные (с правильным MCU) . Так что с него можно спокойно снимать >7000 семплов в минуту (или ~1000 при точности 15bit +-2bit).

В случае со стартером с его инерцией несколько (десятков) семплов попадут на неподвижное его состояние.

А если пытатся выловить токовые пики к примеру на DC/DC конверторе - то тогда все будет как вы описали.

P.S. dimax , чуть не забыл - спасибо за подсказку в решении моей проблемы :-)

 

 

Рабочий вариант измеряющий U,I,mAH и время в секундах:

#include <INA226.h>
#include <OLED_I2C.h>
volatile uint32_t tim1;
INA226 ina;
OLED  myOLED(SDA, SCL, 8);

//extern uint8_t SmallFont[];
extern uint8_t MediumNumbers[];
extern uint8_t BigNumbers[];
float Amps_per_bit = 0.0166667;
int8_t tick = 0;
int32_t k = 0;
int16_t ADC_01S_to_MA_H = 2160;
int16_t Amps_ADC_Zerro_Offset = 1;
int16_t i = 0;
int32_t ADC_01S = 0;
int32_t MA_H = 0;
int32_t Time = 0;
int32_t Old_Time = 0;

void loop()
{
  Time = tim1;
  tick = Time - Old_Time;
  if (tick > 0)
  {
    ADC_01S = ADC_01S + ((ina.readShuntADC() + Amps_ADC_Zerro_Offset) * tick);
    Old_Time = Time;
    while ((abs(ADC_01S) - abs(ADC_01S_to_MA_H)) >= 0)
    {
      if (ADC_01S > 0)
      { MA_H++;
        ADC_01S = ADC_01S - ADC_01S_to_MA_H;
      };
      if (ADC_01S < 0)
      { MA_H--;
        ADC_01S = ADC_01S + ADC_01S_to_MA_H;
      };
    }
  }

  myOLED.clrScr();
  myOLED.printNumF(float (ina.readBusVoltage()), 3, LEFT, 0);
  myOLED.printNumF(float ((ina.readShuntADC() + Amps_ADC_Zerro_Offset)*Amps_per_bit), 2, LEFT, 20);
  myOLED.printNumF(float (tim1 / 10), 0, RIGHT, 20);
  myOLED.printNumF(float (MA_H), 0, LEFT, 40);
  myOLED.update();
}

void setup()
{
  myOLED.begin();
  ina.begin();
  ina.configure(INA226_AVERAGES_64, INA226_BUS_CONV_TIME_1100US, INA226_SHUNT_CONV_TIME_8244US, INA226_MODE_SHUNT_BUS_CONT);
  ina.calibrate(0.015, 5);
  myOLED.setFont(MediumNumbers);
  TCNT1 = 0; TCNT0 = 0;  TCCR1A = 0;
  TCCR1B = (1 << CS12) | (1 << WGM12); //CTC Mode, DIV256
  OCR1A = 6249; //10 раз в секунду
  TIMSK1 = (1 << OCIE1A); // разрешить прерывание
}

ISR (TIMER1_COMPA_vect) {
  tim1++;
}

 

только добавил немного в библиотеку

INA226.cpp

int16_t INA226::readShuntADC(void)
{
   int16_t voltage;
//
  voltage = readRegister16(INA226_REG_SHUNTVOLTAGE);
//
return (voltage); 
 //   return (voltage);
}

 

INA226.h

class INA226
{
    public:
.....................................................
	int16_t readShuntADC(void);
....................................................

гонял 5А лабораторником, убежал всего на 2 секунды (на картинке неверно обозвал секунды минутами) и 3 ma*ч

 /sites/default/files/u9165/ina.jpg

 

P.S. экран у меня оказался разбитый,  показывает через строчку что не даёт возможности сделать на экране подписи параметров. Нормально видны лишь большие цифры.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Вот есть несколько таймеров в одном

https://github.com/DetSimen/Arduino-

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

dimax пишет:

BigAleksey, что именно не пошло? Тут чистая математика. Вы хотели раз в 10mS - вот расчёт:    Div * 0.0625*(OCR1A+1) =  (uS) И вообще непонятно, зачем при измерении заряда/разряда может потребоваться таймер чаще раза в секунду.

По стандарту надо измерять раз в минуту, этого достаточно ))) это я ГОСТ вспомнил

А библиотеку какую брали? Эту???

На INA219 код мне как-то понятней был:
 

INA219 monitor;

  monitor.begin(64); // i2c address 64=0x40
 // monitor.configure(0, 2, 10, 10, 7); // 4S -2.13ms
 // monitor.configure(0, 2, 11, 11, 7); // 8S -4.26ms
  monitor.configure(0, 2, 12, 12, 7); // 16S -8.51ms
 // monitor.configure(0, 2, 13, 13, 7); // 32S -17.02ms
 // monitor.configure(0, 2, 14, 14, 7); // 64S -34.05ms
 // monitor.configure(0, 2, 15, 15, 7);  // 128S - 68.10ms
 // monitor.configure(0, 2, 8, 8, 7);
                           // range, gain, bus_adc, shunt_adc, mode
                           // range = 1 (0-32V bus voltage range)
                           // gain = 3 (1/8 gain - 320mV range)
                           // bus adc = 3 (12-bit, single sample, 532uS conversion time)
                           // shunt adc = 3 (12-bit, single sample, 532uS conversion time)
                           // mode = 7 (continuous conversion)
   monitor.calibrate(0.100, 0.32, 16, 3.2); 
                           // R_шунта, напряж_шунта, макcнапряж, максток

 

BigAleksey
Offline
Зарегистрирован: 16.08.2014

ua6em пишет:

По стандарту надо измерять раз в минуту, этого достаточно ))) это я ГОСТ вспомнил

А библиотеку какую брали? Эту???

Когда ГОСТ писали небыло батарей отдающих всю ёмкость за 6 минут.

Библиотака та.