Please visit the homepage for location and information on open hours


Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Charlieplexing
09-17-2015, 07:11 PM, (This post was last modified: 09-17-2015, 07:15 PM by Brendan.)
#1
Charlieplexing
It's no surprise: I like blinky lights. In fact, I can't see much need for an electronic project if it doesn't have LEDs in it!

That being said, there are times when I want / need to control more LEDs than I have available I/O lines for on my micro-controller. I've implemented my Binary Clock project using two I/O expander chips. These work great; you simply give them power, and I2C communications...and your two I2C lines can effectively become 16 I/O lines (per expander chip). However, I don't always have the real-estate on a PCB for a port expander, or, frankly, want to pay for the Port Expander if I can work around it. Fortunately, by combining the properties of LEDs (they only bias or "work" when powered in a certain direction) with the I/O functionality of modern M/Cs, I'm able to control individually many more LEDs than I have I/O lines for! This type of LED wiring is called, "Charlieplexing".

An LED only needs two wires...Power and Ground. When forward biased (turned on) the LED will light; when reverse-biased (turned off) the LED will not light. If we take two I/O lines from an M/C chip, and connect them to an LED, we can turn the LED "on" by setting the Anode-I/O "High" and the Cathode-I/O "Low". We could connect two LEDs in parallel to the same I/O lines...but turn the second LED around...so setting PIN 1 "High" and Pin 2 "Low" would turn on LED #1, but would reverse-bias LED #2 (not turn it on). We could then switch PIN 1 to be "Low" and PIN 2 to be "High" and LED #1 would go out (now being reverse-biased), and LED #2 would light.

We can take this further...since I/O pins on modern M/C chips have 3 states: Output High, Output Low, and High-Impedance "Input" mode. When an PIN is set to "input" it is neither high nor low...it acts like an open circuit.

So, using the three states of I/O pins (High, Low, Input) we can drive many more LEDs than PINS. The formula is: (n-squared) - n. So, with 3 I/O pins, we can drive (9) - 3 or 6 LEDs. With 20 I/O pins, we can drive ( 400 ) - 20 or 380 LEDs!

Attached is an example schematic of 4 I/O lines being used to drive ( 16 ) - 4 = 12 LEDs. Below the schematic is a "Truth Table" which shows all the pin settings necessary to drive the 12 LEDs individually.

Attached is a picture of an 8-Pin PIC Microcontroller (PIC 12F1840) driving 12 LEDs. The PIC is using one pin for the status LED (blinking at 1Hz) and 1 pin for the push-button input...leaving 4 pins to handle Charlieplexing. I have laid out the LEDs so they are numbered from the top, left to right...so upper-left is #1, upper-right is #3, lower-right is #12. This made the wiring a little easier.

This microcontroller is also fast enough (32Mhz) that it can turn on, and then off, an LED and then move onto another and turn it on-and-off...this happens fast enough that human eyes can't descern the power cycles, and to us, it appears more than 1 LED is on at a time. In fact, this circuit can light up all 12 LEDs at the "same time".

I'll bring this PCB in if you'd like to take a look. The Push-button will interrupt the various displays, and allow you to cycle through each LED individually...if you leave the circuit alone for 10 seconds, it'll go back to its random display.

If you have a project, and you're looking for a way to light up more LEDs (say for a status display) than you have I/O...or if you're just looking to save the I/O space, and you have room in your code...this might be something you'd like to play with.

               

Here is the C-Code for this project...I've tried to clean it up and make it readable...but let me know if you have any questions.

This was written in MPLAB X. A free IDE for working with Microchip microcontrollers.

Normally, I would have broken this up into multiple files...but for the ease of posting / sharing, I've included everything in one file.

Brendan

Code:
/*
* File:   CharliePlexMain.c
* Author: brendan
*
* Created on September 15, 2015
*/


#define _XTAL_FREQ 32000000
#include <stdio.h>
#include <stdlib.h>
#include "serial.h"
#include <xc.h>


// #pragma config statements should precede project file includes.
// Since I'm using a bootloader; these config statemens don't matter...
// The config bytes are set in the bootloader...I have to really try
// to change the config settings...and it generally isn't necessary
// these are just FYI

// CONFIG1
#pragma config FOSC = INTOSC    // Oscillator Selection (HS Oscillator, High-speed crystal/resonator connected between OSC1 and OSC2 pins)
#pragma config WDTE = OFF       // Watchdog Timer Enable (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable (PWRT disabled)
#pragma config MCLRE = OFF      // MCLR Pin Function Select (MCLR/VPP pin function is digital input)
#pragma config CP = OFF         // Flash Program Memory Code Protection (Program memory code protection is disabled)
#pragma config CPD = OFF        // Data Memory Code Protection (Data memory code protection is disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable (Brown-out Reset disabled)
#pragma config CLKOUTEN = OFF   // Clock Out Enable (CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin)
#pragma config IESO = OFF       // Internal/External Switchover (Internal/External Switchover mode is disabled)
#pragma config FCMEN = OFF      // Fail-Safe Clock Monitor Enable (Fail-Safe Clock Monitor is disabled)

// CONFIG2
#pragma config WRT = OFF        // Flash Memory Self-Write Protection (Write protection off)
#pragma config PLLEN = ON       // PLL Enable (4x PLL enabled)
#pragma config STVREN = ON      // Stack Overflow/Underflow Reset Enable (Stack Overflow or Underflow will cause a Reset)
#pragma config BORV = LO        // Brown-out Reset Voltage Selection (Brown-out Reset Voltage (Vbor), low trip point selected.)
#pragma config LVP = ON         // Low-Voltage Programming Enable (Low-voltage programming enabled)

#define LED LATAbits.LATA0      // P1B PWM output
#define PB PORTAbits.RA3        // Push Button

#define L1_set LATAbits.LATA1       // Line 1
#define L1_tris TRISAbits.TRISA1

#define L2_set LATAbits.LATA2       // Line 2
#define L2_tris TRISAbits.TRISA2

#define L3_set LATAbits.LATA4       // Line 3
#define L3_tris TRISAbits.TRISA4

#define L4_set LATAbits.LATA5       // Line 4
#define L4_tris TRISAbits.TRISA5

#define testbit(var, bit)   ((var) & (1L <<(bit)))
#define setbit(var, bit)    ((var) |= (1L << (bit)))
#define clrbit(var, bit)    ((var) &= ~(1L << (bit)))

// Lay out where LEDs are in the array, so I can call them by color, row, column, etc.
#define red 0b001000000100
#define green 0b010000000010
#define blue 0b100000000001
#define yellow 0b000010101000
#define white  0b000101010000
#define row1 0b000000000111
#define row2 0b000000111000
#define row3 0b000111000000
#define row4 0b111000000000
#define column1 0b001001001001
#define column2 0b010010010010
#define column3 0b100100100100

// Declare my subroutines here

void setup();
void interrupt timeout();

void one();
void two();
void three();
void four();
void five();
void six();
void seven();
void eight();
void nine();
void ten();
void eleven();
void twelve();
void zero();

void manual();
void random();
void rows();
void columns();
void police();
void colors();
void cyclethru();
void allon();

// define my variables...these are global, can be used anywhere in the program

unsigned long millis; // keep track of elapsed milliseconds...50 days at 32-bit
unsigned int millis2, LEDs;  // for LED blinking timeout

void main() {


// Set up the system
    setup();
    LEDs = 0;   // All off...

// Do forever...
    while (1){

        cyclethru();    // One at a time, slow
        allon();        // Light 'em all up
        LEDs = 0;       // All off
        colors();       // Cycle through R G B Y W
        LEDs = 0;       // All off
        police();       // Pull over!
        rows();         // Rows 1 - 4
        columns();      // Columns 1 - 3
        random();       // Blinky!
  
    }   // End of While

}   // End of Main


void setup(){

// Setup

    // CPU Speed
    OSCCON        = 0b11110000; // Set up the oscillator for 32 MHZ 1, 1110, 0, 00


    // Ports
    TRISA=0b00111110;       // set up 0 as output (This will change like crazy in the code...)
    PORTA=0b00000000;


    // Serial     Disabled since using SEROUT line for LED
//    SPBRG = 51;     // 9600 baud @ 32MHz
//    TXSTA = 0b10100010;     // setup USART transmit
//    RCSTA = 0b10010000;     // Enable serial port, but no receive


    // Interrupts

//   INTCONbits.IOCIE = 1;       // Enable interrupt on change
//   IOCANbits.IOCAN3 = 1;        // Enable interrupt on negative for RA3
// not using Interrupt on Change for the Push Button...because staying in that
// manual code prevents other interrupts from working.


    
    
    // Timers...
// Add a 1 millisecond timer here to service stuff...
//Timer0 Registers Prescaler= 64 - TMR0 Preset = 131 - Freq = 1000.00 Hz - Period = 0.001000 seconds

    OPTION_REGbits.T0CS = 0;  // bit 5  TMR0 Clock Source Select bit...0 = Internal Clock (CLKO) 1 = Transition on T0CKI pin
    OPTION_REGbits.T0SE = 0;  // bit 4 TMR0 Source Edge Select bit 0 = low/high 1 = high/low
    OPTION_REGbits.PSA = 0;   // bit 3  Prescaler Assignment bit...0 = Prescaler is assigned to the Timer0
    OPTION_REGbits.PS2 = 1;   // bits 2-0  PS2:PS0: Prescaler Rate Select bits
    OPTION_REGbits.PS1 = 0;
    OPTION_REGbits.PS0 = 1;
    TMR0 = 131;             // preset for timer register
    INTCONbits.TMR0IE = 1;  // Enable timer 0 Interupt
    INTCONbits.TMR0IF = 0;  // clear Timer 0 flag...just in case
    INTCONbits.GIE = 1; // Enable Global interrupts
//   INTCONbits.PEIE = 1;    // Enable periphial interrupts (is this needed for TMR 0? No, it's not)

// PWM Configuration

PR2 = 0xFF ; // Needs to be FF for 1.95kHz frequency at 32 MHz
T2CON = 0b00000110 ; // 7=0, 6-3 = Post scaler, 2=timer on, 1-0 = pre-scaler
CCP1CON = 0b00001100 ;
// PWM Enabled on P1B out but P1A is low...I don't want this...
// need to use PWM steering to steer over to P1B...that's the LED pin....
PSTR1CONbits.STR1SYNC = 0;
PSTR1CONbits.STR1B = 1;     // Steer PWM to P1B
PSTR1CONbits.STR1A = 0;     // Leave P1A as a port pin


}   // end of setup

void interrupt timeout(){

    if (INTCONbits.TMR0IF == 1) {       // Tmr 0 has overflowed...millisecond
        TMR0 = 131; // reset the preload
        millis ++;  // Increment milliseconds
        millis2 ++; // Increment LED timer

//        if (millis2 < 900) {
//            LED = 0;      
//        }
//        else if (millis2 < 1000){
//            LED = 1;
//        }
//        else millis2 = 0;

// Heartbeat LED tasks
        
        int dc = millis2 / 4;       // fade up over a second
        if (dc > 250){
            dc = 500 - dc;          // fade down over a second
        }
        CCPR1L = dc;                // change PWM to dc value
        if (millis2 == 2000){
            millis2 = 0;
        }

        INTCONbits.TMR0IF = 0;  // Clear the flag.

// check the LEDs bits for which lights to turn on...

        if (LEDs == 0) {
            zero();
            return;
        }

        if testbit(LEDs,0) {
            one();
        __delay_us (70);

        }
        if testbit(LEDs,1) {
            two();
            __delay_us (70);
        }
        if testbit(LEDs,2) {
            three();
            __delay_us (70);
        }
        if testbit(LEDs,3) {
            four();
           __delay_us (70);
        }
        if testbit (LEDs,4) {
            five();
            __delay_us (70);
        }
        if testbit (LEDs,5) {
            six();
             __delay_us (70);
        }
        if testbit (LEDs,6) {
            seven();
           __delay_us (70);
        }
        if testbit (LEDs,7) {
            eight();
           __delay_us (70);
        }
        if testbit (LEDs,8) {
            nine();
            __delay_us (70);
        }
        if testbit (LEDs,9) {
            ten();
            __delay_us (70);
        }
        if testbit (LEDs,10) {
            eleven();
            __delay_us (70);
        }
        if testbit (LEDs,11) {
            twelve();
            __delay_us (70);
        }
        zero();
        
    

    }   // end of TMR0 tasks

//   else if (IOCAFbits.IOCAF3 == 1){    // Button is pressed...

//       __delay_ms(300);
//       if (PB == 0){
//           manual();
//       }
//       IOCANbits.IOCAN3 = 1;   // reenable interrupts
//       IOCAFbits.IOCAF3 = 0;   // clear the flag

//   }   // End of Interrupt on change tasks...




}       // End of interrupt

