前言
有时间我就按照网上的时序推理了WS2812的传输时序。之前就推过时序了,但是当时时序好像没对,因为没用逻辑分析仪查看,就以为通过电片机的运行主频,在控制NOP,就能得到us级的延时控制,但是真实的情况是,调用调用函数,运行循环之类的对于us级来说也需要大量时间了,所以所推时序时,一定要用逻辑分析仪或示波器查看,时序电平的实际反转时间。
本文要点:1.WS2812的时序基础。2.基于STM32HAL库函数,的引脚电平反转,与WS2812数据传输的代码实现。优点:代码可能简单易懂。缺点:单纯的引脚反转,时序达不到WS2812所指定的要求,但是在控制一盏灯时,只要放在主循环中确能跑出,指定颜色的代码。所以说发出来给大家参考参考。有时间在用DMA写一个看看。
环境
- STM32F103C6T6系统板,72MHz主频
- 基于STM32CubeMX生成的HAL库代码
- keil5中,进行代码的编写修改
WS2812
-
时序基础
-
数据结构
代码实现
以下给出三个关键块的代码 WS2812S.H WS2812S.C main.c
下面中main.c中省略了,WS2812S数据引脚的初始化定义,还有WS2812S.H引脚的宏定义。大家可以自行调整配置。
-
WS2812S.H
#include "main.h" #include <WS2812S.H> //超低时间总得要些东西来堵塞 void WS2812S_Delay_300ns() { __NOP;__NOP;__NOP;__NOP;__NOP; } void WS2812S_Delay_800ns() { for(char i=1;i>0;i--) { __NOP;__NOP;__NOP;__NOP;__NOP; } } //0.1纳秒延时(没用了可忽视) void WS2812S_Delay_us(int Num) { for(;Num>0;Num--) { for(char js=7;js>0;js--) { __NOP(); } } } //WS2812S高电平 void WS2812S_H() { HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_SET); WS2812S_Delay_800ns(); HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_RESET); WS2812S_Delay_300ns(); } //WS2812S低电平 void WS2812S_L() { HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_SET); WS2812S_Delay_300ns(); HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_RESET); WS2812S_Delay_800ns(); } //WS2812S时序复位 void WS2812S_R() { //HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_RESET); WS2812S_Delay_us(50); } //WS2812S写入字节 void WS2812S_WR(uint8_t dat) { uint8_t i; for(i=0;i<8;i++) { if(dat & (1 << (7-i))) { WS2812S_H(); } else { WS2812S_L(); } } } //WS2812S点亮LED void WS2812S_WRLED(uint8_t green,uint8_t red,uint8_t blue) { HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_RESET); WS2812S_WR(green); WS2812S_WR(red); WS2812S_WR(blue); HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_SET); WS2812S_R(); } //十次的传输 void WS2812S_WRLED10(uint8_t green,uint8_t red,uint8_t blue) { char i; for(i=0;i<10;i++) { WS2812S_WRLED(green,red,blue); } }
-
WS2812S.C
#ifndef __WS2812S_H__ #define __WS2812S_H__ void WS2812S_H(void); void WS2812S_L(void); void WS2812S_R(void); void WS2812S_WR(uint8_t dat); void WS2812S_Delay_us(int Num); void WS2812S_WRLED(uint8_t green,uint8_t red,uint8_t blue); void WS2812S_WRLED10(uint8_t green,uint8_t red,uint8_t blue); #endif
-
main.c
//主函数中导人库后,调用配置即可 WS2812S_WRLED10(0,0,255); //WS2812S灯珠
时序问题
-
main.c调用WS2812S.H函数中WS2812S的’0’码和’1’码的函数,就已经完全超过时序要求的了。组成高电平的部分没超,低电平的超了。因为调用的过程中不可避免的有循环,函数之类的调用,增加了时间。
//WS2812S高电平 void WS2812S_H() { HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_SET); //WS2812S_Delay_800ns(); HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_RESET); //WS2812S_Delay_300ns(); } //WS2812S低电平 void WS2812S_L() { HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_SET); //WS2812S_Delay_300ns(); HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_RESET); //WS2812S_Delay_800ns(); }
-
但是我在主函数中,单纯的调用引脚电平反转,就貌似能组成可以用的时序电平时间。从一定程度上就能验证我上面的说法,调用的过程中不可避免的有循环,函数之类的调用,增加了时间。当然一般的模块都是要封装成模块,还有用循环来发送数据位的,所以说在主函数中直接调用肯定是行不通的。
HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(WS2812S_GPIO_Port, WS2812S_Pin, GPIO_PIN_RESET);