Отстают millis() и micros() при совместном использовании I2C OLED дисплея и INA226
- Войдите на сайт для отправки комментариев
Ср, 21/06/2017 - 08:17
Пытаюсь сделать измеритель Ёмкости и мощности заряда/разряда АКБ и столкнулся с такой проблеммой:
При совметном использовании 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);
}
Наиболее вероятная причина - длительные запреты прерывания в библиотеках. Или же в них вмешательство в работу таймера.
Скорее всего косяк сидит в IC2 которой пользуются обе библиотеки. Таймеры начинают врать при отдельном использовании и дисплея и INA226. Вопрос как избавится от этого косяка....
Вопрос как избавится от этого косяка....
Посмотреть что не так с библиотеками и либо поправить их, либо (скорее всего это будет проще) вовсе отказаться от их использования.
либо (скорее всего это будет проще) вовсе отказаться от их использования.
Только краеугольный камень проекта это читать ток/напряжение по I2C c ina226 и выводить на I2C дисплей. И при этом считать Aч и Втч (для чего и нужно точное время).
либо (скорее всего это будет проще) вовсе отказаться от их использования.
Только краеугольный камень проекта это читать ток/напряжение по I2C c ina226 и выводить на I2C дисплей. И при этом считать Aч и Втч (для чего и нужно точное время).
И что? А причём тут кривые библиотеки? Всё это можно сделать руками, причём так. чтобы одно другому не мешало. Чаще всего именно так и приходится делать потому, что эти библиотеки писались без малейшеё мысли о совместимости друг с другом.
И что? А причём тут кривые библиотеки? Всё это можно сделать руками, причём так. чтобы одно другому не мешало. Чаще всего именно так и приходится делать потому, что эти библиотеки писались без малейшеё мысли о совместимости друг с другом.
Если не будет простого решения, то прийдётся все руками писать. Но тогда пожалуй проще уйти на STM32, что-бы прокачивать скилы на более быстром камне с более вкусным набором переферии.
P.S. есть у кого проекты с плотной работой по I2C ? Просьба посмотреть там millis() и micros() корректно считают время?
P.P.S. посмотрел библиотеки INA226 и дисплея, запрета прерываний в них не нашел, общаются с I2C через функции TWxx ...
BigAleksey, I2C не имеет к этой проблеме никакого отношения. -Ваш принцип счёта времени идеологически неверный. Загляните в эту ветку, читать можно с #67 поста. Всё поймёте.
BigAleksey, I2C не имеет к этой проблеме никакого отношения. -Ваш принцип счёта времени идеологически неверный. Загляните в эту ветку, читать можно с #67 поста. Всё поймёте.
Почитал ветку и позволю не согласится:
У меня в тестовом прогоне идёт прямое отображение micros();, без накопительных ошибок на промежуточной переменной
что в первом
47myOLED.printNumF(float(micros()/1000 ), 0, LEFT, 40);то во втором случае
time = micros();09//выводит количество микросекунд с момента начала выполнения программы10Serial.println(time);При этом в первом случае micros() в течении минуты отстаёт на ~7 секунд (если отключаю опрос INA226 то отставание сокращается примерно до 5 сек), а во втором случае за 10 минут идут +- 0,5 сек (измерял время секундомером на телефоне, и такой точности мне вполне будет достаточно)
BigAleksey, с чем не согласится? Какой ещё первый случай, какой второй? Мораль той темы - что нужно интервалы аппаратным таймером делать через прерывание, а не сравнивать миллисы и прочую чушь. И пример там готовый.
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с
время начинает отставать в 3-4 раза.
P.S. что-бы не было сюрпризом, использование этого таймера ломает какие-либо стандартные функции (PWM) ?
BigAleksey, желательно переключить прескалер на поменьше:
BigAleksey, желательно переключить прескалер на поменьше:
Так не пошло, время ускорилось в разы.
Я так понимаю можно оставлять и со старым прескалером, только шаг коррекции хода часов за счет OCR1A -+ будет более грубым (полученная точность хода меня пока вполне устраивает).
P.S. без заглядывания в даташиты ваш код напоминает BrainFuck ;-)
BigAleksey, что именно не пошло? Тут чистая математика. Вы хотели раз в 10mS - вот расчёт: Div * 0.0625*(OCR1A+1) = (uS) И вообще непонятно, зачем при измерении заряда/разряда может потребоваться таймер чаще раза в секунду.
BigAleksey, что именно не пошло? Тут чистая математика.
время ускорилось в ~4,5 раза , точно для DIV8 1<<CS11 а для DIV256 1<<CS12 ?
И вообще непонятно, зачем при измерении заряда/разряда может потребоваться таймер чаще раза в секунду.
Тут просто - основа девайса 500А 75mV шунт c подключенной платкой INA226, одной из задумок было измерение потребляемой ёмкости Ач (и Вт*ч) при запуске авто (тут можно выйти из положения встроенным в INA226 усреднителем), с измерением пикового тока. Изначально хотел интегрировать показания с квантом 0,01 сек; но ардуинка не тянет такое быстродействие, а AVR как-то мне не заходит - изучать С меня тянет на STM32.
P.S. INA226 вещь, при 500А шунте уже сейчас железка измеряет ток с точностью +- 0,02А
время ускорилось в ~4,5 раза , точно для DIV8 1<<CS11 а для DIV256 1<<CS12 ?
совершенно точно. даташит, стр 134 Table 16-5.
(тут можно выйти из положения встроенным в INA226 усреднителем), с измерением пикового тока.
Именно так и нужно делать. А данные из чипа снимать 1 раз в секунду.
Именно так и нужно делать. А данные из чипа снимать 1 раз в секунду.
Пиковый максимальный ток (и пиковая мощность) остаётся в таком случае не пойманый, а как я изначально задумывал настроить INA226 на вычисление с усреднением 4 и выдачей импульса на ноге по окончанию раз в ~64ms, с ноги по прерыванию считывать показания по IC2. Но быстродействия на Wiring хронически не хватает, усредняю пока в INA226 по 64 семплам.
BigAleksey, ina226 не предназначен для снятия пиковых токов, вы будете считывать нечто, не имеющее никакого отношения к пикам. нужен специализированный чип или собирать пиковый детектор, который должен удерживать заряд достаточное время что бы МК гарантированно его считал.
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. экран у меня оказался разбитый, показывает через строчку что не даёт возможности сделать на экране подписи параметров. Нормально видны лишь большие цифры.
Вот есть несколько таймеров в одном
https://github.com/DetSimen/Arduino-
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напряж, макстокПо стандарту надо измерять раз в минуту, этого достаточно ))) это я ГОСТ вспомнил
А библиотеку какую брали? Эту???
Когда ГОСТ писали небыло батарей отдающих всю ёмкость за 6 минут.
Библиотака та.