Forum: Mikrocontroller und Digitale Elektronik Idee: Charlieplexing - Intelligenter Steuerungsalgorithmus?


von Paul H. (powl)


Lesenswert?

Da ich mich darüber aufgeregt hab, dass ich bei meiner Binär-Uhr, an der 
ich 17 Pins für LEDs benötige, genau einen Pin zu wenig frei hab, hab 
ich im Internet mal nach Charlieplexing gegoogelt und wenigstens 
folgende Seite gefunden:

http://www.josepino.com/pic_projects/index?how_control_leds.jpc

Ich frag mich nun ob es irgendwie möglich ist dafür einen einigermaßen 
intelligenten Algorithmus in C zu schreiben. Hab mich vorhin nur mal 
kurz mit 3-Pin Charlieplexing für 6 LEDs beschäftigt und bin zum 
Ergebnis gekommen, dass es da verschiedene LED- Paare gibt, die jeweils 
gleichzeitig leuchten können, somit wär es möglich alle LEDs in nur 3 
Charlieplex-schritten zum leuchten zu bringen.

Natürlich besteht auch die Möglichkeit, die LEDs einfach alle einzeln 
hintereinander aufleuchten zu lassen, allerdings erhöht sich dabei der 
Spitzenstrom durch einen PortPin weil eine der 6 LEDs jeweils nur 1/6 
der Zykluszeit an sein kann. Mit intelligenterem Verfahren, d.h. man 
findet raus welche Paare gleichzeitig angesteuert werden können. 
Anstatt, dass nun zwei LEDs mit dem 6-fachen Standardstrom 
hintereinander aufleuchten, können nun zwei LEDs mit dem 3-fachen 
Standardstrom gleichzeitig und doppelt so lange aufleuchten.

Das Problem ist halt, es kann nicht jedes X-beliebige Paar aufleuchten. 
Man beachte dieses Bild:
http://www.josepino.com/pic_projects/control_leds/led-table.gif

gleichzeitig gehen immer nur
A & D
A & E
C & E
C & B
B & F
F & D

wenn man nun eine Speicherstelle hat in der die Bits die leuchtenden 
LEDs repräsentieren, dann müsste man erstmal die leuchtenden Paare 
rauspicken bzw das ganze irgendwie aufteilen.

Hier leuchten z.B. alle Paare:

ABCDEF
111111

A   E
   D F
 BC

A  D
 B   F
  C E

Einzelne LEDs können ja ausgeblendet werden, ich schätze jedoch bei 4 
oder 5 Portpins wird das ganze noch wesentlich komplizierter.

Meint ihr das ist hinzubekommen, so dass es im µC noch mit ausreichender 
Geschwindigkeit läuft? LEDs ein und ausschalten ist ja relativ 
langweilig, eine 8-Bit software PWM wär dazu auch noch ganz nett.

Ist alles nur eine Idee aber vielleicht lohnt es sich, dieser mal 
nachzugehen.

Vorteile:
- Weniger Spitzenstrom => Mehr LEDs treibbar bzw. heller
- Eventuell sogar schneller

Nachteile:
- Aufwändiger Algorithmus

lg PoWl

von Hagen R. (hagen)


Lesenswert?

schau mal hier rein:

Beitrag "Glühwürmchen in Rotkohlglas gefangen"
Beitrag "10 Duo LEDs mit ATTiny15 über 5 Pins steuern"

im ersten Projekt werden an 4 Pin also 3*4=12 LEDs getrieben, im 2. 
Projekt an 5 Pins = 4*5=20 LEDs.

Bei dem Glühwürmchenprojekt wurde in WinAVR GCC und die zeitkritischen 
ISR in Assembler programmiert. Mit 8 Mhz Takt und Prescaler 64 bekommt 
man bei 12 LEDs eine Wiederholrate von 122 Hz. Alle PWM Berechnungen und 
das "Nachladen" der PWM Muster (Wavetable) findet im Himtergrund in den 
ISRs statt und die Auslastung der MCU beträgt dabei maximal 1.48%.

Beide Projekte benutzen Charlieplexing. Für die Anzeige in deinem 
Projekt kannst du auch mit kürzeren Dutycycles noch gute Ergebnisse im 
Leuchten der LEDs erzielen.

