Это ещё один маленький домашний DIY (апгрейд гирлянды) на, практически, самом младшем из младших микроконтроллеров из линейки ATtiny - на ATtiny10. Эти МК уже прошли пик своей популярности, лучшие характеристики можно найти за те же деньги в других МК... да что там характеристики, уже ядра в штуках раздают! (ESP32, например).

Но именно этот МК мне нравится сочетанием функциональности (64-битный таймер, АЦП, ШИМ, широкий диапазон питания), удобства разработки и, главное, размера! На хабре есть статьи о проектах на этом МК, последнее, что я видел - это вольтметр, ссылка в конце статьи. В общем для мелкого DIY самое то. Смотрите:

  • Это все находится корпусе SOT-23-6 размером 3x2.7x1 мм. Буквально со спичечную головку. Но при этом паять можно без ухищрений.

  • Производительность - может лупить на 12МГц (если, конечно, придумаете что-то полезное на килобайт прошивки).

  • Для запуска не нужен никакой обвес, кроме напряжения от 1.8В - это любая батарейка/две с напряжением в 3В. Внутренний тактовый генератор присутствует, последовательности сброса при запуске не нужны.

  • Таймер, 2 канала ШИМ, 4 АЦП, аналоговый компаратор, вочдог, разные режимы энергопотребления - всё это позволяет решать широкий спектр задач.

  • Простая разработка без ардуинов: поддержка всех возможностей открытыми решениями - библиотека avr-libc, сборка avr-gcc, программирование avrdude и сторублёвый программатор usbasp, среда разработки VSCode c плагином.

  • Цена - 110р в ЧипиДипе, в других местах можно за 60р при покупке больше десятка.

Мне даже кажется я где-то видел статью о запуске TinyML на этом МК... шутка)

В общем лежали эти "тиньки" у меня в коробке и однажды под новый год в квартире появилась эрзац-ёлка - несколько еловых лап в вазе, обмотанных простой одноцветной гирляндой из 100 белых smd-диодов и отсеком на 3 АА батарейки.

И не то, чтобы это сразу требовало доработки, смотрелось довольно красиво. Но эти 100 мелких светящихся засранцев наваливались на батарейки так, что потребление было около 1А. Сколько работал первый набор батареек я не засёк, перешёл на аккумуляторы, но и их выносило за 2-3 часа. Они, блин, даже заряжаться не успевали! Это всё безобразие, естественно, пробудило Дух DIY и дальше я уже себе не подчинялся.

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

Hardware

Итак, гирлянда состоит из двух проводов в лаке и 100 диодов висящих на них параллельно, ограничения тока нет, видимо расчёт на то, что на 100 штук диодов (плюс их прямое сопротивление в открытом состоянии) батарейка не отдаст больше чем может и будет это как раз 10-20мА. Для создания разных эффектов "тинька" должна коммутировать ток около ампера и для этого подошёл полевичок FDN337N также в корпусе SOT-23. Тоже очень удобный для всяких мелких батареечных поделок транзистор c достаточным током сток-исток, низким порогом открытия, низким сопротивлением канала и высоким напряжением сток-исток и стоимостью 6р в ЧипиДипе. Смотрите:

FDN337N
FDN337N
FDN337N
FDN337N

Схема этого апгрейда получается простая. Микроконтроллер, транзистор, затворный резистор, конденсатор на питание и ещё один резистор для того, чтобы понизить ток гирлянды. Подбирал его опытным путём так, чтобы не эстетика не пострадала. 10 Ом получилось.
Также добавил гребёнку для программирования и джампер. Дело в том, что выход ШИМ-каналов так или иначе совпадает с пинами для программирования и цепи схемы могут мешать при программировании. Не знаю как там помешала цепь, которая упирается через резистор в базу полевика, ёмкость маленькая, ток никуда не течёт... кто знает почему - расскажите. А я просто поставил джампер который снимается когда подключаем программатор. Хедеров J2 и J3 на самом деле не будет, они нарисованы ради площадок, такое вот костыльное решение придумано в EasyEDA.

Рисовал в EasyEda
Рисовал в EasyEda

Однослойную лутовую плату размером 12х14мм сделал под размеры свободного пространства в батарейной коробке.

Рисовал в EasyEDA. Прошу прощения за прямой угол дорожки к GND U1. Электроны в этом месте по инерции выскакивают наружу и с этим приходится мириться.
Рисовал в EasyEDA. Прошу прощения за прямой угол дорожки к GND U1. Электроны в этом месте по инерции выскакивают наружу и с этим приходится мириться.

