Charlieplexing Basics on an ATTiny13

Go To Last Post
13 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I plan to use an ATTiny13 for a traffic light with pedestrian crossing lights and a railroad crossing.  PB0 as an interrupt for sensor to activate crossing lights.  So 12 leds (6 for traffic lights, 4 for pedestrian crossing, 2 for railroad crossing).  Charlieplexing gives me 12 LEDs for pins 1, 2, 3, 4. 

 

I first wanted to illuminate each LED to make sure I had all configured correctly, but came to an issue when I tested the first three LEDs.  After I had this figured out I would put together loops to sequence the lights and flicker as needed.

 

The first three LEDs share a common High, PB1.  Low is PB2, PB3, PB4.  Each pin has a resistor, same LEDs.

 

Unfortunately I have not gotten past the first three LEDs. 

 

I expected them to illuminate one at a time, one second apart. What I get is Nred bright, Ngreen dim,  Ngreen bright.

 

#include <avr/io.h>
#include <util/delay.h>

#define DELAYflashing 1000   

 

int main(void)
{
    /* Test sequence
        light each led one at a time to verify its address and functionality
    */
        
    while (1)
    {

 

    DDRB = 0b00000110; PORTB |= (1<<PB1); PORTB &= ~(1<<PB2); // Nred
    _delay_ms(DELAYflashing);
    DDRB = 0b00001010; PORTB |= (1<<PB1); PORTB &= ~(1<<PB3); // Ngreen    
    _delay_ms(DELAYflashing);
    DDRB = 0b00010010; PORTB |= (1<<PB1); PORTB &= ~(1<<PB4); // Nyellow
    _delay_ms(DELAYflashing);

   }
    
}

 

Any suggestions?

 

Last Edited: Wed. Oct 19, 2016 - 06:53 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well it's exceedingly difficult to help you with a problem like this without seeing a circuit diagram.  The program is simple enough that it's unlikely to be the source of the problem, especially since nothing changes in your loop except DDRB.  After the first line within the loop PORTB is always 0b00000010.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Well, Ports 1,2,3,4 are always outputs : you should try : DDRB=0x1E outside the while (1)  loop

 

Rezer hints that PORTB |= (1<<PB1);

should be replaced by

PORTB = (1<<PB1) |  (1<<PB2) |(1<<PB3) |  (1<<PB4)  ; // PB1 is activated, each LED is off (both pins are high) == 0x1E

 

 

  (PORTB  should not "remember" the value it got for the other LEDS)

or, perhaps better

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

"After the first line within the loop PORTB is always 0b00000010."

 

Perhaps my code is off?  This is my first work with tri-state.

 

In the third line,

 

DDRB = 0b00001010; PORTB |= (1<<PB1); PORTB &= ~(1<<PB3); // Ngreen    

 

 My intent is to have a line of code for each light.  First set DDRB I/O. Input for Z, output for High/Low.  Then set the high and low pins.

 

In this line, DDRB has PB1 and PB3 set to output, the rest for input.  Then PB1 is set high, PB3 set low.   I would expect high impedance Z on all the pins except PB1 and PB3

 

 

Last Edited: Wed. Oct 19, 2016 - 08:28 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

PORTB |= somevalue means you OR somevalue to the inbitial value of PORTB?

PORTB = somevalue means PORTB has the value you want, without extra calculations

 

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks.  Helped greatly.  With only 1k to work with, want as little calculation as necessary.

 

I changed to this and it worked:

DDRB = 0b00000110; PORTB = (1<<PB1)|(0<<PB2); // Nred

_delay_ms(DELAYflashing);

DDRB = 0b00001010; PORTB = (1<<PB1)|(0<<PB3); // Ngreen

_delay_ms(DELAYflashing);

DDRB = 0b00010010; PORTB = (1<<PB1)|(0<<PB4); // Nyellow

_delay_ms(DELAYflashing);

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

aneastonfarmer wrote:

"After the first line within the loop PORTB is always 0b00000010."

 

Perhaps my code is off?  This is my first work with tri-state.

 

Follow through your code on paper and you'll see what I mean.  There's nothing "wrong" with the code, the PORTB stuff just doesn't do anything since every line is setting it to the same value, i.e. set bit PB1 of PORTB and clear bit PB2/PB3/PB4.  Since none of those bits are ever set, it's always 0b00000010, which is fine for what you're trying to do, just superfluous.

 

