基于STM32的智能灯控系统(光敏传感器+WS2812/LED)涉及PWM/DMA/ADC
一、前言
这是实验室项目要求实现的一个小功能,自己又想试一下写博客,都说有帮助,所以打算试一下,如有错误请指正!谢谢大家!
并且我发现CSDN的各种标题都长得差不多,可能看着会很混乱,强烈推荐配合目录食用!
实现的功能
亮度或光照变暗时,WS2812或者LED自动变亮,其中LED可以实现随光照强度自动调整亮度。
二、使用到的核心模块
STM32F103C8T6、WS2812、LED、光敏传感器。
光敏传感器

检测环境亮暗,光越强电阻越小,输出电压变化。有AO 模拟量(测亮度)和DO 数字量(亮 / 暗开关),用来做光控灯、亮度检测。
配合LED时:使用四引脚光敏传感器,VCC与GND正常接入面包板,AO(模拟输出,配合ADC使用)接入ADC指定的IO引脚,引脚持续获取光敏传感器输出的ADC值,达到自己设置的阈值触发代码,对LED进行点亮并根据光敏传感器输出的ADC时时调整亮度。
WS2812模块

WS2812 就是自带驱动芯片的 RGB 灯珠。一根信号线就能串很多个,发不同数据就能亮任意颜色,时序要求很严,一般用 PWM+DMA 驱动。若是多个WS2812级联,上一个WS2812的DO接入下一个WS2812的DI。

三、开发环境
STM32cubeMX+keil5,使用HAL库开发,下载工具为ST-Link。
四、实验1原理(WS2812+光敏传感器)
光敏传感器+WS2812实现天暗自动开灯
光敏传感器作用及其运作原理
使用四引脚光敏传感器,VCC与GND正常接入面包板,DO(数字输出,可通过电位器调节阈值)接入设定IO引脚,引脚检测到电平变化后,触发相应代码,则对WS2812进行点亮或者实现其他功能。
WS2812作用及其运作原理
VCC接5V(但是我自己实测接3.3V也没问题),GND接GND,DIN接设定的IO引脚。
使用TIM生成PWM+DMA的形式对WS2812实现控制。并且WS2812有 0码、1码和reset 三种输入信号的概念,可以理解为专属于WS2812的低电平0和高电平1,以此来对WS2812模块进行编程,可以通过特殊方式(下面的原理讲)给WS2812生成0码和1码和reset。
WS2812原理:在指定计数周期下(1.25us),不同的高电平信号时长能输出0码、1码和reset信号。

简单记:短高电平 = 0;长高电平 = 1;长低电平 = 复位。
WS2812运作原理:每颗灯珠能按照G, R, B(绿红蓝)三种颜色,组合实现炫彩的灯光效果。而要修改其色彩,需要对其输入24个bit长度的0码/1码信号进行调整。严格按照G, R, B(绿红蓝)顺序,每种颜色需8个字节长度的0码/1码信号作为颜色数据,3种颜色即需要24位。
例如:红色(1111 1111)绿色(0000 0000)蓝色(0000 0000)=红色(255,0,0)
红色(1111 1111)绿色(1111 1111)蓝色(1111 1111)=白色(255,255,255)

关于如何调整颜色数据,因为涉及到一个计数周期内的高电平时长(0码/1码的设置),我们考虑使用PWM波,通过调节PWM波的占空比,实现调整一个时钟周期内的高电平时长,从而实现输出0码/1码/reset信号的目的。
至于使用DMA,是因为WS2812对时序要求十分严格,cpu手动控制时,可能一个小小的中断,甚至可能是因为cpu慢了一点,就会导致时序错误;其次,不使用DMA,对cpu占用严重,cpu就得一直盯着翻转电平,严重干扰cpu执行其他任务。用了 DMA,CPU 把 “发灯珠数据” 这个活交给 DMA 后,自己就能去处理其他任务,直接提高效率。
一段话总结
本项目通过四引脚光敏传感器检测环境亮度,光敏电阻随光线强弱改变阻值,经模块比较器从 DO 引脚输出高低电平来判断天黑或天亮,电平变化触发单片机动作;单片机利用定时器 PWM+DMA驱动 WS2812 灯珠,通过调整 PWM 占空比生成符合时序要求的 0 码、1 码和复位信号,按 GRB 顺序控制灯光颜色,DMA 保证时序精准且不占用 CPU,最终实现环境变暗时自动点亮 WS2812 灯光的完整流程。
实验1:STM32cubeMX配置流程
引脚配置
使用到的引脚如下图(串口引脚可用来调试打印电压用,不冲突)

光敏传感器引脚:选择接入PA0(任意的可输入的引脚)配置为默认上拉(Pull-up)。
WS2812引脚:由于要使用到PWM,我们选择TIM1的PWM通道1。引脚为PA8。
PWM参数配置
PWM参数配置,基于我的时钟主频为8MHz,如图(折叠起来的使用默认参数)
分频系数:不选择分频。
计数模式:向上计数。
自动重装载值:9。
不使用影子寄存器。

计算是否符合WS2812的计数周期(1.25us):8MHz=周期为125ns,因为自动重装载值为9,则计数器需计数(9+1)次,则125ns计算10次的时间=1.25us,符合WS2812严格的时序要求。
DMA参数配置
模式为正常模式。
为TIM1_CH1添加DMA,数据搬运方向为内存到外设(对应代码中存有WS2812颜色数据的数组到TIM1_CH1通道的寄存器)。
设置外设地址不自增,内存地址自增。
设置数据长度为半字。

实验1:核心代码解读
(其余配置类代码由STM32cubeMX生成,这里不做展示,只展示核心代码)
重要的宏定义与变量
#define WS2812_NUM 16 //灯珠数量,我买的模块直接集成了16个灯珠
#define WS2812_BITS 24 //每个灯珠的24bit颜色数据位
#define TIM1_ARR 9 //PWM的ARR值,能直接修改,方便适应其他的主频
//结合主频8MHz、ARR=9,高电平持续时间 = (CCR+1) * 时钟周期。要达成0码(高电平约0.4us),需要CCR=3;1码(高电平约0.8us),需要CCR=6。
#define CODE0_HIGH 3 //根据1.25us的周期,我计算出来的0码所需要设置的高电平的占空比
#define CODE1_HIGH 6 //根据1.25us的周期,我计算出来的1码所需要设置的高电平的占空比
uint32_t WS2812_buf[WS2812_NUM*WS2812_BITS];//存取WS2812颜色数据
WS2812设置颜色函数
/**
* @brief 设置WS2812灯带的颜色
* @param r 红色分量 (0-255)
* @param g 绿色分量 (0-255)
* @param b 蓝色分量 (0-255)
* @retval 无
*/
void WS2812_SetColor(uint8_t r, uint8_t g, uint8_t b)
{
// 循环计数器
int8_t i = 0, j = 0;
// 将RGB三色合并为32位数据(格式: GRB)
uint32_t color = ((uint32_t)g << 16) | ((uint32_t)r << 8) | (uint32_t)b;
// 缓冲区索引,记录当前写入位置
uint16_t idx = 0;
// 双重循环:为每一颗LED生成对应的颜色数据
for(j = 0; j < WS2812_NUM; j++) // 遍历所有LED
{
for(i = WS2812_BITS - 1; i >= 0; i--) // 从高位到低位处理24位颜色数据
{
// 检查当前位是否为1
if(color & (1 << i)) // 如果该位为1
{
WS2812_buf[idx++] = CODE1_HIGH; // 存储表示"1"的时序数据
}
else // 如果该位为0
{
WS2812_buf[idx++] = CODE0_HIGH; // 存储表示"0"的时序数据
}
}
}
}
写入颜色数组函数
/**
* @brief 发送WS2812颜色数据到灯带
* @note 通过TIM1的PWM+DMA方式发送数据,实现精确时序控制
* @retval 无
*/
void WS2812_SendData(void)
{
// 停止之前的DMA传输,确保通道空闲
HAL_TIM_PWM_Stop_DMA(&htim1, TIM_CHANNEL_1);
// 启动PWM DMA传输
// 参数1: 定时器句柄 htim1
// 参数2: 定时器通道 TIM_CHANNEL_1
// 参数3: 要发送的数据缓冲区指针 WS2812_buf
// 参数4: 数据长度 WS2812_NUM * WS2812_BITS
// 说明: DMA会自动将缓冲区中的数据按顺序发送,每个数据决定PWM的高电平时间
HAL_TIM_PWM_Start_DMA(&htim1, TIM_CHANNEL_1,
(uint32_t*)WS2812_buf,
WS2812_NUM * WS2812_BITS);
// 延时1ms,等待DMA传输完成
// 注意: 此延时时间需要根据实际数据量调整
// 如果延时太短可能导致数据未发送完,太长会影响响应速度
HAL_Delay(1);
}
main函数
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_USART1_UART_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
// 初始状态:点亮所有LED为白色(200,200,200)
WS2812_SetColor(200, 200, 200);
WS2812_SendData(); // 发送数据到LED灯带
while (1)
{
//读取光敏传感器的信号
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET)
{
//低于阈值,点亮WS2812为白色(可自己调整颜色)
WS2812_SetColor(200,200,200);
WS2812_SendData();
}
else
{
//高于阈值,关灯
WS2812_SetColor(0,0,0);
WS2812_SendData();
}
HAL_Delay(50); //延时用于防止DMA在前一次传输未完成时又被启动,导致数据错乱
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
/* USER CODE END 3 */
}
效果演示请关注dy或者b站"无敌贵点大王"
五、实验2原理(普通LED+光敏传感器)
光敏传感器+LED实现天暗自动开灯并且自调亮度
光敏传感器作用及其运作原理
使用四引脚光敏传感器,VCC与GND正常接入面包板,AO(模拟输出)接入设定IO引脚,引脚实时监测光敏传感器的模拟输出,触发相应代码,对LED进行点亮并调节亮度。
LED作用及其运作原理
具象体现ADC值对电压的影响。
一段话总结
通过光敏传感器的模拟输出(AO),持续获得因光照变化的ADC值,再通过实时变化的ADC值,实时修改PWM波的占空比,从而实现实时修改LED引脚的电压值,实现控制LED的开关和亮暗调节。
实验2:STM32cubeMX配置流程
引脚配置
使用到的引脚如下图(串口引脚可用来打印电压调试用,不冲突)

光敏传感器引脚:选择接入PA0(任意的可输入的引脚)配置为默认上拉(Pull-up)。
LED引脚:由于要使用到PWM,我们选择TIM1的PWM通道1。引脚为PA8。
PWM参数配置
PWM参数配置,基于我的时钟主频为8MHz,如图(折叠起来的使用默认参数)
分频系数:7,使得频率变成1MHz。
计数模式:向上计数。
自动重装载值:999,使得周期变成1ms。
使用影子寄存器,防止PWM波形突变。

ADC参数配置(使用ADC1通道0)
独立模式;不打开扫描;不打开连续转换;转换通道数量为1。

实验2:核心代码解读
重要的宏定义和变量
/* ADC采样值范围定义 */
#define ADC_MAX 4096 /* ADC最大采样值(12位ADC,理论最大值4095,这里用4096便于计算)*/
#define ADC_MIN 1000 /* ADC最小有效采样值(用来控制触发LED点亮的阈值,值越大,点亮LED的亮度越大)*/
/* PWM比较值范围定义,便于适应不同时钟频率下的PWM值*/
#define PWM_CCR_MAX 999 /* PWM最大比较值(对应100%占空比,TIM1周期设为999)*/
#define PWM_CCR_MIN 0 /* PWM最小比较值(对应0%占空比,输出低电平)*/
获取ADC值函数
/**
* @brief 获取ADC采样值(8次采样取平均,软件滤波)
* @param void
* @return uint32_t 返回8次采样的平均值,范围:0-4095
* @note 此函数会阻塞运行,直到完成8次采样
* 每次采样间隔取决于ADC转换时间(约几微秒)
*/
uint32_t Get_ADCValue()
{
uint32_t ADC_Value = 0; /* 最终返回的平均值 */
uint32_t sum = 0; /* 8次采样的累加和 */
uint32_t count = 0; /* 循环计数器 */
/* 1. 启动ADC转换 */
HAL_ADC_Start(&hadc1);
/* 连续采样8次,用于软件滤波 */
for(count = 0; count < 8; count++)
{
/* 2. 等待转换完成
* HAL_MAX_DELAY:无限等待直到转换完成
* 函数会在转换完成后返回
*/
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
/* 3. 读取转换结果并累加
* HAL_ADC_GetValue() 返回12位ADC值(0-4095)
*/
sum += HAL_ADC_GetValue(&hadc1);
/* 注意:这里没有停止ADC,下次循环继续启动
* 每次循环都 等待-读取,确保每次采样独立
*/
}
/* 4. 计算8次采样的算术平均值
* count=8,sum是8次总和
* 平均值 = 总和 / 8
*/
ADC_Value = sum / count; /* 整数除法,自动取整 */
/* 5. 停止ADC,降低功耗
* 采样完成后关闭ADC,直到下次需要采样再开启
*/
HAL_ADC_Stop(&hadc1);
/* 6. 返回滤波后的ADC值 */
return ADC_Value;
}
计算并调整CCR的函数
/**
* @brief 将ADC采样值映射到PWM比较值(CCR)
* @param ADC_Value 获取的ADC采样值(0-4095)
* @return uint16_t 映射后的PWM比较值(0-999)
* @note 实现ADC值到PWM占空比的线性映射
*/
uint16_t Get_CCR(uint32_t ADC_Value)
{
uint16_t CCR; /* 存储计算得到的PWM比较值 */
/* 映射关系:
ADC_MIN (1000) → 0 (LED熄灭)
ADC_MAX (4096) → 999 (LED最亮)
*/
if(ADC_Value < ADC_MIN) ADC_Value = ADC_MIN; /* 低于下限则取下限,最暗 */
if(ADC_Value > ADC_MAX) ADC_Value = ADC_MAX; /* 高于上限则取上限 ,最亮*/
/* 线性映射公式:
* 公式:CCR = 999 * (ADC - 1000) / (4096 - 1000)
* 将[ADC_MIN, ADC_MAX]区间线性映射到[PWM_CCR_MIN, PWM_CCR_MAX]区间
*/
CCR = (PWM_CCR_MAX * (ADC_Value - ADC_MIN) / (ADC_MAX - ADC_MIN));
return CCR; /* 返回计算得到的PWM比较值 */
}
main函数
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_ADC1_Init();
MX_TIM1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
//提前打开PWM通道1
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
/* USER CODE END 2 */
/* Infinite loop */
while (1)
{
//调用函数,先获取ADC值,在通过ADC值转换成CCR值
uint32_t ADC_Value=Get_ADCValue();
uint16_t duty=Get_CCR(ADC_Value);
//设置转换好的CCR值,实现控灯效果
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, duty);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
效果演示请关注dy或者b站"无敌贵点大王"
更多推荐
所有评论(0)