
Пролог
У меня было множество вело фар и всегда меня раздражало то, что фара включается практически мгновенно. Глаза даже не успевают приспособиться и это доставляет существенный дискомфорт. При этом на работе я много лет как раз разрабатывал ECU-прошивки для управления кузовной электроникой. В связи с этим я принял решение разработать себе прототип вело фары с плавным включением яркости для вело-фары.
Постановка задачи
Разработать прототип вело-фары с плавным включением и плавным отключением. Яркость должна изменяться постепенно. Обеспечить возможность дистанционного включения лампы (например по IR пульту). Сделать возможность регулирования яркости свечения через инкрементный энкодер. Настройки интенсивности свечения сохранять в энергонезависимой памяти NVRAM. Попробовать два алгоритма регулирования яркости : PWM и PDM.
Аппаратная часть
В качестве отладочной платы для прототипа я по-прежнему выбрал с JZ-F407VET6.

Распиновка для сборки прототипа такая
GPIO |
Pull |
Wire Name |
Dir |
Comment |
PE0 |
Up |
Encoder A |
in |
Инкрементный энкодер |
PE1 |
Up |
Encoder B |
in |
Инкрементный энкодер |
PA6 |
up |
IR sensor |
in |
TSOP-36 |
PA9 |
no |
USART1_TX |
out |
CLI |
PA10 |
up |
USART1_RX |
in |
CLI |
PA0 |
none |
TIM5_CH1 |
out |
PWM |
PA3 |
none |
TIM9_CH2 |
out |
PWM |
PE4 |
none |
DeltaSigma |
out |
PDM |
В роли драйвера 12-вольтового LED я выбрал микросхему H-моста DRV8870. Вот так выглядит отладочная плата для этой микросхемы.

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

Весь прототип выглядит вот так

В чем трудность управления LED фонарями?
1) Главная особенность LED лампы (в отличие от ламп накаливания) в том, что она полностью безинерционная. То есть, если с нуля подать скважность 100%, то лампа в самом деле мгновенно и ослепительно включится на все 100%. Выглядит как вспышка. Для меня это как раз нежелательный сценарий. Я же хочу сделать как раз искусственную инерционность LED лампы. Чтобы хрусталики глаз могли приспособиться к постепенному нарастанию яркости. Как же мне этого добиться?
2) Вторая особенность LED в том, что LEDы не так-то просто плавно включить. Уже при 0.7 % заполнения LED включается и это тоже визуально воспринимается как резкое включение. Хочется как-то сделать акцент именно на начальной фазе нарастания яркости и на фазе окончательного потухания. Добавить программную инерционность вокруг нулевого заполнения.
Программная часть
Прежде чем писать прошивку надо определиться с тем по какому алгоритму следует производить нарастание яркости. Простой линейный закон изменения яркости - это не так эффектно. Сначала я попробовал экспоненциальный закон нарастания яркости. С потуханием все хорошо, однако с включением плохо. На прототипе это выглядело как резкое включение.

В коде он выглядит так
Экспоненциальное нарастание и спад яркости LED-а
import matplotlib.pyplot as plt import numpy as np max_illum=100.0 scale = 0.5 time_s = np.arange(1, 7) time_max=10 time_s = np.linspace(0, time_max, 100) illumination_climax = max_illum*(1.0- np.exp(-scale*time_s)) illumination_decay = max_illum* np.exp(-scale*time_s) plt.plot(time_s, illumination_climax) plt.plot(time_s, illumination_decay) plt.title('Illumination change') plt.xlabel('Time,[s]') plt.ylabel('Light level, [Lx]') plt.grid() plt.show()
Затем я попробовал реализовать нарастание и снижение яркости при помощи логистической кривой. Логистическая функция выражается формулой ниже, где k - коэффициент крутизны наклона кривой

Вот ее графики