void one(){     // high 1 low 2

        L3_tris = 1;    // input
        L4_tris = 1;    // Input
        L1_tris = 0;    // output
        L1_set = 1;     // high
        L2_tris = 0;    // output
        L2_set = 0;     // Low


}

void two(){     // high 1 low 3

        L3_tris = 0;    // output
        L4_tris = 1;    // Input
        L1_tris = 0;    // output
        L2_tris = 1;    // input

        L1_set = 1;
        L3_set = 0;



}

void three(){   // high 2 low 4

        L3_tris = 1;    // input
        L4_tris = 0;    // out
        L1_tris = 1;    // input
        L2_tris = 0;    // output

        L2_set = 1;
        L4_set = 0;

}

void four(){    // low 1 high 2
        L3_tris = 1;    // input
        L4_tris = 1;    // Input
        L1_tris = 0;    // output
        L2_tris = 0;    // output

        L1_set = 0;
        L2_set = 1;


}

void five(){   // low 1 high 3
        L3_tris = 0;    // output
        L4_tris = 1;    // Input
        L1_tris = 0;    // output
        L2_tris = 1;    // input

        L1_set = 0;
        L3_set = 1;

}

void six(){     // low 2 high 4

        L3_tris = 1;    // input
        L4_tris = 0;    // output
        L1_tris = 1;    // input
        L2_tris = 0;    // output

        L2_set = 0;
        L4_set = 1;

}

void seven(){   // high 1 low 4


        L3_tris = 1;    // input
        L4_tris = 0;    // output
        L1_tris = 0;    // output
        L2_tris = 1;    // input

        L1_set = 1;
        L4_set = 0;


}

void eight(){   // high 2 low 3


        L3_tris = 0;    // output
        L4_tris = 1;    // Input
        L1_tris = 1;    // input
        L2_tris = 0;    // output

        L2_set = 1;
        L3_set = 0;


}

void nine(){   // high 3 low 4


        L3_tris = 0;    // output
        L4_tris = 0;    // output
        L1_tris = 1;    // input
        L2_tris = 1;    // input

        L3_set = 1;
        L4_set = 0;

}

void ten(){   // low 1 high 4

        L3_tris = 1;    // input
        L4_tris = 0;    // output
        L1_tris = 0;    // output
        L2_tris = 1;    // input

        L1_set = 0;
        L4_set = 1;


}

void eleven(){  // low 2 high 3


        L3_tris = 0;    // output
        L4_tris = 1;    // Input
        L1_tris = 1;    // input
        L2_tris = 0;    // output

        L2_set = 0;
        L3_set = 1;

}

void twelve(){ // low 3 high 4


        L3_tris = 0;    // output
        L4_tris = 0;    // output
        L1_tris = 1;    // input
        L2_tris = 1;    // input

        L3_set = 0;
        L4_set = 1;

}

void zero(){

        L3_tris = 1;    // input
        L4_tris = 1;    // Input
        L1_tris = 1;    // input
        L2_tris = 1;    // input


}


void manual (){

   IOCANbits.IOCAN3 = 0;   // disable interrupts
   LEDs = 0;
   unsigned long exitTime = millis + 10000;
   while (millis < exitTime){
       if (PB == 0){
           __delay_ms(200);
           if (PB == 0) {
               if (LEDs == 0){
                   LEDs = 1;
               }
               else {
                   LEDs *= 2;
                   if (LEDs > 2048){
                       LEDs = 0;
                   }
               }
               exitTime = millis + 10000;   // reset the countdown
           }
       }
   }
}

