Skip to content

Instantly share code, notes, and snippets.

@RickKimball
Last active November 26, 2018 09:34
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RickKimball/9761b8f5a89d46a53939 to your computer and use it in GitHub Desktop.
Save RickKimball/9761b8f5a89d46a53939 to your computer and use it in GitHub Desktop.
ws281x driver using DMA and MSP430F5529 (tested on ws2811 and ws2812b aka neo pixels)

This code is used to drive a ws281x strip using SPI driven by DMA on an msp430f5529.

/*
* main.c - msp430f5529 ws281x driver
*
* Desc:
* Drives ws281x (ws2811/ws2812b/neopixels) leds using SPI driven
* by DMA. This code also provides an example of how to use inline
* asm with msp430-elf-gcc to create some utility functions.
*/
#include <msp430.h>
#include <msp430f5529.h>
#include <stdint.h>
#define F_MCLK 24000000UL
// defines for internal routines
void init_clocks(void);
void init_spi(void);
void setup(void);
void ledbits2pulse(const uint8_t * const src, uint8_t *dst);
void sendGRB(const uint8_t * const color_data, unsigned byte_cnt);
void setvcoreup(unsigned int level);
#define USE_DMA_SPI /* define to use DMA, undef to use polled SPI */
#define USE_ASM_VERSION /* define to use inline msp430 asm, undef to use C versions */
#define USE_32K_XTAL /* define to use the external 32.768k XTAL instead of the REF clock */
#if !defined(__GNUC__) && defined(USE_ASM_VERSION)
#error msp430-elf-gcc is required to use the inline assembler version!
#endif
#define ANIMATION_FRAMES 4 /* how many frames are we animating */
#define LED_CNT 4 /* how many leds do you have */
#define BYTE_PER_LED 3 /* green 8 bits, red 8 bits, blue 8 bits 0-255 where 0 is dark and 255 bright*/
#define RGB(RED,GREEN,BLUE) (GREEN),(RED),(BLUE)
/*
* rotate the led pattern exercising all colors on all leds
*/
static const uint8_t leds[BYTE_PER_LED * LED_CNT * ANIMATION_FRAMES] = {
// 1st frame
RGB(0x0f, 0x00, 0x00), // red First LED
RGB(0x00, 0x0f, 0x00), // green Second LED
RGB(0x00, 0x00, 0x0f), // blue Third LED
RGB(0x2f, 0x2f, 0x2f), // white Fourth LED
// 2nd frame
RGB(0x0f, 0x0f, 0x0f), // w
RGB(0x0f, 0x00, 0x00), // r
RGB(0x00, 0x0f, 0x00), // g
RGB(0x00, 0x00, 0x0f), // b
// 3rd frame
RGB(0x00, 0x00, 0x0f), // b
RGB(0x0f, 0x0f, 0x0f), // w
RGB(0x0f, 0x00, 0x00), // r
RGB(0x00, 0x0f, 0x00), // g
// 4th frame
RGB(0x00, 0x0f, 0x00), // g
RGB(0x00, 0x00, 0x0f), // b
RGB(0x0f, 0x0f, 0x0f), // w
RGB(0x0f, 0x00, 0x00), // r
};
uint8_t pulse_data[BYTE_PER_LED * LED_CNT * 8]; // buffer to hold spi pulse bits
void init_clocks(void)
{
P1DIR |= BIT0; // ACLK set out to pins
P1SEL |= BIT0;
P2DIR |= BIT2; // SMCLK set out to pins
P2SEL |= BIT2;
P7DIR |= BIT7; // MCLK set out to pins
P7SEL |= BIT7;
#if defined(USE_32K_XTAL)
P5SEL |= BIT4|BIT5; // Select XT1
UCSCTL6 &= ~(XT1OFF|XCAP_3); // XT1 On, clear XCAP settings
UCSCTL3 = SELREF__XT1CLK; // FLL Reference Clock = XT1, default not actually needed
#else
UCSCTL3 |= SELREF__REFOCLK; // Set DCO FLL reference = REFO
UCSCTL4 |= SELA__REFOCLK; // Set ACLK = REFO
#endif
__bis_SR_register(SCG0); // Disable the FLL control loop
UCSCTL0 = 0x0000; // Set lowest possible DCOx, MODx
UCSCTL1 = DCORSEL_6; // Select DCO range 24MHz operation
#if F_MCLK == 6400000
UCSCTL2 = FLLD_1 + 194 + 1; // Set DCO Multiplier for ~12MHz
#define DCO_DELAY 200000
#elif F_MCLK == 12000000
UCSCTL2 = FLLD_1 + 365 + 1; // Set DCO Multiplier for ~12MHz
#define DCO_DELAY 375000
#elif F_MCLK == 16000000
UCSCTL2 = FLLD_1 + 488 + 1; // Set DCO Multiplier for ~16MHz
#define DCO_DELAY 500000
#elif F_MCLK == 17120000
UCSCTL2 = FLLD_1 + 522 + 1; // Set DCO Multiplier for ~17.12MHz
#define DCO_DELAY 535000
#elif F_MCLK == 19200000
UCSCTL2 = FLLD_1 + 585 + 1; // Set DCO Multiplier for ~17.12MHz
#define DCO_DELAY 600000
#elif F_MCLK == 24000000
UCSCTL2 = FLLD_1 + 730 + 1; // Set DCO Multiplier for ~24MHz
#define DCO_DELAY 750000
#else
#error Set a valid F_MCLK value
#endif
// (N + 1) * FLLRef = Fdco
// (522 + 1) * 32768 = ~17.12MHz
// 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 17.12 MHz / 32,768 Hz = 535000 = MCLK cycles for DCO to settle
// delay above based used to compute alogrithm
__delay_cycles(DCO_DELAY);
// Loop until XT1,XT2 & DCO fault flag is cleared
do {
UCSCTL7 &= ~(XT2OFFG + XT1LFOFFG + DCOFFG); // Clear XT2,XT1,DCO fault flags
SFRIFG1 &= ~OFIFG; // Clear fault flags
} while (SFRIFG1 & OFIFG); // Test oscillator fault flag
}
void init_spi(void)
{
/* Code is using UCB0 */
/* configure UCB0SIMO on P3.0 */
P3SEL |= BIT0; // Set P3.0 to be USC0MOSI (Master Out/Slave In)
UCB0CTL0 |= (UCCKPH | UCMSB | UCMST | UCSYNC); // 3-pin, 8-bit SPI master
UCB0CTL1 |= UCSSEL_2; // SMCLK used as SPI clock
#if F_MCLK == 6400000
UCB0BR0 |= 0x01; // 1/(6.4MHz/1) = ~0.15625us per bit
#elif F_MCLK == 12000000
UCB0BR0 |= 0x02; // 1/(12MHz/2) = ~0.166us per bit
#elif F_MCLK == 16000000
UCB0BR0 |= 0x03; // 1/(16MHz/3) = ~0.1875us per bit
#elif F_MCLK == 17120000
UCB0BR0 |= 0x03; // 1/(17.12MHz/3) = ~0.175us per bit
#elif F_MCLK == 19200000
UCB0BR0 |= 0x03; // 1/(19.2MHz/3) = ~0.15625us per bit
#elif F_MCLK == 24000000
UCB0BR0 |= 0x04; // 1/(24MHz/4) = ~0.166us per bit
#endif
UCB0BR1 = 0;
UCB0CTL1 &= ~UCSWRST;
}
void setup(void)
{
setvcoreup(0x01); // crank up core power to max
setvcoreup(0x02);
setvcoreup(0x03);
init_clocks(); // configure UCS clocks
init_spi(); // setup spi mosi on P3.0 use SMCLK
}
int main(void)
{
WDTCTL = WDTHOLD | WDTPW;
setup();
while (1) {
unsigned frame_offset;
// loop through each "animation frame" 3 bytes per led, 4 leds for a total of 12 bytes
for (frame_offset = 0; frame_offset < BYTE_PER_LED * LED_CNT * ANIMATION_FRAMES;
frame_offset += (BYTE_PER_LED * LED_CNT)) {
sendGRB(&leds[frame_offset], BYTE_PER_LED * LED_CNT);
__delay_cycles(500 * (F_MCLK / 1000));
}
}
}
/*
* ledbits2pulse - create SPI pulse bits
*
* Convert each bit of an RGB color to a ws281x pulse for use with SPI
* save in dst. Assumes user has provided enough room for dst which is 8x
* the size of src.
*
* 0 pulse is ~350ns high, ~1000ns low
* 1 pulse is ~700ns high, ~700ns low
*/
void ledbits2pulse(const uint8_t register * const src, uint8_t register *dst)
{
#if defined(USE_ASM_VERSION)
unsigned register mask, work, p0, p1;
__asm__ volatile (
" mov #128,%[mask]\n"
" mov #0xc0,%[p0]\n"
" mov #0xf0,%[p1]\n"
" mov.b @%[src],%[work]\n"
"1:\n"
" bit.b %[mask],%[work]\n"
" jnz 2f\n"
" mov.b %[p0],@%[dst]\n"
" jmp 3f\n"
"2:\n"
" mov.b %[p1],@%[dst]\n"
"3:\n"
" inc %[dst]\n"
" rra %[mask]\n"
" jnz 1b\n"
: [mask] "=&r" (mask), [work] "=&r" (work), [dst] "+&r" (dst), [p0] "=&r" (p0), [p1] "=&r" (p1)
: [src] "r" (src)
: "cc"
);
#else
unsigned register mask = 0x80;
do {
*dst++ = (*src & mask) ? 0b11110000 : 0b11000000;
mask = mask >> 1;
} while (mask);
#endif
}
#if defined(USE_DMA_SPI)
/*
* sendRGB() send green red blue using byte_count via DMA driven SPI
*
* led_data - 3 bytes per led in GRB order
*/
void sendGRB(const uint8_t * const color_data, unsigned byte_cnt)
{
// convert each bit into a 8 bit pulse for SPI
unsigned x = 0;
do {
ledbits2pulse(&color_data[x], &pulse_data[x * 8]);
x++;
} while (x < byte_cnt);
byte_cnt <<= 3; // use pulse_data length = byte_cnt * 8
DMACTL0 = DMA0TSEL_19;
__data16_write_addr((unsigned short) &DMA0SA, (unsigned long ) &pulse_data[1]); // src
__data16_write_addr((unsigned short) &DMA0DA, (unsigned long ) &UCB0TXBUF); // dest
DMA0SZ = byte_cnt - 1; // block size
DMA0CTL = DMADT_0 | DMASRCINCR_3 | DMASBDB; // single transfers, inc src, enable
DMA0CTL |= DMAEN;
UCB0TXBUF = pulse_data[0]; // prime the SPI pump to trigger
while (DMA0CTL & DMAEN)
; // wait for DMA to finish
}
#else
/*
* sendRGB() send green red blue using byte_count
*
* led_data - 3 bytes per led in GRB order
*/
void sendGRB(const uint8_t * const color_data, unsigned byte_cnt)
{
unsigned x = 0;
do {
ledbits2pulse(&color_data[x], &pulse_data[x * 8]);
x++;
} while (x < byte_cnt);
byte_cnt <<= 3; // use pulse_data length = byte_cnt * 8
#if defined(USE_ASM_VERSION)
register unsigned pulse_bits;
asm (
"1:\n"
" mov.b @%[pulse_data]+, %[pulsebits]\n" // do one byte at a time
"2:\n"
" and.b %[txempty],%[txifg]\n" // spin wait for txbuf to empty
" jz 2b\n"
" mov.b %[pulsebits], %[txbuf]\n"// send bits
" dec %[byte_cnt]\n"
" jnz 1b\n"// if more, continue with next color
:
[byte_cnt] "+&r" (byte_cnt) /* read/write byte count */
,[pulsebits] "=&r" (pulse_bits) /* work register */
:
[pulse_data] "r" (pulse_data) /* read access to rgb data */
,[txbuf] "m" (UCB0TXBUF) /* address of tx buffer */
,[txifg] "m" (UCB0IFG) /* address of tx status flag */
,[txempty] "i" (UCTXIFG) /* constant bitmask for tx buffer empty*/
: "cc"
);
#else
const uint8_t * dst = pulse_data;
do {
UCB0TXBUF = *dst++;
while(!(UCB0IFG & UCTXIFG))
;
} while(--byte_cnt);
#endif
}
#endif
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;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment