STM32 + RT-Thread + LwIp + DM9000

2023-06-05 18:01:12

一、概述

  • 開發板:STM32F103ZET6(戰艦)
  • RT-Thread:5.0.0
  • LwIp:2.1.2
  • 網路卡晶片:DM9000
  • 編譯環境:keil

我簡單瞭解了一下,在嵌入式中,網路晶片的使用方式大致有三種,如下:

  • (MCU + MAC + PHY)
  • (MUC + MAC) —— PHY
  • MCU —— (MAC + PHY)

注意:我用括號裡面的表示在同一塊晶片中

二、RT-Thread 移植

移植 RT-Thread 不是此文章的重點,可以參考一下我之前的筆記,或者直接使用 RT-Thread Studio、STM32CubeMX等工具直接生成,這裡我就不過多介紹了

三、新增 LwIp

  1. 新增使用是需要的API檔案

  2. 新增核心原始碼

    注意:這裡的動態記憶體是使用的RT-Thread中完成的,在 sys_arch.c 檔案中完成

  3. 新增IPv4或者IPv6需要使用的檔案,這裡我只用到了IPv4,所以沒有新增IPv6的檔案、

  4. 新增網路卡檔案

    注意:ethernet檔案在路徑 src\netif 中

  5. 在 keil 中新增標頭檔案路徑 「src\include」

四、新增 LwIp 需要的標頭檔案

五、新增驅動檔案

RT-Thread 已經寫好了驅動,我們值需要拷貝就行,不需要更改其中的內容,主要需要的檔案有 sys_arch.c、sys_arch.h、ethernetif.c、ethernetif.h。

其中 sys_arch 檔案主要實現了 LwIp 在作業系統下需要的功能好書,比如執行緒的建立、號誌、鎖等功能。
ethernetif 檔案主要實現 LwIp的驅動實現,移植時明白接收執行緒和傳送執行緒的工作,相對就比較簡單了。

完成以上步奏後,編譯應該是可以通過的,接下來值需要完成 BSP 程式的實現即可。

六、BSP 驅動檔案

以上的操作都不需要我們編寫程式碼的,只需要完成 BSP 部分的程式碼即可,因為我用的是正點原子的戰艦開發板,所以這裡我直接使用 DM9000 部分的程式碼,進行簡單更改即可。

  1. 註冊網路卡

    
    /**
     * @brief 註冊網路卡裝置
     */
    static int rt_hw_stm32_eth_init(void)
    {
        rt_err_t state = RT_EOK;
    
        /* 設定工作方式為自動模式 */
        stm32_eth_device.eth_mode = DM9000_AUTO;
        /* DM9000的SRAM的傳送和接收指標自動返回到開始地址,並且開啟接收中斷 */
        stm32_eth_device.imr_all = IMR_PAR | IMR_PRI;
    
        /* 前三位可以自定義 */
        stm32_eth_device.dev_addr[0] = 0x02;
        stm32_eth_device.dev_addr[1] = 0x00;
        stm32_eth_device.dev_addr[2] = 0x00;
        /* 根據 96 位唯一 ID 生成 MAC 地址(僅用於測試) */
        stm32_eth_device.dev_addr[3] = *(rt_uint8_t *)(UID_BASE + 4);
        stm32_eth_device.dev_addr[4] = *(rt_uint8_t *)(UID_BASE + 2);
        stm32_eth_device.dev_addr[5] = *(rt_uint8_t *)(UID_BASE + 0);
    
        // 初始化組播地址
        stm32_eth_device.multicase_addr[0] = 0Xff;
        stm32_eth_device.multicase_addr[1] = 0Xff;
        stm32_eth_device.multicase_addr[2] = 0Xff;
        stm32_eth_device.multicase_addr[3] = 0Xff;
        stm32_eth_device.multicase_addr[4] = 0Xff;
        stm32_eth_device.multicase_addr[5] = 0Xff;
        stm32_eth_device.multicase_addr[6] = 0Xff;
        stm32_eth_device.multicase_addr[7] = 0Xff;
    
        stm32_eth_device.parent.parent.init = rt_stm32_eth_init;
        stm32_eth_device.parent.parent.open = rt_stm32_eth_open;
        stm32_eth_device.parent.parent.close = rt_stm32_eth_close;
        stm32_eth_device.parent.parent.read = rt_stm32_eth_read;
        stm32_eth_device.parent.parent.write = rt_stm32_eth_write;
        stm32_eth_device.parent.parent.control = rt_stm32_eth_control;
        stm32_eth_device.parent.parent.user_data = RT_NULL;
    
        stm32_eth_device.parent.eth_rx = rt_stm32_eth_rx;
        stm32_eth_device.parent.eth_tx = rt_stm32_eth_tx;
    
        /* 註冊網路卡裝置 */
        state = eth_device_init(&(stm32_eth_device.parent), "e0");
        if (RT_EOK != state)
        {
            LOG_E("emac device init faild: %d", state);
            return -RT_ERROR;
        }
    
        /* 初始化DM9000 */
        state = DM9000_Init();
        if (RT_EOK != state)
        {
            LOG_E("DM9000 initialization failed");
            return -RT_ERROR;
        }
    
        /* 啟動 PHY 監視器, 線上程中完成網路卡的設定 */
        rt_thread_t tid;
        tid = rt_thread_create("dm9000",
                               dm9000_monitor_thread_entry,
                               RT_NULL,
                               1024,
                               RT_THREAD_PRIORITY_MAX - 2,
                               2);
        if (tid != RT_NULL)
        {
            rt_thread_startup(tid);
            state = RT_EOK;
        }
        else
        {
            state = -RT_ERROR;
        }
    
        return state;
    }
    INIT_DEVICE_EXPORT(rt_hw_stm32_eth_init);
    
    #ifdef RT_USING_FINSH
    #include <rtthread.h>
    static void red_dm9000_reg(int argv, char *argc[])
    {
        if (argv != 2)
        {
            rt_kprintf("Please enter the correct command, such as dm9000_ Red 0 \r\n");
            return;
        }
    
        rt_kprintf("0x%02X \r\n", DM9000_ReadReg(atoi(argc[1])));
    }
    
    MSH_CMD_EXPORT_ALIAS(red_dm9000_reg, DM9000_read_reg, Read the register value of DM9000);
    
  2. 讀取函數的實現

    /**
     * @brief DM9000接收封包,接收到的封包存放在DM9000的RX FIFO中,地址為0X0C00~0X3FFF
     * 接收到的封包的前四個位元組並不是真實的資料,而是有特定含義的,如下
     * byte1:表明是否接收到資料,為0x00或者0X01,如果兩個都不是的話一定要軟體復位DM9000
     *          0x01,接收到資料
     *          0x00,未接收到資料
     * byte2:第二個位元組表示一些狀態資訊,和DM9000的RSR(0X06)暫存器一致的
     * byte3:本幀資料長度的低位元組
     * byte4:本幀資料長度的高位元組
     * @return pbuf格式的接收到的封包
     */
    struct pbuf *rt_stm32_eth_rx(rt_device_t dev)
    {
        // LOG_E("rt_stm32_eth_rx");
    
        struct pbuf *p;
        struct pbuf *q;
        rt_uint8_t rxbyte;
        volatile rt_uint16_t rx_status, rx_length;
        rt_uint16_t *data;
        rt_uint16_t dummy;
        rt_int32_t len;
    
        p = NULL;
    __error_retry:
        DM9000_ReadReg(DM9000_MRCMDX);     // 假讀
        rxbyte = (rt_uint8_t)DM9000->DATA; // 進行第二次讀取
        if (rxbyte)                        // 接收到資料
        {
            if (rxbyte > 1) // rxbyte大於1,接收到的資料錯誤,掛了
            {
                LOG_E("dm9000 rx: rx error, stop device\r\n");
                DM9000_WriteReg(DM9000_RCR, 0x00);
                DM9000_WriteReg(DM9000_ISR, 0x80);
                return (struct pbuf *)p;
            }
            DM9000->REG = DM9000_MRCMD;
            rx_status = DM9000->DATA;
            rx_length = DM9000->DATA;
            // if(rx_length>512)printf("rxlen:%d\r\n",rx_length);
            p = pbuf_alloc(PBUF_RAW, rx_length, PBUF_POOL); // pbufs記憶體池分配pbuf
            if (p != NULL)                                  // 記憶體申請成功
            {
                for (q = p; q != NULL; q = q->next)
                {
                    data = (rt_uint16_t *)q->payload;
                    len = q->len;
                    while (len > 0)
                    {
                        *data = DM9000->DATA;
                        data++;
                        len -= 2;
                    }
                }
            }
            else // 記憶體申請失敗
            {
                LOG_E("pbuf記憶體申請失敗:%d\r\n", rx_length);
                data = &dummy;
                len = rx_length;
                while (len)
                {
                    *data = DM9000->DATA;
                    len -= 2;
                }
            }
            // 根據rx_status判斷接收資料是否出現如下錯誤:FIFO溢位、CRC錯誤
            // 對齊錯誤、物理層錯誤,如果有任何一個出現的話丟棄該資料框,
            // 當rx_length小於64或者大於最巨量資料長度的時候也丟棄該資料框
            if ((rx_status & 0XBF00) || (rx_length < 0X40) || (rx_length > DM9000_PKT_MAX))
            {
                LOG_E("rx_status:%#x\r\n", rx_status);
                if (rx_status & 0x100)
                    LOG_E("rx fifo error\r\n");
                if (rx_status & 0x200)
                    LOG_E("rx crc error\r\n");
                if (rx_status & 0x8000)
                    LOG_E("rx length error\r\n");
                if (rx_length > DM9000_PKT_MAX)
                {
                    LOG_E("rx length too big\r\n");
                    DM9000_WriteReg(DM9000_NCR, NCR_RST); // 復位DM9000
                    rt_thread_delay(10);
                }
                if (p != NULL)
                    pbuf_free((struct pbuf *)p); // 釋放記憶體
                p = NULL;
                goto __error_retry;
            }
        }
        else
        {
            DM9000_WriteReg(DM9000_ISR, ISR_PTS);         // 清除所有中斷標誌位
            stm32_eth_device.imr_all = IMR_PAR | IMR_PRI; // 重新接收中斷
            DM9000_WriteReg(DM9000_IMR, stm32_eth_device.imr_all);
        }
        return (struct pbuf *)p;
    }
    
    
  3. 寫入函數的實現

    /**
     * @brief 通過DM9000傳送封包
     * @param dev 裝置結構體
     * @param p pbuf結構體指標
     */
    rt_err_t rt_stm32_eth_tx(rt_device_t dev, struct pbuf *p)
    {
        struct pbuf *q;
        rt_uint16_t pbuf_index = 0;
        rt_uint8_t word[2], word_index = 0;
    
        DM9000_WriteReg(DM9000_IMR, IMR_PAR); // 關閉網路卡中斷
        DM9000->REG = DM9000_MWCMD;           // 傳送此命令後就可以將要傳送的資料搬到DM9000 TX SRAM中
        q = p;
        // 向DM9000的TX SRAM中寫入資料,一次寫入兩個位元組資料
        // 當要傳送的資料長度為奇數的時候,我們需要將最後一個位元組單獨寫入DM9000的TX SRAM中
        while (q)
        {
            if (pbuf_index < q->len)
            {
                word[word_index++] = ((u8_t *)q->payload)[pbuf_index++];
                if (word_index == 2)
                {
                    DM9000->DATA = ((rt_uint16_t)word[1] << 8) | word[0];
                    word_index = 0;
                }
            }
            else
            {
                q = q->next;
                pbuf_index = 0;
            }
        }
        // 還有一個位元組未寫入TX SRAM
        if (word_index == 1)
            DM9000->DATA = word[0];
        // 向DM9000寫入傳送長度
        DM9000_WriteReg(DM9000_TXPLL, p->tot_len & 0XFF);
        DM9000_WriteReg(DM9000_TXPLH, (p->tot_len >> 8) & 0XFF); // 設定要傳送資料的資料長度
        DM9000_WriteReg(DM9000_TCR, 0X01);                       // 啟動傳送
        while ((DM9000_ReadReg(DM9000_ISR) & 0X02) == 0)
            ;                                                  // 等待傳送完成
        DM9000_WriteReg(DM9000_ISR, 0X02);                     // 清除傳送完成中斷
        DM9000_WriteReg(DM9000_IMR, stm32_eth_device.imr_all); // DM9000網路卡接收中斷使能
        return ERR_OK;
    }
    
    

七、BSP 驅動檔案

#include <rtthread.h>

#ifdef BSP_USING_ETH
#include "drv_config.h"
#include "drv_eth_dm9000.h"
#include <netif/ethernetif.h>
#include <lwipopts.h>
#include <drv_common.h>
#include <rthw.h>

/* debug option */
// #define DRV_DEBUG


#define LOG_TAG "drv.dm9000"
#include <drv_log.h>

struct rt_stm32_eth
{
    /* 從 LwIp 中繼承 */
    struct eth_device parent;
#ifndef PHY_USING_INTERRUPT_MODE
    rt_timer_t poll_link_timer;
#endif

    /* 網路卡模式 */
    DM9000_PHY_mode eth_mode;
    /* 中斷型別 */
    rt_uint8_t imr_all;
    /* 每個封包大小 */
    rt_uint16_t queue_packet_len;
    /* 裝置 MAC 地址 */
    rt_uint8_t dev_addr[MAX_ADDR_LEN];
    /* 組播地址 */
    rt_uint8_t multicase_addr[MULTICASE_ADDR_LEN];
};

/* 網路卡裝置結構體 */
static struct rt_stm32_eth stm32_eth_device;

/* 注意:SRAM 控制程式碼不能為區域性變數 */
SRAM_HandleTypeDef DM9000_Handler; // SRAM控制程式碼

/**
 * @brief 初始化DM9000
 */
