Это третья часть мини-серии. В части 2 я описал, как настроить инструменты разработки и отладить первый проект (см. « Учебное пособие: NeoPixels Adafruit WS2812B с платой Freescale FRDM-K64F — Часть 2: Инструменты программного обеспечения »). Теперь пришло время изучить концепции программного обеспечения. Цель состоит в том, чтобы запустить NeoPixel от Adafruit (WS2812B) с платой Freescale FRDM-K64F :
Щит Adafruit 8 × 8 NeoPixel с платой Freescale FRDM-K64F
Список учебников Mini Series
- Учебник. AdoPruit WS2812B NeoPixels с платой Freescale FRDM-K64F — Часть 1. Аппаратное обеспечение
- Учебник. AdoPruit WS2812B NeoPixels с платой Freescale FRDM-K64F — Часть 2. Инструменты программного обеспечения
- Учебное пособие: Adafruit WS2812B NeoPixels с платой Freescale FRDM-K64F — Часть 3. Основные понятия
- Учебник. AdoPruit WS2812B NeoPixels с платой Freescale FRDM-K64F — Часть 4. Таймер
- Учебник. AdoPruit WS2812B NeoPixels с платой Freescale FRDM-K64F — Часть 5. DMA
Контур
В этой статье объясняется протокол и время разговора со светодиодами WS2812B (или Adafruit NeoPixel). Это объясняет подход с таймерами и DMA для удовлетворения требований к оборудованию. Я использую инструменты GNU ARM Embedded (launchpad) с IDE на основе Eclipse (Freescale Kinetis Design Studio v3.0.0). В качестве драйверов программного обеспечения я использую Freescale Kinetis SDK v1.2.
WS2812 Цепочка
Светодиоды WS2812 (B) имеют встроенный драйвер тока с регистром сдвига: запись данных в регистр сдвига зафиксирует биты, а драйвер постоянного тока «подведет» ток к красной, зеленой и синей части СВЕТОДИОД.
Данные записываются на одном проводе (DIN-контакт) первого WS2812 в цепи. Первое устройство будет «сохранять» первые 24 бита, а все остальное смещается на вывод DOUT. Таким образом, чтобы предоставить данные двум светодиодам, я сдвигаю 2х24 бита: первые 24 бита предназначены для первого устройства в цепочке, затем следующие 24 бита для следующего в цепочке и т. Д.
Светодиодная цепь WS2812 (Источник: https://cpldcpu.wordpress.com/2014/01/14/light_ws2812-library-v2-0-part-i-understanding-the-ws2812/ )
WS2812 информация о цвете
Каждый светодиод нуждается в 24 битах, кодируя цвета Зеленый, Красный и Синий по 8 бит каждый. Так что это GRB, а не RGB! Каждый 8-битный цвет определяет яркость этого цвета от 0x00 (выкл.) До 0xff (полная яркость). биты передаются MSB (старший бит) в первую очередь.
Чтобы включить красную часть светодиода на полную яркость, когда синий и зеленый выключены, я должен отправить следующие биты:
0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0
Чтобы иметь все (GRB) светодиоды с половиной яркости, я отправляю
0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1 0 1 1 1 1 1 1 1
WS2812 Сроки
WS2812 / B требует специальной синхронизации этих битов. Я настоятельно рекомендую прочитать статью Тима (« Библиотека Light_WS2812 V2.0 — Часть I: Понимание WS2812 ») о времени WS2812:
Сроки WS2812 (Источник: https://cpldcpu.wordpress.com/2014/01/14/light_ws2812-library-v2-0-part-i-understanding-the-ws2812/ )
WS2812B — это никогда не вариант, и имеет немного другое время. Более новые Adafruit NeoPixels все WS2812B. Поскольку время имеет некоторые допуски, я использую его для следующих битов ‘0’ и ‘1’:
WS2812B Сроки
Генерация битового потока
Существует несколько возможных способов создания такого потока битов. Одним из способов является использование вывода GPIO (общего назначения ввода / вывода) с Bit-Banging (переключение выходных выводов при нормальной работе порта GPIO):
void Init(void) {
GPIO_DDR = OUTPUT; /* initialize PORT as output */
GPIO_DATA = 0; /* bits low */
}
void PutBit(uint8_t bit) {
if (bit) { /* one bit */
GPIO_DATA = 1; /* put pin HIGH for 0.9 us */
WAIT_NS(900); /* wait 900 ns */
GPIO_DATA = 0; /* LOW for 0.35 us */
WAIT_NS(350); /* wait for 350 ns */
} else { /* zero bit */
GPIO_DATA = 1; /* put pin HIGH for 0.35 us */
WAIT_NS(350); /* wait 350 ns */
GPIO_DATA = 0; /* LOW for 0.9 us */
WAIT_NS(900); /* wait for 900 ns */
}
}
Даже улучшенная версия — избегать проверки битов:
void PutBit(uint8_t bit) {
GPIO_DATA = 1; /* put pin HIGH */
WAIT_NS(350); /* wait 350 ns */
GPIO_DATA = bit; /* LOW if zero bit, otherwise remains HIGH */
WAIT_NS(550); /* wait for 550 ns */
GPIO_DATA = 0; /* put pin LOW */
WAIT_NS(350); /* wait for 350 ns */
}
Запись битов порта для генерации сигнала
Теперь поместите все биты в массив и выполните потоковую передачу следующим образом:
#define NEO_NOF_PIXEL 2 /* number of pixels */
#define NEO_NOF_BITS_PIXEL 24 /* 24 bits for pixel */
#define NEO_DMA_NOF_BYTES sizeof(transmitBuf)
static uint8_t transmitBuff[NEO_NOF_PIXEL*NEO_NOF_BITS_PIXEL]; /* WS2812 Bits */
void Transmit(unsigned char bit) {
int i;
for(i=0; i<sizeof(transmitBuff); i++) {
PutBit(i);
}
}
Но частота сигнала 800 кГц! Таким образом, Bit-Banging не будет работать, если вы не используете ручной сборочный код для достижения скорости и времени. Кроме того, мне пришлось бы отключить прерывания, так как любое прерывание также может испортить время! Будет работать отключение прерываний на несколько светодиодов, но чем длиннее цепь светодиодов, тем дольше прерывания должны быть отключены. Определенно не достаточно хорош.
Использование таймера
Вместо того, чтобы все делать в цикле, почему бы не использовать вместо этого прерывания по таймеру? По сути, установка трех прерываний таймера срабатывает каждый на частоте 800 кГц, с синхронизацией, смещенной так, что она соответствует синхронизации WS2812. Таким образом, я могу использовать один таймер с тремя каналами: в соответствующих прерываниях я могу записывать данные в DATA для генерации битовой синхронизации:
Водительские биты с таймером прерывания
Это теперь намного лучше, чем делать вещи в цикле. Тем не менее, это означает, что прерывания происходят с частотой 3 × 800 кГц, или каждые ~ 0,35 мкс прерывания! Хлоп! Моя система будет занята прерываниями почти все время: для каждого прерывания ЦПУ необходимо сложить регистры, изменить контекст на подпрограмму обработки прерываний, выполнить операции с регистром DATA в подпрограмме и вернуться к исходному состоянию. контекст. Много операций!
Использование DMA
Так что, если переключение булавки слишком медленное, использование прерываний создает слишком много прерываний, почему бы не найти способ сделать это быстрее? Решением этого является DMA (прямой доступ к памяти) . DMA позволяет выполнять операции без использования процессора. DMA позволяет мне читать / записывать память (и даже больше) и является своего рода сопроцессором.
Я все еще использую этот таймер. Но вместо того, чтобы запускать прерывания, я вызову «прямую операцию памяти» в фоновом режиме, попросив записать мои данные непосредственно в (отображенный в память) регистр DATA:
Водительские биты с DMA
Таким образом, операция может выполняться в фоновом режиме, без участия процессора ? . И хорошая новость: многие современные микроконтроллеры имеют возможности DMA, и Kinetis K64F на плате FRDM-K64F тоже имеет это ? .
Резюме
В этом посте я объяснил принципы создания правильного потока битов для управления NeoPixels WS2812B. Так как сигнал высокочастотный, я должен использовать таймеры с DMA, иначе процессор будет загружен слишком сильно.
В следующей статье я собираюсь реализовать таймер с Kinetis SDK. Так что следите за обновлениями …
ССЫЛКИ
- Понимание сроков WS2812 и WS2812B: https://cpldcpu.wordpress.com/2014/01/14/light_ws2812-library-v2-0-part-i-understanding-the-ws2812/
- Руководство Adafruit по NeoPixels: https://learn.adafruit.com/adafruit-neopixel-uberguide/overview
- Плата Freescale Freedom FRDM-K64F: http://www.freescale.com/webapp/sps/site/prod_summary.jsp?code=FRDM-K64F
- Студия дизайна Freescale Kinetis: http://www.freescale.com/kds
- Freescale Kinetis SDK: http://www.freescale.com/kds