Finally! The powerful and low-cost Teensy can control those cool RGB LED Panels. Here I’ll explain a bit about how the sketch came to be and
how you can use it in your project. To begin I’ll say I am not a programmer. That being said I still wanted to use the powerful Teensy 3.1 with the LED panel. The library for the panel is written for Arduino to the extent it uses some assembly to increase the speed of the program one area. This is one of the main problems when trying to port Adafruit’s RGBmatrixPanel. Another would be the interrupts, Arduino uses an Atmel microcontroller while the Teensy uses a Freescale ARM chip. So after trying to modify that library many times I decided to write my own script for the Teensy.
Theory:
BAM and PWM:
To understand the code you must first understand bit angle modulation, or BAM for short. BAM is like PWM in that it is used to control brightness of LEDs. They both work by fluctuating between on and off to give the appearance of brightness to the human eye. Imagine PWM as this:
__—-__—-__—-__—-
where the upper part is on while the bottom is off. This would be half brightness for PWM. If you want to increase the brightness than you’d increase the time that the LED stays on compared to off. Simple right? Most microcontrollers have some built-in PWM control, for Arduino its 8 bit modulation or values 0-255; BAM instead of increasing the ratio of on to off it has increments of time which it can either be on or off. Try this time, to imaging that we have 4 slots to be either on or off. The first slot will last 1 time unit, the second slot will last 2 time units, the third will last 4 time units, and the fourth will last 8 time units.
1 _ 2 _ _ 3 _ _ _ _ 4 _ _ _ _ _ _ _ _ , like this.
With this we can control brightness on the range 0-15 a 4 bit number. If we want to have a brightness of 8, converted to binary is B1000, we write HIGH when it is the fourth interval. The time that the intervals are left on are all a power of 2: 2^1, 2^2, 2^3, and 2^4. These values allow binary to be written directly to the intervals for any level of brightness in the range. Why BAM over PWM? Well basicly the number of times that the code needs to be inturrupted is drasticly decressed. This means more levels of brightness with the same CPU resoucres. Color on a screen is determined by the individual brightness of each pixel’s red, green, and blue component. So more levels of brightness means more colors! See the links below if you want to learn more about BAM or color depth.
How the panel works:
If you don’t know how the panel works than I would read through Adafruit’s tutorial. In short, pins A, B, and C control which section of the panel to write to with a 3 to 8 decoder. R1, R2, G1, G2, B1, and B2 are for color data pins for the top and bottom half of the panel, R1 being top half and R2 being bottom. Whichever line A, B, and C are on is where the data is displayed. CLK, LAT, and OE are controls for the 16 bit constant current shift registers on the board. So you select the line you want with A, B, and C. Disable outputs with OE HIGH and put latch to LOW. Than data can be shifted in with the 6 data color lines R1-B2 and the clock clocks them 32 time. Finally latch is pulled HIGH and OE is LOW to display that row.
Port Addressing in short:
The code uses direct port manipulation. On the Teensy 3.0 there are 12 convenient pins available to write to on GPIOC_PDOR, addresses 0x001-0x800. These pins can’t be changed to a different port and are listed in the declared variables. You could move them to another port with some code modifications, but that is not a supported function. Read more about direct port manipulation and the Teensy 3.0 in the links below.
The Code:
First thing you’ll need is the interrupt handling library from here, PITimer does a great job of making PIT easy on the Teensy. Lets start by setting up the interrupt.
void timerInit() { PITimer1.period(0.000042); PITimer1.start(timerCallBack); BAM = 0; }
The code uses timer one and sets its initial period to 42μS. Then it sets the timer’s function to timerCallBack. BAM is the variable used to control how long each period lasts before the interrupt is called again.
[cpp]
void timerCallBack() {
attackMatrix(); // Updated the display
if(BAM > BAMMAX) { //Checks the BAM cycle for next time.
BAM = 0;
PITimer1.period(0.000042);
actDisplay = false;
} else {
BAM ++;
actDisplay = true;
}
}
[/cpp]
This function is called every time the interrupt is triggered.
[cpp]
//The updating matrix stuff happens here
//each pair of rows is taken through its BAM cycle
//then the rowNumber is increased and id done again
void attackMatrix() {
uint16_t portData;
//sets up which BAM the matrix is on
if(BAM == 0) { PITimer1.period(.000042); } //code takes max 41 microsec to complete
if(BAM == 1) { PITimer1.period(.000084); } //so 42 is a safe number
if(BAM == 2) { PITimer1.period(.000168); }
if(BAM == 3) { PITimer1.period(.000336); }
if(BAM == 4) { PITimer1.period(.000672); }
if(BAM == 5) { PITimer1.period(.001344); }
if(BAM == 6) { PITimer1.period(.005376); }
if(BAM == 7) { PITimer1.period(.010752); }
portData = 0; // Clear data to enter
portData |= (abcVar[rowN])|OE; // abc, OE
portData &=~ LATCH; //LATCH LOW
GPIOC_PDOR = portData; // Write to Port
for(uint8_t _x = 0; _x < 32; _x++){
uint16_t tempC[6] = { //Prepare data in correct place
((rMatrix[_x][rowN])) , ((rMatrix[_x][rowN+8])<<1),
((gMatrix[_x][rowN])<<2), ((gMatrix[_x][rowN+8])<<3),
((bMatrix[_x][rowN])<<4), ((bMatrix[_x][rowN+8])<<5) };
uint16_t allC =//Put OUTPUT data into temp variable
((tempC[0]>>BAM)&RED1)|((tempC[1]>>BAM)&RED2)|
((tempC[2]>>BAM)&GREEN1)|((tempC[3]>>BAM)&GREEN2)|
((tempC[4]>>BAM)&BLUE1)|((tempC[5]>>BAM)&BLUE2);
GPIOC_PDOR = (portData)|(allC); // Transfer data
GPIOC_PDOR |= SCLK;// Clock HIGH
GPIOC_PDOR &=~ SCLK;// Clock LOW
}
GPIOC_PDOR |= LATCH;// Latch HIGH
GPIOC_PDOR &=~ OE;// OE LOW, Displays line
if(BAM == BAMMAX){
if(rowN == 7){
rowN = 0;
} else {
rowN ++;
}
}
}
[/cpp]
This is the meat of the code. It’s called attackMatrix()
because it directly controls the LED panel. Because of the panel’s nature of green ghosting each row is run through all of the BAM cycles once before moving onto the next row. So the code first write how long it needs to display one row as the new timer. Then it starts to shift out the data. The code directly accesses the ports of the Teensy and a link below describes how that works.
- It reads BAM and sets the next timer period.
- After that it shifts out the data for display the two rows based off of abcVar[]. The for() loop outputs the 32 data values per row.
- It then repeats this BAMMAX +1 number of times for that section.
- Once the section is done then it rowN is increased and it moves onto the next section.
Download the Arduino .ino and color header I use here : _16x32_Matrix
Video of the Conway’s Game of Life:
Links :
Thanks to all of these websites as they helped greatly in writing this code.
- BAM vs. PWM
- Color Depth: how different levels of brightness control affect color.
- Video of BAM: it took me a while to understand, hope this helps. It’s for a LED cube but the points are great.
- Direct Port Manipulation on Teensy 3.0
Update:
MarkusL took my code and made it better! The pin out is different and the code is written for a java application to do the image processing, but other than that he has made nice optimizations. Here is the forum post.
Update 2:
Looks like Adafruit has improved the panel even more, now with a Raspberry Pi! That’s right 700 MHz, Ethernet, USB, and python scripting (if python is something your into). Well no currently there is no color control on the device but it seems like a good idea to use the Pi because it is cheap and common. Maybe if the code is written in C it can get even faster IO.
Update 3:
If your looking to buy this panel or similar LED matrix I would suggest looking at the options available at Aliexpress. $9 can get you the same 16×32 Matrix I bought for around $40.