Как стать автором
Обновить

К вопросу о светодиодах и управлении ими через МК

Время на прочтение13 мин
Количество просмотров5.5K

Существует целая линейка весьма интересных приборов - трехцветные светодиоды со встроенной схемой управления (ws2811, ws2812, ws2812b, ws2813...). Характерной особенностью их является цена, которую иначе, как смешной, назвать трудно, что и определяет их необычайную популярность среди любителей. Почему-то часто их называют адресными светодиодами, что, на мой взгляд, не вполне верно, поскольку данные приборы собственного уникального адреса не имеют и адресуются положением в цепочке подключения, но оставим тонкости терминологии за скобками.

Примечание на полях (Пнп): Выпускается это семейство, что совершенно естественно, за пределами нашей необъятной Родины. Как специалист, непосредственно связанный с данной тематикой, не могу не выразить своего недоумения по поводу отсутствия в номенклатуре электронных приборов, выпускаемых отечественными производителями, подобных изделий. Я понимаю, что изготовление микросхем по нормам 90 и менее в РФ невозможно, невзирая на неоднократные заявления об освоении данных процессов Ангстремом, но компоненты, вроде описываемых в данном посте, просто не могут требовать применения столь продвинутых технологий, так что ответ на вопрос лежит, вероятно, более в экономической, нежели технологической плоскости. Но, тем не менее, наши южные соседи подобные приборы делают и, наверняка, не в убыток себе.

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

В данном случае детали заключаются в длительностях кодирующих импульсов - для достижения высоких скоростей передачи они приняты весьма и весьма небольшими (конечно, смотря с чем сравнивать) - порядка сотен наносекунд, конкретные значения мы увидим позже. В сочетании с нестандартным форматом передачи ("Господи, ну Англия то чем ему не угодила") реализация такого протокола на стандартных МК представляет собой определенную проблему, о возможных путях решения которой мы и поговорим.

Для начала уточним задачу - информация передается по-битово импульсами активного (высокого) уровня определенной длительности, разделенными паузами пассивного (низкого) уровня также определенной длительности. Пакет для одного светодиода состоит из 8 битов/цвет*3 цвета= 24 битовых посылок (для упрощения будем считать, что их 32). Таких пакетов должно быть n (или даже N) по количеству светодиодов. Дополнительная пауза между пакетами не предусмотрена, завершение передачи кодируется паузой повышенной длины. Вроде на первый взгляд все нормально и понятно, но при внимательном взгляде на документацию мы наблюдаем "трэш, угар и содомию".

Откроем техническую документацию на рассматриваемые приборы и увидим крайне интересные вещи. Например, авторы некоторых из рассматриваемых документов уверены, что 150*2=600, по крайней мере, они так пишут: допустимый разброс длительности импульса - 150 нс, допустимый разброс длительности паузы между импульсами - 150 нс, разброс длительности символа - 600 нс. В общем то, последний параметр вообще ни о чем, поскольку приведен как бы справочно, но все равно, "не аккуратненько получается".

Для младших моделей семейства возможны две скорости передачи: 400 (низкая) и 800 (высокая) кбод, для всех остальных - только 800 кбод. Причем, опять мы видим в документации только требуемые значения длительностей импульса и паузы для низкой скорости, точность их задания и фразу "Примечание: для режима высокой скорости все интервалы времени уменьшаются в 2 раза, но время сброса (reset time) остается неизменным", все остальное приходится додумывать самим, например, требуемую точность задания интервалов для высокой скорости. Пнп: интересно, как разработчики м/сх реализовали внутри определение текущей скорости и можно ли менять ее "на лету" - но это вопрос скорее теоретического плана, и далее мы будем рассматривать только высокую скорость, поскольку именно этот режим является универсальным и присутствует во всех моделях семейства.

При этом у каждого представителя семейства (каждого производителя м/сх?) свои представления о распределении длительности импульса и паузы внутри битового интервала, что несколько удивляет. Тем не менее, благодаря довольно таки широким допускам на длительности, можно определить параметры импульса и паузы, которые будут приемлемы для всех компонентов.

(мкс)

WS2811

WS2812

WS2813

T0H

0.22-0.38

0.2-0.5

0.3-0.45

0.375

T1H

0.58-1.0

0.75-1,05

0.75-1.0

0.875

T0L

0.58-1.0

0.75-1,05

0.3-100

0.875

T1L

0.58-1.0

0.2-0,5

