mikrocontroller.net

Forum: Compiler & IDEs Tut gelesen und trotzdem Problem mit Strings im Flash


Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo zusammen,

ich habe mir einen Webserver mit ENC28J60 und einem AT89C51ED2 
"zusammengebastelt" also Hardware und Software zum grösstenteil selber 
geschrieben. Jetzt habe ich mich mal ein bisschen mit den AVR 
beschäftigt und dachte ich könnte diesen Webserver mal auf einen ATMEGA 
644 portieren. Eigentlich funktioniert soweit auch alles. Ich habe 
allerdings ein problem damit eine Webseite per TCP zu versenden. Die 
Webseiten liegen bei mir im Flash und sollen über einen buffer im ram an 
den ENC weitergereicht werden. Solange die Webseite < 256 Bytes ist 
funktioniert das ganze auch wunderbar. Ist die Webseite grösser dann 
haut zuerst die Checksummenberechnung nicht mehr hin, mache ich sie noch 
grösser dann hängt sich der Kontroller auf. Die Beispiele im Tutorial 
habe ich auprobiert, auch die in der GCC Faq, aber das Ergebniss ist 
immer das selbe. Beim 8051 gibt es ja das Iram mit 256 Bytes, gibt es 
das beim AVR auch? Wie schaffe ich es die 4K Ram beim AVR komplett zu 
nutzen?

Mein Buffer im Ram habe ich so angelegt:
unsigned char buffer[1518]; // ENC Sende und Empfangs Buffer

Meine Website liegt hoffentlich so im Flash:
PROGMEM unsigned char HtmlTest[] =
{ 
//Die Website
}

Und so versuche ich die Daten aus dem Flash in den Buffer zu kopieren:
unsigned int http_data_out (PGM_P pointer, unsigned int seq_no)
{ 
unsigned char *http_buffer;
    unsigned int new_length = 0, zgr = 0;

    http_buffer = &buffer [ 58 + zgr ];

    while ( pgm_read_byte ( pointer + zgr ) )
    {
       http_buffer[zgr] = pgm_read_byte ( pointer + zgr );
       zgr++;
    }
    new_length = zgr;
    send_tcp_data (new_length, seq_no);
    return new_length;
}

Autor: Werner B. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Segmentierung gibt es beim AVR nicht. Kling eher danach als ob du 
irgendwo bei deinen Berechnungen einen unsigned char als index in den 
Speicher benutzt der dann natürlich überläuft. Kann ein Überrest einer 
"Iram-Optimierung" sein ;-)

An der obigen Funktion kanns nicht liegen. Ist zwar unnötig kompliziert, 
aber ich kann keinen Fehler sehen.

P.S.
Ich würde das so machen. Allerdings noch nicht dem Compiler übergeben, 
kann sein dass noch Typecast für 'pointer' notwendig sind.
unsigned int http_data_out (PGM_P pointer, unsigned int seq_no)
{
    unsigned char *http_buffer = &buffer[ 58 ];
    unsigned int index = 0;
    unsigned char c; 

    while ((index < (sizeof(buffer)-58))
        && ((c = pgm_read_byte(&pointer[index])) != '\0'))
    {
       *http_buffer++ = c;
       index++;
    }
    send_tcp_data (index, seq_no);
    return index;
}

Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Werner,

unnötig kompliziert, damit könntest Du recht haben, aber ich beisse mir 
schon seit 2 wochen die Zähne an diesem Problem aus. Dementsprechend 
Banane sieht der Code mittlerweile auch aus. Eine "IRam optimierung" 
gibt es bei dem Originalcode vom AT89C51ED2 leider auch nicht, weil ich 
da natürlich alles "Datenfressende" in das XRAM verschoben habe. Aber 
der Hinweis das es eigentlich so funktionieren müsste, hilft mir schon 
mal, dann lag ich ja nicht ganz falsch. Sind doch ein paar unterschiede 
zwischen 8051 und AVR.

Ich werde jetzt noch mal versuchen den betreffenden Code umzuschreiben.

Gruß aus Köln

Frank

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Du schreibst ja:
> Ist die Webseite grösser dann haut zuerst die Checksummenberechnung
> nicht mehr hin, ...

Ich würde mich als erstes mal dort umsehen, wo die Checksumme berechnet 
wird. Hast du vielleicht dort einen 8-Bit-Index?

Außerdem: Wie sieht es mit dem Gesamtspeicherbedarf deiner Software aus?
Es könnte auch sein, dass du mit dem Stack kollidierst.

Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo nochmal,

also ich habs nochmal umgeschrieben und es ist immer noch dasselbe.
Bei 256 Bytes ist schluss mit lustig. Eigentlich macht die Routine doch 
nichts anderes wie die Adresse der Daten im Flash zu bekommen und dann 
Byte für Byte in das Ram zu kopieren. Also das Ram ist 1518 - 58 = 1460 
Bytes gross und die Daten im Flash sind 328 Byte gross. Ist auch schön 
mit /0 terminiert lt. Ponyprog. In diesem zustand sehe ich mit Wireshark 
noch das TCP Paket, aber die checksumme stimmt schon nicht mehr, da ja 
die daten im buffer nicht mehr mit den aus dem Flash zu lesenden Daten 
übereinstimmt.

So langsam bin ich mit meinem Latein am Ende.
Hat vieleicht noch jemand eine Idee ?

Gruß aus Köln

Frank

Autor: Frank aus Köln (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Stefan,

Speicherbedarf:
Program = 9356 Bytes
Data = 1732 Bytes

Ich weiss nicht wie man den Speicherverbrauch noch weiter aufdröseln 
kann, ich bin noch nicht so lange auf dem AVR unterwegs, beim SDCC ging 
das über das .map File. Das gibt es zwar hier auch, aber ich habe noch 
nicht raus welche speicherbereiche wo stehen. Den Speicherverbrauch oben 
zeigt mir das makefile an. Die CRC Routine schliesse ich erstmal aus, da 
sie auf dem AT89C51ED2 ja läuft. Ich hänge sie aber mal mit an.

Gruß aus Köln

Frank

Autor: die ??? (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Zu erkennen ist, dass du die

  pgm_read_byte / pgm_read_byte_near

Funktion nutzt. Nun ist es aber möglich, dass deine Daten an einer 
Adresse liegen, an die ein 16Bit-Pointer nicht gelangt (Adresse >= 
2^16). Dafür gibt es die

  pgm_read_byte_far

Funktion [1]. Allerdings werden Daten vom Linker/Locator i.d.R. 
möglichst im unteren Bereich abgelegt, sodass es meist keine Probleme 
damit gibt. Knifflich wird es halt erst bei vielen Daten/großen 
zusammenhängenden Blöcken im Programmspeicher.

Just my two cents.

[1] http://www.nongnu.org/avr-libc/user-manual/group__...

Autor: die ??? (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Edit: Wenn dein Programmspeicher mit nur ~10k belegt ist, erübrigt sich 
mein Post von 11:13h.

Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo die ???,

danke für den Tip, das wird dann wohl das nächste Problem werden. Beim 
ED2 hatte ich am schluss mit Website und Debugmeldungen 50K voll.

Gruß aus Köln

Frank

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Meine Website liegt hoffentlich so im Flash:
> PROGMEM unsigned char HtmlTest[] =>
> {
> //Die Website
> }

Du hoffst nur oder hast du den Flash-Inhalt kontrolliert, ob die Daten 
wie gewünscht dort drin stehen? Die Position des PROGMEN Schlüsselworts 
ist ungewöhnlich. Im AVR-GCC-Tutorial wird das anders gemacht.

In deiner Kopierroutine wird ja das Nullbyte nicht mitkopiert. Ist das 
Absicht? Hier die Kopierroutine etwas gestrafft:

unsigned int http_data_out (PGM_P pointer, unsigned int seq_no)
{
  unsigned char *http_buffer;
  unsigned char c;
  unsigned int count = 0;

  http_buffer = &buffer[58];

  while ( (c = pgm_read_byte ( pointer )) )
  {
     *http_buffer++ = c;
     count++;
  }

  /* http_buffer bzw. buffer ist jetzt NICHT mit '\0' abgeschlossen! */

  send_tcp_data (count, seq_no);
  return count;
}


Wie sieht deine send_tcp_data (count, seq_no); aus?

Die scheint ja für die Prüfsummenberechnung zuständig zu sein. Ist dort 
vielleicht eine Laufvariable nur unsigned char statt unsigned int?

Autor: die ??? (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Frank aus Köln wrote:
> ...hatte ich am schluss mit Website und Debugmeldungen 50K voll.

Immernoch kleiner als 64k, somit erstmal Entwarnung.  ;^)

Autor: die ??? (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan hat nicht unrecht, ungewöhnlich ist es - es sollte aber in den 
meisten Konstellationen funktionieren. Ich rate dazu, die vordefinierten 
Typen zu verwenden:

typedef void PROGMEM            prog_void
typedef char PROGMEM            prog_char
typedef unsigned char PROGMEM   prog_uchar
typedef int8_t PROGMEM          prog_int8_t
typedef uint8_t PROGMEM         prog_uint8_t
typedef int16_t PROGMEM         prog_int16_t
typedef uint16_t PROGMEM        prog_uint16_t
typedef int32_t PROGMEM         prog_int32_t
typedef uint32_t PROGMEM        prog_uint32_t
typedef int64_t PROGMEM         prog_int64_t
typedef uint64_t PROGMEM        prog_uint64_t

Das Beste ist, wenn du dein kompilierbares Projekt hier mal hochlädst.

Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Stefan,

dieser PROGMEM Syntax ist aus dem Quellcode vom Simon Schulz (AVRETH1).

Ursprünglich hatte ich es so geschrieben:
const char HtmlTest[] PROGMEM =
{
//Die Website
}

Aber weil es alles nicht so lief wie es sollte, habe ich mir das bei den 
anderen Webserverprogrammierern angeschaut. Macht aber auch keinen 
unterschied, habe ich gerade noch mal ausprobiert. Das mit der /0 
Terminierung ist ok, wird bei TCP auch nicht mit übertragen.

Hier noch die send Routine:

void send_tcp_data (unsigned int datlen, unsigned int nextseq)
{
    struct IP_header *IP_packet;
    struct TCP_header * TCP_packet;
    struct IP_Pseudoheader *IP_pseudopacket;
    IP_packet = (struct IP_header *)&buffer[ETHERNET_HEADER_LENGTH];
    TCP_packet = (struct TCP_header *)&buffer[ETHERNET_HEADER_LENGTH + 
IP_HEADER_LENGTH];
    IP_pseudopacket = (struct IP_Pseudoheader 
*)&buffer[ETHERNET_HEADER_LENGTH + IP_HEADER_LENGTH - 
IP_PSEUDOHEADER_LENGHT];
    MakePseudoHeader (IP_packet->IP_DestinationIP,0x06, 
TCP_HEADER_LENGHT + datlen);
    MakeTCPHeader 
(TCP_packet->TCP_DestinationPort,ChangeEndian32bit(TCP_packet->TCP_Ackno 
wledgeNumber),ChangeEndian32bit(TCP_packet->TCP_SequenceNumber)+nextseq, 
TCP_PSH_FLAG  | TCP_ACK_FLAG, datlen , TCP_packet->TCP_Options);
    MakeIPheader(IP_pseudopacket->IP_DestinationIP,0x06,TCP_HEADER_LENGHT+da 
tlen);
    enc28j60PacketSend( ETHERNET_HEADER_LENGTH + IP_HEADER_LENGTH + 
TCP_HEADER_LENGHT + datlen,buffer);
}

Gruß aus Köln

Frank

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ist das eine fertige (auf AVR getestete) Library oder muss man da tiefer 
einsteigen? Die Arbeitsfunktion zur Prüfsumme sehe ich nicht und kann 
sie auch nicht beurteilen, die ist noch tiefer angesiedelt.

In deiner http_data_out hast du 58 als magic number. Kannst du die 
in Beziehung zu den Makrokonstanten in der send_tcp_data setzten, um 
Bufferoverflows (Bufferbereiche werden in Headerbereiche kopiert oder 
umgekehrt) auszuschliessen?

Autor: Frank aus Köln (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Hallo nochmal,

wenn ich sehe wie der Code nach mühevoller bearbeitung im 
Vorschaufenster aussieht, dann wird mir ganz anders.

Ich hänge das was ich habe einfach mal dran. Achtung es wimmelt vor 
furchtbaren Kommentaren, es hagelt warnings, aber es läuft. Bis auf TCP. 
:(

Kompilieren lässt sich das ganze mit WinAVR 20071221.

ACHTUNG SICHERHEITSHINWEIS !!! ICH BIN AVR NEULING :)

Gruß aus Köln

Frank

Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Stefan,

das mit der 58 ist so:

Ich habe mir im RAM einen Bereich reserviert der 1518 Bytes gross ist.
Dann kommt zuerst der Ethernetheader mit 14 Bytes, dann IP Header mit 20 
Bytes, dann TCP Header mit 24 Bytes grösse. Das 58te Byte stellt den 
Startpunkt für meine HTTP Daten dar. Hier würde dann die eigentliche 
Webseite landen. Das funktioniert für 256 Bytes und dann ist schluss.
Ich vermute die CRC routine ist ok, da sie auf dem ED2 ja auch 
funktioniert hat. Ausserdem rechnet sie ja auch weiter, aber die Daten 
im Paket hören einfach ab dem 256 Byte auf. Was mich an dieser ganzen 
geschichte stört ist die Zahl 256. Es sind ja auch bereits 58 Bytes 
Header im paket und nur bei der Kopierroutine aus dem Flash hört er 
einfach auf in den buffer zu schreiben.

Nun gut der code ist gepostet, vielleicht sieht ja jemand noch etwas.

Gruß aus Köln

Frank

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das Archiv zu posten, war eine gute Idee. Die Bedenken wg. 
Bufferoverflow kann man so schon zerstreuen. Die magische 58 ist 
erklärbar. Den Rest schaue ich mir in Ruhe heute abend an.

Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo zusammen,

vielen Dank nochmal an alle für eure Hilfe.
Als ich die Frage heute morgen reingestellt habe, hatte ich schon die 
befürchtung das wird direkt als typische Anfängerfrage in der Luft 
zerissen. Mit soviel Hilfe habe ich nicht gerechnet. Ich bin jetzt echt 
platt !!

Gruß aus Köln

Frank

Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Nur nochmal für die Interresierten

Die Adresse von dem Funktionierende Webserver mit dem AT89C51ED2

http://colonia66.dnsalias.com/main.html

Da möchte ich gerne mit dem AVR weitermachen

Gruß aus Köln

Frank

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
In der Funktion http_data_out kann ich keine Probleme entdecken. Der 
String aus HtmlTest wird sauber nach buffer kopiert. Alle 328 Zeichen. 
Es findet auch später kein Überschreiben statt.

Aber kontrolliere mal diese Stelle in send_tcp_data (und ähnliche 
Stellen in den anderen send_... Funktionen).

Ich denke, dass die Sequenznummer zuerst erhöht werden muss, bevor der 
Wert von Little Endian (AVR) nach Big Endian (Network) gewandelt werden 
muss (If TRUE Fall)

void send_tcp_data (unsigned int datlen, unsigned int nextseq)              //
{                                                                           //
    struct IP_header *IP_packet;                                            // IP_struct anlegen
    struct TCP_header * TCP_packet;                                         // TCP struct anlegen
    struct IP_Pseudoheader *IP_pseudopacket;                                // IP_Pseudoheader struct anlegen
    IP_packet = (struct IP_header *)&buffer[ETHERNET_HEADER_LENGTH];                            // IP_packet start @ buffer[14]
    TCP_packet = (struct TCP_header *)&buffer[ETHERNET_HEADER_LENGTH + IP_HEADER_LENGTH];        // TCP_packet start @ buffer[34]
    IP_pseudopacket = (struct IP_Pseudoheader *)&buffer[ETHERNET_HEADER_LENGTH + IP_HEADER_LENGTH -   // IP_pseudo start @ buffer[22]
        IP_PSEUDOHEADER_LENGHT];                                            //
    MakePseudoHeader (IP_packet->IP_DestinationIP,0x06, TCP_HEADER_LENGHT + datlen);// Pseudoheader für die Checksumme bauen
    MakeTCPHeader (TCP_packet->TCP_DestinationPort,ChangeEndian32bit(       // TCP Header generieren es wird die selbe Ack nummer gesendet
        TCP_packet->TCP_AcknowledgeNumber), ChangeEndian32bit(              // die Seq nummer wird um die länge der bis jetzt gesendeten daten
#if 0
        TCP_packet->TCP_SequenceNumber)+nextseq,                            // erhöht
#else
        TCP_packet->TCP_SequenceNumber+nextseq),                            // erhöht
#endif
        TCP_PSH_FLAG | TCP_ACK_FLAG, datlen,TCP_packet->TCP_Options);       //
    MakeIPheader(IP_pseudopacket->IP_DestinationIP,0x06,                    // IP Header generieren
        TCP_HEADER_LENGHT+datlen);                                          //
    #ifdef _DEBUG                                                           // DEBUG **************************************
    print_debug("SEND_TCP_DATA->TCP Data Packet send to: ");                //
    print_uart_ip (IP_packet->IP_DestinationIP);                            //
    #endif                                                                  // DEBUG **************************************
    enc28j60PacketSend( ETHERNET_HEADER_LENGTH + IP_HEADER_LENGTH           // ACK + PSH zurücksenden
        + TCP_HEADER_LENGHT+datlen,buffer);                                 //
}               


Das Versagen bei Paketen mit mehr als 256 Bytes Nutzdaten käme mir 
plausibel vor, wenn z.B. ein Paket 256 Bytes Nutzdaten hat und durch die 
falsche Erhöhung alle Folgepakete ausser dem ersten wg. falscher 
Checksumme verworfen werden. Ich kann das aber nicht mit Bestimmtheit 
sagen, weil ich eine minimale Version getestet habe mit willkürlichem 
Aufruf von http_data_out, d.h. nicht weiss, wie du deine Seiten 
auslieferst - alle 328 Bytes am Stück (s. Test) oder in kleineren 
Portionen

main.c
#define DEBUG

#include <stdint.h>                                                         // ISO C99 Integer types
#include <avr/io.h>                                                         // ATTINY I/O's
#include <util/delay.h>                                                     // delay Lib.
#include <avr/pgmspace.h>
#include "global.h"                                                         //
#include "spi.h"                                                            //
#include "ethernet.h"                                                       //
#include "enc28j60.h"                                                       //
#include "timer.h"                                                          //
#include "dhcp.h"
#include "dns.h"
#include "sntp.h"   
#include "udp.h"
#include <stdlib.h>                                                         //

#ifdef DEBUG

int main(void)
{
  // http_data_out(array[0], 0);
  http_data_out(HtmlTest, 0);
  while(1);
}

#else

// Original Code aus main.c

#endif


Add:

Mit der Zeile

http_data_out(array[0], 0);

hatte ich Probleme. Da stimmt noch was nicht mit der Verknotung der 
Pointer auf die Strings.

Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Stefan,

mit dem erhöhen der Sequenznummer und dann nach Big Endian wandeln, 
könntest Du durchaus recht haben, kommt mir jetzt auch ein bisschen 
merkwürdig vor. Ich werde das gleich mal ausprobieren. Obwohl ich mir 
dann noch nicht erklären kann, warum das Problem nicht auch schon bei 
kleineren Paketen auftritt.
In dem einen Fall schicke ich ein Paket mit < 256 Bytes HTTP Daten und 
danach ein TCP_FIN paket und Wireshark sagt alles ok und die Webseite 
wird im Browser angezeigt.
In dem anderen Fall schicke ich auch nur ein Paket allerdings mit 328 
Bytes HTTP Daten und danach ein TCP_FIN Paket und Wireshark meckert das 
CRC nicht stimmt und die Seite wird im Browser nicht angezeigt.
Man sieht im 2. fall auch das sich die Checksumme ändert und er diese 
Routine weiter abarbeitet. Der Kontroller ist danach noch mit einem Ping 
erreichbar. Erst wenn ich noch mehr HTTP Daten sende (~1K) dann schmiert 
der Kontroller ab und ist dann auch nicht mehr erreichbar.
Die ursprungliche Senderoutine beim ED2 war so, das die HTTP daten so 
groß sein konnten wie es erforderlich ist, und die http_data_out 
Funktion hat die Daten dann in Ethernetkonforme Häppchen zerteilt.
Also sollen bis zu 1450 Bytes HTTP Daten am Stück gesendet werden und 
wenn dieses problem beseitigt ist mache ich mich wieder an das zeteilen 
der HTTP Daten.

Die Zeile:
http_data_out(array[0], 0);

Ist noch ein Überbleibsel aus diversen versuchen aus dem Flash zu lesen.
In der GCC Faq gibt es einen Abschnitt wo beschrieben ist, wie man 
komplette Strings im Flash ablegt und anschliessend wieder ausliest.
Dort wurde ein Array aus Zeigern auf die Strings im Flash angelegt und 
diese Strings wurden dann so adressiert. Kann man also vergessen, fliegt 
jetzt sowieso wieder raus.

Vielen Dank und gruß aus Köln

Frank

Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo nochmal,

also das mit der Sequenznummer ist so wie ich es gemacht habe korrekt.
Ich habe jetzt nochmal eine Verbindungssequenz mit Wireshark mitgeloggt.

Beispiel:
SYN     - Seq_No 94 65 17 5E
SYN_ACK - Seq_No 94 65 17 5F  SYN_ACK = Seq_No + 1
ACK     - Seq_No 94 65 1A 13  ACK = Seq_No + Datenlänge (692 Bytes)

Also muss die Sequenznummer nach dem Wandeln auf Big Endian erhöht 
werden.

Gruß aus Köln

Frank

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Aha. Dann schaue ich heute abend mal weiter.

Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Stefan,

ich habe mal weitergemacht und kann nun sagen, das das Problem irgendwie 
im bereich der TCP Routine zu suchen ist. Ich habe mal spasseshalber mit 
der pgm_read_byte routine die Website in ein UDP-Paket gepackt und 
gesendet. Dann kann ich mit Wireshark auch das komplette Datenpaket mit 
den 328 Byte Nutzdaten sehen. Im Umkehrschluss heisst das, das sowohl 
der buffer als auch die Flashkopierfunktion ok sind.

Gruß aus Köln

Frank

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Im Umkehrschluss heisst das, das sowohl der buffer als auch die
> Flashkopierfunktion ok sind.

Das war auch meine Schlussfolgerung gestern abend.

Ich habe einen Plan für heute abend - ich werde mit einer modifizierten 
http_data_out Pakete mit 250 bis 270 Bytes Nutzdaten konstruieren und 
auf Absonderlichkeiten bei der Berechnung der Checksummen achten, wenn 
die 8-Bit Grenze überschritten wird.

Als Nutzdaten plane ich Nullbytes zu verwenden, denn deren Inhalt sollte 
bei dieser Checksummenberechnung nicht in die Summe eingehen. Lediglich 
die Anzahl kommt an verschiedenen Stellen ins Spiel und eventuell gibt 
es da Merkwürdigkeiten.

Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Stefan,

Die Checksummenberechnung hatte ich jetzt auch im verdacht, aber diese 
CRC Funktion wird bei mir auch bei den udp_send funktion angewendet und 
da hat Wireshark, bei mehr als 256 Bytes, nichts auszusetzen. Ziemlich 
seltsam das ganze.

Ich prüfe jetzt nochmal diese Pseudoheader und IPHeadergeschichten 
vieleicht liegt das problem ja auch hier.

Gruß aus Köln

Frank

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Frank schau dir mal in send_tcp_data den Aufruf von MakeTCPHeader an und 
die Funktion selber - es wird beim Aufruf und in der Funktion selber die 
Endianess berechnet. Ist das richtig so?

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Oh, da ist eine Mischung bei der Endianess in der Source. Deshalb auch 
das doppelte Umwandeln im obigen Aufruf.

Ich kann mir gut vorstellen, dass gerne eine Umwandlung zuviel oder 
zuwenig durchflutscht. Deshalb würde ich das Ganze nochmal peinlich 
genau unter die Lupe nehmen und folgende Regeln beachten/anwenden:

1/ Es werden die bekannten Makros htonl (d.h. host-to.network-long), 
htons, ntohl und ntohs verwendet, um von network byte order  auf /host 
byte order/ umzurechnen.

Du hast zwar 32-Bit und 16-Bit Umwandelfunktionen, aber die sind für 
beide Richtungen gleich benannt. Dadurch fällt es beim Lesen der Source 
unnötig schwer, die Plausibilität des jeweiligen Codes sofort zu 
beurteilen.

Beim AVR wären die Makros:

// AVR
#define LITTLE_ENDIAN

#if defined(BIG_ENDIAN)

#define htons(A) (A) 
#define htonl(A) (A) 
#define ntohs(A) (A) 
#define ntohl(A) (A)

#elif defined(LITTLE_ENDIAN)

#define htons(A) ((((A) & 0xff00) >> 8) | (((A) & 0x00ff) << 8))
#define htonl(A) ((((A) & 0xff000000) >> 24) \
               | (((A) & 0x00ff0000) >> 8)   \
               | (((A) & 0x0000ff00) << 8)   \
               | (((A) & 0x000000ff) << 24)) 
#define ntohs htons 
#define ntohl htonl


2/ Ausserhalb der Paketheader werden "normale" Variablen, Konstanten 
und Funktionsargumente immer in der host byte order angegeben. D.h. 
Das Programm (und der debuggende Zuschauer) arbeitet immer mit der 
Zahlendarstellung, die auf dem Host üblich ist.

Bsp:
  
  // IP Adresse für ENC  192.168.0.123 = = c0.a8.00.7b
  MyIP.LONG = 0xc0a8007b;

3/ Beim Lesen aus den Paketheadern werden immer die ntohs bzw. ntohl 
Makros verwendet. D.h. in den Headern bleiben die Werte immer in der 
network byte order aber werden im Programm in der host byte order 
verarbeitet. Wenn solche Werte an Funktionen übergeben werden - 
ebenfalls mit dem Makro in der host byte order.

Bsp 1:

  MakeTCPHeader (
    ntohs(TCP_packet->TCP_DestinationPort),
    ntohl(TCP_packet->TCP_AcknowledgeNumber), 
    ntohl(TCP_packet->TCP_SequenceNumber+nextseq),
    TCP_PSH_FLAG | TCP_ACK_FLAG, 
    datlen,
    ntohl((unsigned long) (TCP_packet->TCP_Options))); 
    // Anm.: ...->TCP_Options in MakeTCPHeader auf NULL checken!
    //       die Zuweisung dort mit Options[0] usw. crasht!

Bsp 2:

void ip( unsigned int packet_lenght )                                     
{                                                                         
  struct IP_header *IP_packet;                                          

  IP_packet = (struct IP_header *)&buffer[ETHERNET_HEADER_LENGTH];      

  switch ( IP_packet->IP_Protocol )
  {
    case 0x01:
      if ( ntohl(IP_packet->IP_DestinationIP) != MyIP.LONG )
      {
        return;
      }
      #ifdef _DEBUG
      print_debug("IP->ICMP Packet received from: ");
      print_uart_ip ( ntohl(IP_packet->IP_SourceIP) );
      #endif
      icmp( packet_lenght );
      break;
    ...

4/ Beim Schreiben von Paketheadern werden die zugewiesenen Werte immer 
mit den htonl bzw. htons aufbereitet und dann zugewiesen. D.h. auch hier 
ist wieder sichergestellt, dass alle Werte in den Paketen immer in der 
network byte order sind.

Bsp:

void MakeIPheader(unsigned long SourceIP,unsigned char Protocoll,unsigned int Datalenght)
{                                                                           
  unsigned int checksum;                                                  
  struct IP_header *IP_packet;                                            

  IP_packet = (struct IP_header *)&buffer[ETHERNET_HEADER_LENGTH];        

  IP_packet->IP_Version_Headerlen = 0x45;                          
  IP_packet->IP_TOS = 0x00;                                        
  IP_packet->IP_Totallenght = htons(IP_HEADER_LENGTH + Datalenght);
  IP_packet->IP_Identification = htons(0xAC1D);                           
  IP_packet->IP_Flags = 0x40;                                      
  IP_packet->IP_Fragmentoffset = 0x00;                             
  IP_packet->IP_TTL = 0x80;                                        
  IP_packet->IP_Protocol = Protocoll;                              
  IP_packet->IP_Headerchecksum = 0x0000;                             
  IP_packet->IP_SourceIP = htonl(MyIP.LONG);                              
  IP_packet->IP_DestinationIP = htonl(SourceIP);                          
  checksum = Checksum_16( &buffer[14], IP_HEADER_LENGTH);          
  IP_packet->IP_Headerchecksum = htons( checksum );    
}                                                                           

Ausserdem würde ich möglichst viele magische Zahlen wie buffer[14]... 
durch Konstanten ersetzen, so wie es teilweise schon gemacht wurde.

Und last but not least - Mir persönlich gefällt der Stil mit // an jeder 
Zeilenende nicht so gut. Beim Einfügen von Code oder Umschreiben ist das 
meistens hinderlich (abgesehen von der verhunzten hier im Forum, deshalb 
habe ich die Kommentare oben entfernt)

Das hört sich jetzt umfangreich an, ist aber überschaubar.

Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Guten Morgen Stefan,

Entschuldige das ich mich erst jetzt melde, aber konnte gestern erstmal 
meinen PC neu aufsetzen. :( (Festplatte lagerschaden) Ist aber gerade 
noch gutgegangen.
Nun zur Software:
Da werde ich mich wohl nochmal durchbeissen müssen. Die bekannten Makros 
htonl htons ntohl und ntohs kannte ich bis jetzt noch nicht, aber so 
etwas in der art hätte ich gerne von anfang an eingebaut. Ich bin 
nämlich mehr wie einmal auf verdrehte IP Adressen reingefallen. Mit den 
"magischen Zahlen" gebe ich Dir recht, ist wirklich schwer 
durchschaubar, ist aber leider irgenwann eingerissen, weil mir das zu 
viel schreibarbeit war. (z.B. buffer[ETHERNET_HEADER_LENGTH + 
IP_HEADER_LENGTH + Datalength + usw.])
Das mit den Kommentaren am Zeilenende wird mit Sicherheit besser, das 
dient mir im Moment dazu, meinen Code auch in einem halben Jahr noch zu 
verstehen. In C bin ich nämlich auch noch nicht so lange unterwegs (ca 2 
Jahre), in Assembler war das aber immer sehr Hilfreich. :)
Blöderweise habe ich die Hardware in der Firma liegengelassen, (jaja der 
Karfreitag), aber die Software kann ich ja schon mal anfangen umzubauen.

Vielen Dank erstmal und Frohe Ostertage

Gruß aus Köln

Frank

Autor: Stefan B. (stefan) Benutzerseite
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
No Panic ;-)

Manchmal tut ein bisschen Abstand ganz gut. Es fallen dann oft Bugs auf, 
die sonst unbemerkt durchschlüpfen.

Das mit der Schreibarbeit kann ich verstehen. Das lässt sich aber mit 
ein paar Makros beheben. Im Anhang eine Source, bei der das Durchgezogen 
wurde.

Kommentare sind nicht schlecht. Was mühsam ist, ist deine Art die Zeilen 
mitten im Statement umzubrechen, den Rest mit vielen Leerzeichen zu 
füllen und dann auf einer festen Position mit dem Kommentar zu beginnen. 
Das erschwert es neuen Code einzugeben, weil zuerst die Formatierung 
gebrochen werden muss und dann ggf. wieder hergestellt werden muss.

In dem Anhang ist das komplette Projekt. Die wesentlichen Änderungen 
stehen in comments.txt. Mach vor dem Austesten ein Backup deines 
Projekts, ich denke zwar, dass alle Änderungen sauber mit SVERSION/STEST 
gekapselt sind, aber sicher ist sicher. ARP, DNS, ICMP und DCHP habe ich 
nixht explizit überarbeitet, nur die offensichtlichen Stellen.

Ich kann im Moment mangels realer Hardware nicht weiter testen. Nützlich 
für die weitere Simulation im Debugger wären verschiedene Binärfiles mit

1/ einem korrekten TCP-Paket µC => PC (< 255 Zeichen)
2/ einen nicht korrekten TCP-Paket µC => PC (328 Zeichen HtmlTest?)
3/ einem TCP-Paket PC => µC, was ein Senden von 1/ oder 2/ auslöst

Wenn du mit Wireshark sowas mitschneiden kannst (ggf. von dem 
funktionierenden Webserver) wäre das klasse. Auf den colonia66 komme 
ich nicht drauf (Timeout bei der Abfrage) sonst hätte ich es selbst 
probiert, Testdaten zu beschaffen.

Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Stefan,

da hast Du ja ganz schön gewirbelt. :) Ich lese mich gerade mal durch 
deinen Code und muss sagen er ist wirklich besser lesbar. Wenn ich darf 
würde ich einige Sachen gerne übernehmen. Der colonia66 Webserver ist am 
besten zu jeder vollen Stunde erreichbar, da er dann eine mail 
verschickt und die Zeit synchronisiert. Kann Du die Wiresharkdateien 
lesen? Dann würde ich mal ein paar logs mit dranhängen.

Gruß aus Köln

Frank

Autor: Frank aus Köln (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Hallo nochmal,

hier die Wiresharkdateien, bei denen man den Fehler gut sehen kann.
Die 192.168.0.99 ist der anfragende PC, die 192.168.0.200 ist der 
AVR-Webserver. Ich habe noch nichts an der Software geändert, dazu 
brauch ich noch ein bisschen Zeit.

Gruß aus Köln

Frank

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Mit den Dateien müsste ich heute abend zurecht kommen.

Entweder als Binärdatei behandeln und den interessanten Part mit einem 
Hexeditor rauskopieren oder mit einem Analysetool (WinDump, Ethereal, 
Snort) auslesen.

Autor: Frank aus Köln (Gast)
Datum:
Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Stefan,

ich habe jetzt mal einen grossen Teil umgeschrieben und kann jetzt 
zumindest schon mal ein positives Zwischenergebniss mitteilen. Mit 328 
Bytes läuft es jetzt. :) Es scheint an der CRC Routine zu liegen.
Ich habe nach und nach die TCP Funktionen umgeschrieben und hatte immer 
noch die selben Probleme. Nachdem ich die checksum Funktion ausgetauscht 
habe ging es mit 328 Bytes. Wenn ich allerdings nochmal ca. 80Bytes 
dranhänge dann hakt es wieder. Mit Wireshark kann ich das Paket dann 
nicht mehr sehen, scheint so als würde der ENC das Paket dann als 
ungültig verwerfen. Per ICMP ist der Webserver aber dann noch ereichbar. 
Ausserdem wird jetzt die CRC bei UDP nicht mehr richtig gerechnet. 
(checksum = 0xFFFF).
Im Anghang ist jetzt erstmal die Wireshark datei mit dem derzeitigen 
Stand.
TCP 328 Bytes ok, DNS abfrage checksummenfehler bei UDP.

Gruß aus Köln

Frank

Autor: Dirk Broßwick (sharandac)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Frank

Ich findes es schön das du an meinen Code weiterbastelt und ihn nach 
einen AT89C51ED2 portiert hast oder es versucht und jetzt wieder auf 
einen ATmega644. Was ich mir aber stark wünsche ist das du auch den 
Urheber der eigentlichen Sourcen namentlich weiterhin erwähnst, da dies 
in der dem Orginalsource beiliegenden GNU-Lizenz ausdrücklich verlangt 
wird. Und da ich große Teile meines Sourcecode wiedererkenne + den 
Signifikanten Stellen im Code selber würde ich mir das auch für die 
Zukunft wünschen. Vielen DanK.

CA Dirk Broßwick

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das wäre/ist natürlich übel. Ehre wem Ehre gebührt und Lizenzen sind zu 
beachten.

Vom Programm und den WinPCAP-Dumps her, kann ich trotz intensivem 
Simulieren keine Problemursache ausmachen. Auffallend ist lediglich, 
dass in dem "Nicht OK"-Mitschnitt die Antwort abgeschnitten ist. Bis zur 
Übergabe an den enc28j60 ist in der Simulation der Sendebuffer jedoch 
OK. Ich kann also nur spekulieren, dass das Problem in der Ebene 
enc28j60 und tiefer liegt. In der Simulation kann ich z.B. kein Timing 
testen, da müsste Hardwaredebugging z.B. über UART ran.

Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo zusammen,

@Dirk

Du hast natürlich Recht, einige Grundlegende Teile des Sourcecodes 
stammen aus Deinem Originalcode. Ich möchte hier auch niemand etwas 
wegnehmen. Das was ich hier reingestellt habe ist meine derzeitige nicht 
lauffähige bastelversion. Ich möchte mich hier auch nicht mit fremden 
Federn schmücken. Ich bin immmer noch C anfänger und habe mir diverse 
Webserverprogramme angeschaut. Die Teile die ich verstanden habe, und 
die ich auch nicht viel anders hätte schreiben können, habe ich 
implementiert.
Teile die ich nicht verstanden habe, habe ich selber geschrieben und 
mich durch die entsprechenden Protokolle gewühlt. Viele sachen wie z.B 
POP3 oder SMTP habe ich woanders gar nicht gefunden und selber 
geschrieben. Deine DHCP Geschichte habe ich mir auch angesehen und die 
lief bei mir z.B. gar nicht. Nach diversen änderungen lief das dann auch 
an 4 verschiedenen DHCP Servern. Das habe ich Dir auch in diesem Thread 
Beitrag "ENC28J60 Basics[Beispielprogramm in AVRGCC für atmega8]" geschrieben.
Ich habe kommentierungen so geändert das ich diesen Code verstehe, aber 
alles erstmal nur für mich privat. Das hier noch die 
Originalquellverweise fehlen war für mich selbst (zu Hause) erstmal 
zweitrangig. Das jetzt dieser "private" Code hier ist, war nur so ein 
Schnellschuss, da ich im moment an einem problem nicht weiterkomme. 
Sollte dieses Problem beseitigt sein und ich meine ich müsste den 
fertigen Code hier reinstellen dann sind auch die Quellenverweise für 
die entsprechenden Codestellen wieder drin. Ich möchte das jetzt hier 
aber auch nicht einfach so stehen lassen als würde ich mir nur Code 
zusammenklauen und das dann als mein geistiges Eigentum "verkaufen" 
wollen. Das ist nur für mich privat, ich handel nicht damit, ich 
verkaufe nichts und ich verdiene kein Geld damit.
Sollte ich ich Dir mal bei Deinem Projekt helfen können, so werde ich 
das auch gerne tun. ( sofern man das als C Anfänger kann )

Gruß aus Köln

Frank

Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Stefan,

ich denke auch, das ich mir jetzt erstmal eine RS232 schnittstelle an 
den AVR hängen muss, um den Fehler weiter einzukreisen. Da muss ich aber 
erstmal wieder bei Reichelt bestellen, da ich im moment keinen MAX für 
3,3V habe.

Gruß aus Köln

Frank

Autor: Dirk Broßwick (sharandac)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
@Frank

Nee, es geht ja auch nicht ums wegnehmen, das ist ja auch so gewollt, 
deshalb steht ja der Source unter GPL :-). Es ärgert einen immer nur 
wenn einer vergisst das es eben auch unter GPL steht. Da mein Code schon 
in diversen Projekten verwendet wird, zwar nicht in der Version die du 
benutzt hast, freue ich mich wenn ich lobend erwähnt werde :-). Der Code 
auf den du baust ist zwar etwas älter denn er ist in der jetzigen schon 
wesentlich überarbeitet worden.
Da ich auch meine Studienarbeit dadrüber schreibe und das Projekt sich 
schon zu einem ganzen Framework entwickelt hat würde ich dir auch gerne 
helfen b.z.w. die jetztigen Sourcen für einen ATMega2561 mit externen 
RAM zukommen lassen. Diese lassen sich denke ich mal auch leicht für 
einen ATMega644 anpassen, da die erweiterte Entwicklung für diesen 
gedacht war. Wenn du noch Fragen haben solltest kannst du ja mal per PM 
über Mikrocontroller.net schreiben.

CA Dirk

Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Dirk,

eigentlich geht die ganze Geschichte ja noch viel weiter runter.
Ich habe bis zu diesem Thread 
Beitrag "ENC28J60 Basics[Beispielprogramm in AVRGCC für atmega8]" immer nur in Assembler 
auf dem 8051 meine Programme geschrieben. Ich habe mehrmals versucht 
einen einstieg in C zu bekommen, aber spätestens nach dem berühmten 
"Hello World" hat sich das Thema schnell wieder erledigt. Als ich dann 
diesen Thread gelesen habe, dachte ich ich könnte den ENC mit einem 8051 
verheiraten, in Assembler. :( (Macht nicht wirklich spass)
Dann kamst Du mit deinem Source. Das war für mich im Prinzip der 
Einstieg in C. Dieser Code war für mich als Einsteiger gut zu verstehen 
und ich konnte viel lernen. Also habe ich das ganze für den 8051 und den 
SDCC umgebaut und auch jede menge neu dazugeschrieben. Ich denke mit dem 
jetzigen wissen, könnte ich auch versuchen das ganze Projekt selbst neu 
zu schreiben, aber das macht in meinen Augen keinen Sinn.
Um es kurz zu machen:
Gäbe es nicht so Leute wie Du, die ihren Code veröffentlichen würden, 
dann hätten es viele Einsteiger schwer, etwas zu lernen. Ich habe 
seitdem, alle meine Assemblerprogramme auf C umgebaut.
Ich hoffe ich habe Dich nicht zu sehr geärgert ;) aber das war ganz 
bestimmt keine absicht von mir.

Gruß aus Köln

Frank

Autor: Dirk Broßwick (sharandac)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
ÄH, der ansicht war ich mit Assembler auch mal gewesen, aber das geht 
nur bis zu einer bestimmt komplexzität gut, danach gehts einfach nicht 
mehr da es sehr unübersichtlich wird und man mehr mit Registern und 
ähnlichem beschäftigt ist als mit den Problem selber :-).
Das ist jetzt aber schon einige Zeit her. Der Webserver war auch das 
erste wirklich große Projekt welches ich in C geschrieben haben. Aus 
heutiger sicht ist mein Programmierstil nicht besonders gut gewesen. Man 
lernt halt immer dazu. Am besten ist es wenn man richtig einsteigen 
möchte erst mal am PC mit C zu programmieren um sich sicher zu sein wie 
C arbeitet, zumal die möglichkeit des debuggens wesentlich einfacher 
sind. Bücher sind dafür mit das beste Werkzeug und das lesen der Threads 
hier im Forum. Nach und nach erschließen sich die möglichkeiten von C 
einem wie von selbst und es fällt wesentlich einfacher ein Problem zu 
lösen, da man irgentwann nicht mehr mit der Sprache an sich kämpft :-). 
Da hilft aber nur üben, üben ... .
Aber zurück zum Sourcecode, da ich immer noch einige altlasten in meinen 
Sourcen mit rumschleppe würde mich nochmal der Code zum DHCP-Client 
interessieren, da ich diesen bis jetzt seid dem auch nicht mehr 
angefasst habe. Du hattest geschrieben das dieser jetzt reibungsloser 
funktioniert, hast du das Problem genauer gefunden? Und wenn ja, wo 
liegen die probleme?

CA Dirk

Autor: Frank aus Köln (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo Dirk,

ist jetzt auch schon fast wieder ein Jahr her, wo ich mich das letzte 
mal mit dem DHCP-Client auseinandergesetzt habe. Aber hier hatte ich das 
Problem mal beschrieben.
Beitrag "Re: ENC28J60 Basics[Beispielprogramm in AVRGCC für atmega8]"

Und zwar schickst Du bei der Funktion DHCP_SendDiscover keinen "Client 
Identifier" mit. Das Problem taucht nicht bei jedem DHCP Server auf, 
aber bei meinem Netgear konnte ich das schön beobachten. In Problemfall 
bekommt der Server zwar den Request aber antwortet danach einfach nicht 
mehr.
Vermutlich verwirft der Server das Paket einfach als ungültige Anfrage.
In dem oben im Thread angehängten Quellcode ist meine änderung bereits 
mit eingebaut. Das kannst Du wahrscheinlich direkt mit einbauen.

Gruß aus Köln

Frank

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.