Можно поэкспериментировать с различными параметрами переходного процесса отрисовывая графики: длительность переходного процесса и коэффициент в степени.
Код построения логистической кривой
import matplotlib.pyplot as plt import numpy as np max_illum=100.0 scale = 0.95 time_s = np.arange(1, 7) time_max=10 time_s = np.linspace(0, time_max, 100) time1_s=time_s-time_max/2 illumination_climax = max_illum*1.0/(1.0 + np.exp(-scale*time1_s)) illumination_decay = max_illum-illumination_climax #illumination_decay1 = max_illum* np.exp(-scale*time_s) plt.plot(time_s, illumination_climax) plt.plot(time_s, illumination_decay) plt.title('Illumination change') plt.xlabel('Time,[s]') plt.ylabel('Light level, [Lx]') plt.grid() plt.show()
Этот переход уже что надо, однако надо помнить, что у меня есть еще и инкрементный энкодер для ручного управления яркостью. Функциональное управление яркостью согласно логистической кривой без обратной связи перебивает алгоритм управления яркостью по квадратурному энкодеру. Как же разрешить это противоречие?
PID регулятор
Чтобы решить эту проблему я обратился к PID регулятору. PID-регулятор — это программа, которая читает показания с обратной связи и управляет мощностью так, чтобы значение датчика стало тем, что вы задали. Что, если энкодером и кнопками задавать целевое значение для PID регулятора? При этом PID регулятор уже сам будет как-то делать желаемый переходной процесс.
Главная роль тут отводится интегральной части регулятора. Ее вклад в финальное заполнение и будет вносить основной результат. Однако и тут облом. Дело в том, что сразу после установки нового целевого значения яркости образуется максимальная ошибка (погрешность). Это приводит к интенсивному нарастанию яркости на начальном этапе, которое как раз и портит весь визуальный эффект. PID сам по себе не делает S-образный (smooth) переходной процесс. Он по природе стремится к экспоненциальному или колебательному выходу на ступенчатое воздействие. Как же быть?
Чтобы сделать медленное изменение на начальном и конечном участке я слегка модернизировал PID регулятор. Добавил еще один параметр (смешение): отклонение от прошлой цели. Смещение противоположно ошибке. Смещение плюс погрешность дают расстояние, которое надо пройди регулятору от предыдущей цели к новой цели.
Чтобы появилась возможность вычислять смещение, надо при каждой установке нового целевого значения регулятора запоминать предыдущее значение цели last_target. Вот так.
bool pid_target_set(uint8_t num, float target) { bool res = false; PidHandle_t* Node = PidGetNode(num); if(Node) { res = is_float_equal_absolute(target, Node->target,0.0001); if(!res) { LOG_INFO(PID, "PID%u,Set,Target:%f->%f", num, Node->target, target); Node->last_target = Node->target; // !!!!!!! Node->target = target; res = true; } } else { LOG_ERROR(PID, "NodeErr,PID%u", num); } return res; }
Затем на каждой итерации PID регулятора вычислять абсолютное значение смещения shift = |out - last_target|

Благодаря вычислению смещения можно на каждой итерации PID регулятора вычислять такое приращение к интегральной части, которое в результате и сделает нам S-образный переходной процесс (см зелёный график d ). Дифферециальная часть PID регулятора тут и вовсе не нужна. Коэффициент D можно обнулить. Однако, чтобы процесс начался нужно сделать ненулевой пропорциональный коэффициент. Код ядра PID регулятора получается такой.
static bool pid_proc_value_ll(PidHandle_t* const Node) { bool res = false; if(Node) { Node->shift = Node->last_target - Node->out; Node->d_sum = 0.0f; if(0.0 < Node->error) { Node->d_sum = MATH_MIN(fabsf(Node->shift),Node->error); } else { Node->d_sum = MATH_MAX(-fabsf(Node->shift),Node->error); } Node->error_sum += Node->d_sum; Node->out = 0.0f; Node->out += Node->p * Node->error; Node->out += Node->i * Node->error_sum; LOG_DEBUG(PID, "%s", PidNodeToStr(Node)); res = true; } return res; }
Вот так это работает в реальности. Перед Вами график заполнения PWM от времени прочитанный из реального UART лога. Как видно, это S-образный переходной процесс, который рассчитывается в потоковом режиме.

Варьируя интегральный коэффициент можно управлять скоростью переходного процесса.
Управление яркостью на низком уровне
Второй вопрос, что выбрать для непосредственного регулирования яркости: PWM или PDM?

Проблема PWM в том, что он не позволяет непрерывно включаться с нуля и включает яркость скачком уже на 0.7% заполнения. Поэтому я попробовал запрограммировать программный PDM.

Настроил прерывания по таймеру 1 c частотой 20kHz. В обработчике прерываний стал делать вычисление PDM семплов.
bool delta_sigma_isr_proc_one_ll(DeltaSigmaHandle_t* Node) { bool res = false; Node->error = Node->target - Node->dac_out; Node->sum_error += Node->error; Node->pdm = adc_1bit(Node->sum_error, Node->comparator_middle); res = gpio_logic_level_set(Node->Pad, (GpioLogicLevel_t)Node->pdm); Node->dac_out = dac_1bit(Node->pdm, Node->min, Node->max); Node->sample_cnt++; return res; }
Однако это дело не улучшило. PDM дает заметное мерцание на малой яркости. В результате я выбрал PWM, который по крайней мере не мерцает.
В результате, прошивка приняла такую структуру.

Возможные приложения прошивки драйвера LEDов
--Тестер автомобильных светодиодных лампочек
--Вело фара
--Торшеры для чтения
--Люстры
--Настольные светильники
--Плавное включение может пригодиться для управления подсветкой смартфонов при выходе из режима блокировки экрана. Это добавит эргономики.
--Можно включать LED лампы на малое свечение ночью, чтобы сбивать с толку комаров.
--S-образный переходной процесс может быть как нельзя кстати для управления шаговыми двигателями (ШД). Там как раз запрещено скачкообразное изменение скорости управляющего воздействия, чтобы не потерять отработку шагов на обмотках статора.
Итог
Удалось сделать прототип электроники и прошивки для велосипедной фары, который позволяет производить плавное управление яркостью LED при помощи инкрементного энкодера. Также заложен алгоритм плавного включения и отключения фары. Удалось сделать S-образный переходной процесс при помощи слегка модернизированного PI-регулятора.
Прошивка готова и ее можно скачать на github. Дальнейшим развитием проекта может быть проработка электропитания, разработка миниатюрной PCB, конструктива и крепления вело фары.
Ссылки
Название |
URL |
Бинарь прошивки вело-лампы jz_f407vet6_bicycle_headlamp_gcc_m |
https://github.com/aabzel/Artifacts/tree/main/jz_f407vet6_bicycle_headlamp_gcc_m |
Исходники прошивки вело-лампы |
https://github.com/aabzel/trunk/tree/main/source/projects/jz_f407vet6_bicycle_headlamp_gcc_m |
Обзор учебно-тренировочной платы JZ-F407VET6 (или электронная парта) |
|
Delta-sigma modulation |
|
Дельта-сигма модуляция или PDM метод кодирования сигнала |
|
Конечный автомат инкрементного энкодера |
|
Сборка драйвера LED с управлением от энкодера jz_f407vet6_encoder_lamp_gcc_m |
https://github.com/aabzel/Artifacts/tree/main/jz_f407vet6_encoder_lamp_gcc_m |
“Сигма дельта” или как сделать хорошую звуковую карту из STM32F401 @Sdima1357 |
|
Конечный автомат инкрементного энкодера |
|
Обзор микросхемы DRV8870 (или Драйвер H-моста) |
|
Декодирование IR сигнала с TV (или исследование пультовых лучей) |
|
PID (ПИД) без математики: как просто понять P, I и D @RYAZHENKA-11 |
|
Python: Построение графиков по данным из файла |
Вопросы:
1) Как при помощи PID регулятора сделать S-образный переходной процесс?
2) Как избавиться от мерцания при PDM управлении свечением?
3) Существуют ли в продаже миниатюрные отладочные платы с STM32, GPIO разъемами и драйвером hi-side ключей, чтобы управлять автомобильными LEDами.
Комментарии (46)

xSVPx
13.06.2026 13:10Тут, похоже, наиболее правильным было бы иметь дополнительный временный тусклый свет. Включаешь его, а уж потом фару "на полную", а его выключаешь...
Иначе, скорее всего не победить чрезмерную яркость "на старте". Это еще и со всеми лампами будет по-разному работать скорее всего.

Brazil
13.06.2026 13:10Китайские чипы AT32F435 гораздо круче. Раз в три дешевле и раза в два быстрее
Вчера пока работал Fable5 он мне накатал из китайского pdf подробный компонент со всеми альтернативными функциями и оптимальным способом раскидал сигналы
Получил конроллер с управлением не менее 16 фарами и радаром ближнего действия, с дисплеем и аудиплейером.
Эх, крутая штука была Fable 5.

aabzel Автор
13.06.2026 13:10Китайские чипы AT32F435 гораздо круче. Раз в три дешевле и раза в два быстрее
Согласен с Вами на 101%. Сам программировал Artery микроконтроллеры в НПП ИТЭЛМА и очень мне понравились Artery микроконтроллеры (больше чем STM32)
Даже написал методичику как с ними работать.
ToolChain: Настройка сборки прошивок для микроконтроллеров Artery из Makefile
https://habr.com/ru/articles/792590/
Brazil
13.06.2026 13:10Ну так что заставило опять на STM32 вернутся?

