Варианты управления гирляндой / лентой

Валяется у меня stm32f303cct6 без дела + осталось 50 светодиодов ws2812, на cmsis в stm32cubeide нарисовал прогу, потихоньку (до НГ :slight_smile:) буду всякие эффекты придумывать, на будущее думаю как всем этим управлять:

  • яркость
  • скорость переключения программ
  • рабочая программа / случайный выбор

Можно конечно и экран прилепить и Wi-Fi и что то ещё монстрообразное, но хочется что то простое и эффективное, чтоб пользователи (жена например) мозг не включала и тупо все могла сделать.

Пока вижу такой вариант:
Три кнопки и три светодиода:

  • яркость - светик не горит=случайная, горит с разной яркостью = соответствует яркости ленты
    -скорость - аналогично, последовательно кнопкой перебирается скорость смены эффектов
    -программа - не горит=случайный выбор, горит светодиод - какая то фиксированная программа

У кого есть опыт - какие ещё удобные реализации управления лентой?

1 лайк

прикрепить микрофон

  • хлопки : переключение режимов-эффектов
  • а если “играть” на хрустальном бокале пальцем, то будет менятся яркость(или скорость ) эффекта.
1 лайк

Мысль конечно интересная, но вспомнив последний праздник, 20+ человек компании → страшный шум, особо не похлопаешь :thinking:

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

Хм…например визуализировать программу можно, т е нажимаю кнопку, программы по очереди меняются, ещё одно нажатие - программа фиксируется. Длительное нажатие - переход к случайном выбору программы.
А как этой же кнопкой перейти в режим выбора напрмпер яркости?
Особенно любопытно сделать без дисплея настройку количества светодиодов, хотя бы кратно 50 шт.

Сейчас пока выбираю random все параметры + меняется период изменения параметров, получается достаточно динамично и интересно, возможно не буду делать управление :thinking:

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

1 лайк

Понял, спасибо, буду думать :thinking:

Учите морзянку.

Я то выучу, боюсь жена не поймёт :crazy_face:

Реализовал управление двумя кнопками:

  • PGM/SET - программирование, след.нажатие-фиксация программы/параметра
  • SEL выбор программы/скорости/яркости

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

Спойлер
/**
 ******************************************************************************
 * @file           : main.c
 * @author         : Auto-generated by STM32CubeIDE
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2023 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */

#include <stdint.h>
#include "f303cct6.h"
#include "f3ws281Y.h"
#include <stdlib.h>
#include "ws281eff.h"

uint32_t SystemCoreClock = 8000000UL; /*!< System Clock Frequency (Core Clock) */

/*#if !defined(__SOFT_FP__) && defined(__ARM_FP)
  #warning "FPU is not initialized, but the project is compiling for an FPU. Please initialize the FPU before use."
#endif*/

unsigned char colorsData[count_bytes_in_buf];

int main(void)
{
	f303cct6init(); // init RCC
	f303initTIM4(); // init millis timer
	DWT_Delay_Init(); // init micros delay
	f303initC13led(); // init board led
	f303initA0A1keys(); // init keys PGM/SET and SEL

	f303setC13led(1);
	for (unsigned long j=100UL; j>0; --j) DWT_Delay_us(1000UL); // delay 0.1 sec -> one blynk led
	f303setC13led(0);

	f303initWS281X(); // init strip
	f303setBrBytes(0); // min bright -> 1%

	while (1) {
		// test led PC13 blink
		/*static unsigned long timerBlinkC13 = 0;
		static unsigned char ledStat = 0;
		if ((millis() - timerBlinkC13) >= 1000UL) {
			timerBlinkC13 = millis();
			f303setC13led(ledStat = !ledStat);
		}*/
		ws281loopWork((unsigned char *) colorsData);
		// -- end loop --
	}

}

/*
 * f303cct6.h
 *
 *  Created on: Feb 6, 2023
 *      Author: Andrey
 */

#ifndef F303CCT6_H_
#define F303CCT6_H_

#define keys_short_press ((unsigned long)50)
#define keys_long_press ((unsigned long)3000)

void f303cct6init(void);
void f303initC13led(void);
void f303setC13led(unsigned char AledONOFF);
void f303initTIM4(void);
unsigned long millis(void);
void DWT_Delay_Init(void);
void DWT_Delay_us(unsigned long au32_microseconds);
void f303initA0A1keys(void);
unsigned char f303readA0(void);
unsigned char f303readA1(void);
unsigned char f303keysA0A1read(void); // out bit0 short press A0, bit1-A1, out bit2 long press A0, bit3-A1

#endif /* F303CCT6_H_ */


/*
 * f303cct6.c
 *
 *  Created on: Feb 6, 2023
 *      Author: Andrey
 */

#include "f303cct6.h"
#include <stm32f30x.h>

volatile unsigned long msTicks = 0;

unsigned char f303keysA0A1read(void) { // out bit0 short press A0, bit1-A1, out bit2 long press A0, bit3-A1
	unsigned char outByte = 0;
	static unsigned char lastLevel[] = {1, 1};
	static unsigned char flagKeysDown[] = {0, 0};
	static unsigned long startTimeDown[2];
	unsigned char currentLevel[] = {f303readA0(), f303readA1()};
	for(int i = 0; i < 2; ++i) { // 2 keys
		if (currentLevel[i] != lastLevel[i]) { // change level
			if (currentLevel[i] == 0) { // key down
				if (flagKeysDown[i] == 0) { // no timer
					flagKeysDown[i] = 1; // begin timer
					startTimeDown[i] = millis();
				}
			} else { // key up
				if (flagKeysDown[i] != 0) { // timer started
					if ((millis() - startTimeDown[i]) >= 500) { // error press
						flagKeysDown[i] = 0; // stop timer
					} else if ((millis() - startTimeDown[i]) >= keys_short_press) { // end timer
						flagKeysDown[i] = 0; // stop timer - short press
						outByte |= 1 << i;
					}
				}
			}
		} else { // level not change
			if ((flagKeysDown[i] != 0) && (currentLevel[i] == 0)) { // timer started and key down
				if ((millis() - startTimeDown[i]) >= keys_long_press) { // end timer
					flagKeysDown[i] = 0; // stop timer - long press
					outByte |= 1 << (i+2);
				}
			} else if (currentLevel[i] != 0) { // key up
				flagKeysDown[i] = 0; // reset timer
			}
		}
		lastLevel[i] = currentLevel[i]; // save current level key
	}
	return outByte;
}

unsigned char f303readA0(void) {
	if (GPIOA->IDR & GPIO_IDR_0) return 1; else return 0;
}

unsigned char f303readA1(void) {
	if (GPIOA->IDR & GPIO_IDR_1) return 1; else return 0;
}

void f303initA0A1keys(void) {
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN; // Включаем тактирование порта GPIOA
	GPIOA->MODER &= ~GPIO_MODER_MODER0; // для начала все сбрасываем в ноль -> 00: Input mode (reset state)
	GPIOA->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR0; // 00: Low speed
	GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR0; // No pull-up, pull-down
	GPIOA->PUPDR |= GPIO_PUPDR_PUPDR0_0; // pull-up 01
	GPIOA->MODER &= ~GPIO_MODER_MODER1; // для начала все сбрасываем в ноль -> 00: Input mode (reset state)
	GPIOA->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR1; // 00: Low speed
	GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR1; // No pull-up, pull-down
	GPIOA->PUPDR |= GPIO_PUPDR_PUPDR1_0; // pull-up 01
}

void TIM4_IRQHandler(void) {
    TIM4->SR &= ~TIM_SR_UIF;
	++msTicks;
}

void f303cct6init(void) {
	// TODO Auto-generated constructor stub
	  volatile int StartUpCounter;
	  RCC->CR |= RCC_CR_HSEON; //Запускаем генератор HSE
	  for(StartUpCounter=0; ; StartUpCounter++) { //Ждем успешного запуска или окончания тайм-аута
	    if(RCC->CR & RCC_CR_HSERDY) break; //Если успешно запустилось, то выходим из цикла
	    if(StartUpCounter > 0x1000) { //Если не запустилось, то //отключаем все, что включили
	      RCC->CR &= ~RCC_CR_HSEON; //Останавливаем HSE
	      return; //и возвращаем ошибку
	    }
	  }
	  RCC->CFGR |= RCC_CFGR_PLLMULL; // 0111: PLL input clock x 9 //PLL множитель равен 9 //Настраиваем и запускаем PLL
	  RCC->CFGR &= ~RCC_CFGR_PLLMULL_3; // 0111: PLL input clock x 9
	  RCC->CFGR |= RCC_CFGR_PLLSRC; // 1: HSE/PREDIV selected as PLL input clock //Тактирование PLL от HSE
	  RCC->CR |= RCC_CR_PLLON; // Запускаем PLL
	  for(StartUpCounter=0; ; StartUpCounter++) { // Ждем успешного запуска или окончания тайм-аута
	    if(RCC->CR & RCC_CR_PLLRDY) break; //Если успешно запустилось, то //выходим из цикла
	    if(StartUpCounter > 0x1000) { //Если по каким-то причинам не запустился PLL, то //отключаем все, что включили
	      RCC->CR &= ~RCC_CR_HSEON; //Останавливаем HSE
	      RCC->CR &= ~RCC_CR_PLLON; //Останавливаем PLL
	      return; //и возвращаем ошибку
	    }
	  }
	  //Настраиваем FLASH и делители
	  //Устанавливаем 2 цикла ожидания для Flash
	  //так как частота ядра у нас будет 48 MHz < SYSCLK <= 72 MHz
	  FLASH->ACR |= FLASH_ACR_LATENCY_1; // 010: Two wait sates, if 48 < HCLK ≤ 72 MHz
	  //Делители
	  RCC->CFGR &= ~RCC_CFGR_PPRE2; //Делитель шины APB2 отключен // 0xx: HCLK not divided
	  RCC->CFGR &= ~RCC_CFGR_PPRE1; //Делитель нишы APB1 равен 2 // 100: HCLK divided by 2
	  RCC->CFGR |= RCC_CFGR_PPRE1_2; // 100: HCLK divided by 2
	  RCC->CFGR &= ~RCC_CFGR_HPRE; //Делитель AHB отключен // 0xxx: SYSCLK not divided
	  RCC->CFGR |= RCC_CFGR_SW_1; //Переключаемся на работу от PLL // 10: PLL selected as system clock
	  while(!(RCC->CFGR & RCC_CFGR_SWS_1)); // Ждем, пока переключимся // 10: PLL used as system clock
	  //Настройка и переклбючение сисемы
	  //на внешний кварцевый генератор
	  //и PLL запершилось успехом.
	  SystemCoreClock = 72000000UL;
	  //Выходим
}

void f303initC13led(void) {
    RCC->AHBENR |= RCC_AHBENR_GPIOCEN; // Включаем тактирование порта GPIOC
	GPIOC->MODER &= ~GPIO_MODER_MODER13; // для начала все сбрасываем в ноль -> 00: Input mode (reset state)
	GPIOC->MODER |=  GPIO_MODER_MODER13_0; // 01: General purpose output mode
	GPIOC->OTYPER &= ~GPIO_OTYPER_OT_13; // 0: Output push-pull (reset state)
	GPIOC->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR13; // 00: Low speed
	GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR13; // No pull-up, pull-down
}

void f303setC13led(unsigned char AledONOFF) {
	if (AledONOFF) GPIOC->BRR |= GPIO_BRR_BR_13;
	else GPIOC->BSRR |= GPIO_BSRR_BS_13;
}

void f303initTIM4(void) {
	RCC->APB1ENR |= RCC_APB1ENR_TIM4EN; // clock Timer4 ON
	TIM4->SR = 0;
	TIM4->CNT = 0;
	TIM4->PSC = (SystemCoreClock / 2 / 1000000) - 1; // делитель
	TIM4->ARR = 1999; // значение перезагрузки -> 1 msec
	TIM4->DIER |= TIM_DIER_UIE;
	NVIC_EnableIRQ (TIM4_IRQn);
	TIM4->CR1 |= TIM_CR1_CEN; // включаем счётчик
    TIM4->SR &= ~TIM_SR_UIF;
}

unsigned long millis(void) {
	return msTicks;
}

/*
 * https://deepbluembedded.com/stm32-delay-microsecond-millisecond-utility-dwt-delay-timer-delay/
 */
void DWT_Delay_Init(void) {
    /* Disable TRC */
    CoreDebug->DEMCR &= ~CoreDebug_DEMCR_TRCENA_Msk; // ~0x01000000;
    /* Enable TRC */
    CoreDebug->DEMCR |=  CoreDebug_DEMCR_TRCENA_Msk; // 0x01000000;
    /* Disable clock cycle counter */
    DWT->CTRL &= ~DWT_CTRL_CYCCNTENA_Msk; //~0x00000001;
    /* Enable  clock cycle counter */
    DWT->CTRL |=  DWT_CTRL_CYCCNTENA_Msk; //0x00000001;
    /* Reset the clock cycle counter value */
    DWT->CYCCNT = 0;
    /* 3 NO OPERATION instructions */
    __ASM volatile ("NOP");
    __ASM volatile ("NOP");
    __ASM volatile ("NOP");
}

/*
 * This Function Provides Delay In Microseconds Using DWT
 * https://deepbluembedded.com/stm32-delay-microsecond-millisecond-utility-dwt-delay-timer-delay/
 */
void DWT_Delay_us(unsigned long au32_microseconds) {
	  uint32_t au32_initial_ticks = DWT->CYCCNT;
	  uint32_t au32_ticks = (SystemCoreClock / 1000000);
	  au32_microseconds *= au32_ticks;
	  while ((DWT->CYCCNT - au32_initial_ticks) < au32_microseconds-au32_ticks);
}


/*
 * f3ws281Y.h
 *
 *  Created on: Feb 3, 2023
 *      Author: Andrey
 */

#ifndef INC_F3WS281Y_H_
#define INC_F3WS281Y_H_

#ifndef count_of
#define count_of(a) (sizeof(a)/sizeof((a)[0]))
#endif

#define numLEDSws281X ((unsigned short)25)

#define ws281x_bytes_per_pixel ((unsigned short)3) // RGB/RGBW // число байт на пиксель, используется для расчета максимального числа пикселей в буфере
#define ws281x_max_count_bytes ((unsigned short)16384) // size PWM DMA buffer // размер буфера в байтах -> один байт = один бит ленты
// 16384 bytes -> 600 pixels RGB

#define ws281y_max_bright_percent ((unsigned char)3) // максимально возможно выставляемая яркость в процентах из учета мощности БП и количества светодиодов

#define count_bytes_in_buf ((unsigned short)(numLEDSws281X*ws281x_bytes_per_pixel))

#define ws281x_arr_timer_125_usec ((unsigned short)89) // значение перезагрузки таймера // период 1.25 микросекунды
#define ws281x_ccr_timer_full_high ((unsigned short)(ws281x_arr_timer_125_usec+1)) // значение перезагрузки таймера для постоянной HIGH на пине
#define ws281x_ccr_th0_timer ((unsigned short)(ws281x_arr_timer_125_usec*0.32)) // краткая часть периода ws2812
#define ws281x_ccr_th1_timer ((unsigned short)(ws281x_arr_timer_125_usec-ws281x_ccr_th0_timer)) // длинная часть периода ws2812
#define ws281x_arr_timer_reset_bus ((unsigned short)((50/1.25+1)*ws281x_arr_timer_125_usec)) // время сброса шины

void f303initWS281X(void);
void f303sendWS281X(unsigned char * inData, unsigned short dataCount);
unsigned char f303busyWS281X(void);
void f303setBrBytes(unsigned char inVal); // установка яркости 0...255

#endif /* INC_F3WS281Y_H_ */


/*
 * f3ws281Y.c
 *
 *  Created on: Feb 3, 2023
 *      Author: Andrey
 */

#include <stm32f30x.h>
#include "f3ws281Y.h"

volatile unsigned char ws281Xmode = 0; // в процессе отправки
unsigned char ws281Ybrval = 1; // яркость в процентах

unsigned short ws281XrealSizeDMAbuf; // реальный размер данных в буфере, кратно количеству пикселей * размер пикселя * 8
unsigned char ws281XbufPWM[ws281x_max_count_bytes+1]; // сам буфер для отправки // +1 байт для подлнялия GPIO вверх после отправки последнего байта

unsigned char f303busyWS281X(void) {
	return ws281Xmode;
}

void f303setBrBytes(unsigned char inVal) { // установка яркости в процентах
	if (inVal>100) ws281Ybrval = 100;
	else if (inVal==0) ws281Ybrval = 1;
	else ws281Ybrval = inVal;
}

void prepareDMAbuf(const unsigned char * inData, const unsigned short dataCount) { // заполняем буфер для ШИМ
	unsigned short maxBytesBuf = ws281x_max_count_bytes / 8; // сколько всего байт в буфер влезет
	unsigned short maxPixelsBuf = maxBytesBuf / ws281x_bytes_per_pixel; // Сколько всего пикселей в буфер влезет
	maxBytesBuf = maxPixelsBuf * ws281x_bytes_per_pixel; // сколько всего байт в буфер влезет, кратно одному пикселю
    if (dataCount > maxBytesBuf) { // число отправляемых байт больше чем размер буфера
    	ws281XrealSizeDMAbuf = maxBytesBuf * 8;
    } else { // все влезет в один буфер
    	ws281XrealSizeDMAbuf = dataCount * 8;
    	maxBytesBuf = dataCount;
    }
    for (unsigned short i=0; i<maxBytesBuf; ++i) {
    	unsigned char inByte = *(inData+i);
    	inByte = (unsigned char)(((unsigned short)((unsigned short)(inByte)*(unsigned short)(ws281Ybrval)))/100);
    	for (unsigned char j=8; j>0; j--) {
    		if (inByte & (1<<(j-1))) ws281XbufPWM[i*8+(8-j)] = ws281x_ccr_th1_timer;
    		else ws281XbufPWM[i*8+(8-j)] = ws281x_ccr_th0_timer;
    	}
    }
    ws281XbufPWM[maxBytesBuf*8] = 0; // последним байтом пропишем ШИМ с минимальным заполнением, опустить линию в ноль после последнего пикселя
    ++ws281XrealSizeDMAbuf; // на один байт увеличим количество отправляемых данных
}

void f303initPB0pwm(void) {
	GPIOB->BSRR |= GPIO_BSRR_BS_0; // PB0 HIGH
	GPIOB->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR0; // 00: Low speed
	GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR0; // No pull-up, pull-down
	GPIOB->OTYPER &= ~GPIO_OTYPER_OT_0; // 0: Output push-pull (reset state)
	GPIOB->AFR[0] &= 0x0F; // clear AF by PB0
	GPIOB->AFR[0] |= 2; // Назначаем PB0 выводу альтернативную функцию AF2
	GPIOB->MODER &= ~GPIO_MODER_MODER0; // для начала все сбрасываем в ноль -> 00: Input mode (reset state)
	GPIOB->MODER |=  GPIO_MODER_MODER0_1; // 10: Alternate function mode
}

void f303initTIM3pwmCH3(void) {
	TIM3->CR1 &= ~(TIM_CR1_CEN); //останавливаем таймер
	TIM3->CNT = 0; //Очищаем счетный регистр
	TIM3->PSC = 0; // делитель x1 72000000 MHz
	TIM3->ARR = ws281x_arr_timer_125_usec; // значение перезагрузки // 1.25 usec
	TIM3->CCR3 = ws281x_ccr_timer_full_high; // коэф. заполнения
	TIM3->CCMR2 &= ~(TIM_CCMR2_OC3M); //сбрасываем все биты OCxM
	TIM3->CCMR2 |= TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3PE; // настроить канал
	TIM3->CCER  = TIM_CCER_CC3E; // настроим на выход канал 3
	TIM3->BDTR = TIM_BDTR_MOE; // разрешим использовать выводы таймера как выходы
	TIM3->CR1 &= ~TIM_CR1_DIR; // считаем вверх
	TIM3->CR1 &= ~TIM_CR1_CMS; // выравнивание по фронту, Fast PWM
	TIM3->CR1 |= TIM_CR1_ARPE;    //Регистры таймера с буферизацией
}

void DMA1_CH2_IRQHandler(void) {
	DMA1->IFCR |= DMA_IFCR_CTCIF2; // Сбрасываем флаг прерываний
	DMA1_Channel2->CCR &= ~(DMA_CCR_EN); //Отключаем канал DMA
	TIM3->CR1 &= ~(TIM_CR1_CEN); //останавливаем таймер
	ws281Xmode = 0; // закончили передачу
}

void TIM3_IRQHandler(void) { //прерывание от таймера //Сюда попадаем после завершения формирования //сигнала RET шины ws2812b
	TIM3->SR &= ~(TIM_SR_UIF); //сбрасываем флаг прерывания
	TIM3->CR1 &= ~(TIM_CR1_CEN); //останавливаем таймер
	TIM3->DIER &= ~TIM_DIER_UIE; //прерывание не вызывать
	TIM3->CNT = 0; //Очищаем счетный регистр
	TIM3->ARR = ws281x_arr_timer_125_usec; // значение перезагрузки // 1.25 usec
	TIM3->CCR3 = 0; // коэф. заполнения // WS2812 -> reset bus // PA8 LOW
	TIM3->DIER |= TIM_DIER_CC3DE; //Разрешить запрос DMA
	DMA1_Channel2->CNDTR = ws281XrealSizeDMAbuf; // передаем нужное количество
    TIM3->CR1 |= TIM_CR1_CEN; //Запускаем таймер
	DMA1_Channel2->CCR |= DMA_CCR_EN; // включаем передачу данных
}

void f303initDMA1CH2(void) {
	DMA1_Channel2->CCR &= ~DMA_CCR_EN; // отключаем любое действие канала если идет передача
	DMA1_Channel2->CPAR = (unsigned long)(&TIM3->CCR3); //заносим адрес регистра куда кидать
	DMA1_Channel2->CMAR = (unsigned long)((unsigned char *)(ws281XbufPWM)); //заносим адрес данных в регистр CMAR
	DMA1_Channel2->CCR &= ~DMA_CCR_MEM2MEM; //режим MEM2MEM отключен
	DMA1_Channel2->CCR &= ~DMA_CCR_PL; //приоритет низкий
	DMA1_Channel2->CCR &= ~DMA_CCR_MSIZE; //разрядность данных в памяти 8 бит // 00: 8-bits
	DMA1_Channel2->CCR &= ~DMA_CCR_PSIZE; //разрядность регистра данных 16 бит
	DMA1_Channel2->CCR |= DMA_CCR_PSIZE_0; //разрядность регистра данных 16 бит
	DMA1_Channel2->CCR |= DMA_CCR_MINC; // включить инкремент адреса памяти
	DMA1_Channel2->CCR &= ~DMA_CCR_PINC; //Инкремент адреса периферии отключен
	DMA1_Channel2->CCR &= ~DMA_CCR_CIRC; //кольцевой режим отключен
	DMA1_Channel2->CCR |= DMA_CCR_DIR; //1 - из памяти в периферию
	DMA1->IFCR |= DMA_IFCR_CTCIF2; // Сбрасываем флаг прерываний
    DMA1_Channel2->CCR |= DMA_CCR_TCIE; //прерывание завершения передачи
    NVIC_EnableIRQ(DMA1_Channel2_IRQn); //от DMA
    NVIC_SetPriority(DMA1_Channel2_IRQn,14);
}

void f303initWS281X(void) {
	ws281Ybrval = 1; // яркость минимальная
	RCC->AHBENR |= RCC_AHBENR_GPIOBEN; // Включаем тактирование порта GPIOB
	  RCC->AHBENR |= RCC_AHBENR_DMA1EN; // DMA1
	RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; // clock Timer3 ON
  f303initPB0pwm();
  f303initDMA1CH2();
  f303initTIM3pwmCH3();
}

void f303sendWS281X(unsigned char * inData, unsigned short dataCount) {
	ws281Xmode = 1; // заняты передачей
	DMA1_Channel2->CCR &= ~(DMA_CCR_EN); //Отключаем канал DMA
	TIM3->CR1 &= ~(TIM_CR1_CEN); //останавливаем таймер
	prepareDMAbuf(inData, dataCount);
	TIM3->DIER &= ~TIM_DIER_CC3DE; // запретить запрос DMA
	TIM3->CNT = 0; //Очищаем счетный регистр
	TIM3->ARR = ws281x_arr_timer_reset_bus; // значение перезагрузки // 50+ usec // (50/1,25+1)*89 = 3649
	TIM3->CCR3 = 0; // коэф. заполнения // WS2812 -> reset bus // PA8 LOW
	TIM3->SR &= ~(TIM_SR_UIF); //сбрасываем флаг прерывания
	TIM3->DIER |= TIM_DIER_UIE; //прерывание по обновлению
	NVIC_EnableIRQ(TIM3_IRQn); // включим прерывание таймера
    NVIC_SetPriority(TIM3_IRQn,13);
	TIM3->CR1 |= TIM_CR1_CEN; //Поехали считать!
}



/*
 * ws281eff.h
 *
 *  Created on: 6 февр. 2023 г.
 *      Author: Andrey
 */

#ifndef WS281EFF_H_
#define WS281EFF_H_

#define control_wait_unuse_period ((unsigned long)20000) // время бездействия миллисекунд, после которого возврат в рабочий режим

#define ws281_min_period_change_pattern ((unsigned long)5) // минимальное время между сменой эффектов миллисекунд
#define ws281_max_period_change_pattern ((unsigned long)30) // максимальное время между сменой эффектов миллисекунд
#define ws281_min_work_pattern ((unsigned short)900) // число запусков одной программы до смены

void ws281loopWork(unsigned char * outBuf);

#endif /* WS281EFF_H_ */


/*
 * ws281eff.c
 *
 *  Created on: 6 февр. 2023 г.
 *      Author: Andrey
 */

#include <stdlib.h>
#include "ws281eff.h"
#include "f3ws281Y.h"
#include "f303cct6.h"

unsigned char stripInWork = 1; // гирлянда не в режиме настройки
unsigned char currentPattern = 0xFF; // bit7=1 -> random
unsigned char currentSpeed = 0xFF; // bit7=1 -> random

unsigned short tLoop= 0; // глобальный счетчик, должен быть минимум в 4 раза длиннее количества светодиодов, что бы все программы целиком успели отработать

typedef enum {modeChangePattern, modeChangeSpeed} ctrlMode;

void pattern_random(unsigned char * oneBuf, unsigned short tIn) {
    if (tIn % 8) return;
    for (unsigned short i = 0; i < count_bytes_in_buf; ++i) *(oneBuf+i) = random();
}

void pattern_snakes(unsigned char * oneBuf, unsigned short tIn) {
    for (unsigned short i = 0; i < numLEDSws281X; ++i) {
        unsigned short x = (i + (tIn >> 1)) % 64;
        if (x < 10){
        	*(oneBuf+i*ws281x_bytes_per_pixel) = 0xFF; // R
        } else if (x >= 15 && x < 25) {
        	*(oneBuf+i*ws281x_bytes_per_pixel+1) = 0xFF; // G
        } else if (x >= 30 && x < 40) {
        	*(oneBuf+i*ws281x_bytes_per_pixel+2) = 0xFF; // B
        } else {
        	*(oneBuf+i*ws281x_bytes_per_pixel) = 0x00; // R
        	*(oneBuf+i*ws281x_bytes_per_pixel+1) = 0x00; // G
        	*(oneBuf+i*ws281x_bytes_per_pixel+2) = 0x00; // B
        }
        if (ws281x_bytes_per_pixel>3) *(oneBuf+i*ws281x_bytes_per_pixel+3) = 0x00; // W
    }
}

void pattern_sparkle(unsigned char * oneBuf, unsigned short tIn) {
    if (tIn % 8)  return;
    for (unsigned short i = 0; i < numLEDSws281X; ++i) {
    	unsigned char putByte = random() % 16 ? 0 : 0xFF;
    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+i*ws281x_bytes_per_pixel+j) = putByte;
    }
}

void pattern_greys(unsigned char * oneBuf, unsigned short tIn) {
    for (unsigned short i = 0; i < numLEDSws281X; ++i) {
    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+i*ws281x_bytes_per_pixel+j) = (unsigned char)tIn;
    }
}

void pattern_blink(unsigned char * oneBuf, unsigned short tIn) {
	static unsigned char cR, cG, cB;
	static unsigned short currPos = 0;
	static unsigned short currPeriod;
	if (!currPos) { // start new
		cR = random(); cG = random(); cB = random();
		for (unsigned short i = 0; i < numLEDSws281X; ++i) {
			*(oneBuf+i*ws281x_bytes_per_pixel) = cR;
			*(oneBuf+i*ws281x_bytes_per_pixel+1) = cG;
			*(oneBuf+i*ws281x_bytes_per_pixel+2) = cB;
			if (ws281x_bytes_per_pixel>3) *(oneBuf+i*ws281x_bytes_per_pixel+3) = cR+cG+cB;
		}
		++currPos;
		currPeriod = 20 + (random() >> 2);
	} else { // cont
		if (currPos == 3) for (unsigned short i = 0; i < count_bytes_in_buf; ++i) *(oneBuf+i) = 0;
		if ((++currPos) >= currPeriod) currPos = 0;
	}
}

void pattern_fillr(unsigned char * oneBuf, unsigned short tIn) {
    if (tIn % 4)  return;
	static unsigned short i = 0;
	static unsigned char fillMode = 0;
	switch (fillMode) {
		case 0: {
	    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+i*ws281x_bytes_per_pixel+j) = random();
			break;
		}
		case 1: {
	    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+i*ws281x_bytes_per_pixel+j) = 0;
			break;
		}
		case 2: {
	    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+(numLEDSws281X-i-1)*ws281x_bytes_per_pixel+j) = random();
			break;
		}
		case 3: {
	    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+(numLEDSws281X-i-1)*ws281x_bytes_per_pixel+j) = 0;
			break;
		}
		default: {}
	}
	if ((++i) >= numLEDSws281X) {
		i = 0;
		if ((++fillMode) >= 4) fillMode = 0;
	}
}

void pattern_bomb(unsigned char * oneBuf, unsigned short tIn) {
	static unsigned short s = 16;
    if (tIn % s)  return;
	static unsigned short i = 0;
	static unsigned char fillMode = 0;
	static unsigned char c1R, c2R, c1G, c2G, c1B, c2B;
	if ((!i) && (!fillMode)) {
		c1R = random(); c1G = random(); c1B = random();
		c2R = random(); c2G = random(); c2B = random();
		s = 16;
	}
	switch (fillMode) {
		case 0: {
	    	*(oneBuf+i*ws281x_bytes_per_pixel) = c1R;
	    	*(oneBuf+i*ws281x_bytes_per_pixel+1) = c1G;
	    	*(oneBuf+i*ws281x_bytes_per_pixel+2) = c1B;
	    	*(oneBuf+(numLEDSws281X-i-1)*ws281x_bytes_per_pixel) = c2R;
	    	*(oneBuf+(numLEDSws281X-i-1)*ws281x_bytes_per_pixel+1) = c2G;
	    	*(oneBuf+(numLEDSws281X-i-1)*ws281x_bytes_per_pixel+2) = c2B;
			if (ws281x_bytes_per_pixel>3) {
				*(oneBuf+i*ws281x_bytes_per_pixel+3) = c1R+c1G+c1B;
				*(oneBuf+(numLEDSws281X-i-1)*ws281x_bytes_per_pixel+3) = c2R+c2G+c2B;
			}
			if (i) { // clear prev pixel
		    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+(i-1)*ws281x_bytes_per_pixel+j) = 0;
		    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+(numLEDSws281X-i)*ws281x_bytes_per_pixel+j) = 0;
			}
			break;
		}
		case 1: { // full fill pixel
	    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+((numLEDSws281X >> 1)-1-i)*ws281x_bytes_per_pixel+j) = 0xFF;
	    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+((numLEDSws281X >> 1)+i)*ws281x_bytes_per_pixel+j) = 0xFF;
			break;
		}
		default: {}
	}
	if ((++i) >= (numLEDSws281X >> 1)) { // half strip
		i = 0;
		if ((++fillMode) >= 2) {
			fillMode = 0;
			for (unsigned short i = 0; i < count_bytes_in_buf; ++i) *(oneBuf+i) = 0;
		} else {
			s = 4;
		}
	}
}

typedef void (*pattern)(unsigned char * oneBuf, unsigned short tIn);
const struct {
    pattern pat;
} pattern_table[] = {
        {pattern_snakes},
        {pattern_random},
		{pattern_sparkle},
		{pattern_greys},
		{pattern_blink},
		{pattern_fillr},
		{pattern_bomb}
};

void showOnePattern(unsigned char * outBuf, unsigned char onePat, unsigned short inTloop) {
	pattern_table[onePat].pat(outBuf, inTloop);
	f303sendWS281X(outBuf, count_bytes_in_buf);
	while(f303busyWS281X());
}

unsigned char getCountPattern() {
	return count_of(pattern_table);
}

void keysControlLoop(unsigned char * outBuf) {
	unsigned char outKeys = f303keysA0A1read();
	static unsigned char ctrlCurrentPattern; // bit7=1 -> random
	static unsigned char ctrlCurrentSpeed; // bit7=1 -> random
	static ctrlMode currentMode;
	static unsigned long timerControl = 0;
	if (outKeys & 1) { // short press A0
		timerControl = millis();
		if (stripInWork) { // start control mode
			stripInWork = 0;
			currentMode = modeChangePattern;
			ctrlCurrentPattern = 0xFF;
			ctrlCurrentSpeed = 0xFF;
		    for (unsigned short i = 0; i < numLEDSws281X; ++i) {
		    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(outBuf+i*ws281x_bytes_per_pixel+j) = 0;
		    }
			f303sendWS281X(outBuf, count_bytes_in_buf);
			while(f303busyWS281X());
		} else { // save selected mode and goto next param
			switch (currentMode) {
				case modeChangePattern: {
					currentPattern = ctrlCurrentPattern;
					++currentMode;
					break;
				}
				case modeChangeSpeed: {
					currentSpeed = ctrlCurrentSpeed;
					stripInWork = 1;
					break;
				}
				default:{}
			}
			tLoop = 2 * ws281_min_work_pattern; // reset counter
		}
	} else if (outKeys & 2) { // short press A1
		timerControl = millis();
		switch (currentMode) {
			case modeChangePattern: {
				if (ctrlCurrentPattern & 0x80) {
					ctrlCurrentPattern = 0;
				} else if ((++ctrlCurrentPattern) >= getCountPattern())  {
					ctrlCurrentPattern = 0xFF;
				}
				break;
			}
			case modeChangeSpeed: {
				if (ctrlCurrentSpeed & 0x80) {
					ctrlCurrentSpeed = 0;
				} else {
					ctrlCurrentSpeed += 10;
					if (ctrlCurrentSpeed >= 101)  {
						ctrlCurrentSpeed = 0xFF;
						}
				}
				break;
			}
		}
	}
	if (!stripInWork) {
		if ((millis() - timerControl) >= control_wait_unuse_period) { // unuse control
			stripInWork = 1;
		} else { // show current parameters
			static unsigned long showControl = 0;
			unsigned char showPat;
			unsigned long showSpeed;
			if ((ctrlCurrentPattern & 0x80)) {
				showPat = random() % getCountPattern();
			} else {
				showPat = ctrlCurrentPattern;
			}
			if (ctrlCurrentSpeed & 0x80) {
				showSpeed = ws281_min_period_change_pattern + (random() % 101) * (ws281_max_period_change_pattern - ws281_min_period_change_pattern) / 100;
			} else {
				showSpeed = ws281_min_period_change_pattern + (ctrlCurrentSpeed % 101) * (ws281_max_period_change_pattern - ws281_min_period_change_pattern) / 100;
			}
			if ((millis() - showControl) >= showSpeed) {
				showControl = millis();
				showOnePattern(outBuf, showPat, tLoop);
				++tLoop;
			}
			if (tLoop >= ws281_min_work_pattern) tLoop = 0;
		}
	}
}

void ws281loopWork(unsigned char * outBuf) {
	static unsigned char currentPat = 0; // текущая рабочая программа
	static unsigned short patChange = ws281_min_work_pattern; // окончание глобального счетчика, после чего устанавливается скорость и программа
	static unsigned long timerSpeed = 0; // таймер запуска каждой программы в цикле
	static unsigned long periodSpeed = ws281_min_period_change_pattern; // период запуска программы в цикле
	keysControlLoop(outBuf);
	if (!stripInWork) return;
	if (tLoop >= patChange) { // закончился глобальный счетчик
		patChange = ws281_min_work_pattern + (unsigned char)random(); // в небольших пределах меняем время смены программ
		if (currentPattern & 0x80) { // если случайный выбор программы
			currentPat = random() % getCountPattern();
		} else { // если фиксированная программа
			currentPat = currentPattern;
		}
		if (currentSpeed & 0x80) { // если случайный выбор скорости
			periodSpeed = ws281_min_period_change_pattern + (random() % 101) * (ws281_max_period_change_pattern - ws281_min_period_change_pattern) / 100;
		} else { // фиксированная скорость
			periodSpeed = ws281_min_period_change_pattern + (currentSpeed % 101) * (ws281_max_period_change_pattern - ws281_min_period_change_pattern) / 100;
		}
		tLoop = 0;
	}
	if ((millis() - timerSpeed) >= periodSpeed) {
		timerSpeed = millis();
		showOnePattern(outBuf, currentPat, tLoop);
		++tLoop;
	}
}

Скорость или частоту переключений можно сопоставлять с цветом - фиолетовая лента это самая большая частота, красная - маленькая. А шкалой - настройку количества светодиодов :slight_smile:

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

сделал шкалой, первыми 10 светодиодами, отображение скорости, вполне норм.

настройку количества светодиодов не планирую пока делать, вряд ли проект дальше моей новогодней елки пойдет. В STM32 нет EEPROM, городить внешнюю лень.

Переделанные функции управления периферией и вывода на ленту WS2812 под более распространенный МК STM32F103C8T6/CH32F103C8T6

Спойлер
/**
 ******************************************************************************
 * @file           : main.c
 * @author         : Auto-generated by STM32CubeIDE
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2023 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */

#include <stdint.h>
#include "stm32f10x.h"
#include <stdlib.h>

unsigned long SystemCoreClock = 8000000UL; /*!< System Clock Frequency (Core Clock) */
volatile unsigned long msTicks = 0;

#ifndef count_of
#define count_of(a) (sizeof(a)/sizeof((a)[0]))
#endif

#define numLEDSws281X ((unsigned short)5)

#define ws281x_bytes_per_pixel ((unsigned short)4) // RGB/RGBW // число байт на пиксель, используется для расчета максимального числа пикселей в буфере
#define ws281x_max_count_bytes ((unsigned short)16384) // size PWM DMA buffer // размер буфера в байтах -> один байт = один бит ленты
// 16384 bytes -> 600 pixels RGB

#define ws281y_max_bright_percent ((unsigned char)10) // максимально возможно выставляемая яркость в процентах из учета мощности БП и количества светодиодов

#define count_bytes_in_buf ((unsigned short)(numLEDSws281X*ws281x_bytes_per_pixel))

#define ws281x_arr_timer_125_usec ((unsigned short)89) // значение перезагрузки таймера // период 1.25 микросекунды
#define ws281x_ccr_timer_full_high ((unsigned short)(ws281x_arr_timer_125_usec+1)) // значение перезагрузки таймера для постоянной HIGH на пине
#define ws281x_ccr_th0_timer ((unsigned short)(ws281x_arr_timer_125_usec*0.32)) // краткая часть периода ws2812
#define ws281x_ccr_th1_timer ((unsigned short)(ws281x_arr_timer_125_usec-ws281x_ccr_th0_timer)) // длинная часть периода ws2812
#define ws281x_arr_timer_reset_bus ((unsigned short)((50/1.25+1)*ws281x_arr_timer_125_usec)) // время сброса шины

unsigned char colorsData[count_bytes_in_buf];

volatile unsigned char ws281Xmode = 0; // в процессе отправки
unsigned char ws281Ybrval; // яркость в процентах

unsigned short ws281XrealSizeDMAbuf; // реальный размер данных в буфере, кратно количеству пикселей * размер пикселя * 8
unsigned char ws281XbufPWM[ws281x_max_count_bytes+1]; // сам буфер для отправки // +1 байт для подлнялия GPIO вверх после отправки последнего байта

void f103setBrBytes(unsigned char inVal) { // установка яркости в процентах
	if (inVal>ws281y_max_bright_percent) ws281Ybrval = ws281y_max_bright_percent;
	else if (inVal==0) ws281Ybrval = 1;
	else ws281Ybrval = inVal;
}

void prepareDMAbuf(const unsigned char * inData, const unsigned short dataCount) { // заполняем буфер для ШИМ
	unsigned short maxBytesBuf = ws281x_max_count_bytes / 8; // сколько всего байт в буфер влезет
	unsigned short maxPixelsBuf = maxBytesBuf / ws281x_bytes_per_pixel; // Сколько всего пикселей в буфер влезет
	maxBytesBuf = maxPixelsBuf * ws281x_bytes_per_pixel; // сколько всего байт в буфер влезет, кратно одному пикселю
    if (dataCount > maxBytesBuf) { // число отправляемых байт больше чем размер буфера
    	ws281XrealSizeDMAbuf = maxBytesBuf * 8;
    } else { // все влезет в один буфер
    	ws281XrealSizeDMAbuf = dataCount * 8;
    	maxBytesBuf = dataCount;
    }
    for (unsigned short i=0; i<maxBytesBuf; ++i) {
    	unsigned char inByte = *(inData+i);
    	inByte = (unsigned char)(((unsigned short)((unsigned short)(inByte)*(unsigned short)(ws281Ybrval)))/100);
    	for (unsigned char j=8; j>0; j--) {
    		if (inByte & (1<<(j-1))) ws281XbufPWM[i*8+(8-j)] = ws281x_ccr_th1_timer;
    		else ws281XbufPWM[i*8+(8-j)] = ws281x_ccr_th0_timer;
    	}
    }
    ws281XbufPWM[maxBytesBuf*8] = 0; // последним байтом пропишем ШИМ с минимальным заполнением, опустить линию в ноль после последнего пикселя
    ++ws281XrealSizeDMAbuf; // на один байт увеличим количество отправляемых данных
}

/*http://dimoon.ru/obuchalka/stm32f1/uroki-stm32f103-chast-4-nastroyka-rcc.html?ysclid=leflwcqmz5475683246*/
//Настраиваем тактирование системы от внешнего кварца
//через PLL на саксимально возможных частотах.
//Внешний кварц должен быть на 8МГц
//Возвращает:
//  0 - завершено успешно
//  1 - не запустился кварцевый генератор
//  2 - не запустился PLL
int ClockInit(void) {
  __IO int StartUpCounter;
  ////////////////////////////////////////////////////////////
  //Запускаем кварцевый генератор
  ////////////////////////////////////////////////////////////
  RCC->CR |= RCC_CR_HSEON; //Запускаем генератор HSE
  //Ждем успешного запуска или окончания тайм-аута
  for(StartUpCounter=0; ; StartUpCounter++) {
    //Если успешно запустилось, то
    //выходим из цикла
    if (RCC->CR & RCC_CR_HSERDY) break;
    //Если не запустилось, то
    //отключаем все, что включили
    //и возвращаем ошибку
    if (StartUpCounter > 0x1000) {
      RCC->CR &= ~RCC_CR_HSEON; //Останавливаем HSE
      return 1;
    }
  }
  ////////////////////////////////////////////////////////////
  //Настраиваем и запускаем PLL
  ////////////////////////////////////////////////////////////
  //Настраиваем PLL
  RCC->CFGR |= RCC_CFGR_PLLMULL_0 | RCC_CFGR_PLLMULL_1 | RCC_CFGR_PLLMULL_2;  //PLL множитель равен 9 // 0111: PLL input clock x 9
  RCC->CFGR |= RCC_CFGR_PLLSRC_HSE; //Тактирование PLL от HSE
  RCC->CR |= RCC_CR_PLLON; //Запускаем PLL
  //Ждем успешного запуска или окончания тайм-аута
  for(StartUpCounter=0; ; StartUpCounter++) {
    //Если успешно запустилось, то
    //выходим из цикла
    if (RCC->CR & RCC_CR_PLLRDY) break;
    //Если по каким-то причинам не запустился PLL, то
    //отключаем все, что включили
    //и возвращаем ошибку
    if (StartUpCounter > 0x1000) {
      RCC->CR &= ~RCC_CR_HSEON; //Останавливаем HSE
      RCC->CR &= ~RCC_CR_PLLON; //Останавливаем PLL
      return 2;
    }
  }
  ////////////////////////////////////////////////////////////
  //Настраиваем FLASH и делители
  ////////////////////////////////////////////////////////////
  //Устанавливаем 2 цикла ожидания для Flash
  //так как частота ядра у нас будет 48 MHz < SYSCLK <= 72 MHz
  FLASH->ACR |= FLASH_ACR_LATENCY_1; // 010 Two wait states, if 48 MHz < SYSCLK ≤ 72 MHz
  //Делители
  RCC->CFGR &= ~RCC_CFGR_PPRE2; //Делитель шины APB2 отключен // 0xx: HCLK not divided
  RCC->CFGR |= RCC_CFGR_PPRE1_DIV2; //Делитель нишы APB1 равен 2 // 100: HCLK divided by 2
  RCC->CFGR &= ~RCC_CFGR_HPRE; //Делитель AHB отключен // 0xxx: SYSCLK not divided
  RCC->CFGR &= ~RCC_CFGR_SW; //Переключаемся на работу от PLL // 10: PLL selected as system clock
  RCC->CFGR |= RCC_CFGR_SW_1; //Переключаемся на работу от PLL // 10: PLL selected as system clock
  //Ждем, пока переключимся
  while(!(RCC->CFGR & RCC_CFGR_SWS_1));
  //После того, как переключились на
  //внешний источник такирования
  //отключаем внутренний RC-генератор
  //для экономии энергии
  RCC->CR &= ~RCC_CR_HSION;
  //Настройка и переклбючение сисемы
  //на внешний кварцевый генератор
  //и PLL запершилось успехом.
  //Выходим
  return 0;
}

#if !defined(__SOFT_FP__) && defined(__ARM_FP)
  #warning "FPU is not initialized, but the project is compiling for an FPU. Please initialize the FPU before use."
#endif

void f103initPB0pwm(void) {
	GPIOB->BSRR |= GPIO_BSRR_BS0; // PB0 HIGH
	GPIOB->CRL &= ~GPIO_CRL_MODE0; // 00: Input mode (reset state)
	GPIOB->CRL |= GPIO_CRL_MODE0_0; // 01: Output mode, max speed 10 MHz.
	GPIOB->CRL &= ~GPIO_CRL_CNF0; // 00: General purpose output push-pull
	GPIOB->CRL |= GPIO_CRL_CNF0_1; // 10: Alternate function output Push-pull
	AFIO->MAPR &= ~AFIO_MAPR_TIM3_REMAP; // 00: No remap (CH1/PA6, CH2/PA7, CH3/PB0, CH4/PB1)
}

void f103setC13led(unsigned char AledONOFF) {
	if (AledONOFF) GPIOC->BSRR |= GPIO_BSRR_BR13; // PC13 LOW
	else GPIOC->BSRR |= GPIO_BSRR_BS13; // PC13 HIGH
}

void f103initPC13led(void) {
	RCC->APB2ENR |= RCC_APB2ENR_IOPCEN; // GPIO port C
	GPIOC->CRH &= ~GPIO_CRH_MODE13; // 00: Input mode (reset state)
	GPIOC->CRH |= GPIO_CRH_MODE13_0; // 01: Output mode, max speed 10 MHz.
	GPIOC->CRH &= ~GPIO_CRH_CNF13; // 00: General purpose output push-pull
	f103setC13led(0);
}

void TIM4_IRQHandler(void) {
    TIM4->SR &= ~TIM_SR_UIF;
	++msTicks;
}

void f103initTIM4(void) {
	RCC->APB1ENR |= RCC_APB1ENR_TIM4EN; // clock Timer4 ON
	TIM4->SR = 0;
	TIM4->CNT = 0;
	TIM4->PSC = (SystemCoreClock / 2 / 1000000) - 1; // делитель
	TIM4->ARR = 1999; // значение перезагрузки -> 1 msec
	TIM4->DIER |= TIM_DIER_UIE;
	NVIC_EnableIRQ (TIM4_IRQn);
	TIM4->CR1 |= TIM_CR1_CEN; // включаем счётчик
    TIM4->SR &= ~TIM_SR_UIF;
}

unsigned long millis(void) {
	return msTicks;
}

void f103initTIM3pwmCH3(void) {
	TIM3->CR1 &= ~TIM_CR1_CEN; //останавливаем таймер
	TIM3->CNT = 0; //Очищаем счетный регистр
	TIM3->PSC = 0; // делитель x1 72000000 MHz
	TIM3->ARR = ws281x_arr_timer_125_usec; // значение перезагрузки // 1.25 usec
	TIM3->CCR3 = 0; // коэф. заполнения // WS2812 -> reset bus // PA8 LOW
	TIM3->CCMR2 &= ~TIM_CCMR2_OC3M; //сбрасываем все биты OCxM
	TIM3->CCMR2 |= TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3PE; // настроить канал
	TIM3->CCER  = TIM_CCER_CC3E; // настроим на выход канал 3
	TIM3->BDTR = TIM_BDTR_MOE; // разрешим использовать выводы таймера как выходы
	TIM3->CR1 &= ~TIM_CR1_DIR; // считаем вверх
	TIM3->CR1 &= ~TIM_CR1_CMS; // выравнивание по фронту, Fast PWM
	TIM3->CR1 |= TIM_CR1_ARPE;    //Регистры таймера с буферизацией
}

unsigned char f103busyWS281X(void) {
	return ws281Xmode;
}

void f103initDMA1CH2(void) {
	DMA1_Channel2->CCR &= ~DMA_CCR1_EN; // отключаем любое действие канала если идет передача
	DMA1_Channel2->CPAR = (unsigned long)(&TIM3->CCR3); //заносим адрес регистра куда кидать
	DMA1_Channel2->CMAR = (unsigned long)((unsigned char *)(ws281XbufPWM)); //заносим адрес данных в регистр CMAR
	DMA1_Channel2->CCR &= ~DMA_CCR1_MEM2MEM; //режим MEM2MEM отключен
	DMA1_Channel2->CCR &= ~DMA_CCR1_PL; //приоритет низкий
	DMA1_Channel2->CCR &= ~DMA_CCR1_MSIZE; //разрядность данных в памяти 8 бит // 00: 8-bits
	DMA1_Channel2->CCR &= ~DMA_CCR1_PSIZE; //разрядность регистра данных 16 бит
	DMA1_Channel2->CCR |= DMA_CCR1_PSIZE_0; //разрядность регистра данных 16 бит
	DMA1_Channel2->CCR |= DMA_CCR1_MINC; // включить инкремент адреса памяти
	DMA1_Channel2->CCR &= ~DMA_CCR1_PINC; //Инкремент адреса периферии отключен
	DMA1_Channel2->CCR &= ~DMA_CCR1_CIRC; //кольцевой режим отключен
	DMA1_Channel2->CCR |= DMA_CCR1_DIR; //1 - из памяти в периферию
	DMA1->IFCR |= DMA_IFCR_CTCIF2; // Сбрасываем флаг прерываний
    DMA1_Channel2->CCR |= DMA_CCR1_TCIE; //прерывание завершения передачи
    NVIC_EnableIRQ(DMA1_Channel2_IRQn); //от DMA
    NVIC_SetPriority(DMA1_Channel2_IRQn,14);
}

void f103initWS281X(void) {
	f103setBrBytes(100);
	RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; // GPIO port B
	RCC->AHBENR |= RCC_AHBENR_DMA1EN; // DMA1
	RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; // clock Timer3 ON
	f103initPB0pwm();
	f103initDMA1CH2();
	f103initTIM3pwmCH3();
}

void DMA1_Channel2_IRQHandler(void) {
	DMA1->IFCR |= DMA_IFCR_CTCIF2; // Сбрасываем флаг прерываний
	DMA1_Channel2->CCR &= ~DMA_CCR1_EN; //Отключаем канал DMA
	TIM3->CR1 &= ~TIM_CR1_CEN; //останавливаем таймер
	ws281Xmode = 0; // закончили передачу
}

void TIM3_IRQHandler(void) { //прерывание от таймера //Сюда попадаем после завершения формирования //сигнала RET шины ws2812b
	TIM3->SR &= ~TIM_SR_UIF; //сбрасываем флаг прерывания
	TIM3->CR1 &= ~TIM_CR1_CEN; //останавливаем таймер
	TIM3->DIER &= ~TIM_DIER_UIE; //прерывание не вызывать
	TIM3->CNT = 0; //Очищаем счетный регистр
	TIM3->ARR = ws281x_arr_timer_125_usec; // значение перезагрузки // 1.25 usec
	TIM3->CCR3 = 0; // коэф. заполнения // WS2812 -> reset bus // PA8 LOW
	TIM3->DIER |= TIM_DIER_CC3DE; //Разрешить запрос DMA
	DMA1_Channel2->CNDTR = ws281XrealSizeDMAbuf; // передаем нужное количество
    TIM3->CR1 |= TIM_CR1_CEN; //Запускаем таймер
	DMA1_Channel2->CCR |= DMA_CCR1_EN; // включаем передачу данных
}

void f103sendWS281X(unsigned char * inData, unsigned short dataCount) {
	ws281Xmode = 1; // заняты передачей
	DMA1_Channel2->CCR &= ~DMA_CCR1_EN; //Отключаем канал DMA
	TIM3->CR1 &= ~TIM_CR1_CEN; //останавливаем таймер
	prepareDMAbuf(inData, dataCount);
	TIM3->DIER &= ~TIM_DIER_CC3DE; // запретить запрос DMA
	TIM3->CNT = 0; //Очищаем счетный регистр
	TIM3->ARR = ws281x_arr_timer_reset_bus; // значение перезагрузки // 50+ usec // (50/1,25+1)*89 = 3649
	TIM3->CCR3 = 0; // коэф. заполнения // WS2812 -> reset bus // PA8 LOW
	TIM3->SR &= ~TIM_SR_UIF; //сбрасываем флаг прерывания
	TIM3->DIER |= TIM_DIER_UIE; //прерывание по обновлению
	NVIC_EnableIRQ(TIM3_IRQn); // включим прерывание таймера
    NVIC_SetPriority(TIM3_IRQn,13);
    TIM3->CR1 |= TIM_CR1_CEN; //Запускаем таймер
}

int main(void) {
	if (ClockInit() == 0) SystemCoreClock = 72000000UL;
	f103initPC13led();
	f103initTIM4();
	/*static unsigned long timerDelay = 0;
	while((millis() - timerDelay) < 6000UL);*/
	f103initWS281X();
	/*colorsData[0] = 0xFF;  colorsData[1] = 0x00;  colorsData[2] = 0x00;  colorsData[3] = 0x00; // G
	colorsData[4] = 0x00;  colorsData[5] = 0xFF;  colorsData[6] = 0x00;  colorsData[7] = 0x00; // R
	colorsData[8] = 0x00;  colorsData[9] = 0x00;  colorsData[10] = 0xFF; colorsData[11] = 0x00; // B
	colorsData[12] = 0x00; colorsData[13] = 0x00; colorsData[14] = 0x00; colorsData[15] = 0xFF; // W
	f103sendWS281X((unsigned char *)colorsData, count_bytes_in_buf);
	while(f103busyWS281X());*/
    /* Loop forever */
	while (1) {
		// test led PC13 blink
		static unsigned long timerBlinkC13 = 0;
		static unsigned char ledStat = 0;
		if ((millis() - timerBlinkC13) >= 1000UL) {
			timerBlinkC13 = millis();
			f103setC13led(ledStat = !ledStat);
		}
		// random pixels
		static unsigned long timerFlash = 0;
		if ((millis() - timerFlash) >= 100UL) {
			timerFlash = millis();
			for(unsigned short i = 0; i < count_bytes_in_buf; ++i) colorsData[i] = random() % 0x100;
			f103sendWS281X((unsigned char *)colorsData, count_bytes_in_buf);
			while(f103busyWS281X());
		}
		// -- end loop --
	}
}

Итоговый вариант гирлянды.
STM32F303CCT6
STM32CubeIDE v.1.11.2
CMSIS

Спойлер
/**
 ******************************************************************************
 * @file           : main.c
 * @author         : Auto-generated by STM32CubeIDE
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2023 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */

#include <stdint.h>
#include "f303cct6.h"
#include "f3ws281Y.h"
#include <stdlib.h>
#include "ws281eff.h"

uint32_t SystemCoreClock = 8000000UL; /*!< System Clock Frequency (Core Clock) */

unsigned char colorsData[count_bytes_in_buf];

int main(void)
{
	f303cct6init(); // init RCC
	f303initTIM4(); // init millis timer
	DWT_Delay_Init(); // init micros delay
	f303initC13led(); // init board led
	f303initA0A1keys(); // init keys PGM/SET and SEL

	f303initWS281X(); // init strip

	while (1) {
		ws281loopWork((unsigned char *) colorsData);
	}

}


/*
 * f303cct6.h
 *
 *  Created on: Feb 6, 2023
 *      Author: Andrey
 */

#ifndef F303CCT6_H_
#define F303CCT6_H_

#define keys_short_press ((unsigned long)50)
#define keys_long_press ((unsigned long)3000)

void f303cct6init(void);
void f303initC13led(void);
void f303setC13led(unsigned char AledONOFF);
void f303initTIM4(void);
unsigned long millis(void);
void DWT_Delay_Init(void);
void DWT_Delay_us(unsigned long au32_microseconds);
void f303initA0A1keys(void);
unsigned char f303readA0(void);
unsigned char f303readA1(void);
unsigned char f303keysA0A1read(void); // out bit0 short press A0, bit1-A1, out bit2 long press A0, bit3-A1

#endif /* F303CCT6_H_ */


/*
 * f303cct6.c
 *
 *  Created on: Feb 6, 2023
 *      Author: Andrey
 */

#include "f303cct6.h"
#include <stm32f30x.h>

volatile unsigned long msTicks = 0;

unsigned char f303keysA0A1read(void) { // out bit0 short press A0, bit1-A1, out bit2 long press A0, bit3-A1
	unsigned char outByte = 0;
	static unsigned char lastLevel[] = {1, 1};
	static unsigned char flagKeysDown[] = {0, 0};
	static unsigned long startTimeDown[2];
	unsigned char currentLevel[] = {f303readA0(), f303readA1()};
	for(int i = 0; i < 2; ++i) { // 2 keys
		if (currentLevel[i] != lastLevel[i]) { // change level
			if (currentLevel[i] == 0) { // key down
				if (flagKeysDown[i] == 0) { // no timer
					flagKeysDown[i] = 1; // begin timer
					startTimeDown[i] = millis();
				}
			} else { // key up
				if (flagKeysDown[i] != 0) { // timer started
					if ((millis() - startTimeDown[i]) >= 500) { // error press
						flagKeysDown[i] = 0; // stop timer
					} else if ((millis() - startTimeDown[i]) >= keys_short_press) { // end timer
						flagKeysDown[i] = 0; // stop timer - short press
						outByte |= 1 << i;
					}
				}
			}
		} else { // level not change
			if ((flagKeysDown[i] != 0) && (currentLevel[i] == 0)) { // timer started and key down
				if ((millis() - startTimeDown[i]) >= keys_long_press) { // end timer
					flagKeysDown[i] = 0; // stop timer - long press
					outByte |= 1 << (i+2);
				}
			} else if (currentLevel[i] != 0) { // key up
				flagKeysDown[i] = 0; // reset timer
			}
		}
		lastLevel[i] = currentLevel[i]; // save current level key
	}
	return outByte;
}

unsigned char f303readA0(void) {
	if (GPIOA->IDR & GPIO_IDR_0) return 1; else return 0;
}

unsigned char f303readA1(void) {
	if (GPIOA->IDR & GPIO_IDR_1) return 1; else return 0;
}

void f303initA0A1keys(void) {
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN; // Включаем тактирование порта GPIOA
	GPIOA->MODER &= ~GPIO_MODER_MODER0; // для начала все сбрасываем в ноль -> 00: Input mode (reset state)
	GPIOA->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR0; // 00: Low speed
	GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR0; // No pull-up, pull-down
	GPIOA->PUPDR |= GPIO_PUPDR_PUPDR0_0; // pull-up 01
	GPIOA->MODER &= ~GPIO_MODER_MODER1; // для начала все сбрасываем в ноль -> 00: Input mode (reset state)
	GPIOA->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR1; // 00: Low speed
	GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR1; // No pull-up, pull-down
	GPIOA->PUPDR |= GPIO_PUPDR_PUPDR1_0; // pull-up 01
}

void TIM4_IRQHandler(void) {
    TIM4->SR &= ~TIM_SR_UIF;
	++msTicks;
}

void f303cct6init(void) {
	// TODO Auto-generated constructor stub
	  volatile int StartUpCounter;
	  RCC->CR |= RCC_CR_HSEON; //Запускаем генератор HSE
	  for(StartUpCounter=0; ; StartUpCounter++) { //Ждем успешного запуска или окончания тайм-аута
	    if(RCC->CR & RCC_CR_HSERDY) break; //Если успешно запустилось, то выходим из цикла
	    if(StartUpCounter > 0x1000) { //Если не запустилось, то //отключаем все, что включили
	      RCC->CR &= ~RCC_CR_HSEON; //Останавливаем HSE
	      return; //и возвращаем ошибку
	    }
	  }
	  RCC->CFGR |= RCC_CFGR_PLLMULL; // 0111: PLL input clock x 9 //PLL множитель равен 9 //Настраиваем и запускаем PLL
	  RCC->CFGR &= ~RCC_CFGR_PLLMULL_3; // 0111: PLL input clock x 9
	  RCC->CFGR |= RCC_CFGR_PLLSRC; // 1: HSE/PREDIV selected as PLL input clock //Тактирование PLL от HSE
	  RCC->CR |= RCC_CR_PLLON; // Запускаем PLL
	  for(StartUpCounter=0; ; StartUpCounter++) { // Ждем успешного запуска или окончания тайм-аута
	    if(RCC->CR & RCC_CR_PLLRDY) break; //Если успешно запустилось, то //выходим из цикла
	    if(StartUpCounter > 0x1000) { //Если по каким-то причинам не запустился PLL, то //отключаем все, что включили
	      RCC->CR &= ~RCC_CR_HSEON; //Останавливаем HSE
	      RCC->CR &= ~RCC_CR_PLLON; //Останавливаем PLL
	      return; //и возвращаем ошибку
	    }
	  }
	  //Настраиваем FLASH и делители
	  //Устанавливаем 2 цикла ожидания для Flash
	  //так как частота ядра у нас будет 48 MHz < SYSCLK <= 72 MHz
	  FLASH->ACR |= FLASH_ACR_LATENCY_1; // 010: Two wait sates, if 48 < HCLK ≤ 72 MHz
	  //Делители
	  RCC->CFGR &= ~RCC_CFGR_PPRE2; //Делитель шины APB2 отключен // 0xx: HCLK not divided
	  RCC->CFGR &= ~RCC_CFGR_PPRE1; //Делитель нишы APB1 равен 2 // 100: HCLK divided by 2
	  RCC->CFGR |= RCC_CFGR_PPRE1_2; // 100: HCLK divided by 2
	  RCC->CFGR &= ~RCC_CFGR_HPRE; //Делитель AHB отключен // 0xxx: SYSCLK not divided
	  RCC->CFGR |= RCC_CFGR_SW_1; //Переключаемся на работу от PLL // 10: PLL selected as system clock
	  while(!(RCC->CFGR & RCC_CFGR_SWS_1)); // Ждем, пока переключимся // 10: PLL used as system clock
	  //Настройка и переклбючение сисемы
	  //на внешний кварцевый генератор
	  //и PLL запершилось успехом.
	  SystemCoreClock = 72000000UL;
	  //Выходим
}

void f303initC13led(void) {
    RCC->AHBENR |= RCC_AHBENR_GPIOCEN; // Включаем тактирование порта GPIOC
	GPIOC->MODER &= ~GPIO_MODER_MODER13; // для начала все сбрасываем в ноль -> 00: Input mode (reset state)
	GPIOC->MODER |=  GPIO_MODER_MODER13_0; // 01: General purpose output mode
	GPIOC->OTYPER &= ~GPIO_OTYPER_OT_13; // 0: Output push-pull (reset state)
	GPIOC->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR13; // 00: Low speed
	GPIOC->PUPDR &= ~GPIO_PUPDR_PUPDR13; // No pull-up, pull-down
}

void f303setC13led(unsigned char AledONOFF) {
	if (AledONOFF) GPIOC->BRR |= GPIO_BRR_BR_13;
	else GPIOC->BSRR |= GPIO_BSRR_BS_13;
}

void f303initTIM4(void) {
	RCC->APB1ENR |= RCC_APB1ENR_TIM4EN; // clock Timer4 ON
	TIM4->SR = 0;
	TIM4->CNT = 0;
	TIM4->PSC = (SystemCoreClock / 2 / 1000000) - 1; // делитель
	TIM4->ARR = 1999; // значение перезагрузки -> 1 msec
	TIM4->DIER |= TIM_DIER_UIE;
	NVIC_EnableIRQ (TIM4_IRQn);
	TIM4->CR1 |= TIM_CR1_CEN; // включаем счётчик
    TIM4->SR &= ~TIM_SR_UIF;
}

unsigned long millis(void) {
	return msTicks;
}

/*
 * https://deepbluembedded.com/stm32-delay-microsecond-millisecond-utility-dwt-delay-timer-delay/
 */
void DWT_Delay_Init(void) {
    /* Disable TRC */
    CoreDebug->DEMCR &= ~CoreDebug_DEMCR_TRCENA_Msk; // ~0x01000000;
    /* Enable TRC */
    CoreDebug->DEMCR |=  CoreDebug_DEMCR_TRCENA_Msk; // 0x01000000;
    /* Disable clock cycle counter */
    DWT->CTRL &= ~DWT_CTRL_CYCCNTENA_Msk; //~0x00000001;
    /* Enable  clock cycle counter */
    DWT->CTRL |=  DWT_CTRL_CYCCNTENA_Msk; //0x00000001;
    /* Reset the clock cycle counter value */
    DWT->CYCCNT = 0;
    /* 3 NO OPERATION instructions */
    __ASM volatile ("NOP");
    __ASM volatile ("NOP");
    __ASM volatile ("NOP");
}

/*
 * This Function Provides Delay In Microseconds Using DWT
 * https://deepbluembedded.com/stm32-delay-microsecond-millisecond-utility-dwt-delay-timer-delay/
 */
void DWT_Delay_us(unsigned long au32_microseconds) {
	  uint32_t au32_initial_ticks = DWT->CYCCNT;
	  uint32_t au32_ticks = (SystemCoreClock / 1000000);
	  au32_microseconds *= au32_ticks;
	  while ((DWT->CYCCNT - au32_initial_ticks) < au32_microseconds-au32_ticks);
}


/*
 * f3ws281Y.h
 *
 *  Created on: Feb 3, 2023
 *      Author: Andrey
 */

#ifndef INC_F3WS281Y_H_
#define INC_F3WS281Y_H_

#ifndef count_of
#define count_of(a) (sizeof(a)/sizeof((a)[0]))
#endif

#define numLEDSws281X ((unsigned short)50)

#define ws281x_bytes_per_pixel ((unsigned short)3) // RGB/RGBW // число байт на пиксель, используется для расчета максимального числа пикселей в буфере
#define ws281x_max_count_bytes ((unsigned short)16384) // size PWM DMA buffer // размер буфера в байтах -> один байт = один бит ленты
// 16384 bytes -> 600 pixels RGB

#define ws281y_max_bright_percent ((unsigned char)80) // максимально возможно выставляемая яркость в процентах из учета мощности БП и количества светодиодов

#define count_bytes_in_buf ((unsigned short)(numLEDSws281X*ws281x_bytes_per_pixel))

#define ws281x_arr_timer_125_usec ((unsigned short)89) // значение перезагрузки таймера // период 1.25 микросекунды
#define ws281x_ccr_timer_full_high ((unsigned short)(ws281x_arr_timer_125_usec+1)) // значение перезагрузки таймера для постоянной HIGH на пине
#define ws281x_ccr_th0_timer ((unsigned short)(ws281x_arr_timer_125_usec*0.32)) // краткая часть периода ws2812
#define ws281x_ccr_th1_timer ((unsigned short)(ws281x_arr_timer_125_usec-ws281x_ccr_th0_timer)) // длинная часть периода ws2812
#define ws281x_arr_timer_reset_bus ((unsigned short)((50/1.25+1)*ws281x_arr_timer_125_usec)) // время сброса шины

void f303initWS281X(void);
void f303sendWS281X(unsigned char * inData, unsigned short dataCount);
unsigned char f303busyWS281X(void);
void f303setBrBytes(unsigned char inVal);
unsigned char f303getBrBytes(void);

#endif /* INC_F3WS281Y_H_ */


/*
 * f3ws281Y.c
 *
 *  Created on: Feb 3, 2023
 *      Author: Andrey
 */

#include <stm32f30x.h>
#include "f3ws281Y.h"

volatile unsigned char ws281Xmode = 0; // в процессе отправки
unsigned char ws281Ybrval; // яркость в процентах

unsigned short ws281XrealSizeDMAbuf; // реальный размер данных в буфере, кратно количеству пикселей * размер пикселя * 8
unsigned char ws281XbufPWM[ws281x_max_count_bytes+1]; // сам буфер для отправки // +1 байт для подлнялия GPIO вверх после отправки последнего байта

unsigned char f303getBrBytes(void) {
	return ws281Ybrval;
}

unsigned char f303busyWS281X(void) {
	return ws281Xmode;
}

void f303setBrBytes(unsigned char inVal) { // установка яркости в процентах
	if (inVal>ws281y_max_bright_percent) ws281Ybrval = ws281y_max_bright_percent;
	else if (inVal==0) ws281Ybrval = 1;
	else ws281Ybrval = inVal;
}

void prepareDMAbuf(const unsigned char * inData, const unsigned short dataCount) { // заполняем буфер для ШИМ
	unsigned short maxBytesBuf = ws281x_max_count_bytes / 8; // сколько всего байт в буфер влезет
	unsigned short maxPixelsBuf = maxBytesBuf / ws281x_bytes_per_pixel; // Сколько всего пикселей в буфер влезет
	maxBytesBuf = maxPixelsBuf * ws281x_bytes_per_pixel; // сколько всего байт в буфер влезет, кратно одному пикселю
    if (dataCount > maxBytesBuf) { // число отправляемых байт больше чем размер буфера
    	ws281XrealSizeDMAbuf = maxBytesBuf * 8;
    } else { // все влезет в один буфер
    	ws281XrealSizeDMAbuf = dataCount * 8;
    	maxBytesBuf = dataCount;
    }
    for (unsigned short i=0; i<maxBytesBuf; ++i) {
    	unsigned char inByte = *(inData+i);
    	inByte = (unsigned char)(((unsigned short)((unsigned short)(inByte)*(unsigned short)(ws281Ybrval)))/100);
    	for (unsigned char j=8; j>0; j--) {
    		if (inByte & (1<<(j-1))) ws281XbufPWM[i*8+(8-j)] = ws281x_ccr_th1_timer;
    		else ws281XbufPWM[i*8+(8-j)] = ws281x_ccr_th0_timer;
    	}
    }
    ws281XbufPWM[maxBytesBuf*8] = 0; // последним байтом пропишем ШИМ с минимальным заполнением, опустить линию в ноль после последнего пикселя
    ++ws281XrealSizeDMAbuf; // на один байт увеличим количество отправляемых данных
}

void f303initPB0pwm(void) {
	GPIOB->BSRR |= GPIO_BSRR_BS_0; // PB0 HIGH
	GPIOB->OSPEEDR &= ~GPIO_OSPEEDER_OSPEEDR0; // 00: Low speed
	GPIOB->PUPDR &= ~GPIO_PUPDR_PUPDR0; // No pull-up, pull-down
	GPIOB->OTYPER &= ~GPIO_OTYPER_OT_0; // 0: Output push-pull (reset state)
	GPIOB->AFR[0] &= 0x0F; // clear AF by PB0
	GPIOB->AFR[0] |= 2; // Назначаем PB0 выводу альтернативную функцию AF2
	GPIOB->MODER &= ~GPIO_MODER_MODER0; // для начала все сбрасываем в ноль -> 00: Input mode (reset state)
	GPIOB->MODER |=  GPIO_MODER_MODER0_1; // 10: Alternate function mode
}

void f303initTIM3pwmCH3(void) {
	TIM3->CR1 &= ~(TIM_CR1_CEN); //останавливаем таймер
	TIM3->CNT = 0; //Очищаем счетный регистр
	TIM3->PSC = 0; // делитель x1 72000000 MHz
	TIM3->ARR = ws281x_arr_timer_125_usec; // значение перезагрузки // 1.25 usec
	TIM3->CCR3 = ws281x_ccr_timer_full_high; // коэф. заполнения
	TIM3->CCMR2 &= ~(TIM_CCMR2_OC3M); //сбрасываем все биты OCxM
	TIM3->CCMR2 |= TIM_CCMR2_OC3M_2 | TIM_CCMR2_OC3M_1 | TIM_CCMR2_OC3PE; // настроить канал
	TIM3->CCER  = TIM_CCER_CC3E; // настроим на выход канал 3
	TIM3->BDTR = TIM_BDTR_MOE; // разрешим использовать выводы таймера как выходы
	TIM3->CR1 &= ~TIM_CR1_DIR; // считаем вверх
	TIM3->CR1 &= ~TIM_CR1_CMS; // выравнивание по фронту, Fast PWM
	TIM3->CR1 |= TIM_CR1_ARPE;    //Регистры таймера с буферизацией
}

void DMA1_CH2_IRQHandler(void) {
	DMA1->IFCR |= DMA_IFCR_CTCIF2; // Сбрасываем флаг прерываний
	DMA1_Channel2->CCR &= ~(DMA_CCR_EN); //Отключаем канал DMA
	TIM3->CR1 &= ~(TIM_CR1_CEN); //останавливаем таймер
	ws281Xmode = 0; // закончили передачу
}

void TIM3_IRQHandler(void) { //прерывание от таймера //Сюда попадаем после завершения формирования //сигнала RET шины ws2812b
	TIM3->SR &= ~(TIM_SR_UIF); //сбрасываем флаг прерывания
	TIM3->CR1 &= ~(TIM_CR1_CEN); //останавливаем таймер
	TIM3->DIER &= ~TIM_DIER_UIE; //прерывание не вызывать
	TIM3->CNT = 0; //Очищаем счетный регистр
	TIM3->ARR = ws281x_arr_timer_125_usec; // значение перезагрузки // 1.25 usec
	TIM3->CCR3 = 0; // коэф. заполнения // WS2812 -> reset bus // PA8 LOW
	TIM3->DIER |= TIM_DIER_CC3DE; //Разрешить запрос DMA
	DMA1_Channel2->CNDTR = ws281XrealSizeDMAbuf; // передаем нужное количество
    TIM3->CR1 |= TIM_CR1_CEN; //Запускаем таймер
	DMA1_Channel2->CCR |= DMA_CCR_EN; // включаем передачу данных
}

void f303initDMA1CH2(void) {
	DMA1_Channel2->CCR &= ~DMA_CCR_EN; // отключаем любое действие канала если идет передача
	DMA1_Channel2->CPAR = (unsigned long)(&TIM3->CCR3); //заносим адрес регистра куда кидать
	DMA1_Channel2->CMAR = (unsigned long)((unsigned char *)(ws281XbufPWM)); //заносим адрес данных в регистр CMAR
	DMA1_Channel2->CCR &= ~DMA_CCR_MEM2MEM; //режим MEM2MEM отключен
	DMA1_Channel2->CCR &= ~DMA_CCR_PL; //приоритет низкий
	DMA1_Channel2->CCR &= ~DMA_CCR_MSIZE; //разрядность данных в памяти 8 бит // 00: 8-bits
	DMA1_Channel2->CCR &= ~DMA_CCR_PSIZE; //разрядность регистра данных 16 бит
	DMA1_Channel2->CCR |= DMA_CCR_PSIZE_0; //разрядность регистра данных 16 бит
	DMA1_Channel2->CCR |= DMA_CCR_MINC; // включить инкремент адреса памяти
	DMA1_Channel2->CCR &= ~DMA_CCR_PINC; //Инкремент адреса периферии отключен
	DMA1_Channel2->CCR &= ~DMA_CCR_CIRC; //кольцевой режим отключен
	DMA1_Channel2->CCR |= DMA_CCR_DIR; //1 - из памяти в периферию
	DMA1->IFCR |= DMA_IFCR_CTCIF2; // Сбрасываем флаг прерываний
    DMA1_Channel2->CCR |= DMA_CCR_TCIE; //прерывание завершения передачи
    NVIC_EnableIRQ(DMA1_Channel2_IRQn); //от DMA
    NVIC_SetPriority(DMA1_Channel2_IRQn,14);
}

void f303initWS281X(void) {
	f303setBrBytes(30);
	RCC->AHBENR |= RCC_AHBENR_GPIOBEN; // Включаем тактирование порта GPIOB
	  RCC->AHBENR |= RCC_AHBENR_DMA1EN; // DMA1
	RCC->APB1ENR |= RCC_APB1ENR_TIM3EN; // clock Timer3 ON
  f303initPB0pwm();
  f303initDMA1CH2();
  f303initTIM3pwmCH3();
}

void f303sendWS281X(unsigned char * inData, unsigned short dataCount) {
	ws281Xmode = 1; // заняты передачей
	DMA1_Channel2->CCR &= ~(DMA_CCR_EN); //Отключаем канал DMA
	TIM3->CR1 &= ~(TIM_CR1_CEN); //останавливаем таймер
	prepareDMAbuf(inData, dataCount);
	TIM3->DIER &= ~TIM_DIER_CC3DE; // запретить запрос DMA
	TIM3->CNT = 0; //Очищаем счетный регистр
	TIM3->ARR = ws281x_arr_timer_reset_bus; // значение перезагрузки // 50+ usec // (50/1,25+1)*89 = 3649
	TIM3->CCR3 = 0; // коэф. заполнения // WS2812 -> reset bus // PA8 LOW
	TIM3->SR &= ~(TIM_SR_UIF); //сбрасываем флаг прерывания
	TIM3->DIER |= TIM_DIER_UIE; //прерывание по обновлению
	NVIC_EnableIRQ(TIM3_IRQn); // включим прерывание таймера
    NVIC_SetPriority(TIM3_IRQn,13);
	TIM3->CR1 |= TIM_CR1_CEN; //Поехали считать!
}



/*
 * ws281eff.h
 *
 *  Created on: 6 февр. 2023 г.
 *      Author: Andrey
 */

#ifndef WS281EFF_H_
#define WS281EFF_H_

#define control_wait_unuse_period ((unsigned long)20000) // время бездействия миллисекунд, после которого возврат в рабочий режим

#define ws281_min_period_change_pattern ((unsigned long)5) // минимальное время между сменой эффектов миллисекунд
#define ws281_max_period_change_pattern ((unsigned long)30) // максимальное время между сменой эффектов миллисекунд
#define ws281_min_work_pattern ((unsigned short)900) // число запусков одной программы до смены

void ws281loopWork(unsigned char * outBuf);

#endif /* WS281EFF_H_ */


/*
 * ws281eff.c
 *
 *  Created on: 6 февр. 2023 г.
 *      Author: Andrey
 */

#include <stdlib.h>
#include "ws281eff.h"
#include "f3ws281Y.h"
#include "f303cct6.h"

unsigned char stripInWork = 1; // гирлянда не в режиме настройки
unsigned char currentPattern = 0xFF; // bit7=1 -> random
unsigned char currentSpeed = 0xFF; // bit7=1 -> random

unsigned short tLoop= 0; // глобальный счетчик, должен быть минимум в 4 раза длиннее количества светодиодов, что бы все программы целиком успели отработать

typedef enum {modeChangePattern, modeChangeSpeed} ctrlMode;

void pattern_random(unsigned char * oneBuf, unsigned short tIn) {
    if (tIn % 8) return;
    for (unsigned short i = 0; i < count_bytes_in_buf; ++i) *(oneBuf+i) = random();
}

void pattern_snakes(unsigned char * oneBuf, unsigned short tIn) {
    for (unsigned short i = 0; i < numLEDSws281X; ++i) {
        unsigned short x = (i + (tIn >> 1)) % 64;
        if (x < 10){
        	*(oneBuf+i*ws281x_bytes_per_pixel) = 0xFF; // R
        } else if (x >= 15 && x < 25) {
        	*(oneBuf+i*ws281x_bytes_per_pixel+1) = 0xFF; // G
        } else if (x >= 30 && x < 40) {
        	*(oneBuf+i*ws281x_bytes_per_pixel+2) = 0xFF; // B
        } else {
        	*(oneBuf+i*ws281x_bytes_per_pixel) = 0x00; // R
        	*(oneBuf+i*ws281x_bytes_per_pixel+1) = 0x00; // G
        	*(oneBuf+i*ws281x_bytes_per_pixel+2) = 0x00; // B
        }
        if (ws281x_bytes_per_pixel>3) *(oneBuf+i*ws281x_bytes_per_pixel+3) = 0x00; // W
    }
}