Переходим к практике с традиционными ошибками любителя. Принтерутюгдремельтравлениедремельпаяльник. С первого раза не получилось - забыл отзеркалить при печати и понял это только когда уже вытравил (перекись водорода, соль, лимонная кислота).
Принтерутюгдремельтравлениедремельпаяльник. После второго подхода нашёл ещё косяк - когда на схеме перемещал и крутил транзистор, не заметил как сток и исток поменялись местами ))) Поправил наживую. На такую мелочь - как неправильная площадка для конденсатора я уже даже не обратил внимание (сделал под 0805, но у меня таких не оказалось).
Стараюсь не думать о более сложных платах которые у меня в плане.
После того, как всё заработало, новый год прошёл и гирлянда уехала в коробку в шкаф, я подрихтовал в редакторе плату: перевернул опять транзистор, добавил место для резистора который ограничивает ток гирлянды. Ну просто из перфекционизма. Этот финальный вариант как раз на картинке выше.

Вот такой результат получился (немного отличается, как я выше написал). Ещё один момент - для принтера не стояли родные драйверы и качество выше 300dpi не поднималось. Из-за этого у некоторых дорожек видна пила по краю.

Software

Теперь возвращаемся на родную территорию - программирование)

Прогаем в VSCode с плагином AVR Helper. Плагин добавляет несколько удобных действий типа build и flash с правильными вызовами avr-gcc и avrdude для данного МК. Установку/настройку не описываю, потому что если просто поставить VSCode, плагин и выполнить действия из его инструкции, то сразу всё получится.

Наш проект состоит из одного файла - main.cpp. Ссылка на код на гитхабе ниже, кому понадобится - используйте.

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

Зависимости только от avr-libc. Все функции/константы/макросы не объявленные явно в коде, объявлены в этих хэдерах:

#include <avr/io.h>
#include <avr/power.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>

Эффекты описаны декларативно, интерпретатор будет дальше. Одна строка - один эффект.

  • Начальное состояние первого эффекта - выключено.

  • Ноль - конец последовательности.

  • Если число положительное, то это переход во включенное состояние, иначе в выключенное.

  • Если число меньше 128, то это мгновенный переход в состояние, иначе плавный через ШИМ в течение n миллисекунд.

  • Если знак очередного числа равен предыдущему, то это просто задержка.

Пример: 2000, 1000, -1, -500, 1, 500, -1, -500, 1, 500, -1, -500, 1, 1000, -2000

плавно в течение 2 секунд включили, подождали секунду, мигнули 3 раза (по 500 мс), подождали секунду, плавно в течение 2 секунд выключили.

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

const int * const patterns[] = {
    (const int16_t[]){500, -500, 500, -500, 1, 50, -1, -50, 1, 50, -1, -50, 1, 50, -3000, -2000, 0},
    (const int16_t[]){1, 500, -1, -500, 0},
    (const int16_t[]){4000, -1, -2000, 0},
    (const int16_t[]){1, 250, -1, -250, 0},
    (const int16_t[]){500, -500, -3000, 0},
    (const int16_t[]){1, 50, -1, -50, 0},
    (const int16_t[]){1, -4000, -2000, 0},
    (const int16_t[]){2000, -1, -100, 1, 100, -1, -100, 1, 100, -1, -100, 1, 100, -1, -100, 1, -2000, -3000, 0},
    (const int16_t[]){1000, -1000, 0}
};

Код инициализации таймера и ШИМ. Привожу его сразу полностью, потому что остальной код зависит от него: тактирование выставляем от внутреннего генератора 128КГц, ограничиваем счётчик таймера значением 128, разрешаем прерывания по таймеру и получаем вызов нашего обработчика один раз в миллисекунду. Теперь удобно отсчитывать задержки.

static void setup()
{
    // Уменьшение энергопотребления для режима Idle
    // Отключение ADC
    power_adc_disable();

    // Отключение analog comparator
    ACSR ^= ~_BV(ACIE);
    ACSR |= ACD;

    // Установка тактирования на Internal 128 kHz Oscillator
    CCP = 0xD8;
    CLKMSR = 0b01;
    CCP = 0xD8;
    CLKPSR = 0;

    // Настройка ШИМ на порту PB0
    DDRB |= _BV(DDB0);

    cli();

    TCCR0A = 0;
    TCCR0B = 0;

    // Режим - FastPWM 8 bit, вывод на OC0A, частота - system clock, TOP=128
    TCCR0A = _BV(COM0A1) | _BV(WGM01);
    TCCR0B = _BV(CS00) | _BV(WGM02) | _BV(WGM03);
    ICR0 = 128;
    OCR0A = 0;

    sei();

    // Переполнение таймера также отсчитывает системные тики (1мс)
    TIMSK0 |= _BV(TOIE0);
}

