【stm32简单外设篇】- WS2812单线地址式 RGB 灯带

一、适用场景

适用场景:炫彩灯效(跑马、流水、渐变、呼吸)、状态指示、可穿戴灯光、舞台/装饰灯带、视觉化数值显示(温度/音频)以及练习精确时序、DMA+定时器应用与实时动画算法。

二、器材清单

WS2812(Neopixel)LED 灯带

stm32f103(或其它 STM32)开发板 ×1

若干杜邦线(母对母/公对母)×1组

稳定电源(3.3V 或 5V,按模块标注)×1

三、工作原理(要点)

WS2812 使用单线串行协议,时序严格(常见 800kHz):每个 LED 接收 24 位数据(按 GRB 顺序),每个位占用 约 1.25µs(Tbit)。

逻辑 0:T_H ≈ 0.35µs,T_L ≈ 0.9µs

逻辑 1:T_H ≈ 0.7µs,T_L ≈ 0.6µs

复位(latch)脉冲:拉低 ≥ 50µs,LED 把接收的 24×N 位数据写入并显示。

因时序严格,常用两种驱动方式:

软件 bit-bang(阻塞):屏蔽中断、严格微秒延时逐位输出 ------ 实现简单但会阻塞 CPU,适合少量 LED / 教学。

硬件 PWM + DMA:用定时器 PWM 输出固定周期(例如 1.25µs 分 30 个计数),把"0/1"映射为不同的 CCR 值,通过 DMA 把整个比特流送入 CCR 寄存器,精度高且 CPU 空闲,适合中大量灯带(我使用的也是这种方法)。

母头是输入信号,公头是输出,注意这里的公母头看的是接口的公母,不是外壳上的公母

四、接线示意

白色线 → GND

红色线 → 5V/3.3V电源

另外还有两根补压线(一根5V,一根GND,当LED灯带超过300个LED灯或灯带长5米时最后进行一个补压操作,实际补压时最好大于5V)暂时不使用

标准库

绿色线 → PB6

HAL库

绿色线 → PA8

五、示例代码

标准库

cpp 复制代码
#include "stm32f10x.h"
#include "stdio.h"
#include "bsp_SysTick.h"
#include "bsp_usart.h"
#include "oled.h"

#define led_num 10
uint32_t ws2812b_buf[led_num];
uint16_t ws2812b_bit[24*led_num+1];
uint16_t showflag=0,showtime=0;
int z;
char show[20];
int flag;


void WS2812B_IRQHandler(void)//自定义的中断,是当DMA搬运完一次数据后才会触发的
{
	TIM_SetCompare1(TIM4,0);//设置pwm的比较值为0
	TIM_Cmd(TIM4,DISABLE);//关闭定时器
	flag=1;
//	for(z=0;z<100000;z++);
}


void WS2812B_ClearBuf(void)
{
	uint8_t i;
	for(i=0;i<led_num;i++)
	{
		ws2812b_buf[i]=0x000000;//给每个led写入24位的0,即清除颜色
	}
}

void WS2812B_Init(void)
{
	Dma1_SetIRQHandler(WS2812B_IRQHandler);//指定DMA中断后执行的中断函数,并不是用系统的函数
	Dma1_Configuare((uint32_t)(&ws2812b_bit));//指定DMA帮运的数组
	Remote_Init();//进行TIM的初始化
}

void WS2812B_SetBuf(uint32_t color)
{
	uint8_t i;
	for(i=0;i<led_num;i++)
	{
		ws2812b_buf[i]=color;//循环给每个led赋值24位的颜色数据
	}
}

void WS2812B_UpdateBuf(void)//将所有灯的颜色数据进行全部更新
{
	uint8_t i,j;
	for(j=0;j<led_num;j++)//遍历每个灯
	{
		for(i=0;i<24;i++)//遍历每个灯的颜色位
		{
			if(ws2812b_buf[j]&(0x800000>>i))//判断每个灯的24位颜色数据,如果为0则定义其对应的PWM数据为30,反之为60
				ws2812b_bit[j*24+i]=60;
			else
				ws2812b_bit[j*24+i]=30;
		}
		Dma1_Configuare((uint32_t)(&ws2812b_bit));//将更新好的数组重新赋值到DMA初始化中
		Dma1_start(24*led_num);//启动DMA并将一次要搬运的数据个数指明
		Time4_run(ENABLE);//开启定时器
		while(flag==0);//等待数据搬运完成
		flag=0;
	}
}



void init_ALL(void)
{
	WS2812B_Init();
	WS2812B_ClearBuf();
	tim2_Init();
}

void showbreath()
{
	static uint8_t i,color;
	showtime=6;//设置运行时间
	if(i==0)WS2812B_SetBuf((color));
	if(i==1)WS2812B_SetBuf((255-color));
	if(i==2)WS2812B_SetBuf((color)<<8);
	if(i==3)WS2812B_SetBuf((255-color)<<8);
	if(i==4)WS2812B_SetBuf((color)<<16);
	if(i==5)WS2812B_SetBuf((255-color)<<16);
	if(i==6)WS2812B_SetBuf((color)|(color)<<8);
	if(i==7)WS2812B_SetBuf((255-color)|(255-color)<<8);
	if(i==8)WS2812B_SetBuf((color)|(color)<<16);
	if(i==9)WS2812B_SetBuf((255-color)|(255-color)<<16);
	if(i==10)WS2812B_SetBuf(((color)<<8)|((color)<<16));
	if(i==11)WS2812B_SetBuf(((255-color)<<8)|((255-color)<<16));
	if(i==12)WS2812B_SetBuf(((color))|((color)<<8)|((color)<<16));
	if(i==13)WS2812B_SetBuf(((255-color))|((255-color)<<8)|((255-color)<<16));
	
	color++;
	if(color==0)
	{
		i++;
		i%=14;
	}
}


void startshow()
{
	uint8_t i,num;
	uint32_t R,G,B;
	static uint8_t j;
	showtime=20;
	
	for(i=led_num;i>0;i--)
	{
		ws2812b_buf[i]=ws2812b_buf[i-1];
	}
	
	if(j==0)
	{
		num=rand()%7;
		if(num==0)ws2812b_buf[0]=0x0000ff;
		if(num==1)ws2812b_buf[0]=0x00ff00;
		if(num==2)ws2812b_buf[0]=0xff0000;
		if(num==3)ws2812b_buf[0]=0x00ffff;
		if(num==4)ws2812b_buf[0]=0xff00ff;
		if(num==5)ws2812b_buf[0]=0xffff00;
		if(num==6)ws2812b_buf[0]=0xffffff;
	}else if(j<15)
	{
		R=ws2812b_buf[1]/0X100%0x100;
		G=ws2812b_buf[1]/0X10000%0x100;
		B=ws2812b_buf[1]%0x100;
		
		if(G>20) G-=20;
		if(R>20) R-=20;
		if(B>20) B-=20;
		ws2812b_buf[0]=(G<<16)|(R<<8)|B;
	}else{
		ws2812b_buf[0]=0;
	}
	j++;
	j%=50;
}

int main(void)
{	
	init_ALL();
	USART_Config();
	while(1)
	{ 
		if(showflag == 1)//定时一段时间后会自动开启颜色设置
		{
			showflag=0;
			startshow();//将颜色数据写入数组中
//			showbreath();//将颜色数据写入数组中
			WS2812B_UpdateBuf();//将数组数据更新到DMA中,有DMA自行去将数据写入到LED灯带中
		}
	}
}
 
  
	
#include "bsp_SysTick.h"

extern uint16_t showflag,showtime;
void Remote_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    TIM_OCInitTypeDef  TIM_OCInitStruct;
 
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE); //使能PORTB时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);	//TIM4 时钟使能
 
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;				 //PB9 输入
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; 		//上拉输入
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
//    GPIO_SetBits(GPIOB,GPIO_Pin_9);	//初始化GPIOB.9
 
 
    TIM_TimeBaseStructure.TIM_Period = 90-1; //设定计数器自动重装值 最大10ms溢出
    TIM_TimeBaseStructure.TIM_Prescaler = 1-1; 	//配置输入分频,不分频,即72MHz,约等于1/72MHz=0.00000001s=10ns
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
 
    TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx
 
    TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;  // 选择输入端 IC4映射到TI4上
    TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;	//上升沿捕获
    TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStruct.TIM_Pulse = 0;	 //配置输入分频,不分频
		TIM_OC1Init(TIM4, &TIM_OCInitStruct);//初始化定时器输入捕获通道
 
    
		TIM_DMAConfig(TIM4,TIM_DMABase_CCR1,TIM_DMABurstLength_1Transfer);
		TIM_DMACmd(TIM4,TIM_DMA_Update,ENABLE);
		TIM_Cmd(TIM4,DISABLE);
}

void tim2_Init(void)
{
    NVIC_InitTypeDef NVIC_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
 
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);	//TIM2 时钟使能
 
 
    TIM_TimeBaseStructure.TIM_Period = 1000; //设定计数器自动重装值 最大1ms溢出
    TIM_TimeBaseStructure.TIM_Prescaler =(72-1); 	//预分频器,1M的计数频率,1us加1.
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
		TIM_TimeBaseStructure.TIM_RepetitionCounter= 0 ;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx
		TIM_ClearFlag(TIM2,TIM_FLAG_Update);//手动把更新中断标志位清除一下,解决刚初始化完就进入中断计数从1开始的问题
    TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
	
    
 
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;  //TIM2中断
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
    NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器

		TIM_Cmd(TIM2,ENABLE ); 	//使能定时器2
}


void Time4_run(FunctionalState NewState)//控制TIM的运行
{
	TIM_Cmd(TIM4,NewState);
}


void Time4_SetCompare(uint16_t Compare1)//控制TIM的比较值
{
	TIM_SetCompare1(TIM4,Compare1);
}



void TIM2_IRQHandler(void)
{
		static uint16_t rupttime;
    if(TIM_GetITStatus(TIM2,TIM_IT_Update)!=RESET)//计时器更新中断
    {
			rupttime++;
			if(rupttime >= showtime)//超时打开标志位去设置led的数据并打开灯
			{
				showflag=1;
				rupttime=0;
			}
    }
    TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}

HAL库

cpp 复制代码
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 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.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "more-led.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
TIM_HandleTypeDef htim1;
TIM_HandleTypeDef htim2;
DMA_HandleTypeDef hdma_tim1_ch1;

UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_TIM1_Init(void);
static void MX_TIM2_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
extern uint16_t showflag;
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_TIM1_Init();
  MX_TIM2_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */
	init_ALL();
	HAL_TIM_Base_Start_IT(&htim2);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {	
		if(showflag == 1)//定时一段时间后会自动开启颜色设置
		{
			showflag=0;
//			startshow();//将颜色数据写入数组中
			showbreath();//将颜色数据写入数组中
			WS2812B_UpdateBuf();//将数组数据更新到DMA中,有DMA自行去将数据写入到LED灯带中
		}
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief TIM1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM1_Init(void)
{

  /* USER CODE BEGIN TIM1_Init 0 */

  /* USER CODE END TIM1_Init 0 */

  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

  /* USER CODE BEGIN TIM1_Init 1 */

  /* USER CODE END TIM1_Init 1 */
  htim1.Instance = TIM1;
  htim1.Init.Prescaler = 0;
  htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim1.Init.Period = 89;
  htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim1.Init.RepetitionCounter = 0;
  htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_PWM_Init(&htim1) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 0;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
  if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
  sBreakDeadTimeConfig.DeadTime = 0;
  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
  if (HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM1_Init 2 */

  /* USER CODE END TIM1_Init 2 */
  HAL_TIM_MspPostInit(&htim1);

}

/**
  * @brief TIM2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_TIM2_Init(void)
{

  /* USER CODE BEGIN TIM2_Init 0 */

  /* USER CODE END TIM2_Init 0 */

  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM2_Init 1 */

  /* USER CODE END TIM2_Init 1 */
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 71;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 999;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM2_Init 2 */

  /* USER CODE END TIM2_Init 2 */

}

/**
  * @brief USART2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART2_UART_Init(void)
{

  /* USER CODE BEGIN USART2_Init 0 */

  /* USER CODE END USART2_Init 0 */

  /* USER CODE BEGIN USART2_Init 1 */

  /* USER CODE END USART2_Init 1 */
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 115200;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART2_Init 2 */

  /* USER CODE END USART2_Init 2 */

}

/**
  * Enable DMA controller clock
  */
static void MX_DMA_Init(void)
{

  /* DMA controller clock enable */
  __HAL_RCC_DMA1_CLK_ENABLE();

  /* DMA interrupt init */
  /* DMA1_Channel2_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(DMA1_Channel2_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel2_IRQn);

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
#include "more-led.h"

DMA_HandleTypeDef hdma_tim4_ch1;
extern TIM_HandleTypeDef htim1;
extern TIM_HandleTypeDef htim2;
extern DMA_HandleTypeDef hdma_tim4_up;
volatile uint32_t ADC_ConvertedValue[2]={0};
/* 颜色位缓冲区和实际 LED 数据缓冲 */

uint32_t ws2812b_buf[LED_NUM];
uint16_t ws2812b_bit[LED_NUM * 24];

/* 传输完成标志与回调指针 */
volatile uint8_t flag = 0;
uint16_t showtime;
uint8_t showflag;

/* 存放用户回调 */
static DMA_CallbackTypeDef user_dma_cb = NULL;


//static void WS2812B_IRQHandler(void)
//{
//    /* 关闭 PWM 输出------恢复为零电平 */
//    __HAL_TIM_SET_COMPARE(&htim4, TIM_CHANNEL_1, 0);
//    /* 停止 TIM4 PWM */
//    HAL_TIM_PWM_Stop(&htim4, TIM_CHANNEL_1);
//		
//    /* 通知上层数据已全部发送完 */
//    flag = 1;
//}

/*------------------------------------------------------------------------------*/
/* 2. 清空 LED 数据缓冲,所有颜色置黑 */
void WS2812B_ClearBuf(void)
{
    for (uint8_t i = 0; i < LED_NUM; i++)
    {
        ws2812b_buf[i] = 0x000000;
    }
}


/*------------------------------------------------------------------------------*/
/* 4. 设置所有 LED 同一个颜色 */
void WS2812B_SetBuf(uint32_t color)
{
    for (uint8_t i = 0; i < LED_NUM; i++)
    {
        ws2812b_buf[i] = color;
    }
}

/*------------------------------------------------------------------------------*/
/* 5. 更新并发送整个 LED 缓冲区 */
void WS2812B_UpdateBuf(void)
{
    for (uint8_t j = 0; j < LED_NUM; j++)
    {
			uint32_t c = ws2812b_buf[j];
        /* 逐 LED 展开为 24bit PWM 数据流 */
        for (uint8_t i = 0; i < 24; i++)
        {
					ws2812b_bit[j*24 + i] =
                (c & (1UL << (23 - i))) ? 60 : 30;
//            if (ws2812b_buf[j] & (0x800000 >> i))
//                ws2812b_bit[j * 24 + i] = 60;  /* "1" 对应高电平时长 */
//            else
//                ws2812b_bit[j * 24 + i] = 30;  /* "0" 对应高电平时长 */
        }
		}

		Time4_run(ENABLE);            // 启动 TIM4 + DMA
		while (flag == 0) { } 
		flag = 0;
}

/*------------------------------------------------------------------------------*/
/* 6. 系统初始化入口,清缓冲并启动周期中断(TIM2) */
void init_ALL(void)
{
    WS2812B_ClearBuf();
    /* TIM2 中断由 CubeMX 的 HAL_TIM_Base_Start_IT(&htim2) 在 main() 中启动 */
}

/*------------------------------------------------------------------------------*/
/* 7. 呼吸灯效果驱动,每次调用更新一次显示缓冲和定时参数 */
void showbreath(void)
{
    static uint8_t idx = 0, color = 0;

    showtime = 4;  /* 由 TIM2 中断控制刷新周期 */

    switch (idx)
    {
			case  0: WS2812B_SetBuf( color); break;
			case  1: WS2812B_SetBuf((255 - color)); break;
			case  2: WS2812B_SetBuf( color << 8); break;
			case  3: WS2812B_SetBuf((255 - color) << 8); break;
			case  4: WS2812B_SetBuf( color << 16); break;
			case  5: WS2812B_SetBuf((255 - color) << 16); break;
			case  6: WS2812B_SetBuf(( color) | ( color << 8)); break;
			case  7: WS2812B_SetBuf(((255 - color)) | ((255 - color) << 8)); break;
			case  8: WS2812B_SetBuf(( color) | ( color << 16)); break;
			case  9: WS2812B_SetBuf(((255 - color)) | ((255 - color) << 16)); break;
			case 10: WS2812B_SetBuf(( color << 8) | ( color << 16)); break;
			case 11: WS2812B_SetBuf(((255 - color) << 8) | ((255 - color) << 16)); break;
			case 12: WS2812B_SetBuf(( color) | ( color << 8) | ( color << 16)); break;
			case 13: WS2812B_SetBuf(((255 - color)) | ((255 - color) << 8) | ((255 - color) << 16)); break;
    }
    color++;
    if (color == 0)
    {
        idx = (idx + 1) % 14;
    }
}

/*------------------------------------------------------------------------------*/
/* 8. 滚动色带/随机色效果 */
void startshow(void)
{
    static uint8_t j = 0;
    uint8_t num;
    uint32_t R, G, B;

    showtime = 20;

    /* 数据右移一位,为最新色腾出位置 */
    for (uint8_t i = LED_NUM - 1; i > 0; i--)
    {
        ws2812b_buf[i] = ws2812b_buf[i - 1];
    }

    if (j == 0)
    {
        num = rand() % 7;
        const uint32_t colors[7] = {
            0x0000FF, 0x00FF00, 0xFF0000,
            0x00FFFF, 0xFF00FF, 0xFFFF00, 0xFFFFFF
        };
        ws2812b_buf[0] = colors[num];
    }
    else if (j < 15)
    {
        R=ws2812b_buf[1]/0X100%0x100;
				G=ws2812b_buf[1]/0X10000%0x100;
				B=ws2812b_buf[1]%0x100;

        if (G > 20) G -= 20;
        if (R > 20) R -= 20;
        if (B > 20) B -= 20;
        ws2812b_buf[0] = (G << 16) | (R << 8) | B;
    }
    else
    {
        ws2812b_buf[0] = 0;
    }
    j = (j + 1) % 50;
}



void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim)
{
	__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 0);
  HAL_TIM_PWM_Stop_DMA(&htim1,TIM_CHANNEL_1);
	flag = 1;
}




/**
  * @brief  启停 TIM4 PWM / DMA
  * @param  NewState: ENABLE 或 DISABLE
  */
void Time4_run(FunctionalState NewState)
{
    if (NewState) {
				HAL_TIM_PWM_Start_DMA(&htim1,TIM_CHANNEL_1,(uint32_t *)ws2812b_bit,LED_NUM * 24);
    } else {
        HAL_TIM_PWM_Stop_DMA(&htim1, TIM_CHANNEL_1);
    }
}

/**
  * @brief  更新 TIM4 的比较寄存器 CCR1 值,用于输出PWM控制LED
  * @param  Compare1: 新的比较值
  */
void Time4_SetCompare(uint16_t Compare1)
{
    __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, Compare1);
}

/**
  * @brief  TIM 中断定时到达回调(弱定义,可被重载)
  * @param  htim: 产生中断的定时器句柄
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
    static uint16_t rupttime = 0;

    if (htim->Instance == TIM2)
    {
        rupttime++;
        if (rupttime >= showtime)
        {
            showflag = 1;
            rupttime = 0;
        }
    }
}
#ifndef _ADC_TIME_H
#define _ADC_TIME_H
#include "main.h"
#define LED_NUM    10
/* 用户用来接收 DMA 完成回调的函数指针类型 */
typedef void (*DMA_CallbackTypeDef)(void);
static void WS2812B_IRQHandler(void);
void WS2812B_ClearBuf(void);
void WS2812B_Init(void);
void WS2812B_SetBuf(uint32_t color);
void WS2812B_UpdateBuf(void);
void init_ALL(void);
void showbreath(void);
void startshow(void);
void Dma1_SetIRQHandler(DMA_CallbackTypeDef cb);
void Dma1_start(uint16_t DataNumber);
void Time4_run(FunctionalState NewState);
void Time4_SetCompare(uint16_t Compare1);
void Dma1_Configuare(uint32_t MemoryBaseAddr);
#endif

六、讲解视频

https://www.bilibili.com/video/BV1hvNNzaE3d/?spm_id_from=333.1387.homepage.video_card.click&vd_source=b035825fef3be39bc47a9c50b324d086

https://www.bilibili.com/video/BV1wiNKzQE8v/?spm_id_from=333.1387.homepage.video_card.click&vd_source=b035825fef3be39bc47a9c50b324d086

https://www.bilibili.com/video/BV1ZtNuzXEod/?spm_id_from=333.1387.homepage.video_card.click&vd_source=b035825fef3be39bc47a9c50b324d086

相关推荐
7yewh5 小时前
jetson_yolo_deployment 02_linux_dev_skills
linux·python·嵌入式硬件·yolo·嵌入式
djarmy5 小时前
ubuntu20.04搭建openharmony6.0的master分支。构建编译环境报错解决记录
c语言·ubuntu
竹烟淮雨7 小时前
C语言指针概念详解:数组指针与二级指针的本质区别
c语言
senijusene9 小时前
用C语言制作一个简易HTTP服务器:实现手机商城用户认证与搜索
服务器·c语言·http
Aaswk9 小时前
蓝桥杯2025年第十六届省赛真题(更新中)
c语言·数据结构·c++·算法·职场和发展·蓝桥杯
香水5只用六神9 小时前
【DMA】存储器到外设模式实验2
c语言·git·stm32·单片机·嵌入式硬件·github·visual studio
Yupureki10 小时前
《C++实战项目-高并发内存池》4.CentralCache构造
c语言·开发语言·c++·单例模式·github
forAllforMe10 小时前
用STM32+LAN9252实现etherCAT 从站IO控制
stm32·单片机·嵌入式硬件
AnalogElectronic10 小时前
RP2040学习4,LED点亮,OLED显示,DHT11温湿度传感器数据读取
单片机·嵌入式硬件·学习