Forum: Mikrocontroller und Digitale Elektronik 16bit zu 2x 8bit, 4bit ausmaskieren TLC5947


von Martin K. (dschadu)


Lesenswert?

Hi,

ich bin aktuell dran einen TLC5947 anzusteuern. Das funktioniert soweit, 
Daten in einem Array vorgeben etc klappt problemlos.

Jetzt hab ich mir eine Funktion geschrieben, die ich einfach mit dem 
Ausgang (Kanal 0..23) fütter und dem PWM Wert.
Bei geraden Kanälen klappt das Problemlos (0, 2, 4, 6...), nur die 
Ungeraden wollen nicht. Die richtigen Byte's im Array werden 
geschrieben, allerdings ist die Helligkeit viel zu hoch. Selbst bei 10 
(von 4095) sind die LEDs an den ungeraden Kanälen extrem Hell.

Meine Funktion:
1
char lightdata[36] = {
2
  0x00,0x00,0x00,0x00,0x00,0x00,
3
  0x00,0x00,0x00,0x00,0x00,0x00,
4
  0x00,0x00,0x00,0x00,0x00,0x00,
5
  0x00,0x00,0x00,0x00,0x00,0x00,
6
  0x00,0x00,0x00,0x00,0x00,0x00,
7
  0x00,0x00,0x00,0x00,0x00,0x00
8
};
9
10
void SetLight(uint8_t kanal, uint16_t data)
11
{
12
  if (kanal % 2 == 0) //Auf Gerade Zahl prüfen
13
  {
14
    kanal *= 1.5;
15
    lightdata[kanal] = data;
16
    lightdata[kanal+1] = data<<8 | (lightdata[kanal+1] & 0x0f);
17
  }
18
  else
19
  {
20
    kanal *= 1.5;
21
    lightdata[kanal] = data | (lightdata[kanal] & 0xf0);  
22
    lightdata[kanal+1] = data<<8;
23
  }
24
}
25
26
27
int main(void)
28
{
29
    while(1)
30
    {
31
    SetLight(5, 10);
32
    }
33
}

Die Multiplikation mit 1.5 ist um vom Kanal auf das richtige Byte zu 
kommen (12Bit PWM = 1 1/2Byte). Das geht wie gesagt Problemlos, die 
Richtige Zahl kommt bei raus.
Problem ist die Helligkeit.

Wo liegt mein Fehler, bzw Denkfehler? Ich starr da jetzt schon über eine 
Stunde drauf.
Bin dankbar für jede Hilfe!

von Stefan W. (dl6dx)


Lesenswert?

Martin K. schrieb:
> kanal *= 1.5

Das hat zwar mit deinem eigentlichen Problem vermutlich nichts zu tun, 
aber bist du sicher, dass bei der Multiplikation eines u8int_t mit einer 
float-Konstante wirklich immer das richtige heraus kommt?

Der Wert von kanal wird dabei nämlich zunächst nach float gewandelt, 
dann kommt die (float-)Multiplikation mit 1.5, dann wird zurück nach 
u8int_t gecastet. (Ist im erzeugten Assemblercode auch gut zu sehen.)
1
    kanal *= 1.5;
2
  f4:  68 2f         mov  r22, r24
3
  f6:  70 e0         ldi  r23, 0x00  ; 0
4
  f8:  80 e0         ldi  r24, 0x00  ; 0
5
  fa:  90 e0         ldi  r25, 0x00  ; 0
6
  fc:  0e 94 b8 03   call  0x770  ; 0x770 <__floatsisf>
7
 100:  20 e0         ldi  r18, 0x00  ; 0
8
 102:  30 e0         ldi  r19, 0x00  ; 0
9
 104:  40 ec         ldi  r20, 0xC0  ; 192
10
 106:  5f e3         ldi  r21, 0x3F  ; 63
11
 108:  0e 94 86 02   call  0x50c  ; 0x50c <__mulsf3>
12
 10c:  46 2f         mov  r20, r22
13
 10e:  57 2f         mov  r21, r23
14
 110:  68 2f         mov  r22, r24
15
 112:  79 2f         mov  r23, r25
16
 114:  cb 01         movw  r24, r22
17
 116:  ba 01         movw  r22, r20
18
 118:  0e 94 99 00   call  0x132  ; 0x132 <__fixunssfsi>

Mit einer kleinen Umformulierung (kanal += kanal / 2) wird 
Integer-Arithmetik verwendet und viel weniger Code erzeugt:
1
  kanal += kanal / 2;
2
  d8:  e8 2f         mov  r30, r24
3
  da:  e6 95         lsr  r30
4
  dc:  e8 0f         add  r30, r24

von Stefan W. (dl6dx)


Lesenswert?

Martin K. schrieb:

Du willst die Daten ja wohl so anordnen:
1
------------------------------------------------
2
| lightdata[2]  | lightdata[1]  | lightdata[0]  |
3
------------------------------------------------
4
|B,A,9,8,7,6,5,4|3,2,1,0,B,A,9,8|7,6,5,4,3,2,1,0|
5
------------------------------------------------
6
|7,6,5,4,3,2,1,0|7,6,5,4,3,2,1,0|7,6,5,4,3,2,1,0|
7
------------------------------------------------
wobei 0 das LSB und B das MSB des PWM-Werts ist. (Bits 0 bis 11 von 
data.

Schaun wir mal.

Gerade Kanalnummer
>     lightdata[kanal] = data;
Hm. Implizites Abschneiden widerstrebt mir immer etwas. data & 0xFF wäre 
etwas sicherer. Aber sei's drum.

>     lightdata[kanal+1] = data<<8 | (lightdata[kanal+1] & 0x0f);
Nein.
Du willst die Bits 8 bis 11 von data in die Bits 0 bis 3 von 
lightdata[kanal] packen.
Der passende Ausdruck ist entweder
((data >> 8) & 0x0F) oder
((data & 0x0F) >> 8)
Die oberen vier Bit von lightdata[kanal+1] sollen erhalten bleiben, die 
unteren vier für das bitweise Oder gelöscht werden.
Der passende Ausdruck ist lightdata[kanal+1] & 0xF0

Also insgesamt:
1
lightdata[kanal+1] = ((data >> 8) & 0x0F) | (lightdata[kanal+1] & 0xF0);

Jetzt der ungerade Kanal:
>     lightdata[kanal] = data | (lightdata[kanal] & 0xf0);
Auch hier stimmt die Bitmaske für die "Altdaten" nicht und data muss um 
vier bit nach links geschoben werden.
1
lightdata[kanal] = ((data & 0x0F) << 4) | (lightdata[kanal] & 0x0F);
>     lightdata[kanal+1] = data<<8;
Du willst Bit 4 bis 11 haben. Also
1
lightdata[kanal+1] = (data >> 4) & 0xFF;

So. Das Ergebnis des Compiler-Durchlauf kommt gleich.

Grüße

Stefan

von Falk B. (falk)


Lesenswert?


von Stefan W. (dl6dx)


Lesenswert?

Stefan Wagner schrieb:
> Das Ergebnis des Compiler-Durchlauf kommt gleich.
1
#include <stdint.h>
2
3
char lightdata[36] = {
4
  0x00,0x00,0x00,0x00,0x00,0x00,
5
  0x00,0x00,0x00,0x00,0x00,0x00,
6
  0x00,0x00,0x00,0x00,0x00,0x00,
7
  0x00,0x00,0x00,0x00,0x00,0x00,
8
  0x00,0x00,0x00,0x00,0x00,0x00,
9
  0x00,0x00,0x00,0x00,0x00,0x00
10
};
11
12
void SetLight(uint8_t kanal, uint16_t data)
13
{
14
  if (kanal % 2 == 0) //Auf Gerade Zahl prüfen
15
  {
16
    kanal += kanal / 2;
17
    lightdata[kanal] = data; //& 0xFF;
18
    lightdata[kanal+1] = ((data >> 8) & 0x0F) | (lightdata[kanal+1] & 0xF0);
19
  }
20
  else
21
  {
22
    kanal += kanal / 2;
23
    lightdata[kanal] = ((data & 0x0F) << 4) | (lightdata[kanal] & 0x0F);  
24
    lightdata[kanal+1] = (data >> 4); // & 0xFF;
25
  }
26
}
27
28
29
int main(void)
30
{
31
    while(1)
32
    {
33
    SetLight(5, 10);
34
    }
35
}

lässt sich compilieren, aber ich habe auf diesem PC keinen lauffähigen 
Simulator (sieht aus, als vertrügen sich AVR Studio 4 und Atmel Studio 6 
nicht richtig.)
Na, probier es mal aus und gib eine Rückmeldung.

Grüße

Stefan

PS: Zu den beiden auskommentierten Ausdrücken "& 0xFF;": Ich habe das 
explizite Wegmaskieren des high byte jetzt doch mal auskommentiert.

von Martin K. (dschadu)


Lesenswert?

Hi,

erstmal vielen dank! Das mit dem float vermeiden werd ich definitiv so 
übernehemn.

Dein Vorschlag Stefan funktioniert leider nicht, ich habs einfach mal 
1:1 übernommen

Als Beispiel:
Angeschlossen ist es immer Kanal 0 Blue, Kanal 1 Green, Kanal 2, Red.

Schreib ich "SetLight(0, 40);" sind die LEDs blau.
Schreib ich "SetLight(0, 4000);" werden die LEDs türkis, also es kommt 
viel grün mit.
SetLight(1, 40); sollte grün sein, ist aber blau.

Ich versteh aktuell nicht ganz, warum du >> verschiebst, nicht << ? Ich 
möchte doch Bit 0-7 vom uint16_t an die Stelle 8-15 bringen?


Ich spiel mal noch ein wenig rum.

von Stefan W. (dl6dx)


Lesenswert?

Martin K. schrieb:
> funktioniert leider nicht

Stimmt, hab ich gerade auch gemerkt. Für ungerade Kanäle war der Index 
falsch, da hab ich beim Kopieren des Codes nicht aufgepasst.
1
void SetLight(uint8_t kanal, uint16_t data)
2
{
3
  if (kanal % 2 == 0) //Auf Gerade Zahl prüfen
4
  {
5
    kanal += kanal / 2;
6
    lightdata[kanal] = data; //& 0xFF;
7
    lightdata[kanal+1] = ((data >> 8) & 0x0F) | (lightdata[kanal+1]&0xF0);
8
  }
9
  else
10
  {
11
    kanal += kanal / 2;
12
    lightdata[kanal+1] = ((data&0x0F) << 4) | (lightdata[kanal+1]&0x0F);  
13
    lightdata[kanal+2] = (data >> 4);
14
  }
15
}

> Ich versteh aktuell nicht ganz, warum du >> verschiebst, nicht << ? Ich
> möchte doch Bit 0-7 vom uint16_t an die Stelle 8-15 bringen?

Sagst du bitte, welche Stelle im Code du meinst?

Grüße

Stefan

von Martin K. (dschadu)


Lesenswert?

Ich meine bei
1
lightdata[kanal+1] = ((data >> 8) & 0x0F) | (lightdata[kanal+1]&0xF0);
2
                       |H i e r|

Muss ich nicht 8bits nach links schieben, damit die bits 0-7 an der 
Stelle 8-15 sitzen, damit.....
moment... denkfehler.
bei uint8_t = uint16_t hol ich mir ja die unteren 8bits aus dem 16bit 
int, nicht, nicht die oberen.

Leider funktioniert das mit den ungeraden noch immer nicht. Die Geraden 
gehen wie gehabt. Interessanterweise geht auch meine Version, obwohl ich 
die bits ja falsch rum schiebe?! Ich glaub ich mach heute mal schluss.

Die ungeraden zeigen wie gesagt weiterhin seltsames verhalten, der kanal 
sollte ja durch
1
kanal += kanal / 2;
 stimmen. Mit dem +1 und +2 landet man dann im falschen Byte.
Die Helligkeit ändert sich nicht und es ist bei "SetLight(3, 2500);" 
Kanal 2 (rot) und Kanal 3 (Blau) leicht am glimmen. Höhere Werte lassen 
das rot dunkler werden, das blau leicht heller. Bei niedriegeren werten 
kommt grün dazu (Kanal 4). Also ganz kurios...

von Martin K. (dschadu)


Lesenswert?

So, nochmal vielen dank für die super hilfe!
Es funktioniert jetzt:
1
if (kanal % 2 == 0) //Auf Gerade Zahl prüfen
2
  {
3
    kanal += kanal / 2;
4
    lightdata[kanal] = data & 0xff;
5
    lightdata[kanal+1] = data >> 8 | (lightdata[kanal+1] & 0xf0);
6
  }
7
  else
8
  {
9
    kanal += kanal / 2;
10
    lightdata[kanal] = data << 4 | (lightdata[kanal] & 0x0f);
11
    lightdata[kanal+1] = data >> 4;
12
  }

Hab heute nochmal alles in Ruhe aufgeschrieben und Schritt für Schritt 
vor gegangen. Dabei ist mir auch noch aufgefallen, das ich die Bytes 
falsch herum raus sende (Schieberegister...)
Wie gesagt, es geht! Und nochmal danke für die Hilfe.

von Stefan W. (dl6dx)


Lesenswert?

Stefan Wagner schrieb:
> Für ungerade Kanäle war der Index falsch,

Das war natürlich falsch. (kanal + kanal / 2) zeigt schon an die 
richtige Stelle. Aber das hast du ja auch gemerkt.

Martin K. schrieb:
> Hab heute nochmal alles in Ruhe aufgeschrieben und Schritt für Schritt
> vorgegangen. Dabei ist mir auch noch aufgefallen, das ich die Bytes
> falsch herum raus sende (Schieberegister...)

Ok, das konnte ich natürlich nicht sehen.

> Wie gesagt, es geht! Und nochmal danke für die Hilfe.

Bitte, gerne.

Vielleicht noch ein Tipp: Spiel mal verschiedene Bitschiebereien und 
Bitmaskierereien im Simulator durch (Optimierung dafür maximal auf -O1 
stellen). Das gibt ein besseres Gefühl dafür, was da passiert.

Grüße

Stefan

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.