Код для отсчёта времени и организации задержек не потребляющих ресурсы: функция, реализующая задержку засыпает, просыпается каждую миллисекунду (потому что прерывание выводит проц из сна), проверяет нужно ли ещё спать и всё повторяет. Мне было важно это реализовать, потому что весь сыр-бор начался как раз из-за очень короткого времени работы гирлянды от одного заряда аккумуляторов/комплекта батареек. Не было бы смысл всё это начинать если не приложить максимальные усилия.

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

// Счётчик системных тиков
volatile uint16_t ticks = 0;

// Обработчик прерывания
ISR(TIM0_OVF_vect)
{
    ++ticks;
}

// Задержка с использованием sleep mode. Каждое переполнение таймера будит цикл.
void sleep(const uint16_t & delay)
{
    uint16_t end = ticks + delay;

    do
    {
        set_sleep_mode(SLEEP_MODE_IDLE);  
        sleep_enable();
        sleep_cpu(); 
        sleep_disable();
    } 
    while (end > ticks);
}

Основной цикл. Места во флэше вроде хватало, поэтому решил реализовать линейную яркость. То есть компенсировать нелинейное восприятие зрением различных значений яркости. Без этого казалось, что при плавном включении светодиоды слишком быстро проходят низкие значения яркости и долго зависают в ярком состоянии, где изменение между соседними уровнями практически не видно на глаз. Для этого просто чуть быстрее пробегаем промежуток где диод уже слишком ярок.

Итак, перебираем все эффекты (в коде - "паттерны") в бесконечном цикле, каждый эффект крутим 30 секунд. Реализация эффекта резкого включения/выключения простая - сразу втыкаем минимальный или максимальный период ШИМ. На минимальном уровне светодиоды еле светятся и не гаснут полностью. В темноте это даже прикольно.

// Каждый паттерн проигрывается 30 сек
const uint16_t PATTERN_DURATION = 30000;

// В массиве находятся точки ускорения/замедления, после которых шаг приращения периода увеличивается/уменьшается на 1.
// Первый и последний элементы - ограничительные.
const uint8_t accelerationPoints[] = {0, 30, 60, 80, 100, 255};