rt_err_t DM9000_Init(void)
{
    int result = RT_EOK;
    /* 設定 GPIO */
    GPIO_InitTypeDef GPIO_InitStruct;

    /* FSMC */
    FSMC_NORSRAM_TimingTypeDef DM9000_Timing;

    /*----------------------------------------- 使能時鐘 -----------------------------------------*/
    __HAL_RCC_GPIOD_CLK_ENABLE(); // 使能GPIOD時鐘
    __HAL_RCC_GPIOE_CLK_ENABLE(); // 使能GPIOE時鐘
    __HAL_RCC_GPIOF_CLK_ENABLE(); // 使能GPIOF時鐘
    __HAL_RCC_GPIOG_CLK_ENABLE(); // 使能GPIOG時鐘
    __HAL_RCC_FSMC_CLK_ENABLE();  // 使能FSMC時鐘
    __HAL_RCC_AFIO_CLK_ENABLE();  // 使能複用功能時鐘

    /* PD7 網路卡的復位引腳  */
    GPIO_InitStruct.Pin = GPIO_PIN_7;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推輓輸出
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

    /* PD0 1 4 5 8 9 10 14 15複用 */
    GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_4 | GPIO_PIN_5 |
                          GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_14 | GPIO_PIN_15;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 複用推輓輸出
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
    /* PG7 8 9 10 11 12 13 14 15複用 */
    GPIO_InitStruct.Pin = GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 |
                          GPIO_PIN_12 | GPIO_PIN_13 | GPIO_PIN_14 | GPIO_PIN_15;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 複用推輓輸出
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
    /* PF13複用 */
    GPIO_InitStruct.Pin = GPIO_PIN_13;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 複用推輓輸出
    HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
    /* PG9複用 */
    GPIO_InitStruct.Pin = GPIO_PIN_9;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; // 複用推輓輸出
    HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);

    /*----------------------------------------- 設定 SRAM -----------------------------------------*/
    DM9000_Handler.Instance = FSMC_NORSRAM_DEVICE;
    DM9000_Handler.Extended = FSMC_NORSRAM_EXTENDED_DEVICE;
    DM9000_Handler.Init.NSBank = FSMC_NORSRAM_BANK2;                        // 使用NE2
    DM9000_Handler.Init.DataAddressMux = FSMC_DATA_ADDRESS_MUX_DISABLE;     // 地址/傳輸線不復用
    DM9000_Handler.Init.MemoryType = FSMC_MEMORY_TYPE_SRAM;                 // SRAM
    DM9000_Handler.Init.MemoryDataWidth = FSMC_NORSRAM_MEM_BUS_WIDTH_16;    // 16位元資料寬度
    DM9000_Handler.Init.BurstAccessMode = FSMC_BURST_ACCESS_MODE_DISABLE;   // 是否使能突發存取,僅對同步突發記憶體有效,此處未用到
    DM9000_Handler.Init.WaitSignalPolarity = FSMC_WAIT_SIGNAL_POLARITY_LOW; // 等待訊號的極性,僅在突發模式存取下有用
    DM9000_Handler.Init.WaitSignalActive = FSMC_WAIT_TIMING_BEFORE_WS;      // 記憶體是在等待週期之前的一個時鐘週期還是等待週期期間使能NWAIT
    DM9000_Handler.Init.WriteOperation = FSMC_WRITE_OPERATION_ENABLE;       // 記憶體寫使能
    DM9000_Handler.Init.WaitSignal = FSMC_WAIT_SIGNAL_DISABLE;              // 等待使能位,此處未用到
    DM9000_Handler.Init.ExtendedMode = FSMC_EXTENDED_MODE_DISABLE;          // 讀寫使用相同的時序
    DM9000_Handler.Init.AsynchronousWait = FSMC_ASYNCHRONOUS_WAIT_DISABLE;  // 是否使能同步傳輸模式下的等待訊號,此處未用到
    DM9000_Handler.Init.WriteBurst = FSMC_WRITE_BURST_DISABLE;              // 禁止突發寫

    /*----------------------------------------- FSMC 讀寫時序控制 -----------------------------------------*/
    DM9000_Timing.AddressSetupTime = 0x00; // 地址建立時間(ADDSET)為1個HCLK 1/72M = 13.8ns
    DM9000_Timing.AddressHoldTime = 0x00;  // 地址保持時間(ADDHLD)模式A未用到
    DM9000_Timing.DataSetupTime = 0x03;    // 資料儲存時間為3個HCLK = 4*13.8 = 55ns
    DM9000_Timing.BusTurnAroundDuration = 0X00;
    DM9000_Timing.CLKDivision = 0X00;
    DM9000_Timing.DataLatency = 0X00;
    DM9000_Timing.AccessMode = FSMC_ACCESS_MODE_A; // 模式A

    /* 使能FSMC的Bank1_Bank1_NORSRAM2 */
    if (HAL_SRAM_Init(&DM9000_Handler, &DM9000_Timing, &DM9000_Timing) != HAL_OK)
    {
        LOG_E("SDRAM init failed!");
        result = -RT_ERROR;
    }
    return result;
}

/**
 * @brief 向DM9000指定暫存器中寫入指定值
 * @param reg 要寫入的暫存器
 * @param data 要寫入的值
 */
void DM9000_WriteReg(rt_uint16_t reg, rt_uint16_t data)
{
    DM9000->REG = reg;
    DM9000->DATA = data;
}

/**
 * @brief 讀取DM9000指定暫存器的值
 * @param reg 暫存器地址
 * @param data DM9000指定暫存器的值
 */
rt_uint16_t DM9000_ReadReg(rt_uint16_t reg)
{
    DM9000->REG = reg;
    return DM9000->DATA;
}

/**
 * @brief 向DM9000的PHY暫存器寫入指定值
 * @param reg PHY暫存器
 * @param data 要寫入的值
 */
void DM9000_PHY_WriteReg(rt_uint16_t reg, rt_uint16_t data)
{
    DM9000_WriteReg(DM9000_EPAR, DM9000_PHY | reg);
    DM9000_WriteReg(DM9000_EPDRL, (data & 0xff));        // 寫入低位元組
    DM9000_WriteReg(DM9000_EPDRH, ((data >> 8) & 0xff)); // 寫入高位元組
    DM9000_WriteReg(DM9000_EPCR, 0X0A);                  // 選中PHY,傳送寫命令
    rt_thread_delay(50);
    DM9000_WriteReg(DM9000_EPCR, 0X00); // 清除寫命令
}

/**
 * @brief 讀取DM9000的PHY的指定暫存器
 * @param reg 要讀的PHY暫存器
 * @return 返回值:讀取到的PHY暫存器值
 */
