wuguangtao 发表于 2020-11-25 20:04

【MSP430F5529测评】6. DMA & SPI 驱动WS2812B 彩色点阵屏

<div class='showpostmsg'> 本帖最后由 wuguangtao 于 2020-11-27 21:41 编辑

# MSP430F5529 驱动 WS2812B LED

上次用了MAX7219驱动了单色的LED点阵屏,这回换个更炫酷的,WS2812B LED点阵屏,全彩LED,单线控制,5V电源供电。开始吧。

## WS2812B

下面这是我淘来的WS2812B 彩灯屏,8*8共64个全彩LED。



WS2812B是应用非常广泛的LED灯,可用作彩灯、彩带、彩球、彩屏等等。每个LED含4个IO口,LED之间可以级联,通过DIN和DOUT相连形成彩带,点阵屏实际也是彩带的变种,呈S型分布。



| NO.| Symbol | Function description       |
| ---- | ------ | -------------------------- |
| 1    | VDD    | Power supply LED         |
| 2    | DOUT   | Control data signal output |
| 3    | VSS    | Ground                     |
| 4    | DIN    | Control data signal input|

### WS2812B时序

虽然只有一根控制线,但是却可以控制成百上千的LED,所以其时序要求比较严格。



如上图所示,每个比特位由一段高低电平决定, 差不多1.25us代表一个0或1. 精度达us级。

* 0 - T0H (0.4us) - T0L(0.85us)
* 1 - T1H(0.85us) - T1L(0.4us)

每个LED的颜色由R、G、B 3个字节组成,也就是24bit,对于一个灯,只需发送3个字节然后延迟50us以上即可点亮。如果是多个LED,比如64个,那么需要连续发送64*3个字节的数据,然后延迟50us以上。



**需要注意的是,RGB的发送顺序是GRB,G部分开头,高位在先。**



介绍完了WS2812B灯,接下来看看如果使用MSP430F5529LP来驱动它,并实现些好玩的灯光效果。


## UCS

首先考虑的是时序问题,需要us级,所以要配置高频时钟。正好这里详细讲讲5529的时钟配置。



MSP430F5529主要包含上表中前3个时钟,主系统时钟MCLK通常用于CPU clk,在低功耗模式下禁用。SMCLK为子系统时钟,用于驱动高速外设,在LPM0低功耗模式下启用,但在LPM3,LPM4,LPM5模式下禁用。ACLK为辅助时钟,驱动低速外设,LPM3模式下可用,LPM4,LPM5模式禁用。

这几个时钟都可以通过以下6个时钟源产生。

| Source | Description                                                |
| ------ | ------------------------------------------------------------ |
| DCO    | digitally controlled oscillator                              |
| FLL    | frequency-locked loop                                        |
| REFO   | This is a modestly precise low-power on-chip oscillator thatdoes not require a crystal. Itoperates at 32 kHz. |
| LFXT1| This is a crystal oscillator. It is very precise and lower powerthan the REFO, but it requires a crystal. It, too, operates at 32 kHz. |
| VLO    | This oscillator is not very precise but does not require acrystal and has the lowest power of the three. It usually operates somewherebetween 12 kHz and 20 kHz. |
| XT2    | USBCLK 4MHz                                                |

VLO为内部精度不高的very low oscillator,频率只有12kHz。REFO为内部中等精度的32KHz时钟源,通常作为FLL的参考时钟。而FLL为锁频环,配合DCO可以进行倍频和分频,从而输出高精度时钟频率。

最后XT1和XT2外部晶振,在MSP430F5529LP中,XT1为32768Hz,XT2为4MHz。它们的管脚定义如下图所示:





默认情况下,XT1、XT2是不启用的,需要通过设置对应IO,选择复用功能才可以。



下面是官方文档提供的时钟图,可以看出,MCLK/SMCLK/ACLK都可以通过XT1CLK,VLOCLK,REFOCLK, DCOCLK, DCOCLKDIV, 和 XT2CLK 共6种方式产生。



### 选择时钟

针对此次实验,需要通过高速SPI通信,我们可以选择使用SMCLK,其时钟源可以是REFO,也可以是XT1/XT2结合FLL实现。

根据WS2812B的时序要求,需要产生0.65us ~ 1.85us 的切换频率,如果使用16MHz SMCLK,设置波特率系数为3,每次SPI传输对应1个字节需要大概3/16M * 8 约等于1.5us, 满足需求。然后每次传输的字节虽然包含8位,但只用来代表1个bit,`0b11110000`代表1,`0b11000000`代表0,这样完美解决时钟问题。

下面是根据需求使用REFO作为参考时钟,通过DCO和FLL分频得到16M 的MCLK,SMCLK。

```c
void init_ucs()
{
    UCSCTL3 |= SELREF_2;                      // Set DCO FLL reference = REFO

    __bis_SR_register(SCG0);                  // Disable the FLL control loop
    UCSCTL0 = 0x0000;                         // Set lowest possible DCOx, MODx
    UCSCTL1 = DCORSEL_5;                      // Select DCO range 24MHz operation
    UCSCTL2 = FLLD_1 + 499;                   // Set DCO Multiplier for 12MHz
                                              // (N + 1) * FLLRef = Fdco
                                              // (499 + 1) * 32768 = 16MHz
                                              // Set FLL Div = fDCOCLK/2
    __bic_SR_register(SCG0);                  // Enable the FLL control loop

    // Worst-case settling time for the DCO when the DCO range bits have been
    // changed is n x 32 x 32 x f_MCLK / f_FLL_reference. See UCS chapter in 5xx
    // UG for optimization.
    // 32 x 32 x 16 MHz / 32,768 Hz = 500000 = MCLK cycles for DCO to settle
    __delay_cycles(500000);
}
```




## SPI

根据上面分析,SPI的波特率系数为3,使用SMCLK,3-pin master模式,实际只用了一个IO,就是`P3.0`

```c
void init_spi()
{
    P3SEL |= BIT0;                               // P3.0 SPI MOSI option select

    UCB0CTL1 |= UCSWRST;                         // **Put state machine in reset**
    UCB0CTL0 |= UCMST + UCCKPH + UCMSB + UCSYNC; // 3-pin, 8-bit SPI master, MSB
    UCB0CTL1 |= UCSSEL_2; // SMCLK source (16 MHz)

    UCB0BR0 = 0x03;       // 16 MHz / 3 = .1875 us per bit
    UCB0BR1 = 0;          //
    UCB0CTL1 &= ~UCSWRST; // Initialize USCI state machine
}
```

### 驱动WS2812B LED

SPI配置好了,要点亮LED就简单了。使用结构体保存单个LED信息,按G\R\B顺序,初始化为全0,然后根据LED个数逐个点亮,对于每个LED,循环发送3组RGB值,点亮所有LED的`UCB0TXBUF`次数为`NUM_LEDS * 3 * 8`

```C
#define NUM_LEDS      (64)    // NUMBER OF LEDS IN YOUR MATRIX
#define MATRIX_WIDTH    (8)   // Width of led matrix
#define MATRIX_HEIGHT   (8)   // HEIGHT of led matrix

// Transmit codes
#define HIGH_CODE   (0xF0)      // b11110000
#define LOW_CODE    (0xC0)      // b11000000

// WS2812 takes GRB format
typedef struct {
    uint8_t green;
    uint8_t red;
    uint8_t blue;
} LED;

LED leds = { { 0, 0, 0 } };

// Send colors to the matrix and show them. Disables interrupts while processing.
void showMatrix() {
    __bic_SR_register(GIE);// disable interrupts

    // send RGB color for every LED
    unsigned int i, j;
    for (i = 0; i < NUM_LEDS; i++) {
      uint8_t *rgb = (uint8_t *)&leds; // get GRB color for this LED

      // send green, then red, then blue
      for (j = 0; j < 3; j++) {
            uint8_t mask = 0x80;    // b1000000

            // check each of the 8 bits
            while (mask != 0) {
                while (!(UCB0IFG & UCTXIFG)); // wait to transmit
                if (rgb & mask) {      // most significant bit first
                  UCB0TXBUF = HIGH_CODE;// send 1
                } else {
                  UCB0TXBUF = LOW_CODE;   // send 0
                }

                mask >>= 1;// check next bit
            }
      }
    }

    // send RES code for at least 50 us (800 cycles at 16 MHz)
    _delay_cycles(800);

    __bis_SR_register(GIE);    // enable interrupts
}

```

这样LED最简单的驱动就完成了。

## PMM