aabzel Автор
13.06.2026 13:10После ИТЭЛМА в следующей компании принято программировать STM32.
Вот и пришлось опять вникать в специфику программирования STM32.
Настройка ToolChain(а) для Win10+GCC+С+Makefile+ARM Cortex-Mx+GDB
https://habr.com/ru/articles/673522/
Brazil
13.06.2026 13:10Да ладно вам постить эту лабуду.
Fable 5 конечно нет, но и Opus 4.8 снегерит рабочее окружение для билдинга и отладки Corteх-M4 для любой IDE.
И бы рекомендовал VS Code.
aabzel Автор
13.06.2026 13:10"никакой искусственный интеллект не способен одолеть тупость человеческого"
(академик Гермоген Сергеевич Поспелов)

old_merman
13.06.2026 13:10Что то не увидел я в данном проекте решения основополагающей проблемы светодиодного освещения: светодиод - токовый прибор, т.е. для его долгой и стабильной работы главным и критическим параметром является протекающий через него ток! А никаких мер для стабилизации этого самого тока и нету... Ну и ШИМ в осветительном приборе для транспортного средства - недопустимая, опасная для жизни штука, порождающая у наблюдателей оптические иллюзии по поводу подвижности/неподвижности освещаемых объектов.
Перед началом разработки, полезно ознакомиться с опытом предков: 15 лет назад на Фонарёвке был разработан высокоэффективный импульсный драйвер для питания 10вт светодиода от 1 лития, со стабилизацией тока, несколькими уровнями яркости без ШИМ, плавным вкл/выкл и прочими плюшками: https://forum.fonarevka.ru/showthread.php?t=13353 , https://forum.fonarevka.ru/showthread.php?t=20143 , https://forum.fonarevka.ru/showthread.php?t=20659 . При этом, управляющим контроллером драйвера служил attiny85, и свободного места в его флеше оставалость очень прилично, так что использовать для аналогичных целей готовую плату на stm32 - это стрельба из пушки не то что по воробьям, а по комарам )

Brazil
13.06.2026 13:10Не знаю за STM32 низких серий, но на AT32F вы сделаете еще более эффективный драйвер и в 10 раз больше плюшек.
Смотрите https://biricha.com/design-software/st-wds/
Там регуляторы 3..4 порядка. Сверхэффективные. Для широчайших диапазонов тока. С тучей защит и детекциями деградаций.
Там attiny85 просто нечего делать.

VT100
13.06.2026 13:10+100500. Автору достаточно было открыть DS на любой LED,чтобы убедиться в практически прямой пропорциональности светоотдачи току (т.е. экспоненте приложенного напряжения) и не писать чушь о 0,7%.

aabzel Автор
13.06.2026 13:10светодиод - токовый прибор, т.е. для его долгой и стабильной работы главным и критическим параметром является протекающий через него ток!
Существуют ли ASICи генераторы токов с управлением по SPI?

