Ардуина и светодиод, или как прокачать детский конструктор +58





Мой сын крепко “подсел” на магнитный конструктор Magformers. Однажды просматривая серию Фиксиков где фигурировал такой же конструктор ребенок спросил: “Папа, а почему у фиксиков детальки светятся, а у нас нет?”.

Оказалось, что действительно существует набор “Magformers Neon LED Set”, где помимо обычных строительных блоков есть еще и элемент со светодиодом. Поскольку к этому времени у нас уже собрался целый ящик магнитиков всех возможных форм и размеров (как по мне, китайский магформерс ничуть не уступает оригиналу), покупать еще один набор только ради лампочки как-то не хотелось. Тем более, что этот набор стоил ощутимо дороже аналогичного без подсветки.

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

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

Первые шаги


Мне показалось, что светяшка на обычном светодиоде (пускай даже RGB) это скучно и банально. А вот пощупать что нибудь вроде WS8212 показалось интересным. На ебее предлагались как отдельные светодиоды, так и матрицы размером до 16х16. Накупив несколько разных модулей свой выбор я остановил на матрице 4х4. В ней достаточно много светодиодов, чтобы побаловаться различными визуальными эффектами, при этом модуль сопоставим по размерам с окошком квадратного блока конструктора.



Для управления светодиодной матрицей достаточно всего одного пина микроконтроллера, так что даже ардуина нано выглядит как перебор (к тому же она не влезет в корпус). А вот клон digispark на контроллере ATTiny85 оказался в самый раз — в нем не очень много памяти и пинов, но более чем достаточно для светодиодной моргалки. Модуль отлично интегрируется с Arduino IDE и имеет на борту загрузчик по USB, поэтому программировать этот модуль очень просто и комфортно. Давно хотел его попробовать.

Начал с простейшей схемы.



В таком виде удалось достаточно быстро отладить все алгоритмы свечения/моргания (о них ниже). Но вот игрушка с проводным питанием это не дело — нужно подумать о питании от батарей. Причем чтобы не разорится на пальчиковых батареях (которые к тому же не влезают в габарит) решено было использовать литиевую. А раз есть литиевая батарея, то нужно думать как ее заряжать. В закромах как раз нашелся купленный по случаю “народный” контроллер заряда на микросхеме TP4056.

Только вот подключить его сразу не получилось. Схема модуля Digispark ATTiny85 не очень на такое рассчитана — там либо питание от USB, но тогда питание подается напрямую на микроконтроллер (по шине +5), либо от входа VIN, но тогда питание идет через линейный стабилизатор 7805. Вариант, когда модуль зарядки лития вставляется в разрыв между разъемом USB и микроконтроллером не предусмотрен. Пришлось доработать немного схему и выпаять лишние детали.



Так, теперь питание от USB поступает на ножку VIN и дальше уходит на вход зарядника. Выход зарядника (по сути аккумулятор подключается напрямую) заходит назад в плату через ножку 5V. И хотя на самом деле там будет от 3 до 4.2В (напряжение аккумулятора) это вполне нормально — диапазон рабочих напряжений микроконтроллера 1.8-5.5В. И даже светодиодный модуль нормально работает от 2.7В, хотя ниже 3.2В синему светодиоду немного не хватает и цвета немного “плывут” в желтый.

В целях экономии электроэнергии вечно горящий светодиод D2 я тоже выпаял. Общая схема теперь выглядит так



Питать схему можно было бы и через USB разъем в заряднике, но тогда бы потерялась возможность заливать прошивку через USB разъем на плате контроллера. Можно было бы оставить два USB разъема различного назначения — один для зарядки, другой для прошивки, но это как-то неправильно.

Аккумулятор размера 6х25х35 купил на ебее, но он оказался либо бракованный, либо я его убил коротким замыканием или большим током заряда (у платы по умолчанию ток заряда установлен в 1А и нужно перепаивать один резистор, чтобы уменьшить ток). В любом случае при подключении нагрузки даже в 10мА напряжение на аккуме падало до 1В. На время тестирования я переключился на полу-сдохшую LiPo батарею от мелкого квадрокоптера. Чуть позже заказал аккум у другого продавца и он оказался хорошим.

В принципе, на этом можно было бы и остановится, припаять соединительные провода и аккуратно затолкать все в какой нибудь корпус, но я решил измерить потребление схемы. И тут я прослезился. Ладно, что в рабочем состоянии (когда лампочки сияют на полную) эта штука жрет до 130мА, так в состоянии покоя потребление более 25мА! Т.е. мою батарею в 600мАч эта моргалка слопает менее чем за сутки!

Оказалось, что около 10мА потребляют светодиоды. Даже если они не светятся — в каждом из них все равно работает микроконтроллер и ожидает команду. Т.е. нужно придумать схему отключения питания светодиодам.

Оставшиеся 15 мА потребляет микроконтроллер. Да, его можно уложить спать и согласно даташиту потребление будет измеряться микроамперами, но на деле меньше 1 мА получить не удалось. Я и АЦП отключал и пины переводил в input. Похоже где-то в схеме есть какая-то утечка, но моих скромных познаний в электронике недостаточно, чтобы ее найти и понять.

Усложняем схему


Тут я вспомнил, что я себе купил на пробу микросхему PT1502. Эта микросхема — контроллер заряда литиевого аккумулятора в комплекте с источником питания с несколькими управляющими входами. Единственная сложность — микросхема идет в корпусе QFN20 размером 4х4 мм и требует некоторой обвязки. Паять такое дома сложно, но можно. Плата получается сложной для обычного ЛУТа и нужно заказывать у китайцев. Но мы ведь не боимся сложностей, правда?

В нескольких квадратиках схему можно описать так.



