Ich möchte mit dem LPC1768-DK2-board Daten per Ethernet austauschen. Ide
ist codeRed. Die Beispielcodes easyweb und uip laufen. Erste Frage: Was
ist besser geeignet, Easyweb oder uip?
Da uip zuerst funktioniert hat, habe ich mich vorläufig für uip
entschieden. das ganze läuft mit Proto_Threads. Das ist so kompliziert,
dass ich da nicht wirklich einsteigen möchte/kann. Möglicherweise muss
man das aber auch nicht, um uip benutzen zu können. Grundlegende
Funktion scheint uip.c/uip_process() zu sein. Im Kopfteil heißt es:
1
/*
2
* uIP is a small implementation of the IP, UDP and TCP protocols (as
3
* well as some basic ICMP stuff). The implementation couples the IP,
4
* UDP, TCP and the application layers very tightly. To keep the size
5
* of the compiled code down, this code frequently uses the goto
6
* statement. While it would be possible to break the uip_process()
7
* function into many smaller functions, this would increase the code
8
* size because of the overhead of parameter passing and the fact that
9
* the optimier would not be as efficient.
10
*
11
* The principle is that we have a small buffer, called the uip_buf,
12
* in which the device driver puts an incoming packet. The TCP/IP
13
* stack parses the headers in the packet, and calls the
14
* application. If the remote host has sent data to the application,
15
* this data is present in the uip_buf and the application read the
16
* data from there. It is up to the application to put this data into
17
* a byte stream if needed. The application will not be fed with data
18
* that is out of sequence.
19
*
20
* If the application whishes to send data to the peer, it should put
21
* its data into the uip_buf. The uip_appdata pointer points to the
22
* first available byte. The TCP/IP stack will calculate the
23
* checksums, and fill in the necessary header fields and finally send
24
* the packet back to the peer.
25
*/
Kernproblem für den Datenaustausch ist daher wohl der Zugriff auf
uip_buf. Zum Senden von Daten müssen Daten in den uip_buf gestellt
werden. Hat jemand dafür schon fertige Funktionen?
Für Anregungen zum Problem Datenaustausch per Ethernet wäre ich äüßerst
dankbar.
vielen Dank für die breite Unterstützung.
Wie man sieht, funktioniert der TCP-Empfang mit diesem Code:
1
/* receive packet and put in uip_buf */
2
uip_len=tapdev_read(uip_buf);
3
if(uip_len>0)/* received packet */
4
{
5
if(BUF->type==htons(UIP_ETHTYPE_IP))/* IP packet */
6
{
7
//uip_arp_ipin();//obsolet
8
9
uip_process(UIP_DATA);//==//uip_input();
10
memcpy(kktest,uip_buf,50);
11
kktest[50]=1;
Dargestellt wird eine Sendung der Daten 012345 per UDP. Angezeigt wird
Empfänger,Protokoll (17=UDP) und die payload. Damit ist eine Auswertung
eingehender TCP/UDP Daten problemlos möglich.
Was mir jetzt noch fehlt, ist die Sendung von Daten. Auch bei diesem
Problem vertraue ich auf breite Unterstützung.
Ich benutze oft das Makro PSOCK_GENERATOR_SEND, um Daten zu senden, die
berechnet werden.
Zum Beispiel Variable i enthält eine Integer zahl, die soll als
Menschen-Lesbarer String gesendet senden. Also ist der String zur
Laufzeit berechnet. Da hast du zwei Möglichkeiten:
1) Wandle die Zahl mit sprintf in einen String um, speichere den String
in ein Array und sende ihn mit PSOCK_SEND_STR. Du brauchst also ein
Array als Zwischenspeicher das groß genu ist, den String aufzunehmen.
2) Rufe PSOCK_GENERATOR_SEND auf, einer der Argumente ist eine Funktion,
die den Puffer füllen soll. Sie wandelt die Zahl mit sprintf in einen
String um und benutzt dabei direkt den I/O Buffer. Dann brauchst du kein
Array, also weniger RAM.
Allerdings muss das Programm dann jederzeit imstande sein, die
Umwandlung zu wiederholen (nämlich wenn der Empfänger einen
Übertragungsfehler meldet oder dessen Acknowledge unterwegs verloren
geht).
PSOCK_GENERATOR_SEND(&app_state->socket, myfunction, app_state);
Was man dabei beachten sollte, ist dies:
PSOCK_GENERATOR_SEND sendet bei jedem Aufruf genau ein Ethernet Paket.
Was da gesendet wird, muss also (mitsamt Protokoll-Overhead) in den
Puffer passen. Außerdem ist es nicht gut, auf diese weise viele kleine
Pakete zu senden, weil sonst der Overhead größer ist, als die Nutzdaten.
Zum Beispiel würde ich nicht auf diese Art 10.000 Integer zahlen einzeln
senden. Denn das ergäbe 10.000 mini kleine Pakete.
Achtung: Du kannst Dich nicht darauf verlassen, dass die Ethernet Pakete
so groß sein dürfen, wie der Puffer. DSL überträgt zum Beispiel maximal
rund 1400 bytes soweit ich mich recht erinnere, als rund 1300 bytes
Nutzdaten.
Die Funktion uip_mss() verrät Dir, wie viele Bytes du tatsächlich
maximal am Stück senden kannst.
Ach ich Esel. ich habe gar nicht bemerkt, dass ed Dir um UDP geht. Und
ich labere dich voll von wegen TCP...
naja, für UDP findest Du in meinem Projekt auch was zum Abgucken, und
zwar in der Datei dhcpc.c.
uip_send(...) ist die gesuchte Funktion.
es gibt auch eine uip Doku und Beispiele wie den Webserver. Es wird
zyklisch ein uip_periodic() aufgerufen und darin deine appcall(). Darin
wird der Verbindungszustand geprüft und bei connected wird
handle_connection() als Standard abgearbeitet. Und die sieht so aus das
erst auf ankommende Daten geprüft wird und dann auf ausgehende. Für die
ausgehenden Daten wird dann der buffer+len gesetzt. Damit die state
machine für das Senden richtig funktioniert muss man aber schon die PT_
Funktionen nutzen.
Für UDP wird eine uip_udp_apcall() aufgerufen.
Vielen Dank, das ist doch schon mal sehr hilfreich. Mir geht es auch
nicht um UDP. Mit TCP kann man auch UDP empfangen. Reines UDP
funktioniert bei meiner uIP-Version nicht. Die UDP-Verbindung habe ich
nur deswegen aufgezeichnet, weil ich ein UDP-Terminal auf dem PC habe.
UDP und TCP sind zwei paar Schuhe, die gemeinsame Sohle ist IP, auf dem
Level hast du die Pakete abgefischt. Damit musst du aber das TCP
Protokoll selber implementieren und das ist zu mühselig wo dir uip das
doch schon liefert.
Ausserdem hast du das ARP auskommentiert was man auch nicht tun sollte.
Damit geht deine Verbindung kaputte wenn dein Gegner oder ein Switch
nach einer Zeit die IP-MAC Zuordnung verwirft. Die Paketbehandlung in
der mainloop oder uip_task sollte man erst mal so lassen wie sie ist.
Der Angriffspunkt ist dann das Makro UIP_APPCALL in uipopt.h. Dann
brauchst du nur noch die Behandlung für connect und Daten rein/raus zu
bauen.
Wenn du TCP Server für mehrere Ports implementieren möchtest sollte die
appcall Funktion ein Verteiler sein, etwa so:
1
#include"uip.h"
2
#include"fs20d.h"
3
#include"httpd.h"
4
5
voidtcpdispatcher_init(void)
6
{
7
httpd_init();
8
fs20d_init();
9
}
10
voidtcpdispatcher_appcall(void)
11
{
12
switch(uip_conn->lport)
13
{
14
caseHTONS(80):
15
httpd_appcall();
16
break;
17
caseHTONS(3333):
18
fs20d_appcall();
19
break;
20
}
21
}
Für UDP muss in uip-conf.h die Unterstützung mit dem define
UIP_CONF_UDP=1 eingeschaltet werden und in der main loop auch die
Behandlung für uip_udp_appcall (ähnlich der TCP Verarbeitung) drin sein.
Jojo S. schrieb:> Ausserdem hast du das ARP auskommentiert
uip_arp_ipin() ist nur eine Scheinfunktion ohne Inhalt:
#define uip_arp_ipin()
Warum RDB so etwas in einem Beispielcode läßt, verstehe ich nicht.
Bei Aktivierung von UDP kommt die Fehlermeldung
unknown type name 'uip_udp_appstate_t' uip.h /RDB1768cmsis2_uIP/uip
line 1223 C/C++ Problem
es gibt zwar eine structure uip_tcp_appstate_t nicht jedoch
uip_udp_appstate_t?
das RDB_uip Beispiel habe ich auch gefunden, warum das uip_arp_ipin()
auskommentiert ist weiss ich jetzt ad hoc auch nicht. Die Funktion ist
jedenfalls vorhanden und nur auskommentiert, in uip_arp.h per define und
in uip_arg.c per '#ifdef 0'. Es gibt aber noch ein uip_arp_arpin() und
das ist aktiv. Vielleicht ist die erste Funktion aus Performancegründen
auskommentiert worden, die wird ja bei jedem ankommenden Paket
ausgeführt.
Den udp_app_state Type soll man sich selber definieren in uipopt.h, das
kann ein void Pointer oder sonstwas sein damit man sich
Verbindungsabhängig noch einen Status merken kann.
typedef int uip_udp_appstate_t;
Ist schon länger her das ich damit ein UDP gesendet habe und ich
erinnere mich das es fummelig war, an dieser Stelle habe ich glaube ich
auch lange gesucht.
einen Sende-frame kktest zusammengestellt, über tapdev_send in den
uip_buf
geladen. Das device versucht zu senden, bei wireshark kommt aber nichts
an. Beim Debugging sieht das ganze sehr zerhackt aus. Es gibt nicht
nachvollziehbare Rücksprünge. Ich fürchte, dass das am Protothread
liegt.
Da ich noch relativ weit am Anfang bin, nochmal die Systemfrage
-insbesondere an Stefanus, der die Protothreads offensichtlich
verstanden hat:
uip scheint mir eher ein Auslaufmodell zu sein, wie die Funktion
uip_arp_ipin zeigt, die bei einer Programmpflege - da fehlerbehaftet und
überflüssig - im Programm nicht mehr auftauchen dürfte. Adam Dunkel
arbeitet selbst bei lwip mit, was das Ende der von ihm entwckelten
Protothreads bedeuten dürfte. Ist diese Einschätzung richtig?
Welches der möglichen Stack-Programme für Arms wird am sinnvollsten
eingesetzt?
-uip
-lwip
-easyweb
oder macht es möglicherweise sogar Sinn, den Webserver von U-Radig
auf den Arm anzupassen?
Ich bin interessierter Laie, es reicht mir wenn es irgendwie läuft.
karl k. schrieb:> tapdev_send(kktest,50);> ...> einen Sende-frame kktest zusammengestellt, über tapdev_send in den
so geht das nicht. tapdev_send() ist die low level Funktion um einen
Frame zu senden, die wird von uip aufgerufen und die must du in Ruhe
lassen.
Zu sendende Daten müssen in den Buffer nach uip_appdata und uip_len muss
>0 gesetzt werden. In der main loop wird dann bei uip_len > 0 das Senden
ausgelöst. Das setzt aber vorraus das auch eine richtige TCP-Verbindung
aufgebaut wurde, sonst steht im Header Murks. Nur die IP Adresse
reinpatchen reicht nicht.
Das uip_arp_ipin() ist kein echtes Problem, auskommentieren und gut ist.
Den gleichen Code findet man auch in dem weitergeführten Contiki
Projekt. In der main loop wird auf das ARP reagiert, dafür gibt es einen
eigenen Ethernettype. Der auskommentierte Teil war für ein Refresh bei
beliebigen IP Paketen mit passender MAC Adresse, das soll man aber gar
nicht tun.
uip ist schon brauchbar, lwip ist 'schöner' und an den BSD sockets
orientiert, braucht aber mehr Resourcen. lwip habe ich auch schon in
mehreren kommerzielen Produkten gesehen, Contiki ist wohl eher ein Mini
Betriebssystem für kleine Geräte.
Das Beispiel von dem du ausgegangen bist enthält doch einen Webserver,
richtig? Funktioniert der denn wenn du den Code unverändert lässt?
vielen dank für den Link auf das manual.
Ich habe die Beispiele ausprobiert. Sie gehen alle nicht.
Neuer Ansatz:
void uip_send(const void *data, int len);
* Send data on the current connection.
*
* This function is used to send out a single segment of TCP
* data. Only applications that have been invoked by uIP for event
* processing can send data.
*
* The amount of data that actually is sent out after a call to this
* funcion is determined by the maximum amount of data TCP allows. uIP
* will automatically crop the data so that only the appropriate
* amount of data is sent. The function uip_mss() can be used to query
* uIP for the amount of data that actually will be sent.
->Daten zusammenstellen + app invoke for event processing
1.Problem: Zusammenstellen der Daten im array kktest
Die von mir verwendete Funktion xDefaultPartUDPPacketHeader aus lwip
passt irgendwie nicht.
der Aufbau muss wohl sein:
udp: IPUDPH 20iph + 8udph =28byte
tcp: IPTCPH 20iph + 20tcph =40byte
8udp byte ist sport+dport+len+chks
20 tcp byte beginnend ab sourceIP bis urgent pointer?
20 ip byte verion bis destip
Wo gibt es im Programm eine Maske zum Ausfüllen dieser Daten oder muss
man dies von Hand machen?
Meine bisherigen Sendeversuche haben jedenfall schon deswegen nicht
funktioniert, weil der header nicht stimmte.
Ich empfinde diese Protothreads auch als wilde Trickserei, aber
irgendwie funktioniert es ja... Im do{}while(0) kann der Code nicht
hängen, ist ja einfach eine Schleife die einmal ausgeführt und nicht
wiederholt wird. Der Grund ist Fehler bei der Makroauflösung zu
verhindern:
http://c-faq.com/cpp/multistmt.html
Eine gute und ausführliche Beschreibung der Protodingens findest du
hier:
http://stefanfrings.de/avr_io/protosockets.html
Um etwas zu Senden musst du dich erstmal entscheiden ob TCP oder UDP.
Wenn TCP dann ist eine Verbindung nötig, dann ist die nächste Frage ob
die Verbindung aktiv (Client) oder passiv (Server) aufgebaut werden
soll. Bei UDP ist keine Verbindung nötig, aber du musst dem uip per
uip_udp_bind() sagen wohin du senden möchtest. Diese appcall Geschichte
ist dann trotzdem nötig, wenn deine app aufgerufen wird darfst du mit
uip_send() senden, der Header wird dann von uip gefüllt.
Das problem memcpy ist erledigt. Daten werden jetzt mit uip_send richtig
eingesetzt.
Viele meiner Probleme scheinen an der Optimierung zu liegen. Do while(0)
fliegt dabei raus. Deswegen funktioniert zb uip_periodici nicht. Heute
abend geht es weiter.
karl k. schrieb:> No source available for "memcpy() at 0x3160"
Das ist aber kein Fehler, das heisst ja nur das zur genannten Funktion
kein Quellcode vorliegt. memcpy() ist in der C-Runtime und da kann man
davon ausgehen das sie tut was sie soll. Oder im Disassembler
durchsteppen wenn man mag. memcpy() ist natürlich immer potentiell
gefährlich weil man sich den Speicher überschreiben kann, ohne
Speicherschutz Mechanismen bekommt man das nicht sofort mit.
Und deine 'Optimierung' solltest du lieber lasser, für ein while(0)
generiert dir der Compiler gar keinen Code aber verhindert Syntaxfehler
bei der Makroexpandierung.
Wenn uip_periodic() nicht funktioniert hast du etwas anderes
falschgemacht. Wie sieht deine appcall() Anwendung denn jetzt aus?
uip_periodic() ist für TCP, wolltest du nicht UDP senden? Oder meintest
du uip_udp_periodic()?
> uip scheint mir eher ein Auslaufmodell zu sein
Mir auch, aber nicht wegen mutmaßlicher Fehler sondern weil lwip
komfortabler ist und dazu passende Mikrocontroller inzwischen auch für
Hobbyelektroniker leicht zu haben sind.
uIP ist meines Wissens nach ohne Alternativen, wenn man Mikrocontroller
mit wenig RAM (<4KB) verwenden möchte. Der geringe Speicherbedarf
verursacht wiederum das größte Manko: uIP hat eine feste Window-Size von
1, weswegen vor allem als Webserver im öffentlichen Internet lahm ist.
Wobei ich generell davon abrate, solche potentiell unsicheren Sachen ins
Netz zu hängen.
Ich finde, dass die Programmierung von Webservern auf einem Multitasking
OS (wie Linux) 100x komfortabler ist, als auf Mikrocontrollern. Also
mach das nur, wenn es wirklich unbedingt sein muss oder wenn Du
sportlichen Ehrgeiz dafür hast.
Jojo S. schrieb:> Wenn uip_periodic() nicht funktioniert hast du etwas anderes> falschgemacht.
Es ist tatsächlich so, dass die do-while(0)-Macros vom vom codeRed
compiler wegoptimiert werden. Aber dieses Problem ist gelößt.
Ich möchte im Prinzip einen Ethernet-Hausbus aufbauen. Ob Daten per TCP
oder per UDP gesendet werden, ist im Prinzip egal.
Ich hänge derzeit bei uip_process: Wenn ich irgendetwas an einen per
uip_listen geöffneten Port sende, wird die UIP_APPCALL in uip_process
nicht aufgerufen weil die flags nicht stimmen.
Stefanus schrieb:> wegen mutmaßlicher Fehler
Die Fehler liegen sicher nicht im uip-Programm. Ein solches Programm zu
erstellen ist eine gigantische Leistung. Es ist nur leider sehr
schwierig, dieses Programm anzupassen.
Ich mach jedenfalls erstmal ein paar Tage Pause.
APPCALL ruft den dispatcher auf. Das funktioniert aber nur mit dem
httpd-port 80. Obwohl andere ports per uip_listen initialisiert sind,
wird der dispatcher nicht angesprungen, UIP_APPCALL() in uip_process()
also nicht ausgeführt. wenn ich Port 80 eine andere Funktion zuweise
wird diese bei Aufruf der Webseite ausgeführt, nicht jedoch wenn ich per
UDP-Terminal an POrt 80 sende. Wie kann ich das Programm dazu bringen,
dass andere Ports als Port 80 den dispatcher aufrufen?
karl k. schrieb:> wird diese bei Aufruf der Webseite ausgeführt, nicht jedoch wenn ich per> UDP-Terminal an POrt 80 sende.
UDP != TCP. Mit deinem UDP Terminal kannst du kein TCP Paket senden.
Probiere mal 'telnet lpc1768-ipadresse 60', dann sollte deine
example_app1() angesprungen werden.
Die Portnr. ist dem uip egal, das kennt keine reservierten Systemports.
Und Telnet akzeptiert normalerweise auch beliebige ports wenn die als
Option angegeben wird.
Alternativ kannst du das Terminalprogramm 'putty' und da die 'raw'
session als Option verwenden:
http://www.chiark.greenend.org.uk/~sgtatham/putty/
Um mit UDP zu arbeiten muss wie geschrieben das UDP #define gesetzt
sein, eine uip_udp_appcall angelegt werden und statt uip_listen()
uip_bind() benutzen.
uip_listen ist nur für TCP.
UDP Programme werden an einen Port gebunden und aufgerufen, wenn etwas
auf dem Port empfangen wurde sowie in regelmäßigen Abstände (periodic).
Schau wie ich den dhcp Client implementiert habe.
State-Holder für den Thread:
1
struct dhcpc_state {
2
struct pt pt; // Protothread of the DHCP client
3
struct uip_udp_conn *conn; // Connection of the UPD socket
4
...
5
// Hier gehören Benutzerdefinierte Variablen der UDP Anwendungen hin.
6
// Alle UDP Anwendungen müssen die gleiche Struktur verwenden.
7
};
8
9
typedef char uip_udp_appstate_t;
So sagst Du uIP, welche Funktion bei Empfang eines UDP Paketes
aufgerufen werden soll:
1
void dhcpc_appcall(void) {
2
...
3
}
4
5
#define UIP_UDP_APPCALL dhcpc_appcall
Der UDP teil von uIP ist (im Gegensatz zum TCP Teil) nur für eine
einzige UDP Anwendung ausgelegt. Diese eine Anwendung kann auf vielen
Ports empfangen, aber nur an eine Gegenstelle Senden.
Durch das Binden eines Portes machst Du uIP bereit, Daten auf dem Port
zu senden bzw. zu empfangen. Es handelt sich dabei NICHT um eine
Verbindung (an dieser Stelle ist der von uIP vorgegebene Begriff conn
irreführend). UDP arbeitet Verbindungslos. Dementsprechend entfallen
auch Verbindungs-Aufbau und -Abbau. Es werden einfach einzelne Pakete
von Rechner A Port x nach Rechner B Port y gesendet. Ob sie auch
wirklich ankommen, ist dem UDP Protokoll egal.
Einen Port binden:
1
uip_ipaddr(addr, 255, 255, 255, 255);
2
status.conn = uip_udp_new(&addr, HTONS(67));
3
if (status.conn != NULL) {
4
uip_udp_bind(status.conn, HTONS(68));
5
}
Du sagst damit dem uIP, dass die UDP Anwendung zu 255.255.255.255 Port
67 senden soll, und empfangen tut er auf seiner eigenen IP-Adresse Port
68.
Anmerkung: Die meisten UDP Anwendungen nutzen für Sendung und Empfang
den gleichen Port. In meinem Fall geht's aber um DHCP, da ist das etwas
anders.
Wenn nun uIP etwas empfängt, dann ruft er die Anwendung auf, also in
meinem Fall dhcpc_appcall(). Er ruft die Applikation außerdem periodisch
auf. In meinem Fall ungefähr einmal pro Sekunde, weil ich den Timer
entsprechen initialisiert habe:
1
timer_set(&periodic_timer, CLOCK_SECOND);
In meiner UDP Applikation benutze ich uip_newdata(), um herauszufinden,
ob etwas empfangen wurde. Die Empfangenen Daten befinden sich im Puffer
uip_appdata. UDP Pakete können nicht größer sein, als der Puffer, also
enthält der Puffer immer das vollständige empfangene Paket.
Nachdem Du empfangene Daten verarbeitet hast (falls überhaupt etwas
empfangen wurde), darfst du senden. Befülle dazu den Puffer uip_appdata
mit den Daten, die du senden willst. Danach rufst Du uip_send auf:
1
uip_send(uip_appdata, length);
Length ist die Anzahl von bytes, die gesendet werden soll. Auch hier
gilt, dass das ganze Paket in den Puffer rein passen muss. UDP Pakete
können nicht größer sein.
Die Funktion uip_mss() verrät Dir, wie viele Bytes du maximal in den
Puffer schreiben darfst.
Zum Debugging kann es hilfreich sein, die Windows Firewall zu
deaktivieren. Die blockt nämlich den Empfang aus sämtlichen Ports ab,
sofern sie nicht ausdrücklich freigegeben werden. Dem normalen Windows
User fällt das nicht auf, wiel die Setup-Programme sich um die
Konfiguration der Windows-Firewall kümmern.
Wireshark ist übrigens auch sehr hilfreich.
> Der UDP teil von uIP ist (im Gegensatz zum TCP Teil) nur für eine> einzige UDP Anwendung ausgelegt. Diese eine Anwendung kann auf> vielen Ports empfangen, aber nur an eine Gegenstelle Senden.
Sorry, das war quatsch. Vergiss diesen Satz einfach.
Ich glaube, ich sollte noch eine seltsame Sache in meiner dhcpc.h
erklären:
1
struct dhcpc_state {
2
struct pt pt; // Protothread of the DHCP client
3
struct uip_udp_conn *conn; // Connection of the UPD socket
4
...
5
};
6
7
//typedef struct dhcpc_state uip_udp_appstate_t;
8
typedef char uip_udp_appstate_t;
9
10
#define UIP_UDP_APPCALL dhcpc_appcall
Eigentlich ist es in uip vorgesehen, diese eine auskommentierte Zeile zu
haben, wo der Typ vom UDP Status (uip_udp_appstate_t) definiert wird.
Jede "virtuelle" UDP Verbindung hat eine eigene Instanz dieser Struktur
(wie bei TCP).
Ich brauchte allerdings eine einfache Möglichkeit, jederzeit von Außen
auf den Status meines DHCP Clients zuzugreifen, sogar schon bevor er
überhaupt gestartet wurde. Deswegen hatte ich in dhcpc.c einfach eine
statische Instanz der Struktur angelegt:
1
static struct dhcpc_state status;
Und ich habe uip_udp_appstate_t durch einen Dummy vom Typ char ersetzt.
Ich bin inzwischen der Ansicht, dass diese Lösung ziemlich quick and
dirty ist, mach das besse nicht. Du solltest in deiner Anwendung lieber
wie vorgesehen die Zeile
bei Stefanus wird
PT_THREAD(handle_dhcp(void)) {
aufgerufen. Bei diesem Appcall müssen doch nur noch die per UDP
gesendeten Daten aus uip_buf ausgelesen werden? Wozu braucht man da
einen Protothread?
als nächstes versuche ich über uip_process(UIP_UDP_SEND_CONN) Daten zu
senden.
> Wozu braucht man da einen Protothread?
Du hast Recht, ich hätte den DHCP Client auch ohne Protothreads
implementieren können, wenn ich das gewollt hätte.
Der DHCP CLient sendet einen DHCP Request, wartet auf Antwort und
wiederholt ggf mehrmals. Außerdem muss er vor Ablauf der Adresse, einen
Refresh Anfordern. Und Falls der DHCP Server gar nicht passend
antwortet, muss er auf eine statische IP Adresse zurück fallen
(jedenfalls wollte ich es so haben).
Ich darf aber keine Warteschleifen programmieren. Weiterhin darf die
Applikation bei jedem Aufruf durch uIP nur ein einziges Paket senden.
Und sie muss schnellstmöglich (zu uIP) zurück kehren, sonst bekommt uIP
weder Gelegenheit, den Inhalt des Puffers zu senden (an den Ethernet
Controller zu übergeben), noch empfangene Pakete beim Ethernet
Controller abzuholen.
Also hätte ich entweder einen Zustandautomaten gebraucht, oder eben die
Protothreads, was ja letztendlich nichts anderes ist.
wenn ich das richtig verstanden habe, füllt uip_send den uip_buffer.
Danach wird der Inhalt des uip_buf durch uip_process abgearbeitet.
Bei TCP über Aufruf von uip_process(UIP_DATA), bei UDP demzufolge
uip_process(UIP_UDP_SEND_CONN). Sonst wird der UDP-Teil in uip_process
gar nicht angesprungen?
Nein, uip_send() löst das Senden des Puffers aus.
uip_process sollst du aus der Applikation heraus gar nicht aufrufen. Die
Funktion ist im Referenz-Manual von uIP 1.0 auch gar nicht dokumentiert.
uip_process() wird (indirekt über Makros) von der Hauptschleife
aufgerufen. Schau mal in uip.h, welche defines alles uip_process
enthalten.
ich bin mit tcp senden etwas weitergekommen. Die Befehlsfolge
1
uip_ipaddr_tipaddr;
2
uip_ipaddr(&ipaddr,192,168,0,124);
3
uip_send(kktest,4);// payload in uip_buf
4
uip_connect(&ipaddr,HTONS(70));
sendet einen korrekten TCP-Frame/ len=60, überträgt allerding die
Payload nicht mit.
die uip_process-Macros habe ich aufgelößt, weil ich das Programm
verstehen will. Warum diese Hauptfuktion durch Macros verschleiert wird,
ist für mich nicht nachvollziehbar.
Du hast recht, uip_process(UIP_UDP_SEND_CONN) ist nicht erforderlich,
weil der udp Teil über
{uip_udp_conn = &uip_udp_conns[i];
uip_process(UIP_UDP_TIMER);}//=>uip_udp_periodic(i);
aufgerufen wird
/* Return and let the caller do the actual transmission. */
9
uip_flags=0;
Wenn ich hier uip_len und uip_buf verändere, wird die payload korrekt
übertragen, d.h. der Fehler liegt wahrscheinlich bei der Ausführung von
uip_send.
karl k. schrieb:> uip_send(kktest, 4);// payload in uip_buf> uip_connect(&ipaddr, HTONS(70));
du sendest zuerst und baust danach die Verbindung auf? Sorry, aber noch
abstruser kann man das uip nicht missbrauchen :-(
Wer ist der Gegner für das connect? Wie siehst du das die Verbindung
aufgebaut wurde??
karl k. schrieb:> Warum diese Hauptfuktion durch Macros verschleiert wird,> ist für mich nicht nachvollziehbar.
dazu musst du verstehen was protothreads sind ...
dann ergibt das plötzlich alles einen sinn ^^
dann würdest du feststellen das dein uip_send einfach nur sendet ...
während der protothread auch das ACK ( bei tcp) bearbeitet und
dementsprechend eine sendewiederholung macht
aber generell gilt:
verbindung öffnen ... senden ... dann schließen
uip_send sendet nicht sondern stellt nur Daten in uip_buf ein. Durch das
Einstellen der Daten wird über den Timer bei uip_len>0 uip_process
ausgelößt. Erst am Ende von uip_process wird über
UIP_STAT(++uip_stat.ip.sent) das Senden über den Protothread aktiviert.
Wegen der Verzögerung durch den Timer funktioniert auch uip_send vor
uip_connect, wobei ich das jetzt umgestellt habe. Das war aber nicht das
Problem. Problem war das Wegoptimieren der while(0)-Schleifen -warum
auch immer- und die Verschiebung durch optdata.
TCP-Senden und TCP-Empfangen funktioniert jetzt einwandfrei. Die
Anpassungen im Code sehen so aus:
karl k. schrieb:> TCP-Senden und TCP-Empfangen funktioniert jetzt einwandfrei. Die> Anpassungen im Code sehen so aus:> //Sendebefehl:> kksend_status=1;> uip_ipaddr_t ipaddr;> uip_ipaddr(&ipaddr, 192,168,0,124);> uip_connect(&ipaddr, HTONS(70));> uip_send(kktest, 14);// payload in uip_buf
glaube ich nicht, nie und nimmer wenn die Befehle genauso nacheinander
aufgerufen werden. Das ist dann vielleicht KKP, aber niemals TCP!
Nochmal die Frage: gegen was testest du das? Und hast du dir mal die
Grundlagen zu UDP und TCP angesehen?
TCP ist ein Protokoll bei dem eine Verbindung aufgebaut wird und es gibt
verschiedene Zustände von Aufbau bis Abbau. Die ignorierst du alle wenn
du den Stack verfummelst und versuchst ohne die appcall wie in den
Beispielen zu arbeiten.
uip_connect() führt keinen connect aus, nach dem Aufruf der Funktion
ist noch keine Verbindung etabliert(!). Das uip_connect() bereitet nur
den TCP Header vor und setzt den Verbindungsstatus auf aktive
Verbindung. Abgehandelt wird das automagisch in der main loop über
uip_process().
Du musst in der appcall brav auf das Ereignis uip_connected() warten,
dann weisst du das der Gegner deine Verbindung akzeptiert hat. Und
Senden/Empfangen darfst du dann in uip_poll().
Im Anhang nochmals mein Beispiel das einen einfachen TCP Server
implementiert. Der wartet auf ein connect von einem Client und der muss
erstmal den String 'Jojos iPhone' senden. Dann wird ein 'ok'
zurückgesendet. Dann wird in den Empfangsstauts gewechselt und auf
Strings im Format 'FS20 xxxx' gewartet und wenn sowas angekommen ist
wird eine Funktion mit (xxxx) aufgerufen.
Benutze uIP so wie es vorgesehen ist, oder verzichte drauf.
Offensichtlich möchtest Du lieber deinen eingenen TCP/IP Stack
schreiben. Nur zu, dann mach es! Sobald Du das Protokoll und den
Algorithmus für die CRC verstanden hast, kannst Du damit loslegen.
Aber versuche bitte nicht, uIP zu vergewaltingen. Das kann nicht gut
gehen. uIP funktioniert, wenngleich der Code hässlich ist und schwer zu
durchschauen. Aber er funktioniert und ist korrekt.
natürlich habt ihr recht. Ich habe zwischenzeitlich das Tutorial von
Stefan durchgearbeitet und das telnet-Beispiel auch zum laufen gebracht.
Uip ist aber wohl nicht das was ich will. Die Protothreads haben ihre
Begründung darin, möglichst schlanken code für kleine MCs zu generieren.
Ich arbeite zur Zeit mit einem LPC1768 und einem Atmega1284. Beide haben
ausreichend Resourcen, dafür braucht man keine Protothreads. Es muss
auch einfacher gehen.
Das LPC-DK2-Board hat neben Ethernet auch einen SD-Steckplatz. Einen
Vs1053 habe ich bereits zum laufen gebracht. Es bietet sich also an, mit
dem Webserver gleich ein Webradio zu integrieren. Hat jemand dafür -
einfachen - brauchbaren Code?
karl k. schrieb:> natürlich habt ihr recht. Ich habe zwischenzeitlich das Tutorial> von> Stefan durchgearbeitet und das telnet-Beispiel auch zum laufen gebracht.> Uip ist aber wohl nicht das was ich will. Die Protothreads haben ihre> Begründung darin, möglichst schlanken code für kleine MCs zu generieren.> Ich arbeite zur Zeit mit einem LPC1768 und einem Atmega1284. Beide haben> ausreichend Resourcen, dafür braucht man keine Protothreads. Es muss> auch einfacher gehen.>> Das LPC-DK2-Board hat neben Ethernet auch einen SD-Steckplatz. Einen> Vs1053 habe ich bereits zum laufen gebracht. Es bietet sich also an, mit> dem Webserver gleich ein Webradio zu integrieren. Hat jemand dafür -> einfachen - brauchbaren Code?https://github.com/watterott/WebRadio
der IP stack ist evtl für dich einfacher zu gebrauchen ...
udp senden und empfangen läuft jetzt mit korrektem Protokoll problemlos.
Der einfachste im Netz zu findende stack ist der mega8udp. Der Autor ist
unbekannt.
Zunächst habe ich den lowlevel-Teil vom redlib-uip übernommen und
isoliert. Die Schnittstelle sind die drei Funktionen init, send,
receive. Der mega8udp-stack stellt auf rund 200 codezeilen für diese
drei lowlevel-Funktionen praktisch den zu sendenden frame zusammen.
Einfach und übersichtlich und für UDP-Datenaustausch völlig ausreichend.
Ich habe diesen code um eine UDP-Sendefunktion ergänzt, so dass das
DK2-board von sich aus senden kann. Da der lowlevel-Teil getrennt ist,
ist der code auch für andere Systeme verwendbar.
Als nächstes möchte ich Daten aus dem local-net hinaus über das Internet
versenden. Anfangen möchte ich mit einem ntp-Abruf. Kann ich hier direkt
von meinem DK2-board auf den ntp-server zugreifen oder muss der gateway
zwischengeschaltet werden? Hat jemand eine einfache ntp-Funktion?