void pattern_sparkle(unsigned char * oneBuf, unsigned short tIn) {
    if (tIn % 8)  return;
    for (unsigned short i = 0; i < numLEDSws281X; ++i) {
    	unsigned char putByte = random() % 16 ? 0 : 0xFF;
    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+i*ws281x_bytes_per_pixel+j) = putByte;
    }
}

void pattern_greys(unsigned char * oneBuf, unsigned short tIn) {
    for (unsigned short i = 0; i < numLEDSws281X; ++i) {
    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+i*ws281x_bytes_per_pixel+j) = (unsigned char)tIn;
    }
}

void pattern_blink(unsigned char * oneBuf, unsigned short tIn) {
	static unsigned char cR, cG, cB;
	static unsigned short currPos = 0;
	static unsigned short currPeriod;
	if (!currPos) { // start new
		cR = random(); cG = random(); cB = random();
		for (unsigned short i = 0; i < numLEDSws281X; ++i) {
			*(oneBuf+i*ws281x_bytes_per_pixel) = cR;
			*(oneBuf+i*ws281x_bytes_per_pixel+1) = cG;
			*(oneBuf+i*ws281x_bytes_per_pixel+2) = cB;
			if (ws281x_bytes_per_pixel>3) *(oneBuf+i*ws281x_bytes_per_pixel+3) = cR+cG+cB;
		}
		++currPos;
		currPeriod = 20 + (random() >> 2);
	} else { // cont
		if (currPos == 3) for (unsigned short i = 0; i < count_bytes_in_buf; ++i) *(oneBuf+i) = 0;
		if ((++currPos) >= currPeriod) currPos = 0;
	}
}

void pattern_fillr(unsigned char * oneBuf, unsigned short tIn) {
    if (tIn % 4)  return;
	static unsigned short i = 0;
	static unsigned char fillMode = 0;
	switch (fillMode) {
		case 0: {
	    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+i*ws281x_bytes_per_pixel+j) = random();
			break;
		}
		case 1: {
	    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+i*ws281x_bytes_per_pixel+j) = 0;
			break;
		}
		case 2: {
	    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+(numLEDSws281X-i-1)*ws281x_bytes_per_pixel+j) = random();
			break;
		}
		case 3: {
	    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+(numLEDSws281X-i-1)*ws281x_bytes_per_pixel+j) = 0;
			break;
		}
		default: {}
	}
	if ((++i) >= numLEDSws281X) {
		i = 0;
		if ((++fillMode) >= 4) fillMode = 0;
	}
}

void pattern_bomb(unsigned char * oneBuf, unsigned short tIn) {
	static unsigned short s = 16;
    if (tIn % s)  return;
	static unsigned short i = 0;
	static unsigned char fillMode = 0;
	static unsigned char c1R, c2R, c1G, c2G, c1B, c2B;
	if ((!i) && (!fillMode)) {
		c1R = random(); c1G = random(); c1B = random();
		c2R = random(); c2G = random(); c2B = random();
		s = 16;
	}
	switch (fillMode) {
		case 0: {
	    	*(oneBuf+i*ws281x_bytes_per_pixel) = c1R;
	    	*(oneBuf+i*ws281x_bytes_per_pixel+1) = c1G;
	    	*(oneBuf+i*ws281x_bytes_per_pixel+2) = c1B;
	    	*(oneBuf+(numLEDSws281X-i-1)*ws281x_bytes_per_pixel) = c2R;
	    	*(oneBuf+(numLEDSws281X-i-1)*ws281x_bytes_per_pixel+1) = c2G;
	    	*(oneBuf+(numLEDSws281X-i-1)*ws281x_bytes_per_pixel+2) = c2B;
			if (ws281x_bytes_per_pixel>3) {
				*(oneBuf+i*ws281x_bytes_per_pixel+3) = c1R+c1G+c1B;
				*(oneBuf+(numLEDSws281X-i-1)*ws281x_bytes_per_pixel+3) = c2R+c2G+c2B;
			}
			if (i) { // clear prev pixel
		    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+(i-1)*ws281x_bytes_per_pixel+j) = 0;
		    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+(numLEDSws281X-i)*ws281x_bytes_per_pixel+j) = 0;
			}
			break;
		}
		case 1: { // full fill pixel
	    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+((numLEDSws281X >> 1)-1-i)*ws281x_bytes_per_pixel+j) = 0xFF;
	    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(oneBuf+((numLEDSws281X >> 1)+i)*ws281x_bytes_per_pixel+j) = 0xFF;
			break;
		}
		default: {}
	}
	if ((++i) >= (numLEDSws281X >> 1)) { // half strip
		i = 0;
		if ((++fillMode) >= 2) {
			fillMode = 0;
			for (unsigned short i = 0; i < count_bytes_in_buf; ++i) *(oneBuf+i) = 0;
		} else {
			s = 4;
		}
	}
}

typedef void (*pattern)(unsigned char * oneBuf, unsigned short tIn);
const struct {
    pattern pat;
} pattern_table[] = {
        {pattern_snakes},
        {pattern_random},
		{pattern_sparkle},
		{pattern_greys},
		{pattern_blink},
		{pattern_fillr},
		{pattern_bomb}
};

void showOnePattern(unsigned char * outBuf, unsigned char onePat, unsigned short inTloop) {
	pattern_table[onePat].pat(outBuf, inTloop);
	f303sendWS281X(outBuf, count_bytes_in_buf);
	while(f303busyWS281X());
}

unsigned char getCountPattern() {
	return count_of(pattern_table);
}

void clearStrip(unsigned char * outBuf) {
    for (unsigned short i = 0; i < numLEDSws281X; ++i) {
    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(outBuf+i*ws281x_bytes_per_pixel+j) = 0;
    }
	f303sendWS281X(outBuf, count_bytes_in_buf);
	while(f303busyWS281X());
}

void keysControlLoop(unsigned char * outBuf) {
	unsigned char outKeys = f303keysA0A1read();
	static unsigned char ctrlCurrentPattern; // bit7=1 -> random
	static unsigned char ctrlCurrentSpeed; // bit7=1 -> random
	static ctrlMode currentMode;
	static unsigned long timerControl = 0;
	if (outKeys & 1) { // short press A0
		timerControl = millis();
		if (stripInWork) { // start control mode
			stripInWork = 0;
			currentMode = modeChangePattern;
			ctrlCurrentPattern = 0xFF;
			ctrlCurrentSpeed = 0xFF;
			clearStrip(outBuf);
		} else { // save selected mode and goto next param
			if (!stripInWork) { // in control - change params
				switch (currentMode) {
					case modeChangePattern: {
						currentPattern = ctrlCurrentPattern;
						clearStrip(outBuf);
						++currentMode;
						break;
					}
					case modeChangeSpeed: {
						currentSpeed = ctrlCurrentSpeed;
						stripInWork = 1;
						break;
					}
					default:{}
				}
				tLoop = 2 * ws281_min_work_pattern; // reset counter
			}
		}
	} else if (outKeys & 2) { // short press A1
		timerControl = millis();
		if (!stripInWork) { // in control - change params
			switch (currentMode) {
				case modeChangePattern: {
					if (ctrlCurrentPattern & 0x80) {
						ctrlCurrentPattern = 0;
					} else if ((++ctrlCurrentPattern) >= getCountPattern())  {
						ctrlCurrentPattern = 0xFF;
					}
					break;
				}
				case modeChangeSpeed: {
					if (ctrlCurrentSpeed & 0x80) {
						ctrlCurrentSpeed = 0;
					} else {
						ctrlCurrentSpeed += 10;
						if (ctrlCurrentSpeed >= 101)  {
							ctrlCurrentSpeed = 0xFF;
						}
					}
					break;
				}
			}
		} else { // in strip mode work - change brightness
			static unsigned char bigVal = 0;
			unsigned char currBr = f303getBrBytes();
			currBr += 10;
			if (currBr >= ws281y_max_bright_percent) {
				if (bigVal) {
					currBr = 0; bigVal = 0;
				} else {
					bigVal = 1;
				}
			}
			f303setBrBytes(currBr);
		}
	}
	if (!stripInWork) {
		if ((millis() - timerControl) >= control_wait_unuse_period) { // unuse control
			tLoop = 2 * ws281_min_work_pattern; // reset counter
			stripInWork = 1;
		} else { // show current parameters
			static unsigned long showControl = 0;
			unsigned char showPat;
			unsigned long showSpeed;
			if ((ctrlCurrentPattern & 0x80)) {
				showPat = random() % getCountPattern();
			} else {
				showPat = ctrlCurrentPattern;
			}
			if (ctrlCurrentSpeed & 0x80) {
				showSpeed = ws281_min_period_change_pattern + (random() % 101) * (ws281_max_period_change_pattern - ws281_min_period_change_pattern) / 100;
			} else {
				showSpeed = ws281_min_period_change_pattern + (ctrlCurrentSpeed % 101) * (ws281_max_period_change_pattern - ws281_min_period_change_pattern) / 100;
			}
			if ((millis() - showControl) >= showSpeed) {
				showControl = millis();
				switch (currentMode) {
    				case modeChangePattern: {
    					showOnePattern(outBuf, showPat, tLoop);
	    				break;
		    		}
			    	case modeChangeSpeed: {
			    		static unsigned char i = 0;
			    		static unsigned char outByte = 0xFF;
			    		if (!(tLoop % 8)) {
			    			if (ctrlCurrentSpeed & 0x80) {
			    				for (unsigned short i = 0; i < 10; ++i) {
			    					unsigned char bb;
			    					if (i & 0x01) bb = 0xFF; else bb = 0x00;
				    		    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(outBuf+i*ws281x_bytes_per_pixel+j) = bb;
					    			f303sendWS281X(outBuf, 10 * ws281x_bytes_per_pixel);
					    			while(f303busyWS281X());
			    				}
			    			} else {
			    		    	for (unsigned char j = 0; j < ws281x_bytes_per_pixel; ++j) *(outBuf+i*ws281x_bytes_per_pixel+j) = outByte;
				    			f303sendWS281X(outBuf, 10 * ws281x_bytes_per_pixel);
				    			while(f303busyWS281X());
				    			if ((++i) >= 10) {
				    				i = 0;
				    				outByte = ~outByte;
				    			}
			    			}
			    		}
				    	break;
				    }
				}
				++tLoop;
			}
			if (tLoop >= ws281_min_work_pattern) tLoop = 0;
		}
	}
}

void ws281loopWork(unsigned char * outBuf) {
	static unsigned char currentPat = 0; // текущая рабочая программа
	static unsigned short patChange = ws281_min_work_pattern; // окончание глобального счетчика, после чего устанавливается скорость и программа
	static unsigned long timerSpeed = 0; // таймер запуска каждой программы в цикле
	static unsigned long periodSpeed = ws281_min_period_change_pattern; // период запуска программы в цикле
	keysControlLoop(outBuf);
	if (!stripInWork) return;
	if (tLoop >= patChange) { // закончился глобальный счетчик
		patChange = ws281_min_work_pattern + (unsigned char)random(); // в небольших пределах меняем время смены программ
		if (currentPattern & 0x80) { // если случайный выбор программы
			currentPat = random() % getCountPattern();
		} else { // если фиксированная программа
			currentPat = currentPattern;
		}
		if (currentSpeed & 0x80) { // если случайный выбор скорости
			periodSpeed = ws281_min_period_change_pattern + (random() % 101) * (ws281_max_period_change_pattern - ws281_min_period_change_pattern) / 100;
		} else { // фиксированная скорость
			periodSpeed = ws281_min_period_change_pattern + (currentSpeed % 101) * (ws281_max_period_change_pattern - ws281_min_period_change_pattern) / 100;
		}
		tLoop = 0;
	}
	if ((millis() - timerSpeed) >= periodSpeed) {
		timerSpeed = millis();
		showOnePattern(outBuf, currentPat, tLoop);
		++tLoop;
	}
}

Лента подключается к PB0
Кнопки к PA0 PA1