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