Go Down

Topic: Charlieplexing 8 LEDs - trouble generalizing routine (Read 2398 times) previous topic - next topic

acegard

Hello everyone! I am using an Arduino Uno to drive 8 LEDs, Charplieplexed with four I/O pins, using the following schematic:
http://i.imgur.com/WTZrzFH.png (The image is far too large to post inline).

While I understand the theory behind Charlieplexing, I am running into trouble when creating my routine in code to be fast, generic, and able to turn on any combination of the eight LEDs and keep them lit using, using POV techniques obviously since it's impossible to constantly have lit any arbitrary configuration of Charlieplexed LEDs. For example, I might want to have LEDs A,C, and D lit but none of the others, or I may want to flash the sequence AB-CD-GH-EF repeatedly.

Few examples I have looked up cover code beyond the basics: putting every line of code in the main program loop to illustrate charlieplexing theory. This is not my problem. My problem is that I am having trouble finding a solution that is generic and persistent.

I think that the solution would probably contain two parts: a container that could be updated anywhere in the program to hold what LEDs are desired to be on, and a routine, perhaps in an ISR, that runs constantly to enable POV and updates the LEDs. I am getting hung up trying to wrap my head around this algorithm.

Here is the code I am working with now:
Code: [Select]
//Define the LEDs. The first nibble is the port direction register, the second is the actual port output
#define LED_A 0x31
#define LED_B 0x62
#define LED_C 0xC4
#define LED_D 0x98
#define LED_E 0x32
#define LED_F 0x64
#define LED_G 0xC8
#define LED_H 0x91

///////used by the SetOutputs function//////////////
byte leds[] = {LED_A,LED_B,LED_C,LED_D,
              LED_E,LED_F,LED_G,LED_H};
byte ledsEnabled = 0x00;
////////////////////////////////////////////////////


void setup()
{
  Serial.begin(9600);
  ledsEnabled = 0x0F;
}

void loop()
{
  SetOutputs();
}

void SetOutputs()
{
  PORTB &= 0x00;
  for(int i = 0; i < 8; i++)
  {
//      Serial.println(i);
//      Serial.println((ledsEnabled & (1<<i))>>i);
//      Serial.println();
   
    //check to see if the led is enabled
    if((ledsEnabled & (1<<i))>>i == 1)
    {
      WRITE_LED(leds[i]);
      delayMicroseconds(10);
    }
  }
}

void WRITE_LED(byte LED)
{
  DDRB &= 0x00;
  DDRB = (LED & 0xF0) >> 4;
  PORTB = (LED & 0x0F);
}




//void LED_ON(byte LED)
//{
//  DDRB &= 0x00;
//  DDRB = (LED & 0xF0) >> 4;
//  PORTB |= (LED & 0x0F);
//}
//void LED_OFF(byte LED)
//{
//  DDRB &= ~(LED & 0xF0) >> 4;
//  PORTB &= ~(LED & 0x0F);
//}


It works nicely for what I have now: lighting the first column (A-D) of LEDs, but if I need to do anything fancier, say, alternating the first and second column of LEDs, it is not up to the task. That is what I need help with.

I am using direct port access to speed up and synchronize the outputs (especially the direction registers to enable quick tri-state switching which enables the charlieplexing technique) and to keep the program size down - I plan to implement this on an ATTiny85, but an Uno is easier to prototype with.

Any help is welcomed and appreciated :) I'm always able to find great answers on this forum!

Thanks!

Paul__B

My first observation is that your circuit is wrong.  Four lines can Charlieplex twelve LEDs; your arrangement is a degenerate version, missing four of the LEDs.

Perhaps more to the point, your resistors are connected wrongly.  Only one LED terminal goes to a resistor, choose either cathode or anode, the opposite pin of the LED; the one not connected through a resistor; will be the "strobe" that is powered whilst however many (up to three) of the LEDs connected to that pin are lit at a time.  Four wires - four strobe phases.

If you need to drive more than 7 mA per LED (i.e., total of 20 mA if three LEDs simultaneously lit, or 10 mA per LED if you are still using only 8 LEDs); there is a way of adding NPN transistors as emitter followers so that you can drive up to 20 mA per LED, and the transistor collectors can be connected to your unregulated supply (any value up to the transistor spec.) so that the current is not supplied by your regulator.

Think in terms of four strobe phases; generally driving each of the anode commons HIGH in turn (with the resistors in series with the cathodes) and pulling whatever 2 or 3 cathodes you need to activate, LOW for that strobe time.

Note that each of the four strobes must persist for the specified time regardless of whether any LEDs are lit, otherwise the brightness will vary depending on how many LEDs are lit.


acegard

I only need to drive 8 LEDS; however as I plan to put this on an ATTiny85 I will only really have 4 pins to work with. Hence this technique.

I am having trouble understanding what you're talking about when you mention my incorrect resistor configuration - do you think you could post a quick schematic to illustrate better what you're talking about? Right now, I only have one LED illuminated at a time, and 2 resistors are connected for any configuration of single LEDs. Each resistor is 150 Ohms, resulting in an LED current of 16mA, well within the boundaries of the outputs, so I don't need to use NPN transistors. I won't need that much current; I don't need them to be particularly bright.

Think in terms of four strobe phases; generally driving each of the anode commons HIGH in turn (with the resistors in series with the cathodes) and pulling whatever 2 or 3 cathodes you need to activate, LOW for that strobe time.

Note that each of the four strobes must persist for the specified time regardless of whether any LEDs are lit, otherwise the brightness will vary depending on how many LEDs are lit.



I know this, I'm just having trouble implementing it.

PaulRB

#3
Sep 22, 2014, 11:17 pm Last Edit: Sep 22, 2014, 11:47 pm by PaulRB Reason: 1
Hi, how about this (compiled but untested):

Code: [Select]


//pin that should be HIGH to light each of the 8 leds
byte highPin[] = {1,2,3,4,2,3,4,1};

//pin that should be LOW to light each of the 8 leds
byte lowPin[] = {2,3,4,1,1,2,3,4};

//holds state of each led
boolean ledIsOn[8];

//led currently in action
byte currLed = 0;

//time last led was switched on
unsigned long updateTime;

void setup() {
 //set up the on/off status of each led
 ledIsOn[0] = true;
 ledIsOn[1] = false;
 ledIsOn[2] = true;
 ledIsOn[3] = true;
 ledIsOn[4] = false;
 ledIsOn[5] = true;
 ledIsOn[6] = false;
 ledIsOn[7] = true;
}

void loop() {
 updateLeds();
}

void updateLeds() {
 
 //is it time for next change of led?
 if (updateTime - millis() > 2) {
   
   //switch off the previous led
   if (ledIsOn[currLed]) {
     pinMode(highPin[currLed], INPUT);
     pinMode(lowPin[currLed], INPUT);
   }
   
   //move to next led
   currLed++;
   if (currLed == 8) currLed = 0;
   
   //switch on next led
   if (ledIsOn[currLed]) {
     pinMode(highPin[currLed], OUTPUT);
     digitalWrite(highPin[currLed], HIGH);
     pinMode(lowPin[currLed], OUTPUT);
     digitalWrite(lowPin[currLed], LOW);
   }

   //record time of this update
   updateTime = millis();
 }

}



Its not massively efficient because it uses a 1:8 multiplex ratio. In theory two leds could be lit simultaneously without overloading any pin, achieving a 1:4 multiplex, which would be brighter. For example led A could be lit at the same time as G, because to light A, you would set pin 1 low and pin 2 high. At the same time, if pin 3 was high and 4 was low, G would light. The sketch would then be:

Code: [Select]

//pin that should be HIGH to light each of the 8 leds
byte highPin[] = {1,2,3,4,2,3,4,1};

//pin that should be LOW to light each of the 8 leds
byte lowPin[] = {2,3,4,1,1,2,3,4};

//led that could be lit at same time as leds 0, 1, 2 & 3
byte otherLed[] = {6,7,4,5};

//holds state of each led
boolean ledIsOn[8];

//led currently in action
byte currLed = 0;

//time last led was switched on
unsigned long updateTime;

void setup() {
  //set up the on/off status of each led
  ledIsOn[0] = true;
  ledIsOn[1] = false;
  ledIsOn[2] = true;
  ledIsOn[3] = true;
  ledIsOn[4] = false;
  ledIsOn[5] = true;
  ledIsOn[6] = false;
  ledIsOn[7] = true;
}

void loop() {
  updateLeds();
}

void updateLeds() {
 
  //is it time for next change of led?
  if (updateTime - millis() > 2) {
   
    //switch off the previous led
    if (ledIsOn[currLed]) {
      pinMode(highPin[currLed], INPUT);
      pinMode(lowPin[currLed], INPUT);
    }
   
    byte currLed2 = otherLed[currLed];
   
    //switch off the other previous led
    if (ledIsOn[currLed2]) {
      pinMode(highPin[currLed2], INPUT);
      pinMode(lowPin[currLed2], INPUT);
    }
   
    //move to next led
    currLed++;
    if (currLed == 4) currLed = 0;
   
    //switch on next led
    if (ledIsOn[currLed]) {
      pinMode(highPin[currLed], OUTPUT);
      digitalWrite(highPin[currLed], HIGH);
      pinMode(lowPin[currLed], OUTPUT);
      digitalWrite(lowPin[currLed], LOW);
    }

    currLed2 = otherLed[currLed];
   
    //switch on the other next led
    if (ledIsOn[currLed2]) {
      pinMode(highPin[currLed2], OUTPUT);
      digitalWrite(highPin[currLed2], HIGH);
      pinMode(lowPin[currLed2], OUTPUT);
      digitalWrite(lowPin[currLed2], LOW);
    }

    //record time of this update
    updateTime = millis();
  }

}


