32x32 Pixel RGB LED Matrix [XCore beginner here]

XCore Project reviews, ideas, videos and proposals.
lberezy
Member
Posts: 13
Joined: Sat May 17, 2014 11:51 am

32x32 Pixel RGB LED Matrix [XCore beginner here]

Post by lberezy »

Hello,

My StartKit arrived in the mail a few days ago so I decided to get to know how it all worked with a small project. I decided to have a go at interfacing a generic 32x32 pixel RGB LED matrix I found from an online seller in China. These panels are completely without documentation, but I found Adafruit were selling a similar (nay, identical) panel with a bit of provided code for the Arduino, which was invaluable for reverse-engineering the board.

Turns out its a semi-parallel sequential control system in which you clock in two rows of pixel data at a time through a shift register before latching it to the LED drivers.

The Xmos chip appears to be super powerful for this task and after reading just a bit of the documentation I got something working!

I did notice that the "killer app" for the XCore seems to be its parallel threaded architecture, so if anyone could give me some advice as to how best modify my code to best make use of this, that would be fantastic! (there's a section in the refreshDisplay() routine that could be clocking in pixel data whilst waiting on the delay there)



I've tried to implement Binary Coded Modulation in order to get greater bit-depth out of the panel (LEDs can only be ON or OFF and must be constantly refreshed), but it's not working so great. If anyone has any experience doing software PWM or BCM, then that would be a massive help.



Anyway, the code is up on GitHub with some more information, go check it out!

https://github.com/lberezy/rgb-matrix-xcore

Project page here: http://www.xcore.com/projects/32x32-pix ... led-matrix
Last edited by lberezy on Sat May 17, 2014 2:08 pm, edited 1 time in total.


User avatar
leon_heller
XCore Expert
Posts: 546
Joined: Thu Dec 10, 2009 10:41 pm
Location: St. Leonards-on-Sea, E. Sussex, UK.
Contact:

Post by leon_heller »

lberezy
Member
Posts: 13
Joined: Sat May 17, 2014 11:51 am

Post by lberezy »

Yep, that's it. They're all sourced from China with no official documentation.
User avatar
Folknology
XCore Legend
Posts: 1274
Joined: Thu Dec 10, 2009 10:20 pm
Contact:

Post by Folknology »

Hey lberezy, nice looking project for the starter kit, please let me know where you got the cheap displays from, I would love to have a play with one myself.

As to how you might make this more concurrent..

I would break it up into tasks

Code: Select all

interface display {
  void setPixelAt(const uint8_t x, const uint8_t y, const pixel_t pixel);
  pixel_t getPixelAt(const uint8_t x, const uint8_t y);
  ...
}
..
void display_server(server interface display disp) {
  pixel_t framebuffer[PANEL_WIDTH][PANEL_HEIGHT];
  // init setup framebuffer etc..
  ....
  while(1) {
    select {
      case disp.setpixel(uint8_t x, uint8_t, pixel_t pixel) :
        // Implement setpixel as before here or..
        setpixel(uint8_t x, uint8_t, pixel_t pixel)
        break;
      case disp.getPixelAt(uint8_t x, uint8_t y) -> pixel :
        // implement get pixel or..
        pixel = getPixelAt(x,y);
        break;
      default : 
        refresh(&framebuffer, PANEL_WIDTH,PANEL_HEIGHT);
        break;
    }
  }
}

void display_client(client interface display disp) {
  // do some stuff by calling setpixel and get pixel etc..
  ..
  disp.setpixel(2,3,mypixel);
  mylastpixel = disp.getpixel(2,3);
  ..
}


void main(void) {
  ...
  interface display disp;
  par {
    display_server(disp); // thread 1
    display_client(disp); // thread 2
  }
}

None of this is compiled,tried,tested or optimised code just a suggestive structure idea for threading it
lberezy
Member
Posts: 13
Joined: Sat May 17, 2014 11:51 am

Post by lberezy »

Thanks for your input! I'll definitely fully check out this interface pattern and modify my code if I get some time today.

As for the panels, I found them on AliExpress. I can't find the exact seller, but various combinations of "RGB panel" and "32x32" should bring up results similar to this: http://www.aliexpress.com/item/SMD-Indo ... 42571.html

When ordering, check out the pictures of the product, especially pictures of the PCB on the back of the panel. My particular panel has two IDC female headers (16 pins each, IIRC) - one for data in, and another for data out used to daisy chain another panel. Some panels have 4 of these connectors and are really two 16x32 panels stacked. This isn't necessarily a problem, but it seems to be the least common wiring configuration and... more wires.
I think I ended up paying around $25 with shipping for my panel. Very happy with it, nice black solder mask on the back PCB and no defects.


Thanks again!
User avatar
Folknology
XCore Legend
Posts: 1274
Joined: Thu Dec 10, 2009 10:20 pm
Contact:

Post by Folknology »

Or even better still replace the default section with a timer case and make the task combinable:

Code: Select all

void display_server(server interface display disp) {
  timer t;
  uint32_t time;
  const uint32_t delay = 5
  pixel_t framebuffer[PANEL_WIDTH][PANEL_HEIGHT];
  // init setup framebuffer etc..
  ....
  t :> time
  while(1) {
    select {
      case disp.setpixel(uint8_t x, uint8_t, pixel_t pixel) :
        // Implement setpixel as before here or..
        setpixel(uint8_t x, uint8_t, pixel_t pixel)
        break;
      case disp.getPixelAt(uint8_t x, uint8_t y) -> pixel :
        // implement get pixel or..
        pixel = getPixelAt(x,y);
        break;
      case t when timerafter(time) :> void : 
        time += delay;
        refresh(&framebuffer, PANEL_WIDTH,PANEL_HEIGHT);
        break;
    }
  }
}
However you might remove and externalise the refresh bit loop and pass it in:

Code: Select all

time += bit < RESOLUTION_BITS ? delay * (1 << (bit++)) : bit = 1;
refresh(bit);
You might want to experiment with that outer loop to optimise.

PS I ordered one of those displays from ali to play with..
lberezy
Member
Posts: 13
Joined: Sat May 17, 2014 11:51 am

Post by lberezy »

Alright, I found a bit of time to play around with today and implemented your first suggestion.

Code's up in a testing branch on github (https://github.com/lberezy/rgb-matrix-xcore/tree/test) if you want to take a look. I'm passing the framebuffer array directly as according to (https://www.xmos.com/published/how-comm ... s?secure=1) " If an array argument is given for an intra-tile communication, the array is not copied but the compiler will optimize it to just pass a reference to the array, allowing the recipient to access the array in memory." occurs. I think I'm forgetting my C arrays/pointers and couldn't work out the correct type signature that should be accepted by the refreshDisplay(pixel_t frambuffer) function. A pointer to an array of pixel_t is what I want, but "pixel_t* framebuffer[WIDTH][HEIGHT]" isn't correct.

That said, the display seems to refresh now at an incredibly slow rate compared to the previous sequential code. I can actually see the line of pixels crawling up the display as they are refreshed.

I've got a short video of the difference between the new attempt and my bodged attempt at implementing your suggestion, but it's taking its sweet time to upload. I'll edit this post when I've got a link.
https://www.youtube.com/watch?v=wubWsiPJCHw
wubWsiPJCHw


As far as the timer stuff goes, I'm already confused about my refresh loop. What benefit would externalising the bit loop bring? (I'm probably missing something very obvious)


Thanks again!
User avatar
Folknology
XCore Legend
Posts: 1274
Joined: Thu Dec 10, 2009 10:20 pm
Contact:

Post by Folknology »

Before you had an outer bit loop, now you have an inner bit loop, try changing that back first to see what difference that has made before we move onto the timer stuff, otherwise we are not comparing like with like.


(I too need to understand your various refresh looping)

P.S. I will be out this afternoon, back this evening (UK) speak later

regards
Al
lberezy
Member
Posts: 13
Joined: Sat May 17, 2014 11:51 am

Post by lberezy »

Alrighty, changed the bit loop back to where it was. (actually, it was originally inside - was just something I was playing with and seemed to have no net effect on the old code).

I went ahead and moved the body of refreshDisplay into the default case for the display server (no passing framebuffer through the function, just direct access). Still runs just as slow.

Could it be contention issues with many calls to setPixel() not allowing any time for the refresh routine?

I tried changing the WAIT_TIME constant which controls how long the system blanks the display for and this did reduce the overall brightness of the display as expected. This means that all pixels are being refreshed in a timely manner. However, I do see columns of pixels being slowly built up scanning across the display in the same way a for loop would iterate across the framebuffer.

This leads me to suspect that the setPixel() routine is blocking the display client too much and it can't update the entire display fast enough.


Would this be solved by having the display refresh on a timer so that it happens at no more than ~60 Hz?
User avatar
Folknology
XCore Legend
Posts: 1274
Joined: Thu Dec 10, 2009 10:20 pm
Contact:

Post by Folknology »

Ok try changing the 'default' for a timer case

Code: Select all

timer t;
uint32_t time;
const uint32_t delay = 500
....
t :> time
while(1){
  select {
  ..
  case t when timerafter(time) :> void : 
        time += delay;
        //refresh code here
        break;
  }
}
..


Have a play around with the delay value and check out the effects.

let me know what you find

regards
Al
Post Reply