int main()
{
    setup();

    // Текущее состояние, true = включено, false = выключено
    bool state = false;

    while (true)
    {   
        // Перебираем все паттерны по очереди
        for (uint8_t i = 0; i < sizeof(patterns)/sizeof(patterns[0]); i++)
        {
            // Сбрасываем счётчик системных тиков, которого хватает на 65 сек (1кГц*16бит) чтобы не попасть на переполнение
            ticks = 0; 

            // Каждый паттерн крутим в течение PATTERN_DURATION
            do 
            {
                // Перебираем все ноты
                for (uint8_t j = 0; patterns[i][j] != 0; j++)
                {
                    int16_t delay = patterns[i][j];
                    bool sign = true;

                    // Сохраняем знак и оставляем модуль задержки
                    if (delay < 0)
                    {
                        delay = -delay;
                        sign = false;
                    } 

                    // Если текущее состояние не изменяется, то просто спим
                    if (state == sign)
                    {   
                        sleep(delay);
                    }
                    // Иначе переключаем состояние
                    else
                    {
                        // Быстро
                        if (delay < 128)
                        {
                            setPwmDutyCycle(sign ? 127 : 0);
                        }

Для плавного включения/выключения нам нужно перебрать все (на самом деле не все) значения периода ШИМ от минимального до максимального (или наоборот) и немного поспать на каждом значении. То есть от 0 до 128. Это в теории.

А на практике используется всего 64 уровня яркости. И вот почему: если делать нелинейную яркость, то надо как-то укоротить более яркие состояния... или сделать таких состояний меньше. Использую второй способ. Задержки на каждом шаге всегда одинаковые, но когда мы двигаемся по "более ярким" значениям периода ШИМ, то начинаем пропускать некоторые уровни.

В массиве accelerationPoints как раз обозначены значения после которых шаг начинает ускоряться. Именно поэтому несмотря на то, что цикл идёт по 128 периодам ШИМ, шагов на самом деле меньше (30 шагов от нуля до 30, 15 шагов от 30 до 60, 7 шагов от 60 до 80, 6 шагов от 80 до 105, 3 шага от 105 до 128... примерно:)) В коде выбрано число 64 для количества шагов, это оптимизации кода, чтобы деление выполнялось сдвигом (оптимизация по размеру - отдельная песня, во время которой я порвал баян).

                        // Или плавно
                        else
                        {
                            // Используется 64 шага для плавного включения/выключения с помощью ШИМ. 
                            // Так как задержки не делятся нацело на 64, сохраняем остаток от деления для поправки задержек
                            uint8_t remainder = delay % 64;

                            // Шаг приращения периода ШИМ меняется из-за нелинейной зависимости яркости от заполения
                            uint8_t step;

                            if (sign)
                            {
                                // Если включаемся, то начинаем делать это медленно
                                step = 1;
                            }
                            else
                            {
                                // Если выключаемся, то начинаем делать это быстро. 
                                // Максимальный шаг равен кол-ву точек минут ограничительные элементы 
                                step = sizeof(accelerationPoints) / sizeof(accelerationPoints[0]) - 2;
                            }

                            // Проходим 128 ступеней ШИМ с задержкой delay/64 на каждой итерации и прыгая через step ступеней
                            for (uint8_t k = 1; k < 128; k += step)
                            {
                                uint8_t value;

                                if (sign)
                                {
                                    value = k - 1;

                                    // Реагируем на точки ускорения
                                    if (value >= accelerationPoints[step])
                                    {
                                        ++step;
                                    }
                                }
                                else
                                {
                                    value = 128 - k;

                                    // Реагируем на точки замедления
                                    if (value <= accelerationPoints[step - 1])
                                    {
                                        --step;
                                    }
                                }

                                // Устанавливаем период ШИМ для данной итерации
                                setPwmDutyCycle(value);

При расчёте задержек есть фишка: в этом МК нам доступны только целочисленные арифметические операции и поэтому легко набегают ошибки. Для задержек меньше 64мс простым делением на 64 мы получим 0 и всё сломается. И даже если задержка больше, чем 64, то всё равно можно получить значительное ускорение если отбрасывать остаток от деления. Например, при задержке в 125мс, при потере остатка от деления (125/64=1,953125) мы потеряем 61 миллисекунду. Короче говоря, задержки станут кратны 64. Поэтому сохраняем остаток от деления (он получается в миллисекундах) и потихоньку размазываем его по всем шагам.

Мне кажется, красивое решение.

                                // Учитываем "потерянное" на целочисленном делении время сна
                                if (remainder)
                                {
                                    --remainder;
                                    sleep((delay / 64) + 1);                                
                                }
                                else
                                {
                                    sleep(delay / 64);                                
                                }
                            }
                        }

                        state = sign;
                    }
                }
            } while (ticks < PATTERN_DURATION);
        }
    }
}

Вот и всё. Хотя, как я написал выше, я довольно много времени потратил на оптимизацию по размеру. Потому что реально не влезал в 1024 байта. А хотелось закодить побольше разных эффектов, иначе зачем всё это было начинать. Смотрел на дизассемблированный листинг, искал выражения, которые занимают меньше машинного кода, перебирал типы, смотрел на размер кода арифметических операций и т.д. Должен сказать, что изначально код выглядел совсем по-другом (но с тем же смыслом).

Но теперь, всё оптимизировано и отлажено, собираем кнопкой Build в VSCode:


 *  Executing task: AVR Helper: ???? Sun Aug 06 2023 00:05:28 GMT+0300 (heure normale de Moscou) 

Working directory: /Users/tigreavecdesailes/Projects/attiny10_guirland

Command: /opt/local/bin/avr-gcc -MM -mmcu=attiny10 -DF_CPU=128000UL -I/opt/local/avr/lib/ -I/opt/local/avr/lib/avr3/ -I/opt/local/avr/lib/avr31/ -I/opt/local/avr/lib/avr35/ -I/opt/local/avr/lib/avr25/ -I/opt/local/avr/lib/avr6/ -I/opt/local/avr/lib/avr51/ -I/opt/local/avr/lib/avr4/ -I/opt/local/avr/lib/avrtiny/ -I/opt/local/avr/lib/avrxmega4/ -I/opt/local/avr/lib/avrxmega2/ -I/opt/local/avr/lib/avrxmega5/ -I/opt/local/avr/lib/avrxmega7/ -I/opt/local/avr/lib/avrxmega6/ -I/opt/local/avr/lib/tiny-stack/ -I/opt/local/avr/lib/avr5/ -I/opt/local/avr/lib/ldscripts/ -I/opt/local/avr/lib/avr25/tiny-stack/ /Users/tigreavecdesailes/Projects/attiny10_guirland/main.cpp
✅ Compilation

