Pages: [1]   Go Down
Author Topic: RG LED 8x8 display with PWM via shiftregisters+div  (Read 1440 times)
0 Members and 1 Guest are viewing this topic.
Norway
Offline Offline
Sr. Member
****
Karma: 4
Posts: 423
microscopic quantum convulsions of space-time
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I was a bit unsure about where to post this, but as I'm over to the software side now, I posted it here.

I've been playing with my 8x8 red-green LED display lately, and used some external ciruitry to minimize the connections to the Arduino. It currently uses 5 pins to PWM control an 8 by 8 LED matrix.

In short I'm using two chained 74HC595 shiftregisters, one for each color on the anode side of the LED's. And one 74HC161 4-bit binary counter connected to a 74HC138, 3-to-8 line decoder. This is for multiplexing the GND to the cathode rows of the display.

Now I'm not entirely sure if I'm within specs of the chips, as I drive the display directly from the 74HC595 and 74HC138 IC's. It seems to work so far  8-) Then again, I haven't tested a full yellow display yet...
To make a nice yellow color found that 1k ohm for the red led's, and about 130 ohm for the green led's, worked well. Vcc is 5V, of course.

This is my current breadboard setup with a quick crude schematics by the side (I'm using the Freeduino BBB from moderndevices):



Here is a test with 2, 3, 4 and finally 8 PWM levels. This is just a proof-of-concept test, the code is not optimised in any particularl way.

Tthe photos not too clear with respects to the colors. The flash have something to do with that I guess.

     

At 8 PWM levels there is virtually,well, almost no flickering when looking right at the display. I haven't measured exactly, but some tests suggests that for a frame interval of 20ms, there seems to be about 10-12 ms available for some code to execute between each frame. Also, at 8 PWM levels, I don't use any timer between rows, it goes as fast as it can in its present implementation.

For 4 or less PWM's, about 1 ms between rows makes it flicker a bit, but the colors are brighter than without..

I haven't learned anything about interrupts or any advanced features of the arduino language yet, nor have I used any form of direct port manipulation, so this obviously have room for improvement. For now I'm just happy I managed to get my RG display going PWM smiley

Here is my code.

Code:
// Shiftregister PWM RG LED 8x8 matrix

// Red-Green LED 8x8 matrix display via two chained 74HC595 shiftregisters with latch
// and 74HC161 4-bit counter connected to 74HC138 3-to-8 line decoder for multiplexed row GND's.
// This is not optimised code, just bare-bones proof-of-concept.

// Copyright 2009 raron

/*
    GNU GPLv3 license

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

// Unneccesarily detailed history:
// 2009.10.18 - v.0.1 - raron - Started tinkering with it.
// 2009.10.19 - v.0.2 - raron - works in simple row-by-row mode
// 2009.10.20 - v.0.3 - raron - changed to per-frame display
// 2009.10.20 - v.0.4 - raron - Arbitrary nr. of PWM implemented!
// 2009.10.21 - v.0.4b- raron - Improved PWM. Added potmeter control for test.


const int ledPin = 8;          // just a power-on LED
const int potPin = 0;

const int rowResetPin = 9;     // connected to 74CH161 4-bit counter ~MCLR input
const int rowClockPin = 10;    // 74HC161
const int srDataPin = 11;      // connected to 74HC595 shiftregister with latch
const int srLatchPin = 12;     // 74HC595
const int srClockPin = 13;     // 74HC595

int rowCounter;
unsigned long rowTimer;
unsigned long rowInterval = 0 ;    // one row interval time in ms
const unsigned long frameInterval = 20 ; // milliseconds, or 1/framerate
unsigned long frameTimer;

unsigned char greenByte;
unsigned char redByte;

unsigned char PWMres = 8 ;  // PWM resolution/levels
unsigned char PWMcontrol;
unsigned char redBitmap[8][8];
unsigned char greenBitmap[8][8];
unsigned char x,y;

int potValue;
int normalize;

void setup()
{
  frameTimer = millis();
  pinMode(ledPin,OUTPUT);
  pinMode(rowResetPin,OUTPUT);
  pinMode(rowClockPin,OUTPUT);
  pinMode(srDataPin,OUTPUT);
  pinMode(srLatchPin,OUTPUT);
  pinMode(srClockPin,OUTPUT);

  digitalWrite(rowResetPin,LOW);          // reset row counter, active low
  digitalWrite(rowResetPin,HIGH);
  digitalWrite(ledPin,HIGH);              // power-on LED
  digitalWrite(srDataPin,LOW);
  digitalWrite(srLatchPin,LOW);
  digitalWrite(srClockPin,LOW);
}

void loop()
{

  potValue = analogRead(potPin);
  PWMres = 1 + (unsigned char)(potValue/128);
  if ( PWMres < 2 ) PWMres = 2;
  if ( PWMres > 8 ) PWMres = 8;

  if ( PWMres < 4 ) rowInterval = 1;
  if ( PWMres > 4 ) rowInterval = 0;

  
  normalize = 8 / PWMres ;

  
  // Make the same color test pattern over and over...
  for (y=0;y<8;y++)
  {
    for (x=0;x<8;x++)
    {
      redBitmap[x][y] = (7-x) / normalize ;
      greenBitmap[x][y] = y / normalize ;
    }
  }
  
 //delay(10); // wasting some time as usual

 
  
  // Display a frame
  if ( millis() - frameTimer > frameInterval )
  {
    frameTimer = millis();
    digitalWrite(rowResetPin,LOW);        // Reset / re-sync physical row counter/decoder
    digitalWrite(rowResetPin,HIGH);
    for ( rowCounter = 0 ; rowCounter < 8 ; rowCounter++)
    {
      for ( PWMcontrol = 0 ; PWMcontrol < PWMres - 1 ; PWMcontrol++)
      {
        // get row data into shiftout bytes and PWM the colors.
        // PWM = colorvalue / (PWMres-1)
        // Example: With 8 PWM levels (PWMres = 8): max red value = 7 = 7/7 red = full on.
        redByte = 0; greenByte = 0;
        for ( x = 0 ; x < 8 ; x++)
        {
          redByte *= 2;  // "<< 1" would'nt work for some reason
          greenByte *= 2;
          if ( redBitmap[x][rowCounter] > PWMcontrol ) redByte |= 1 ;
          if ( greenBitmap[x][rowCounter] > PWMcontrol ) greenByte |= 1 ;
        }
        shiftOut(srDataPin,srClockPin,LSBFIRST,greenByte);
        shiftOut(srDataPin,srClockPin,LSBFIRST,redByte);
        // ----- Increase physical row counter -----
        if ( (rowCounter > 0) && !PWMcontrol )
        {
          digitalWrite(rowClockPin,HIGH);       // Row counter clockpuls
          digitalWrite(rowClockPin,LOW);
        }
        // Show new bitpattern on new row ASAP
        digitalWrite(srLatchPin,HIGH);
        digitalWrite(srLatchPin,LOW);
        rowTimer = millis();
        while (millis()-rowTimer < rowInterval); // Display the line for a little while
      }
    }
  }
  // Clear shiftregister between frames (prevents last line bleed-through to first line, and last line brighter than the others)
  shiftOut(srDataPin,srClockPin,LSBFIRST,0x00);
  shiftOut(srDataPin,srClockPin,LSBFIRST,0x00);
  digitalWrite(srLatchPin,HIGH);
  digitalWrite(srLatchPin,LOW);
}

« Last Edit: October 20, 2009, 09:06:07 pm by raron » Logged

Florida
Offline Offline
Sr. Member
****
Karma: 4
Posts: 431
hookedup!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks for posting.  

You may want to check out this post for details on using some other shift out methods - may allow for more PWM levels.

http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1254488619

I am new so take it for what it's worth. But using the hardware SPI method - I have been able to get PWM control on lots of pins (over 100) with 64 levels of brightness making for some killer RGB action.

Logged

Norway
Offline Offline
Sr. Member
****
Karma: 4
Posts: 423
microscopic quantum convulsions of space-time
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks for answering! And yes I'm planning to improve it, I just haven't had (or taken) the time yet. Also I'm just "getting my feets wet" (or how the saying goes) concerning matrix displays, and PWM'ing them.

One possibly dumb question, can the hardware SPI work on any pin? I'm not sure how I invoke any hardware SPI either yet, btw, but I will definately take a look at it!
Logged

Florida
Offline Offline
Sr. Member
****
Karma: 4
Posts: 431
hookedup!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

I think you need specific pins.  I use the following pins.  

Code:
//--- Pin connected to ST_CP of 74HC595
int latchPin = 10; //green
//--- Pin connected to SH_CP of 74HC595
int clockPin = 13; //yellow
//--- Pin connected to DS of 74HC595
int dataPin = 11; //blue


Logged

Florida
Offline Offline
Sr. Member
****
Karma: 4
Posts: 431
hookedup!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Here is code with the matrix pins made virtual with PWM control 64 levels.

You can set any of the "pins" on each LED to a value from 0 to 255.  The rest is handled by iprocess.

This is just a simple example with advanced stuff stripped out.

Hope it helps others.

Code:
/**************************************************************
 * Name    : Matrix LED Controller Via Shift Registers
 * By      : Joseph Francis
 *
 * Open Source
 *
 * Date    : 03 Nov, 2009                                      
 * Version : 1.0                                              
 * Notes   : Code for using a 74HC595 Shift Register          
 * : to run a matrix with PWM on 128 virtual ports using 3 SR's
 ****************************************************************/
#include <TimerOne.h>

#define TICKER_MAX 64
#define TICKER_STEP 4

int timerDelay = 200;

byte srCommon = B11111011;

//--- Pin connected to ST_CP of 74HC595
int latchPin = 10;
//--- Pin connected to SH_CP of 74HC595
int clockPin = 13;
//--- Pin connected to DS of 74HC595
int dataPin = 11;

//--- Used for faster latching
int latchPinPORTB = latchPin - 8;

//======================

int groundMax = 8;  //number of multiplex

int ticker = 0;

int groundAt = 255;

byte srPins[128] = {
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0,
  0,0,0,0,0,0,0,0
};

void pwmWrite(int port, byte val){
    srPins[port] = val;  
}

void iProcess(){

  ticker++;
  if( ticker > TICKER_MAX )
    ticker = 0;
  int myPos = ticker * TICKER_STEP;

    groundAt++;
    if( groundAt > groundMax - 1 )
      groundAt = 0;


//IMPORTANT: This but be swapped for common cathod
  byte mySR1  = B11111111;
  byte mySR2  = B11111111;
  srCommon = 0;

/* for Common Cathode
  byte mySR1  = 0;
  byte mySR2  = 0;
  srCommon = B11111111;

*/
  for (int i = 0 ; i < 8; i++ ){
    int iLED = i;
    int myLev = 0;

    myLev = 1;
    if (srPins[i+(groundAt*16)] > myPos)
      myLev = 0;
    bitWrite(mySR1,i,myLev );

    myLev = 1;
    if (srPins[(i+8)+(groundAt*16)] > myPos)
      myLev = 0;
    bitWrite(mySR2,i,myLev );

  }


  bitWrite(srCommon,groundAt,1 );

/* for Common Cathode - use this instead
  for (int i = 0 ; i < 8; i++ ){
    int iLED = i;
    int myLev = 0;

    myLev = 1;
    if (srPins[i+(groundAt*16)] > myPos)
      myLev = 1;
    bitWrite(mySR1,i,myLev );

    myLev = 0;
    if (srPins[(i+8)+(groundAt*16)] > myPos)
      myLev = 1;
    bitWrite(mySR2,i,myLev );

  }


  bitWrite(srCommon,groundAt,0);

*/
  //SR's are daisy chained.  The 3rd SR is the common (anode or cathode)
  //Update order if needed so the common is correct for your arrangement
   spi_transfer(srCommon);
   spi_transfer(mySR2);
   spi_transfer(mySR1);

  latchOn();
  latchOff();

}



int fadeStep = 5;
int fadeDelay = 10;
int currLED = 0;

void loop() {

  srPins[currLED] = 0;  
  currLED++;
  if (currLED > 127)
    currLED = 0;
    
 for( int i = 0 ; i < 255 ; i += fadeStep){
  srPins[currLED] = i;  
  delay(fadeDelay);
 }

}

void latchOn(){
  bitSet(PORTB,latchPinPORTB);
}

void latchOff(){
  bitClear(PORTB,latchPinPORTB);
}

void setupSPI(){
  byte clr;
  SPCR |= ( (1<<SPE) | (1<<MSTR) ); // enable SPI as master
  //SPCR |= ( (1<<SPR1) | (1<<SPR0) ); // set prescaler bits
  SPCR &= ~( (1<<SPR1) | (1<<SPR0) ); // clear prescaler bits
  clr=SPSR; // clear SPI status reg
  clr=SPDR; // clear SPI data reg
  SPSR |= (1<<SPI2X); // set prescaler bits
  //SPSR &= ~(1<<SPI2X); // clear prescaler bits

  delay(10);
}

byte spi_transfer(byte data)
{
  SPDR = data;                    // Start the transmission
  loop_until_bit_is_set(SPSR, SPIF);
  return SPDR;                    // return the received byte, we don't need that
}

void setup() {
  randomSeed(analogRead(0));
  Serial.begin(9600);
  pinMode(latchPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin, OUTPUT);

  digitalWrite(latchPin,LOW);
  digitalWrite(dataPin,LOW);
  digitalWrite(clockPin,LOW);

  setupSPI();
  Timer1.initialize(timerDelay); // Timer for updating pwm pins
  Timer1.attachInterrupt(iProcess);

}


« Last Edit: November 03, 2009, 11:37:59 am by marklar » Logged

Florida
Offline Offline
Sr. Member
****
Karma: 4
Posts: 431
hookedup!
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Due to multiplexing eight ways .. 64 levels of PWM didn't look so hot.  I updated to 16 16 from 64 8 in the code .. looks cleaner.

I'll post a fuller version with a color class and running off some patterns as I get time.

Logged

Pages: [1]   Go Up
Jump to: