+7(960) 250-82-68 spam@mirossa.ru


  MCU         C           1С         PHP       JAVA      разное 


Статьи
 
 

STM32F103C8T6 + адресная лента ws2812b

Простейшее зажигание ленты на адресных диодах.

WS2812B LED datasheet

Весь исходный код. ide CooCox.

Недостатки:
1. Тут вообще ничего нет кроме зажигания всей ленты одним цветом и включения/выключения конкретных диодов.
2. Встроенных эффектов нет. (Для эффектов можно использовать разные цветовые модели (HSV,...))
3. В функции отправки данных на ленту есть цикл ожидания завершения этой операции ( while(!ws2812b_transferComplete); ). Его можно перекинуть в отдельную функцию и делать проверку на уровне приложения(убрать цикл).

Ещё:
Длина адресной диодной ленты задаётся в заголовочном файле ws2812b.h константой NumberOfLEDS. Но к длине нужно прибавить секцию сброса и конечный технический байт. Не стал добавлять ещё одну константу для использования в приложении. Поэтому размер массива имеет такой вид(используется в пользовательском коде):
uint8_t tbuf[48 + NumberOfLEDS * 24 + 1];

в коде используются:
Канал 2 таймера 2; Канал 7 DMA1; пин PA1 для передачи нанных на ленту.

модули:

/* main.c */

#include <stm32f10x.h>
#include <stm32f10x_rcc.h>
#include <stm32f10x_gpio.h>
//#include <string.h>

#include "ws2812b.h"

void otherInit(void);

void Delay(uint32_t nTime);
uint8_t debounce(uint8_t last);


uint8_t tbuf[48 + NumberOfLEDS * 24 + 1]; // так надо!


int main(void)
{
	uint8_t currentButton;
	uint8_t lastButton;
	struct color Color1;

	uint8_t bttnSt = 0;

	otherInit(); // Initialize user button

	ws2812b_init(tbuf);

	lastButton = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
	currentButton = lastButton;

	// Configure SysTick Timer
	if (SysTick_Config(SystemCoreClock / 1000))
			while (1);

	while (1)	{


		currentButton = debounce(lastButton);

		if ( lastButton == Bit_RESET && currentButton == Bit_SET ){

			if ( bttnSt == 0 ){

				// заполнить массив "погашенными" диодами
				ws2812b_fillLEDsArray(tbuf, 0);

				Color1.G = 100;
				Color1.R = 0;
				Color1.B = 0;
				ws2812b_changeLed(tbuf, On, 1, &Color1);
				Color1.G = 0;
				Color1.R = 100;
				Color1.B = 0;
				ws2812b_changeLed(tbuf, On, 3, &Color1);
				Color1.G = 0;
				Color1.R = 0;
				Color1.B = 100;
				ws2812b_changeLed(tbuf, On, 5, &Color1);
				ws2812b_showLEDsArray();

				bttnSt++;
			} else if ( bttnSt == 1 ){

				ws2812b_changeLed(tbuf, Off, 3, 0);
				ws2812b_showLEDsArray();

				bttnSt++;
			}else if ( bttnSt == 2 ){

				Color1.G = 0;
				Color1.R = 10;
				Color1.B = 80;
				ws2812b_fillLEDsArray(tbuf, &Color1);
				ws2812b_showLEDsArray();
				bttnSt = 0;
			}

		}

		lastButton = currentButton;

	}//while

    return 0;
}

void otherInit(void){
	GPIO_InitTypeDef GPIO_InitStructure;

	// user button A0
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOA,	&GPIO_InitStructure);
}

static __IO uint32_t TimingDelay;

void Delay(uint32_t nTime){
	TimingDelay = nTime;
	while(TimingDelay != 0);
}


void SysTick_Handler(void){
	if (TimingDelay != 0x00)
		TimingDelay--;
}

uint8_t debounce(uint8_t last){
	uint8_t current = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
	  if ( last != current ){
		  Delay(5);
		  current = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
	  }
  return current;
}

#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
	/* Infinite loop */
	/* Use GDB to find out why we're here */
	while (1);
}
#endif


/* ws2812b.h */

#ifndef WS2812b_H_
#define WS2812b_H_

#define NumberOfLEDS 9 //устанавливается пользователем

#define On 1
#define Off 0

struct color {
	uint8_t G;
	uint8_t R;
	uint8_t B;
};

// Инициализация
void ws2812b_init(const void *tbuf);

//Отправить данные на адресную ленту.
//(указатель на массив данных был определён в функции ws2812b_init()
// параметром tbuf)
void ws2812b_showLEDsArray(void);

//Заполнить массив определённым цветом или обнулить массив.
// tbuf - массив диодов
// color - указатель на цвет или 0 для обнуления массива
void ws2812b_fillLEDsArray(uint8_t *tbuf, struct color * Colr);

//Установить цвет определённого диода или выключить диод.
// tbuf - массив диодов
// on : 1 - вкл, 0 - выкл
// number - номер диода. от нуля
// color - цвет ( если on=0, то в color передать 0 )
void ws2812b_changeLed(uint8_t *tbuf, uint8_t on, uint16_t number, struct color * Colr);

//
void dma_initial(const void *tbuf);
void init_timers(void);
#endif


/* ws2812b.c */

#include <stm32f10x.h>
#include <stm32f10x_rcc.h>
#include <stm32f10x_gpio.h>
#include <stm32f10x_tim.h>
#include <stm32f10x_dma.h>
#include <misc.h>

#include "ws2812b.h"

#define bitValue0 29
#define bitValue1 61

uint8_t ws2812b_transferComplete = 1;

void init_timers(void) {
    GPIO_InitTypeDef	GPIO_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    //Настройка PA1 как выхода для канала 2 таймера 2.
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

    GPIO_StructInit(&GPIO_InitStructure);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed	= GPIO_Speed_2MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;

    // Включить тактирование таймера
    RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM2, ENABLE );
    // configure timer
    // PWM frequency = 50 hz with 24 ,000 ,000 hz system clock
    //  24 ,000 ,000/480 = 50 ,000
    // 100 ,000/100 = 50

    TIM_TimeBaseStructInit(&TIM_TimeBaseStructure );
    TIM_TimeBaseStructure.TIM_Prescaler = 0; // 72 MHz
    TIM_TimeBaseStructure.TIM_Period = 90 - 1; // 1,242 мкс. нужно 1,25 мкс
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit (TIM2, &TIM_TimeBaseStructure );
    // CRR == 29 // 0.4 мкс  "0"
    // CRR == 61 // 0.85 мкс "1"

    //для 24 МГц:
    //период = 30 (1.251 мкс);
    //CCR 0.4 мкс = 10 (0,417 мкс)
    //CCR 0.85 мкс = 21 (0,875 мкс)

// PWM1 Mode configuration : Channel2
//Edge - aligned ; not single pulse mode
    TIM_OCStructInit (&TIM_OCInitStructure );
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    //вкл выход на соответствующий выходной пин
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OC2Init(TIM2, &TIM_OCInitStructure );

    // вкл. Загрузка значения CCR2 в теневой регистр при событии обновления.
    TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);

    TIM2->CNT = 0;
    TIM2->CCR2 = 0;

    TIM_DMACmd(TIM2, TIM_DMA_CC2, ENABLE);  //Разрешаем запросы DMA

    TIM_ITConfig(TIM2, TIM_IT_CC2, ENABLE); // Разрешаем возникновение прерывания

// Enable Timer
    //TIM_Cmd(TIM2, ENABLE ); //вынесена в отдельную функцию

    //TIM2->CCER |= 0x10;     // делается выше ( TIM_OutputState_Enable )

    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        // DISABLE
	NVIC_Init(&NVIC_InitStructure);

}

//массив tbuf со значениями битов для диодной ленты.
//Каждый бит задаётся байтом в массиве. Массив разбит на:
// 1.секция сброса (после сброса передаваемые на ленту данные зажигают диоды С НАЧАЛА ленты).
//   Занимает 24 байта с нулевыми значениями. Линия будет притянута к земле
// 2. секция данных. Тут находятся значения GBR для каждого диода.
// 3. последний технический нулевой байт. Прижимает линию к земле после включения массива диодов.
//   (диоды остаются включёнными)
// Пользователь работает с секцией данных через tbuf.
//
// Порядок передачи байтов на ленту в порядке GRB и первым идёт старший бит.

void dma_initial(const void *tbuf){

	DMA_InitTypeDef DMA_InitStructure;
	DMA_Channel_TypeDef *txChan;

	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

	txChan = DMA1_Channel7;      //для канала 2 таймера 2

	DMA_DeInit(txChan);

	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(TIM2->CCR2));

	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;

	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_BufferSize = 1;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //DMA_Mode_Circular;
	DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

	//
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)tbuf;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
	DMA_Init(txChan, &DMA_InitStructure);

	//после установки флага будет происходить прерывание от DMA
	DMA_ITConfig(txChan, DMA_IT_TC, ENABLE);

	// Enable channel
	//DMA_Cmd(txChan, ENABLE);

	// Wait for completion
	//while (DMA_GetFlagStatus(DMA1_FLAG_TC7) == RESET);

	// Disable channels
	//DMA_Cmd(txChan, DISABLE);

	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel7_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;              // DISABLE
	NVIC_Init(&NVIC_InitStructure);

}

void ws2812b_init(const void *tbuf){
	uint8_t i;

	for ( i = 0; i < 48; i++ ) {
		((uint8_t*)tbuf)[i] = 0; //заполнение секции Сброса
	}

	//конечный технический байт. Прижимает линию.
	((uint8_t*)tbuf)[48 + NumberOfLEDS * 24] = 0;

	init_timers();
	dma_initial(tbuf);
}

void ws2812b_showLEDsArray(void){

	ws2812b_transferComplete = 0;

	DMA1_Channel7->CNDTR = 49 + NumberOfLEDS * 24;
	// Enable DMA channel
	DMA_Cmd(DMA1_Channel7, ENABLE);


TIM2->EGR |= 0x01; // для обновления регистров при включении таймера

	// Enable Timer
	TIM_Cmd(TIM2, ENABLE);

	while(!ws2812b_transferComplete);

}

// GRB и данные передаются старшим битом вперёд
void ws2812b_fillLEDsArray(uint8_t *tbuf, struct color * Colr){

uint8_t colors8bit[8], threeColors[3];
uint8_t bit;
uint8_t * data = tbuf + 48;
uint8_t i, z;
uint16_t y;

 if( Colr != (struct color *)0 ){
	 threeColors[0] = Colr->G;
	 threeColors[1] = Colr->R;
	 threeColors[2] = Colr->B;
 }else {
	 threeColors[0] = 0;
	 threeColors[1] = 0;
	 threeColors[2] = 0;
 }

 for ( z = 0; z < 3; z++ ){

	 bit = 0x80;

	 for( i = 0; i < 8; i++ ){

		 if( threeColors[z] & bit ){
			 colors8bit[i] = bitValue1;
		 }else {
			 colors8bit[i] = bitValue0;
		}

		 bit = bit >> 1;
	 }

	 for ( y = 0; y < NumberOfLEDS; y++ ){

		 for ( i = 0; i < 8; i++){
			 *(data + z*8 + y*24 + i) = colors8bit[i];
		 }
	 }

 }

}

void ws2812b_changeLed(uint8_t *tbuf, uint8_t on, uint16_t number, struct color * Colr){

uint8_t colors8bit[8], threeColors[3];
uint8_t bit;
uint8_t * data = tbuf + 48;
uint8_t i, z;

if ( number >= NumberOfLEDS )
	return;

if ( on ){
 threeColors[0] = Colr->G;
 threeColors[1] = Colr->R;
 threeColors[2] = Colr->B;
}else{
	threeColors[0] = 0;
	threeColors[1] = 0;
	threeColors[2] = 0;
}
 for ( z = 0; z < 3; z++ ){

	 bit = 0x80;

	 for( i = 0; i < 8; i++ ){

		 if( threeColors[z] & bit ){
			 colors8bit[i] = bitValue1;
		 }else {
			 colors8bit[i] = bitValue0;
		}

		 bit = bit >> 1;
	 }

	 for ( i = 0; i < 8; i++){
		 *(data + number*24 + z*8 + i) = colors8bit[i];
	 }


 }

}

void DMA1_Channel7_IRQHandler(void){

	if ( DMA1->ISR & DMA1_IT_TC7 ){
		DMA1->IFCR |= (DMA1_IT_TC7 | DMA1_IT_HT7);

		ws2812b_transferComplete = 1;

	}

}

void TIM2_IRQHandler(void){

	if( TIM2->SR & TIM_FLAG_CC2 ){
		TIM2->SR &= ~TIM_FLAG_CC2; // rc_w0

		if ( ws2812b_transferComplete == 1 ){
			// Disable Timer
			TIM_Cmd(TIM2, DISABLE);

			DMA_Cmd(DMA1_Channel7, DISABLE);

		}
	}
}


Комментарии




information