WS2812 и STM32Cube

в 16:15, , рубрики: Песочница, метки: , ,

На Geektimes уже были статьи, посвященные подключению светодиодов WS2812 к микроконтроллерам семейства STM32. Вот например эта. В ней достаточно подробно описан протокол общения с WS2812 и способ реализации этого протокола при помощи таймера и прямого доступа к памяти. Однако, те, кто, не побоюсь этого слова, программирует при помощи STM32Cube, наверняка задаются вопросом: «Какие же галки мне ставить, чтобы так получилось?» Попробуем им помочь.

Итак, запускаем STM32Cube, создаем новый проект, выбираем чип. Я буду использовать stm32f030f4p6. Это, наверно, самый «слабый» чип из линейки STM32, так что и на других чипах должно получиться.

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

image

То есть, нам нужно уметь генерировать импульсы длиной 0 мкс (для сброса), 0,4 мкс и 0,85 мкс. Для этого настроим таймер для работы в режиме ШИМ(PWM). Частота тактовых импульсов для таймера будет 20МГц и длина цикла пересчета 25 импульсов. Это даст нам нужную длительность цикла (1,25 мкс). Для генерации импульса нужной длительности мы должны будем поместить в регистр сравнения таймера соответственно 0 — для импульса нулевой длины, 8 — для импульса 0,4 мкс, соответствующего нулю и 16 для импульса 0,8 мкс, соответствующего единице.

Во вкладке «Pinout» настроим таймер для работы в режиме ШИМ.

image

Я буду использовать второй канал таймера TIM1, выход ШИМ которого связан с ногой PA9 чипа.

Чтобы получить требуемые 20 МГц, настроим на вкладке «Clock configuration» системный генератор тактовых импульсов на 40 МГц, которые затем поделим пополам предделителем таймера.

image

Теперь сконфигурируем сам таймер. На вкладке «Configuration» нажимаем кнопку «Tim1». В открывшемся окошке на вкладке «Parameter settings» задаем частоту тактовых импульсов 20Мгц. Чтобы поделить частоту системного генератора пополам в окошке «Prescaler» задаем 1 (0 соответствует коэффициенту деления 1). В окошке «Counter period» устанавливаем период пересчета таймера в 25 импульсов.

image

На вкладке «DMA settings» настраиваем контроллер прямого доступа к памяти на загрузку значения для следующего импульса без участия процессора. Жмем кнопку «Add» чтобы добавить новый запрос. В качестве источника запроса выбираем второй канал таймера TIM1, который мы используем. Обратите внимание, что нужный канал DMA выбирается автоматически, избавляя от необходимости лезть в документацию. Задаем направление передачи «Memory to peripherial» — будем загружать значения из массива в памяти в регистр сравнения таймера. Задаем «Mode» -«Circular», чтобы после окончания передачи всего массива, передача возобновлялась сначала. Задаем автоматическое увеличение адреса в памяти галкой «Increment address — Memory» и размер данных приемника «Half word» (помните, что на предыдущей вкладке размер регистра сравнения таймера был 16 бит?). Теперь завершение цикла пересчета таймера будет вызывать загрузку нового значения из массива в регистр сравнения.

image

И, наконец, на вкладке «GPIO settings» настроим связанный с таймером выход GPIO на работу на максимальной скорости. Не забываем нажать кнопку «Ok».

image

Все. Сохраняем проект и генерируем исходники для своей IDE.

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

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

Объявляем массив:

uint8_t buf[PREAMBLESIZE+LEDS*24];

где PREAMBLESIZE — размер преамбулы, не менее 40 (50/1,25=40 циклов таймера), и LEDS — количество светодиодов в линейке.

Заполняем массив, например так:

for (uint16_t i=0;i<[PREAMBLESIZE+LEDS*24;i++) 	buf[i]=(i<PREAMBLESIZE)?0:8;

И, наконец, запускаем таймер:

HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_2, (uint32_t *)buf,PREAMBLESIZE+LEDS*24);

Вот так выглядит результат на экране осциллографа (цена деления 10 мкс) при LEDS=1. Виден импульс сброса в 50 мкс и 24 битовых импульса.

image

А вот импульс при более близком рассмотрении (цена деления 1 мкс).

image

Автор: Абитура

Источник

Поделиться новостью

  1. Sanya:

    Огромное спасибо!
    Заработало с пол тычка.
    У Вас опечатка в коде:
    for (uint16_t i=0;i< ___[____ PREAMBLESIZE+LEDS*24;i++) buf[i]=(i<PREAMBLESIZE)?0:8;

* - обязательные к заполнению поля