还要说明一点的是,对于LED数量较多的情况,MSP430默认电压可能驱动力不足,需要调整CPU核心电压,这里就要用到PMM(**Power Management Module** )

```c
void SetVcoreUp (unsigned int level)
{
    // Open PMM registers for write
    PMMCTL0_H = PMMPW_H;
    // Set SVS/SVM high side new level
    SVSMHCTL = SVSHE + SVSHRVL0 * level + SVMHE + SVSMHRRL0 * level;
    // Set SVM low side to new level
    SVSMLCTL = SVSLE + SVMLE + SVSMLRRL0 * level;
    // Wait till SVM is settled
    while ((PMMIFG & SVSMLDLYIFG) == 0);
    // Clear already set flags
    PMMIFG &= ~(SVMLVLRIFG + SVMLIFG);
    // Set VCore to new level
    PMMCTL0_L = PMMCOREV0 * level;
    // Wait till new level reached
    if ((PMMIFG & SVMLIFG))
    while ((PMMIFG & SVMLVLRIFG) == 0);
    // Set SVS/SVM low side to new level
    SVSMLCTL = SVSLE + SVSLRVL0 * level + SVMLE + SVSMLRRL0 * level;
    // Lock PMM registers for write access
    PMMCTL0_H = 0x00;
}

void setup(void)
{
    // Increase Vcore setting to level3 to support fsystem=25MHz
    // NOTE: Change core voltage one level at a time..
    SetVcoreUp (0x01);
    SetVcoreUp (0x02);
    SetVcoreUp (0x03);
}
```

具体参数看手册好了,注意一点就是,电压等级需要逐级改变,不能跳级哦。



## DMA

最后关于MSP430F5529LP的一点是,如果需要循环发送控制信号给LED屏,可以考虑使用DMA,解放CPU,降低功耗,又灵活。后面会单独给出一个详细例程。

MSP430F5529共有DMA0,DMA1,DMA2 3个DMA控制器,每个控制器含32路触发通道。下图中DMAxSA为源地址,DMAxDA为目的地址。



DMA触发通道的对应关系可以在datasheet中找到,这里截图过来。



后续例程将选用19路通道,也就是UCB0TXIFG,对应SPI 发送中断。也就是每发送一个字节就触发一次。具体见后面例程。

## 代码 & 测试

下面我们实现WS2812B的驱动代码,基本初始化部分在上面已经完成了。但是如果要实现扫屏、点亮指定区域等功能,就需要进一步扩展了。

### 点阵坐标编码

前面说过,点阵屏上LED之间是S型走线,但是对于方阵而言,我们更喜欢按行、列去控制每个灯,而不是按彩灯的蛇形走位。所以就涉及到一个**坐标编码**,简言之,就是根据给定的行列号去求得该LED在彩带中的具体编号(0~63). 这里我画了个图来形象的表示这个变换规则。



其实很简单,就是根据奇偶不同计算id。

```c
index=(row%2==0)?(row*8+col):(row*8+(7-col))
```

这样就把行列坐标(0, 0)-(7,7) 映射到了(0,63)。因为毕竟简单,可以使用宏定义。

```c
/* map coordinate to index of led matrix
* coordinate: (0, 0) - (7, 7)
* index: (0) - (63)
*/
#define INDEX(row, col) ((row)%2==0?((row)*8+(col)):((row)*8+(7-(col))))
```

### 扫屏

扫屏就是将LED遍历一遍,每个LED既可以使用相同颜色,也可以使用不同颜色,扫屏过程中使用延时就可以产生动画效果。

```c
// Fill the matrix with a solid color. This will update the matrix.
void fillMatrix(uint8_t r, uint8_t g, uint8_t b) {
    int i;
    for (i = 0; i < NUM_LEDS; i++) {
      setLEDColor(i, r, g, b);// set all LEDs to specified color
    }
    showMatrix();// refresh matrix
}
void fillMatrixByRGB(uint32_t RGB) {
    int i;
    for (i = 0; i < NUM_LEDS; i++) {
      setLEDColorByRGB(i, RGB);// set all LEDs to specified color
    }
    showMatrix();// refresh matrix
}

// Clear the color of all LEDs (make them black/off)
void clearMatrix() {
    fillMatrix(0x00, 0x00, 0x00);// black
}

// fill specific number of led matrix
void gradualFill(uint16_t n, uint8_t r, uint8_t g, uint8_t b){
    int i;
    for (i = 0; i < n; i++){      // n is number of LEDs
      setLEDColor(i, r, g, b);
      showMatrix();
      _delay_cycles(500000);       // lazy delay
    }
}
void gradualFillByRGB(uint16_t n, uint32_t RGB){
    int i;
    for (i = 0; i < n; i++){      // n is number of LEDs
      setLEDColorByRGB(i, RGB);
      showMatrix();
      _delay_cycles(500000);       // lazy delay
    }
}
```

`fillMatrix`, `fillMatrixByRGB`都是显示相同颜色,只不过参数不一样,类似函数重载。`clearMatrix`用于清空,也就是熄灭所有的灯。`gradualFill`, `gradualFillByRGB`可以产生动画,形成彩带循环更新的样式。

使用以下代码就可以在灰色背景下更新不同的颜色,然后熄灭。

```c
// Colors
#define COLOR_RED       (0xFF0000)
#define COLOR_GREEN   (0x00FF00)
#define COLOR_BLUE      (0x0000FF)
#define COLOR_WHITE   (0xFFFFFF)
#define COLOR_BLACK   (0x000000)
#define COLOR_CLEAR   (0x000000)
#define COLOR_YELLOW    (0xFFFF00)
#define COLOR_CYAN      (0x00FFFF)
#define COLOR_MAGENTA   (0xFF00FF)

fillMatrixByRGB(0x333333);
gradualFillByRGB(8, COLOR_RED);
gradualFillByRGB(16, COLOR_GREEN);
gradualFillByRGB(24, COLOR_BLUE);
gradualFillByRGB(32, COLOR_YELLOW);
gradualFillByRGB(40, COLOR_CYAN);
gradualFillByRGB(48, COLOR_MAGENTA);
gradualFillByRGB(56, COLOR_WHITE);
gradualFillByRGB(64, COLOR_CLEAR);
```

展示如下:



### 矩阵显示

接下来看看坐标编码的应用,指定矩形区域点亮LED. 这里就用到了上面定义的坐标变换宏`INDEX`

```c
void setRectLEDColorByRGB(uint8_t row, uint8_t col, uint8_t width, uint8_t height, uint32_t RGB) {
    if (width < 1 || height < 1)
      return;

    uint8_t row_end = row + width;
    uint8_t col_end = col + height;
    if (row_end > MATRIX_WIDTH)
      row_end = MATRIX_WIDTH;
    if (col_end > MATRIX_HEIGHT)
      col_end = MATRIX_HEIGHT;

    uint8_t i, j;
    uint8_t id;
    for (i = row; i < row_end; i++)
      for (j = col; j < col_end; j++) {
            id = INDEX(i, j);
            setLEDColorByRGB(id, RGB);
      }
}
```

展示如下:



### UART控制

对了,控制LED怎么少的了串口,蓝牙也是ok的,通过串口发送数据,然后显示不同的颜色和区域也是很有趣的。下面的代码是在启用uart和以上描述的代码基础上编写的。setup函数用于初始化硬件,main函数在初始化后进行一轮扫屏,然后等待串口输入,根据串口信息点亮指定区域。

```c
void init_uart()
{
    // USCI_A1 UART
    P4SEL |= BIT4+BIT5;                     // P4.4,5 = USCI_A1 TXD/RXD
    UCA1CTL1 |= UCSWRST;                      // **Put state machine in reset**
    UCA1CTL1 |= UCSSEL_2;                     // SMCLK
    UCA1BR0 = 145;                            // 16MHz 115200 (see User's Guide)
    UCA1BR1 = 0;                              // 16MHz 115200
    UCA1MCTL |= UCBRS_1 + UCBRF_0;            // Modulation UCBRSx=1, UCBRFx=0
    UCA1CTL1 &= ~UCSWRST;                     // **Initialize USCI state machine**
    UCA1IE |= UCRXIE;                         // Enable USCI_A1 RX interrupt
}

void setup()
{
    WDTCTL = WDTPW | WDTHOLD;   // stop watchdog timer
    P1DIR |= BIT0;

    // Increase Vcore setting to level3 to support fsystem=25MHz
    // NOTE: Change core voltage one level at a time..
    SetVcoreUp (0x01);
    SetVcoreUp (0x02);
    SetVcoreUp (0x03);

    init_ucs();
    init_uart();
    init_spi();
    clearMatrix();
    // fillMatrix(0xFF, 0x00, 0xFF);
}


volatile uint8_t chr = 0xFF;               // set a invalid value
int main(void)
{
    setup();
    __bis_SR_register(GIE);                   // interrupts enabled

    // show time
    fillMatrixByRGB(0x333333);
    gradualFillByRGB(8, COLOR_RED);
    gradualFillByRGB(16, COLOR_GREEN);
    gradualFillByRGB(24, COLOR_BLUE);
    gradualFillByRGB(32, COLOR_YELLOW);
    gradualFillByRGB(40, COLOR_CYAN);
    gradualFillByRGB(48, COLOR_MAGENTA);
    gradualFillByRGB(56, COLOR_WHITE);
    gradualFillByRGB(64, COLOR_CLEAR);

    uint8_t r=0x60, g=0x40, b=0x20;
        uint32_t RGB = 0xFFFFFF;
        while(1){
          r+=33;
          g+=66;
          b+=77;
      if (r >= 0xFF)
            r = g + 0x03;
      if (g >= 0xFF)
            g = b + 0x05;
      if (b >= 0xFF)
            b = 0x06;

      if (chr < 0xFF) {
            clearCache();
            RGB = (uint32_t)((r << 16) + (g << 8) + b);
            setRectLEDColorByRGB(r % 8, g % 8, 3, 3, RGB);
            setLEDColor(chr % NUM_LEDS, g, b, r);
            showMatrix();// refresh strip
            chr = 0xFF;
//            gradualFill(8, r, g, b);
      }
      __delay_cycles(5000000);
          P1OUT ^= 1;
        }
}


// Echo back RXed character, confirm TX buffer is ready first
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector=USCI_A1_VECTOR
__interrupt void USCI_A1_ISR(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(USCI_A1_VECTOR))) USCI_A1_ISR (void)
#else
#error Compiler not supported!
#endif
{
    switch(__even_in_range(UCA1IV,4))
    {
      case 0:break;                           // Vector 0 - no interrupt
      case 2:                                 // Vector 2 - RXIFG
            while (!(UCA1IFG&UCTXIFG));         // USCI_A1 TX buffer ready?
            chr = UCA1RXBUF;
            UCA1TXBUF = chr;                      // TX -> RXed character
            break;
      case 4:break;                           // Vector 4 - TXIFG
      default: break;
    }
}



```



### DMA 定时更新LED

好啦,最后来看下使用DMA该如何点亮,具体原理已经讲过了,下面是**完整代码**。

```c
#include <msp430.h>
#include <stdint.h>
#include <stdbool.h>

#define NUM_LEDS                64                                  // 64 LEDs matrix, configure this here
#define WS_BIT_0                0xc0                              // Bit 0
#define WS_BIT_1                0xf0                              // Bit 1
#define SIZE_OF_LED_ARRAY       (NUM_LEDS * 3 * 8)                  // DMA buffer, 24 bytes per LED


uint8_t led_raw; // DMA space, incl. 50usec break time

typedef struct {
    uint8_t r;
    uint8_t g;
    uint8_t b;
} led_color_t;      // led color struct


void init_ucs(void) {
    UCSCTL3 |= SELREF_2;                      // Set DCO FLL reference = REFO

    __bis_SR_register(SCG0);                  // Disable the FLL control loop
    UCSCTL0 = 0x0000;                         // Set lowest possible DCOx, MODx
    UCSCTL1 = DCORSEL_5;                      // Select DCO range 24MHz operation
    UCSCTL2 = FLLD_1 + 499;                   // Set DCO Multiplier for 12MHz
                                              // (N + 1) * FLLRef = Fdco
                                              // (499 + 1) * 32768 = 16MHz
                                              // Set FLL Div = fDCOCLK/2
    __bic_SR_register(SCG0);                  // Enable the FLL control loop

    // Worst-case settling time for the DCO when the DCO range bits have been
    // changed is n x 32 x 32 x f_MCLK / f_FLL_reference. See UCS chapter in 5xx
    // UG for optimization.
    // 32 x 32 x 16 MHz / 32,768 Hz = 500000 = MCLK cycles for DCO to settle
    __delay_cycles(500000);
}


/* timer_a0_init
*
* initalizes timerA0 to 10msec intervals, CCRO, running from REFO (32kHz)
*
*/
void timer_a0_init(void) {
    TA0CCTL0 = CCIE;
    TA0CCR0 = 160;
    // ACLK, continous up, clear TAR
    TA0CTL = TASSEL_1 + MC__CONTINOUS + TACLR;
}

/*
* init_spi
* configures USCIA0 to SPI master ,8MHz clock rate, 8Bit, running from SMCLK
*
*/
void init_spi(void) {
    P3SEL |= BIT0;                               // P3.0 SPI MOSI option select
    P1OUT &= ~BIT0;

    UCB0CTL1 |= UCSWRST;                         // **Put state machine in reset**
    UCB0CTL0 |= UCMST + UCCKPH + UCMSB + UCSYNC; // 3-pin, 8-bit SPI master, MSB
    UCB0CTL1 |= UCSSEL_2; // SMCLK source (16 MHz)

    UCB0BR0 = 0x03;       // 16 MHz / 3 = .1875 us per bit
    UCB0BR1 = 0;          //
    UCB0CTL1 &= ~UCSWRST; // Initialize USCI state machine
}


void init_dma(void) {
    // channel 19 = UCBTXIFG as trigger for DMA
    DMACTL0 = 0;
    DMACTL1 = DMA2TSEL_19;
    DMACTL2 = 0;
    DMACTL3 = 0;
    // DMA2 configuration
    // bytewise access for source and destination, increment source, single transfer
    DMA2CTL = DMADT_0 | DMASRCINCR_3 | DMASRCBYTE | DMADSTBYTE | DMAIE;
    DMA2SA = (uint16_t)(&led_raw); // source
    DMA2DA = (uint16_t)&UCB0TXBUF;    // destination
    DMA2SZ = SIZE_OF_LED_ARRAY-1;   // size in bytes
}

/* SetVCoreUp
* TI PMM driverlib Code to set Vcore to a given level
*
* this is complicated code, but apparently is needed to come around
* erratum FLASH37
* removed some original comments to compress code
*
*/
uint16_t SetVCoreUp(uint8_t level)
{
    // Open PMM registers for write
    PMMCTL0_H = PMMPW_H;
    // Set SVS/SVM high side new level
    SVSMHCTL = SVSHE + SVSHRVL0 * level + SVMHE + SVSMHRRL0 * level;
    // Set SVM low side to new level
    SVSMLCTL = SVSLE + SVMLE + SVSMLRRL0 * level;
    // Wait till SVM is settled
    while ((PMMIFG & SVSMLDLYIFG) == 0);
    // Clear already set flags
    PMMIFG &= ~(SVMLVLRIFG + SVMLIFG);
    // Set VCore to new level
    PMMCTL0_L = PMMCOREV0 * level;
    // Wait till new level reached
    if ((PMMIFG & SVMLIFG))
    while ((PMMIFG & SVMLVLRIFG) == 0);
    // Set SVS/SVM low side to new level
    SVSMLCTL = SVSLE + SVSLRVL0 * level + SVMLE + SVSMLRRL0 * level;
    // Lock PMM registers for write access
    PMMCTL0_H = 0x00;
}


/* _set_single_color
*
* writes one brightness byte at the given location in the led array
*
* as the DMA uses the led array directly to output the bits over SPI
* it must be deflated in advance in memory. One bit of WS2812 information
* is one byte to transfer over SPI
* -> bit1 = WS_BIT_1 and bit0 = WS_BIT_0
*
* disadvantage is that it uses 8x more RAM...
*
* this function is normally called by ::set_LED
*
* @param led_color_ptr    pointer to location within the ::led_raw array
* @param led_color          single byte, represents the brightness
*/

void _set_single_color(uint8_t* led_color_ptr, uint8_t brightness)   {
    uint8_t i;
    uint8_t mask = 0x80;
    for (i=0; i<8; i++) {
      if (brightness & mask) {
            *led_color_ptr = WS_BIT_1;
      } else {
            *led_color_ptr = WS_BIT_0;
      }
      mask = mask >> 1;
      led_color_ptr++;
    }
}

/* set_LED
*
* writes the color information (RGB) of the given LED number
* within the ::led_raw array
*
* @param led_nr             position of LED within the matrix
* @param led_color          RGB information
*/
void set_LED(uint8_t led_nr, led_color_t* led_color) {
    uint8_t* led_ptr = led_raw + led_nr * 24;
    _set_single_color(led_ptr, led_color->g);
    _set_single_color(led_ptr+8, led_color->r);
    _set_single_color(led_ptr+16, led_color->b);
}

int main(void)
{
    uint16_t time = 0;
    led_color_t col;

    WDTCTL = WDTPW + WDTHOLD;

    // set vcore to highest value
    SetVCoreUp(0x01);
    SetVCoreUp(0x02);
    SetVCoreUp(0x03);

    // hw init
    init_ucs();
    init_spi();
    timer_a0_init();
    init_dma();

    // internal LED and debug pins
    P1DIR |= BIT0;
    __bis_SR_register(GIE);    // enable interrupts

    // all set, now let the DMA run
    DMA2CTL |= DMAEN ;
    UCB0TXBUF = led_raw;
    while (1)
    {
      time++;
      P1OUT ^= BIT0;
      LPM0;
      // DMA transfer has finished, you may calculate LED updates now
      // timer A is triggered app. every 10msec, so you have around 6msec time
      // to update the matrix

      // this is a color fading thing
      uint8_t i;
      for (i=0; i<NUM_LEDS; i++){
//            col.g = (time % 512) + i*4;
//            col.r = (time % 256) + i;
//            col.b = (time / 1024) + (NUM_LEDS - i);
            col.g = (time % 512) + i*4;
            col.r = (time % 256);
            col.b = (time / 1024)+i;
            set_LED(i,&col);
      }
    }
}

// TimerA0 Interrupt Vector (TAIV) handler, re-enables the DMA
// Echo back RXed character, confirm TX buffer is ready first
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector=TIMER0_A0_VECTOR
__interrupt void TIMER0_A0_ISR(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(TIMER0_A0_VECTOR))) TIMER0_A0_ISR (void)
#else
#error Compiler not supported!
#endif
{
    TA0CCR0 += 160;
    DMA2CTL |= DMAEN | DMAIE ;
    DMA2SA = (uint16_t)(&led_raw); // source
    UCB0TXBUF = led_raw;
}

// the DMA interrupt of channel2 just exits from low power mode
// so that a foreground application can do its work
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector=DMA_VECTOR
__interrupt void DMA_ISR(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(DMA_VECTOR))) DMA_ISR (void)
#else
#error Compiler not supported!
#endif
{
    switch(DMAIV) {
      case 0: break;
      case 2: break;
      case 4: break;                        // DMA1IFG = DMA Channel 1
      case 6:
            // DMA transfer to LEDs finished, exit low power
            LPM0_EXIT;
            break;
      case 8: break;                        // DMA3IFG = DMA Channel 3
      case 10: break;                         // DMA4IFG = DMA Channel 4
      case 12: break;                         // DMA5IFG = DMA Channel 5
      case 14: break;                         // DMA6IFG = DMA Channel 6
      case 16: break;                         // DMA7IFG = DMA Channel 7
      default: break;
    }
}


```

展示如下:


## 小结

这次的实验学习和使用到的功能还不少,包含UCS,DMA,SPI,UART, PMM等模块,还学会了WS2812B的SPI控制方法。彩屏虽小,但是颜色可以随意变换,还可以设计很多好玩的动画,哈哈。篇幅和时间限制就先到这啦。欢迎交流讨论。

## 参考资料

- https://github.com/niwhsa9/WS2182B_Driver_For_MSP430F5529
- https://github.com/mjmeli/MSP430-NeoPixel-WS2812-Library</div><script>                                        var loginstr = '<div class="locked">查看本帖全部内容,请<a href="javascript:;"   style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
                                       
                                        if(parseInt(discuz_uid)==0){
                                               
                                        }                </script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script>

alan000345 发表于 2020-12-10 21:44

<p><img height="50" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/wanwan33.gif" width="58" />真心好资料,硬件和软件都讲透彻了,必须顶一下。谢谢分享。</p>

freebsder 发表于 2020-12-11 19:05

<p>写的好详细。eagle pcb?</p>
页: [1]
查看完整版本: 【MSP430F5529测评】6. DMA & SPI 驱动WS2812B 彩色点阵屏