Hi,
ich habe ein Problem mit einem ESP32, dem Entwicklungsframework ESP-IDF
v3 und einem KSZ8863RLL Ethernet-Phy.
Kurzfassung: Das ganze Funktioniert nicht.
Langfassung mit Kontext:
Ich habe ein eigenes Board für [url=https://kno.wled.ge/]WLED[/url]
gebaut (siehe Anhang). Da drauf ist ein ESP32-WROVER-E und ein
KSZ8863RLL 3Port Ethernetswitch / Phy.
Ziel der Übung ist es Lampen zu bauen, die man auf Ethernet-Seite
kaskadieren kann ohne einen zentralen Switch zu benötigen, sowas ich
üblich bei Bühnentechnik.
WLED ist ein PlatformIO-Projekt und basiert auf dem Arduino-Framework.
Intern basiert das Arduino-Framework auf dem ESP-IDF v3. ESP-IDF v3 (und
damit Arduino) in der Version unterstützt leider nur sehr wenige Phys,
keiner davon mit integriertem Switch.
Im ESP-IDF v5 gibt es ein Beispiel wie man einen KSZ8863 anspricht, das
Beispiel lässt sich bauen und funktioniert auf meinem Board -> die
Hardware ist in Ordnung. Leider sind die Unterschiede zwischen v3 und v5
enorm, der Treiber lässt sich quasi nicht portieren. WLED/Arduino lässt
sich auch nicht auf das ESP-IDF v5 hochziehen, da gibt es politische
Verwerfungen zwischen PlatformIO und Espressif und alles stinkt zum
Himmel. Wenn man das versucht explodiert das PlatformIO-Buildsystem und
kotzt einem Python Stacktraces vor die Füße.
Einfach einen anderen Phy angeben funktioniert nicht, die Register vom
KSZ8863 sind zu unterschiedlich, außerdem lässt sich der Chip über SMI
nochtmal komplett ansprechen. Nur über I2C oder SPI kann man alle
Register erreichen.
Der KSZ8863RLL hat drei Ports, Port 1+2 sind normale 100Mbps
Ethernet-Ports, Port 3 ist über RMII zu erreichen und hängt an meinem
ESP32.
ESP-IDF v3 hat auch eine Schnittstelle um eigene Treiber zu integrieren,
also will ich mich da reinhängen. Da das nicht direkt in WLED ist
sondern noch der Arduino-Core dazwischen ist habe ich Angefangen den
Arduino-Core zu modifizierne, genauer gesagt die ETHClass.
In der Methode ETHClass::begin gibt es den folgenden Code der die
konfigurierbaren Phys durchgeht, dort haben ich meinen KSZ8863
reingehängt:
Ab Zeile 17 gehen sie die verschiedenen Phy-Typen durch.
"phy_lan8720_default_ethernet_config" sind vorgefertige Strukturen vom
IDF-Framework.
Ab zeile 28 fängt mein Code an, ich nehme erst die Default-Config vom
ip101, dann überschreibe ich die ganzen Funktionspointer die die
Struktur hat.
Am Ende landet alles in eth_config, dann kommen noch die dynamischen
Teile von WLED dazu (Zeile 46-49).
In Zeile 56 wird der TcpIP-Stack initialisiert (das ist wieder
Arduino-Code, an dem habe ich nicht rumgefummelt, in Zeile 57 wird die
EMAC-Hardware vom ESP32 initialisiert.
Das ganze läuft ohne Fehlermeldung durch.
Hier sind noch die Funktionen, die ich dem IDF-Framework unterschiebe:
1
uint8_tksz_read_reg(uint8_treg)
2
{
3
Wire.beginTransmission(KSZ8863_ADR);
4
Wire.write(reg);//register
5
Wire.endTransmission();
6
Wire.requestFrom(KSZ8863_ADR,1);
7
uint8_treg1=Wire.read();// read that byte into 'slaveByte2' variable
8
9
returnreg1;
10
}
11
12
13
14
esp_err_tksz_phy_init(void)
15
{
16
phy_mii_enable_flow_ctrl();
17
returnESP_OK;
18
}
19
20
boolksz_phy_check_link(void)
21
{
22
uint8_tport1_status_0=ksz_read_reg(30);
23
uint8_tport2_status_0=ksz_read_reg(46);
24
25
if(port1_status_0&(1<<5))
26
Serial.printf("Port 1 Link good\n");
27
else
28
Serial.printf("Port 1 Link bad\n");
29
30
if(port2_status_0&(1<<5))
31
Serial.printf("Port 2 Link good\n");
32
else
33
Serial.printf("Port 2 Link bad\n");
34
35
36
// for(int i=0; i < 240; i++)
37
// Serial.printf("Read register %d: 0x%02X\n", i, ksz_read_reg(i));
38
39
returntrue;
40
}
41
42
voidksz_phy_check_init(void)
43
{
44
45
}
46
47
eth_speed_mode_tksz_phy_get_speed_mode(void)
48
{
49
returnETH_SPEED_MODE_100M;
50
}
51
52
eth_duplex_mode_tksz_phy_get_duplex_mode(void)
53
{
54
returnETH_MODE_FULLDUPLEX;
55
}
56
57
boolksz_phy_get_partner_pause_enable(void)
58
{
59
returnfalse;
60
}
61
62
voidksz_phy_power_enable(boolenable)
63
{
64
65
}
Wie man sieht sehr spartanisch. Zum Beispiel kann der Link gar nicht
abbrechen da er ja direkt mit einem Switch verbunden ist, außerdem habe
ich erstmal die Geschwindigkeit auf 100Mbps gepinnt.
Ich hab den Sourcecode für die im IDF-Framework enthaltenen Phys
durchgeschaut, aber die konfigurieren wirklich nur den Phy über SMI und
drehen offenbar nicht an der ESP32-EMAC-Hardware rum.
Außerdem habe ich einen Registerdump gemacht und per Hand mit dem
KSZ8863-Datenblatt abgeglichen, dabei habe ich keine "Verkonfigurierte"
Option gefunden. Ich gehe daher davon aus, dass die EMAC-Config und
nicht die Phy-Config das Problem ist.
Jetzt das beobachtete Verhalten:
- Switching zwischen Port 1 und Port 2 funktioniert, ich kann auf Port 1
einen DHCP-Server anklemmen und auf Port 2 einen Client, das sieht sich
und funktioniert.
- Dem ESP32 habe ich eine feste IP gegeben, das funktioniert scheinbar
auch.
- Laptop am Port 1 (192.168.69.10), ESP32 fixe IP 192.168.69.100)
- Ping vom Laptop zum PC, monitor mit Wireshark zeigt: Keine Antwort auf
den ARP Request.
- Mit dem Oszi das RMII Interface anschauen:
* RMII Clock läuft (kommt aus GPIO0 raus, in den PHY)
* Die Datenleitungen vom Phy zum ESP32 zeigen aktivität
* Auf den Datenleitungen vom ESP32 zum Phy ist tote Hose
* Der TCPIP-Stack wird nicht aufgerufen.
Das ganze Baubar zu Verfügung zu stellen ist leider nicht ganz einfach
weil es über mehrere Repos (WLED, Arduino) verstreut ist, das müsste ich
erst Forken und selber Hosten...
Hat jemand eine Idee?
Ich bin nicht sicher ob es gut ist auf 100 MBit zu fixieren. Habe ich
auch mal mit einem einfach PHY gemacht, da musste der Switch aber auch
auf 100 Mbit fix eingestellt werden, mit Auto haben die sich nicht
verstanden.
Monk schrieb:> an, insbesondere Kapitel 4.2, weil mir da eine Abweichung aufgefallen> ist.
Das stimmt, allerdings habe ich überstehende Boards bei ESPs noch nie
gesehen - aber wenn man WLAN benutzen möchte bestimmt ein guter Tipp.
Zu Ethernet / RMII findet sich jedoch nix.
J. S. schrieb:> Ich bin nicht sicher ob es gut ist auf 100 MBit zu fixieren.
Hm, ich sehe da kein Problem, weil der Switch ja noch direkt mit auf dem
Board ist.
Außerdem habe ich die Register des Phys gechecked, da steht
1
Register 63 fürs RMII Interface:
2
-> TX flow control active
3
-> RX flow control active
4
-> link speed 100Mbps
5
-> Full-Duplex operation
Also auch wenn ich das return 100Mbps; durch eine Register-Abfrage
ersetze wird trotzdem 100Mbps raus kommen. Ich baue es gleich mal um
Ich habe jetzt mal nochmal zum Testen das ganze WLED/Arduino-Theater
weggelassen und direkt das Espressif Beispiel angepasst.
Jetzt passt das ganze in 300 Zeilen (im Anhang), baubar mit ESP-IDF
v3.3.6
Darin habe ich jetzt auch get_speed_mode() und get_duplex_mode() richtig
implementiert. Geändert hat sich am Verhalten leider gar nichts.
Im log sehe ich
1
I (465) KSZ: ksz_phy_init
2
W (475) KSZ: Except next write to fail because of SW reset
3
I (575) I2C: i2c_master_cmd_begin failed
4
E (575) I2C: I2C_read failed
5
I (575) KSZ: ChipID ok
6
I (575) emac: emac reset done
7
I (575) KSZ: Port 3 speed mode: 100M
8
I (575) eth_example: Ethernet Started
9
I (2575) eth_example: Port 1 Link good
10
11
I (2575) eth_example: Port 2 Link good
12
13
I (2585) KSZ: Port 3 speed mode: Full Duplex
14
I (2585) KSZ: Port 3 speed mode: 100M
15
I (2595) KSZ: Port 3 speed mode: Full Duplex
16
I (2595) KSZ: Port 3 speed mode: 100M
17
I (2605) KSZ: Port 3 speed mode: Full Duplex
18
I (2605) KSZ: Port 3 speed mode: 100M
19
I (2615) KSZ: Port 3 speed mode: Full Duplex
20
I (2615) KSZ: Port 3 speed mode: 100M
21
I (2625) KSZ: Port 3 speed mode: Full Duplex
22
I (2625) KSZ: Port 3 speed mode: 100M
23
I (2635) KSZ: Port 3 speed mode: Full Duplex
24
I (2635) KSZ: Port 3 speed mode: 100M
25
I (2635) eth_example: Ethernet Link Up
26
I (4575) eth_example: Port 1 Link good
27
28
I (4575) eth_example: Port 2 Link good
Die wichtigen Zeilen hier sind:
I (575) eth_example: Ethernet Started
I (2635) eth_example: Ethernet Link Up
Danach würde ich eigentlich ein SYSTEM_EVENT_ETH_GOT_IP event erwarten
(Ausgabe "Ethernet Got IP Addr"), das kommt aber nie. Die RMII-Leitungen
mit der Datenrichtung ESP32 -> PHY bleiben tot.
Circa 1x pro Sekunde fragt er den Status der beiden Ports ab.
J. S. schrieb:> hmm, da werden doch Fehler in Zeile 3 und 4 für die I2C> Kommunikation> gemeldet, läuft die dann überhaupt richtig?
Ja, siehe Zeile 2 :)
"Except next write to fail because of SW reset"
Der Phy ist mit seinem Reset so schnell, dass er sogar das ACK für den
Resetbefehl vergisst...
Schreiben auf andere Bits geht ohne Fehler.
ja ok, jetzt habe ich es auch verstanden.
Nach dem PHY init sollte der Emac Code aber nichts mehr mit dem PHY zu
tun haben, bis auf das Pollen des Linkstatus. Und das liefert ja Link
up.
Der KSZ benutzt ja sicher auch so strapping bits, wird der wirklich mit
Autonegotation initialsiert? Oder andere Einstellungen durch diese
Straps falsch gesetzt?
Ja, das ist auch mein Verständnis, aber irgendwie macht der emac nichts.
Daher bin ich gerade rat- und planlos. Das emac-init geht ja auch ohne
Fehler durch.
Torben H. schrieb:> Monk schrieb:>> an, insbesondere Kapitel 4.2, weil mir da eine Abweichung aufgefallen>> ist.>> Das stimmt, allerdings habe ich überstehende Boards bei ESPs noch nie> gesehen - aber wenn man WLAN benutzen möchte bestimmt ein guter Tipp.
Wer wlan braucht könnte ja auch das Trägerboard an der Antenne vom ESP
ausklinken, dann steht nichts über.
Joachim B. schrieb im
> Wer wlan braucht könnte ja auch das Trägerboard an der Antenne vom ESP> ausklinken, dann steht nichts über.
Oder ein ESP32-WROVER-IE einlöten, das hat eine Antennenbuchse
Kannst du den Inhalt von tcpip_adapter_eth_input zwischen v3 / v5
vergleichen? Das ist ja die Routine die vom Interrupt aufgerufen wird
wenn Daten angekommen sind. Oder ist die Interrupt Aktivierung jetzt
anders oder fehlt?
Oder eine Funktion dazwischensetzen,
config.tcpip_input = my_tcpip_adapter_eth_input;
my_tcpip_adapter_eth_input() dann einen Portpin toggeln lassen und dann
die richtige Funktion aufrufen um zu sehen ob es bis dahin kommt.
J. S. schrieb:> Der KSZ benutzt ja sicher auch so strapping bits, wird der wirklich mit> Autonegotation initialsiert? Oder andere Einstellungen durch diese> Straps falsch gesetzt?
Hmmm, ja, da wird einiges mit strapping bits gemacht, aber nur für Port
1/2 (die, an die RJ45 Buchsen kommen).
Für Port3 sind z.b. die ganzen Autonegotiation-Bits "Reserved, Not
Applicable to Port 3"
Macht ja auch Sinn (aus Switch-Sicht), Port3 hat ja nur RMII und an
keiner Stelle physikalische Ethernet-Pegel.
J. S. schrieb:> Kannst du den Inhalt von tcpip_adapter_eth_input zwischen v3 / v5> vergleichen?
tcpip_adapter_eth_input wird in meinem Fall nie aufgerufen.
Interrupt-Aktivierung... guter Punkt, da muss ich wohl direkt mal im
ESP32 auf die Register schauen. Eigentlich (tm) hatte ich erwartet, dass
das in Beispiel drin ist (entweder in tcpip_adapter_init(),
esp_eth_init() oder esp_eth_enable().
Dann bin ich da mit meinem Halbwissen zum ESP auch am Ende.
D.h. sowas wie
eth_config_t config;
gibt es in der STM HAL ja auch reichlich. Da bin ich mal darauf
reingefallen das die Struktur geändert wurde und in meinem Code nicht
alle Member gesetzt wurden. Wenn die Struktur auf dem Stack liegt, dann
hat die zufälligen Inhalt. Gibt es eine init Funktion um die Struktur zu
löschen? Sonst mit memset auf 0 setzen.
edit:
Das wird es auch nicht sein, sieht so aus das die Struktur in v3 gleich
ist. in v5 ist einiges umbenannt.
Aber in emac_start werden die Ints aktiviert, in
https://github.com/espressif/esp-idf/blob/b64b3752342a23469ada0188d4838a4fb96fe172/components/ethernet/emac_main.c#L834
Das nutzt das REG_WRITE Macro was ja wahrscheinlich SMI benutzt, SMI
muss also auch funktionieren.
J. S. schrieb:> Das nutzt das REG_WRITE Macro was ja wahrscheinlich SMI benutzt, SMI> muss also auch funktionieren.
Ne, REG_WRITE schreibt ein Hardwareregister vom ESP32, in diesem Fall
DMAIN_EN_REG (Seite
https://www.espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf
)
Es aktiviert im emac die Bits TIE, RIE, RBUE und NISE
(Transmit interrupt, Receive Interrupt, Receive Buffer unavailable
interrupt und normal interrupts enable).
J. S. schrieb:> Das wird es auch nicht sein,
Jap, leider nicht. Ich hab direkt die eth_config_t -Struktur ausm Header
kopiert und dann Zeile für Zeile gefüllt.
Leider geht noch immer kleine Kommunikation, aber jetzt wird zumindest
der TCP/IP-Stack aufgerufen und ich sehe Daten aufm dem Oszi in beide
Richtungen.
Fortschritt! :)
Ich konnte das Problem lösen, es bekommt jetzt zumindest eine IP :)
Ich verstehe nur noch nicht warum es funktioniert.
Die Lösung war das Bit 3 im Register 197, "P3 RMII Clock Selection:
Internal".
Nicht funktionierend stand es auf "External". Meiner Meinung nach ist
"External" auch richtig, schließlich habe ich dem ESP gesagt, dass er
den Takt ausgeben soll. (mode GPIO0_OUT).
GPIO0_OUT ist auch der richtige Modus für den ESP, wenn ich das
testweise auf "IN" umstelle geht es nicht mehr.
also "GPIO00_OUT" und "RMII CLock selection Internal" ist die richtige
Kombination. Auch vorher war aber ein Takt messbar...
-
Jetzt bekommt es eine IP und das Webinterface lädt auch, aber irgendwas
hat noch ziemlich Packagedrop :<
Ich hoffe, dass das nicht an der Signalintegrität aufm RMII-Interface
liegt.
Torben H. schrieb:> also "GPIO00_OUT" und "RMII CLock selection Internal" ist die richtige> Kombination. Auch vorher war aber ein Takt messbar...
Und auch dieses Rätsel ist gelöst, es war noch ein Hardwarefehler mit
dem Takt - in der Kombination haben ESP32 und Phy den RMII Takt
generiert, natürlich nicht synchron - so kommt man auf
(temperaturabhängige) 5-25% Paketverlust.
Jetzt funktioniert alles, die Software gibts hier:
Einmal der modifizierte Arduino-Core mit KSZ8863-Treiber:
https://github.com/Bauteiltoeter/arduino-esp32-ksz8863
Und einmal das modifizierte WLED, verlinkt auf den oben genannten
Arduino-Core und mit "KSZ8863"-Option im Webinterface:
https://github.com/Bauteiltoeter/WLED/tree/add-ksz8863-support
Vielen Dank für die Hilfe hier, Projekt erfolgreich!