Пролог
У меня было множество вело фар и всегда меня раздражало то, что фара включается практически мгновенно. Глаза даже не успевают приспособиться и это доставляет существенный дискомфорт. При этом на работе я много лет как раз разрабатывал 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 от времени
Управление заполнением PWM от времени

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

Управление яркостью на низком уровне

Второй вопрос, что выбрать для непосредственного регулирования яркости: 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 (или электронная парта)

https://habr.com/ru/articles/988494/

Delta-sigma modulation

https://en.wikipedia.org/wiki/Delta-sigma_modulation

Дельта-сигма модуляция или PDM метод кодирования сигнала

https://vpayaem.ru/inf_delta_sigma.html

Конечный автомат инкрементного энкодера

https://habr.com/ru/articles/1044930/

Сборка драйвера LED с управлением от энкодера jz_f407vet6_encoder_lamp_gcc_m

https://github.com/aabzel/Artifacts/tree/main/jz_f407vet6_encoder_lamp_gcc_m

“Сигма дельта” или как сделать хорошую звуковую карту из STM32F401 @Sdima1357

https://habr.com/ru/articles/662141/

Конечный автомат инкрементного энкодера

https://habr.com/ru/articles/1044930/

Обзор микросхемы DRV8870 (или Драйвер H-моста)

https://habr.com/ru/articles/952484/

Декодирование IR сигнала с TV (или исследование пультовых лучей)

https://habr.com/ru/articles/950764/

PID (ПИД) без математики: как просто понять P, I и D @RYAZHENKA-11

https://habr.com/ru/articles/1000846/

Python: Построение графиков по данным из файла

https://habr.com/ru/articles/748282/

Вопросы:
1) Как при помощи PID регулятора сделать S-образный переходной процесс?
2) Как избавиться от мерцания при PDM управлении свечением?
3) Существуют ли в продаже миниатюрные отладочные платы с STM32, GPIO разъемами и драйвером hi-side ключей, чтобы управлять автомобильными LEDами.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
У вас есть вело фара?
41.67%да15
58.33%нет21
Проголосовали 36 пользователей. Воздержались 3 пользователя.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вам пригодилась бы вело фара с плавным пуском?
27.5%да11
72.5%нет29
Проголосовали 40 пользователей. Воздержались 2 пользователя.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вам пригодилась бы вело фара с управлением от TV пульта?
7.69%да3
92.31%нет36
Проголосовали 39 пользователей. Воздержались 2 пользователя.
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Вам пригодилась бы вело фара с регулировкой яркости энкодером?
37.84%да14
62.16%нет23
Проголосовали 37 пользователей. Воздержались 3 пользователя.