Du kannst im Glühwürmchenprojekt in der TOV ISR natürlich das Laden der 
PWM Muster für die LEDs aus der Wavetable rausbauen, sind die ersten 
drei ASM Blöcke im Code. Stattdessen baust du im SRAM eine Tabelle für 
deine 12/20/30 LEDs in denen der aktuelle Dutycycle als Byte 
abgespeichert wird. Das wird den Code dann sogar einfacher und schneller 
machen, so ca. 0.8% Rechenleistung der MCU wäre dann noch nötig für die 
PWM.

>Nachteile:
>- Aufwändiger Algorithmus

nicht grundsätzlich. Der Algo. ist ansich ziemlich einfach, wenn man ihn 
erstmal durchschaut hat. Dessen Umsetzung würde ich aber nicht pur in C 
realisieren da du mit ca. 50% Unoptimierungsoverhead im Vergleich zu 
handmade Assembler erwarten musst (so war es bei meiner ersten reinen C 
Version des aktuellen Glühwürmchencodes). Am Ende benötigte die Routine 
in Assembler mit 2 ISRs um die 200 Taktzyklen.

Achso: beide Projekt können die Helligkeit der LEDs auch einstellen. 
Falls dies für dein Projekt nicht erforderlich ist so vereinfacht sich 
die Sache nochmals gewaltig.

Du könntest auch alle möglichen Leuchtmuster der LEDs offline 
vorausberechnen und im FLASH als Tabelle abspeichern. Dann nur noch die 
Timer ISR programmieren die für das aktuelle Zeichen diese Muster 
nacheinander aus dem FLASH lädt und die LEDs am Port ansteuert.

Gruß Hagen

von Paul H. (powl)


Lesenswert?

Stimmt, eigentlich muss man ja nur in einer Tabelle die LED-"paare" und 
die jeweilige Kombination zum Einstellen der I/O Ports einspeichern. Bei 
meinem Dreifach-Charlieplexing wären das.

A & D
B & F
C & E

Bei einem Vierfach-Charlieplexing könnten sogar drei LEDs gleichzeitig 
leuchten.

Eine Frage bevor ich mir das anschau noch: Wird das bei dir "berechnet" 
welche Paare jeweils zusammen leuchten können? Oder leuchtet sowieso 
immer nur eins?

Oder wird mit einer Tabelle gearbeitet so wie ich das grad beschrieben 
hab?

lg PoWl

von Hagen R. (hagen)


Lesenswert?

>Eine Frage bevor ich mir das anschau noch: Wird das bei dir "berechnet"
>welche Paare jeweils zusammen leuchten können? Oder leuchtet sowieso
>immer nur eins?

>Oder wird mit einer Tabelle gearbeitet so wie ich das grad beschrieben
>hab?

Du kannst es berechnen oder als Tabelle machen, es hängt eigentlich nur 
davon ab an welchen Pins und Ports du die LEDs angeschlossen hast.

Angenommen du benutzt nur PORTB und alle PINs aufsteigend beginnend bei 
PB0 bis PB?. Dann kann man das super einfach berechnen, denn wenn PB0 
auf Ausgang/High ist dann können die anderen Pins PB1 bis BP? nur auf 
Ausgang/Low oder High-Z/Eingang geschaltet sein, jenachdem ob die LEDs 
die von PB0 mit iherer Anode hin zu den restlichen Pins PB1 bis PB? 
leuchten sollen oder nicht.

Schau dir den 2. Link oben an da fiondest du einen Schaltplan.
Zeichne dir auf einem Blatt zb. 5 waagerechte Linien. Dann bename diese 
Linien links von PB0 bis PB4 durch. Nun zeichnest du von PB0 Linie zu 
allen anderen Linien 4 Dioden mit Anode an PB0 Line ein. Dann mit PB1 
Linie das gleiche Spiel, von dieser 4 Dioden nach PB0,PB2,PB3,PB4 
einzeichnen, jeweils Anode an PB1 Linie. Und das machst du so weiter bis 
du bei PB4 fetig bist. Und schwups hast du 5*(5-1) = 20 LEDs 
eingezeichnet.

Wenn nun PB0 auf Ausgang/High ist dann kann ein Strom durch die Dioden 
nach PB1,PB2,PB3,PB4 fließen. Jenachdem ob diese Pins nun auf 
Ausgang/Low oder Eingang/High-Z geschaltet sind leuchten diese LEDs auf.

Bei 5 Pins und 20 LEDs hast du also 5 Unterteilungen=Zeilen mit jeweils 
4 LEDs die gleichzeitig leuchten können. Nach 5 Multiplexingschritten 
ist ein kompletter Durchlauf fertig. Heist das in diesem Fall die LEDs 
mit 20% Duty angesteuert werden können. Bei 20mA pro Pin also bei einer 
LED ein Dauerstrom von 4mA, und wenn alle 4 gleichzeitig leuchten sollen 
dann 1mA Effektivstrom im Dauerbetrieb. Die meisten LEDs leuchten dann 
aber noch ordentlich.

Bei so einer Anordnung kann man natürlich alles sehr einfach berechnen, 
siehe 2. Projekt in dem die Zeile=Pin der grade treibt in einem Register 
gespeichert wurde und in jedem Schritt einfach geshiftet/rotiert wird. 
Dieses Zeilenregister wird auch als AND-Maske benutzt um das DDRB 
Register korrekt anzusteueren.

In beiden Projekten benutze ich eine kleine Tabelle im FLASH die die 
anzusteuerenden Pins enthält. Damit kann man in Grenzen die Zuordnung 
der LEDs zu einem "Index" festlegen und verändern, also die Reihenfolge 
mit der die LEDs angesteuert werden. Das geht natürlich nur Zeilenweise 
umzusortieren. Der zweite Vorteil dieser Tabelle ist das man nun Pins an 
einem PORT oder mehren PORTs benutzen kann die nicht sequentiell 
aufeinanderfolgend sind. Zb. im Glühwürmchenprojekt habe ich 
PB0,PB1,PB2,PB5 benutzt, also nicht PB4.

Gruß Hagen

von Hagen R. (hagen)


Lesenswert?

Du hast also folgendes (wir bleiben man bei obigen Beispiel mit PORTB 
und PB0-PB4).

1.) Zeilenregister = 0x01 und dieser Wert landet immer in PORTB
2.) Spaltenmaskeregister = xx or Zeilenregister und dere landet immer in 
DDRB

1.) Phase, Zeile PB0 treibt 4 LEDs nach PB1-PB4

PB0 auf Ausgang/Highlevel
PB1-PB4 im DDRB Register jenachdem ob sie leuchten sollen = 1 oder nicht 
= 0.

PORTB = (1 << PB0)
DDRB  = (1 << PB0) | (? << PB1) | (? << PB2) | (? << PB3) | (? << PB4)

2.) Phase, Zeile PB1 treibt 4 LEDs nach PB0,PB2-PB4

PORTB =              (1 << PB1)
DDRB  = (? << PB0) | (1 << PB1) | (? << PB2) | (? << PB3) | (? << PB4)

3.) Phase, Zeile PB2 treibt 4 LEDs nach PB0,PB1,PB3,PB4

PORTB =                           (1 << PB2)
DDRB  = (? << PB0) | (? << PB1) | (1 << PB2) | (? << PB3) | (? << PB4)

4.) Phase, Zeile PB3 treibt 4 LEDs nach PB0,PB1,PB2,PB4

PORTB =                                        (1 << PB3)
DDRB  = (? << PB0) | (? << PB1) | (? << PB2) | (1 << PB3) | (? << PB4)

5.) Phase, Zeile PB4 treibt 4 LEDs nach PB0,PB1,PB2,PB3

PORTB =                                                     (1 << PB4)
DDRB  = (? << PB0) | (? << PB1) | (? << PB2) | (? << PB3) | (1 << PB4)

erkennst du das Muster ?

Kurz also so

1.) in jeder Phase rotiere einen Wert von 1 nach 2 nach 4 nach 8 und 
schreibe es in PORTB
2.) berechne DDRB Wert und OR-verknüpfe PORTB-Wert mit diesem und 
schreibe ihn in DDRB

Nachteile:

- an den 5 Pins müssen die LED Vorwiderstände
- somit fließt zb. bei PB0 = on ein Strom über diesen Vorwiderstand über 
maximal 4 LEDs und zurück über 4 identische Vorwiderstände in die 4 
anderen Pins. Der Strom wird begrenzt durch den Vorwiderstand an PB0 und 
teilt sich durch 4 auf.
- die Helligkeit der LEDs sinkt also immer dramatischer je mehr Pins man 
benutzen möchte
- auf Grund dessen das wir mit 3 Pin Zuständen arbeiten müssen -> 
Ausgang/High + Ausgang/Low und Eingang/High-Z kann man nicht so ohne 
weiteres mit externen Treibertransistoren/MOSFETs arbeiten.
- sollte zb. bei PB0=on nicht alle 4 LEDs leuchten so fließt ein 
größerer Einzelstrom über diejenigen LEDs die leuchten sollen in der 
aktuellen Phase, ergo bei einer LED fließt der 4 fache Strom im 
Vergleich wenn alle 4 LEDs leuchten. Man müsste dies kompensieren indem 
man die Leuchtzeit an die Anzahl der leuchtenden LEDs anpasst. Leuchten 
alle 4 LEDs in einer Phase so  ist die Leuchtzeit maximal lang, leuchtet 
nur einer der 4 LEDs so sollte die Leuchtdauer nur noch 1/4'tel betragen

