Электронная нагрузка для разряда аккумуляторов на микроконтроллере PIC16F628A

от автора

Привет, Хабр! У данного микроконтроллера отсутствует встроенный аналогово-цифровой преобразователь (АЦП), зато есть цифро-аналоговый (ЦАП) и компараторы. Это позволяет использовать PIC16F628A для управления разрядным устройством с довольно продвинутым функционалом.

▍ Постановка задачи

Я занимаюсь свинцово-кислотными аккумуляторами, и однажды мне понадобилось несколько устройств для разряда стабилизированным током до касания определённого уровня напряжения под нагрузкой.

Нагрузки требовались для исследований в области восстановления не сферических аккумуляторных батарей в вакууме, а реальных 12-вольтовых. То есть, состоящих из шести «банок» — свинцово-кислотных ячеек.

Разряд было необходимо производить в двух вариантах: полный до 10.5 вольт и частичный до 12 вольт.

Для электровелосипедных батарей ёмкостью 12 А*ч требовался разрядный ток от 1.2 до 1.4 А, что соответствует мощности тепловыделения до 20 ватт, тогда как автомобильные 60-ампер-часовые аккумуляторы следовало разряжать током 4-6 А. Последнее означало 80 ватт тепловой мощности.

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

Зато в закромах валялась кучка радиаторов от микропроцессоров Intel и выходных транзисторов строчной развёртки отечественных телевизоров. Имелись в наличии и опрометчиво купленные микроконтроллеры PIC16F628A без модулей АЦП.

Один из них был успешно использован при создании спидометра с одометром для электромопеда на базе Риги-12. Девять оставшихся ждали своего часа, и он, наконец, пришёл.

▍ Начнём с основного

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

Простейший стабилизатор тока можно собрать на микросхеме LM317 в корпусе TO-220, добавив к ней шунт — резистор сопротивлением 1 Ом, способный рассеивать порядка полутора ватт тепла в продолжительном режиме.

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

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

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

Последовательный диод служит для защиты схемы от подключения аккумулятора в неверной полярности. А индицирующий о процессе разряда светодиод можно подключить параллельно стабилизатору тока.

▍ Нужно больше мощности

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

В моём распоряжении имелось несколько мощных полевых транзисторов из силовых цепей старых телевизоров. 2 штуки таких прекрасно справятся с 80 ваттами тепла, будучи установленными на один радиатор для Socket 478.

«Телевизионные» транзисторы 3N80 и 9NB50 достались мне в изолированных корпусах TO-220F. Немного работы надфилем, и корпуса стали неизолированными для меньшего теплового сопротивления.

Стабилизатор тока на полевом транзисторе и операционном усилителе — совершенно стандартная схема.

Подтяжка инвертирующих входов операционных усилителей к плюсу питания сделана для гарантированного отключения разрядной нагрузки низким логическим уровнем с выхода микроконтроллера.

Номиналы резисторов выбраны такими, чтобы обеспечить нужный разрядный ток. Микроконтроллер будет питаться от прецизионного стабилизатора напряжения AMS1117-5.0, поэтому опорное напряжение для стабилизатора тока можно брать прямо с цифрового выхода PIC16F628A.

Маленький полевик IRLML2502 отвечает за включение и выключение вентилятора одновременно с нагрузкой. Чтобы уберечь его от пробоя повышенным напряжением на клеммах, добавлен супрессор D2.

▍ Первые сложности

Кроме выхода на включение нагрузки, нам потребуется вход компаратора, трёхразрядный семисегментный индикатор с десятичной точкой и две кнопки. Это 1 + 1 + 8 + 3 + 2 = 15 выводов микроконтроллера.

Их как раз хватит, поскольку у PIC16F628A два полных восьмиразрядных порта, итого 16 сигнальных выводов плюс земля и плюс питания.

Однако коллегам по проекту захотелось использовать кварцевый резонатор, несмотря на прецизионность тактового RC-генератора, встроенного в микроконтроллер. Кварцу требуется две ножки, а 15 уже заняты. Одной не хватает.

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

