Jump to content
Gareeeesh

16x16 RGB LED Matrix Displaying Images - Help!

Recommended Posts

Hello everyone!

 

I'm continuing my project of creating an RGB LED matrix capable of displaying live images being transmitted from a laptop via a serial cable to the MSP430G2553.

At the moment, I'm coding four 8x8 Common Anode RGB LED matrices joined together to create a 16x16 display.

Hardware I'm using:      

- 3 * TLC5940s (one for each colour)

- 2 * 74HC595 shift registers (drives the columns/anodes)

 

I'm using code provided by RobG from this link <http://forum.43oh.com/topic/2315-tlc5940-examples/> and this one <http://forum.43oh.com/topic/3328-rgb-to-hsv-conversion-with-msp430g2231-help/#entry29106> in order to drive the LEDs with PWM enabled.

BUT, the problem I'm having is that I'm trying to allow every pixel to have different colours, but when running, the microcontroller isn't refreshing the pixels fast enough so there is MASSIVE flickering - which isn't ideal....

 

Here is the code from which I am working : feel free to insult :-P

 

#include <msp430g2553.h>
#include <msp430.h>
// Need to sort hsv2rgb because yellow is pish...
// u_long is 32 bits so 2^32 - 1 is max value. Need more for 32x32 matrix
// TLC inputs
#define VPRG_PIN	BIT0 // TIE TO GND?
#define GSCLK_PIN	BIT4
#define SCLK_PIN	BIT5
#define DATA	        BIT6  // DS -> 1.6 | 595 DATA PIN
#define MOSI_PIN	BIT7
#define MOSI595         BIT2 // 595 data pin ? THIS ISNT DOING ANYTHING

#define DCPRG_PIN	BIT0 // TIE IT TO GND?
#define XLAT_PIN	BIT1
#define BLANK_PIN	BIT2
#define CLOCK 		BIT3 // SH 11 -> 2.3  // 595 OUTPUTS
#define LATCH	        BIT4 // ST 12 -> 2.4t

typedef unsigned char u_char;
typedef unsigned int u_int;
typedef unsigned short u_short;
typedef unsigned long u_long;
// ================================//
//      Prototypes                 //
// ================================//
void init( void );
void SPI_setup( void );
void updateTLC( void );
void shiftOut( u_long );
void HSV2RGB( u_short*, u_short*, u_short*, short, u_char);
void set_row_char_hue (u_char, u_long, short, u_char);
void row_refresh(u_char,short,short,short,short,short,short,short,short,short,short,short,short,short,short,short,short);

#define NUMBER_OF_OUTS 48 // TLC OUTPUTS
#define NUMBER_OF_ROWS 8
#define NUMBER_OF_COLUMNS 16
#define max_COLUMN NUMBER_OF_COLUMNS-1
#define max_ROW NUMBER_OF_ROWS-1

#define OFF 0
u_short leds[NUMBER_OF_OUTS];  // 0 - 15 Red Rows, 16 - 31 Blue Rows, 32 - 47 Green Rows {0, }
u_char timerCounter = 0;

void init(void)
{
	WDTCTL = WDTPW + WDTHOLD; // disable WDT
	BCSCTL1 = CALBC1_16MHZ; // 16MHz clock
	DCOCTL = CALDCO_16MHZ;
	BCSCTL2 |= DIVS_0; // divide clock by 1 

	P1OUT &= ~(VPRG_PIN);
	P1DIR |= (VPRG_PIN + GSCLK_PIN + DATA); // port 1.4 configured as SMCLK out
	P1SEL |= GSCLK_PIN;
	P2DIR |= (BLANK_PIN + XLAT_PIN + CLOCK + LATCH + DCPRG_PIN);
	P2OUT &= ~(BLANK_PIN + XLAT_PIN);
	P2OUT &= ~DCPRG_PIN;

	// setup timer
	CCR0 = 0xFFF;
	TACTL = TASSEL_2 + MC_1 + ID_0; // SMCLK, up mode, 1:1
	CCTL0 = CCIE; // CCR0 interrupt enabled
}

void SPI_setup(void)
{
	// setup UCA0 ----UCA0 ISNT DOING ANYTHING YET! SHOULD I CONFIGURE THE 595's TO BE CONTROLLED BY SPI?
	P1SEL |= MOSI595; // p1.2
	P1SEL2 |= MOSI595; // UCA0SIMO
	UCA0CTL0 = UCCKPH + UCMSB + UCMST + UCSYNC; // data captured on 1st UCLK edge/changed on follwing edge, MSB first, master, 3-pin SPI,synchronous
	UCA0CTL1 |= UCSSEL_2; // SMCLK
	UCA0BR0 |= 0x01; // 1:1
	UCA0BR1 = 0;
	UCA0CTL1 &= ~UCSWRST; // clear SW
	// setup UCB0   
	P1SEL |= SCLK_PIN + MOSI_PIN; // pins 5 + 7
	P1SEL2 |= SCLK_PIN + MOSI_PIN; // UCB0CLK + UCB0SIMO
	UCB0CTL0 = UCCKPH + UCMSB + UCMST + UCSYNC; // data captured on 1st UCLK edge/changed on follwing edge, MSB first, master, 3-pin SPI,synchronous
	UCB0CTL1 |= UCSSEL_2; // SMCLK
	UCB0BR0 |= 0x01; // 1:1
	UCB0BR1 = 0;
	UCB0CTL1 &= ~UCSWRST; // clear SW
}

void main(void)
{
	init();
	SPI_setup();
	updateTLC();

	P2OUT |= (XLAT_PIN);
	P2OUT &= ~(XLAT_PIN);

	short o = 240; 
	short y = 60; // yellow
	short b = 240; // blue
	short hue = 0;
	_bis_SR_register(GIE);

	for(;
	{ 
//     creates box image
			row_refresh(0, 	b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,;
			row_refresh(1, 	b,y,y,y,y,y,y,y,y,y,y,y,y,y,y,;
			row_refresh(2, 	b,y,b,b,b,b,b,b,b,b,b,b,b,b,y,;
			row_refresh(3, 	b,y,b,y,y,y,y,y,y,y,y,y,y,b,y,;
			row_refresh(4, 	b,y,b,y,b,b,b,b,b,b,b,b,y,b,y,;
			row_refresh(5, 	b,y,b,y,b,y,y,y,y,y,y,b,y,b,y,;
			row_refresh(6, 	b,y,b,y,b,y,b,b,b,b,y,b,y,b,y,;
			row_refresh(7, 	b,y,b,y,b,y,b,y,y,b,y,b,y,b,y,;
			row_refresh(8, 	b,y,b,y,b,y,b,y,y,b,y,b,y,b,y,;
			row_refresh(9, 	b,y,b,y,b,y,b,b,b,b,y,b,y,b,y,;
			row_refresh(10,	b,y,b,y,b,y,y,y,y,y,y,b,y,b,y,;
			row_refresh(11,	b,y,b,y,b,b,b,b,b,b,b,b,y,b,y,;
			row_refresh(12,	b,y,b,y,y,y,y,y,y,y,y,y,y,b,y,;
			row_refresh(13,	b,y,b,b,b,b,b,b,b,b,b,b,b,b,y,;
			row_refresh(14,	b,y,y,y,y,y,y,y,y,y,y,y,y,y,y,;
			row_refresh(15,	b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,;

	_bis_SR_register(LPM0_bits); // go to sleep
	} // end of for(; loop
}


void updateTLC(void)                 // RobG's code
{
u_char ledCounter = NUMBER_OF_OUTS >> 1;
   while (ledCounter-- > 0)
   {
	u_char i = ledCounter << 1;
	UCB0TXBUF = leds[i + 1] >> 4;
	while (!(IFG2 & UCB0TXIFG)); // TX buffer ready?
	u_char unib = leds[i + 1] << 4;
	u_char lnib = (leds[i] >> 8) & 0x0F;
	UCB0TXBUF = unib | lnib;
	while (!(IFG2 & UCB0TXIFG))	; // TX buffer ready?
	UCB0TXBUF = leds[i];
	while (!(IFG2 & UCB0TXIFG)); // TX buffer ready?
   }
}

void row_refresh(u_char row,short a,short b,short c,short d,short e,short f,short g,short h,short i,short j,short k,short l,short m,short n,short o,short p)
{
// this function checks if led is supposed to be ON 
// for example, if row = 0, and a = 360 then the top left led is OFF
// if a = 120, then the led will be ON, and will be assigned the appropriate hue (green)
	u_long col_data = 0x0000;
	if(a < 360){col_data = 0x8000; set_row_char_hue(row,col_data,a,1);} // 0
	if(b < 360){col_data = 0x4000; set_row_char_hue(row,col_data,b,1);} // 1
	if(c < 360){col_data = 0x2000; set_row_char_hue(row,col_data,c,1);} // 2
	if(d < 360){col_data = 0x1000; set_row_char_hue(row,col_data,d,1);} // 3
	if(e < 360){col_data = 0x0800; set_row_char_hue(row,col_data,e,1);} // 4
	if(f < 360){col_data = 0x0400; set_row_char_hue(row,col_data,f,1);} // 5
	if(g < 360){col_data = 0x0200; set_row_char_hue(row,col_data,g,1);} // 6
	if(h < 360){col_data = 0x0100; set_row_char_hue(row,col_data,h,1);} // 7
	if(i < 360){col_data = 0x0080; set_row_char_hue(row,col_data,i,1);} // 8
	if(j < 360){col_data = 0x0040; set_row_char_hue(row,col_data,j,1);} // 9
	if(k < 360){col_data = 0x0020; set_row_char_hue(row,col_data,k,1);} // 10
	if(l < 360){col_data = 0x0010; set_row_char_hue(row,col_data,l,1);} // 11
	if(m < 360){col_data = 0x0008; set_row_char_hue(row,col_data,m,1);} // 12
	if(n < 360){col_data = 0x0004; set_row_char_hue(row,col_data,n,1);} // 13
	if(o < 360){col_data = 0x0002; set_row_char_hue(row,col_data,o,1);} // 14
	if(p < 360){col_data = 0x0001; set_row_char_hue(row,col_data,p,1);} // 15
}

void set_row_char_hue (u_char row, u_long col_data, short hue, u_char bool)
{
	u_char led = 0;
	u_long byte;

	for(led = 0 ; led < 16 ; led++ )
	{
		byte = (col_data>>led)&(0x01);
		if( byte )
		{
			HSV2RGB( &leds[row], &leds[row + 32], &leds[row + 16], hue, bool); //on;
			shiftOut( col_data );
		}
	}
	// turn off leds
	leds[row] = OFF;
	leds[row+32] = OFF;
	leds[row+16] =  OFF;

}

void HSV2RGB(u_short* r, u_short* g, u_short* b, short h, u_char bool)
{
	const u_char s = 255;
	u_char v = 255;
	u_char i, p, q, t;
	u_short fs;

	if (h < 60 ) i = 0;
	else if (h < 120) i = 1;
	else if (h < 180) i = 2;
	else if (h < 240) i = 3;
	else if (h < 300) i = 4;
	else if (h < 360) i = 5;
	else return;

	fs = ((h - i * 60) * s)/ 60;
	p = 255 - s;
	q = 255 - fs;
	t = 255 - s + fs;

	switch(i)
	{
	case 0:
		*r = 255;*g = t;*b = p;break;
	case 1:
		*r = q;*g = 255;*b = p;break;
	case 2:
		*r = p;*g = 255;*b = t;break;
	case 3:
		*r = p;*g = q;*b = 255;break;
	case 4:
		*r = t;*g = p;*b = 255;break;
	case 5:
		*r = 255;*g = p;*b = q;break;
	}
	if ( bool == 1 )
	{
		*r *= v/16;
		*g *= v/16;
		*b *= v/16;
	}
	else {
		*r *= 0;
		*g *= 0;
		*b *= 0;
	}
}

#pragma vector = TIMER0_A0_VECTOR
__interrupt void Timer_A0(void)
{
	// 4096 GS clock cycles have been generated, time to restart PWM
	P2OUT |= (BLANK_PIN);
	P2OUT |= (XLAT_PIN);
	P2OUT &= ~(XLAT_PIN);
	P2OUT &= ~(BLANK_PIN);
	// increase timer counter
	timerCounter++;
	if (timerCounter == 1)                  // this is also from RobG, except updateTLC is every interrupt because otherwise it is far too slow
	{ // is it time to for next frame?
		updateTLC();		 // data was already prepared by main loop when it was awake last time
		timerCounter = 0;
		_bic_SR_register_on_exit(LPM0_bits); // wake up main loop so that it can prepare data for the next frame
	}
}

void shiftOut(u_long val)
{
	P2OUT &= ~LATCH;
	int i;
	for (i = 0; i < 16; i++) // iterate over each bit 16
	{
		if(val & (1 << i)) {
			P1OUT |= DATA;
		} else {
			P1OUT &= ~DATA;
		}
		P2OUT |= CLOCK; // PULSE CLOCK
		P2OUT ^= CLOCK;
	}
	//	}
	P2OUT |= LATCH; // PULSE LATCH
	P2OUT &= ~LATCH;
}


If anyone has any suggestions on how to improve this code, it will be greatly appreciated, because I've really hit a wall with it.

I did think that using SPI P1.2 for UCA0SIMO instead of the shiftOut() function might be a solution but I don't know how to go about doing that.

Keep in mind that soon I'll be using P1.1 for RXD.

 

Cheers!

Share this post


Link to post
Share on other sites

What you're attempting may very well be beyond the capabilities of an MSP430. I did a project a while back driving RGB LEDs, and that required multiple FPGAs to actually drive the LEDs fast enough, and used megabit serial (!) to transmit data to the FPGAs. This was generated by an LPC1768 running at a clock speed well out of reach of an MSP430.

 

Anyway, let's do some calculations:

 

You have 16x16 display, which means you have 256*3 = 768 bits of data to output if all you were going to do is have each LED either on or off (no PWM).

 

You are trying to PWM with 255 brightness levels for each LED, which means you must output 768 bits of data 255 times over, and fast enough to not perceive any flickering. I've found 75Hz is about what's required to avoid any perception of flickering, so you'll have to output 768*255*75 bits of data per second. That's about 14 megabits, which means you'd need a clock speed of 30Mhz just to get the data out - leaving no time over to calculate anything or load any data into registers or anything.

 

Also, if you want to get this data into the MSP over serial, I presume you'll want to store the image data in RAM on the MSP. Your 16x16x3 bytes of memory is 768 bytes, which is more RAM than is available in the MSP.

 

So my advice is to drop the number of brightness levels you need to (say) four, this is two bits per channel per pixel. Forget about trying to calculate HSV to RGB transformations on the MSP430. Work on just getting the LEDs on or off to start with, to make sure you can get everything running fast enough to get that going. And then try four brightness levels.

 

My feeling is though that to get enough speed for this project you will either need to get into assembly or use a more powerful micro. The LPC1768 is a nice one, but anything with a bit more grunt is probably what you'll need. Your shift registers go up to 100Mhz input, why not use a micro that can give you that kind of speed?

Share this post


Link to post
Share on other sites

Put a break point where the frame begins, turn the clock on, let it run until the next frame, note how many clock cycles it takes to display each frame.

Share this post


Link to post
Share on other sites

I had to switch to the F5438A because of the RAM issue and was able to calibrate the clock at 20MHz - although there was some flickering, it suited the project brief well enough. Thanks for your replies guys!

Share this post


Link to post
Share on other sites

You're quite heavy on the MSP430, but I think you could manage this. First, make sure you have the SPI running at the highest speed possible. Then try to find memory heavy operations and optimise them.

 

I found the HSV2RGB function optimisable, since you're not doing any saturation or value driving (hardcoded at 255), below I tried to replace all code that uses satuarion/value agnostic approach and replace it with the hardcoded variant

void Hue2RGB(u_short* r, u_short* g, u_short* b, short h, u_char bool)
{
	u_short i, p, q, t, u;

	if (h < 60 )      i = 60 * 0;
	else if (h < 120) i = 60 * 1;
	else if (h < 180) i = 60 * 2;
	else if (h < 240) i = 60 * 3;
	else if (h < 300) i = 60 * 4;
	else if (h < 360) i = 60 * 5;
	else return; // if h >= 360, the rest of this function is skipped, so the caller does not need to check for h < 360

        if (bool)
        {
	    t = ((h - i) * 4095)/ 60; // t is the angle from the lower 60 degree multiple scaled from 0-59 to 0-4095
	    p = 0;
	    q = 4095 - t;
            u = 4095;
        }
        else
        {
            t = ((h - i) * 255)/ 60; // t is the angle from the lower 60 degree multiple scaled from 0-59 to 0-255
            p = 0;
            q = 255 - t;
            u = 255;
        }

	switch(i)
	{
	case 60 * 0:
		*r = u;*g = t;*b = p;break;
	case 60 * 1:
		*r = q;*g = u;*b = p;break;
	case 60 * 2:
		*r = p;*g = u;*b = t;break;
	case 60 * 3:
		*r = p;*g = q;*b = u;break;
	case 60 * 4:
		*r = t;*g = p;*b = u;break;
	case 60 * 5:
		*r = u;*g = p;*b = q;break;
	}
}

I moved the check for bool up. You could split this in two functions, one with always true for bool, and one with always false. Essentially you'd have a Hue2RGB12bit() and Hue2RGB8bit() respectively. Since you're only using the 12 bit version, you can drop the 8 bit version from your code.

Also, I replaced the multiplication by 60 with doing that in the assignment already.

Another optimisation (though dropping a tiny bity of accuracy) would be to precalculate *4095 / 60 and replace it with * 68, saving you a division! Same for *255 / 60, replace it with * 4, but you're not using the 8 bit version anyway.

 

As I stated in the comment already; this function will not replace the values in r, g, b when h >= 360, so you can skip the check in row_refresh. You pass an aweful lot of parameters to that function, try replacing it with passing an array, like this:

const u_short image[16][16] = {
    {b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b},
    {b,y,y,y,y,y,y,y,y,y,y,y,y,y,y,b},
    {b,y,b,b,b,b,b,b,b,b,b,b,b,b,y,b},
    {b,y,b,y,y,y,y,y,y,y,y,y,y,b,y,b},
    {b,y,b,y,b,b,b,b,b,b,b,b,y,b,y,b},
    {b,y,b,y,b,y,y,y,y,y,y,b,y,b,y,b},
    {b,y,b,y,b,y,b,b,b,b,y,b,y,b,y,b},
    {b,y,b,y,b,y,b,y,y,b,y,b,y,b,y,b},
    {b,y,b,y,b,y,b,y,y,b,y,b,y,b,y,b},
    {b,y,b,y,b,y,b,b,b,b,y,b,y,b,y,b},
    {b,y,b,y,b,y,y,y,y,y,y,b,y,b,y,b},
    {b,y,b,y,b,b,b,b,b,b,b,b,y,b,y,b},
    {b,y,b,y,y,y,y,y,y,y,y,y,y,b,y,b},
    {b,y,b,b,b,b,b,b,b,b,b,b,b,b,y,b},
    {b,y,y,y,y,y,y,y,y,y,y,y,y,y,y,b},
    {b,b,b,b,b,b,b,b,b,b,b,b,b,b,b,b}};
	for(;
	{ 
//     creates box image
			row_refresh(0, 	image[0]);
			row_refresh(1, 	image[1]);
			row_refresh(2, 	image[2]);
			row_refresh(3, 	image[3]);
			row_refresh(4, 	image[4]);
			row_refresh(5, 	image[5]);
			row_refresh(6, 	image[6]);
			row_refresh(7, 	image[7]);
			row_refresh(8, 	image[8]);
			row_refresh(9, 	image[9]);
			row_refresh(10,	image[10]);
			row_refresh(11,	image[11]);
			row_refresh(12,	image[12]);
			row_refresh(13,	image[13]);
			row_refresh(14,	image[14]);
			row_refresh(15,	image[15]);

	_bis_SR_register(LPM0_bits); // go to sleep
	} // end of for(; loop
}

//......

void row_refresh(u_char row, u_short[16] rowdata)
{
// this function checks if led is supposed to be ON 
// for example, if row = 0, and a = 360 then the top left led is OFF
// if a = 120, then the led will be ON, and will be assigned the appropriate hue (green)
	u_long col_data = 0x8000;
	                   set_row_char_hue(row,col_data,row_data[0],1); // 0
	col_data >>= 1; set_row_char_hue(row,col_data,row_data[1],1); // 1
	col_data >>= 1; set_row_char_hue(row,col_data,row_data[2],1); // 2
	col_data >>= 1; set_row_char_hue(row,col_data,row_data[3],1); // 3
	col_data >>= 1; set_row_char_hue(row,col_data,row_data[4],1); // 4
	col_data >>= 1; set_row_char_hue(row,col_data,row_data[5],1); // 5
	col_data >>= 1; set_row_char_hue(row,col_data,row_data[6],1); // 6
	col_data >>= 1; set_row_char_hue(row,col_data,row_data[7],1); // 7
	col_data >>= 1; set_row_char_hue(row,col_data,row_data[8],1); // 8
	col_data >>= 1; set_row_char_hue(row,col_data,row_data[9],1); // 9
	col_data >>= 1; set_row_char_hue(row,col_data,row_data[10],1); // 10
	col_data >>= 1; set_row_char_hue(row,col_data,row_data[11],1); // 11
	col_data >>= 1; set_row_char_hue(row,col_data,row_data[12],1); // 12
	col_data >>= 1; set_row_char_hue(row,col_data,row_data[13],1); // 13
	col_data >>= 1; set_row_char_hue(row,col_data,row_data[14],1); // 14
	col_data >>= 1; set_row_char_hue(row,col_data,row_data[15],1); // 15
}

As you can see, a lot of code is repeating itself, I leave it up to you to replace it with for loops and to inline functions.

 

Shift operations aren't that cheap on the MSP430, try using an intermediate value in set_row_char_hue:

void set_row_char_hue (u_char row, u_short col_data, short hue, u_char bool)
{
	u_char led = 0;
	u_short byte = col_data; // copy

	for(led = 0 ; led < 16 ; led++ )
	{
		if( byte & 0x01 ) // move masking out of bit to here
		{
			HSV2RGB( &leds[row], &leds[row + 32], &leds[row + 16], hue, bool); //on;
			shiftOut( col_data );
		}
                byte >>= 1; // byte now only shifts by 1 per iteration
	}
	// turn off leds
	leds[row] = OFF;
	leds[row+32] = OFF;
	leds[row+16] =  OFF;

}

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×