STM32 Ethernet

Einige STM32 Controller haben einen integrierten Ethernet MAC(Media Access Controller). Das ist die Layer 2 Kontrolleinheit, Empfängt also die Bits und Bytes, berechnet die Prüfsummen und ist für Kollisionskontrolle und mehr verantwortlich. Darauf baut man dann einen Ethernet-Stack auf, der höherwertigen Protokolle wie TCP/IP, UDP usw auswertet. Um aber mit einem Netzwerk verbunden zu werden, ist noch ein PHY(Physikalische Schnittstelle) notwendig. Da dieser über MII(Media Independent Interface) angeschlossen wird, kann man frei wählen zwischen Kabelgebundenem Ethernet, Lichtwellenübertrager oder anderen PHYs mit MII/RMII.
Hier wird die Verwendung des MAC anhand eines STM32F207ZTG6 in Verbindung mit einem TI DP83848C PHY, der über RMII(Reduced Media Independent Inetrface) angeschlossen ist, gezeigt. Der Verwendete TCP/IP Stack ist lwIP in Version 1.3.2.

RMII

Hardware

RMII erfordert 7 Leitungen zwischen dem PHY und dem Prozessor: RxD[1:0], TxD[1:0], CRS_DV, TX_EN und RMII_Ref_Clock. Ganz wichtig ist, dass der DP83848 und der STM32 den selben Ref-Clock von 50Mhz bekommen. Am Phy muss der Clock an den Takt-Eingang X1, am STM an den REF_Clock Eingang des MAC. Hier wird der Clock aus dem MCO1 Pin bezogen, es wird aber häufig berichtet das dies Unsauber sei, Jitter habe und die Signalintegrität stark beeinflusst. Es wir die Verwendung von MII empfohlen, bei dem der PHY einen eigenen 25MHz Taktgenerator/Quarz hat.
Zusätzlich wird noch MDIO angeschlossen, dass ist eine an I2C angelehnte Management Schnittstelle für PHYs. Der STM32F207 hat hierfür extra ein Modul im der MAC Peripherie.
Zusätzlich kann der Interrupt Pin des DP83848 an einen ExtInt Eingang des STM32 angeschlossen werden um dynamisch auf das entstehen oder wegfallen einer Verbindung zu reagieren.
Die Beschaltung des PHYs sieht folgendermaßen aus:
PHY Schematics
L2 - L5 sind Übertrager und common mode choke. Je nach Anwendung hat man diese in der Ethernet Buchse integriert oder als ein Modul erhältlich. Die Hier gezeigten werden Aufgrund einer Anforderung in der eigentlichen Applikation der verwendeten Hardware verwendet.
Auch sollte man Pin 29 an den Reset-Schaltkreis angeschlossen werden. Die Beschaltung des PHYs sollte in Hinblick auf die eigene Applikation nochmal kontrolliert werden, ich kann dafür keine Garantie abgeben.
Die Beschaltung des Prozessors sieht so aus:
STM32F207 Schematics
Hier wird nur der Relevante Ausschnitt gezeigt. RMII_CLK(Ref_Clk) ist an PA1/MCO1 angeschlossen. Alle zusätzliche Beschaltung(Abblockung, Spannungsversorgung, Konfiguration) ist dem Datenblatt/Application Note zu entnehmen.
Auch gilt es die üblichen Regeln für das Design einzuhalten, daher Leitungslängen von Datenleitungen möglichst gleich halten, differentielle Signal möglichst immer zusammen routen, Analoge und Digitale Grounds trennen, Ethernet Interface und MII nicht nebeneinander/Übereinander routen, etc. Genaueres den Datenblättern/Application Notes entnehmen.

Oszilator

Ein großes Problem bei RMII ist es, den Referenz-Takt sauber zu erzeugen. Man kann einen 50MHz Takt aus MCO1 erzeugen, jedoch habe ich im Test große Probleme mit der Übertragung gehabt(Ich habe es gelöst, indem ich MII benutzt habe). Man kann aber einen externen 50MHz Oszillator nutzen, aber unbedingt darauf achten, dass das Taktsignal sauber ankommt, im Zweifelsfall immer noch zusätzliche Filter und/oder Widerstände vorsehen.

Software

Nachdem die Hardware steht, geht es an die Software. Hier wird der Ethernet Stack lwIP verwendet. Wichtig für diesen ist der Port auf den entsprechenden Prozessor, den es bei ST gibt. Für den hier verwendeten STM32F207 findet man den Port im Source-Code zur AN3384(LwIP TCP/IP stack demonstration for STM32F2x7 microcontrollers). Darin ist auch der Ethernet Treiber enthalten, diesen gibt es nicht direkt in der SPL.

Notwendige Dateien

Hat man sich den Code zur AN3384(Oder der zum verwendeten Prozessor passenden) herunter geladen, muss man ein paar Dateien zurechtkopieren.
Im Ordner „Utilities\Third_Party\lwip_v1.3.2“ findet man die Dateien zu lwIP und dem Port für den STM32F207. Die Dateien im ordner „src“ benötigt man alle, aus dem Ordner „port\STM32F2x7“ benötigt man „arch“ und, je nach Verwendung, die Dateien aus „FreeRTOS“ oder „Standalone“.
Zudem noch aus dem Projects Ordner eine „netconf.c“ und „netconf.h“ sowie „lwipopts.h“, „stm32f2x7_eth_conf.h“ und „main.h“. Hat man alles zusammen muss man noch zusehen, dass die includes stimmen.

Initialisierung

Die in meinem Projekt verwendete Initialisierung:

__IO uint32_t LocalTime = 0;
 
