Charlie
Plexing
ist eine Technik zur Ansteuerung von z.B. LEDs in einer Matrix.
Siehe dazu:
Mein WINAVR Programm für den Download: ATM328-SparkArray_8x7_V2.zip
http://www.elektronik-labor.de/AVR/Charlieplexing.html
Mir hat sich die Frage gestellt, wie man 8x7=56 LEDs nur mit 8
Steuerleitungen A-H darstellen/ansteuern will und Muss.
Der Trick heißt „Charlie-Plexing“.
das How to ?
Ein Pin hat bis zu 3(~4) Zustände:
Neben 0= "0" Volt und 1="5" Volt, gibt es nch den DRITTEN Zustand, "NC=Not Connected" oder sinngleich
Portpin=INPUT (ohne Pullup)"
1. Output: 0 (0V)
2. OutPut: 1 (~5V)
3. Input-mit Pullup: schwache_5V
(nicht relevant)
4. Input: Hochohmig=TRI-STATE
Hier wird es
Interessant!..
Betrachten wir die Methoden.
Die klassische Matrix !
Dies heisst: 5 +5 = 10 Leitungen erforderlich für 25
LEDs
Charlie-Plexing Matrix
Schon deutlich weniger Leitungen --> 5x4 LEDs
5 Leitungen für 20 LEDS. Aber
etwas verwirrend.
Aber wie geht das ???? Wie ist die Ansteuerung ?
Aus den 4 möglichen Zuständen lassen sich neue
Kombinationsmöglichkeiten ableiten.
Und diese lassen sich Kombinieren
P1:+, P2:-, P3:Tristate
P1:-, P2:+, P3:Tristate
P1:Tristate, P2:+, P3:-
P1:Tristate, P2:-, P3:+
P1:+, P2:Tristate, P3:-
P1:-, P2: Tristate, P3:+
6 Kombinationen mit Tristate, mit 3 Leitungen. Dazu kommen noch
„ohne Tri-state“.
Also müssten auch 6 LEDs mit 3 Leitungen steuerbar sein.
LEDs sind ja selbst Dioden, und somit in der Matrix definiert
ansprechbar.
Die „1“ ist das Reihen Selektier Bit mit +3.3V..5V
1. Die Spalte hat 7 Bits bei 8 Reihen.
2. Das 8te Bit selektiert die waagerechte Linie mit +5V
3. Da das Reihen Selektier-Bit sich durchschiebt, ist das Datenbyte(
7Bit ) an dieser Stelle zu Splitten, und die rechte Hälfte um 1 nach
rechts zu schieben .
4. Alles was NICHT leuchten soll, bekommt im Daten Direktion Register
eine 0=Input auf dieses Bit. Das dazugehörige PORT Bit = 0 = „Pullup
Off“
5. Alles was leuchten soll, bekommt im Daten Direction Register eine 1,
Aber im PORT Register wir an diesem BIT eine 0=0V geschrieben.
ERGO, Das Port bekommt immer eine 0 außer das Selektier Bit. -->
PORTD = (1<<u8NN);
//Globals
uint8_t gu8ParkFun[FONT_HEIGHT]; //8 7 Teilen,
8 Spalten, 1 Bit=selector
volatile uint32_t gu32_Ticks;
volatile uint8_t gu8StatusFlags=0;
volatile uint8_t gu8Spark8x7Flags=0;
//
6 Bits auf D
#define
A_F_DATA_MASK 0b00111111
#define
A_F_DATA_SHIFT_NR 2 //<<
#define
A_F_PORT PORTD
#define
A_F_DDR DDRD
#define
A_F_PORT_MASK 0b11111100
//
2 Bits auf B
#define
G_H_DATA_MASK 0b11000000
#define
G_H_DATA_SHIFT_NR 6
//>>
#define
G_H_PORT PORTB
#define
G_H_DDR DDRB
#define
G_H_PORT_MASK 0b00000011
--
Beispiel der FONT7x8.h Datei
ifndef
FONT7X8_H
#define FONT7X8_H
#define
FONT_CHAR_FIRST 32
#define
FONT_CHAR_LAST 126
#define
FONT_CHARS
95
#define
FONT_WIDTH 7
#define
FONT_HEIGHT
8
#define
FONT_SPACING
1
#include
<avr/pgmspace.h>
{
PROGMEM
static
const prog_char
cmaFont7x8[] = {
0,
0, 0,
0,
0, 0,
0, // ' '
32
0,
6, 95, 95,
6, 0,
0, // '!'
33
0,
7, 7,
0,
7, 7,
0, // '"' 34
20, 127, 127,
20, 127, 127, 20, // '#' 35
36, 46,
107, 107, 58, 18,
0, // '$' 36
70, 102,
48, 24, 12,
102,
98, // '%' 37
48,
122, 79,
93, 55, 122, 72,
// '&' 38
4,
7, 3,
0,
0, 0,
0, // '''
39
0,
28, 62, 99,
65, 0,
0, // '('
40
0,
65, 99, 62,
28, 0,
0, // ')'
41
......................................................
usw..
Noch sehr wichtig.Zur Ansteuerung benötigt man eine genaue
wie
gleichmäßige Zeitscheibe.
Das geht nur über den 8 Bit Timer.
Flackerfrei geht das nur über eine Geschwindigkeit von
~30*/Zeile/Sekunde
30*8 = 240 ist mindestens 240*/Sek. Oder schneller ! Ich wählte
977/Sek.
// In Main Intitialsierung
OCR0A = 127; // PWM=Halbe Helligkeit
TCCR0A = 0;
TIMSK0 = (1<<TOIE0) | (1<<OCIE0A);
TCCR0B = (1<<CS01)| (1<<CS00); // F_CPU/256/64= 977
ISR( TIMER0_OVF_vect )
{
ByteToMatrix(gu8RowNN);
gu8RowNN++;
gu8RowNN %= 8;
}; (dazu kommt noch ein PWM Abschaltinterrupt,
der die Helligkeit regelt.
ISR( TIMER0_COMPA_vect )
{
A_F_DDR &= ~A_F_PORT_MASK;
G_H_DDR &= ~G_H_PORT_MASK;
A_F_PORT &= ~A_F_PORT_MASK;
G_H_PORT &= ~G_H_PORT_MASK;
};
// ********************************
void ByteToMatrix( uint8_t u8Row )
// ********************************
{
uint8_t u8Split_Low_Data;
uint8_t u8Split_High_Data;
uint8_t u8SParkFun8x7Data;
uint8_t u8Low_0_Maske;
uint8_t u8High_0_Maske;
//Data sind schon "left flush"
u8Low_0_Maske = (1<<u8Row) - 1; //bsp3:
1000-"1"=00000111
// 11110111 & ~00000111 = 11110000
u8High_0_Maske = ~(1<<u8Row) & ~u8Low_0_Maske;
// +5V Selektiert Reihe, 0V = LED ON
u8Split_Low_Data = ((gu8ParkFun[u8Row]) >> 1) & u8Low_0_Maske;
u8Split_High_Data = gu8ParkFun[u8Row] & u8High_0_Maske;
u8SParkFun8x7Data = (u8Split_High_Data | u8Split_Low_Data) |
(1<<u8Row);
//lösche Port
A_F_DDR &= ~A_F_PORT_MASK;
G_H_DDR &= ~G_H_PORT_MASK;
A_F_PORT &= ~A_F_PORT_MASK;
G_H_PORT &= ~G_H_PORT_MASK;
if( u8Row < 6)
{
A_F_DDR |= (((u8SParkFun8x7Data & A_F_DATA_MASK) | (1<<u8Row)
) << A_F_DATA_SHIFT_NR) & A_F_PORT_MASK ;
G_H_DDR |= ((u8SParkFun8x7Data & G_H_DATA_MASK) >>
G_H_DATA_SHIFT_NR) & G_H_PORT_MASK;
A_F_PORT |= ((~A_F_DDR) | ((1<<u8Row) <<
A_F_DATA_SHIFT_NR)) & A_F_PORT_MASK;
G_H_PORT |= (~G_H_DDR) & G_H_PORT_MASK;
}
else // Row>=6
{
A_F_DDR |= ((u8SParkFun8x7Data & A_F_DATA_MASK) <<
A_F_DATA_SHIFT_NR) & A_F_PORT_MASK ;
G_H_DDR |= (((u8SParkFun8x7Data & G_H_DATA_MASK) |
(1<<u8Row)) >> G_H_DATA_SHIFT_NR) & G_H_PORT_MASK;
A_F_PORT |= (~A_F_DDR) & A_F_PORT_MASK;
G_H_PORT |= ((~G_H_DDR) | ((1<<u8Row) >>
G_H_DATA_SHIFT_NR)) & G_H_PORT_MASK;
};
// ********************************
void FetchZeichen(uint8_t u8Char)
// ********************************
{
uint8_t u8NN;
uint8_t u8aOrgField[FONT_WIDTH]; //7
for(u8NN=0; u8NN < 8; u8NN++)
{
//'A'=65 ('A'-32)*7
u8aOrgField[u8NN] = pgm_read_byte_near(cmaFont7x8 +
((u8Char - FONT_CHAR_FIRST) * FONT_WIDTH) + u8NN);
};
//lösche BIFfeld
for(u8NN=0;u8NN < FONT_HEIGHT; u8NN++)//8
{
gu8ParkFun[u8NN] = 0;
};
//Drehe Bitfeld nach Links und Spiegele
//Linksbündig
for(u8NN=0; u8NN < FONT_WIDTH; u8NN++) //7
{
if(u8aOrgField[u8NN] & 0x01) {gu8ParkFun[0] |=
(1<<(7-u8NN));};
if(u8aOrgField[u8NN] & 0x02) {gu8ParkFun[1] |=
(1<<(7-u8NN));};
if(u8aOrgField[u8NN] & 0x04) {gu8ParkFun[2] |=
(1<<(7-u8NN));};
if(u8aOrgField[u8NN] & 0x08) {gu8ParkFun[3] |=
(1<<(7-u8NN));};
if(u8aOrgField[u8NN] & 0x10) {gu8ParkFun[4] |=
(1<<(7-u8NN));};
if(u8aOrgField[u8NN] & 0x20) {gu8ParkFun[5] |=
(1<<(7-u8NN));};
if(u8aOrgField[u8NN] & 0x40) {gu8ParkFun[6] |=
(1<<(7-u8NN));};
};
gu8ParkFun[7] = 0;
};
//im header
#define TICK_EVENT 0x01
#define ZEICHEN_EVENT 0x02
#define ORDER_FLAG 0x04
#define TOGGLE_FLAG 0x08
//gu8StatusFlags
In Main gibt es diese Sequenz:
if( gu8StatusFlags & ZEICHEN_EVENT )
{
gu8StatusFlags &=
~ZEICHEN_EVENT;
FetchZeichen( 32 + u8XX
);
u8XX++;
u8XX %=
95;
};