www.mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik switch-case-Anweisung zu langsam


Autor: Blitzenblitz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ich schreibe gerade an einem C-Programm für einen AVR.
Dort habe ich eine unsigned char Variable Befehl.
Das werte ich mit switch case aus.
Hier nur ein Beispiel!
  switch(Befehl)
  {
    case 0: if(PORTB &  (1 << 7)) PINB = (1 << 7); break; // CLR PB7
    case 1: if(PORTB & ~(1 << 7)) PINB = (1 << 7); break; // SET PB7
    case 2: if(PORTB &  (1 << 6)) PINB = (1 << 6); break;
    case 3: if(PORTB & ~(1 << 6)) PINB = (1 << 6); break;
    case 4: if(PORTB &  (1 << 5)) PINB = (1 << 5); break;
    case 5: if(PORTB & ~(1 << 5)) PINB = (1 << 5); break;
    case 6: if(PORTB &  (1 << 4)) PINB = (1 << 4); break;
    case 7: if(PORTB & ~(1 << 4)) PINB = (1 << 4); break;
    // Hier kommen noch 93 andere Befehle !!!
  }
Bei der switch-case-Anweisung stört mich, das das Programm bei sehr 
langen Listen jeden einzelnen case testen muß.
Gibt es eventuel noch eine andere Möglichkeit?

Autor: ozo (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Eventuell mit "Befehl" einen Binärbaum durchlaufen?

Autor: ozo (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Oder die Bitmaske für PORT und PIN in ner Tabelle nachschlagen? Mit 
Befehl als Index?

Autor: dr.no (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hast du bereits geschaut ob dein Compiler das switch() nicht über eine 
Sprungtabelle auflöst ?

Ansonsten mit Funktionspointern arbeiten.
const BefehlPtr[] =
{
   Befehl0,
   Befehl1,
   Befehl2,
   Befehl3
};

Ausführung dann mittels:
BefehlPtr[Befehl]();

Autor: Peter (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Blitzenblitz schrieb:
> // Hier kommen noch 93 andere Befehle !!!

kannst du noch ein paar mehr zeigen? die Frage ist wie es weiter geht ob 
man das nicht komplett berechnen kann. Es wird zwar dadurch vermutlich 
nicht schneller aber es spart viel code.

Der Compiler sollte sotwas schön als Sprungtabelle ablegen, eventuell 
sollte man mal suchen warum er sotwas nicht macht.

Autor: dr.no (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Es würde im übrigen helfen, wenn du schreibst was du vor hast.
Das Programmstück sieht so aus, als wolltest du die Möglichkeit 
implementieren über Befehle alle verfügbaren Pins zu steuern. Dafür gibt 
es in der Tat bessere Ansätze.

Autor: Blitzenblitz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
dr.no schrieb:
> Ansonsten mit Funktionspointern arbeiten.

Sowas hatte ich im Sinn.
Leider bin ich mit Pointern in C noch nicht so fit...
Wo würde ich die ganzen Befehls-sequenzen hinschreiben?
Kannst du mir da mal ein geben?

Autor: dr.no (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Blitzenblitz schrieb:
> Wo würde ich die ganzen Befehls-sequenzen hinschreiben?

In diesem Fall in eine separate Funktion.
typedef void (* FUNC_PTR)(void);

static void Befehl_0(void);
static void Befehl_1(void);

const FUNC_PTR befehle[] =
{
  Befehl_0,
  Befehl_1
  // ... weitere Befehle
};


static void Befehl_0(void)
{
  // ... code Befehl 0
}

static void Befehl_1(void)
{
  // ... code Befehl 1
}

Zugriff dann mit:
befehle[Befehl]();

Es empfiehlt sich zu prüfen ob "Befehl" innerhalb des gültigen Bereichs 
liegt bevor man das tut.

Nochmal: Es würde helfen wenn du erklärst was du vor hast.

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Blitzenblitz schrieb:

> Bei der switch-case-Anweisung stört mich, das das Programm bei sehr
> langen Listen jeden einzelnen case testen muß.

Nein. Muss es eben nicht. Praktisch jeder C-Compiler weiß, wie's besser 
geht. Anhängig von der Anzahl/Dichte der Fälle sind ALternativen zB
 - binärer Entscheidungsbaum
 - Sprungtabelle

> Gibt es eventuel noch eine andere Möglichkeit?

Wie sieht der Code denn überhaupt aus? Wieso werden keine Sprungtabellen 
erzeugt?

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Funktionspointer sind hier auch nicht der Weisheit letzter Schluss. Das 
artet in Unmengen von Funktionen aus.

Am Besten:
Zurücklehenen, erst mal erzählen was die AUfgabenstellung aus einer 
höheren Werte gesehen ist und dann kann man entscheiden welche Technik 
angebracht ist.

So wie es jetzt aussieht, hast du einen verkorksten Ansatz. Den rettet 
aber auch eine Änderung auf tiefster Ebene nicht mehr.

So sind zum Beispiel in deinem Beispiel alle ungeraden Befehle Tests ob 
am PortB ein Bit gelöscht ist, und alle geraden Befehle sind Tests ob am 
Port (B?) ein bestimmtes Bit gesetzt ist. Alleine mit dieser 
Unterscheideung kann man schon mal 2 Gruppen bilden, bei der dann in 
weiterer Folge nur noch die Hälfte der Befehle geprüft werden muss.

Autor: Peter (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
wenn es wirklich so sein muss (was ich auch noch bezweifle) dann kann 
man immer noch zu ASM ausweichen. Da die Befehle alle gleich lang sind. 
Kann man einfach den

Adresszeiger = Adresszeiger + Befehltslänge * Befehl

rechnen und schon steht man an der richtigen stelle.

Autor: Johann L. (gjlayde) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Peter schrieb:
> wenn es wirklich so sein muss (was ich auch noch bezweifle) dann kann
> man immer noch zu ASM ausweichen.

ARGL, nö. Hier geht's weiter:

Karl heinz Buchegger schrieb:

> So wie es jetzt aussieht, hast du einen verkorksten Ansatz. Den rettet
> aber auch eine Änderung auf tiefster Ebene nicht mehr.

Autor: Kakadu (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
In der Überschrift steht: "switch-case-Anweisung zu langsam"

Wie kommst Du überhaupt darauf? Hast Du irgendetwas gemessen?
Ich schätze mal: nein.

Und falls es wider Erwarten doch zu langsam abgearbeitet werden sollte, 
einfach den internen Takt von 1MHz auf 8MHz stellen, wenn wir vom AVR 
reden.

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Man sollte versuchen, das Gemeinsame aller Cases zu erkennen und dann 
die Funktion mit ner Variable zu schreiben, die den Unterschied 
abbildet.
#include <io.h>

uint8_t BITMASK[] = { 0x80, 0x40, 0x20, 0x10, 8, 4, 2, 1 };

void setclr( uint8_t befehl )
{
  uint8_t dir = 0;

  if( befehl < 16 ){
    if( befehl & 1 )
      dir = 0xFF;
    PINB = (PORTB ^ dir) & BITMASK[befehl / 2];
  }
}


Peter

Autor: MaWin (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Bei der switch-case-Anweisung stört mich, das das Programm bei
> sehr langen Listen jeden einzelnen case testen muß.

Kaum.
Also mich würde eher stören, daß der ganze Code nichts taugt.

    case 0: if(PORTB &  (1 << 7)) PINB = (1 << 7); break; // CLR PB7

wird mitnichten ein Bit löschen

    case 1: if(PORTB & ~(1 << 7)) PINB = (1 << 7); break; // SET PB7

was willst du an PIN auch setzen ?


Warum schreibst du nicht funktionierende Programme ?

if(befehl&1) // Setzen
{
    PORTB|=1<<(befehl>>1);
}
else // löschen
{
    PORTB&=~(1<<(befehl>>1));
}

erledigt deine 16 ersten Befehle, und es funktioniert damit sogar.

Ersetzt man PORTB noch alle 16 befehle durch das Richtige

int PORT[5]={PORTB,PORTA,PORTC,PORTD,PORTE};

if(befehl&1) // Setzen
{
    *PORT[befehl>>4]|=1<<(befehl>>1);
}
else // löschen
{
    *PORT[befehl>>4]&=~(1<<(befehl>>1));
}

erschlägt der Code gar alle deine 100 Befehle (funktioniert aber so auf 
einem AVR wohl nicht weil das nicht deinen Befehlen entspricht).

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
MaWin schrieb:

> Ersetzt man PORTB noch alle 16 befehle durch das Richtige
>
> int PORT[5]={PORTB,PORTA,PORTC,PORTD,PORTE};
>
> if(befehl&1) // Setzen
> {
>     *PORT[befehl>>4]|=1<<(befehl>>1);
> }
> else // löschen
> {
>     *PORT[befehl>>4]&=~(1<<(befehl>>1));
> }

Ja, so ungefähr hätte ich mir das auch vorgestellt. Daher die 
Aufforderung an den TO, mal etwas mehr zu erzählen. Dann könnte man 
entscheiden ob die Stossrichtung stimmt.

Auch Hybridmethoden wären denkbar (er wird ja doch nicht für jedes Bit 
an jedem Port einen eigenen Befehl eingeführt haben. Oder doch?), wenn 
es ausser dem gezeigten 'Befehlsschema' noch andere gibt.

> erschlägt der Code gar alle deine 100 Befehle (funktioniert aber so auf
> einem AVR wohl nicht weil das nicht deinen Befehlen entspricht).

Liese sich aber leicht erreichen. Das wäre nicht das Problem.

Autor: Blitzenblitz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Habe dieses von MaWin mal ausprobiert:
int PORT[3]={PORTB,PORTC,PORTD};

if(Befehl&1) // Setzen
{
    *PORT[Befehl>>4] |=  (1<<(Befehl>>1));
}
else // löschen
{
    *PORT[Befehl>>4] &= ~(1<<(Befehl>>1));
}
bekomme leider die Fehlermeldungen:
invalid type argument of 'unary *' (have 'int')

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
#include <avr/io.h>

volatile uint8_t * PORT[3]={ &PORTB, &PORTC, &PORTD };

int main()
{
  uint8_t Befehl = 23;

  if(Befehl&1) // Setzen
  {
    *PORT[Befehl>>4] |=  (1<<((Befehl&0x0F)>>1));
  }
  else // löschen
  {
    *PORT[Befehl>>4] &= ~(1<<((Befehl&0x0F)>>1));
  }

  // oder, da Befehl ja sowieso ein uint8_t (also unsigned) ist
  // da wirds dann vielleicht ein wenig klarer was da passiert
  if( Befehl % 2 == 1 )
  {  // die Befehle mit ungerader Befehlsnummer
    *PORT[Befehl/16] |=  (1 << (Befehl%16/2));
  }
  else // löschen
  {
    *PORT[Befehl/16] &= ~(1 << (Befehl%16/2) );
  }

  // den Teil mit der variablen Bitschieberei könnte man noch ersetzen

  {
    uint8_t SetMask[]   = { 0x01,  0x02,  0x04,  0x08,  0x10,  0x20,  0x40, 0x80 };
    uint8_t ClearMask[] = {~0x01, ~0x02, ~0x04, ~0x08, ~0x10, ~0x20, ~0x40, 0x7F };

    if( Befehl % 2 == 1 )
    {  // die Befehle mit ungerader Befehlsnummer
      Befehl = Befehl / 2;
      *PORT[Befehl/8] |=  SetMask[ Befehl % 8 ];
    }
    else // löschen
    {
      Befehl = Befehl / 2;
      *PORT[Befehl/8] &= ClearMask[ Befehl % 8 ];
    }

  }
}



Autor: Blitzenblitz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
mit
volatile uint8_t * PORT[3]={ &PORTB, &PORTC, &PORTD };
bekomme ich die Meldung: Build succeeded with 0 Warnings.

Ohne das volatile bekomme ich allerdings die Warnung:
initialisation discards qualfiers from pointer target type

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Blitzenblitz schrieb:
> mit
>
> volatile uint8_t * PORT[3]={ &PORTB, &PORTC, &PORTD };
> 
> bekomme ich die Meldung: Build succeeded with 0 Warnings.

Mit anderen Worten: alles in Ordnung.

Auch wenn ich manchmal Fehler mache, ich bin ja auch nicht auf der 
Nudelsuppe dahergeschwommen gekommen.

> Ohne das volatile bekomme ich allerdings die Warnung:
> *initialisation discards qualfiers from pointer target type*

Mit anderen Worten: das volatile muss da sein. Und genau deswegen habe 
ich es hingeschrieben. qed

Autor: Blitzenblitz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ja, besten Dank.

if(PORTB &  (1 << 7)) PINB = (1 << 7); break; // CLR PB7

if(PORTB & ~(1 << 7)) PINB = (1 << 7); break; // SET PB7
Ich habe das mit PINB bei mir im Programm, damit es interruptbar ist.
Wenn ich es so mache:
PORTB |=  (1 << 7); // SET PB7

PORTB &= ~(1 << 7); // CLR PB7
Ist es eine Read Modify Write Sequenz
Wird diese von einem Interrupt unterbrochen, der ebenfalls PORTB 
verfummelt dann kann das in die Hose gehn...

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Blitzenblitz schrieb:

> Wird diese von einem Interrupt unterbrochen, der ebenfalls PORTB
> verfummelt dann kann das in die Hose gehn...

du kannst ja immer noch ein sei/cli drum herum machen :-)
Von Interrupts war ja bisher nicht die Rede.

Autor: MaWin (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> bekomme leider die Fehlermeldungen

Drum schrieb ich "funktioniert aber so auf einem AVR wohl
nicht" (weil mir die Definitonsweise des PORTB dort
unbekannt ist).

Also nicht stumpf abtippen, sondern selber denken,
hat Karl heinz ja auch geschafft und sogar das
fehlende &0x0F nachgerüstet, obwohl ich &7 genommen hätte:

  if(Befehl&1) // Setzen
  {
    *PORT[Befehl>>4] |=  (1<<((Befehl>>1)&7));
  }
  else // löschen
  {
    *PORT[Befehl>>4] &= ~(1<<((Befehl>>1)&7));
  }

Autor: Blitzenblitz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl heinz Buchegger schrieb:
> du kannst ja immer noch ein sei/cli drum herum machen :-)

Wie geht das?

Autor: Blitzenblitz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl heinz Buchegger schrieb:
> du kannst ja immer noch ein sei/cli drum herum machen :-)

Ah ja sei ist ein Assemblerbefehl und heißt SEt Interrupt
und cli heißt CLear Interrupt.
Ich schreibe in C:
sei(); // Interrupts freigeben
PORTB |=  (1 << 7); // SET PB7
cli(); // Interrupts sperren
Wobei zu testen ist, ob das länger dauer als mein
if(PORTB & ~(1 << 7)) PINB = (1 << 7); break; // SET PB7
und ob ich mir damit nicht noch andere Probleme einhandle...

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
MaWin schrieb:

>     case 0: if(PORTB &  (1 << 7)) PINB = (1 << 7); break; // CLR PB7
> wird mitnichten ein Bit löschen

Doch. Nicht auf Mega8/32, wohl aber auf Mega88/324.
1 auf PINx schreiben hat da Toggle-Funktion.

Besser wär aber:
     case 0: PINB = PORTB & (1 << 7);

Autor: Εrnst B✶ (ernst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Blitzenblitz schrieb:

> Ah ja sei ist ein Assemblerbefehl und heißt SEt Interrupt
> und cli heißt CLear Interrupt.
> Ich schreibe in C:sei(); // Interrupts freigeben
> PORTB |=  (1 << 7); // SET PB7
> cli(); // Interrupts sperren
> Wobei zu testen ist, ob das länger dauer als meinif(PORTB & ~(1 << 7)) PINB = (1 
<< 7); break; // SET PB7
> und ob ich mir damit nicht noch andere Probleme einhandle...

Du kannst auch einfach dem GCC das Optimieren erlauben. Aus
  PORTB |= (1<<7);
wird dann:
 256:   c7 9a           sbi     0x18, 7 ; 24

Also ein einzelner "sbi"-Befehl. Der ist Atomar, kein sei/cli nötig.

Bei der Tabellen-Version oben, da brauchst du das ggfs.

Autor: A. K. (prx)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Εrnst B✶ schrieb:

> Also ein einzelner "sbi"-Befehl. Der ist Atomar, kein sei/cli nötig.

Aber Vorsicht! Nicht alle Ports aller AVRs liegen in von SBI/CBI 
erfassbaren Adressraum.

Autor: Blitzenblitz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Danke an alle fleißigen Helfer,
habe dieses jetzt mal programmiert und simuliert:
#include <avr/io.h>
#include <avr/interrupt.h>
//***********************************************
typedef void (* FUNC_PTR)(void);
void Befehl_0(void);
void Befehl_1(void);
void Befehl_2(void);
void Befehl_3(void);
void Befehl_4(void);
void Befehl_5(void);
void Befehl_6(void);
void Befehl_7(void);
void Befehl_8(void);
void Befehl_9(void);
//***********************************************
const FUNC_PTR befehle[] =
{
  Befehl_0,
  Befehl_1,
  Befehl_2,
  Befehl_3,
  Befehl_4,
  Befehl_5,
  Befehl_6,
  Befehl_7,
  Befehl_8,
  Befehl_9
};
//***********************************************
int main(void)
{
  unsigned char Befehl = 0;
  DDRB = 0xFF;
  while(1)
  {
    befehle[Befehl++]();
    if(Befehl == 10) Befehl = 0;
  }
}
//***********************************************
void Befehl_0(void) {if(~PORTB & (1 << 4)) PINB = (1 << 4);}
void Befehl_1(void) {if(~PORTB & (1 << 3)) PINB = (1 << 3);}
void Befehl_2(void) {if(~PORTB & (1 << 2)) PINB = (1 << 2);}
void Befehl_3(void) {if(~PORTB & (1 << 1)) PINB = (1 << 1);}
void Befehl_4(void) {if(~PORTB & (1 << 0)) PINB = (1 << 0);}
void Befehl_5(void) {if( PORTB & (1 << 4)) PINB = (1 << 4);}
void Befehl_6(void) {if( PORTB & (1 << 3)) PINB = (1 << 3);}
void Befehl_7(void) {if( PORTB & (1 << 2)) PINB = (1 << 2);}
void Befehl_8(void) {if( PORTB & (1 << 1)) PINB = (1 << 1);}
void Befehl_9(void) {if( PORTB & (1 << 0)) PINB = (1 << 0);}
Scheint zu funktionieren.
Der tiefe Sinn dahinter, den ich bisher verschwiegen habe, war:
1. Meine C-Kenntnisse zu verbessern...
2. Eine Möglichkeit zu finden, historische oder selbsterdachte µC's auf 
einem AVR in Software nachbilden zu können...

Autor: Erich (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
>Der tiefe Sinn dahinter
>"switch-case-Anweisung zu langsam"

Die "Lösung" vom Datum: 12.01.2011 16:21 wird  NIEMALS  schneller sein 
als der Anfang dises Threads.
Sie ist nur
= umständlicher
= schlechter lesbar
= schlechter wart- und erweiterbar  (aufwendiger)

Autor: Blitzenblitz (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Erich schrieb:
> Die "Lösung" vom Datum: 12.01.2011 16:21 wird  NIEMALS  schneller sein
> als der Anfang dises Threads.
> Sie ist nur
> = umständlicher
> = schlechter lesbar
> = schlechter wart- und erweiterbar  (aufwendiger)

Nö, eigentlich bin ich ganz zufrieden.
Ich würde im weiteren Verlauf den Befehlen dann natürlich sinnvollere 
Namen als Befehl_123 geben.
Mein eigentliches Programm würde dann hier stehen:
const FUNC_PTR befehle[] =
{
  SET_PB1,
  SET_PB3,
  SET_PB5,
  SWAP_A_at_R0, // Befehl wie beim 8051, nur Gedankenspiel
  CLR_PB2,
  CLR_PB4,
// usw.
};
Kritiker werden einwenden können ich hätte das ja auch so machen können:
#define SET_PB4 {if(~PORTB & (1 << 4)) PINB = (1 << 4);}
#define CLR_PB4 {if( PORTB & (1 << 4)) PINB = (1 << 4);}
// usw.
Allerdings würde der Preprozzessor dann für jeden Befehl den kompletten 
Code einfügen und ruckzuck wär mein Speicher voll.

Autor: Falk Brunner (falk)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@  Blitzenblitz (Gast)

>Mein eigentliches Programm würde dann hier stehen:

>const FUNC_PTR befehle[] =

Und auf dem Besten weg, ein langsames, akademisches Monster zu werden. 
Diese ganzen Structs und Datenzugriffe kosten Zeit, auch auf einem 
schnellen Prozessor. Zeit, die du angeblich nicht hast.

>Allerdings würde der Preprozzessor dann für jeden Befehl den kompletten
>Code einfügen

Das ist der Sinn der Sache, wenn es schnell sein soll. Lies mal den 
Betreff DEINES Beitrags . . .

> und ruckzuck wär mein Speicher voll.

Nimm einen größeren Prozessor. Und vor allem musst du schon VERDAMMT 
viele solche Sachen machen, um mit ein paar IO Klimpereien einen 
normalen uC vollzustopfen.

Ergo: Thema verfehlt, sechs.

MFG
Falk

Autor: Blitzenblitz (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
@Falk Brunner: :-(

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.