На неинвертирующий вход я буду подавать опорное напряжение со встроенного ЦАП. Осуществить это без задействования вывода RA3/AN3/CMP1, к сожалению, невозможно. Поэтому первую ножку микроконтроллера придётся оставить висящей в воздухе.

Итак, восьмиразрядный порт B будет отведён под сегменты индикатора с общим катодом. Два вывода порта A забирает кварц, ещё два — компаратор, и ещё один — управление включением нагрузки.

Остаётся три вывода, из которых один — MCLR/Vdd/RA5 — способен работать только на вход. К нему будет подключена одна кнопка.

Вторую кнопку и переключение трёх разрядов светодиодного индикатора надо ухитриться реализовать с использованием всего двух ножек микроконтроллера. К счастью, это вполне возможно.

▍ Разработка принципиальной схемы

Я просто обожаю диодно-транзисторную логику на дискретных компонентах. Транзисторные ключи, зажигающие разряды индикатора, представляют собой инверторы — логические элементы НЕ.

При логической единице на входе инвертора транзистор открывается, и соответствующий разряд индикатора зажигается.

Если открыт транзистор первого или второго разряда, то анод светодиода D7 просаживается до напряжения, недостаточного для открытия D7 и Q5. Третий разряд не светит.

Когда закрыты и Q3, и Q4, светодиод открывается и обеспечивает ток базы Q5. На индикаторе зажигается третий разряд.

Предусматриваем резисторы, ограничивающие токи сегментов.

И, наконец, подключаем микроконтроллер.

Осталось написать программное обеспечение. Для этого я пользуюсь пакетом JAL V.2.

▍ Пишем прошивку

Загружаем библиотеку, соответствующую используемому микроконтроллеру.

include 16f628a

Указываем компилятору, что тактирование будет осуществляться от внешнего 4-мегагерцового кварца. Частота команд получается 1 мегагерц, так как одна команда выполняется за 4 такта.

pragma target clock 4_000_000 -- 4 МГц pragma target OSC      XT -- внешний пассивный кварц

Настраиваем защиты микроконтроллера.

pragma target BROWNOUT ENABLED pragma target LVP      DISABLED -- низковольтное программирование запрещено pragma target CP       DISABLED -- защита от считывания исполняемого кода отключена pragma target CPD      DISABLED -- защита от считывания EEPROM программатором отключена pragma target PWRTE    ENABLED -- таймер задержки включения активирован pragma target MCLR     INTERNAL  -- внутренний сброс

Нашему разрядному устройству потребуется калибровочная константа, учитывающая реальную тактовую частоту и реальный разрядный ток конкретного экземпляра. При прошивке микроконтроллера в EEPROM будет записано значение этой константы по умолчанию.

pragma EEDATA 0xB0, 0x04

▍ Присваиваем имена

Назовём выводы микроконтроллера соответственно возлагаемым на них функциям.

alias digit1_on is   pin_A0 -- зажигает первый разряд индикатора alias cmp_in    is   pin_A1 -- вход компаратора для контроля напряжения на клеммах АКБ alias vrefout   is   pin_A2 -- выход источника опорного напряжения (не используется) alias dscg_on   is   pin_A3 -- включает разрядный ток alias digit2_on is   pin_A4 -- зажигает второй разряд alias btn_minus is   pin_A4 -- кнопка «минус» var bit minus_dir at TRISA:4 -- переключение режима вывода: 1 - вход, 0 - выход alias btn_plus  is   pin_A5 -- кнопка «плюс» alias clkout    is   pin_A6 -- выход тактового генератора на кварц alias clkin     is   pin_A7 -- вход с кварца alias dp_on     is   pin_B2 -- зажигает десятичную точку

▍ Кодировка символов

