Поменьше математики процессору, или оптимизация расчётов.

Досталась горстка дохлых ноутбучных батарей, тех самых, что на литиевых габарита 18650 собраны. По этому случаю делаю тут измеритель фактической ёмкости акумуляторов, чтобы хоть как то отбраковать их. Хочется видеть цифры по итогу в удобоваримом формате. А поскольку делаю на ATTiny26 (Не пинать! Ну, завалялся у меня.), то тащить вычисления с точкой накладно. Сел, помозговал — решаемо. Материал под катом может кому-то показаться очевидной истиной, может оказаться кому-то не востребованным, но надеюсь — большинство хоть что-нибудь подчерпнут для себя. Для нетерпеливых итог:
(1)(1)
Для тех, кто не боится формул — ниже.
И так, в каких «попугаях» мы привыкли видеть ёмкость малогабаритных аккумуляторв? Правильно, в мА*часах. Или так, если в единицах Си (чтобы не запутатся в размеренностях):

ёмкость(2)
Чтобы не заморачиваться стабильным током и не досыпать ненужных элементов на плату, можно просто повесить резистор, и измерять напряжение. При известном номинале и напряжении ток раситывается согласно закону Ома:

(3)(3)
Емкость — величина интергальная, например — сумма значений тока, измеренных через относительно малые промежутки времени. Так, если мы намеряли 1А и через 1 час наш элемент «кончился» — с каким-то допущением можно считать, что аккумулятор ёмкостью 1000 мА*ч. А если мы успели померять ещё раз перед самым отключением и показания были 0,9А — то можно посчитать как 1000 мА*ч или 900 мА*ч (метод прямоугольников), или же 950 мА*ч (метод трапеций).
Это я к чему? Сделаем замену в формуле (2), получим:

(4)(4)

Так. Теперь о железе. В даташите, в описании АЦП, есть формула для значений «в отсчётах», переведу сразу в вольты:

(5.1)(5.2)(5)
Оценим точность, с какой имеет смысл задавать значение опорного напряжения (например — откалиброваться, и хранить его в EEPROM). Диапазон по даташиту — 2,4..2,7 в, берём минимальное значение:

(6)(6)
Вот тут-то мы и упёрлись в вычисления с точкой. Как быть? А просто — запятую мы сами нарисуем на индикаторе в нужном месте, а хранить значение можно в «попугаях» или «мартышках». Но надо помнить, что и результат будет умножен на эту же величину. Поскольку 2,7в опорки (максимально по даташиту) даже в сотых долях вольта («270») больше одного байта («255»=«0xFF»), а нам ещё и поточнее бы надо (у нас всё же 1024 отсчёта АЦП), то будем хранить с целью удобочитаемости и приемлемой точности в тысячных, благо «2700» мВ помещается в 16 бит. В общем, как-то так:

(7)(7)
Это уже реализовано на настоящий момент, да и в конечном варианте полезно — задать порог отключения аккумуляторов от нагрузки удобрей будет, нежели в отсчётах или других «попугаях».
Теперь вернёмся к (4), и подставим вместо напряжения в вольтах последнее выражение:

(8)(8)
Чуть упростим:

(9.1)
(9.2)(9)
Одна «1000» входит в хранимое значение опорки Vref (обозначил скобками), а вот вторую можно и нужно сократить:

(10)(10)
Получим такое выражение:

(11)(11)
В формуле ещё видим «ч» — часы, как еденица измерения времени. Думаю, мало кого устроит емкость, если мы ток при разряде контролировали раз в час. А как часто измерять? Можно запустить прерывания АЦП и смотреть, сколько прошло времени от предущего измерения — это даст наиболее точный результат. Но не хочу я загружать на столько контроллер. Как часто тогда? А давайте раз в секунду? Вроде нормально.

Дальше была ошибка, связанная с тем, что посчитал секнду 1/60 часа. Писал ночью, поэтому так напортачил. Спасибо, буду поправлять с учётом коментариев. Ну а пока — выпилено и заменено.

Теперь ещё одна хитрушка. Что такое секунда? 1/3600 часть часа. А можно поменять на степень двойки! Можно же измерять 2048 или 4096 раза в час? Легко — чуть перенастроить таймер. В общем-то можно и 2, 4, 8,… раз за час, но мне понравилась такая величина.

12.112.2(12)

Подставим, преобразуем:

13(13)
Здесь первая дробь — выделил множитель для расчёта напряжения. Можно в основном цикле не считать это всё, а сделать один раз до входа в основной цикл и домножать на показания АЦП.
Оценим возможность переполнения. Предположим, максимальная ёмкость аккумулятора 3500 мА*ч, самый маленький ток, каким он будет разряжаться к концу цикла, будет при напряжении 3,1в. Ну, и время разряда таким током посчитать не сложно. Как и количество сделанных выборок за это время:

14.114.2(14)
Чтобы прикинуть максимальное значение, вычислим верхнюю часть дроби (13):