Command: /opt/local/bin/avr-gcc -mmcu=attiny10 -DF_CPU=128000UL -ffunction-sections -fdata-sections -pipe /Users/tigreavecdesailes/Projects/attiny10_guirland/build/obj/Users/tigreavecdesailes/Projects/attiny10_guirland/main.cpp.obj -o /Users/tigreavecdesailes/Projects/attiny10_guirland/build/output.elf
✅ Linkage

Command: /opt/local/bin/avr-objdump --disassemble --source --line-numbers --demangle /Users/tigreavecdesailes/Projects/attiny10_guirland/build/output.elf
✅ Disassembling

Command: /opt/local/bin/avr-size -G /Users/tigreavecdesailes/Projects/attiny10_guirland/build/output.elf
      text       data        bss      total filename
       782        156          2        940 /Users/tigreavecdesailes/Projects/attiny10_guirland/build/output.elf

✅
 *  Terminal will be reused by tasks, press any key to close it. 

940 байт. В запасе ещё 64 байта, можно ещё реализовать блэкджек, да лениво.
Теперь непрерывное время работы гирлянды от 3-х Ni-Mh аккумуляторов - больше 4 суток.

Весь код и экспорты из EasyEda (схема и герберы) выложил на гитхабе, может кому-то пригодится.

В железной части я совсем любитель, критику только приветствую.
В софтовой части я совсем не любитель... (ну вы поняли).

Ссылки (есть в статье, но тут отдельно):

ATtiny в ЧипиДип

FDN337N в ЧипиДип

avr-libc

avr-gcc (нет, из России не заблокировано)

avrdude https://www.nongnu.org/avrdude/

Расширение AVR Helper к VSCode

Недавние статьи на Хабре про ATtiny10:

Меньше точно не бывает! Делаем вольтметр на ATTINY10
Я подарю тебе маленькую вселенную

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


  1. Grey83
    08.08.2023 06:59

    Но эти 100 мелких светящихся засранцев наваливались на батарейки так, что потребление было около 1А. Сколько работал первый набор батареек я не засёк, перешёл на аккумуляторы, но и их выносило за 2-3 часа.
    Я с такими гирляндами поступил проще: припаял 3мм гнездо под трубчатый штекер параллельно крайним контактам батареек и подключал к старому нокиевскому ЗУ (туда на конец провода соответствующий штекер припаял, благо я давно штекеров и гнёзд таких пару десятков для самоделок покупал).
    Т.е. можно отключать от ЗУ и снова вставлять батарейки.
    С другой такой гирляндой сделал ещё проще: припаял таким же образом гнездо microUSB и подключаю старое ЗУ на 700мА через стандартный шнур.

    P.S. Напротив гнёзд, естественно, в корпусе сделаны отвертстия под соответствующие штекеры. Ну и сами гнёзда зафиксированы термоклеем.


  1. Soorin
    08.08.2023 06:59

    Вот странно - и микроконтроллер на 12 МГц, и мощный полевик - и тут 10 Ом балластный резистор... Какой-то фейспалм.


    1. tigreavecdesailes Автор
      08.08.2023 06:59

      Почему фейспалм?


      1. Grey83
        08.08.2023 06:59
        +1

        Потому что при использовании ШИМ можно обойтись вообще без балластных сопротивлений?


        1. SuperTEHb
          08.08.2023 06:59
          +3

          Импульсные токи большие могут быть, лучше уж так.


        1. Holix
          08.08.2023 06:59
          +1

          По моему, пусть лучше лишний резистор, но полный диапазон значений ШИМ и уверенность в значении максимального тока потребления. Да и программировать так проще.


          1. tigreavecdesailes Автор
            08.08.2023 06:59

            Во, точно! Смысл моей телеги в двух предложениях)


        1. tigreavecdesailes Автор
          08.08.2023 06:59
          +1

          Не знаю как обойтись. Ведь светодиод - это токовой прибор, тут ШИМ ведь не используется для управления напряжением, а используется для управления яркостью только благодаря интегратору яркости в человеческом глазе) А ток в моменты открытия транзистора течёт максимальный возможный для этой цепи. Без резистора, 1-2 ампера, которые выдает батарейный блок распределяются поровну и каждый диод получает 10-20 мА - это, зачастую, для таких диодиков максимальный ток и большим можно их спалить (например, если попадутся качественные батарейки, которые отдадут больше чем 2А).

          Но даже если максимальный ток без резистора не превысит критический для диодов, светиться они будут сильно ярче. И мне придётся использовать от всего возможного диапазона ШИМ периодов только, скажем, половину, чтобы эту яркость обуздать. А чтобы при этом сохранить плавность включения пришлось бы тогда совсем другой расклад по настройке таймера придумывать.

          Но, соглашусь, что резистор на фото некрасивый ))) Поэтому я разводку подрихтовал, нашёл место для smd.


          1. agalakhov
            08.08.2023 06:59
            +1

            Поставьте не резистор, а дроссель, сделайте buck-преобразователь.


            1. tigreavecdesailes Автор
              08.08.2023 06:59

              И что, при этом между анодом и катодом диода будет разность потенциалов не меньше 3 вольт и при этом ток в наноамперы?


              1. tigreavecdesailes Автор
                08.08.2023 06:59

                Хотел сказать "в сотни микроампер".


        1. sim2q
          08.08.2023 06:59
          +1

          По нормальному - ставят LC фильтр, получается полупрограммный dc/dc.
          Даже уж на что китайцы экономные, но ставят такое иногда даже на платки плавной регулировки скорости вентилятора.


          1. SuperTEHb
            08.08.2023 06:59

            Тоже выход. Но тогда стоит поставить обводной диод вдобавок к транзистору и задрать частоту повыше для уменьшения габаритов дросселя. А тут уже, скорее всего, затвор полевичка микроконтроллер уже не раскачает, понадобится драйвер какой-нибудь... Зато можно будет легко умощнить всё.

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


          1. tigreavecdesailes Автор
            08.08.2023 06:59
            +3

            Это если мы регулируем напряжение. А тут диоды, они работают на постоянном значении тока. ШИМ только для управления соотношением времени включено/выключено в секунду.

            Есть модное слово dc-dimming, но и там ограничения. Менять рабочий ток светодиода без нюансов относительно яркости/цвета можно только вверху рабочего диапазона - от максимального до минимального по даташиту. Для этого, действительно, можно проинтегрировать ШИМ-сигнал так чтобы полученное напряжение давало нужный ток. Но как только мы приблизимся к границе минимального тока через диод, начнутся проблемы связанные с разбросом характеристик каждого диода. Появятся разные оттенки, какие-то будет ярче, какие-то темнее. И при этом средний уровень будет всё равно слишком яркий. Поэтому в смартфонах, где этот dc-dimming есть, на низких значениях яркости опять включается ШИМ,

            Вот пример того как светится светодиодная лента на минимальном токе без ШИМ и как разброс характеристик диодов виден вживую.


            1. SuperTEHb
              08.08.2023 06:59
              +1

              Бесплатный дополнительный эффект для гирлянды!


              1. tigreavecdesailes Автор
                08.08.2023 06:59

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

                И даже хуже, по моему опыту это произойдёт уже примерно на половине дистанции между "не светит" и "светит максимально ярко".


                1. evtomax
                  08.08.2023 06:59

                  У меня при последовательном соединении светодиодов (плата со светодиодами из лампы Remez) DC-димминг отлично работает от еле заметного свечения до яркости, достаточной для настольной лампы. Только ШИМ пришлось делать 10 бит и заморочиться с самодельным драйвером, который реагирует на ШИМ примерно по экспоненте.


                1. sim2q
                  08.08.2023 06:59

                  Минимальной яркости так не добиться.

                  кстати да, согласен, но у вас же целых 64 байта ещё!)
                  можно сделать ШИМ второго порядка когда будем не просто двигать скважность, а ещё и нормированными по току импульсами)


        1. agalakhov
          08.08.2023 06:59
          +2

          Нельзя. Дело не в яркости, а в регулировке пикового тока. При использовании ШИМ можно заменить резистор дросселем и диодом, сделав фактически buck-преобразователь, но совсем выкидывать нельзя. Работать-то будет, но срок службы светодиодов снизит резко. В дешевых длинных гирляндах, где светодиоды просто воткнуты последовательно без регулировки тока, летит по одному светодиоду за сезон.


    1. Nikita_64
      08.08.2023 06:59

      мощный полевик

      Подскажите, как правильно оценить мощность полевика: если смотреть на Figure 9. Maximum Safe Operating Area в datasheet, то при 3.6-4В при длительности нагрузки 1с (примерно как горит гирлянда на максимуме) ток не должен превышать 0.6-0.7А. Исходя из этого нужно выбирать резистор (или рассчитывать ШИМ с соответствующей скважностью). Это правильные рассуждения?


      1. SuperTEHb
        08.08.2023 06:59
        +2

        то при 3.6-4В

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

        Пускай на затворе будет 2,5 вольт, тогда номинальное сопротивление канала получится 70 мОм. То есть при токе 1 Ампер получим 70 мВ на транзисторе и выделившуюся мощность 70 мВт. Совсем немного.


        1. Nikita_64
          08.08.2023 06:59

          Спасибо, стало ясно


  1. Grey83
    08.08.2023 06:59

    Можно почитать статьи со схемами драйверов для фонариков всяких (вроде тут не единожды были, ещё можно на муську и фонарёвку заглянуть, ну и ещё есть другие железнячные сайты/форумы).

    Не нашёл в статье информации о напряжении питания схемы, но судя по характеристикам тиньки — это максимум 5В. ЗУ от мобилки, что ли будет в качестве БП?
    Если это так то ток будет не таким уж и большим (особенно если БП не слишком мощный). Тем более, что я выше писал, что я такую же гирлянду запитал напрямую от ЗУ на 700мА (но ограничительный резистор оставил в схеме, т.к. у меня нет никакой регулировки по току).

    Ну а максимальную скважность можно же сделать зависимой от напряжения питания или тинька такое не умеет определять?


    1. tigreavecdesailes Автор
      08.08.2023 06:59

      Она же на батарейках :) Изначально гирлянда рассчитана на 1.5Вх3=4.5В. Я использую 1.2В Ni-Mh аккумуляторы, получается 3.6В.

      Ёлок подключенных к розетке я иррационально очень боюсь:)) Поэтому использую только батареечные гирлянды. Но в целом, наверное, ЗУ от мобилки будет лучше, чем дешёвый комплектный китайский БП без развязки.

      Чтобы программно сделать скважность зависимой от напряжения, нужно замерить напряжение (не помню чтобы у тиньки можно было это значение прочитать из регистров). Это ещё несколько деталей на делитель и код для замера. В килобайт не влезет. Как и на одну сторону такой платки)


      1. Grey83
        08.08.2023 06:59

        Она же на батарейках
        И?
        Светодиоды, по идее рассчитаны на 3В питания.

        Вообще чем выше напряжение питания, тем больше ток потребляет светодиод. ШИМ же позволяет сделать так чтобы светодиод не успевал сгореть от превышения тока.

        И вообще проще было сделать контроллер тока на основе копеешной платки для зарядки Li-Ion акукумуляторов, в которых используется микросхема TP4056. Только добавить к штатному токозадающему резистору (который задаст максимальный ток в 1000мА; если нужен меньший ток, то нужно будет последовательно с ним установить ещё один или просто заменить резистор 1.2кОм на более подходящий номинал) последовательно подстроечный на номинал до 30кОм (при номинале в 30кОм на выходе платы будет максимальный ток 50мА).
        Вот формула расчёта зарядного тока из даташита:


        В общем: можно выкинуть к чертям токоограничительный резистор и вместо него задействовать такую платку (если купите с защитой от переразряда, то спаренные полевики FS8205A можно будет задействовать вместо используемого в схеме, благо они токи и в 3А выдержат).


        1. SuperTEHb
          08.08.2023 06:59
          +1

          У неё минимальное входное заявлено 4 Вольта. Такой манёвр отлично проходит при питании от USB.


          1. Grey83
            08.08.2023 06:59

            Так ведь она и предназначена для питания от USB.


            1. SuperTEHb
              08.08.2023 06:59
              +1

              Тогда зачем советуете её при батарейном питании?


              1. Grey83
                08.08.2023 06:59

                При батарейном и резистор не особо нужен.
                Кроме того светодиодам важнее величина тока, а не напряжения (если ограничить ток, то повышение напряжения ему до определённого порога не страшно).
                Т.е. можно поднять напряжения питания: т.к. использование обычных пальчиковых батареек не рационально (слишком маленькая ёмкость при сравнительно высокой цене) и аккумуляторы очень быстро окупят себя, то можно задействовать даже 2 аккумулятора последовательно (микруха держит до 8В, хотя проще задействовать повербанк).

                Вообще я такую же гирлянду использую с питанием от ЗУ или повербанка.
                Переделку описал выше. =)


        1. LampTester
          08.08.2023 06:59
          +4

          ШИМ же позволяет сделать так чтобы светодиод не успевал сгореть от превышения тока.

          Подход, достойный Ардуино. :)))

          Нет. Нормальная схема должна гарантированно обеспечить штатный режим. Так что в данном случае резистор - самый адекватный выход. Городить что-то более серьезное смысла тут нет.


  1. dontsov
    08.08.2023 06:59

    Я не знаю, может это компилятор оптимизирует, но деление на 64 можно записать как delay >> 5

    Я продлил время жизни гирлянды припаяв резистор последовательно...


    1. tigreavecdesailes Автор
      08.08.2023 06:59

      Да, компилятор довольно агрессивно оптимизирует, и если видит умножение/деление на степень двойки, то подсказок не просит:)


  1. LampTester
    08.08.2023 06:59
    +1

    const int * const patterns[] = {
        (const int16_t[]){500, -500, 500, -500, 1, 50, -1, -50, 1, 50, -1, -50, 1, 50, -3000, -2000, 0},
        (const int16_t[]){1, 500, -1, -500, 0},
        (const int16_t[]){4000, -1, -2000, 0},
        (const int16_t[]){1, 250, -1, -250, 0},
        (const int16_t[]){500, -500, -3000, 0},
        (const int16_t[]){1, 50, -1, -50, 0},
        (const int16_t[]){1, -4000, -2000, 0},
        (const int16_t[]){2000, -1, -100, 1, 100, -1, -100, 1, 100, -1, -100, 1, 100, -1, -100, 1, -2000, -3000, 0},
        (const int16_t[]){1000, -1000, 0}
    };

    А версия AVR-GCC, которую вы использовали, уже научилась не копировать константы в RAM при таком их объявлении?

    Классически, чтобы читать константы из FLASH, не копируя их в RAM, надо было использовать avr/pgmspace.h и макрос PROGMEM.

    Или вы сознательно решили пожертвовать частью RAM, чтобы не тратить FLASH на код загрузки из FLASH (pgm_read_xxx)?


    1. tigreavecdesailes Автор
      08.08.2023 06:59
      +3

      Это ещё одна причина по которой мне нравится ATtiny10 - у них флэш память отображается в адресное пространство, доступное обычным командам работы с RAM (ld*/st*) и поэтому не нужно использовать специальные макросы (которые превращаются в набор инструкций lpm для копирования из флэша в RAM).

      Компилятор сам кладёт const объявления во флэш и сам подправляет адреса при обращении к ним. В общем, руководствуется этим положением из даташита тиньки:

      Constant tables can be allocated within the entire address space of program memory by using load/store
      instructions. Since program memory can not be accessed directly, it has been mapped to the data
      memory. The mapped program memory begins at byte address 0x4000 in data memory. Although
      programs are executed starting from address 0x000 in program memory it must be addressed starting
      from 0x4000 when accessed via the data memory.


      1. LampTester
        08.08.2023 06:59

        Ооо! Ооо! Распаковать что ли свои запасы ATtiny10... Страшно сказать, я их лет пять назад покупал поиграться, и вот все никак руки пока не дошли...

        Зато я в рамках работы применял "новые" серии, ATtiny2xx. Очень приятные чипы, мне понравились.


        1. tigreavecdesailes Автор
          08.08.2023 06:59

          Если речь идёт о 8-pin корпусах, то, мне кажется, до сих пор среди тинек вне конкуренции ATtiny85 c 8Кб флэша. На ней можно реализовать usb-протокол и даже в сериале Mr.Robot Эллиот в одной из сцен задумавшись крутит в руках платку Digispark с этим МК которая будет клавиатурным жучком.


  1. NutsUnderline
    08.08.2023 06:59

    Джампер нужно было обозначить хотя бы каким нибуть пунктиром - а то не понятно.

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

    В более древних atmel есть протокол DW при котором и программирование и отладка выведены на RESET, т.е. больше свободных ножек. Но некоторые смелые искусники и RESET используют как GPIO, после чего перешить становиться затруднительно.

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

    Так что иногда стоит подумать, а не взять ли контроллер с числом ножек более 6 :)


  1. Gryphon88
    08.08.2023 06:59

    Маленький хинт для более аккуратных дорожек: лудите через оплетку, она лишнее соберет.


    1. tigreavecdesailes Автор
      08.08.2023 06:59

      С моим ЛУТом так не выходит. Слой тонера очень тонкий и возникают подтравы, в итоге дорожки с мелкой перфорацией) Приходится наступить на эстетику и некрасиво замазывать припоем.


      1. sim2q
        08.08.2023 06:59

        У меня жёлтая бумага с али, она сама по себе немного шершавая. Требуются небольшие ухищрения. Может помочь поднятие температуры при переносе на плату. А ещё такое было с отдельными кусками советского текстолита, у него как будто немного проступала структура ткани, даже шкурился неравномерно. Ещё слышал когда печатают для фотошаблонов - держат в парах ацетона, но этот вариант не пробовал.