В выключенном состоянии питание на контроллер и светодиоды не поступает. У устройства есть кнопка ‘Power’, которая включает моргалку (она же переключает режимы). Светодиод сияет, скажем, минуту и если пользовательской активности нет (никто не нажимает кнопку), то устройство выключается. Т.е. не просто уходит в сон, а именно отключает само себе питание сигналом Power Hold. Причем отключает все сразу — и микроконтроллер, и светодиоды. Функциональность включения и отключения питания реализуется внутри микросхемы PT1502

Осталось всего ничего: нарисовать принципиальную схему и сделать плату. Схема, по большей части, слизана с даташита PT1502, а также модуля Digispark ATTiny85. Микросхема контроллера питания PT1502 функционально делится на несколько частей, потому на схеме разбита на блоки.



Это, собственно, контроллер заряда литиевой батареи со своей обвязкой. Светодиод LED1 показывает состояние заряда — горит, значит идет заряд. Резистор R6 задает ток заряда в 470мА. Поскольку у меня батарея на 600мАч, в принципе можно поднять ток и до 600мА поставив резистор на 780-800 Ом. Впрочем я не уверен в особом качестве моей батареи — пусть лучше заряжает медленнее, но дольше проживет.

Рассмотрим схему управления питанием



Кнопка SW1 запускает всю систему — микросхема PT1502 просыпается сама и затем запускает все источники питания (которых у нее 3). Когда питание установится микросхема запустит микроконтроллер, отпустив сигнал RESET. Для удобства отладки я еще добавил отдельную кнопку Reset.

Сигнал HOLD используется для выключения всей системы. Когда микроконтроллер запустится он должен выставить на этой линии единицу. Когда пора закругляться, микроконтроллер выставляет на линии HOLD ноль и микросхема питания PT1502 остановит все источники питания.

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

Вернемся на секунду к кнопке SW1. Я решил не делать 2 отдельные кнопки для включения и для управления. Поэтому та же кнопка подключена еще и к ATTiny85 и во время работы переключает режимы моргания. Номиналы делителя R7-R8 подобраны так, чтобы не спалить порт микроконтроллера PB2. При всех диапазонах напряжений батареи (3,3 — 4.2В) на ногу контроллера будет поступать напряжение в оговоренных даташитом пределах (0.7*VCC — VCC+0.5В)

Рассмотрим источник питания



Это импульсный DC-DC преобразователь. Напряжение на выходе задается резисторами R10-R11 и согласно формуле из даташита настроено на 3.3В. Все остальное — несложная обвязка.

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



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

Перейдем к логической части.



Обвязка USB слизана с платы Digispark без изменений. Это нужно для согласования напряжений USB (по которому бегает 3.3В) и сигналов микроконтроллера (который в оригинале питается от 5В). Поскольку в моем случае микроконтроллер также питается от 3.3В, то схему можно было бы и упростить, но на всякий случай я развел на плате оригинальную схему.



В обвязке микроконтроллера ничего интересного.

Финальный штрих это разъем



По сути у меня получилась такая себе отладочная плата на ATTiny85 с поддержкой USB и контроллером питания от литиевой батареи. Потому я не стал ограничиваться только выводом линии на светодиод. Вместо этого я вывел все линии микроконтроллера на гребенку — заодно и к программатору удобно подключать.

И пускай почти все линии жестко привязаны к определенному функционалу (PB1 — линия Hold, PB2 — кнопка включения, PB3/PB4 — USB, PB5 — Reset) в будущем можно будет в некоторых пределах обойти. Например, не распаивать обвязку USB и освободить линии PB3/PB4. Или, например, отказаться от ресета и освободить PB5. Ну а пока свободным остается только PB0 — к нему и подключим наш светодиод.

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



Для удобства использования на обратной стороне подписал все возможные функции выводов (идею слямзил с платы Digispark)



Плату заказывал на JLCPCB . Качеством, если честно, не очень доволен — если много раз перепаивать микросхему, то маска возле мелких контактов PT1502 чуток облазит. Ну и мелкие надписи немного поплыли. Впрочем, если все запаять с первого раза, то норм.

Для пайки QFN20 понадобится паяльный фен, все остальное можно при определенной сноровке запаять обычным паяльником. Вот так выглядит распаянная плата



Корпус


Пора переходить к корпусу. Его я напечатал на 3Д принтере. Дизайн без излишеств — коробка и кнопка. На коробке предусмотрены специальные зацепы, чтобы устанавливать светяшку в стандартный квадратный модуль конструктора.



В корпусе живет основная плата и батарея.





Светодиодная панель крепится на крышку, которая в свою очередь привинчивается шурупами к основной коробке

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



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

Первый вариант рассеивателя я пробовал сделать по технологии усадки ПЭТ бутылок строительным феном (подсмотрено у авиамоделистов).

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



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

Пошуршав по сусекам я нашел пробник пластика Verbatim PET Transparent в пару метров. Решил попробовать рассеиватель просто напечатать. И хотя на входе в принтер пластик кажется кристально прозрачным реальная деталь получается матово мутная. Вероятно это из-за внутренней структуры, т.к. слои не заполняют объем полностью а накладываются с промежутками и щелям. Более того если попробовать обработать деталь наждачкой для более гладкой поверхности то получаем еще большее матирование. Впрочем, это как раз то, что мне и было нужно.

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





Прошивка


Для светодиодной моргалки особо сильно в периферию микроконтроллера погружаться не нужно — достаточно парочки функций по работе с GPIO. Но раз уж модуль стыкуется с платформой Ардуино, то почему бы этим не воспользоваться?

Для начала несколько определений и констант

// Number of total LEDs on the board. Mine has 4x4 LEDs
#define NUM_HW_PIXELS 16

// Pin number where LED data pin is attached
#define DATA_PIN 0
// Pin number where mode switch button is attached
#define BUTTON_PIN 2
// Power Enabled pin
#define POWER_EN_PIN 1

// Max brightness (dimming the light for debugging)
#define MAX_VAL 255

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

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

// LED indexes for different patterns
uint8_t circleLEDIndexes[] = {0, 1, 2, 3, 4, 11, 12, 13, 14, 15, 8, 7};
uint8_t beaconLEDIndexes[] = {6, 5, 10, 9};
uint8_t policeLEDIndexes[] = {7, 6, 10, 11, 4, 5, 9, 8};

Для управлением светодиодов я не стал изобретать велосипед и взял готовую библиотеку для работы со светодиодами WS8211. Интерфейс библиотеки слегка побелил-покрасил. Некоторые вспомогательные функции (например конвертация HSV в RGB) также оттуда слямзил.

Для начала плату и библиотеку WS8211 нужно проинициализировать

// Driver
Ai_WS2811 ws2811;

void setup()
{
    // Set up power
    pinMode(POWER_EN_PIN, OUTPUT);
    digitalWrite(POWER_EN_PIN, HIGH);

    // initialize LED data pin
    pinMode(LED_PIN, OUTPUT);

    // Initialize button pin
    pinMode(BUTTON_PIN, INPUT);

    // Initialize WS8211 library
    static CRGB ledsBuf[NUM_HW_PIXELS];
    ws2811.init(DATA_PIN, NUM_HW_PIXELS, ledsBuf);

    // Set the watchdog timer to 2 sec
    wdt_enable(WDTO_2S);
}

Первым делом нужно выставить сигнал POWER HOLD в единицу — это будет сигналом микросхеме PT1502, что микроконтроллер завелся и работает исправно. Микросхема в свою очередь будет исправно поставлять электричество микроконтроллеру и светодиодам до тех пор, пока сигнал HOLD выставлен в единицу.

Далее конфигурируются ножки управления светодиодом на выход и кнопки на вход. После этого можно инициализировать библиотеку WS8211.

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

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

void setRgb(uint8_t led_idx, uint8_t r, uint8_t g, uint8_t b)
{
    CRGB * leds = ws2811.getRGBData();
    leds[led_idx].r = r;
    leds[led_idx].g = g;
    leds[led_idx].b = b;
}

Но в большинстве случаев в цветовой модели RGB считать цвета не очень удобно, а то и вообще невозможно. Например при рисовании всяких радуг удобнее работать с цветовой моделью HSV. Цвет каждого пикселя задается задается значением цветового тона и яркостью. Значение насыщенности для простоты опущено (используется максимальное). Значения цветового тона (hue) сведены к диапазону 0-255 (вместо стандартных 0-359).

/**
* HVS to RGB conversion (simplified to the range 0-255)
**/
void setHue(uint8_t led_idx, int hue, int brightness)
{
	//this is the algorithm to convert from RGB to HSV
	double r = 0;
	double g = 0;
	double b = 0;

	double hf = hue/42.6; // Not /60 as range is _not_ 0-360

	int i=(int)floor(hue/42.6);
	double f = hue/42.6 - i;
	double qv = 1 - f;
	double tv = f;

	switch (i)
	{
		case 0:
			r = 1;
			g = tv;
			break;
		case 1:
			r = qv;
			g = 1;
			break;
		case 2:
			g = 1;
			b = tv;
			break;
		case 3:
			g = qv;
			b = 1;
			break;
		case 4:
			r = tv;
			b = 1;
			break;
		case 5:
			r = 1;
			b = qv;
			break;
	}

    brightness = constrain(brightness, 0, MAX_VAL);

    setRgb(led_idx, 
        constrain(brightness*r, 0, MAX_VAL),
        constrain(brightness*g, 0, MAX_VAL),
        constrain(brightness*b, 0, MAX_VAL)
    );
}

Функция взята из библиотеки Ai_WS8211 и слегка подпилена. В оригинальном варианте этой функции из библиотеки было парочку багов из-за чего цвет на радугах показывался с рывками.

Перейдем к реализации различных эффектов. Каждая функция вызывается из главного цикла для отрисовки одного “кадра”. Поскольку каждый эффект оперирует разными параметрами между вызовами они сохраняются в статических переменных.

Это самый простой эффект — все светодиоды заливаются одним цветом, который плавно меняется.

void rainbow()
{
    static uint8_t hue = 0;
    hue++;
    
    for (int led = 0; led < NUM_HW_PIXELS; led++)
        setHue(led, hue, MAX_VAL);
   
    ws2811.sendLedData();
    delay(80);
}

Следующий эффект поинтереснее — он выводит радугу по контуру матрицы, а цвета в радуге постепенно смещаются по кругу.

void slidingRainbow()
{
    static uint8_t pos = 0;
    pos++;
    
    for (int led = 0; led < ARRAY_SIZE(circleLEDIndexes); led++)
    {
        int hue = (pos + led*256/ARRAY_SIZE(circleLEDIndexes)) % 256;
        setHue(circleLEDIndexes[led], hue, MAX_VAL);
    }
   
    ws2811.sendLedData();
    delay(10);
}

А этот эффект заливает всю матрицу случайным цветом, который сначала плавно загорается, а потом также плавно гаснет.

void randomColorsFadeInOut()
{
    static uint8_t color = 0;
    static bool goesUp = false;
    static uint8_t curLevel = 0;

    if(curLevel == 0 && !goesUp)
    {
        color = rand() % 256;
        goesUp = true;
    }

    if(curLevel == MAX_VAL && goesUp)
    {
        goesUp = false;
    }

    for(int led = 0; led < NUM_HW_PIXELS; led++)
        setHue(led, color, curLevel);

    if(goesUp)
        curLevel++;
    else
        curLevel--;

    ws2811.sendLedData();
    delay(10);    
}

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

void orangeBeacon()
{
    const int ORANGE_HUE = 17;

    static uint8_t pos = 0;
    pos+=3;
    
    for (int led = 0; led < ARRAY_SIZE(circleLEDIndexes); led++)
    {
        int brightness = brightnessByPos(pos, led*255/ARRAY_SIZE(circleLEDIndexes), 70);
        setHue(circleLEDIndexes[led], ORANGE_HUE, brightness);
    }
   
    ws2811.sendLedData();
    delay(1);
}

Технически эффект выглядит как яркая точка, которая двигается по матрице. Но чтобы выглядело красиво соседние светодиоды плавно угасают по мере отдаления от основной точки. Поэтому мне понадобилась функция, которая вычисляет эту самую яркость.

int brightnessByPos(int pos, int ledPos, int delta)
{
    int diff = abs(pos - ledPos);
    if(diff > 127)
        diff = abs(256-diff);
    int brightness = MAX_VAL - constrain(MAX_VAL*diff/delta, 0, MAX_VAL);
    return brightness;
}

Pos это некоторая условная позиция светящейся точки яркость, отображенная на закольцованный диапазон 0-255. ledPos это позиция светодиода (отображенная на тот же диапазон) яркость которого нужно вычислить. Если разница позиций больше delta, то светодиод не горит, а чем ближе к позиции, тем ярче он светится.

Или вот, например, полицейский красно-синий проблесковый маяк

void policeBeacon()
{
    const int RED_HUE = 0;
    const int BLUE_HUE = 170;
    
    static uint8_t pos = 0;
    pos += 2;
    
    for (int led = 0; led < ARRAY_SIZE(policeLEDIndexes); led++)
    {
        int ledPos = led*255/ARRAY_SIZE(policeLEDIndexes);
        int brightness = brightnessByPos(pos, ledPos, 50);
        setHue(policeLEDIndexes[led], RED_HUE, brightness);

        if(brightness == 0)
        {
            brightness = brightnessByPos((pos+100) % 256, ledPos, 50);
            setHue(policeLEDIndexes[led], BLUE_HUE, brightness);
        }
    }
   
    ws2811.sendLedData();
    delay(1);
}

Раз уж речь зашла про машины, то и светофор тут реализовать не проблема.

Это функции, которые включают различные сигналы светофора на различных позициях

void clearPixels()
{
    for(int i=0; i<NUM_HW_PIXELS; i++)
    {
        setRgb(i, 0, 0, 0);
    }
}

void redTrafficLights()
{
    for(int i=0; i<4; i++)
        setRgb(i, MAX_VAL, 0, 0);
    ws2811.sendLedData();
}

void yellowTrafficLights()
{
    for(int i=4; i<8; i++)
        setRgb(i, MAX_VAL, MAX_VAL, 0);
    ws2811.sendLedData();
}

void greenTrafficLights()
{
    for(int i=8; i<16; i++)
        setRgb(i, 0, MAX_VAL, 0);
    ws2811.sendLedData();
}

Пора это оживить. Светофор работает по специальной программе, заданной в чем-то вроде байткода. В табличке описан режим и время на которое этот режим нужно включить.

enum TRAFFIC_LIGHTS
{
    NONE, RED, YELLOW, GREEN
};

struct trafficLightState
{
    uint8_t state;
    uint16_t duration;
};

const trafficLightState trafficLightStates[] = {
    {NONE, 1},         // clear yellow
    {RED, 7000},     // red
    {YELLOW, 2000},      // red + yellow
    {NONE, 1},         // clear red+yellow
    {GREEN, 7000},     // green
    {NONE, 300},       // Blinking green
    {GREEN, 300},       // Blinking green
    {NONE, 300},       // Blinking green
    {GREEN, 300},       // Blinking green
    {NONE, 300},       // Blinking green
    {GREEN, 300},       // Blinking green
    {NONE, 1},         // clear green
    {YELLOW, 2000},      // yellow
};

Собственно функция, которая это все обрабатывает

void trafficLights()
{
    static uint8_t curStateIdx = 0;
    static unsigned long curStateTimeStamp = 0;

    // Switch to a new state when time comes
    if(millis() - curStateTimeStamp > (unsigned long)trafficLightStates[curStateIdx].duration)
    {
        curStateIdx++;
        curStateIdx %= ARRAY_SIZE(trafficLightStates);
        curStateTimeStamp = millis();
    }
    
    switch(trafficLightStates[curStateIdx].state)
    {
        case NONE:
            clearPixels();
            ws2811.sendLedData();
            break;
        case RED:
            redTrafficLights();
            break;
        case YELLOW:
            yellowTrafficLights();
            break;
        case GREEN:
            greenTrafficLights();
            break;
        default:
            break;
    }

    // Just waiting
    delay(10);
}

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

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

void stars()
{
    const uint8_t numleds = 5;
    static uint8_t ledIndexes[numleds] = {0};    
    static uint8_t curVal[numleds] = {0};
    static uint8_t maxVal[numleds] = {0};

    for(int i=0; i<numleds; i++)
    {
        if(ledIndexes[i] == 0)
        {
            uint8_t led = rand() % (NUM_HW_PIXELS+1);
            CRGB * leds = ws2811.getRGBData();
            if(leds[led].r == 0)
            {
                ledIndexes[i] = led;
                maxVal[i] = rand() % (MAX_VAL-1) + 1;
                curVal[i] = 0;
            }
        }
        else
        {
            uint8_t led = ledIndexes[i];
            if(curVal[i] < maxVal[i])
                curVal[i]++;
            else
            if(curVal[i] == maxVal[i])
                maxVal[i] = 0;
            else
            if(curVal[i] == 0 || --curVal[i] == 0)
                ledIndexes[i] = 0;

            setRgb(led-1, curVal[i], curVal[i], curVal[i]);
        }
    }

    ws2811.sendLedData();
    delay(80);    
}

