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

Переделанные функции управления периферией и вывода на ленту 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 --
	}
}