2700мв * 125 * 73992 сэмпла = 24972300000 = 0x5D0770EE0

Ох, ничего себе! И что — теперь 64 бита переменную заводить? Не-не, мы скукожим вычисления. Как?..
Чтобы не терять отсчёты АЦП, их нужно суммировать всё время. Поясню на аналогии.
Нам наливают в стакан какое-то количество воды, мы должны узнать, сколько воды будет нам налито за всё время. Если округлять значения АЦП, то получится, что нам налили, мы перелили это в нашу мерную тару кратно какой-то своей порции, а что осталось в стакане — вылили. Или же так: на стакане метки через 50 грамм миллилитров, мы записываем каждый раз, сколько налито (показания кратны 50), и выливаем. Потом суммируем. А можно даже без меток стакан, но сливать в одну большую мерную посудину, а потом замерить. Даже если потом перемерять стаканами а не стопками единицами по 50, может оказаться в итоге точнее.
С другой стороны, нам нет необходимости знать ёмкость с точностью до единиц микроампер*час. Поэтому мы подсчитаем множитель, на который будем умножать накопленную сумму АЦП.
Кстати, каким будет максимальное накопленное значение?

73992 * 1023 (максимальное значение АЦП) = 75396816 = 0x482FFEF8

На грани фола. То есть, если мы это число умножим, больше, чем на 3, то получим переполнение.

Но если (13) преобразовать, то получим такое выражение:

(14)(1)
То есть, ещё один множитель, который можно вычислить безболезненно перед основным циклом на основе предыдущего (который был для измерения напряжения). Можно, и даже нужно поделить накопленное значение, чтобы потери точности были минимальны. Если кто подскажет, как оцениваются потери точности в таких случаях — буду рад! «Секунды» будут накапливаться автоматически с каждым добавлением отсчёта АЦП к сумме предыдущих.

PS: Чтобы точность была повыше, следует вначале умножать все числа, а потом делить контролировать переполнение и потери разрядов при делении. Ещё можно резистор нагрузочный поставить 8, 16,… Ом — думаю, ход мыслей понятен. Ещё в коментариях подсказали, что можно соотнести номинал резистора с опорным напряжением! С другой стороны — надо этот резистор подгонять каждый раз… Неточность деталей можно подстройкой RC-осциллятора компенсировать, если тактируемся от него… И так далее.
PPS: По хорошему, человеко-часы сейчас ценятся гораздо выше кремния, но знать такие моменты полезно — а вдруг не будет хватать 1 байта или чуть-чуть быстродействия? Хотя, да — этот материал не для профессионалов, но и не для новичков «Hello, LED!».
  • +6
  • 21 ноября 2013, 20:40
  • Hoksmur

Комментарии (27)

RSS свернуть / развернуть
Что такое секунда? 1/60 часть часа.

Вы секунду и минуту перепутали :) В часе 3600 секунд.
+1
Да, есть такое. Завтра поправлю — сегодня баюшки уже пора.
0
Бывает :) Сам наступал на подобные грабли. Вот зачем выбрали такую размерность для исчисления времени :) Вся система С отталкивается от модуля 1000. А вот с измерением временных отрезков — как-то странно, две величины по модулю 60 и одна по модулю 24. Не говоря о том, что размерность месяца, в календаре, плавает.

Да и с переводом на на зимнее/летнее время не все так просто. Пользователь совершает несколько денежных транзакций, потом, после перевода времени, блокирует свой счет (время блокировки получается раньше, чем время проведения транзакций). И, вот, со всем этим приходится бороться…
0
Под ваши задачи есть хорошее устройство radiokot.ru/circuit/power/charger/41/
Можно сэкономить пару десятков человеко-часов.
0
Э… у меня как бэ на 4 аккума одновременно планируется. Соответсвенно — кнопки для управления. Возможно, я потом как та ворона из анекдота буду — «да, сильная, да выносливая», — а надо ли было? Время покажет.
0
Ещё можно резистор нагрузочный поставить 8, 16,… Ом — думаю, ход мыслей понятен.
сейчас я хочу спать, но что то мне говорит что ход мыслей можно продолжить проведя параллель между «8, 16,...» и опорным напряжением.
+1
  • avatar
  • xar
  • 21 ноября 2013, 22:43