0.3-100

0.375

TH+TL

1.1-1.4

0.95-1.55

1.25

Res

>280

>50

>300

>300

Пнп: на самом деле нет, как нетрудно видеть, в строке 4 таблицы в столбцах 1 и 2 данные не пересекаются, но мы будем считать значения в строке 1 ошибочными ну или по крайней мере не полностью определенными). Если это не так, то нам потребуются разные длительности для разных микросхем, что реализовать несложно, но снижает удобство использования.

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

Сведем данные в таблицу и определим длительность импульсов единичного и нулевого бита, а также длительности пауз после них (да, они разные), подходящих для любого прибора семейства. Для импульса нулевого бита это 300-380 нс, для паузы после него 800-900 нс, для импульса единичного бита 640-760 нс с паузой 500-600 нс. Определим также допустимые соотношения период/импульс: для ноля (800+380)/380:(900+300)/300 = 3.1:4 или в рациональном виде 10/3:4/1, для единицы (640+600)/600:(760+500)/500=2.06:2.5 или в рациональном виде 10/6:10/4. Теперь нам следует определить внутреннюю частоту генератора, при которой период четко попадет в требуемые интервалы.

Частота не менее, чем 1/80нс (минимальный требуемый разброс) = 12.5 МГ принципиально позволяет нам решить поставленную задачу. Тем не менее, даже частота менее указанной может, при счастливых обстоятельствах, удовлетворять требованиям. Например, при частоте 8 МГц для передачи нуля потребуется 3 периода частоты = 375нс для формирования импульса и 7 периодов = 875нсек для паузы ("Совпадение ? - Не думаю"), а для передачи единицы - 6 и 4 соответственно. Пнп: здесь и далее мы считаем, что погрешность основного тактового генератора намного меньше периода, мы применяем только внешний генератор МК, поскольку точность внутреннего недостаточна. Теперь, когда мы установили принципиальную возможность решения поставленной задачи, рассмотрим различные конкретные пути ее реализации.

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

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

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

    Минусы не менее очевидны:

  3. нужен тщательно проработанный и далеко не очевидный код,

  4. необходимость модифицировать код при смене частоты,

  5. необходимость использования ассемблера (возможно, в виде вставок в код С),

  6. 100% загрузка процессора передачей и, связанная с ней,

  7. невозможность обработки прерываний от периферийных устройств. Если предположить допустимость удлинения паузы между импульсами, то последнее ограничение можно несколько смягчить, но это сильное предположение и совершенно справедливо только для 2813 кристалла. Пнп: кстати, прерывания придется отключать на довольно-таки длительное время. Если к нашему МК подключена линейка из 64 светодиодов, то полное время передачи составит 64 диода * 32бита/диод *1.25мкс/бит = 2.56мс.

Поэтому ищем другой способ и возникает идея номер два (тоже весьма распространенное решение, хотя и не для данного случая) - использование таймера для генерации прерываний и манипуляции с портами в обработчике. Сразу заявляю, что это не очень хорошая идея вообще и плохая в данном конкретном случае. Прежде всего, нам потребуется значительно бОльшая вычислительная мощность. Если частота процессора не велика (те же самые 8 МГц), то при длительности импульса нуля в 3 такта ни о каких прерываниях не может быть и речи. Если же частота достаточна для осуществления операции в принципе, то мы должны иметь в виду, что будем иметь принципиально неустранимый джиттер фронтов, поскольку время входа в прерывание будет зависеть от внешних, по отношению к нашему решению, обстоятельств. Тем не менее, подобный подход часто применяют при реализации низко-скоростных интерфейсов, где джитер в 2-3 периода тактовой частоты является допустимым. Сразу можно прикинуть необходимую частоту работы МК: 2-3 периода должны уложится в 80нс, то есть частота не может быть ниже 12.5 МГц*3=37.5 МГц.

В данном случае возможна работа совместно с другими задачами и прерываниями при условии, что приоритет прерывания нашего таймера наивысший. Плюсы решения:

  1. простые аппаратные средства (один таймер с прерыванием и произвольная ножка),

  2. при достаточной производительность МК - возможность выполнять другие задачи. Основной минус я уже упомянул - наличие джиттера и необходимость компенсировать его.