Вывод цифры или буквы на определённый разряд индикатора производится путём записи готового числа в порт, ножки которого соединены с анодами сегментов через токоограничивающие резисторы. Это число берётся из массива, индекс в котором соответствует цифре или кодировке буквы.

const byte segments [22] =  -- Здесь должна быть открывающая фигурная скобка, но она делает текст ниже серым. --  -A- -- F   B --  -G- -- E   C --  -D- --FADECpGB -- адреса выводов, зажигающих сегменты 0b11111001, -- 0  ABCDEF 0b00001001, -- 1  BC 0b01110011, -- 2  ABDEG 0b01101011, -- 3  ABCDG 0b10001011, -- 4  BCFG 0b11101010, -- 5  ACDFG 0b11111010, -- 6  ACDEFG 0b01001001, -- 7  ABC 0b11111011, -- 8  ABCDEFG 0b11101011, -- 9  ABCDFG 0b01111010, -- 10 ACDEG 0b10011001, -- 11 BCFE 0b01110000, -- 12 ADE 0b01111000, -- 13 ACDE 0b00011010, -- 14 буква "n" CEG 0b11010010, -- 15 - буква "f" AEFG 0b00000000, -- 16 - пробел 0b11010000, -- 17 - буква "r" AEF 0b00111011, -- 18 - буква "d" BCDEG 0b10101011, -- 19 - буква "y" BCDFG 0b10110010, -- 20 - буква "t" DЕFG 0b11110000  -- 21 - буква "c" ADEF }

Для удобства программирования создадим константы, соответствующие адресам символов.

const letter_o = 0 const letter_s = 5 const letter_n = 14 const letter_f = 15 const space    = 16 const letter_r = 17 const letter_d = 18 const letter_y = 19 const letter_t = 20 const letter_c = 21

▍ Объявляем переменные

var volatile byte sec_100 = 0 -- сотни секунд var volatile byte sec_10  = 0 -- десятки секунд var volatile byte sec_1   = 0 -- единицы секунд var volatile byte ah_1    = 0 -- единицы А*ч var volatile byte ah_10   = 0 -- десятые доли А*ч var volatile byte ah_100  = 0 -- сотые доли А*ч var volatile byte ind1 -- что выводится на первый разряд индикатора var volatile byte ind2 -- на второй разряд var volatile byte ind3 -- на третий разряд var volatile word current -- ток разряда в миллиамперах var byte current_byte[2] at current var volatile word current_old -- старое значение var volatile word mas = 0 -- счётчик миллиампер*секунд var volatile byte scaler = 0 -- делитель 250 герц на 125 var volatile byte idle = 0    -- счётчик времени между событиями var volatile byte minus_count = 0 -- счётчик времени удержания кнопки «минус» var volatile byte threshold      -- порог завершения разряда var volatile byte threshold_old  -- старый порог завершения разряда var volatile byte thr_ind       -- индикация порога завершения разряда var volatile byte ind_off_count = 0 -- Счётчик секунд до отключения индикатора var bit ind_off_count_64 at ind_off_count:6 -- 64 секунды var volatile byte cont_count = 0 -- Счётчик секунд до возобновления разряда var bit cont_count_64 at cont_count:6 -- 64 секунды

▍ Флаги

Эти булевы переменные отражают состояние асинхронного автомата. В однобайтовой переменной может храниться восемь флагов.

var volatile byte flags = 0  var bit digit1_next   at flags:0 -- индикация будет передана старшему разряду var bit digit2_next   at flags:1 -- индикация будет передана среднему разряду var bit dp_1          at flags:2 -- десятичная точка после старшего разряда var bit dp_2          at flags:3 -- десятичная точка после среднего разряда var bit second        at flags:4 -- прошла одна секунда var bit minus_pressed at flags:5 -- нажата кнопка «минус» var bit plus_pressed  at flags:6 -- нажата кнопка «плюс» var bit reset_ok      at flags:7 -- ёмкость сброшена  var volatile byte flags1 = 0 var bit clock_stop    at flags1:0 -- секундомер остановлен var bit calibr_mode   at flags1:1 -- режим калибровки var bit minus_detect  at flags1:2 -- подавление дребезга кнопки «-» var bit plus_detect   at flags1:3 -- антидребезг кнопки «+» var bit even          at flags1:4 -- делитель 2 герц на 2 var bit minus_hold    at flags1:5 -- удерживалась кнопка «-» var bit threshold_set at flags1:6 -- режим переключения порогов var bit dscg_finished at flags1:7 -- разряд завершён  var volatile byte flags2 = 0 var bit ind_off       at flags2:0 -- индикатор выключен var bit cmp_rdy       at flags2:1 -- компаратор сработал var bit continue      at flags2:2 -- продолжать разряд var bit cmp_state     at flags2:3 -- отображать состояние компаратора на индикаторе var bit no_ind        at flags2:4 -- работать без индикации

▍ Выбор порога завершения разряда

Данное разрядное устройство реализует восемь предустановок напряжения завершения разряда.

Встроенный цифро-аналоговый преобразователь позволяет устанавливать опорное напряжение компаратора в виде определённой доли напряжения питания микроконтроллера. За это отвечает управляющий регистр VRCON.

const byte vrc [8] =  -- Здесь должна быть открывающая фигурная скобка.  0b11100100, --  6 вольт 0b11100101, --  7.5 В 0b11100110, --  9 В 0b11000001, -- 10.125 В 0b11100111, -- 10.5 В 0b11000010, -- 11.25 В 0b11101000, -- 12 В 0b11000010  -- 12.375 В } VRCON    =   0b11100100 

При выборе порога завершения разряда с помощью кнопки на индикатор должно выводиться цифровое значение задаваемого напряжения в вольтах.

const byte thr [8] = {  60,75,90,101,105,0,120,124 }  procedure indicate_thr is -- отображение порога  thr_ind = thr[threshold] dp_1 = off dp_2 = on ind1 = thr_ind / 100 ind2 = (thr_ind % 100) / 10 -- остаток от деления на 100, делённый на 10 ind3 = thr_ind % 10 -- остаток от деления на 10 if (thr_ind ==0) then   ind1 = 11   ind2 = 2   ind3 = 5   dp_1 = on   dp_2 = off end if end procedure

▍ Настраиваем периферию

Биты в управляющих регистрах TRISA и TRISB задают режим работы выводов соответствующих портов. Единица означает вход, ноль — выход.

Запись в регистры PORTA и PORTB приводит к изменению состояния тех ножек портов, что настроены на выход.

TRISA    =   0b11110110 TRISB    =   0b00000000 PORTA    =   0b00000000 PORTB    =   0b00000000

Содержимое регистра CMCON определяет конфигурацию компараторов. Нам требуется один независимый компаратор.

CMCON    =   0b00000101 alias cmp_out is CMCON_C2OUT -- выход компаратора

▍ Динамическая индикация

Благодаря инерции зрения, быстро переключающиеся разряды индикатора воспринимаются как светящиеся постоянно. Это называется динамической индикацией.

В данном разрядном устройстве переключение разрядов осуществляется с частотой 250 герц. Эта же частота используется для подсчёта времени разряда.

T2CON    =   0b00000110 -- предделитель 16, постделитель 1 PR2      =   250 - период таймера

Предделитель делит тактовую частоту 1 мегагерц на 16. Получается приращение счётчика таймера с частотой 62500 герц. Период 250 вызовет переполнение таймера 250 раз в секунду.

▍ Прерывания

Разрешаем прерывания только от TIMER2 и обнуляем регистр с флагами прерываний.

PIE1     =   0b00000010 PIR1     =   0b00000000  INTCON   =   0b11000000