As for your revised code working, I don't believe you.  I mean, I'll buy that it works now and not before, but that code change isn't the reason unless I'm missing something.  It's functionally identical, and a bitwise or with 0 bitshifted by any amount doesn't do anything, nearly the same as clearing a bit that's already 0.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Any suggestions?

 Use meaningful symbols or datastructures:

 

#define CATHODE0 0
#define CATHODE1 1
#define CATHODE2 2
#define CATHODE3 3

#define ANODE0 0
#define ANODE1 1
#define ANODE2 2
#define ANODE3 3


// Each LED has an anode output as a 1 and a cathode output as 0.
//  You can never have the anode and the cathode for an LED be on the same pins.
#define NREDDATA ((1<<ANODE0)|(0<CATHODE1))
// using p1 as cathode, therefore it can't be an anode
#define NYELDATA ((1<<ANODE2)|(0<CATHODE1))
#define NGRNDATA ((1<<ANODE3)|(0<CATHODE1))

//  Both the Anode and the Cathode for an LED have to be outputs, but otherwise the DDR
//    register should mention the same bits as the DATA register.
#define NREDDDR ((1<<ANODE0)|1<<CATHODE1))
#define NYELDDR ((1<<ANODE2)|1<<CATHODE1))
#define NGRNDDR ((1<<ANODE3)|1<<CATHODE1))


    DDRB = NREDDDR;  PORTB = NREDDATA;
    _delay_ms(...)

 

yeah, there's a lot of redundant text in there (0<<CATHODEn !!), but it doesn't lead to actual code, and it makes things a lot clearer (IMO.)

You can use an array and/or structs to hold values eventually, so that it will look pretty:

      DDRB = lights[direction].red.ddr;
      PORTB = lights[direction].red.port;

 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Not sure why one code worked and not the other.  They seem the same to me too.  The only HW change was popping chips to program...

 

 Thanks westfw for the suggestions They are really good.  I like clean, readable code, which mine is not, yet. 

 

Quick question on clearing bits.  When charlieplexing all 12 combinations for four pins (my example only has the first three), the PB may be high or low, so setting it low directly, whether it is or not, seems to make sense.  I would think you simply set it low rather than test and set if it is low.

 

Is this good practice? 

 

 

 

 

Last Edited: Thu. Oct 20, 2016 - 03:02 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Yes, there's little use in checking the current port value if you already know the desired value and don't care if it's changing or not.  It's only worth checking first if you plan on doing something with the current value other than overwrite it.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Go get an avr with more pins.

Charlieplexing is adding unneded complexity to the software, wiring, and keeping everything apart.

Surely size of the uC is not going to be an issue for a traffic light?

Good old M8 (M328, whatever) have plenty of pins to ease your wiring and software development.

Doing magic with a USD 7 Logic Analyser: https://www.avrfreaks.net/comment/2421756#comment-2421756

Bunch of old projects with AVR's: http://www.hoevendesign.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I see no problem in doing this, take pen and paper out, and find out how each LED can be on and all other LED's off, (and how all can be off :) ).

When you have that I would have 2 registers where each bit indicate on/off of a LED.

Control those in main, and have a timer ISR that run a state machine that control the LED's.

 

 And since it's all on same port the code will be relative small (the ISR will be about 250 byte so 25% but I guess that the rest of the code take even less), all the rest will then be like you had 12 IO's.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I did some 4x5 charlieplexing for an ATtiny11 some time ago, but that code was in assembler.  Let me know if you'd like it posted.

I decided to "address" the LEDs in a normal x,y 4*5 matrix, and "adjusted" those numbers to account for the charlieplexing before fiddling with the actual bits.  In C, I probably would have used a lookup table.   My example program would flash A, B, C, with chase sequences in between each letter.  The code ended up 268 bytes...  The "meat" was:

 

;;; displayone
;;; light one led for the configured delay.
;;; enter with row and col set appropriately
displayone:
        rcall translaterow
        rcall translatecol
        sub temp, temp          ; assume all pins are inputs
        out ddrb, temp          ; turn off all LEDs
        or temp, cathode        ; cathode is an output
        or temp, anode          ; anode is an output
        out portb, anode        ; send outputs - selects a particular LED
        out ddrb, temp          ; set pin directions- actually turn it on
        rjmp delay              ; delay and return