Gruß Hagen

von Paul H. (powl)


Lesenswert?

Stimmt, das ist gut.

Zu dem Stromproblem: Das liegt ja aber nur darin begründet, dass ein 
gemeinsamer Vorwiderstand für eine variable Anzahl an LEDs (variablem 
Strom) verwendet wird. Entsprechend ist auch der Spannungsabfall immer 
unterschiedlich. Das ganze per Software zu kompensieren ist mir in 
meinem Stadium noch etwas zu kompliziert ;-) Wie wäre es, wenn man jeder 
LED einen eigenen Vorwiderstand verpasst? Das ist zwar nicht besonders 
elegant, dürfte das Problem aber lösen.

Noch eine Allgemeine Frage: Wenn ich an PB0 Low habe, an PB2 High und 
PB1 als Eingang schalte. Würde nicht theoretisch ein Strom über die 
beiden, nun in Reihe geschalteten LEDs zwischen PB0 und PB2 fließen?

lg PoWl

von Hagen R. (hagen)


Lesenswert?

>Wie wäre es, wenn man jeder
>LED einen eigenen Vorwiderstand verpasst? Das ist zwar nicht besonders
>elegant, dürfte das Problem aber lösen.

Nein hilft nicht sonderlich, denn der Ausgangs-Pin ist der einzigste Pin 
der treibt und dessen Strom ist im AVR auf 20mA begrenzt (bzw. sollte 
begrenzt werden). Du müsstest dann schon den Vorwiderstand so hoch 
machen das nur maximal 1mA fließen kann, wenn wir bei obigen Beispiel 
bleiben.

>Noch eine Allgemeine Frage: Wenn ich an PB0 Low habe, an PB2 High und
>PB1 als Eingang schalte. Würde nicht theoretisch ein Strom über die
>beiden, nun in Reihe geschalteten LEDs zwischen PB0 und PB2 fließen?

Jain ;) Es gewinnt die Diode zwischen PB2 und PB0 und leuchtet. Die 
Dioden zwischen PB2-PB1 und PB1-PB0 sind in Serie, haben doppelt so hohe 
Durchbruchspannung und benötigen den doppelten Strom im Vergleich zur 
Diode zwischen PB2-PB0. Sie leuchten also nicht da Dioden quasi den 
Strom "aufsichziehen".

Es ist ansich ja kein Problem die Leuchtdauer innerhalb einer 
Multiplexingphase zu regeln. Wenn man die ISRs in meinem 
Glühwürmchenprojekt umbaut dann hat man eine PWM=Helligkeit pro LED die 
man von 0 bis 256 einstellen kann. Nun unterteilt man diese PWM in 4 
Bereiche, man kann also die Leuchtstärke der einzelnen LEDs nur von 0 
bis 63 einstellen und hat noch 4 weitere PWM Schritte zur Verfügung um 
die Anzhal der gleichzeitg leuchtenen LEDs ausgleichen zu können. Diese 
Unterteilung ist recht simpel, man multipliziert den Helligkeitwert der 
zwischen 0 bis 63 liegen kann einfach mit der Anzahl (0-3) der gerade 
leuchtenden LEDs und subrahiert das von 255 und schups hat man den 
korregierten PWM Timerwert um ein gleichmäßiges leuchten zu bekommen. 
Somit kannst du jede einzenle LED noch in iherer Helligkeit in 64 
Schritten einstellen, sinnvoll nutzbar dürften wohl dann 32 Schritte 
sein (logarithmisches Helligkeitsempfinden unserer Augen).

Ich empfehle aber es erstmal in der Praxis auszuprobieren, denn du wirst 
feststellen das selbst mit der einfachen Methode (ohne Korrektur) und 
entsprechenden Vorwiderständen du denoch ein gleichmäßiges Leuchten 
hinbekommst.

Gruß Hagen

von Paul H. (powl)


Lesenswert?

Ja, dennoch verliere ich dadurch an PWM-Auflösung, die gerade bei den 
unteren Helligkeitswerten ziemlich wichtig ist wegen der 
Empfindlichkeitskennlinie des Auges.

Wieso gerade 1mA? Wenn der Port 20mA treiben kann und jeweils maximal 4 
LEDs gleichzeitig an sind dann gibt das bei mir 5mA :-)

Ich bin schon dabei mir auf meinem Steckboard einen kleinen Test 
aufzubauen.

lg PoWl

von Hagen R. (hagen)


Lesenswert?

>Wieso gerade 1mA? Wenn der Port 20mA treiben kann und jeweils maximal 4
>LEDs gleichzeitig an sind dann gibt das bei mir 5mA :-)

korrekt, aber diese 4 LEDs sind nur 1/5'tel der Zeit wirklich on, da wir 
ja 20 LEDs multiplexen in 5 Phasen a 4 LEDs. Ergo: 5mA / 5 = 1mA 
Effektivstrom. Die maximale Helligkeit der so multiplexten LEDs ist 
identisch zu 1mA Dauerstrom.

Ich habe schon einigemale dieses Verfahren zum Treiben verschiedenster 
LED-Gimmecks benutzt, siehe die beiden Links oben. Bisher habe ich keine 
Unterscheide in den Helligkeiten pro Column bemerkt, egal ob nun 1,2,3,4 
LEDs on waren. Deswegen meinte ich ja das du für dein Projekt es einfach 
mal ausprobieren solltest. Ich denke ja das du garkeine PWM für die 
individuelle Helligkeitregelung der LEDs benötigst. Dh. mit der 
Korrektur pro Column über die Anzahl der ON-LEDs hast du pro Phase 4 PWM 
Stufen * 5 Phasen = 20 Timerzyklen um alle 20 LEDs ein/ausschalten zu 
können und das mit gleichen Helligkeiten.

Gruß Hagen

von Paul H. (powl)


Lesenswert?

ach so meintest du das ;-) ok ich probiere mal und melde michw enn ich 
fertig bin. morgen schau ich mir auch dein projekt genauer an

von Paul H. (powl)


Lesenswert?

So, ich habe mir hier alles nochmal durchgelesen. Also es wird wohl so 
laufen, dass ich da nicht viel berechnen kann und einfach aus einer 
Tabelle das jeweilige Ansteuermuster rausfischen muss.

Beispiel: 4fach Charlieplexing

Dass ich einen der 4 Pins auf Masse treibe und dann jeweils noch 3 
andere LEDs habe, die ich damit zum leuchten bringen kann, ist klar. 
Allerdings brauche ich eine Routine, die die entsprechenden Bits der 
Speicherstelle, die angibt, welche LEDs grade leuchten, den 
entsprechenden Phasen zuteilt.

Bzw. wenn ich da einfach eine 1 durch den Port hindurchshifte, muss ich 
mir die entsprechenden Bits der Speicherstelle rauspicken.

Wenn ich den Schaltplan mal nach dem Schema von dir aufmale und von 
links nach rechts mit ABCD... die LEDs durchnummeriere komme ich auf 
folgende Tabelle:

IO1  IO2  IO3  IO4      LEDs:
 1    0    0    0        A C E
 0    1    0    0        B G I
 0    0    1    0        D H K
 0    0    0    1        F J L

In der Speicherstelle werden die einzelnen Bits den jeweiligen Lichtern 
von links nach rechts zugeordnet:

ABCDEFGHIJKL

Jedoch muss ich nun die jeweiligen Leuchtbits auf die einzelnen Phasen 
aufteilen:

     ABCDEFGHIJKL
1.   A C E
2.    B    G I
3.      D   H  K
4.        F   J L

Wenn ein IO-Pin auf Vcc liegt, dann repräsentieren die anderen Pins, die 
auf Masse liegen, die LEDs die leuchten, und die, die Hochohmig 
geschaltet sind, diejenigen, die nicht leuchten.

IO1  IO2  IO3  IO4
 -    A    C    E
 B    -    G    I
 D    H    -    K
 F    J    L    -

Ob ich die PORT und DDR register Werte vorraus- oder on-the-fly berechne 
ist ja wurscht. Es bietet sich an sie vorraus zu berechnen um 
zwischendurch Rechenleistung zu sparen.