Пишем процедуру обработчика прерывания, которое будет вызываться таймером 250 раз в секунду.

 procedure interrupt is pragma interrupt if PIR1_TMR2IF then     PIR1_TMR2IF = off -- сбрасываем флаг прерывания от таймера   if cmp_state then -- выводим на индикатор состояние компаратора    ind1 = letter_o     if cmp_out then       ind2 = letter_f      ind3 = letter_f     else     ind2 = letter_n     ind3 = space    end if    dp_1 = off    dp_2 = off   end if   no_ind = (ind_off|(continue & even)) -- если индикация отключена или мигает   if digit1_next then    -- зажигаем первый разряд индикатора    digit1_next = off    digit2_next = on    if no_ind then     portb = 0    else     portb = segments[ind1]     dp_on = dp_1    end if    digit1_on = on    digit2_on = off   elsif digit2_next then -- зажигаем второй разряд    digit1_next = off    digit2_next = off    if no_ind then     portb = 0    else     portb = segments[ind2]     dp_on = dp_2    end if    minus_dir = 1    digit2_on = on    digit1_on = off   else                   -- зажигаем третий разряд    if !btn_minus then -- но сначала проверим, нажата ли кнопка «минус»     minus_detect = on -- устанавливаем флаг короткого нажатия    else     if (minus_count > 3) then      minus_hold = on -- флаг длинного нажатия кнопки     end if     minus_count = 0    end if    digit1_next = on    digit2_next = off    if no_ind then     portb = 0    else     portb = segments[ind3]     dp_on = !(dp_2|dp_1)    end if     digit1_on = off     minus_dir = 0 -- переводим ножку в режим выхода     digit2_on = off   end if end if if !btn_plus then -- если нажата кнопка «плюс»   plus_detect = on end if scaler = scaler + 1 -- счётчик срабатываний прерывания if (scaler >= 125) then -- прошло полсекунды   scaler = 0 -- обнуляем   if plus_detect then -- антидребезг    plus_detect = off    plus_pressed = on   end if   if minus_detect then    minus_detect = off    minus_pressed = on    minus_count = minus_count + 1   end if   even = !even   if !even then -- прошла целая секунда    second = on   end if end if end procedure

▍ Индикация ампер*часов

Если вы внимательно изучали таблицу кодировки символов, то могли заметить, что в ней присутствует «цифры» 10 и 11. Благодаря ей, в диапазоне от 10.00 до 11.99 мы можем наблюдать не три, а четыре значащих цифры.

Также в таблице имеются знаки для «цифр» 12 и 13, но в силу трудночитаемости они применены только в режиме калибровки, речь о котором пойдёт ниже.

procedure ah_indicate is if (ah_1 < 12) then    -- от 0.00 до 11.99 А*ч   ind1 = ah_1   ind2 = ah_10   ind3 = ah_100   dp_1 = on -- десятичная точка после старшего разряда   dp_2 = off elsif (ah_1 < 99) then -- от 12.0 до 99.9   ind1 = ah_1 / 10   ind2 = ah_1 % 10 -- остаток от деления на 10   ind3 = ah_10   dp_1 = off   dp_2 = on -- десятичная точка после среднего разряда else                   -- от 100 до 255   ind1 = ah_1 / 100   ind2 = (ah_1 % 100) / 10   ind3 = ah_1 % 10   dp_1 = off -- десятичная точка после младшего разряда   dp_2 = off end if end procedure

▍ Сохранение А*ч в EEPROM

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

procedure save_1 is -- единицы INTCON_GIE = 0 -- запрещаем прерывания перед записью EEADR = 3 EEDATA = ah_1 EECON1_WREN = 1 EECON2 = 0x55 EECON2 = 0xAA EECON1_WR = 1 while EECON1_WR loop end loop EECON1_WREN = 0 INTCON_GIE = 1 end procedure  procedure save_10 is -- десятые доли  INTCON_GIE = 0 EEADR = 4 EEDATA = ah_10 EECON1_WREN = 1 EECON2 = 0x55 EECON2 = 0xAA EECON1_WR = 1 while EECON1_WR loop end loop EECON1_WREN = 0 INTCON_GIE = 1 end procedure  procedure save_100 is -- сотые доли  INTCON_GIE = 0 EEADR = 5 EEDATA = ah_100 EECON1_WREN = 1 EECON2 = 0x55 EECON2 = 0xAA EECON1_WR = 1 while EECON1_WR loop end loop EECON1_WREN = 0 INTCON_GIE = 1 end procedure

▍ Подсчёт ампер*часов

Один А*ч равняется 60*60 = 3600 ампер*секундам, то есть кулонам. Квантом измерения полезной ёмкости аккумулятора в данной электронной нагрузке является сотая доля ампер*часа. Она равна 36 тысячам миллиампер*секунд.

procedure dscg_count is pragma inline mas = mas + current if (mas >= 36000) then   mas = mas - 36000   ah_100 = ah_100 + 1   if (ah_100 > 9) then    ah_100 = 0    ah_10 = ah_10 + 1    if (ah_10 > 9) then     ah_10 = 0     ah_1 = ah_1 + 1     save_1    end if    save_10   end if   save_100 end if end procedure

▍ Режим секундомера

Данный режим активируется подачей питания при зажатой кнопке «минус» и служит для калибровки нашей электронной нагрузки.

procedure clock_indicate is -- отображение секунд pragma inline                ind1 = sec_100 ind2 = sec_10 ind3 = sec_1 dp_1 = off dp_2 = off end procedure  procedure clock_count is -- подсчёт секунд pragma inline sec_1 = sec_1 + 1 if (sec_1 > 9) then   sec_1 = 0   sec_10 = sec_10 + 1   if (sec_10 > 9) then    sec_10 = 0    sec_100 = sec_100 + 1    if (sec_100 > 12) then     sec_100 = 0    end if   end if end if end procedure

Нажатие кнопки «плюс» запускает и останавливает секундомер, а нажатие «минуса» сбрасывает его на ноль.

procedure stopwatch is  pragma inline forever loop   if second then -- прошла секунда    second = off    if !clock_stop then     clock_count    end if    if plus_pressed then     plus_pressed = off     minus_pressed = off     clock_stop = !clock_stop    end if -- plus_pressed    if minus_pressed then     minus_pressed = off      sec_1   = 0     sec_10  = 0     sec_100 = 0    end if -- minus_pressed    clock_indicate   end if -- second end loop end procedure

▍ Калибровка тока и времени

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

Также потребуется измерить фактический средний ток данного экземпляра нагрузки в миллиамперах. Его следует умножить на фактическое время и разделить на показание секундомера нагрузки.

Получится калибровочная константа, которую нужно ввести в энергонезависимую память прибора, переведя его в режим калибровки. Для этого подаём питание с зажатой кнопкой «плюс» и увеличиваем или уменьшаем число на индикаторе соответствующими кнопками.

Когда калибровочная константа введена, следует подождать 10 секунд, не отключая питания и не нажимая кнопок. Произойдёт сохранение в EEPROM, по окончании которого число на индикаторе сменится буквами «rdy» от английского ready — «готово».

procedure ma_indicate_1 is --  до 1399 мА для нагрузки на LM317   ind1 = current / 100   ind2 = (current % 100) / 10   ind3 = current % 10   dp_1 = off   dp_2 = off end procedure  procedure ma_indicate_5 is --  до 9.99 мА для нагрузки на MOSFET   ind1 = current / 1000   ind2 = (current % 1000) / 100   ind3 = (current % 100) / 10   dp_1 = on   dp_2 = off end procedure  alias ma_indicate is ma_indicate_5  procedure calibrate is pragma inline calibr_mode = on current_old = current ma_indicate while !btn_plus loop end loop -- ожидание отпускания кнопки «плюс» plus_pressed = off second = off while !second loop end loop second = off while !second loop end loop second = off while !(plus_pressed|minus_pressed) loop end loop -- ожидание нажатия любой кнопки plus_pressed = off minus_pressed = off while (idle < 11) loop   if second then -- прошла секунда    second = off    idle = idle + 1   end if   if plus_pressed then    plus_pressed = off    idle = 0 --   if (current < 1398) then    if (current < 6000) then     current = current + 1    else --    current = 400     current = 4000    end if    ma_indicate   end if   if minus_pressed then    minus_pressed = off    idle = 0 --   if (current > 401) then    if (current > 4001) then     current = current - 1    else --    current = 1399     current = 5999    end if    ma_indicate   end if end loop if (current_old != current) then   INTCON_GIE = 0   EEADR = 0   EEDATA = current_byte[0]   EECON1_WREN = 1   EECON2 = 0x55   EECON2 = 0xAA   EECON1_WR = 1   while EECON1_WR loop   end loop   EEADR = 1   EEDATA = current_byte[1]   EECON2 = 0x55   EECON2 = 0xAA   EECON1_WR = 1   while EECON1_WR loop   end loop   EECON1_WREN = 0   INTCON_GIE = 1 end if ind1 = letter_r -- индикация «rdy» ind2 = letter_d ind3 = letter_y dp_1 = off dp_2 = off forever loop end loop end procedure

▍ Рабочий режим

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

В данном режиме нажатие кнопки «плюс» включает и выключает разрядный ток, тогда как кнопка «минус» переключает пороги завершения разряда.

procedure switch_thr is -- выбор порога pragma inline threshold = threshold + 1 if (threshold > 7) then   threshold = 0 end if VRCON = vrc[threshold] indicate_thr end procedure

Длительное нажатие кнопки «минус» сбрасывает ампер*часы вместе с десятыми и сотыми долями. Успешный сброс индицируется символами «rst», от английского «reset».

procedure reset_ah is pragma inline mas    = 0 ah_1   = 0 ah_10  = 0 ah_100 = 0 save_1 save_10 save_100 ind1 = letter_r -- вывод «rst» ind2 = letter_s ind3 = letter_t dp_1 = off dp_2 = off reset_ok = on end procedure

Порог отключения разрядного тока запоминается в EEPROM в момент ручного запуска разряда.

procedure save_thr is pragma inline if threshold_old != threshold then   INTCON_GIE = 0   EEADR = 2   EEDATA = threshold   EECON1_WREN = 1   EECON2 = 0x55   EECON2 = 0xAA   EECON1_WR = 1   while EECON1_WR loop   end loop   EECON1_WREN = 0   INTCON_GIE = 1   threshold_old = threshold end if end procedure

▍ Калибровка напряжения

При подаче питания с нулевыми значениями слитой ёмкости в EEPROM индикатор показывает «On» или «Off» в зависимости от состояния компаратора, пока не была нажата ни одна из кнопок.

Это нужно для калибровки напряжения. Запитываем нагрузку от лабораторного блока питания и крутим десятиоборотный подстроечный резистор на плате нагрузки до тех пор, пока фактическое напряжение срабатывания компаратора не совпадёт с установленным.

▍ Обработчики кнопок

Спустя минуту после завершения разряда нагрузка автоматически отключает семисегментный индикатор. Чтобы зажечь его снова и наблюдать значение отданных батареей ампер*часов, требуется просто нажать любую кнопку.

procedure plus_btn is  -- кнопка «плюс» pragma inline ind_off_count = 0 if ind_off then    ind_off = off   elsif !(cmp_state|threshold_set|reset_ok) then   if dscg_on then    dscg_on = off    dscg_finished = on   else    dscg_on = !cmp_out    if dscg_on then     dscg_finished = off     save_thr      -- сохранение порога    end if -- dscg_on   end if -- dscg_on end if -- !ind_off threshold_set = off reset_ok = off cmp_state = off end procedure  procedure minus_btn is -- кнопка «минус» pragma inline reset_ok = off cmp_state = off ind_off_count = 0 if ind_off then   ind_off = off   else    if minus_hold  then   -- долгое нажатие    minus_hold = off     -- сброс ёмкости    threshold_set = off    reset_ah    reset_ok = on   elsif (minus_count < 3) then -- короткое нажатие    indicate_thr              -- переключение порогов    if threshold_set then     switch_thr    end if    threshold_set = on   end if end if -- !ind_off end procedure

