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;

  2. Дима:

    и еще ошибка – таймер настроен неправильно, период пересчета не 25, а 25-1 нужно ставить, для 25 период для бита информации будет 1,3 мкс, а не 1,25. Эта ошибка не даст полностью правильно работать светодиодам

    • John2103:

      1.3 и 1.25 это не критично не пудрите мозг…. В даташите написаны допуски, а на многих сайтах есть исследования допусков и они значительно шире, чем в даташите

  3. Артур:

    Правильно называть эти китайские подели GRB а не RGB диодами именно в такой последовательности будут восприниматься биты данных цвета. И для тех кто попытается использовать данный код должен знать, что инициализация массива 8-ками передает нули на диоды, это означает, что если к выходу ШИМ подключить ленту, то ни один диод гореть не будет, ставьте 16.

  4. John2103:

    Большое спасибо за пояснения… У вас единственный сайт, где я нашел объяснения по поводу настроек и кодирования бит … скок тиков на “1” и “0” .. Очень жаль, что поздно :) Сам додуматься успел, но все равно может кто еще такой же как я будет и его благодарности не будет предела :))))))

  5. Dimaster:

    День добрый. Имеется пара вопросов.
    1. Вот по тому как написано, запихать массив точек через DMA в ШИМ никак не удалось. Заработало, когда я вызвал функцию HAL_TIM_PWM_Start_DMA также и в колбеке. Почему так? Про это нигде не написано.

    2. Как запустить несколько каналов разом с использованием DMA. Добавил в кубе еще два канала ШИМ и завязал их также на DMA, но работает в итоге только один. Тот, который вызван был первым. Остальные не работают. Как это правильно делается? Очень нужен трехфазный ШИМ.

    Заранее спасибо

  6. Алексей:

    Здравтвуйте, я совсем новичек в stm,
    у меня процессор stm32f103c8t6, если я хочу чтобы процессор работал на 72мгц
    какой мне таймер использовать (из 4) какие делители ставить.. и как это все посчитать
    например если 4 светодиода, и как обновлять массив цветов..

    стоит ли вообще пользоваться HAL(cubeMX) или лучше изезженой stand perh libr

    и еще вопрос можно ли чтобы DMA паралельно отправлял данные допустим на 9 портов (по принцыпу работы микросхем памяти)?

  7. bravo24:

    Очень толковая статья.
    Для начинающих – очень хорошо объяснено.
    Одно замечание. Если использовать исходники из поста https://m.geektimes.ru/post/255548/, оба поля Data Width на вкладке DMA Settings обязательно нужно установить в Half Word.

  8. DVF:

    Не завелось в STM32CubeIDE. Что-то не все рассказали ))

  9. DVF:

    Получается, что положительный импульс формируется таймером в одном периоде, а его отрицательное продолжение во втором?

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


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js