old_merman
13.06.2026 13:10Существуют ли ASICи генераторы токов с управлением по SPI?
Увы, на сегодня ответа на данный вопрос я не знаю, причём у меня уже лет 5 ждёт этого ответа кучка купленных до всяких санкций 20Вт High CRI светодиодов Cree XHP50 :(
Сильно подозреваю, ответа и не будет: для токового драйвера на 3+Ампер в принципе неизбежна куча внешней обвязки - индуктивности, конденсаторы, мощные MOSFET-ы, токоизмерительные резисторы и пр. - видимо, проще получается взять микроконтроллер и реализовать на нём.

old_merman
13.06.2026 13:10Дополню ответ
Существуют ли ASICи генераторы токов с управлением по SPI?
На меньшие, чем меня интересуют, токи, у TI есть серия TPS92520, именно с управлением по SPI, но количество требуемой внешней обвязки для чипов не радует.
На ток аж до 6А, есть SiC462 от Vishay, но оно, во-первых, не управляемое, во-вторых, количество внешних элементов там - реально ужОс-ужОс.

qwe101
13.06.2026 13:10Выключатель на 32F401 + DRV8870. Ладно, диммер. Наконец, дожили. А раньше умели транзисторы паять... Такое если только из любви к искусству... Тогда интересно.

aabzel Автор
13.06.2026 13:10Ну а что. Лежат бесхозные электронные детали. Почему бы и не пристроить для дела.

Coder007
13.06.2026 13:10Попробуй Ch32v003j4M6. Чип навороченный, но всего 8 ног, для задач фары - более чем. Не похож на attiny 13 и 85. Аппаратно поддерживает почти все интерфейсы. На 6 свободных пинах ты и поворотники ещё прикрутишь, на адресной ленте.
В конце концов любое управление через один пин и резистивная обвязка.

devprodest
13.06.2026 13:10Какой-то оверинженеринг получился.
Можно же на рассыпухе из пары транзисторов и конденсатора сделать - проще и надежнее.

aabzel Автор
13.06.2026 13:10А как же энкодер?

qwe101
13.06.2026 13:10А зачем? Переменники ещё не отменили.

aabzel Автор
13.06.2026 13:10Энкодеры крутить на ощуть приятнее так как у них есть стопоры на каждые 4 импульса.
Можно не глядя тактильно понять на какой угол ты повернул вал.

qwe101
13.06.2026 13:10А можно посмотреть на яркость фары и подкрутить, если надо. Не наощупь, а целевая функция.
Если хочется пощёлкать, есть ступенчатые переменники, альпс например.

Coder007
13.06.2026 13:10Энкодер - отличное решение, но думаю здесь пары кнопок хватит. Когда летишь через руль, а за тобой летит велосипед, меньше всего хочется что бы на нем было много торчащих предметов (типа ручки энкодера)

sim2q
13.06.2026 13:10Какой-то оверинженеринг получился.
у меня глаза округлялись и после PID уже бы не удивился, что оно к концу статьи где-то на удалённом сервере поедет :)

rukhi7
13.06.2026 13:10Чтобы решить эту проблему я обратился к PID регулятору.
а можно просто реализовать линейную функцию нарастания яркости от времени
Я = К*Т
И регулировкой коэффициента К управлять временем выхода яркости на полную мощность, это было бы где-то в 5 раз проще чем костыли для PID регулятора подставлять. Но тут надо с математикой линеаризации функций разобраться (а не с программированием), мат.анализ надо вспоминать.

Sun-ami
13.06.2026 13:10В действительно хорошей велофаре кроме плавной регулировки яркости нужна индикация прогнозируемого времени работы до разряда батареи, чтобы можно можно максимально возможную яркость при планируемой длительности поездки в темноте. Наверное, эта индикация нужна только во время регулировки яркости, потом её лучше отключить или сделать тусклой.
Кроме того, в велофаре скорее всего будет полезна автоматическая регулировка яркости по освещённости сверху, с большой постоянной времени регулятора. Потому что максимальная яркость велофары нужна тогда, когда нет никакого внешнего освещения, например в лесу, а на хорошо освещённой улице велофара работает скорее как габаритный огонь велосипеда, чем как источник света на дорогу. Впрочем, многие велофары имеют автоматическую регулировку яркости по освещённости, но не умеют плавно регулировать максимальную яркость вручную.
Велофаре были бы полезны светодиодные поворотники, хотя бы спереди, на руле. В темноте не видно, когда велосипедист показывает поворот руками. А в идеале, к передним поворотникам неплохо бы добавить задние поворотники, подключенные к заднему фонарю, и управляемые переключателем на руле вместе с передними.
Ещё велофаре был бы полезен индикатор низкого уровня заряда батарей в начале поездки, который кратковременно включается при начале движения, даже если освещение отключено, предупреждая, что заряда может не хватить.

sergyk2
13.06.2026 13:10ну вот теперь применение микроконтроллера не кажэца оверинжинирингом , как писали вышэ

aabzel Автор
13.06.2026 13:10Почему оверинжинирингом? Благодаря МК у продукта появляется потенциал для модернизации. Благодаря МК можно, например, добавить в фару функцию трансляции азбуки Морзе.

Coder007
13.06.2026 13:10И ещё пару тысяч разных кодировок. Только кому это все нужно?

Sun-ami
13.06.2026 13:10Удивительно, настолько редки светодиодные фонари с передачей сообщений морзянкой. Это, конечно, это скорее понты для выживальщиков, чем практически полезная функция, но прикольно иметь возможность передавать сообщения на расстояние до 15 километров при длительном блэкауте. Вот только с вводом могут быть проблемы, нужна, например, передача данных со смартфона на фонарь. Смартфоном морзянку можно и принимать, с 15 км. Можно, конечно и без фонаря, встроенной вспышкой, но тогда дальность связи будет меньше.

Mike-M
13.06.2026 13:10Прежде чем писать прошивку надо определиться с тем по какому алгоритму следует производить нарастание яркости.
Этот алгоритм давно известен, он называется Square Law. PID для ручной регулировки яркости не требуется. Правда, на LED я этот алгоритм не тестировал.
Можно включать LED лампы на малое свечение ночью, чтобы сбивать с толку комаров.
Можно, но медицина рекомендует людям спать в полной темноте.

arthuru1
13.06.2026 13:10Очень сложно, а можно просто увеличить емкость конденсатора на софтстарт или фильтрующего в опорном источнике для усилителя ошибки по току

Klochko
13.06.2026 13:10А что если LED сделать из ленты и каждую строку регулировать по яркости и ко количеству строк?
Допустим на начальном этапе включается только одна строка и на малой яркости. Далее по достижению Макс яркости первой строки начинать «розжиг» второй?
И первую строку допустим выполнить «теплыми» LED?

Coder007
13.06.2026 13:10Раза 3 перечитал, особенно начало. Особенно про аппаратную часть, функции, PID регулирование. Думал крышей поехал и мне кажется. Ан нет, не поехал.
Автор,у тебя такой потенциал! Потенциалище! А ты его тратишь на изготовление никому ненужных вещей! Поверь, даже самые топовые производители автомобилей никогда не будут использовать такие решения. Это не эффективно, усложнение и удорожание и просто незачем.
Направь свою энергию в буст технологий, для очень полезных и нужных вещей. Я посмотрел твои проекты, мне стало очень интересно. Много проектов, много интересных решений, много энергии, только все в мелочи. Ты как будто боишься сделать что-то достойное, мощное и крупное. Не разменивайся на мелочи.
Я делал раньше так-же, раздавал советы, делился знаниями, а сам ничего, из того что знаю и умею, не реализовывал. Или делал это на общество и бесплатно, а сейчас те, кому я давал информацию живут прекрасно, а мне приходится довольствоваться тем, что осталось.
Время уходит быстро и незаметно.

aabzel Автор
13.06.2026 13:10Автор,у тебя такой потенциал! Потенциалище! А ты его тратишь на изготовление никому ненужных вещей!
Спасибо, но что Вы предложили бы мне смастерить?
Forbruligo_de_vatnikoj
Мне бы пригодился такой диммер, но на ~230В, и, желательно, без микроконтроллера на борту
Mixael_sas
Тиристорный плавный пуск. Только не надо брать "устройство плавного пуска" — огромная наценка за корпус. Я взял в своё время просто тиристор и подключил его в корпус розетки управляемого устройства (скважинного насоса). Пять лет полёт нормальный.
Forbruligo_de_vatnikoj
Плавного отключения простым тиристорным не добиться же? В этом случае нужно ещё в обратную сторону уменьшать рабочую фазу полуволны
Mixael_sas
Отключения? Хм, видимо, нет. Собственно, потому и плавный пуск