void random(){
// Random


    LEDs = 0;

    for (int x = 0; x < 1000; x++){
        unsigned int rnum = rand();
        if (rnum % 12 == 0){
            if testbit (LEDs, 11) {
                clrbit (LEDs, 11);
            }
            else setbit (LEDs, 11);

        }
        else if (rnum % 11 == 0){


            if testbit (LEDs, 10) {
                clrbit (LEDs, 10);
            }
            else setbit (LEDs, 10);
        }
        else if (rnum % 10 == 0){
            if testbit (LEDs, 9) {
                clrbit (LEDs, 9);
            }
            else setbit (LEDs, 9);
        }
        else if (rnum % 9 == 0){
            if testbit (LEDs, 8) {
                clrbit (LEDs, 8);
            }
            else setbit (LEDs, 8);

        }
        else if (rnum % 8 == 0){
           if testbit (LEDs, 7) {
                clrbit (LEDs, 7);
            }
            else setbit (LEDs, 7);

        }
    else if (rnum % 7 == 0){
           if testbit (LEDs, 6) {
                clrbit (LEDs, 6);
            }
            else setbit (LEDs, 6);

        }
        else if (rnum % 6 == 0){
           if testbit (LEDs, 5) {
                clrbit (LEDs, 5);
            }
            else setbit (LEDs, 5);

        }
            else if (rnum % 5 == 0){
           if testbit (LEDs, 4) {
                clrbit (LEDs, 4);
            }
            else setbit (LEDs, 4);

        }
            else if (rnum % 4 == 0){
           if testbit (LEDs, 3) {
                clrbit (LEDs, 3);
            }
            else setbit (LEDs, 3);

        }
            else if (rnum % 3 == 0){
           if testbit (LEDs, 2) {
                clrbit (LEDs, 2);
            }
            else setbit (LEDs, 2);

        }
            else if (rnum % 2 == 0){
           if testbit (LEDs, 1) {
                clrbit (LEDs, 1);
            }
            else setbit (LEDs, 1);

        }
            else {
                if testbit (LEDs, 0) {
                clrbit (LEDs, 0);
            }
                else setbit (LEDs, 0);
            }

            __delay_ms(100);
  if (PB == 0) manual();
        }

}

void rows(){

    // Rows

    LEDs = 0;
    LEDs = row1;
    __delay_ms(500);
      if (PB == 0) manual();
        LEDs = 0;
    __delay_ms(100);
    LEDs = row2;
    __delay_ms(500);
      if (PB == 0) manual();
        LEDs = 0;
    __delay_ms(100);
        LEDs = row3;
    __delay_ms(500);
      if (PB == 0) manual();
        LEDs = 0;
    __delay_ms(100);
        LEDs = row4;
    __delay_ms(500);
      if (PB == 0) manual();
    LEDs = 0;

}

void columns(){
// Columns

    LEDs = column1;
    __delay_ms(500);
      if (PB == 0) manual();
    LEDs = 0;
    __delay_ms(100);

      LEDs = column2;
    __delay_ms(500);
      if (PB == 0) manual();
        LEDs = 0;
    __delay_ms(100);
  LEDs = column3;
    __delay_ms(500);
      if (PB == 0) manual();



}

void police(){

    // Police
    LEDs = 0;
    for (int x = 0; x < 10; x++){
        for (int y = 0; y < 6; y++){
            LEDs = red | white;
            __delay_ms(30);
            LEDs = white;
            __delay_ms(30);
  if (PB == 0) manual();
        }
         for (int y = 0; y < 6; y++){
            LEDs = yellow | blue;
            __delay_ms(30);
            LEDs &= yellow;
            __delay_ms(30);
  if (PB == 0) manual();
        }
    }
}

void colors(){
    // Colors
    LEDs = 0;
    LEDs = red;
    __delay_ms(500);
      if (PB == 0) manual();
       LEDs = blue;
    __delay_ms(500);
      if (PB == 0) manual();
       LEDs = green;
    __delay_ms(500);
      if (PB == 0) manual();
       LEDs = yellow;
    __delay_ms(500);
      if (PB == 0) manual();
       LEDs = white;
    __delay_ms(500);
      if (PB == 0) manual();

}

void cyclethru () {

    LEDs = 1;
    while (LEDs <= 2048) {
        __delay_ms(500);
        LEDs *= 2;
        if (PB == 0) manual();
    }


}

void allon() {

     unsigned long timeout = millis + 2000;    // Another way to "delay"

    while (millis < timeout){
        LEDs = 0b111111111111;  // All LEDs on...
    }
}
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)