rt_uint16_t DM9000_PHY_ReadReg(rt_uint16_t reg)
{
    rt_uint16_t temp;
    DM9000_WriteReg(DM9000_EPAR, DM9000_PHY | reg);
    DM9000_WriteReg(DM9000_EPCR, 0X0C); // 選中PHY,傳送讀命令
    rt_thread_delay(10);
    DM9000_WriteReg(DM9000_EPCR, 0X00); // 清除讀命令
    temp = (DM9000_ReadReg(DM9000_EPDRH) << 8) | (DM9000_ReadReg(DM9000_EPDRL));
    return temp;
}

/**
 * @brief 復位DM9000
 */
void DM9000_Reset(void)
{
    // 復位DM9000,復位步驟參考<DM9000 Application Notes V1.22>手冊29頁
    DM9000_RST(0); // DM9000硬體復位
    rt_thread_delay(10);
    DM9000_RST(1);                                 // DM9000硬體復位結束
    rt_thread_delay(100);                          // 一定要有這個延時,讓DM9000準備就緒!
    DM9000_WriteReg(DM9000_GPCR, 0x01);            // 第一步:設定GPCR暫存器(0X1E)的bit0為1
    DM9000_WriteReg(DM9000_GPR, 0);                // 第二步:設定GPR暫存器(0X1F)的bit1為0,DM9000內部的PHY上電
    DM9000_WriteReg(DM9000_NCR, (0x02 | NCR_RST)); // 第三步:軟體復位DM9000
    do
    {
        rt_thread_delay(25);
    } while (DM9000_ReadReg(DM9000_NCR) & 1); // 等待DM9000軟復位完成
    DM9000_WriteReg(DM9000_NCR, 0);
    DM9000_WriteReg(DM9000_NCR, (0x02 | NCR_RST)); // DM9000第二次軟復位
    do
    {
        rt_thread_delay(25);
    } while (DM9000_ReadReg(DM9000_NCR) & 1);
}

/**
 * @brief 設定DM9000的MAC地址
 * @param macaddr 指向MAC地址
 */
void DM9000_Set_MACAddress(rt_uint8_t *macaddr)
{
    rt_uint8_t i;
    for (i = 0; i < 6; i++)
    {
        DM9000_WriteReg(DM9000_PAR + i, macaddr[i]);
    }
}

/**
 * @brief 設定DM9000的組播地址
 * @param multicastaddr 指向多播地址
 */
void DM9000_Set_Multicast(rt_uint8_t *multicastaddr)
{
    rt_uint8_t i;
    for (i = 0; i < 8; i++)
    {
        DM9000_WriteReg(DM9000_MAR + i, multicastaddr[i]);
    }
}

/**
 * @brief 設定DM900的PHY工作模式
 * @param mode PHY模式
 */
void DM9000_Set_PHYMode(rt_uint8_t mode)
{
    rt_uint16_t BMCR_Value, ANAR_Value;
    switch (mode)
    {
    case DM9000_10MHD: // 10M半雙工
        BMCR_Value = 0X0000;
        ANAR_Value = 0X21;
        break;
    case DM9000_10MFD: // 10M全雙工
        BMCR_Value = 0X0100;
        ANAR_Value = 0X41;
        break;
    case DM9000_100MHD: // 100M半雙工
        BMCR_Value = 0X2000;
        ANAR_Value = 0X81;
        break;
    case DM9000_100MFD: // 100M全雙工
        BMCR_Value = 0X2100;
        ANAR_Value = 0X101;
        break;
    case DM9000_AUTO: // 自動協商模式
        BMCR_Value = 0X1000;
        ANAR_Value = 0X01E1;
        break;
    }
    DM9000_PHY_WriteReg(DM9000_PHY_BMCR, BMCR_Value);
    DM9000_PHY_WriteReg(DM9000_PHY_ANAR, ANAR_Value);
    DM9000_WriteReg(DM9000_GPR, 0X00); // 使能PHY
}

/**
 * @brief 獲取DM9000的晶片ID
 * @return 返回值:DM9000的晶片ID值
 */
rt_uint32_t DM9000_Get_DeiviceID(void)
{
    rt_uint32_t value;
    value = DM9000_ReadReg(DM9000_VIDL);
    value |= DM9000_ReadReg(DM9000_VIDH) << 8;
    value |= DM9000_ReadReg(DM9000_PIDL) << 16;
    value |= DM9000_ReadReg(DM9000_PIDH) << 24;
    return value;
}

/**
 * @brief 獲取DM9000的連線速度和雙工模式
 * @return 返回值:0 是 100M半雙工
 *                1 是 100M全雙工
 *		          2 是 10M半雙工
 * 			      3 是 10M全雙工
 *                0XFF 是連線失敗!
 */
rt_uint8_t DM9000_Get_SpeedAndDuplex(void)
{
    rt_uint8_t temp;
    rt_uint8_t i = 0;
    if (stm32_eth_device.eth_mode == DM9000_AUTO) // 如果開啟了自動協商模式一定要等待協商完成
    {
        while (!(DM9000_PHY_ReadReg(0X01) & 0X0020)) // 等待自動協商完成
        {
            rt_thread_delay(100);
            i++;
            if (i > 100)
                return 0XFF; // 自動協商失敗
        }
    }
    else // 自定義模式,一定要等待連線成功
    {
        while (!(DM9000_ReadReg(DM9000_NSR) & 0X40)) // 等待連線成功
        {
            rt_thread_delay(100);
            i++;
            if (i > 100)
                return 0XFF; // 連線失敗
        }
    }
    temp = ((DM9000_ReadReg(DM9000_NSR) >> 6) & 0X02);  // 獲取DM9000的連線速度
    temp |= ((DM9000_ReadReg(DM9000_NCR) >> 3) & 0X01); // 獲取DM9000的雙工狀態
    return temp;
}

