返回列表 发新帖我要提问本帖赏金: 20.00元(功能说明)

[STM32] 硬件太丑,B格不够,只好彩灯来凑--STM32F103 SPI驱动WS2812

[复制链接]
 楼主| 呐咯密密 发表于 2021-4-1 15:06 | 显示全部楼层 |阅读模式
<
本帖最后由 呐咯密密 于 2021-4-9 10:02 编辑

#申请原创#
之前发了一个基于涂鸦平台的宠物自动喂食器点此跳转,已经有小夜灯功能了,但是躁动的心岂能因此平静,功能以及花里胡哨,那就让他华丽起来,除了画手们的涂装风格,作为电子工程师,得用我们自己的方式美化。正好手里有个ws2812的灯环,全彩模式开始。
WS2812特点:
1.WS2812灯珠内置控制电路与RGB芯片,集成在一个5050封装的元器件中,构成一个完整的外控像素点。
2.每个像素点的三基色可实现256级亮度显示,完成16777216种颜色的全真色彩显示,扫描频率不低于400Hz/s。
3.串行级联接口,能通过一根信号线完成数据的接收与解码。
4.当刷新速率30帧/秒时,低速模式级联数不小于512点,高速模式不小于1024点。
5.数据发送速度可达800Kbps。
数据传输:
ws2812的每颗灯珠的控制需要24位数据,分为8位绿色+8位红色+8位蓝色,每个像素点的三基色颜色可实现256级亮度显示,完成16777216种颜色的全真色彩显示。灯珠之间采用串行级联,在上电复位以后,控制器可向灯带发送一串24bit的数据,比如需要点亮10颗灯,我们发送10个24bit的数据到第一个灯,第一个灯的DIN端接受控制器发过来的24bit数据,第一个灯珠会提取第一个24bit数据后会将该数据送进数据锁存器,剩余的数据经过内部整形电路向下传送,直到所有的灯都获取一个24bit数据。这10个24bit数据的发送间隔不能超过50us,否则会导致下一次的颜色数据被第一个重新锁存,就无法完成完整的点亮。
这里需要着重点一下时序相关的知识,先看时序波形图和数据传输时间表:

根据上面两个图可以看到ws2812的电平反转是纳秒级别的,所以在使用单片机外设时我们需要对单片机的外设的传输速度进行控制,速度慢了根本无法点亮。
操作开始:
在驱动ws2812的时候一般采用PWM或者SPI的方式,这两个速度较快,比直接使用IO口进行电平反转要方便,且控制效果更好。这里我们采用SPI的方式,因为在点灯是的数据发送间隔时间的约束,如果我们使用SPI发送的数据较多,中途遇到中断可能会打断我们的点灯,所以我这里使用SPI的DMA进行控制,不怕被其他影响。

通过硬件SPI我们模拟WS2812的通信时序。单片机选用STM32F103,主频72M,SPI1分频设置为8,这样SPI1的通信频率为9M。时间很充足,如果使用SPI2,则需要减小分频系数,否则无法点亮。我这里采用的便是SPI2,4分频,因为SPI1被我的屏幕占用了。
.h文件只要定义了灯珠的个数和0码1码。不同的灯珠数量只需修改PIXEL_NUM 的值。
  1. #ifndef __WS2812_H
  2. #define __WS2812_H

  3. #include "stm32f10x.h"

  4. #define PIXEL_NUM 24

  5. //硬件spi模拟ws2811时序(用spi的8位数据模拟ws281x的一位数据)
  6. //要将系统时钟设置为56M,分频数设置为8,则SPI的通信频率为7M,传输一位数据的时间约为143纳秒(ns)
  7. //3*143 = 429ns   5*143 = 715ns  符合WS281X芯片的通信时序。
  8. //  _____   
  9. // |     |___|   11111000  high level
  10. //  ___         
  11. // |   |_____|   11100000  low level

  12. #define WS_HIGH 0XF8
  13. #define WS_LOW  0XE0

  14. void ws281x_init(void);
  15. void ws281x_closeAll(void);
  16. void ws281x_rainbowCycle(uint8_t wait);
  17. uint32_t ws281x_color(uint8_t red, uint8_t green, uint8_t blue);
  18. void ws281x_setPixelColor(uint16_t n ,uint32_t GRBcolor);
  19. void ws281x_show(void);

  20. void ws281x_theaterChase(uint32_t c, uint8_t wait);
  21. void ws281x_colorWipe(uint32_t c, uint8_t wait);
  22. void ws281x_rainbow(uint8_t wait);
  23. void ws281x_theaterChaseRainbow(uint8_t wait);

  24. #endif /* __WS2812_H */
  1. #include "../BOARD/ws2812/ws2812.h"
  2. #include "usart.h"
  3. #include "delay.h"

  4. uint8_t pixelBuffer[PIXEL_NUM][24] ;


  5. void ws281x_init(void)
  6. {
  7.   GPIO_InitTypeDef GPIO_InitStructure;
  8.   SPI_InitTypeDef  SPI_InitStructure;
  9.   DMA_InitTypeDef DMA_InitStructure;

  10.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); //PORTA时钟使能
  11.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE); //SPI1时钟使能         
  12.         RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);        //使能DMA传输


  13.   /* PA7  SPI1_MOSI */
  14.         GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
  15.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //PA7复用推挽输出 SPI
  16.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  17.         GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOA

  18.         SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;  //设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工
  19.         SPI_InitStructure.SPI_Mode = SPI_Mode_Master;                //设置SPI工作模式:设置为主SPI
  20.         SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;                //设置SPI的数据大小:SPI发送接收8位帧结构
  21.         SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;                //串行同步时钟的空闲状态为低电平
  22.         SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;        //串行同步时钟的第2个跳变沿(上升或下降)数据被采样
  23.         SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;                //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
  24.         SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;                //定义波特率预分频的值:波特率预分频值为16
  25.         SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;        //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
  26.         SPI_InitStructure.SPI_CRCPolynomial = 7;        //CRC值计算的多项式
  27.         SPI_Init(SPI2, &SPI_InitStructure);  //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器

  28.         SPI_Cmd(SPI2, ENABLE); //使能SPI外设
  29.   SPI_I2S_DMACmd(SPI2, SPI_I2S_DMAReq_Tx, ENABLE);
  30.   
  31.         DMA_DeInit(DMA1_Channel5);   //将DMA的通道1寄存器重设为缺省值
  32.         DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(SPI2 -> DR); //cpar;  //DMA外设ADC基地址
  33.         DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)pixelBuffer; //cmar;  //DMA内存基地址
  34.         DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;  //数据传输方向,从内存读取发送到外设
  35.         DMA_InitStructure.DMA_BufferSize = PIXEL_NUM * 24; //cndtr;  //DMA通道的DMA缓存的大小
  36.         DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //外设地址寄存器不变
  37.         DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //内存地址寄存器递增
  38.         DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  //数据宽度为8位
  39.         DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
  40.         DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  //工作在正常缓存模式
  41.         DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级
  42.         DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x没有设置为内存到内存传输
  43.         DMA_Init(DMA1_Channel5, &DMA_InitStructure);  //根据DMA_InitStruct中指定的参数初始化DMA的通道USART1_Tx_DMA_Channel所标识的寄存器
  44.   
  45.   ws281x_closeAll();  //关闭全部的灯
  46.   delay_ms(100); //关闭全部的灯需要一定的时间  
  47. }

  48. void ws281x_closeAll(void)
  49. {
  50.   uint16_t i;
  51.   uint8_t j;
  52.   
  53.   for(i = 0; i < PIXEL_NUM; ++i)
  54.   {
  55.     for(j = 0; j < 24; ++j)
  56.     {
  57.       pixelBuffer[i][j] = WS_LOW;
  58.     }
  59.   }
  60.   ws281x_show();
  61. }

  62. uint32_t ws281x_color(uint8_t red, uint8_t green, uint8_t blue)
  63. {
  64.   return green << 16 | red << 8 | blue;
  65. }

  66. void ws281x_setPixelColor(uint16_t n ,uint32_t GRBcolor)
  67. {
  68.   uint8_t i;
  69.   if(n < PIXEL_NUM)
  70.   {
  71.     for(i = 0; i < 24; ++i)
  72.     {
  73.       pixelBuffer[n][i] = (((GRBcolor << i) & 0X800000) ? WS_HIGH : WS_LOW);
  74.     }
  75.   }
  76. }

  77. void ws281x_setPixelRGB(uint16_t n ,uint8_t red, uint8_t green, uint8_t blue)
  78. {
  79.   uint8_t i;
  80.   
  81.   if(n < PIXEL_NUM)
  82.   {
  83.     for(i = 0; i < 24; ++i)
  84.     {
  85.       pixelBuffer[n][i] = (((ws281x_color(red,green,blue) << i) & 0X800000) ? WS_HIGH : WS_LOW);
  86.     }
  87.   }
  88. }

  89. void ws281x_show(void)
  90. {
  91.     DMA_Cmd(DMA1_Channel5, DISABLE );  //关闭USART1 TX DMA1 所指示的通道
  92.     DMA_ClearFlag(DMA1_FLAG_TC5);   
  93.          DMA_SetCurrDataCounter(DMA1_Channel5,24 * PIXEL_NUM );//DMA通道的DMA缓存的大小
  94.          DMA_Cmd(DMA1_Channel5, ENABLE);  //使能USART1 TX DMA1 所指示的通道
  95. }



  96. // Input a value 0 to 255 to get a color value.
  97. // The colours are a transition r - g - b - back to r.
  98. uint32_t ws281x_wheel(uint8_t wheelPos) {
  99.   wheelPos = 255 - wheelPos;
  100.   if(wheelPos < 85) {
  101.     return ws281x_color(255 - wheelPos * 3, 0, wheelPos * 3);
  102.   }
  103.   if(wheelPos < 170) {
  104.     wheelPos -= 85;
  105.     return ws281x_color(0, wheelPos * 3, 255 - wheelPos * 3);
  106.   }
  107.   wheelPos -= 170;
  108.   return ws281x_color(wheelPos * 3, 255 - wheelPos * 3, 0);
  109. }

  110. // Fill the dots one after the other with a color
  111. void ws281x_colorWipe(uint32_t c, uint8_t wait) {
  112.   for(uint16_t i=0; i<PIXEL_NUM; i++) {
  113.     ws281x_setPixelColor(i, c);
  114.     ws281x_show();
  115.     delay_ms(wait);
  116.   }
  117. }

  118. void ws281x_rainbow(uint8_t wait) {
  119.   uint16_t i, j;

  120.   for(j=0; j<256; j++) {
  121.     for(i=0; i<PIXEL_NUM; i++) {
  122.       ws281x_setPixelColor(i, ws281x_wheel((i+j) & 255));
  123.     }
  124.     ws281x_show();
  125.     delay_ms(wait);
  126.   }
  127. }

  128. // Slightly different, this makes the rainbow equally distributed throughout
  129. void ws281x_rainbowCycle(uint8_t wait) {
  130.   uint16_t i, j;

  131.   for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
  132.     for(i=0; i< PIXEL_NUM; i++) {
  133.       ws281x_setPixelColor(i,ws281x_wheel(((i * 256 / PIXEL_NUM) + j) & 255));
  134.     }
  135.     ws281x_show();
  136.     delay_ms(wait);
  137.   }
  138. }

  139. //Theatre-style crawling lights.
  140. void ws281x_theaterChase(uint32_t c, uint8_t wait) {
  141.   for (int j=0; j<10; j++) {  //do 10 cycles of chasing
  142.     for (int q=0; q < 3; q++) {
  143.       for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
  144.         ws281x_setPixelColor(i+q, c);    //turn every third pixel on
  145.       }
  146.       ws281x_show();

  147.       delay_ms(wait);

  148.       for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
  149.         ws281x_setPixelColor(i+q, 0);        //turn every third pixel off
  150.       }
  151.     }
  152.   }
  153. }

  154. //Theatre-style crawling lights with rainbow effect
  155. void ws281x_theaterChaseRainbow(uint8_t wait) {
  156.   for (int j=0; j < 256; j++) {     // cycle all 256 colors in the wheel
  157.     for (int q=0; q < 3; q++) {
  158.       for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
  159.         ws281x_setPixelColor(i+q, ws281x_wheel( (i+j) % 255));    //turn every third pixel on
  160.       }
  161.       ws281x_show();

  162.       delay_ms(wait);

  163.       for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
  164.         ws281x_setPixelColor(i+q, 0);        //turn every third pixel off
  165.       }
  166.     }
  167.   }
  168. }


  1. const char s[5];
  2. int8_t i;

  3. int main(void)
  4. {
  5. //  usart1_init(115200);
  6.   delay_init();

  7.   ws281x_init();

  8.   while(1)
  9.   {
  10.      // Some example procedures showing how to display to the pixels:
  11.   ws281x_colorWipe(ws281x_color(255, 0, 0), 50); // Red
  12.   ws281x_colorWipe(ws281x_color(0, 255, 0), 50); // Green
  13.   ws281x_colorWipe(ws281x_color(0, 0, 255), 50); // Blue
  14. //colorWipe(strip.Color(0, 0, 0, 255), 50); // White RGBW
  15.   // Send a theater pixel chase in...
  16.   ws281x_theaterChase(ws281x_color(127, 127, 127), 50); // White
  17.   ws281x_theaterChase(ws281x_color(127, 0, 0), 50); // Red
  18.   ws281x_theaterChase(ws281x_color(0, 0, 127), 50); // Blue

  19.   //ws281x_rainbow(20);
  20.   ws281x_rainbowCycle(20);
  21.   ws281x_theaterChaseRainbow(200);
  22.    
  23.     for(i = 0; i < PIXEL_NUM; ++i)
  24.   {
  25.     ws281x_setPixelColor(i, ws281x_color(0,250,0));
  26.     ws281x_show();
  27.     delay_ms(500);
  28.   }
  29.   }
  30. }
在ws2812.c移植了Adafruit_NeoPixel库的部分函数,用以实现炫酷的显示效果。
都是基础的SPI,不说废话,移植就能用。展示一下效果吧!




本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×

打赏榜单

21小跑堂 打赏了 20.00 元 2021-04-09
理由:恭喜通过原创文章审核!请多多加油哦!

评论

请问如何实现多条灯带的不同点亮效果编程?需要那些配置呀? 灯带的强度如何编程调整呀? 谢谢  发表于 2022-10-27 16:20
这个能支持2811吗  发表于 2021-4-29 12:46
有源程序吗?  发表于 2021-4-29 12:44
学习了。  发表于 2021-4-13 12:12
trucyw 发表于 2021-4-9 09:56 | 显示全部楼层
s
2.每个像素点的三基色可实现256级亮度显示,完成17777216种颜色的全真色彩显示,扫描频率不低于400Hz/s。
这个实际是16777216估计是手误(2^24)
 楼主| 呐咯密密 发表于 2021-4-9 10:02 | 显示全部楼层
s
trucyw 发表于 2021-4-9 09:56
2.每个像素点的三基色可实现256级亮度显示,完成17777216种颜色的全真色彩显示,扫描频率不低于400Hz/s。
...

哈哈,还真的是,感谢提醒,我这就修改一下,大佬心细啊。非常感谢
hugewinner 发表于 2021-4-9 17:58 | 显示全部楼层
s
赞一个!鼓掌!
lcfmax 发表于 2021-4-13 11:34 | 显示全部楼层
s
设计的蛮巧妙的,学些了
lanl 发表于 2021-4-27 09:56 | 显示全部楼层
s
炫酷,学习学习
TT1000 发表于 2021-4-30 09:15 | 显示全部楼层
s
好东西,学习学习
joyall 发表于 2021-5-9 09:40 | 显示全部楼层
s
楼主厉害,驱动这种芯片我见过用IO模似的,用PWM口和SPI口 三种方式, IO模似占用CPU资源太多,但消耗的RAM少, PWM和SPI口的方案设计巧妙,占用CPU资源少,但消耗的RAM空间太大,是IO口模似方案的8倍RAM消耗,在RAM足的芯片上可以使用。
林哈哈 发表于 2023-1-7 09:51 | 显示全部楼层
s
楼主我用这个方法可以控制灯带,但是好像会影响串口,我关了灯带后可以正常和涂鸦通讯,开了灯带,就有问题了
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:苏州澜宭自动化科技嵌入式工程师
简介:本人从事磁编码器研发工作,负责开发2500线增量式磁编码器以及17位、23位绝对值式磁编码器,拥有多年嵌入式开发经验,精通STM32、GD32、N32等多种品牌单片机,熟练使用单片机各种外设。

567

主题

4081

帖子

56

粉丝
快速回复 在线客服 返回列表 返回顶部