Где-то тут закрался злобный баг. Иногда звездочки резко загораются, или наоборот резко гаснут. Но мне, если честно, лень было с этим разбираться — выглядит оно вполне нормально.

Пора подумать об экономии батареи. Я уже приводил значения потребления этой всей штуки. Если не подумать об отключении питания, то светодиоды съедят батарейку за пару часов. Вот эта функция занимается отключением питания через 90 секунд бездействия. Изначально было 60 секунд, но при реальной игре этого оказалось маловато, а 2 минуты уже как-то долго.

void shutdownOnTimeOut(bool resetTimer = false)
{
    static unsigned long periodStartTime = 0;

    if(periodStartTime == 0 || resetTimer)
    {
        periodStartTime = millis();
        return;
    }

    if(millis() - periodStartTime >= 90000UL)
    {   
        periodStartTime = 0;
        shutDown();
    }
}

Собственно отключение питания происходит так.

void shutDown()
{
    clearPixels();
    ws2811.sendLedData();

    wdt_disable();
    digitalWrite(POWER_EN_PIN, LOW);

    // No power after this point
    while(true)
        ;
}

Если пользователь нажимает на кнопки, то таймер сбрасывается. По истечении установленного времени функция выставляет сигнал HOLD в ноль, что является командой PT1502 на отключение питания. Watchdog, кстати, тоже остановить нужно, иначе через 2 секунды он разбудит систему и включит питание опять.

Наконец, главный цикл, который это все запускает

// List of pointers to functions that serve different modes
void (*Modes[])() = 
{
    rainbow,
    slidingRainbow,
    orangeBeacon,
    policeBeacon,
    trafficLights,
    stars,
    randomColorsFadeInOut
};

void loop()
{
    static uint8_t mode = eeprom_read_byte( (uint8_t*) 10 );
    static bool waitingForBtnUp = false;
    static long btnPressTimeStamp;

    // Button switches mode
    if(digitalRead(BUTTON_PIN) == HIGH && !waitingForBtnUp)
    {
        delay(20);
        if(digitalRead(BUTTON_PIN) == HIGH)
        {
            mode++;
            mode %= ARRAY_SIZE(Modes); // num modes
            
            clearPixels();
            ws2811.sendLedData();
            delay(1);

            eeprom_write_byte( (uint8_t*) 10, mode );

            waitingForBtnUp = true;
            btnPressTimeStamp = millis();

            shutdownOnTimeOut(true);
        }
    }

    // Shut down on long press over 5s
    if(digitalRead(BUTTON_PIN) == HIGH && waitingForBtnUp && millis() - btnPressTimeStamp > 5000)
        shutDown();

    // Detect button release
    if(digitalRead(BUTTON_PIN) == LOW && waitingForBtnUp)
        waitingForBtnUp = false;

    // display LEDs according to current mode
    Modes[mode]();

    // pong shutdown timer
    shutdownOnTimeOut();

    // Yes, we still alive
    wdt_reset();
}

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

Если ребенок, скажем, играл в полицейскую машину и через 1.5 минуты мигалка отключилась, то скорее всего после повторного включения сын захочет продолжить играть в полицейскую машину. Для этого выбранный режим сохраняется в EEPROM (ячейка номер 10 выбрана от балды).

Вот видео, которое показывает как это все работает.


Бутлоадер


Почти все готово. Но есть еще одна штука, которую нужно подпилить — бутлоадер. Дело в том, что стандартный бутлоадер нам не подходит.

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

А во-вторых, стандартный загрузчик ничего не знает про микросхему PT1502, которой неплохо было бы подать сигнал HOLD. Без этого сигнала микросхема думает, что микроконтроллер либо не завелся, либо наоборот хочет выключаться. А раз так, то через несколько миллисекунд PT1502 отрубит питание всей схеме.

Благо исправить обе проблемы не составляет труда. В плате digispark ATTiny85 используется загрузчик micronucleus. Этот загрузчик достаточно просто подпилить под наши нужды. Нужно только подправить соответствующие дефайны в файле конфигурации.

Первым делом я скопировал стандартную конфигурацию firmware\configuration\t85_default в свою собственную директорию и в ней уже делал все изменения. Так будет в случае чего легко откатиться на оригинальный загрузчик.

В файле bootloaderconfig.h есть выбор способа входа в загрузчик. Из того, что предлагается из коробки нам ничего не подходит, но ближе всего вариант ENTRY_JUMPER. В этом варианте вход в загрузчик происходит только если на определенном пине появляется определенный уровень (на плате замыкают джампер).

#define ENTRYMODE ENTRY_JUMPER

Джампера у нас нет, но есть кнопка на ноге PB2. Пускай вход в загрузчик будет происходить если при включении питания кнопку держат в течении 5-7 секунд. А вот если нажали и отпустили, то переход в основную прошивку происходит сразу.

Нам нужно определить 3 функции — инициализации, деинициализации и собственно проверка, а не пора ли входить в бутлоадер. В оригинале они все простые и реализованы макросами. У нас простыми будут только первые 2

#define HOLD_PIN      PB1
#define JUMPER_PIN    PB2
#define JUMPER_PORT   PORTB 
#define JUMPER_DDR    DDRB 
#define JUMPER_INP    PINB 

#define bootLoaderInit()   {JUMPER_DDR &= ~_BV(JUMPER_PIN); JUMPER_DDR |= _BV(HOLD_PIN); JUMPER_PORT &= ~_BV(JUMPER_PIN); JUMPER_PORT |= _BV(HOLD_PIN); _delay_ms(1);}
#define bootLoaderExit()   {;}

bootLoaderInit() настраивает пин кнопки (JUMPER_PIN) на вход и выключает на нем подтяжку. Подтяжка у нас уже есть на плате, причем к земле, а при нажатии на кнопку на пине наоборот будет единица. Заодно можно и сразу сконфигурировать сигнал HOLD на вывод и выставить на нем единицу…

За пояснением битовой арифметики ходить, например, сюда, а понимание регистров настройки GPIO в контроллерах AVR можно почерпнуть, например, отсюда.

Функция bootLoaderExit() пустая, т.к. выставленная конфигурация вполне годится для последующего перехода к основной прошивке

Функцию bootLoaderStartCondition() которая отвечает за вход в бутлоадер в формат макроса уже не влезла, а потому стала полноценной функцией

#ifndef __ASSEMBLER__
// Bootloader condition is to hold the button for 5 seconds
inline unsigned char  bootLoaderStartCondition()
{
  long int i;
  for(i=0; i<10000000; i++)
    if( !(JUMPER_INP & _BV(JUMPER_PIN)))
      return 0;

  return 1;
}
#endif

Функция в течении нескольких секунд (по факту около 6-7) проверяет состояние кнопки. Если кнопку отпустили раньше, то входить в бутлоадер нам не нужно. Терпеливых и настойчивых пускают дальше в загрузчик.

Как оказалось файл bootloaderconfig.h участвует в компиляции ассемблерных файлов и сишный код в этом файле вызывает ошибки. Пришлось функцию поместить в блок #ifndef __ASSEMBLER__

Еще один параметр, который я подправил, указывает бутлоадеру что делать если его не подключили к USB — выходить через одну секунду. Дело в том, что во время обкатки сын часто нажимал кнопку и нечаянно заходил в бутлоадер. Я не знаю каким чудом, но бутлоадер если не видел USB соединения мог случайным образом затирать некоторые страницы памяти. Потому если нет соединения будем просто выходить в основную программу.

/*
 * Define bootloader timeout value. 
 * 
 *  The bootloader will only time out if a user program was loaded.
 * 
 *  AUTO_EXIT_NO_USB_MS        The bootloader will exit after this delay if no USB is connected.
 *                             Set to 0 to disable
 *                             Adds ~6 bytes.
 *                             (This will wait for an USB SE0 reset from the host)
 *
 *  All values are approx. in milliseconds
 */
#define AUTO_EXIT_NO_USB_MS    1000

Компилируем… и получаем ошибку, что код не влезает в отведенное ему пространство бутлоадера. Поскольку флеш памяти в контроллере очень мало, то бутлоадер ужимают по максимуму, чтобы оставить побольше места основной программе. Но это легко исправить в файле Makefile.inc следуя инструкции

# hexadecimal address for bootloader section to begin. To calculate the best value:
# - make clean; make main.hex; ### output will list data: 2124 (or something like that)
# - for the size of your device (8kb = 1024 * 8 = 8192) subtract above value 2124... = 6068
# - How many pages in is that? 6068 / 64 (tiny85 page size in bytes) = 94.8125
# - round that down to 94 - our new bootloader address is 94 * 64 = 6016, in hex = 1780
BOOTLOADER_ADDRESS = 1940

Тут я просто уменьшил стартовый адрес бутлоадера одну страницу (64 байта) тем самым увеличив место под загрузчик.

В остальном компиляция и заливка бутлоадер с помощью программатора USBAsp не составила проблем.

Заключение


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

Можно ли было проще? Конечно можно. Я думаю все можно было бы сделать с помощью транзистора. К сожалению вот эту статью я прочитал уже после того как спаял плату. Увидел бы статью раньше — сделал бы все на том же народном TP4056 — его паять легче. Все равно DC-DC преобразователь, который есть внутри PT1502 в этом устройстве, по хорошему, не нужен. Впрочем, практическое исследование микросхемы PT1502 мне пригодится для моего другого проекта, как и умение паять микросхемы в корпусе QFN20.

Напоследок вот ссылки на мой проект:

Код прошивки
Схема и плата
Модель корпуса и рассеивателя
Готовые STL модели для печати

Вы можете помочь и перевести немного средств на развитие сайта



