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

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