Оценим необходимые для реализации данного метода требования по быстродействию. Нам необходимо (далее времена в тактах даны для МК типа AVR, для ARM будет несколько иная, хотя и похожая, картина) войти в прерывание 4-6 т, сохранить ресурсы 4-5 т, получить очередной элемент данных 6-7 т, настроить следующую передачу 4-5 т, восстановить ресурсы 4-5 т, вернуться из прерывания 4т, и сделать что-нибудь полезное 5-6т. Итого 38 тактов, которые должны быть выполнены за время выдачи текущей передачи, то есть за 375 нс, тогда требуемая частота составит 1/Т=1/(37510**Е-9/38)=38/37510**9~100МГц. Величина не характерная для большинства простых МК, хотя вполне достижимая.

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

Будем продолжать искать решения с использованием аппаратных средств МК (как говорили в одном мультике "мне нравится ход твоих маленьких грязных мыслишек") и повнимательнее посмотрим на таймер. Дело в том, что современные МК содержат в своем составе устройства, которые далеко ушли от таймеров в том смысле, который вкладывался в это понятие в эпоху х51 и даже х48 архитектуры. И одной из фич современных таймеров является возможность работы в режиме ШИМ (PWM), в котором на внешней ножке формируется сигнал переменной длительности, а это именно то, что надо. Мы можем дать таймеру задание выдать импульс длиной 3 периода с последующей паузой в 7 периодов (на самом деле мы дадим задание сформировать интервал 10 тактов с импульсом, начиная с 3 такта и до конца интервала, но это не важно с точки зрения результата) и идти отдыхать (ну или заниматься своими делами), а когда он нам сообщит, что текущее задание выполнено, то дать ему следующее. Причем задание будет выполнено в точности, без джиттера, несмотря на то, чем мы занимаемся до его завершения.

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

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

Плюсы данного решения:

  1. гарантированные временные интервалы и отсутствие джиттера

  2. снижение нагрузки на процессор.

    Минусы:

  3. как правило, мы должны использовать конкретную ножку (если в составе МК нет матрицы коммутации),

  4. более сложная программа, по прежнему нужен ассемблер при невысоких частотах ядра. Насколько стало легче ядру - если раньше мы должны были успевать сделать все за минимальное время изменения - 375 нс, то теперь у нас в распоряжении все время выдачи бита - 1250 нс, соответственно нам требуется лишь 38/(1250*10**9) = 31 МГц и границы применения данного способа расширяются.

Что еще можно вытащить из метода ШИМ - если Вы счастливый обладатель МК с реализацией ПДП (STM или XMEGA), то можно еще более разгрузить ядро. При этом осуществляем классический прием - меняем память на время. Нам следует сформировать массив длительностей импульсов для передачи (хорошим выбором будет 32 элемента в массиве, поскольку именно столько битов в одной команде светодиода), настроить аппаратуру ПДП и ждать окончания передачи всего массива. Вернее, мы должны не просто ждать, а сначала сформировать очередной массив для передачи, настроить ПДП на его использование в следующем цикле (ping pong или round robin) и лишь потом "изощряться в искусстве и науках". Тогда для формирования следующего пакета нам потребуется не более, чем 32бит*(5-10)тактов/бит=160-320тактов за время передачи блока данных 32бит*1250нс/бит, так что нам будет достаточно 320/32*125*10*-9 = 10*10/1250=8 МГц, а может и меньше, все зависит от нашего скила в программировании МК. Обратим внимание, что нам потребуется дополнительный буфер размером 2*32 байта, которого раньше не было, мы сразу же выдавали наружу очередной сформированный бит.

С предложенным подходом связана одна интересная задача - как нам обеспечить обработку прерываний от других периферийных устройств. Ведь, с одной стороны, мы обязаны за время передачи блока данных сформировать очередной блок, то есть провести определенные действия в жестком реальном времени, которые займут, допустим, 8т/бит*32бит*50нс/т(для частоты 20 МГц)=12.5 мкс (из допустимых 40мкс). Можно, конечно, запретить все прерывания на это время тем или иным способом, но тогда они отложатся, что не есть хорошо. С другой стороны, если мы позволим прерывать работу по формированию очередного пакета, то вполне можем и не успеть завершить его формирование до нужного момента, а это совершенно недопустимо.

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

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

