【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> <p><img height="50" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/wanwan33.gif" width="58" />真心好资料,硬件和软件都讲透彻了,必须顶一下。谢谢分享。</p>
<p>写的好详细。eagle pcb?</p>
页:
[1]