uint8_t  InitMAC()
{
    GPIO_InitTypeDef GPIO_InitStructure;
    ETH_InitTypeDef ETH_InitStructure;
    RCC_ClocksTypeDef RCC_Clocks;
 
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);
 
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_ETH);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_ETH);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_ETH);
    GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_MCO);
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_7 | GPIO_Pin_8;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
 
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_ETH);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource12, GPIO_AF_ETH);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_ETH);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12 | GPIO_Pin_13;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
 
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource1, GPIO_AF_ETH);
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource4, GPIO_AF_ETH);
    GPIO_PinAFConfig(GPIOC, GPIO_PinSource5, GPIO_AF_ETH);
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
 
 
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC_Rx, ENABLE);
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC_Tx, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
    RCC_AHB1PeriphClockCmd(ETH_LINK_GPIO_CLK, ENABLE);
    SYSCFG_ETH_MediaInterfaceConfig(SYSCFG_ETH_MediaInterface_RMII);
 
    //RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);
    //RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
 
    // Set PLL3 clock output to 50MHz
    RCC_MCO1Config(RCC_MCO1Source_PLLCLK, RCC_MCO1Div_2);
 
    /* Reset ETHERNET on AHB Bus */
    ETH_DeInit();
 
    // Software reset
    ETH_SoftwareReset();
    // Wait for software reset
    while (ETH_GetSoftwareResetStatus() == SET);
 
    // ETHERNET Configuration ------------------------------------------------------
    // Call ETH_StructInit if you don't like to configure all ETH_InitStructure parameter
    ETH_StructInit(&ETH_InitStructure);
 
    /* Fill ETH_InitStructure parametrs */
    /*------------------------   MAC   -----------------------------------*/
    ETH_InitStructure.ETH_AutoNegotiation = ETH_AutoNegotiation_Enable  ;
    ETH_InitStructure.ETH_LoopbackMode = ETH_LoopbackMode_Disable;
    ETH_InitStructure.ETH_RetryTransmission = ETH_RetryTransmission_Disable;
    ETH_InitStructure.ETH_AutomaticPadCRCStrip = ETH_AutomaticPadCRCStrip_Disable;
    ETH_InitStructure.ETH_ReceiveAll = ETH_ReceiveAll_Disable;
    ETH_InitStructure.ETH_BroadcastFramesReception = ETH_BroadcastFramesReception_Enable;
    ETH_InitStructure.ETH_PromiscuousMode = ETH_PromiscuousMode_Disable;
    ETH_InitStructure.ETH_MulticastFramesFilter = ETH_MulticastFramesFilter_Perfect;
    ETH_InitStructure.ETH_UnicastFramesFilter = ETH_UnicastFramesFilter_Perfect;
    #ifdef CHECKSUM_BY_HARDWARE
    ETH_InitStructure.ETH_ChecksumOffload = ETH_ChecksumOffload_Enable;
    #endif
 
    /*------------------------   DMA   -----------------------------------*/
 
    /* When we use the Checksum offload feature, we need to enable the Store and Forward mode:
    the store and forward guarantee that a whole frame is stored in the FIFO, so the MAC can insert/verify the checksum,
    if the checksum is OK the DMA can handle the frame otherwise the frame is dropped */
    ETH_InitStructure.ETH_DropTCPIPChecksumErrorFrame = ETH_DropTCPIPChecksumErrorFrame_Enable;
    ETH_InitStructure.ETH_ReceiveStoreForward = ETH_ReceiveStoreForward_Enable;
    ETH_InitStructure.ETH_TransmitStoreForward = ETH_TransmitStoreForward_Enable;
 
    ETH_InitStructure.ETH_ForwardErrorFrames = ETH_ForwardErrorFrames_Disable;
    ETH_InitStructure.ETH_ForwardUndersizedGoodFrames = ETH_ForwardUndersizedGoodFrames_Disable;
    ETH_InitStructure.ETH_SecondFrameOperate = ETH_SecondFrameOperate_Enable;
    ETH_InitStructure.ETH_AddressAlignedBeats = ETH_AddressAlignedBeats_Enable;
    ETH_InitStructure.ETH_FixedBurst = ETH_FixedBurst_Enable;
    ETH_InitStructure.ETH_RxDMABurstLength = ETH_RxDMABurstLength_32Beat;
    ETH_InitStructure.ETH_TxDMABurstLength = ETH_TxDMABurstLength_32Beat;
    ETH_InitStructure.ETH_DMAArbitration = ETH_DMAArbitration_RoundRobin_RxTx_2_1;
 
    /* Configure Ethernet */
    ETH_Init(&ETH_InitStructure, DP83848_PHY_ADDRESS);
 
    /* Enable the Ethernet Rx Interrupt */
    SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
    RCC_GetClocksFreq(&RCC_Clocks);
    SysTick_Config(RCC_Clocks.HCLK_Frequency / 100);
    /* Configure the PHY to generate an interrupt on change of link status */
 
	return SUCCESS;
 
}
 
void Time_Update(void)
{
    LocalTime += SYSTEMTICK_PERIOD_MS;
}

Der Code in der main.c:

int main(void)
{
    SystemInit();
    InitMAC();       //Init GPIO and ethernet mac
    LwIP_Init();     //Init ethernet stack
    while(1)
    {
        if (ETH_CheckFrameReceived())   //Poll for frames, no interrupts
        {
            LwIP_Pkt_Handle();          //Handle incoming packets
        }
        /* handle periodic timers for LwIP */
        LwIP_Periodic_Handle(LocalTime);
    }
}


Für die eigene Verwendung muss beachtet werden, dass die Peripherie an einem anderen AHB/APB Bus hängt, die Pins andere sein können und sich die Initialisierung der Pins ändern kann.
Zudem habe ich in der Datei „lwipopts.h“ DHCP deaktiviert:

#define LWIP_DHCP               0

Und in der „main.h“ eine valide IP Adresse eingetragen. Der Controller sollte nun mit der eingestellten IP über ping erreichbar sein.
Für die reine Nutzung von lwIP gibt es genug Beispiele.

MII

MII überträgt im Gegensatz zu RMII die Daten über 4 Leitungen pro Richtung(RXD[3:0], TXD[3:0]) und hat noch einige zusätzliche Steuersignale. Die Verwendung von MII ist bei dem STM32F207 nach ausgiebigen Testen dem RMII vorzuziehen, sofern dies möglich ist.