Наверное, еще больше уменьшить требования к быстродействию не удастся, хотя ... есть одна идея. Мы можем заранее сформировать последовательность кодов для ШИМ модулятора и выдавать его через ПДП одним длинным пакетом. Нам потребуется буфер длиной 32*n(лучше N) байтов, зато формировать мы его можем спокойно, без дикой спешки. На первый взгляд, выглядит странно, так разбрасываться памятью, но для Н=64 это всего лишь 32бит/диод*64диод*1байт/бит = 2кб, что не так уж и много для МК, содержащего ПДП (скажи мне кто-либо лет 15 назад, что у МК будет такая внутренняя память, я бы посмеялся). Пнп: на самом деле нам нужен не весь байт, а лишь его половина (требуемая длительность на превышает 16), но я не вижу возможности использовать оставшуюся часть байта, кроме как применить маскирование битов при пересылке через ПДП, которая вряд ли реализована в Вашем МК (да и в каком бы то ни было). Более того, возможен вариант, когда мы сразу храним информацию о цвете каждого светодиода в виде последовательности длин импульсов, что позволит сэкономить на блоке данных в таком случае 32бит/диод*64 диода*(1/8)байт/бит=256 байт. Не слишком большая экономия по памяти, зато вообще не нужно проводить преобразование данных для передачи (хотя все равно придется их производить при формировании данных).

Мы можем рассмотреть и другие возможности, связанные с использованием прочих аппаратных блоков МК. Поскольку нам требуется последовательная передача, кандидатами на применение могут быть I2C/TWI (скорее нет, форматы уж явно различны), SPI (надо подумать, скорее да) и UART (а вот это интересно и точно да).

Почему I2C (конечно же его линия данных, поскольку клок не регулирует свою скважность) не представляется хорошим кандидатом - маловероятно, что удастся выдержать временную диаграмму, учитывая особые состояния (старт, стоп, ответ) хотя можно попробовать режим длинной (более 8 бит) передачи данных.

Что же касается SPI, то здесь при должном упорстве все должно получиться. Почему я говорю об упорстве - в конкретной реализации добиться передачи данных непрерывным потоком (без пауз в 1-2 такта) порой бывает нелегко, да и режим 10-битовой передачи тоже к стандартным не отнесешь. Но в общем и целом задача решаемая, требования к быстродействию для 8-разрядных МК чуть больше (из-за необходимости формирования 10 битов), чем с таймером. Пнп: "тот, кто нам мешает, тот нам поможет" - если мы сможем реализовать режим, в котором точно 2 бита паузы между передаваемыми символами, то нас вполне устроят 8 битов данных.

А теперь об UART - на первый взгляд предложение дурацкое, ведь данный интерфейс имеет неотменяемые особые состояния (старт и стоп биты) что делает невозможным непрерывный битовый поток. Но "тот, кто мешает, тот нам поможет", ведь нам нужно передавать не произвольный набор битов, формирующих временную диаграмму импульса, а два конкретных паттерна, каждый из которых имеет 1 бит в начале посылки и 0 в конце, что счастливым образом совпадает с требуемым форматом данных. Так что, подставляя в данные 0b00000011 и b00011111, получаем необходимую временную диаграмму для передачи 0 и 1 соответственно при скорости передачи 8 мбод (да, скорость нестандартная но кто говорил, что будет легко). Пнп: никогда не задумывался, почему в UART информация передается младшим битом вперед. То есть одно объяснение я могу предложить - чтобы на осциллографе было труднее прочитать передаваемые данные, но вряд ли это было определяющим фактором, так что вопрос остается открытым. Требования по быстродействию ядра остаются такими же, как и для таймера, равно как и соображения по возможности использования ПДП снижения требований.

Есть еще один резерв снижения требований к производительности и к памяти - передача более одного бита за посылку. Если аккуратно подобрать время битового интервала UART, то можно передавать один бит пакета не за 10 тактов (3/10 и 6/10), а за 5 (2/5 и 3/5), что даст нам больше времени для формировании сразу двух битов передаваемого пакета. При помощи подобного трюка мы также снижаем требования к тактовой частоте UART до 4 МГц (хотя стандартной она все равно не становится), но нужно быть очень аккуратными, чтобы длительности и паузы оставались в допустимых пределах.

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

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

P.S. Сказать, что мне не понравился новый редактор - это ничего не сказать.

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Новый редактор:
6.25% Просто замечателен, а все остальное — капризы.1
12.5% Приемлем, если к нему привыкнуть2
81.25% Ни фига не понятно, зачем я должен к нему привыкать13
Проголосовали 16 пользователей. Воздержались 26 пользователей.
Теги:
Хабы:
Всего голосов 19: ↑17 и ↓2+15
Комментарии8

Публикации