▍ Инициализация

При включении питания устройство загружает в регистры оперативной памяти из EEPROM калибровочную константу, порог завершения разряда и отданную аккумулятором ёмкость.

EEADR = 0  EECON1_RD = 1 current_byte[0] = EEDATA EEADR = 1 EECON1_RD = 1 current_byte[1] = EEDATA  procedure prepare is pragma inline EEADR = 2  EECON1_RD = 1 threshold = EEDATA threshold_old = threshold VRCON = vrc[threshold] cmp_rdy = off  EEADR = 3 EECON1_RD = 1 ah_1 = EEDATA EEADR = 4 EECON1_RD = 1 ah_10 = EEDATA EEADR = 5 EECON1_RD = 1 ah_100 = EEDATA

Если в момент подачи питания энергонезависимая память нагрузки содержит ненулевое число отданной аккумулятором ёмкости, то это число будет мигать на индикаторе в течение минуты.

Далее разрядный ток включится автоматически. Нажатие любой кнопки прерывает обратный отсчёт и отменяет автозапуск.

if (ah_1 != 0) then    continue = on       end if if (ah_10 != 0) then   continue = on end if if (ah_100 != 0) then   continue = on end if cmp_state = !continue end procedure

▍ Главный цикл

procedure main_cycle is pragma inline forever loop   if second then -- прошла секунда    second = off    if !(cmp_state|threshold_set|reset_ok) then     ah_indicate -- индикация ёмкости    end if    if continue then -- разряд был прерван     cont_count = cont_count + 1 -- подсчёт секунд     if cont_count_64 then -- по прошествии 64 секунд      continue = off       -- перезапуск разряда      dscg_finished = cmp_out      ind_off_count = 0      dscg_on = !dscg_finished     end if    end if -- continue    if dscg_finished then -- после завершения разряда     ind_off_count = ind_off_count + 1 --      if ind_off_count_64 then -- по прошествии 64 секунд      ind_off_count = 0      ind_off = on            -- отключение индикации     end if    end if -- dscg_finished    if cmp_rdy then     if cmp_out then -- завершение разряда по достижении порога      if dscg_on then       dscg_on = off   -- отключение разрядного тока       dscg_finished = on       ind_off_count = 0      end if     end if    end if -- cmp_rdy    cmp_rdy = cmp_out    if dscg_on then     dscg_count -- подсчёт ёмкости     ah_indicate -- индикация ёмкости    end if    if plus_pressed then     plus_pressed = off     continue = off     minus_pressed = off     plus_btn    end if -- plus_pressed    if minus_pressed then     minus_pressed = off     continue = off     minus_btn    end if -- minus_pressed   end if -- second end loop end procedure 

Один из трёх режимов работы нагрузки запускается в зависимости от кнопки, удерживаемой в момент подачи питания.

if !btn_plus then  plus_pressed = off minus_pressed = off calibrate        -- режим калибровки elsif !btn_minus then  stopwatch        -- режим секундомера else              -- рабочий режим prepare main_cycle        end if

▍ Заработало!

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

А по данной ссылке вы можете посмотреть чудом сохранившееся видео работы двух вариантов электронной нагрузки — с пассивным охлаждением на LM317 и с вентилятором на полевых транзисторах.

Напишите в комментариях, какие самодельные измерительные приборы, на микроконтроллерах и без них, собирали вы или ваши знакомые.

© 2025 ООО «МТ ФИНАНС»

Telegram-канал со скидками, розыгрышами призов и новостями IT 💻


ссылка на оригинал статьи https://habr.com/ru/articles/909540/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *