1. 程式人生 > >WS2812燈珠(二)-- STM32 SPI+DMA方式驅動

WS2812燈珠(二)-- STM32 SPI+DMA方式驅動

    通過硬體SPI的可以很巧妙的模擬出WS2812的通訊時序,用spi的8位資料模擬ws281x的一位資料。

要將系統時鐘設定為56M,SPI分頻數設定為8,則SPI的通訊頻率為7M,1s/7M≈143ns 即傳輸一位資料的時間約為143納秒(ns)
 3*143 = 429ns   5*143 = 715ns  符合WS281X晶片的通訊時序。
 11111000  high level  (十六進位制:0XF8)表示WS281X的1碼
 11100000  low level   (十六進位制:0XE0)表示WS281X的0碼

程式標頭檔案部分: 通過巨集的方式定義了燈珠個數和WS281X的0碼和1碼。

#ifndef __WS2812_H
#define __WS2812_H

#include "stm32f10x.h"

#define PIXEL_NUM 11

//硬體spi模擬ws2811時序(用spi的8位資料模擬ws281x的一位資料)
//要將系統時鐘設定為56M,分頻數設定為8,則SPI的通訊頻率為7M,傳輸一位資料的時間約為143納秒(ns)
//3*143 = 429ns   5*143 = 715ns  符合WS281X晶片的通訊時序。
//  _____   
// |     |___|   11111000  high level
//  ___         
// |   |_____|   11100000  low level

#define WS_HIGH 0XF8
#define WS_LOW  0XE0

void ws281x_init(void);
void ws281x_closeAll(void);
void ws281x_rainbowCycle(uint8_t wait);
uint32_t ws281x_color(uint8_t red, uint8_t green, uint8_t blue);
void ws281x_setPixelColor(uint16_t n ,uint32_t GRBcolor);
void ws281x_show(void);

void ws281x_theaterChase(uint32_t c, uint8_t wait);
void ws281x_colorWipe(uint32_t c, uint8_t wait);
void ws281x_rainbow(uint8_t wait);
void ws281x_theaterChaseRainbow(uint8_t wait);

#endif /* __WS2812_H */

程式的關鍵在硬體SPI和DMA初始化部分:在配置時用到了硬體SPI1的MOSI腳,驅動過程中並不需要接收資料,因而將SPI1方向設定為單線傳送即可,設定為主機模式,因為用SPI的8位資料來模擬WS281X的一位資料,故將SPI資料大小設為8位幀結構。對於CPOL和CPHA的設定,CPOL設定為低的情況下要將CPHA設定為第二個跳邊沿取樣(之前將CPOL設定為低,CPHA設為第一個跳邊沿取樣,通過示波器檢視波形與所需要的時序剛好相反)。DMA部分的設定注意將資料方向設為從記憶體到外設,將資料寬度設為8位即可。

void ws281x_init(void)
{
  GPIO_InitTypeDef GPIO_InitStructure;
  SPI_InitTypeDef  SPI_InitStructure;
  DMA_InitTypeDef DMA_InitStructure;

  RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //PORTA時鐘使能 
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); //SPI1時鐘使能 	
  RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);	//使能DMA傳輸


  /* PA7  SPI1_MOSI */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //PA7複用推輓輸出 SPI
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA

  SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;  //設定SPI單向或者雙向的資料模式:SPI設定為雙線雙向全雙工
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//設定SPI工作模式:設定為主SPI
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//設定SPI的資料大小:SPI傳送接收8位幀結構
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;		//串行同步時鐘的空閒狀態為低電平
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//串行同步時鐘的第2個跳變沿(上升或下降)資料被取樣
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		//NSS訊號由硬體(NSS管腳)還是軟體(使用SSI位)管理:內部NSS訊號有SSI位控制
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;		//定義波特率預分頻的值:波特率預分頻值為16
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//指定資料傳輸從MSB位還是LSB位開始:資料傳輸從MSB位開始
  SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值計算的多項式
  SPI_Init(SPI1, &SPI_InitStructure);  //根據SPI_InitStruct中指定的引數初始化外設SPIx暫存器
 
  SPI_Cmd(SPI1, ENABLE); //使能SPI外設
  SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE);
  
  DMA_DeInit(DMA1_Channel3);   //將DMA的通道1暫存器重設為預設值
  DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &(SPI1 -> DR); //cpar;  //DMA外設ADC基地址
  DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)pixelBuffer; //cmar;  //DMA記憶體基地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;  //資料傳輸方向,從記憶體讀取傳送到外設
  DMA_InitStructure.DMA_BufferSize = PIXEL_NUM * 24; //cndtr;  //DMA通道的DMA快取的大小
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;  //外設地址暫存器不變
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;  //記憶體地址暫存器遞增
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;  //資料寬度為8位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //資料寬度為8位
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;  //工作在正常快取模式
  DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x擁有中優先順序 
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;  //DMA通道x沒有設定為記憶體到記憶體傳輸
  DMA_Init(DMA1_Channel3, &DMA_InitStructure);  //根據DMA_InitStruct中指定的引數初始化DMA的通道USART1_Tx_DMA_Channel所標識的暫存器 
  
  ws281x_closeAll();  //關閉全部的燈
  delay_ms(100); //關閉全部的燈需要一定的時間  
}

配置完成之後便可以構思底層控制函數了,為了方便多個LED燈珠的可控制首先要定義一個緩衝區pixelBuffer[PIXEL_NUM][24]    通過設定顏色將資料填入緩衝區再通過更新函式將資料傳入到LED燈珠上。

//關閉所有燈珠
void ws281x_closeAll(void)
{
  uint16_t i;
  uint8_t j;
  
  for(i = 0; i < PIXEL_NUM; ++i)
  {
    for(j = 0; j < 24; ++j)
    {
      pixelBuffer[i][j] = WS_LOW;
    }
  }
  ws281x_show(); 
}
//將三原色單獨資料合併為24位資料
uint32_t ws281x_color(uint8_t red, uint8_t green, uint8_t blue)
{
  return green << 16 | red << 8 | blue;
}
//設定第n個燈珠的顏色
void ws281x_setPixelColor(uint16_t n ,uint32_t GRBcolor)
{
  uint8_t i;
  if(n < PIXEL_NUM)
  {
    for(i = 0; i < 24; ++i)
    {
      pixelBuffer[n][i] = (((GRBcolor << i) & 0X800000) ? WS_HIGH : WS_LOW);
    }
  }
}
//設定第n個燈珠的顏色
void ws281x_setPixelRGB(uint16_t n ,uint8_t red, uint8_t green, uint8_t blue)
{
  uint8_t i;
  
  if(n < PIXEL_NUM)
  {
    for(i = 0; i < 24; ++i)
    {
      pixelBuffer[n][i] = (((ws281x_color(red,green,blue) << i) & 0X800000) ? WS_HIGH : WS_LOW);
    }
  }
}
//更新顏色顯示(在設定顏色後將顏色資料存入快取只有執行該函式後才會進行顯示)
void ws281x_show(void)
{
   DMA_Cmd(DMA1_Channel3, DISABLE );  //關閉USART1 TX DMA1 所指示的通道 
   DMA_ClearFlag(DMA1_FLAG_TC3);    
   DMA_SetCurrDataCounter(DMA1_Channel3,24 * PIXEL_NUM );//DMA通道的DMA快取的大小
   DMA_Cmd(DMA1_Channel3, ENABLE);  //使能USART1 TX DMA1 所指示的通道 
}

有了上面的底層控制函式,便可以為燈珠顯示加特技了,在這裡移植了Adafruit_NeoPixel庫的部分函式,用以實現炫酷的顯示效果。

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t ws281x_wheel(uint8_t wheelPos) {
  wheelPos = 255 - wheelPos;
  if(wheelPos < 85) {
    return ws281x_color(255 - wheelPos * 3, 0, wheelPos * 3);
  }
  if(wheelPos < 170) {
    wheelPos -= 85;
    return ws281x_color(0, wheelPos * 3, 255 - wheelPos * 3);
  }
  wheelPos -= 170;
  return ws281x_color(wheelPos * 3, 255 - wheelPos * 3, 0);
}

// Fill the dots one after the other with a color
void ws281x_colorWipe(uint32_t c, uint8_t wait) {
  for(uint16_t i=0; i<PIXEL_NUM; i++) {
    ws281x_setPixelColor(i, c);
    ws281x_show();
    delay_ms(wait);
  }
}

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

  for(j=0; j<256; j++) {
    for(i=0; i<PIXEL_NUM; i++) {
      ws281x_setPixelColor(i, ws281x_wheel((i+j) & 255));
    }
    ws281x_show();
    delay_ms(wait);
  }
}

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

  for(j=0; j<256*5; j++) { // 5 cycles of all colors on wheel
    for(i=0; i< PIXEL_NUM; i++) {
      ws281x_setPixelColor(i,ws281x_wheel(((i * 256 / PIXEL_NUM) + j) & 255));
    }
    ws281x_show();
    delay_ms(wait);
  }
}

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

      delay_ms(wait);

      for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
        ws281x_setPixelColor(i+q, 0);        //turn every third pixel off
      }
    }
  }
}

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

      delay_ms(wait);

      for (uint16_t i=0; i < PIXEL_NUM; i=i+3) {
        ws281x_setPixelColor(i+q, 0);        //turn every third pixel off
      }
    }
  }
}

如果需要完整測試例程可以到如下連結下載:https://download.csdn.net/download/xiaoyuanwuhui/10749147