Тоже доставал банки из аккумулятора для ноутбука. В ноутбуке они проработали 2-3 года, но потом ноутбук стал за 15 минут разряжаться. Сейчас этим аккумулятором более 5ти лет — работают не хуже заказанных из Китая самых дешевых 18650, даже лучше.
0
т.е. дело было не в износе аккумуляторных банок, раз они все еще работают?
или Вы намекаете на качество китайских 18650?
0
В аккумуляторе не все банки пришли в негодность, а только пара штук. Да и эти китайские очень быстро заряжаются и быстро садятся.
0
Банки собраны по 2..3 в параллель, и 2..3..4 таких — последовательно. С течением времени из-за разброса параметров один из элементов теряет ёмкость и/или повышается внутреннее сопротивление. Если встроен балансир, то он помогает ровно до тех пор, пока справляется. А потом — встроенный контроллер видит перекос напряжения, говорит «ай-яй-яй!» и не работает.
А остальные то банки живые! Более того — в тех батареях, что мне отдали, только 1 банка в батарее выходила из строя. Остальные, даже при нулевом напряжении при первом замере, после подзарядки током ~200 мА давали положенные 3в, и дальше неплохо заряжались в штатном режиме.
0
а, теперь понятно. спасибо за разъяснение.
0

сек=ч/3600 — глупости.
на самом деле сек=ч*3600, и уже отсюда выйдет следующее равенство.
+3
Чо, самый умный штоле??
-4
Непонятно стремление уменьшить загрузку контроллера вычислениями раз в секунду. Чем он занимается в оставшееся время? Оптимизация по скорости за счет перехода к фиксированной точке дает возможность выполнять что-то еще?
0
Плавающую точку не стоит без FPU или надобности поминать всуе. Называется такой метод расчётов Q-arithmetic: домножаем все аргументы на 10 в степени требуемой точности, потом делим. Я подозреваю, что атмел будет тянуть посторонние либки для флоата, и код разбухнет. Честно говоря, кроме графики можна практически везде обойтись фиксированой точкой на МК, и будет это хорошо.
+1
Оптимизация — это хорошо. Сделана оптимизация по скорости в ущерб точности. Для измерительного прибора это немного странно.
0
Не в ущерб. :)
1) это скорее индикатор, а не измерительный прибор.
2) Если почитаете повнимательней (на стиль указали — учту), то измеренные показания не округляются. Округляется лишь отображаемая на индикаторе часть.
0
Быстрее использовать сдвиг.
0
Если посмотреть разрядные кривые каких-нибудь батарей, то будет понятно, что там туда-сюда трамвайная остановка и от небольшого изменения тока разряда сильно добавить к той погрешности и не получится. Моё мнение — достаточно узнать аж время разряда и не заморачиваться с измерением и интегрированием отданной мощности (учетом совершенной работы). А потом применить fixed-point в лоб. Можно подсмотреть там Tab 3-2 и макросы на стр. 7 (не важно, что не AVR). От оптимизации отказываться не предлагаю:)
ЗЫ В системе Си А·ч автоматом становятся А·с, которые Кл.
0
Надеюсь, что Вы не занимаетесь предподавательской деятельностью. Я на месте Ваших студентов повесился бы. :) (прошу воспринимать это как шутку).

Тема статьи неплохая, а вот ошибки и непоследовательность изложения просто убили желание дочитывать. Пишите еще, но перечитывайте хотя бы пару раз свой материал перед публикацией.
Прошу принять во внимание, что это лишь моё личное мнение, возможно, оно далеко от истины или мнения других читателей.
Желаю успехов!
+1
Хм… интересно конечно. Но у меня перед началом каждого проекта задаётся себе вопрос «Почему мы это делаем? Почему нельзя купить уже готовое? Не дешевле/надёжнее готовое решение?».
Так и здесь почему нельзя использовать клон зарядки Imax6? Я себе такую привёз с диал экстрима доллоров за 45 по моему… (давно было). В городе у нас они по 3К р. так что есть ли смысл тратить столько времени? если готовый зарядник, выдающий теже данные и многое заряжающий можно купить за приемлимые деньги?
0
Почему мы это делаем?
Да простит меня автор, я хотел бы ответить уважаемому коллеге skelet. Смотри тему заметки: это не о зарядке аккумов. Это про вычисления без использования флоат-арифметики. И именно в таком смысле — вполне годная статья. Ведь много, очень много ситуаций, когда вполне можно обойтись целочисленными вычислениями — а народ побаивается, ставит в проге флоаты — и получает гембель с либами. Ну, ладно, не гембель, а просто пухлый проект.
Я всегда рассматриваю возможность обойтись целочисленными типами.
По сути статьи — как-то не хочется придираться. Уважаемый DOOMSDAY слегка погарячился, ИМХО:
непоследовательность изложения просто убили желание дочитывать
Каждый автор вправе на свой подход. Поэтому плюсую, хотя скорее — за популяризацию самой идеи.
+2
Хммм… А где вообще можно про всякие «хитрые» алгоритмы и «волшебные числа» почитать?
0
зря вы так. я по (неведомой) цепочке вышел на блог viva64-блог. (внезапно) оказалось все очень интересно… однажды пару дней там убил.
0
блог… блог — да, мой косяк.
0
Как быть?
считать в милли-/микро-вольтах религия не позволяет?
не хватит long — есть long long.
0
Да, но надо помнить, что вместо 4 байт будем обрабатывать 8 уже. Помноженное на число каналов.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.