基于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 灯珠。一根信号线就能串很多个发不同数据就能亮任意颜色时序要求很严一般用PWMDMA驱动。若是多个WS2812级联上一个WS2812的DO接入下一个WS2812的DI。三、开发环境STM32cubeMXkeil5使用HAL库开发下载工具为ST-Link。四、实验1原理WS2812光敏传感器光敏传感器WS2812实现天暗自动开灯光敏传感器作用及其运作原理使用四引脚光敏传感器VCC与GND正常接入面包板DO数字输出可通过电位器调节阈值接入设定IO引脚引脚检测到电平变化后触发相应代码则对WS2812进行点亮或者实现其他功能。WS2812作用及其运作原理VCC接5V但是我自己实测接3.3V也没问题GND接GNDDIN接设定的IO引脚。使用TIM生成PWMDMA的形式对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执行其他任务。用了 DMACPU 把 “发灯珠数据” 这个活交给 DMA 后自己就能去处理其他任务直接提高效率。一段话总结本项目通过四引脚光敏传感器检测环境亮度光敏电阻随光线强弱改变阻值经模块比较器从 DO 引脚输出高低电平来判断天黑或天亮电平变化触发单片机动作单片机利用定时器 PWMDMA驱动 WS2812 灯珠通过调整 PWM 占空比生成符合时序要求的 0 码、1 码和复位信号按 GRB 顺序控制灯光颜色DMA 保证时序精准且不占用 CPU最终实现环境变暗时自动点亮 WS2812 灯光的完整流程。实验1STM32cubeMX配置流程引脚配置使用到的引脚如下图串口引脚可用来调试打印电压用不冲突光敏传感器引脚选择接入PA0任意的可输入的引脚配置为默认上拉Pull-up。WS2812引脚由于要使用到PWM我们选择TIM1的PWM通道1。引脚为PA8。PWM参数配置PWM参数配置基于我的时钟主频为8MHz如图折叠起来的使用默认参数分频系数不选择分频。计数模式向上计数。自动重装载值9。不使用影子寄存器。计算是否符合WS2812的计数周期1.25us8MHz周期为125ns因为自动重装载值为9则计数器需计数91次则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、ARR9高电平持续时间 (CCR1) * 时钟周期。要达成0码高电平约0.4us需要CCR31码高电平约0.8us需要CCR6。 #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的PWMDMA方式发送数据实现精确时序控制 * 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的开关和亮暗调节。实验2STM32cubeMX配置流程引脚配置使用到的引脚如下图串口引脚可用来打印电压调试用不冲突光敏传感器引脚选择接入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次采样的算术平均值 * count8sum是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_ValueGet_ADCValue(); uint16_t dutyGet_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站无敌贵点大王
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2412079.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!