Комментарии (44):

  1. prs123
    /#19584174

    Очень интересный кейс и здорово, что использовали для этого подходящий контроллер.
    Раз печатали серую часть корпуса на принтере, можно было бы и не мучаться с бутылкой, а взять пластик прозрачный Филаментарно (надеюсь за рекламу не сочтут).
    Когда плату на JLCPCB заказывали, доставка дороже плат не вышла или вы сразу пачку заказывали? На сколько хорошо дорожки сделали? а то народ жаловался на качество дорожек

    • grafalex
      /#19584198

      а взять пластик прозрачный

      Так я же так и сделал.
      Упомнянутый Verbatim PET Transparent это и есть прозрачный пластик для 3Д принтера.
      Выклянчил пробник на какой-то выставке…

      Когда плату на JLCPCB заказывали, доставка дороже плат не вышла или вы сразу пачку заказывали?

      Изготовление 5 плат — $2, доставка — $10.8. Но в заказе я объединил несколько своих проектов на одну плату 100х100, после чего разрезал их ножницами по металлу. Если бы заказывал только под этот проект, то доставка была бы долларов 5
      Дорожки не отваливались, только маска, да и то после второй-третьей перепайки микросхемы в QFN20

      • prs123
        /#19584202

        Так я же так и сделал.
        Упомнянутый Verbatim PET Transparent это и есть прозрачный пластик для 3Д принтера.
        Выклянчил пробник на какой-то выставке…

        каюсь, не заметил
        Изготовление 5 плат — $2, доставка — $10.8. Но в заказе я объединил несколько своих проектов на одну плату 100х100

        А вы указывали это как одну? то есть стоимость не менялась?

        • grafalex
          /#19584310

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

          В данном случае я не заказывал V-Cut (фрезеровка между элементами платы, чтобы удобнее было ломать). Вместо этого я просто налепил на одну панель несколько своих проектов и шелкографией нарисовал границы плат. Прокатило — цена осталась на уровне акционных 2 долларов

          • sashz
            /#19585230

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

            pcbway этим увлекается, принудительно рисуя V-Cut и задирая цену, если видят несколько разобщенных плат на одной большой.

          • Max-812
            /#19586406

            Я для такой компоновки рисую границы контурным слоем, между плат делаю зазор 2 мм, и между каждой из соседних плат делаю мелкий мостик миллиметра 3 шириной, в котором сразу же размечаю несколько мелких отверстия по середине. В итоге платы просто ломаются руками, несколько зазубринок остается аккуратно убрать напильником. Пока вопросов у производителя не было, хотя регулярно в один 100 на 100 вписывается от 2 до 5 различных плат.

            • grafalex
              /#19586602

              между плат делаю зазор 2 мм

              Зазор с фрезеровкой? за фрезеровку доп плату берут?

              • Max-812
                /#19589646

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

                • grafalex
                  /#19590176

                  Спасибо, возьму на вооружение

    • Gluzer
      /#19585764

      народ жаловался на качество дорожек
      Качество изготовления плат на JLCPCB разнится от случая к случаю.

  2. grafalex
    /#19584308

    del

  3. customtema
    /#19584428 / -1

    Этот проект называется Arduino.

    Извините.

    • grafalex
      /#19584436

      Мммммм, не совсем понял у чему Вы придрались

    • BSW
      /#19584448

      Подскажите. Сколько потребляет PT1502 в ''выключенном'' состоянии?

      • grafalex
        /#19584458

        Микроамперы… Когда-то мерял, но не записал.
        К сожалению светяшку заклеил термоклеем и без особой необходимости туда не очень хотелось бы лезть. Но если вдруг полезу — обязательно замерю

      • grafalex
        /#19584616

        Глянул в даташит. Typical — 1uA, Max — 5uA
        Так что батарейки должно хватить на годы.

  4. REPISOT
    /#19584560

    Можете ответить, чем обусловлено вот это в разводке? При том, что сделано, вроде, для фабричного изготовления, а не одноразовая поделка для ЛУТа.

    • grafalex
      /#19584586 / +2

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

      • olartamonov
        /#19584840 / +1

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

        Полигон.

        • olartamonov
          /#19584866 / +2

          Второй большой ляп — USB.

          1) никогда не используйте для защиты USB стабилитроны. Во-первых, стабилитроны вообще не для защиты, для этого есть TVS, во-вторых, у них конская ёмкость, до сотен пикофарад, которая может убить вам даже 12 Мбит/с. Для Full Speed подойдут многие типовые TVS, например, SP0503 — там как раз сборка из трёх TVS (третий на +5 В), для High Speed — только специализированные TVS с низкой паразитной ёмкостью, например, USBLC6.

          2) USB — дифференциальная шина, как минимум ведите обе дорожки на плате параллельно, выдерживайте их длину примерно равной, не допускайте стабов (тупиковых ответвлений, вот как у вас на R3) и т.п. Для High Speed и/или длины больше 3-5 см уже обязателен расчёт пары под импеданс 90 Ом.

          • grafalex
            /#19585306

            Спасибо, это тоже учту.

            Как я написал в статье эту часть я слямзил с платы от DigiSpark. Думаю, что стабилитроны тут не для защиты, а для конвертации уровней 5<->3.3В. Насколько я понимаю эта схемотехника рекомендуется создателями библиотеки V-Usb www.obdev.at/products/vusb/index.html — это полностью программная эмуляция USB Low Speed (1.5MBit)

            Что не так с R3? насколько я понимаю что Low Speed, что Full Speed требуют подтяжки одной из шин к питанию (High Speed пока не копал)

            • olartamonov
              /#19586058

              Вопрос не в подтяжке, вопрос в дорожке.



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

              По той же причине стараются на них не делать переходных отверстий, а на совсем чувствительных штуках — делают дорожки постоянной ширины, включая повороты, площадки деталей и т.п.

              В вашем случае последнее не нужно, но минимальную гигиену (без стабов, обе линии в паре близки по длине) соблюдать стоит всегда.

            • olartamonov
              /#19586064

              Про стабилитроны — кривая реализация, на Low Speed оно ещё может работать, но если вы это перенесёте на контроллер с аппаратным USB — уже на High Speed отвалится практически гарантированно, ну или как минимум работоспособность будет очень сильно зависеть от используемого USB-шнурка.

              • grafalex
                /#19586280

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

                Спасибо за разъяснение про нюансы разводки диф-линий. Как раз в другом своем проекте дошел до разводки платы. Там я собираюсь подключать STM32 к USB без всяких стабилитронов, только через STF202-22 для защиты.

    • Demon_i
      /#19584610 / +2

      С нижней и средней, вроде понятно, а с верхней что не так? Так, чтобы знать.

      • rstepanov
        /#19584746 / +2

        Прямой угол, ну и в целом не оптимально.

      • olartamonov
        /#19584846 / +1

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

    • sashz
      /#19585242

      Eagle авторазводчик такие косяки допускает. Если руками не поправить, то так и останется.

  5. Psychosynthesis
    /#19585102

    Спасибо, интересная статья и результат радует.

    Единственная сложность — микросхема идет в корпусе QFN20 размером 4х4 мм и требует некоторой обвязки. Паять такое дома сложно, но можно.

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

    • olartamonov
      /#19586066

      LGA феном совершенно спокойно ставятся.

      BGA в общем тоже, но там уже вопросы подготовки шариков, площадок, совмещения этого — в то время как LGA можно просто залудить и как-то так примерно ровно сверху положить.

      • Psychosynthesis
        /#19586174

        LGA? Это же разъём, нет?

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

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

        • olartamonov
          /#19586208

          LGA — это вот такое, площадки на нижней поверхности чипа:



          Можно грубо представить как QFN, у которого ноги на боковую сторону не выходят.

          Сложность пайки с QFN одинаковая, и определяется по большей части шагом ножек и их количеством.

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

          Но если их нет, а припаять надо всего 1-2-5 штук, то тот же LGA облуживается снизу одним движением паяльника, после чего просто ставится с феном на площадку, без паяльной пасты, только с тонким слоем жидкого флюса. Результат будет 100-процентный, в общем-то.

          • Psychosynthesis
            /#19586256

            Хз, я пробовал, получалось через раз. Но согласен, количество припоя или флюса решает.

  6. stifff
    /#19585112

    All, а подскажи. Есть ли какой-нибудь софт для расчета/моделирования рассеивателя вроде того, что в устройстве? Чтобы свечение отдельных светодиодов на плате не так сильно бросалось в глаза

    • Gluzer
      /#19586100

      Софт для моделирования оптических элементов конечно же есть. Из профессиональных можно отметить TracePro и LightTools. Но для расчета светорассеивателя в них придется немало попотеть, т.к. софт исключительно узкоспециализированный. Для сохранения минимальной толщины оптического элемента лучше всего использовать конструктив линзы Френеля.

  7. SHmoX
    /#19585472

    отличная статья, отдельное спасибо за Amorphis.

  8. VT100
    /#19585650

    … но на деле меньше 1 мА получить не удалось. Я и АЦП отключал и пины переводил в input. Похоже где-то в схеме есть какая-то утечка, но моих скромных познаний в электронике недостаточно, чтобы ее найти и понять.

    Была-бы схема того варианта — можно было-бы покумекать над поиском утечек. Несомненно — она была по цепи R3-D1.
    Но, скорее, выбран не тот режим энергосбережения (например — Idle вместо Power Down) и что-то не так с выводами. Вместо Input надо, как минимум, Input w. Pull-up. Выход P0 надо переводить в 0, что-бы не запитывать WS8212 через защитный диод. И над PB3, PB4 надо подумать: перевод их в «1» или Pull-up — будет давать утечки через D1, D2, а перевод в «0» — вызывать конфликт на USB.

    Хоть в итоге и получился некий overkill — хорошо. Набитые шишки не будут лишними.

    • grafalex
      /#19585694

      Дык в статье приводил схему. Вот она же без моих каракулей
      image

      Если честно, не понимаю как по R3-D1 что нибудь может утекать, если ЮСБ не подключен, а вывод контроллера переведен в input.

      То же самое непонимание про перевод P0 в ноль. Как WS8212 можно этим запитать?

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

  9. VT100
    /#19585982

    Дык в статье приводил схему.

    Нет, это схема Digispark'а. Я имел в виду полную схему варианта «Digispark без U1 и D3» + TP4056 + WS8212.
    Если честно, не понимаю как по R3-D1 что нибудь может утекать, если ЮСБ не подключен, а вывод контроллера переведен в input.

    Если я правильно понимаю, то в этой версии на линии 5V был аккумулятор. Вот и утекало через R3 пропорционально разнице напряжений аккумулятора и защитного стабилитрона. Можно было-бы переключить верхний вывод R3 на V+, ведь он нужен только для USB.
    Не говоря уж про указанные olartamonov косяки с ёмкостью, хотя для LS это и прокатывает.
    То же самое непонимание про перевод P0 в ноль. Как WS8212 можно этим запитать?

    Если перевести (или оставить) P0 в «1», то будет утечка через верхний диод внутрь WS8212.
    image
    Паразитное питание. Автор, правда, насилует кое-какие факты. Но, в целом, — достаточно объясняет суть явлений.
    Подобным способом, например, питаются 1-wire микросхемы Maxim/Dallas.

    • grafalex
      /#19586260

      Спасибо за разъяснение.

      Если я правильно понимаю, то в этой версии на линии 5V был аккумулятор. Вот и утекало через R3 пропорционально разнице напряжений аккумулятора и защитного стабилитрона. Можно было-бы переключить верхний вывод R3 на V+, ведь он нужен только для USB.

      Да, Вы правы. R3 получается был подключен к батарейке, но течет через него максимум (4.2-3.6)/1500 = 40мкА.

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

      В моей текущей схеме, получается, я допустил сразу 2 ошибки. Во-первых R3 нужно было подключать к VIN (5V). Я же его подключил к 3.3В, а значит резистор нужно было бы другой. Я точно не уверен, но вроде спецификация USB требует подтяжки именно через 1.5к резистор и именно к 5В.

      • holomen
        /#19589246

        Спецификация конечно требует, но на практике у многих bluepill нормально работает с вообще 10К на 5В. Некоторые эту самую пилюлю «улучшают», перепаивая 10к на 4К7 и рассказывают что перепаяли «по уму» и оно теперь работает стабильнее… %)