ws2812b-1-600x600-1000x1000

Контроллер для лент на ws2812. 1000 пикселей. Управление по uart. (Читай — USB через ft232).

Перед тем как браться за свою разработку управления лентой на ws2812, просмотрел много реализаций. Можно с помощью dma гнать данные через spi, тратя по байту памяти на каждый выходной бит. Тоже самое можно через таймер с pwm. Есть конструкция на встроенных логических элементах pic-контроллеров. Но там, кажется, без dma. Поэтому поставил себе ТЗ — размер 1000 пикселей, обновление около 30 раз в секунду, Массив данных —

#define LED_MAX 1001
struct RGB {
uint8_t Green; 
uint8_t Red;
uint8_t Blue;
};
struct RGB OutputArray[LED_MAX];

— по три байта на пиксель. Под это дело, с некоторым запасом по производительности подобрать наиболее дешёвый процессор.

 

Ещё до прочтения статьи про pic-и, у меня уже была идея прикрутить к выходу spi какую-нибудь элементарную логику для преобразования этого сигнала для светодиодной ленты . Под рукой была 74hc02 это четыре «или». И она прекрасно подошла. Суть идеи какая. Сигнал одного бита для ленты состоит, грубо говоря, из трёх частей. Первая и последняя одинаковые, а средняя меняется в зависимости от значения бита.timingДля выделения этой части я с помощью таймера сформировал дополнительный импульс. А логика  Безымянный2 просто выбирает что подать на выход. В начале и в конце бита это сигнал с sck, а в середине, во время импульса, sda. Ещё она служит выходным буфером, согласователем уровней, некоторой защитой.Безымянный

По фронту sck наступает фронт выходного сигнала, во время высокого уровня OutTIM выход зависит от выхода MOSI.

Теперь к реализации. Частота 52 МГц. С предделителем 64  для spi получается поток данных  в ленту 812500 бит в секунду. Ближайшее, что получилось подобрать.  Поскольку у нас spi идёт через dma, на кадр (у меня он 3kB,) нужно всего два прерывания.


void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi){
  HAL_TIM_IC_Stop(&htim3,TIM_CHANNEL_4);
  HAL_TIM_Base_Start_IT(&htim3);
}

Первое случается по окончании массива данных. В нём мы отключаем выход pwm таймера, но не останавливаем его. Это для паузы между кадрами. Когда он дотикает до переполнения случится второе прерывание, которое вновь запустит поток данных и включит pwm.


void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi){
 HAL_TIM_Base_Stop_IT(&htim3);
 HAL_TIM_IC_Start(&htim3,TIM_CHANNEL_4);
 HAL_SPI_Transmit_DMA(&hspi1, &OutputArray[0].Green, LED_MAX*3); 
}

Всё аппаратно! Ядро включается только в этих прерываниях. Всего 30 раз в секунду. Что интересно, скорость их обработки не критична. Я даже приоритеты не выставлял.

Сначала я пробовал на демо-плате с stm32f103. Когда заработало там,  перенёс в самый младший f030f4p6. Потом добавил туда i2c. Потом uart на 256кбод. Всё пашет.. Ещё и место осталось для всяких динамических эффектов. Причём я делал всё через cubeMX, со стандартными библиотеками HAL. Частота у него до 48МГц. На выходе получается 750кГц. Но на ней тоже всё работает. Это очень хорошо и кстати. Так как на этой частоте работает usb периферия в камнях постарше. Если надо больше пикселей, то надо брать контроллер с большим количеством каналов dma.

Был один неприятный момент. После паузы сброса, первый бит данных всегда получался длинным. От чего в первый пиксель попадал 0x80 в зелёный цвет. Хотел было этот диодик ставить на плату. Но потом подправив немного функцию запуска таймера всё заработало.