Forum: Mikrocontroller und Digitale Elektronik 8051, Keil und der Krieg mit der Codeerzeugung


von Matthias L. (matze88)


Lesenswert?

Guten Abend zusammen!

Ich verzweifle gerade etwas an meinem 8051 Programm mit Keil.
Der macht immer so schrecklich langen Code :( Ich will nicht alles in 
Assembler schreiben.
1
unsigned char code PeriodTable[] = { 0, 128, 0, 164, 0, 196, 0, 255 }; /* will be much longer later! */
2
data unsigned char ASIncr[AUDIO_MAX_PARALLEL];      /* input sample offset += ASIncr */
3
data unsigned char ASIncrFrac[AUDIO_MAX_PARALLEL];    /* ASIncrFracCnt += ASIncrFrac */
4
5
static void setSampleTone(unsigned char channel, unsigned char period)
6
{
7
  ASIncr[channel] = PeriodTable[2*period];
8
  ASIncrFrac[channel] = PeriodTable[2*period + 1];
9
}
1
             ; FUNCTION _setSampleTone (BEGIN)
2
                                           ; SOURCE LINE # 220
3
;---- Variable 'period' assigned to Register 'R5' ----
4
;---- Variable 'channel' assigned to Register 'R7' ----
5
                                           ; SOURCE LINE # 221
6
                                           ; SOURCE LINE # 222
7
                       MOV     A,R5
8
C51 COMPILER V7.50   ___SOUND                                                              01/12/2012 23:28:20 PAGE 10  
9
10
                       ADD     A,ACC
11
                 R     ADD     A,#LOW PeriodTable
12
                       MOV     DPL,A
13
                       CLR     A
14
                 R     ADDC    A,#HIGH PeriodTable
15
                       MOV     DPH,A
16
                       CLR     A
17
                       MOVC    A,@A+DPTR
18
                       MOV     R6,A
19
                 R     MOV     A,#LOW ASIncr
20
                       ADD     A,R7
21
                       MOV     R0,A
22
                       MOV     @R0,AR6
23
                                           ; SOURCE LINE # 223
24
                       MOV     A,R5
25
                       ADD     A,ACC
26
                 R     ADD     A,#LOW PeriodTable+01H
27
                       MOV     DPL,A
28
                       CLR     A
29
                 R     ADDC    A,#HIGH PeriodTable+01H
30
                       MOV     DPH,A
31
                       CLR     A
32
                       MOVC    A,@A+DPTR
33
                       MOV     R6,A
34
                 R     MOV     A,#LOW ASIncrFrac
35
                       ADD     A,R7
36
                       MOV     R0,A
37
                       MOV     @R0,AR6
38
                                           ; SOURCE LINE # 224
39
                       RET     
40
             ; FUNCTION _setSampleTone (END)


Ich habe auch mal lokale temporäre Variablen eingeführt, trotzdem macht 
er die Adressrechnung für den Zugriff in das Array nochmal.

Ähnliches Problem in einer etwas komplexeren Funktion, dort fällt es 
aber nicht so auf (hier nur ein Ausschnitt):
1
      fx = tempSongPosition[0] & 0x0F;
2
      period = tempSongPosition[1];
3
      fxParam = tempSongPosition[2];
1
                 R     MOV     DPL,tempSongPosition+01H
2
                 R     MOV     DPH,tempSongPosition
3
                       MOVX    A,@DPTR
4
                       MOV     R7,A
5
                       ANL     A,#0FH
6
                 R     MOV     fx,A
7
                                           ; SOURCE LINE # 293
8
                       INC     DPTR
9
                       MOVX    A,@DPTR
10
                 R     MOV     period,A
11
                                           ; SOURCE LINE # 294
12
                 R     MOV     DPL,tempSongPosition+01H
13
                 R     MOV     DPH,tempSongPosition
14
                       INC     DPTR
15
                       INC     DPTR
16
                       MOVX    A,@DPTR
17
                 R     MOV     fxParam,A

1x klappt das mit dem INC DPTR super, danach lädt er trotzdem neu...
Warum?!

Ich habe häufiger diese Spielchen mit dem kleinen :( von überflüssigen 
MOV Instructions möchte ich garnicht anfangen zu reden, weil man da 
immer nur einen Zyklus verliert. Hier sind es aber 3*2 = 6! Mein 
Prozessor ist (leider) recht beschäftigt.

Hat jemand ein paar Tipps?

Dankeschön!

Matthias

von Matthias K. (matthiask)


Lesenswert?

Keil ist eigentlich für effizienten Kode bekannt. Hast Du die richtige 
Lib eingebunden und mal die Optimierungen angepasst?

von Frank K. (fchk)


Lesenswert?

1
static void setSampleTone(unsigned char channel, unsigned char period)
2
{
3
  unsigned short idx=period<<1;
4
  ASIncr[channel] = PeriodTable[idx];
5
  idx++;
6
  ASIncrFrac[channel] = PeriodTable[idx];
7
}

?

fchk

von Peter D. (peda)


Lesenswert?

Man muß sich etwas abhärten, wenn man aus der Assemblerecke nach C 
kommt. Man findet immer einige Zyklen, die eingespart werden könnten.

Dafür punktet der Compiler an anderer Stelle. Z.B. kann er Variablen 
überlagern, was man in Assemmbler nicht kann und das spart dann ne Menge 
PUSH/POP ein.


Matthias Larisch schrieb:
> Mein
> Prozessor ist (leider) recht beschäftigt.

Dann muß man den Flaschenhals suchen und genau diesen optimieren.
Auf breiter Front zu optimieren, bringt wenig, kostet aber viel Zeit.

Und wenns wirklich eng wird, kann man ja immer noch schnellere 8051-er 
nehmen. Also statt dem alten 12-clocker einen 6-, 4-, 2- oder 1-clocker.
Die 1-clocker gibts von Silabs bis 100MHz hoch (1 NOP = 10ns).


Peter

von Matthias L. (matze88)


Lesenswert?

Frank K. schrieb:
> static void setSampleTone(unsigned char channel, unsigned char period)
> {
>   unsigned short idx = ((unsigned short)period) << 1; [geändert durch matthias]
>   ASIncr[channel] = PeriodTable[idx];
>   idx++;
>   ASIncrFrac[channel] = PeriodTable[idx];
> }
>
> ?
>
> fchk
1
             ; FUNCTION _setSampleTone (BEGIN)
2
                                           ; SOURCE LINE # 220
3
;---- Variable 'channel' assigned to Register 'R4' ----
4
                       MOV     R4,AR7
5
;---- Variable 'period' assigned to Register 'R5' ----
6
                                           ; SOURCE LINE # 221
7
                                           ; SOURCE LINE # 222
8
C51 COMPILER V7.50   ___SOUND                                                              01/13/2012 11:03:25 PAGE 10  
9
10
                       MOV     A,R5
11
                       ADD     A,ACC
12
                       MOV     R7,A
13
;---- Variable 'idx' assigned to Register 'R6/R7' ----
14
                                           ; SOURCE LINE # 223
15
                       MOV     A,R7          ; ----- was soll das?
16
                 R     MOV     DPTR,#PeriodTable
17
                       MOVC    A,@A+DPTR
18
                       MOV     R5,A
19
                 R     MOV     A,#LOW ASIncr
20
                       ADD     A,R4
21
                       MOV     R0,A
22
                       MOV     @R0,AR5
23
                                           ; SOURCE LINE # 224
24
                       INC     R7
25
                 R     CJNE    R7,#00H,?C0058     ; ?!? bitte was? Hängt wohl mit dem 2-byte-increment zusammen, wobei er beim Multiplizieren das 2. byte mal diskret weggelassen hat
26
             ?C0058:
27
                                           ; SOURCE LINE # 225
28
                       MOV     A,R7
29
                 R     MOV     DPTR,#PeriodTable    ; ---- was soll das?
30
                       MOVC    A,@A+DPTR
31
                       MOV     R7,A
32
                 R     MOV     A,#LOW ASIncrFrac
33
                       ADD     A,R4
34
                       MOV     R0,A
35
                       MOV     @R0,AR7
36
                                           ; SOURCE LINE # 226
37
                       RET     
38
             ; FUNCTION _setSampleTone (END)

Somit: Etwas besser (2 Zyklen, wenn ich mich nicht verzählt hab), aber 3 
weitere offensichtlich einsparbar.
Außerdem habe ich gewisse Erwartungen an die Optimieriung eines 
Compilers. Diese 3 offensichtlichen Stellen zählen für mich dazu. Jede 
statische Analyse erkennt, dass die Register vorher garantiert den 
richtigen Wert enthalten.

Auch die Transformation meines Ursprungscodes in diesen hier sollte der 
Compiler doch hinkriegen.

von Karl H. (kbuchegg)


Lesenswert?

Was ist hiermit?
1
struct IncValue {
2
  unsigned char Whole;
3
  unsigned char Frac;
4
}
5
6
struct IncValue PeriodTable[] =
7
{
8
   { 0, 128 },
9
   { 0, 164 },
10
   { 0, 196 },
11
   { 0, 255 }
12
};
13
14
data struct IncValue ASInc[ AUDIO_MAX_PARALLEL ];
15
16
static void setSampleTone(unsigned char channel, unsigned char period)
17
{
18
  ASInc[channel] = PeriodTable[2*period];
19
}

von Matthias L. (matze88)


Lesenswert?

Hallo Peter,

Auf eine Antwort von dir hatte ich gehofft :)

Peter Dannegger schrieb:
> Man muß sich etwas abhärten, wenn man aus der Assemblerecke nach C
> kommt. Man findet immer einige Zyklen, die eingespart werden könnten.

Das schlimme ist ja: Eigentlich mache ich sehr wenig Assembler. Ich 
möchte es auch garnicht. Ich habe aber gewisse Erwartungen an ein 
Produkt, was dann auch noch so teuer sein soll. Klar ist der SDCC noch 
schlechter, aber solche Codestücke fallen mir dann einfach sehr übel ins 
Auge.

> Dafür punktet der Compiler an anderer Stelle. Z.B. kann er Variablen
> überlagern, was man in Assemmbler nicht kann und das spart dann ne Menge
> PUSH/POP ein.

Das sehe ich ein und achte ich hoch :)

>
>
> Matthias Larisch schrieb:
>> Mein
>> Prozessor ist (leider) recht beschäftigt.
>
> Dann muß man den Flaschenhals suchen und genau diesen optimieren.
> Auf breiter Front zu optimieren, bringt wenig, kostet aber viel Zeit.

Ich halte mich hier in einem Codestück auf, was mehr oder weniger alle 
20-50ms aufgerufen wird. Es ist nicht direkt der Flaschenhals, die lange 
Ausführungszeit führt aber zu unschönem Jitter in der Ausgabe 
(Tonerzeugung; Hier wird die Frequenz geändert; Außerdem muss eben noch 
ne ganze Ecke mehr passieren vor/nach diesem Codeschnipsel).
Die Ausgabe an sich erfolgt alle 256 Takte im Interrupt, den habe ich 
schon per Hand geschrieben.


> Und wenns wirklich eng wird, kann man ja immer noch schnellere 8051-er
> nehmen. Also statt dem alten 12-clocker einen 6-, 4-, 2- oder 1-clocker.
> Die 1-clocker gibts von Silabs bis 100MHz hoch (1 NOP = 10ns).

Ja, ja...
Es geht hier um ne Uniaufgabe, die ich mir mal wieder aufwändiger 
gemacht habe, als sie sein müsste :) Vorgabe ist der AT89S52, ich möchte 
halt ein MOD-ähnliches Tracker Format abspielen. Nebenbei gibts noch ne 
20x14 Pixel Matrix(alle 2ms im Interrupt -> hier kann man auch noch 
optimieren) und ein Spiel, was darauf läuft. Das ist aber beides 
deutlich unkritischer als der Ton. Wir takten mit 20 MHz.

von Erich (Gast)


Lesenswert?

Schonmal an einen neueren uC mit besserer Architektur gedacht als der 
8051 mit seinem singulärem DPTR ?
Irgendwann nach 30 Jahren sollte man umstellen !

Gruss

von Karl H. (kbuchegg)


Lesenswert?

> Was ist hiermit?

Oder hiermit (wenn du deinen Code schon nicht mit einer vernünftigen 
struct versehen willst, was softwaretechnisch sowieso die bessere Lösung 
wäre, weil es den Zusammenhang von ganzzahligem und fraktionellem Teil 
betont und es nicht einfach nur um irgendwelche eigenständige Arrays 
geht, die halt zufällig irgendwie zusammengehören)
1
static void setSampleTone(unsigned char channel, unsigned char period)
2
{
3
  unsigned char* pTable = &PeriodTable[2*period];
4
// Alternativ:  unsigned char* pTable = PeriodTable + 2*period;
5
  ASIncr[channel] = *pTable++;
6
  ASIncrFrac[channel] = *pTable;
7
}

von Peter D. (peda)


Lesenswert?

Matthias Larisch schrieb:
> Vorgabe ist der AT89S52

Der AT89S8253 wäre kompatibel, aber doppelt so schnell (24MHz/6).


Peter

von Matthias L. (matze88)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Was ist hiermit?
> struct IncValue {
>   unsigned char Whole;
>   unsigned char Frac;
> }; /* semikolon von mir */
>
> struct IncValue code PeriodTable[] = /* code segment */
> {
>    { 0, 128 },
>    { 0, 164 },
>    { 0, 196 },
>    { 0, 255 }
> };
>
> data struct IncValue ASInc[ AUDIO_MAX_PARALLEL ];
>
> static void setSampleTone(unsigned char channel, unsigned char period)
> {
>   ASInc[channel] = PeriodTable[period]; /* korrigiert von mir */
> }

[x] du möchtest nicht wissen, was nun passiert ist :)
Der Code ist 300 Byte größer geworden. Das Struct möchte der Herr Keil 
gerne mit einer Library Funktion kopieren. Die Multiplikation mit der 
Struct Größe erfolgt mit MUL.

Ich hätte noch den Vorschlag mit einem unsigned short für die Tabelle. 
Kann man dann ja selbst auseinanderpflücken
-> Sieht nicht gut aus.

Letzter Versuch: Beides als unsigned short (also ein kombiniertes Incr, 
IncrFrac).

Das ist die beste Lösung:
1
 ; FUNCTION _setSampleTone (BEGIN)
2
                                           ; SOURCE LINE # 220
3
;---- Variable 'period' assigned to Register 'R5' ----
4
;---- Variable 'channel' assigned to Register 'R7' ----
5
                                           ; SOURCE LINE # 221
6
                                           ; SOURCE LINE # 223
7
                       MOV     A,R5
8
C51 COMPILER V7.50   ___SOUND                                                              01/13/2012 11:35:28 PAGE 10  
9
10
                       ADD     A,ACC
11
                 R     ADD     A,#LOW PeriodTable
12
                       MOV     DPL,A
13
                       CLR     A
14
                 R     ADDC    A,#HIGH PeriodTable
15
                       MOV     DPH,A
16
                       CLR     A
17
                       MOVC    A,@A+DPTR
18
                       MOV     R4,A
19
                       MOV     A,#01H
20
                       MOVC    A,@A+DPTR
21
                       MOV     R5,A
22
                       MOV     A,R7
23
                       ADD     A,ACC
24
                 R     ADD     A,#LOW ASIncr
25
                       MOV     R0,A
26
                       MOV     @R0,AR4
27
                       INC     R0
28
                       MOV     @R0,AR5
29
                                           ; SOURCE LINE # 224
30
                       RET     
31
             ; FUNCTION _setSampleTone (END)

Außerdem kann ich nun im Abspiel-Interrupt vermutlich noch ein oder zwei 
Instruktionen sparen, muss ich nochmal reinschauen. Super!  (Wir 
erinnern uns: Abgespielt wird alle 256 CPU Takte - hier lohnt sich also 
jeder Takt)

Somit auch einen Dank an dich, Karl Heinz.

und Erich:
Ja, ich würde das privat auch nie machen :) Hier freue ich mich aber 
dennoch sehr, auch mal mit dem 8051 in Berührung zu kommen. Es macht 
Spaß :) Sonst würde ich mir vermutlich den Assemblercode nichtmal 
anschauen und einfach ein Pong auf dem Display implementieren (das würde 
die Aufgabenstellung voll erfüllen)

Damit wäre der Thread hier erstmal erledigt. Danke an alle 
Beteiligungen!

PS: Meine Compile-Optionen sind
1
CC_OPTS="SMALL OMF2 PREPRINT SYMBOLS INCDIR(..\\) OBJECTADVANCED CODE OPTIMIZE(11,SPEED)"
2
LD_OPTS="CLASSES(XDATA(X:0x0000-X:0x001FFF)) CODE"

von Karl H. (kbuchegg)


Lesenswert?

Matthias Larisch schrieb:

> [x] du möchtest nicht wissen, was nun passiert ist :)
> Der Code ist 300 Byte größer geworden. Das Struct möchte der Herr Keil
> gerne mit einer Library Funktion kopieren. Die Multiplikation mit der
> Struct Größe erfolgt mit MUL.

Autsch.

In der struct die beiden unsigned char noch mit einer union zu einem 
unsigned int (oder short) zusammenfassen, hätt ich noch im Angebot.

Allerdings wirds jetzt wirklich schon ein wenig absurd.


> Letzter Versuch: Beides als unsigned short (also ein kombiniertes Incr,
> IncrFrac).

Hats du mal den Vorschlag mit dem eigenständigen Pointer probiert? Sowas 
sollte der Compiler eigentlich selber rausfinden, aber na ja.
1
static void setSampleTone(unsigned char channel, unsigned char period)
2
{
3
  unsigned char* pTable = &PeriodTable[2*period];
4
// Alternativ:  unsigned char* pTable = PeriodTable + 2*period;
5
  ASIncr[channel] = *pTable++;
6
  ASIncrFrac[channel] = *pTable;
7
}

von Matthias L. (matze88)


Lesenswert?

Noch von vorhin:

Karl Heinz Buchegger schrieb:
>> Was ist hiermit?
>
> Oder hiermit (wenn du deinen Code schon nicht mit einer vernünftigen
> struct versehen willst, was softwaretechnisch sowieso die bessere Lösung
> wäre, weil es den Zusammenhang von ganzzahligem und fraktionellem Teil
> betont und es nicht einfach nur um irgendwelche eigenständige Arrays
> geht, die halt zufällig irgendwie zusammengehören)

Ich sehe das vollkommen ein. Ich bin aber (leider) derzeit überzeugt 
davon, dass in dieser niedrigen Ebene heutzutage softwaretechnisch 
sinnvolle Lösungen meist kein gutes Ergebnis liefern. Daher habe ich 
kein besonders schlechtes Gewissen, wenn ich diese Grundregel missachte.

Die Lösung mit einem gemeinsamen unsigned short kann man quasi als 
Fixkomma-Lösung auffassen. Somit wäre das softwaretechnisch auch wieder 
in Ordnung :)

Ich bin gespannt, ob wir es schaffen, in einigen Jahren auch auf 
niedrigster Ebene vernünftigen Hochsprachencode schreiben zu können. 
Vermutlich bräuchte man dazu eher eine Art Hardwarebeschreibungssprache, 
welche die niedrigste Ebene implementiert, aber keine Hardware sondern 
eben Instruktionen erzeugt.


Karl Heinz Buchegger schrieb:
>
> Hats du mal den Vorschlag mit dem eigenständigen Pointer probiert? Sowas
> sollte der Compiler eigentlich selber rausfinden, aber na ja.
> static void setSampleTone(unsigned char channel, unsigned char period)
> {
>   unsigned char* pTable = &PeriodTable[2*period];
> // Alternativ:  unsigned char* pTable = PeriodTable + 2*period;
>   ASIncr[channel] = *pTable++;
>   ASIncrFrac[channel] = *pTable;
> }

Spendieren wir noch ein Code-Attribut für den lokalen Zeiger (sonst 
gibts einen generischen und ne Menge Library-Code), dann erhalte ich 
komischerweise die gleiche Codegröße (fürs Gesamtprojekt) wie bei der 
unsigned short Variante, gleichzeitig aber eine etwas längere Funktion. 
Unter Umständen habe ich hier gerade was übersehen.

Aufgrund des Einsparpotenzials am größten "Rechenzeitverschwender" werde 
ich weiterhin die unsigned short Variante bevorzugen.

Also nochmals vielen Dank für die Rege Beteiligung hier, ihr habt mir 
sehr geholfen.

Gruß!

Matthias

Edit: Ich vergaß: In spätestens 4 Wochen, dann ist nämlich Abgabe, gibts 
das ganze Projekt natürlich offen für alle.

von red cruiser (Gast)


Lesenswert?

Mal so probieren


#define    AUDIO_MAX_PARALLEL    5


const  unsigned char code PeriodTable[] = { 0, 128, 0, 164, 0, 196, 0, 
255 }; /* will be much longer later! */
data unsigned char ASIncr[AUDIO_MAX_PARALLEL];      /* input sample 
offset += ASIncr */
data unsigned char ASIncrFrac[AUDIO_MAX_PARALLEL];    /* ASIncrFracCnt 
+= ASIncrFrac */

static void setSampleTone(unsigned char channel, unsigned char period)
{

  period *= 2;
  ASIncr[channel] = PeriodTable[period];
  ++period;
  ASIncrFrac[channel] = PeriodTable[period];
}



erzeugt bei mir




             ; FUNCTION _setSampleTone (BEGIN)
                                           ; SOURCE LINE # 173
;---- Variable 'period' assigned to Register 'R5' ----
;---- Variable 'channel' assigned to Register 'R7' ----
                                           ; SOURCE LINE # 174
                                           ; SOURCE LINE # 176
                       MOV     A,R5
                       ADD     A,ACC
                       MOV     R5,A
                                           ; SOURCE LINE # 177
                 R     MOV     DPTR,#PeriodTable
                       MOVC    A,@A+DPTR
                       MOV     R6,A
                 R     MOV     A,#LOW ASIncr
                       ADD     A,R7
                       MOV     R0,A
                       MOV     @R0,AR6
                                           ; SOURCE LINE # 178
                       INC     R5
                                           ; SOURCE LINE # 179
                       MOV     A,R5
                       MOVC    A,@A+DPTR
                       MOV     R6,A
                 R     MOV     A,#LOW ASIncrFrac
                       ADD     A,R7
                       MOV     R0,A
                       MOV     @R0,AR6
                                           ; SOURCE LINE # 180
                       RET
             ; FUNCTION _setSampleTone (END)


vv

von red cruiser (Gast)


Lesenswert?

Noch ein Fehler in der Lösung - type von "unsigned char period" ist zu 
beachten!


static void setSampleTone(unsigned char channel, unsigned int period)
{

  period *= 2;
  ASIncr[channel] = PeriodTable[period];
  ++period;
  ASIncrFrac[channel] = PeriodTable[period];
}







             ; FUNCTION _setSampleTone (BEGIN)
                                           ; SOURCE LINE # 173
;---- Variable 'period' assigned to Register 'R4/R5' ----
;---- Variable 'channel' assigned to Register 'R3' ----
                       MOV     R3,AR7
                                           ; SOURCE LINE # 174
                                           ; SOURCE LINE # 176
                       MOV     A,R5
                       ADD     A,ACC
                       MOV     R5,A
                                           ; SOURCE LINE # 177
                       MOV     A,R5
                 R     MOV     DPTR,#PeriodTable
                       MOVC    A,@A+DPTR
                       MOV     R7,A
                 R     MOV     A,#LOW ASIncr
                       ADD     A,R3
                       MOV     R0,A
                       MOV     @R0,AR7
                                           ; SOURCE LINE # 178
                       INC     R5
                 R     CJNE    R5,#00H,?C0009
             ?C0009:
                                           ; SOURCE LINE # 179
                       MOV     A,R5
                 R     MOV     DPTR,#PeriodTable
                       MOVC    A,@A+DPTR
                       MOV     R7,A
                 R     MOV     A,#LOW ASIncrFrac
                       ADD     A,R3
                       MOV     R0,A
                       MOV     @R0,AR7
                                           ; SOURCE LINE # 180
                       RET
             ; FUNCTION _setSampleTone (END)

von Bernhard S. (b_spitzer)


Lesenswert?

Hallo,
wäre es nicht eine Idee, im Array PeriodTable einfach 16Bit-Werte 
abzulegen, und die dann in einem Rutsch auszulesen?
Ich nehme mal an, die Werte landen später in den Timer-Registern?? Da 
verwende ich oft eine temporäre Variable zum Datenaustausch. Ich 
definiere eine Int-Variable und an den selben Adressen 2 Byte-Variablen. 
Die Auftrennung in Low- und High-Byte geht über 2 Zugriffe. Int 
schreiben, 2x Char lesen.
z.B. code at 0x08 unsigned int uiTemp;
code at 0x08 unsigned char ucHighByte, ucLowByte;
Die belegen jetzt zusammen die Bytes 0x08 und 0x09, der Compiler meckert 
etwas, aber das ist ja gewollt.
Für einen Timer-Reload mache ich dann z.B.
uiTemp = (65536-Zeit);
TH1 = ucHighByte;
TL1 = ucLowByte;

tschuessle
Bernhard

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.