Was bleibt mir aber anderes übrig die einzelnen Leuchtbits auf die 
entsprechenden Phasen "von Hand" zu verteilen? Die Muster sind ja immer 
unterschiedlich. Entweder viele Ifs nach dem Motto:
1
// wenn A -> 1 dann IO2 -> 1
2
if(bit_is_set(leuchtbits, 1)) { Phase1_DDR |= (1 << 2) }
3
// wenn C -> 1 dann IO3 -> 1
4
if(bit_is_set(leuchtbits, 3)) { Phase1_DDR |= (1 << 3) }
5
// wenn E -> 1 dann IO4 -> 1
6
if(bit_is_set(leuchtbits, 5)) { Phase1_DDR |= (1 << 4) }
...usw...

lg PoWl

von Hagen R. (hagen)


Lesenswert?

Schau dir mal den Glühwürmchensource an, in FireFly.h findest du
1
// LED Rows, Pins connected to LEDs
2
#define R0  (1 << PB0)
3
#define R1  (1 << PB1)
4
#define R2  (1 << PB2)
5
#define R3  (1 << PB5)
6
7
prog_uint8_t ddr_data[16] = {
8
    R0, R0^R1, R0^R2, R0^R3,
9
    R1, R1^R0, R1^R2, R1^R3,
10
    R2, R2^R0, R2^R1, R2^R3,
11
    R3, R3^R0, R3^R1, R3^R2};

Das ist für 12 LEDs an 4 Pins. ddr_data[] enthält die Definitionen 
welche Pins zu steuern sind. Der erste Wert (1. Spalte mit R0, R1 usw.) 
ist der Wert den du direkt in PORTB setzt. Die nachfolgenden 3 Werte 
sind für DDRB und beziehen sich auf jeweils eine LED.

In Phase 1. also PORTB = R0 und DDRB = R0^R1 or R0^R2 or R0^R3 wenn alle 
3 LEDs leuchten sollen.

Gruß Hagen

von Paul H. (powl)


Lesenswert?

ja klar aber darum ging es mir nicht, sondern, dass in einer phase nicht 
A, B C, in der nächsten nicht D, E, F Leuchten, sondern dass die Lichter 
bunt auf die Phasen aufgeteilt sind. Wenn ich nun in einer 
Speicherstelle die Bitinformationen der Reihenfolge nach ABCDEFG.. drin 
stehen hab, dann muss ich die einzelnen Bits ihrer eigenen Phase 
zuordnen. Und welcher Phase die an welche Position zugeordnet werden 
müssen, das muss ich ja im vorraus bestimmen. Entweder ich mach das mit 
ganz vielen IFs, oder ich lege mir eine tabelle an in der drin steht 
welches bit zu welcher phase und an welche position gehört.

lg PoWl

von Hagen R. (hagen)


Lesenswert?

Achso, das ist einfach. Du benutzt ein Array das als Mapping zwischen 
logischer LED zu Index in Timer-PWM-Phase dient. Dieses Array benutzt du 
beim Setzen der PWM bzw. ON/OFF einer logischen LED.

Beispiel:
1
uint8_t LED_PWM[12];
2
uint8_t LED_IDX[12] = {0,3,5,7,1,4,8...}
3
4
main() {
5
6
  LED_PWM[LED_IDX[8]] = 123;
7
}

Gruß hagen

von Paul H. (powl)


Lesenswert?

So, das hier ist mal mein test-code:
1
#include <avr/io.h>
2
#include <util/delay.h>
3
4
uint16_t lights;
5
6
uint8_t phase_DDR[4];
7
8
uint8_t map[] = {
9
  0, 1,
10
  1, 0,
11
  0, 2,
12
  2, 0,
13
  0, 3,
14
  3, 0,
15
  1, 2,
16
  2, 1,
17
  1, 3,
18
  3, 1,
19
  2, 3,
20
  3, 2
21
};
22
23
int main(void)
24
{
25
  uint16_t counter;
26
  uint8_t phase;
27
  uint8_t port;
28
29
  uint8_t a;
30
  uint8_t b;
31
32
  while(1)
33
  {
34
    phase_DDR[0] = 0;
35
    phase_DDR[1] = 0;
36
    phase_DDR[2] = 0;
37
    phase_DDR[3] = 0;
38
39
40
    a = 0;
41
    for(b=0; b<12; b++)
42
    {
43
      if(lights & (1 << b))
44
      {
45
        phase_DDR[map[a]] |= (1 << map[a+1]);
46
      }
47
48
      a += 2;
49
    }
50
51
52
    phase = 0;
53
    port = 1;
54
55
    for(counter=0; counter<100; counter++)
56
    {
57
      PORTA = port;
58
      DDRA = phase_DDR[phase] | port;
59
60
      if(phase < 3)
61
      {
62
        phase++;
63
        port <<= 1;
64
      }
65
      else
66
      {
67
        phase = 0;
68
        port = 1;
69
      }
70
71
      _delay_ms(1);
72
    }
73
74
    lights++;
75
76
    if(lights == 0b0001000000000000)
77
    {
78
      lights = 0;
79
    }
80
  }
81
}

Scheint zu funktionieren, die Leuchtstärke der LEDs ändert sich wirklich 
kaum, wenn ich die Vorwiderstände noch kleiner mach (sind im moment 220 
Ohm bei 5V) dann müsste der Effekt durch die Empfindlichkeitskennlinie 
des Auges noch kleiner werden..

Als nächstes kommt dann noch die PWM und ein Timer ins Spiel :-) ich 
freue mich schon darauf über das Thema später einen Artikel auf meiner 
Homepage zu verfassen^^ finde das interessant. Danke für die Hilfe!

Btw: Ich habs erst mit phase_DDR[map[a++]] |= (1 << map[a++]); versucht, 
hat jedoch nicht funktioniert. Ist das nicht C-konform? In PHP gehts, 
wobei PHP auch sehr tolerant gegenüber nicht standardkonformem 
Programmierstil ist, deshalb frag ich an dieser Stelle nochmal.

lg PoWl

von Simon K. (simon) Benutzerseite


Lesenswert?

Paul Hamacher wrote:
> Btw: Ich habs erst mit phase_DDR[map[a++]] |= (1 << map[a++]); versucht,

Uff, mit den unary operators stellst du dir nur Beinchen. Bist du dir 
sicher, dass es das machen soll, was du denkst?

Ich glaube zu wissen, dass es nicht legal ist eine variable den 
postfix-operator ++ oder -- auf der linken Seite, sowie auch auf der 
rechten Seite anzuwenden.

Ich weiß es aber nicht. Und ehrlich gesagt würde ich es eh nicht 
behalten, wenn ich es wüsste, da ich so etwas nie schreiben würde ;) 
Meiner Meinung nach eher verwirrend und fehlerträchtig (erst recht, wenn 
nicht im C-Standard festgeschrieben).

Deshalb immer weise anwenden diese operators. Ein gutes Beispiel dass 
mir gerade einfällt und meiner Meinung nicht legal ist, ist folgendes:
1
a = a++;

von Paul H. (powl)


Lesenswert?

ist vielleicht nicht gültig, würde aber sinn ergeben.

a = a++;

Weise der Variable a den Inhalt a zu und Inkrementiere a.

a = ++a;

Inkrementiere a und weise der Variable a diesen Wert zu.
Beides praktisch natürlich quatsch. Aber meinetwegen programmier ichs 
auch getrennt :-)

von Paul H. (powl)


Lesenswert?

So, ich wollte mein Charlieplexing gerademal auf meinem ATtiny26 um ein 
Fading erweitern. Allerdings erweißt sich das als nicht allzu einfach.

Gelegentlich flackert jedoch eine LED-Gruppe (bestehend, bei meinem 
4-Fach Charlieplexing, aus 3 LEDs). Das seltsame daran ist, dass das 
während dem Fading passiert, und zwar dann wenn OCR1A irgendwo zwischen 
0 und 255 liegt, der Leuchtstärke nach eher mittig, manchmal jedoch hell 
wenn es ganz dunkel ist.

Ich kann mir das nicht so wirklich erklären. Irgendwo ist da der Wurm 
drin.  Es flackert immer zu anderen Zeitpunkten und mit anderen 
LED-Gruppen. Der Fehler ist reproduzierbar, d.h. wenn ich die 
Stromversorgung kurz trenne und wieder anschließe, finden die gleichen 
Flackereffekte nach der gleichen Zeit statt und alles läuft wieder 
genauso ab. Ich schätze, es gibt hier wohl eine Überschneidung zwischen 
dem Hauptprogramm, das alle 10ms das OCR1A register inkrementiert bzw. 
dekrementiert. Weil der Counter das OCR1A register beim dekrementieren 
eventuell schon überholt hat?

Der AVR-Simulator bringt mich auch nicht weiter, da dauert es, wenn ich 
breakpoints setze, vom anfang der Overflow-ISR bis zum Anfang der 
Compare-ISR bei Compare-Match von 0 oder 1 immer konstante 128 Takte 
(was ja zu lang wäre)!? Auch wenn ich in die Overflow-ISR noch was 
einbaue.
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <util/delay.h>
4
5
6
volatile uint16_t lights = 0b0000111111111111;
7
8
volatile uint8_t phase_DDR[4];
9
10
volatile uint8_t phase = 0;
11
volatile uint8_t port = 1;
12
13
14
uint8_t map[] = { 0, 1, 1, 0, 0, 2, 2, 0, 0, 3, 3, 0, 1, 2, 2, 1, 1, 3, 3, 1, 2, 3, 3, 2 };
15
16
int main(void)
17
{
18
  // Timer aktivieren
19
  TCCR1B = (1 << CS12) | (1 << CS11) | (1 << CS10);
20
  TIMSK = (1 << OCIE1A) | (1 << TOIE1);
21
  OCR1A = 0;
22
23
24
  // Daten fürs Charlieplexing vorbereiten
25
  phase_DDR[0] = 0;
26
  phase_DDR[1] = 0;
27
  phase_DDR[2] = 0;
28
  phase_DDR[3] = 0;
29
30
  uint8_t a;
31
  uint8_t b;
32
33
  a = 0;
34
  for(b=0; b<12; b++)
35
  {
36
    if(lights & (1 << b))
37
    {
38
      phase_DDR[map[a]] |= (1 << map[a+1]);
39
    }
40
41
    a += 2;
42
  }
43
44
  sei();
45
46
  uint8_t fade = 0;
47
48
  // Fading-Schleife
49
  while(1)
50
  {
51
    if(fade)
52
    {
53
      OCR1A--;
54
55
      if(OCR1A == 0)
56
      {
57
        fade = 0;
58
      }
59
    }
60
    else
61
    {
62
      OCR1A++;
63
64
      if(OCR1A == 255)
65
      {
66
        fade = 1;
67
      }
68
    }
69
70
    _delay_ms(10);
71
  }
72
}
73
74
ISR(TIMER1_CMPA_vect)
75
{
76
  PORTA = 0;
77
}
78
79
ISR(TIMER1_OVF1_vect)
80
{
81
  PORTA = port;
82
  DDRA = phase_DDR[phase] | port;
83
84
  if(phase < 3)
85
  {
86
    phase++;
87
    port <<= 1;
88
  }
89
  else
90
  {
91
    phase = 0;
92
    port = 1;
93
  }
94
}

lg PoWl

von Hagen R. (hagen)


Lesenswert?

Ändere folgendes:

1.) globale Variable

volatile uint8_t OCR1AValue = 0;

2.) in Main nun OCR!A Zugriff ersetzen durch OCR1AValue Zugriff

3.) in ISR(TIMER1_CMPA_vect) Zeile hinzufügen mit

OCR1A = OCR1AValue;

Nun ist das Updating des OCR1A Registers synchron zum Timer1 und es kann 
nicht zu deinen besagten Aussetzern kommen. Die passieren nämlich immer 
dann wenn der in Main() OCR1A verkleinert wird und kleiner als der 
aktuelle TNCT1 Wert wird. Dann läuft der Timer nicht mehr bis OCR1A 
sondern er läuft darüber hinaus bis der Timer1 überläuft und erst nach 
diesem Überlauf wird er wieder durch das OCR1A register als neuen TOP 
begrenzt.

Wenn ich deinen Source richtig interpretiere dann änderst du über das 
OCR1A Register die Helligkeiten aller LEDs. Dh. alle LEDs, wenn sie ON 
sind, leuchten mit der gleichen Helligkeit. Mit deinem Source kannst du 
also nicht jede LED individuell in der Helligkeit steuern.

Gruß Hagen

von Paul H. (powl)


Lesenswert?

Dankeschön, auf solche einfachen Ideen komm ich irgendwie garnicht. Ich 
hätte da jetzt eine aufwändige Überprüfung, ob der Timer das OCR1A 
Register schon überholt hat, reingebaut. Dabei wäre ich auf die Idee 
gekommen, eine Variable vom Timer steuern zu lassen die angibt, wann der 
Timer bereit für eine Änderung des OCR1A Registers ist. Unnötig 
kompliziert^^

Nach einer weiteren kleinen Änderung in der ISR funktioniert nun alles 
ohne flackern :-)
1
ISR(TIMER1_OVF1_vect)
2
{
3
  if(OCRvalue > 0)
4
  {
5
    PORTA = port;
6
    DDRA = phase_DDR[phase] | port;
7
  }
8
9
  OCR1A = OCRvalue;

danke!
lg PoWl

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.