static rt_err_t rt_stm32_eth_init(rt_device_t dev)
{
    return RT_EOK;
}

static rt_err_t rt_stm32_eth_open(rt_device_t dev, rt_uint16_t oflag)
{
    LOG_D("emac open");
    return RT_EOK;
}

static rt_err_t rt_stm32_eth_close(rt_device_t dev)
{
    LOG_D("emac close");
    return RT_EOK;
}

static rt_size_t rt_stm32_eth_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
    LOG_D("emac read");
    rt_set_errno(-RT_ENOSYS);
    return 0;
}

static rt_size_t rt_stm32_eth_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
    LOG_D("emac write");
    rt_set_errno(-RT_ENOSYS);
    return 0;
}

static rt_err_t rt_stm32_eth_control(rt_device_t dev, int cmd, void *args)
{
    LOG_D("rt_stm32_eth_control");
    switch (cmd)
    {
    case NIOCTL_GADDR:
        /* get mac address */
        if (args)
        {
            SMEMCPY(args, stm32_eth_device.dev_addr, 6);
        }
        else
        {
            return -RT_ERROR;
        }
        break;

    default:
        break;
    }

    return RT_EOK;
}

/**
 * @brief 通過DM9000傳送封包
 * @param dev 裝置結構體
 * @param p pbuf結構體指標
 */
rt_err_t rt_stm32_eth_tx(rt_device_t dev, struct pbuf *p)
{
    struct pbuf *q;
    rt_uint16_t pbuf_index = 0;
    rt_uint8_t word[2], word_index = 0;

    DM9000_WriteReg(DM9000_IMR, IMR_PAR); // 關閉網路卡中斷
    DM9000->REG = DM9000_MWCMD;           // 傳送此命令後就可以將要傳送的資料搬到DM9000 TX SRAM中
    q = p;
    // 向DM9000的TX SRAM中寫入資料,一次寫入兩個位元組資料
    // 當要傳送的資料長度為奇數的時候,我們需要將最後一個位元組單獨寫入DM9000的TX SRAM中
    while (q)
    {
        if (pbuf_index < q->len)
        {
            word[word_index++] = ((u8_t *)q->payload)[pbuf_index++];
            if (word_index == 2)
            {
                DM9000->DATA = ((rt_uint16_t)word[1] << 8) | word[0];
                word_index = 0;
            }
        }
        else
        {
            q = q->next;
            pbuf_index = 0;
        }
    }
    // 還有一個位元組未寫入TX SRAM
    if (word_index == 1)
        DM9000->DATA = word[0];
    // 向DM9000寫入傳送長度
    DM9000_WriteReg(DM9000_TXPLL, p->tot_len & 0XFF);
    DM9000_WriteReg(DM9000_TXPLH, (p->tot_len >> 8) & 0XFF); // 設定要傳送資料的資料長度
    DM9000_WriteReg(DM9000_TCR, 0X01);                       // 啟動傳送
    while ((DM9000_ReadReg(DM9000_ISR) & 0X02) == 0)
        ;                                                  // 等待傳送完成
    DM9000_WriteReg(DM9000_ISR, 0X02);                     // 清除傳送完成中斷
    DM9000_WriteReg(DM9000_IMR, stm32_eth_device.imr_all); // DM9000網路卡接收中斷使能
    return ERR_OK;
}

/**
 * @brief DM9000接收封包,接收到的封包存放在DM9000的RX FIFO中,地址為0X0C00~0X3FFF
 * 接收到的封包的前四個位元組並不是真實的資料,而是有特定含義的,如下
 * byte1:表明是否接收到資料,為0x00或者0X01,如果兩個都不是的話一定要軟體復位DM9000
 *          0x01,接收到資料
 *          0x00,未接收到資料
 * byte2:第二個位元組表示一些狀態資訊,和DM9000的RSR(0X06)暫存器一致的
 * byte3:本幀資料長度的低位元組
 * byte4:本幀資料長度的高位元組
 * @return pbuf格式的接收到的封包
 */
struct pbuf *rt_stm32_eth_rx(rt_device_t dev)
{
    // LOG_E("rt_stm32_eth_rx");

    struct pbuf *p;
    struct pbuf *q;
    rt_uint8_t rxbyte;
    volatile rt_uint16_t rx_status, rx_length;
    rt_uint16_t *data;
    rt_uint16_t dummy;
    rt_int32_t len;

    p = NULL;
__error_retry:
    DM9000_ReadReg(DM9000_MRCMDX);     // 假讀
    rxbyte = (rt_uint8_t)DM9000->DATA; // 進行第二次讀取
    if (rxbyte)                        // 接收到資料
    {
        if (rxbyte > 1) // rxbyte大於1,接收到的資料錯誤,掛了
        {
            LOG_E("dm9000 rx: rx error, stop device\r\n");
            DM9000_WriteReg(DM9000_RCR, 0x00);
            DM9000_WriteReg(DM9000_ISR, 0x80);
            return (struct pbuf *)p;
        }
        DM9000->REG = DM9000_MRCMD;
        rx_status = DM9000->DATA;
        rx_length = DM9000->DATA;
        // if(rx_length>512)printf("rxlen:%d\r\n",rx_length);
        p = pbuf_alloc(PBUF_RAW, rx_length, PBUF_POOL); // pbufs記憶體池分配pbuf
        if (p != NULL)                                  // 記憶體申請成功
        {
            for (q = p; q != NULL; q = q->next)
            {
                data = (rt_uint16_t *)q->payload;
                len = q->len;
                while (len > 0)
                {
                    *data = DM9000->DATA;
                    data++;
                    len -= 2;
                }
            }
        }
        else // 記憶體申請失敗
        {
            LOG_E("pbuf記憶體申請失敗:%d\r\n", rx_length);
            data = &dummy;
            len = rx_length;
            while (len)
            {
                *data = DM9000->DATA;
                len -= 2;
            }
        }
        // 根據rx_status判斷接收資料是否出現如下錯誤:FIFO溢位、CRC錯誤
        // 對齊錯誤、物理層錯誤,如果有任何一個出現的話丟棄該資料框,
        // 當rx_length小於64或者大於最巨量資料長度的時候也丟棄該資料框
        if ((rx_status & 0XBF00) || (rx_length < 0X40) || (rx_length > DM9000_PKT_MAX))
        {
            LOG_E("rx_status:%#x\r\n", rx_status);
            if (rx_status & 0x100)
                LOG_E("rx fifo error\r\n");
            if (rx_status & 0x200)
                LOG_E("rx crc error\r\n");
            if (rx_status & 0x8000)
                LOG_E("rx length error\r\n");
            if (rx_length > DM9000_PKT_MAX)
            {
                LOG_E("rx length too big\r\n");
                DM9000_WriteReg(DM9000_NCR, NCR_RST); // 復位DM9000
                rt_thread_delay(10);
            }
            if (p != NULL)
                pbuf_free((struct pbuf *)p); // 釋放記憶體
            p = NULL;
            goto __error_retry;
        }
    }
    else
    {
        DM9000_WriteReg(DM9000_ISR, ISR_PTS);         // 清除所有中斷標誌位
        stm32_eth_device.imr_all = IMR_PAR | IMR_PRI; // 重新接收中斷
        DM9000_WriteReg(DM9000_IMR, stm32_eth_device.imr_all);
    }
    return (struct pbuf *)p;
}

/**
 * @brief DM9000 中斷處理常式
 */
static void eth_phy_isr(void *args)
{
    rt_uint16_t int_status;
    rt_uint16_t last_io;
    last_io = DM9000->REG;
    int_status = DM9000_ReadReg(DM9000_ISR);
    DM9000_WriteReg(DM9000_ISR, int_status); // 清除中斷標誌位,DM9000的ISR暫存器的bit0~bit5寫1清零
    if (int_status & ISR_ROS)
        rt_kprintf("overflow\r\n");
    if (int_status & ISR_ROOS)
        rt_kprintf("overflow counter overflow \r\n");
    if (int_status & ISR_PRS) // 接收中斷
    {
        /* 執行緒中接收資料 */
        eth_device_linkchange(&stm32_eth_device.parent, RT_TRUE);
        // eth_device_linkchange(&stm32_eth_device.parent, RT_FALSE);
    }
    if (int_status & ISR_PTS) // 傳送中斷
    {
        // 傳送完成中斷,使用者自行新增所需程式碼
    }
    DM9000->REG = last_io;
    // rt_kprintf("RT_INT.... \r\n");
}

/**
 * @brief DM9000 管理執行緒
 */
static void dm9000_monitor_thread_entry(void *parameter)
{
    rt_uint32_t temp;

    /* 設定 PG6 引腳為外部中斷 */
    rt_pin_mode(PHY_INT_PIN, PIN_MODE_INPUT_PULLUP);
    rt_pin_attach_irq(PHY_INT_PIN, PIN_IRQ_MODE_FALLING, eth_phy_isr, (void *)"callbackargs");
    rt_pin_irq_enable(PHY_INT_PIN, PIN_IRQ_ENABLE);

    /* 復位DM9000 */
    DM9000_Reset();
    rt_thread_delay(100);

    /* 判斷是否復位成功 */
    temp = DM9000_Get_DeiviceID(); // 獲取DM9000ID
    if (temp != DM9000_ID)         // 讀取ID錯誤
    {
        LOG_E("DM9000 reset failed");
        return;
    }
    LOG_D("DM9000 ID: %#x", temp);

    /* 設定PHY工作模式 */
    DM9000_Set_PHYMode(stm32_eth_device.eth_mode);

    DM9000_WriteReg(DM9000_NCR, 0X00);
    DM9000_WriteReg(DM9000_TCR, 0X00); // 傳送控制暫存器清零
    DM9000_WriteReg(DM9000_BPTR, 0X3F);
    DM9000_WriteReg(DM9000_FCTR, 0X38);
    DM9000_WriteReg(DM9000_FCR, 0X00);
    DM9000_WriteReg(DM9000_SMCR, 0X00);                                // 特殊模式
    DM9000_WriteReg(DM9000_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END); // 清除傳送狀態
    DM9000_WriteReg(DM9000_ISR, 0X0F);                                 // 清除中斷狀態
    DM9000_WriteReg(DM9000_TCR2, 0X80);                                // 切換LED到mode1
    /* 設定MAC地址和組播地址 */
    DM9000_Set_MACAddress(stm32_eth_device.dev_addr);      // 設定MAC地址
    DM9000_Set_Multicast(stm32_eth_device.multicase_addr); // 設定組播地址
    DM9000_WriteReg(DM9000_RCR, RCR_DIS_LONG | RCR_DIS_CRC | RCR_RXEN);
    DM9000_WriteReg(DM9000_IMR, IMR_PAR);

    /* 獲取DM9000的連線速度和雙工狀態 */
    temp = DM9000_Get_SpeedAndDuplex();
    if (temp != 0XFF) // 連線成功,通過串列埠顯示連線速度和雙工狀態
    {
        LOG_I("DM9000 Speed:%dMbps,Duplex:%s duplex mode\r\n", (temp & 0x02) ? 10 : 100, (temp & 0x01) ? "Full" : "Half");
    }
    else
    {
        LOG_E("DM9000 Establish Link Failed!\r\n");
    }

    /* 設定 DM900 為中斷接收方式 */
    DM9000_WriteReg(DM9000_IMR, stm32_eth_device.imr_all);
}

/**
 * @brief 註冊網路卡裝置
 */
static int rt_hw_stm32_eth_init(void)
{
    rt_err_t state = RT_EOK;

    /* 設定工作方式為自動模式 */
    stm32_eth_device.eth_mode = DM9000_AUTO;
    /* DM9000的SRAM的傳送和接收指標自動返回到開始地址,並且開啟接收中斷 */
    stm32_eth_device.imr_all = IMR_PAR | IMR_PRI;

    /* 前三位可以自定義 */
    stm32_eth_device.dev_addr[0] = 0x02;
    stm32_eth_device.dev_addr[1] = 0x00;
    stm32_eth_device.dev_addr[2] = 0x00;
    /* 根據 96 位唯一 ID 生成 MAC 地址(僅用於測試) */
    stm32_eth_device.dev_addr[3] = *(rt_uint8_t *)(UID_BASE + 4);
    stm32_eth_device.dev_addr[4] = *(rt_uint8_t *)(UID_BASE + 2);
    stm32_eth_device.dev_addr[5] = *(rt_uint8_t *)(UID_BASE + 0);

    // 初始化組播地址
    stm32_eth_device.multicase_addr[0] = 0Xff;
    stm32_eth_device.multicase_addr[1] = 0Xff;
    stm32_eth_device.multicase_addr[2] = 0Xff;
    stm32_eth_device.multicase_addr[3] = 0Xff;
    stm32_eth_device.multicase_addr[4] = 0Xff;
    stm32_eth_device.multicase_addr[5] = 0Xff;
    stm32_eth_device.multicase_addr[6] = 0Xff;
    stm32_eth_device.multicase_addr[7] = 0Xff;

    stm32_eth_device.parent.parent.init = rt_stm32_eth_init;
    stm32_eth_device.parent.parent.open = rt_stm32_eth_open;
    stm32_eth_device.parent.parent.close = rt_stm32_eth_close;
    stm32_eth_device.parent.parent.read = rt_stm32_eth_read;
    stm32_eth_device.parent.parent.write = rt_stm32_eth_write;
    stm32_eth_device.parent.parent.control = rt_stm32_eth_control;
    stm32_eth_device.parent.parent.user_data = RT_NULL;

    stm32_eth_device.parent.eth_rx = rt_stm32_eth_rx;
    stm32_eth_device.parent.eth_tx = rt_stm32_eth_tx;

    /* 註冊網路卡裝置 */
    state = eth_device_init(&(stm32_eth_device.parent), "e0");
    if (RT_EOK != state)
    {
        LOG_E("emac device init faild: %d", state);
        return -RT_ERROR;
    }

    /* 初始化DM9000 */
    state = DM9000_Init();
    if (RT_EOK != state)
    {
        LOG_E("DM9000 initialization failed");
        return -RT_ERROR;
    }

    /* 啟動 PHY 監視器, 線上程中完成網路卡的設定 */
    rt_thread_t tid;
    tid = rt_thread_create("dm9000",
                           dm9000_monitor_thread_entry,
                           RT_NULL,
                           1024,
                           RT_THREAD_PRIORITY_MAX - 2,
                           2);
    if (tid != RT_NULL)
    {
        rt_thread_startup(tid);
        state = RT_EOK;
    }
    else
    {
        state = -RT_ERROR;
    }

    return state;
}
INIT_DEVICE_EXPORT(rt_hw_stm32_eth_init);

#ifdef RT_USING_FINSH
#include <rtthread.h>
static void red_dm9000_reg(int argv, char *argc[])
{
    if (argv != 2)
    {
        rt_kprintf("Please enter the correct command, such as dm9000_ Red 0 \r\n");
        return;
    }

    rt_kprintf("0x%02X \r\n", DM9000_ReadReg(atoi(argc[1])));
}

MSH_CMD_EXPORT_ALIAS(red_dm9000_reg, DM9000_read_reg, Read the register value of DM9000);
#endif /* RT_USING_FINSH */
#endif /*  BSP_USING_LCD */