Note that these pin numbers just reflect your diagram. In practice you would not use pin 1 because it is used for serial comms on most Arduinos.

BTW, I don't think direct port manipulation will gain you much while multiplexing only 8 leds, and it makes the sketch difficult to read.

Paul

Paul__B


I am having trouble understanding what you're talking about when you mention my incorrect resistor configuration - do you think you could post a quick schematic to illustrate better what you're talking about?


Well I don't blame you.  Most of the diagrams I can find with Google - the vast majority show the resistors incorrectly including those with the buffer transistors, which vividly illustrates how readily patently bad information spreads.


Right now, I only have one LED illuminated at a time, and 2 resistors are connected for any configuration of single LEDs. Each resistor is 150 Ohms, resulting in an LED current of 16mA, well within the boundaries of the outputs, so I don't need to use NPN transistors. I won't need that much current; I don't need them to be particularly bright.

Well, if you only ever want one LED (statically) illuminated at once, then the placing of the resistors does not matter, but commonly you actually do want to multiplex it so that a number of LEDs indicate (visually) at once, in which case you usually want uniform brightness and you do not want more than one LED to share a resistor.

Of course, I agree with PaulRB's comment that there is little point in direct port manipulation when multiplexing - any speed increase is immaterial and you are unlikely to improve the code efficiency or conciseness of the inbuilt optimisers.


PaulRB

@Paul__B, how is your resistor configuration better than the OP's? To my mind it is just... different.

Paul__B


@Paul__B, how is your resistor configuration better than the OP's? To my mind it is just... different.

It is explained in my second last paragraph - it matters when you are driving more than one LED at once (and I do mean at once - while you are performing a given phase in the multiplex).  You do not want two LEDs to be sharing a resistor as you will get a different brightness than if only one LED is driven at once.  It gets worse when you wish to drive even more LEDs at once.

Make sure you actually examine the diagram and understand its implications.  :D

acegard

Thanks guys for the replies :)

First, though you're right to say that in this case the speed gain from direct port manipulation is not appreciable, it still has merit, in that directly setting the port direction registers ensures that the tri-state outputs are changed exactly simultaneously. While the slight effects of this could be unnoticeable, I am trying to follow best-practice, as well as use this as a learning tool to get myself more acquainted with the low-level side of these processors. I will continue to do this, but while I am getting help I will keep it down to that I have already done so it's easier to understand :)  (I also come from a background of almost straight assembly programming in a couple of my classes so it's readable to me. Though do correct me if I am way off base.)

@Paul__B, (So many Pauls helping me!) thank you for the schematic. As a student, my life is busy so I honestly don't have time to re-work my circuit right now. I will do that as soon as possible though, because you're right - being able to light more than one LED at once is the correct way to do it, and would be the "best-practice."

@PaulRB, thank you for your code help! I tested it and it works like a charm, right down to lighting more than one LED at once (if it's enabled). I implemented a routine like yours in my own code, and now my main loop looks like this to flash two patterns:
Code: [Select]

  if(millis() - patternSwitch > patternInterval)
  {
    patternSwitch = millis();
    ledsEnabled = ~ledsEnabled;
  }
  SetOutputs();


It annoys me to work with "if(millis() - patternSwitch > patternInterval)" for "reasons," though, so I want to try triggering SetOutputs() in an interrupt routine. I tried once before with this program, with poor results. I have used the Timer1 library with great success in the past, but again, since this will go on an ATTiny in the future I need to keep the program size down as much as possible so I'd rather not import a bunch of libraries. I suppose the code subforum would be the better place to ask about interrupts like that, though.

Thank you all for your help! :)

Paul__B


First, though you're right to say that in this case the speed gain from direct port manipulation is not appreciable, it still has merit, in that directly setting the port direction registers ensures that the tri-state outputs are changed exactly simultaneously.  While the slight effects of this could be unnoticeable, I am trying to follow best-practice, as well as use this as a learning tool to get myself more acquainted with the low-level side of these processors.

Actually, you could be wrong on this - if you write symmetric code, each transition in the  tri-state outputs will not be simultaneous, but will have the same interval since the previous update, so achieve the same effect.

I was having a think on this before - if you are controlling the ports byte-wide, then the logic flow is along the lines:
Code: [Select]
clear DDR; // blank display
Advance strobe bit; // next strobe
Write strobe byte to data register; // Update strobe line
Select LED data for this strobe;
Write LED data OR strobe byte to DDR; // Activate corresponding outputs
Exit to main loop;

That code sequence is designed to avoid "phantom" illumination of LEDs.  The reason why it is immaterial (and therefore "best-practice" is not) to be concerned with the precise timing, is that these manipulations involve only microseconds, whilst the strobe period is at the minimum, a few milliseconds.


(I also come from a background of almost straight assembly programming in a couple of my classes so it's readable to me. Though do correct me if I am way off base.)

I spent some years in the 1980s working almost purely in assembler (6809) - macro assembler.  In general, I prefer it, however in the Arduino IDE is it pretty much gibberish and just as well to avoid on point of clarity.  :smiley-eek:


Being able to light more than one LED at once is the correct way to do it, and would be the "best-practice."

The only sensible way in fact.  Multiplexing is only used as a design convenience; with LEDs at least, it has no visual advantage over continuous illumination with the same average current.


It annoys me to work with "if(millis() - patternSwitch > patternInterval)" for "reasons," though, so I want to try triggering SetOutputs() in an interrupt routine. I tried once before with this program, with poor results.

I disagree.  Using interrupts is in most cases totally unnecessary; I feel they should only be used where there is a need.  That code structure is the essential basis of implementing multiple tasks; you will need it to do any serious work.  I simply hide it inside a function (or macro).

For example:
Code: [Select]
// Blink without "delay()" - multi!

const int led1Pin =  13;    // LED pin number
const int led2Pin =  10;
const int led3Pin =  11;
int led1State = LOW;        // initialise the LED
int led2State = LOW;
int led3State = LOW;
unsigned long count1 = 0;   // will store last time LED was updated
unsigned long count2 = 0;
unsigned long count3 = 0;

// Have we completed the specified interval since last confirmed event?
// "marker" chooses which counter to check
boolean timeout(unsigned long *marker, unsigned long interval) {
  if (millis() - *marker >= interval) {
    *marker += interval;    // move on ready for next interval
    return true;       
  }
  else return false;
}

// Deal with a button read; true if button pressed and debounced is a new event
// Uses reading of button input, debounce store, state store and debounce interval.
boolean butndown(char button, unsigned long *marker, char *butnstate, unsigned long interval) {
  switch (*butnstate) {               // Odd states if was pressed, >= 2 if debounce in progress
  case 0: // Button up so far,
    if (button == HIGH) return false; // Nothing happening!
    else {
      *butnstate = 2;                 // record that is now pressed
      *marker = millis();             // note when was pressed
      return false;                   // and move on
    }

  case 1: // Button down so far,
    if (button == LOW) return false; // Nothing happening!
    else {
      *butnstate = 3;                 // record that is now released
      *marker = millis();             // note when was released
      return false;                   // and move on
    }

  case 2: // Button was up, now down.
    if (button == HIGH) {
      *butnstate = 0;                 // no, not debounced; revert the state
      return false;                   // False alarm!
    }
    else {
      if (millis() - *marker >= interval) {
        *butnstate = 1;               // jackpot!  update the state
        return true;                  // because we have the desired event!
      }
      else
        return false;                 // not done yet; just move on
    }

  case 3: // Button was down, now up.
    if (button == LOW) {
      *butnstate = 1;                 // no, not debounced; revert the state
      return false;                   // False alarm!
    }
    else {
      if (millis() - *marker >= interval) {
        *butnstate = 0;               // Debounced; update the state
        return false;                 // but it is not the event we want
      }
      else
        return false;                 // not done yet; just move on
    }
  default:                            // Error; recover anyway
    { 
      *butnstate = 0;
      return false;                   // Definitely false!
    }
  }
}

void setup() {
  pinMode(led1Pin, OUTPUT);     
  pinMode(led2Pin, OUTPUT);     
  pinMode(led3Pin, OUTPUT);     
}

void loop() {
  // Act if the latter time (ms) has now passed on this particular counter,
  if (timeout(&count1, 500UL )) {
    if (led1State == LOW) {
      led1State = HIGH;
    }
    else {
      led1State = LOW;
    }
    digitalWrite(led1Pin, led1State);
  }

  if (timeout(&count2, 300UL )) {
    if (led2State == LOW) {
      led2State = HIGH;
    }
    else {
      led2State = LOW;
    }
    digitalWrite(led2Pin, led2State);
  }

  if (timeout(&count3, 77UL )) {
    if (led3State == LOW) {
      led3State = HIGH;
    }
    else {
      led3State = LOW;
    }
    digitalWrite(led3Pin, led3State);
  }
}



I'd rather not import a bunch of libraries. I suppose the code subforum would be the better place to ask about interrupts like that, though.

Libraries are there for you if you cannot be bothered writing your own optimised code.  It's fine to use them as an aid, if you need them.  But you have indicated no need for interrupts in your work so far.  {It may amuse you to code with them, but ...}

Go Up