Hallo *, nachdem hier nun eine Reihe von Webservern für den AVR/ENC veröffentlicht worden sind, möchte ich heute einen etwas anderen Ansatz vorstellen. Bei dem angehängten Code geht es nicht darum einen weitern WebServer, oder eine sonst wie geartete "heal the world Applikation" für den AVR/ENC implementieren, sondern lediglich den uip-ipstack von Adam Dunkels für den AVR + ENC zu portieren. Statt einer Eierlegendenwollmilchsau, ist der vorgestellte code bestenfalls ein Ei, aus dem sich dann aber andere Projekte besser entwicklen lassen sollten, als aus den großen monolitischen WebServer projekten. Beim lesen des Codes wird mancheiner feststellen, dass das ganze verdächtig nach copy&paste aus der UIP Dokumentation aussieht und genau das ist der Fall. Statt das Rad neu zu erfinden, greife ich ganz bewust auf bestehende Programme zurück, zumal wenn sie so gut dokumentiert sind. Grundlage für das Projekt sind daher auch verschieden Codefragmente die ich aus anderen Projekten zusammen getragen habe. Die hier implementierte uipAppCall Funktion scheint auf den ersten Blick ein bisschen unsinnig zu sein (sende die Empfangenen TCP Daten an PORTA), daher kurz beschrieben was ich damit eigentlich mache: Wie bei vielen anderen auch läuft bei mir ein VDR. Der ist allerdings nicht im Wohnzimmer sondern verrichtet im Keller seinen Dienst (wo er auch ruhig normale PC Geräusche von sich geben kann). Audio, Video und LIRC über 10m Kabel an zu schließen ist kein Problem, aber ein Display wie das OPTREX323, dass am ParPort angeschlossen sein will, geht auf die Entfernung nicht. Ich habe also serdisplib um die Funktion erweitert die Daten auch über einen Socket zu senden. Der AVR bekommt die Daten via Ethernet und schickt sie an das am PORTA angeschlossene Display. That's it. Das scheint nun alles ein bisschen sehr trivial zu sein, warum also veröffentliche hier den Code? Bei der Suche nach einer Lösung für mein Problem habe ich einige WebServer Implementationen gefunden. Für das, was ich machen will, ist das aber der totale Overkill. Wenn ich mir die Mühe mache den Code für den AVR zu schreiben, kann ich auch den Code für den Server schreiben und muss dabei nicht versuchen Protokolle wie HTTP oder FTP zu implementieren, die für mein Projekt nicht nötig sind. Versuche die vorgestellten WebServer auf das zu reduzieren was ich eigentlich brauche hab ich schnell aufgegeben (man kann bei einigen schon von Glück reden wenn sie sich für einen anderen Prozessor compilieren lassen). Vielleicht haben ja auch andere ähnliche Anforderungen und sind nicht auf der Suche nach einem komletten WebServer mit allem drum und dran... BTW, nichts gegen die WebServer Projekte, bei meiner Arbeit war das sehr hilfreich und selbstverständlich sind das tolle Projekte, nur eben sehr spezifisch und schlecht an andere Bedürfnisse an zu passen. Hardware: Im großen und ganzen habe ich mich an der auf https://berlin.ccc.de/wiki/AVR-Board_mit_Ethernet vorgestellten Schaltung orientiert, allerdings ist das Ganze auf Streifenraster und den SMD Spannungswandler habe ich durch LF33CV (csd Bestellnummer 23-251) ersetzt um mir das SMD Löten zu sparen. Als MagJack verwende ich den csd 015-54085 (Datenblatt für Anschluss beachten). Den MagJack auf die Streifenraster Platine zu bringen ist natürlich ein bisschen Fummelei, aber selbst mit meinen bescheidenen Fähigkeiten machbar. Prozessor ist ein ATMEGA16L-8PU der ebenfalls über den LF33CV versorgt wird (dazu später mehr). UIP: Wirklich toll dokumentiert. Einigen wird auffallen, dass ich die "uip periodic timer" nicht implementiert sind. Es geht auch ohne, allerdings setzt das vorraus, dass sich alle im Netzwerk richtig verhalten. Ohne periodic timer muss sich der AVR darauf verlassen, dass er z.B. vom peer ein FIN bekommt um den socket wieder frei zu geben, geschieht das nicht, bleibt UIP natürlich irgendwann hängen. Die Timer zu nutzen kann aber eigentlich nicht so problematisch sein: // pseudo code !!!! #define TIME_TICKS TIMER_VORTEILER F_CPU / 1024 #deinfe TIME_TICKS TIMER_VORTEILER / 256 #define CLOCK_CONF_SECOND 1000 / TIME_TICKS // siehe clock-arch.h Nun noch den timer nutzen um ein 32bit integer hoch zu zählen. Den UIP code habe ich in uipopt.h geändert: Statt #define UIP_TCP_MSS (UIP_BUFSIZE - UIP_LLH_LEN - UIP_TCPIP_HLEN) ist das nun #define UIP_TCP_MSS (UIP_BUFSIZE - UIP_LLH_LEN - UIP_TCPIP_HLEN – 4) Warum?? Irgendwie scheint UIP mit der von ENC mitgelieferten CRC nicht zu recht zu kommen. Auch ein len -= 4 in enc28j60PacketReceive hat das Problem nicht gelöst wenn der UIP Stack Packete nahe an MSS bekam. Ich versteh zwar noch nicht warum len -= 4 das Probleme nicht löst, aber sei's drum, die MSS um 4 zu verkleinern geht. uip-conf.h braucht übrigens enc28j60.h um die UIP_BUFFER_SIZE zu definieren #define UIP_CONF_BUFFER_SIZE MAX_FRAMELEN - 4 Interrupts: Nein, verwende ich nicht (der ENC wird zwar mit Interrupts initialisiert, ich werte sie aber nicht aus), macht das Programm IMHO nur unübersichtlich, bringt aber keinen Benefit. Der Sender sendet das nächste Paket erst dann wenn noch Platz im Buffer ist oder ein ACK vom AVR eintrifft. Abholen von Paketen ist also nicht zeitkritisch. Noch ein Wort zur Performance und TCP: serdisplib sendet mit jedem write auf den ParPort ein Byte an den socket. Wenn der AVR schnell genug wäre, hätte das zur Folge, dass ein ziemlicher Overhead entstehen würde (Ethernetheader + IP header + TCP header + CRC = 58 Bytes für 1 Byte Nutzdaten....). Allerdings ist der AVR nicht der schnellste und der sendende TCP stack fast die zu sendenden Bytes zusammen. Die Übertragung ist also optimaler als es zunächst scheint (dank TCP). Kann aber durchaus sein, dass man dass noch verbessern kann. Das OPTREX323 am AVR ist zwar nicht ganz so schnell wie am ParPort, aber mit ~40-50% der Geschwindigkeit immernoch völlig im Rahmen. Anschluss des OPTREX323 an den AVR: Den AVR habe ich ursprünglich auf 3,3 V mit dem ENC laufen lassen (da das Datenblatt des ENC erwähnt, dass bei 5V MC eine, wenn auch nicht wirklich kompliziert zu realisierende Pegelwandlung nötig ist). Die 3,3 V aus PORTA reichen aber nicht als Signalisierung für das Display. Erfreulicherweise funktioniert der ENC auch ohne Pegelanpassung mit dem an 5V betriebenen AVR. Läuft der AVR auf 5V klappt es auch mit dem Display. UDP: abgeschaltet, allerdings denke ich darüber nach, einen IR-Empfänger via max232 am AVR anzuschließen. Dann wäre UDP natürlich sinvoll um das ganze an LIRC zu senden. Noch ein letzter Tipp: Gut lässt sich das Ganze mit NetCat überprüfen, wenn man z.B. die Daten statt auf PORTA einfach an uip_send() gibt (sprich, return to sender). Alles, was man mit NetCat sendet, bekommt man zurück (auch wenn die Zeile, die man in NetCat eingibt, deutlich über der MSS liegt ...) Gruß Sebastian P.S. Vielleicht ist das ja auch alles schon längst ein alter Hut und alle anderen, außer mir, haben das schon lange im Einsatzt. Dann bin ich nur einfach zu doof richtig zu suchen ;-)
Hallo *, heute morgen ist es mir wie Schuppen aus den Haaren gefallen warum das mit der CRC und UIP so merkwürdig ist. In enc28j60PacketReceive muss man len von vornherein (nach dem auslesen der Framelänge) auf len -= 4 setzten damit die CRC garnicht erst gelesen wird (wertet eh keine funktion aus, sollte der ENC selber machen). Damit ändert sich wieder #define UIP_TCP_MSS (UIP_BUFSIZE - UIP_LLH_LEN - UIP_TCPIP_HLEN – 4) in #define UIP_TCP_MSS (UIP_BUFSIZE - UIP_LLH_LEN - UIP_TCPIP_HLEN) die UIP Bibliothek ist also wieder im "original zustand" Bei meinem letzten posting war noch ein kleiner typo..... eigentlich wollte ich #deinfe TIME_TICKS F_CPU [slash] 1024 [slash] 256 schreiben, wurde aber etwas merkwürdig formatiert.... daher jetzt mit [slash] statt / Gruß Sebastian P.S. wie schon gesagt komm ich ganz gute ohne die periodic timer aus, werde das aber nochmal implementieren, nur der Vollständigkeit halber
Hi, ich hab mir deine Sourcen oder die des uIP noch nicht angeschaut, aber was mir noch nicht klar ist: Hast du den Code auf den AVR und ENC portiert, oder "nur" als Basis für deinen Stack genommen? Steffen.
Hallo, @Steffen, er hat deb µIP Stack an den AVR angepasst oder schon einen angepassten benutzt. Sowie den ENC Treiber eingebaut. Gruß Ulrich
Hallo Steffen, Ulrich hat recht, ich habe den uIP stack an die Kombination AVR/ENC angepasst. Am uIP Stack ist jetzt nichts mehr verändert, seit dem mir aufgegangen ist, was mit meine CRC Überlegungen falsch war. Alle Anpassungen an UIP finden in uip-conf.h statt. Der rest der uip sourcen ist unverändert (außer dass in uipopt.h noch ein Kommentar rum geistert). Wenn ich die Timer fertig hab poste ich das ganze nochmal. Gruß Sebastian P.S. wie schon erwähnt ist das eine "Zwerge auf Schultern von Riesen" Arbeit, ich habe bestehenden code lediglich etwas verändert.
@Steffen P.S. der uIP source code dürfte interessant sein, wenn mal selber einen TCP/IP stack schreiben will, für die Programmentwicklung auf Basis von uIP ist die API Dokumentation sehr viel hilfreicher s.a. http://www.sics.se/~adam/uip/index.php/Main_Page
Benutzt dein Code eigentlich die Hardware-Checksum Funktionen des ENC? Oder wird die vom AVR berechnet? (Eher nicht, soweit ich deinen Code überflogen habe) Ich hab leider noch keine AVR-ENC-uIP Anpassung gesehen, die das nutzen würde, vermutlich weil uIP dafür umgekrempelt werden müsste: (zuerst in den ENC Transferieren, dann CHKSum berechnen, dann senden...) Oder ist der Rechenzeitgewinn dadurch garnicht so groß dass es sich lohnen würde? /Ernst
Hallo Ernst, gute Frage... die CRC müßte vom ENC berechnet werden (ohne dass ich jetzt nochmal durch's Datenblatt gegangen bin um das zu verifizieren - wie Du im Header vom enc Treiber sehen kannst, kommt der Code nicht von mir sondern von Pascal Stang - allerdings habe ich ein bisschen "aufgeräumt"). uIP scheint keinen Code zu haben mit dem die CRC berechnet wird (zumindest liefert ein grep -i crc * auf den Quellcode keinen Hinweis und auch in der Dokumentation findet sich nicht einmal das Wörtchen CRC). Es wäre eigentlich auch unsinnig in einer OSI Layer 3/4 Applikation bzw. einem API eine Layer 2 Checksummenberechnung durch zu führen. Ein weiterer Hinweis, dass die CRC vom ENC berechnet wird ist, dass uIP die CRC erst garnich zu Gesicht bekommt, wird direkt abgeschnitten (im Treiber enc28j60PacketReceive // read the packet length len = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0); len |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8; // remove CRC from len (we don't read the CRC from // the receive buffer) len -= 4; Gruß Sebastian
Hrmm...
1 | static u16_t chksum(u16_t sum, const u8_t *data, u16_t len) |
in der uip.c berechnet die CRC Summe. Wird aber nur compiliert, wenn UIP_ARCH_CHKSUM nicht deklariert ist. Das TXCRCEN-Bit im ENC wird aber gesetzt, also wird vermutlich die Ethernet-Checksumme schon vom ENC berechnet (evtl redundant, wenn uIP die auch berechnet?). Inwieweit sich auch noch die IP-Crc da berechnen liesse, weis ich jetzt leider nicht, hab schon zu lang nix mehr mit dem Chip gemacht...
Hi Ernst, ob die Funktion die CRC berechnet weiss ich nicht - den Algorithmus müsste ich mir erst raussuchen.... Wenn ich allerdings mal durch den Code "grepe" und nachseh' wo und wie sie verwendet wird, fällt mir auf dem ersten Blick nichts auf wo über den gesamten Ethernet Frame damit gebüglet wird (UIP checksummen berechnung usw ja... Aber mag sein, dass sich das irgendwo in den Tiefen noch verborgen hält - auch wenn das in Layer 3 nichts verloren hat - und ich nur falsch suche... Wie dem auch sei, in uip-conf ist UIP_ARCH_CHKSUM auf 0 gesetzt.... $ grep chksum * | grep -v tcpchksum | grep -v udpchksum | grep -v ipchksum | grep -v icmpchksum uip.c:chksum(u16_t sum, const u8_t *data, u16_t len) uip.c:uip_chksum(u16_t *data, u16_t len) uip.c: return htons(chksum(0, (u8_t *)data, len)); uip.c: sum = chksum(0, &uip_buf[UIP_LLH_LEN], UIP_IPH_LEN); uip.c:upper_layer_chksum(u8_t proto) uip.c: sum = chksum(sum, (u8_t *)&BUF->srcipaddr[0], 2 * sizeof(uip_ipaddr_t)); uip.c: sum = chksum(sum, &uip_buf[UIP_IPH_LEN + UIP_LLH_LEN], uip.c:uip_icmp6chksum(void) uip.c: return upper_layer_chksum(UIP_PROTO_ICMP6); uip.c: return upper_layer_chksum(UIP_PROTO_TCP); uip.c: return upper_layer_chksum(UIP_PROTO_UDP); uip.h:u16_t uip_chksum(u16_t *buf, u16_t len); uip_arch.h:u16_t uip_chksum(u16_t *buf, u16_t len); Dass sich die TCP checksumme mit dem ENC berechnen ließe, halte ich für unwahrscheinlich (aber es mag natürlich den ein oder anderen Hacker geben, der auf wundersame art das noch aus dem Chip rauskitzelt) Gruß Sebastian
P.S. wer lesen kann ist klar im Vorteil... UIP_ARCH_CHKSUM nicht (!!!) deklariert... dann wäre sie mit eingebunden... ich seh mir mal den Frame aus dem ENC genauer an (ob der nun 2 CRCs hat) und setze UIP_ARCH_CHKSUM mal auf 1.... Gruß Sebastian
Ich hab mir das auch mal überlegt. Aber so einfach lässt sich das glaube ich nicht in den uIP Stack implementieren. 1. Man legt das Paket bevor der Berechnung von TCP und IP Checksummen (Felder mit 0 vorbelegt) in den Enc28j60 und lässt den Hardware-Checksum drüberlaufen. 2. Man kopiert immer den jeweiligen Bereich, über den die Checksumme kreiert werden soll in den Enc28j60 und holt die Checksumme wieder raus. Version 1 wäre eventuell schneller als die Checksumme im AVR zu erzeugen. Aber das ließe sich glaube ich nicht so einfach in den Stack implementieren Version 2 wäre so lahm, dass es schneller wäre die Checksumme direkt im AVR zu berechnen.
Tja, ich glaube nicht das dies funktioniert, da der CRC-Algorithmus ein anderer ist als der TCP/IP-Prüfsummenalgorithmus. IMHO ist es sinnvoller die entsprechenden Routinen in uip in Assembler zu schreiben, damit sie ein bisschen schneller sind. Den wahnsinnigen Geschwindigkeitsvorteil bringt dies aber auch nicht wirklich. Trotzdem habe ich das mal für uIP-AVR gemacht (siehe Anhang) Volker
Hallo, ich habe eine Verständnisfrage, und zwar wird in der Dokumentation vom uIP Stack im Beispieltreiber: http://www.sics.se/~adam/uip/uip-1.0-refman/a00150.html#gb81e78f890dbbee50c533a9734b74fd9
1 | void
|
2 | devicedriver_send(void) |
3 | {
|
4 | hwsend(&uip_buf[0], UIP_LLH_LEN); |
5 | if(uip_len <= UIP_LLH_LEN + UIP_TCPIP_HLEN) { |
6 | hwsend(&uip_buf[UIP_LLH_LEN], uip_len - UIP_LLH_LEN); |
7 | } else { |
8 | hwsend(&uip_buf[UIP_LLH_LEN], UIP_TCPIP_HLEN); |
9 | hwsend(uip_appdata, uip_len - UIP_TCPIP_HLEN - UIP_LLH_LEN); |
10 | }
|
11 | }
|
immer uip_buf gesendet und wenn uip_len größer ist als UIP_LLH_LEN + UIP_TCPIP_HLEN auch noch uip_appdata. In diesem Quellcode wird aber immer nur uip_buf gesendet:
1 | enc28j60PacketSend(uip_len,&uip_buf[0]); |
Warum braucht man hier den Inhalt von uip_appdata nicht zu senden? MFG, GR
Hallo, Ich weiß, der Originalthread ist nun schon einige Zeit alt aber vl. kann mir jemand helfen. Ich habe dieses Beispiel auf einem Atmega16 mit 16Mhz Taktfrequenz implementiert, läuft soweit ganz ok. Nun habe ich den Effekt, wenn sich ein TCP Client auf den Listenerport meines Megas verbindet, nimmt dieser zwar die Verbindung an (bekomme auch das "CONNECTED"), jedoch kommt es zu keiner Verbindung (Client meldet, dass keine Verbindung aufgebaut werden konnte). Laut Wireshark sendet der Controller ein Reset Paket, jedoch kann ich die Stelle im Code nicht finden. Ev. hatte schon jemand mal einen ähnlichen Effekt bzw. kann mir ein paar Tipps geben. Vielen Dank im Voraus!
Hallo, ich hoffe mal dass dies hier noch jemand liest: so wie ich das sehe kann ich mit deiner implementierung nur auf anfragen antworten? das enc28j60PacketSend wird ja nur aufgerufen wenn vorher etwas eingegangen ist. sprich ich kann auch keine verbindung vom avr aus aufbauen? falls ich falsch liege, kann mir bitte jemand sagen wie ich das mache?
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.