【STM32F407】第6章 RL-TCPnet底層驅動說明
最新教程下載:http://www.armbbs.cn/forum.php?mod=viewthread&tid=95243
第6章 RL-TCPnet底層驅動說明
本章節為大家講解RL-TCPnet的底層驅動,主要是STM32自帶MAC的驅動實現和PHY的驅動實現。
6.1初學者重要提示
6.2 MAC+PHY驅動實現方案
6.3 CMSIS-Driver簡介和驅動工作流程
6.4 CMSIS-Driver的PHY底層驅動實現
6.5 CMSIS-Driver的MAC底層驅動實現
6.6 總結
6.1 初學者重要提示
- 學習本章節前,務必學習STM32參考手冊中MAC章節的基礎知識講解,非常重要。
- DM9161和DM9162的手冊可以在官網地址下載,本章節需要用到部分暫存器:http://www.davicom.com.tw/production-item.php。
- 早期STM32F407開發板使用的PHY晶片是DM9161,不過現在基本已經停產了,當前F407,F429和H7開發板統一使用DM9162。底層程式碼對這兩個晶片都可以正確驅動。
- MAC全稱Media Access Control,媒介訪問控制。
- PHY全稱Physical Interface Transceiver,物理層介面收發器。
6.2 MAC+PHY驅動實現方案
STM32F4自帶MAC,所以只需外接PHY晶片即可使用乙太網,示意圖如下:
當前V5開發板使用的PHY晶片是DM9162。反映到硬體設計上,原圖如下:
通過這個原理圖,我們要注意以下兩點:
- 1.8V的電壓是PHY晶片DM916x自己產生的。
- PHY晶片的地址由PHYAD[0:3]引腳決定,當前是將PHYAD[0]引腳接了一個上拉電阻,也就是說DM916x的地址是0x01。
教程配套的開發板採用的RMII介面,即下面這種硬體介面方式:
RMII介面降低了 10/100Mbps下微控制器乙太網外設與外部PHY間的引腳數。根據IEEE 802.3u標準, MII包括16個數據和控制訊號的引腳。RMII規範將引腳數減少為7個(引腳數減少62.5%)。RMII具有以下特性:
- 支援10Mbps和100Mbps的執行速率。
- 參考時鐘必須是 50 MHz。
- 相同的參考時鐘必須從外部提供給 MAC 和外部乙太網 PHY。
- 它提供了獨立的2位寬(雙位)的傳送和接收資料路徑,即發生和接收都是佔用了兩個引腳。
6.3 CMSIS-Driver簡介和驅動工作流程
這個是ARM做好的驅動框架,支援的外設如下:
針對不同廠商,ARM會出一個完整的驅動包,比如STM32F4系列,在MDK安裝目錄的此路徑下(前提是大家安裝了STM32H7軟體包):ARM\PACK\Keil\STM32F4xx_DFP\2.14.0\CMSIS\Driver。
ARM做的這個驅動跟HAL庫有什麼區別呢?ARM做的這個庫要呼叫到HAL的一些API(H7版的有呼叫到,F4版的很少呼叫到,基本是獨立的),然後封裝了一些比較好用的API,方便使用者呼叫。
關於這些不同外設的驅動檔案,它們都有統一的API函式,呼叫流程如下:
關於這個驅動的流程,大家有個認識即可,網路協議棧會直接呼叫這些API進行操作,無需使用者去呼叫。
6.4 CMSIS-Driver的PHY底層驅動實現
PHY驅動由CMSIS-Driver軟體包提供,當前支援的PHY如下(位於MDK安裝路徑ARM\CMSIS-Driver\2.4.0\ETH,數字2.4.0表示當前的CMSIS-Driver版本):
這些驅動檔案主要分為兩類:
- 以ETH開頭的,這些晶片是MAC+PHY二合一。
- 以PHY開頭的,這些晶片僅是個PHY。
CMSIS-Driver現有的驅動裡面是沒有DM9162,所以需要使用者自己實現,這裡將DM9162的實現函式逐一為大家做個說明。CMSIS-Driver已經定義好了API,使用者實現每個API的具體功能即可。
6.4.1 DM9161和DM9162的區別
早期我們釋出的STM32F407開發板的PHY晶片使用的是DM9161,現在這個晶片基本已經停產,所以已經統一改成使用DM9162,這兩個型號主要在以下兩個地方有區別,其它基本都一樣。
- 兩個PHY晶片的的ID不一樣,DM9161的ID是0x0181B8B1,DM9162的ID是0x0181B8A0。
- 系統剛上電時,DM9161的ID暫存器支援立即讀取,但是DM9162不支援,這一點使用者在使用的時候要特別注意。但是DM9161和DM9162都支援立即寫暫存器BMCR,所以當前的操作就是直接對暫存器BMCR發覆位命令,然後再進行相關設定。
對於這兩個晶片,瞭解這兩點區別就可以了。另外,這兩個晶片的手冊和其它的相關知識在這個帖子裡面進行了簡單的彙總:http://www.armbbs.cn/forum.php?mod=viewthread&tid=19577
6.4.2 函式GetVersion
函式原型:
static ARM_DRIVER_VERSION GetVersion (void) { return DriverVersion; }
函式描述:
用於獲取當前的PHY驅動版本。
6.4.3 函式Initialize
函式原型:
static int32_t Initialize (ARM_ETH_PHY_Read_t fn_read, ARM_ETH_PHY_Write_t fn_write) { if ((fn_read == NULL) || (fn_write == NULL)) { return ARM_DRIVER_ERROR_PARAMETER; } if ((PHY.flags & PHY_INIT) == 0U) { /* Register PHY read/write functions. */ PHY.reg_rd = fn_read; PHY.reg_wr = fn_write; PHY.bmcr = 0U; PHY.flags = PHY_INIT; } return ARM_DRIVER_OK; }
函式描述:
初始化讀寫PHY晶片所需要的API
函式引數:
- 第1個引數是讀PHY晶片API地址。
- 第2個引數是寫PHY晶片API地址。
- 返回值,無引數錯誤返回ARM_DRIVER_OK。有引數錯誤返回ARM_DRIVER_ERROR_PARAMETER。
6.4.4 函式Uninitialize
函式原型:
static int32_t Uninitialize (void) { PHY.reg_rd = NULL; PHY.reg_wr = NULL; PHY.bmcr = 0U; PHY.flags = 0U; return ARM_DRIVER_OK; }
函式描述:
復位讀寫PHY晶片所需要的API。
函式引數:
- 返回值,返回ARM_DRIVER_OK
6.4.5 函式PowerControl
函式原型:
static int32_t PowerControl (ARM_POWER_STATE state) { uint16_t val; switch ((int32_t)state) { /* 將PHY斷電 */ case ARM_POWER_OFF: /* 初始化狀態才可以配置POWER OFF */ if ((PHY.flags & PHY_INIT) == 0U) { return ARM_DRIVER_ERROR; } PHY.flags &= ~PHY_POWER; PHY.bmcr = BMCR_POWER_DOWN; /* 設定BMCR暫存器,斷電 */ return (PHY.reg_wr(ETH_PHY_ADDR, REG_BMCR, PHY.bmcr)); /* PHY上電,並清除BMCR暫存器 */ case ARM_POWER_FULL: /* 初始化狀態才可以配置POWER FULL */ if ((PHY.flags & PHY_INIT) == 0U) { return ARM_DRIVER_ERROR; } /* 已經處於POWER ON狀態,直接返回OK */ if (PHY.flags & PHY_POWER) { return ARM_DRIVER_OK; } /* 讀取裝置 */ PHY.reg_rd(ETH_PHY_ADDR, REG_PHYIDR1, &val); /* 讀取ID1 */ if (val != PHY_ID1) { return ARM_DRIVER_ERROR_UNSUPPORTED; } PHY.reg_rd(ETH_PHY_ADDR, REG_PHYIDR2, &val); /* 讀取ID2, 此處做了一個特別處理,遮蔽後面8個bit,方便DM9162和DM9161都可以識別,因為這兩個 PHY後面的ID不同。 */ if ((val & 0xFF00) != PHY_ID2) { return ARM_DRIVER_ERROR_UNSUPPORTED; } /* DM916X用不到這個 */ #if (ETH_PHY_REF_CLK_50M != 0) PHY.reg_rd(ETH_PHY_ADDR, REG_PHYCR2, &val); val |= PHYCR2_REF_CLK_SELECT; PHY.reg_wr(ETH_PHY_ADDR, REG_PHYCR2, val); #endif PHY.bmcr = 0U; /* BMCR暫存器清零 */ if (PHY.reg_wr(ETH_PHY_ADDR, REG_BMCR, PHY.bmcr) != ARM_DRIVER_OK) { return ARM_DRIVER_ERROR; } PHY.flags |= PHY_POWER; return ARM_DRIVER_OK; /* 不支援低功耗操作 */ case ARM_POWER_LOW: default: return ARM_DRIVER_ERROR_UNSUPPORTED; } }
函式描述:
用於控制PHY的上電和斷電。
函式引數:
- 第1個引數是PHY配置
- ARM_POWER_OFF 表示斷電,程式此處做了BMCR暫存器斷電操作。
- ARM_POWER_FULL 表示上電,程式此處讀取PHY的ID,並清除BMCR暫存器。
- ARM_POWER_LOW 表示低功耗,程式此處不支援。
- 第2個引數是寫PHY晶片API地址。
- 返回值,設定正確返回ARM_DRIVER_OK,設定錯誤返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_UNSUPPORTED表示不支援。
6.4.6 函式SetInterface
函式原型:
static int32_t SetInterface (uint32_t interface) { int32_t status; if ((PHY.flags & PHY_POWER) == 0U) { return ARM_DRIVER_ERROR; } /* 僅作了RMII介面支援 */ switch (interface) { case ARM_ETH_INTERFACE_RMII: status = ARM_DRIVER_OK; break; default: status = ARM_DRIVER_ERROR_UNSUPPORTED; break; } return (status); }
函式描述:
用於配置使用SMII,RMII還是MII介面外接的PHY晶片。
函式引數:
- 第1個引數設定使用的PHY介面型別。
- ARM_ETH_INTERFACE_MII,Media Independent Interface (MII)
- ARM_ETH_INTERFACE_RMII,Reduced Media Independent Interface (RMII)
- ARM_ETH_INTERFACE_SMII,Serial Media Independent Interface (SMII)
- 返回值,設定正確返回ARM_DRIVER_OK,設定錯誤返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_UNSUPPORTED表示不支援。
6.4.7 函式SetMode
函式原型:
static int32_t SetMode (uint32_t mode) { uint16_t val; /* 上電狀態才可以配置 */ if ((PHY.flags & PHY_POWER) == 0U) { return ARM_DRIVER_ERROR; } val = PHY.bmcr & BMCR_POWER_DOWN; /* 速度配置10M或者100M */ switch (mode & ARM_ETH_PHY_SPEED_Msk) { case ARM_ETH_PHY_SPEED_10M: break; case ARM_ETH_PHY_SPEED_100M: val |= BMCR_SPEED_SELECT; break; default: return ARM_DRIVER_ERROR_UNSUPPORTED; } /* 全雙工或者半雙工配置 */ switch (mode & ARM_ETH_PHY_DUPLEX_Msk) { case ARM_ETH_PHY_DUPLEX_HALF: break; case ARM_ETH_PHY_DUPLEX_FULL: val |= BMCR_DUPLEX_MODE; break; default: return ARM_DRIVER_ERROR_UNSUPPORTED; } /* 自動協商配置使能 */ if (mode & ARM_ETH_PHY_AUTO_NEGOTIATE) { val |= BMCR_ANEG_EN; } /* 迴環配置使能,方便迴環測試 */ if (mode & ARM_ETH_PHY_LOOPBACK) { val |= BMCR_LOOPBACK; } /* 設定隔離,電氣隔離RMII/MII/SMII介面 */ if (mode & ARM_ETH_PHY_ISOLATE) { val |= BMCR_ISOLATE; } PHY.bmcr = val; return (PHY.reg_wr(ETH_PHY_ADDR, REG_BMCR, PHY.bmcr)); }
函式描述:
用於設定PHY晶片的工作模式。
函式引數:
- 第1個引數設定工作模式,這幾項支援或操作。
- 返回值,設定正確返回ARM_DRIVER_OK,設定錯誤返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_UNSUPPORTED表示不支援。
6.4.8 函式GetLinkState
函式原型:
static ARM_ETH_LINK_STATE GetLinkState (void) { ARM_ETH_LINK_STATE state; uint16_t val = 0U; if (PHY.flags & PHY_POWER) { PHY.reg_rd(ETH_PHY_ADDR, REG_BMSR, &val); } state = (val & BMSR_LINK_STAT) ? ARM_ETH_LINK_UP : ARM_ETH_LINK_DOWN; return (state); }
函式描述:
用於獲取網線插拔狀態。
函式引數:
- 返回值,返回ARM_ETH_LINK_UP表示網線在連線狀態,返回ARM_ETH_LINK_DOWN表示網線處於斷開狀態。
6.4.9 函式GetLinkInfo
函式原型:
static ARM_ETH_LINK_INFO GetLinkInfo (void) { ARM_ETH_LINK_INFO info; uint16_t val = 0U; if (PHY.flags & PHY_POWER) { /* 讀取PHY DSCSR 暫存器 */ PHY.reg_rd(ETH_PHY_ADDR, REG_DSCSR, &val); } /* 獲取速度和雙工模式 */ info.speed = ((val & DSCSR_100M_FD)|(val & DSCSR_100M_HD)) ? ARM_ETH_SPEED_100M : ARM_ETH_SPEED_10M; info.duplex = ((val & DSCSR_100M_FD)|(val & DSCSR_10M_FD)) ? ARM_ETH_DUPLEX_FULL : ARM_ETH_DUPLEX_HALF; return (info); }
函式描述:
用於獲取速度和雙工模式。
函式引數:
- 返回值記錄速度(10Mbps或者100Mbps)和雙工模式(半雙工或者全雙工)。
6.5 CMSIS-Driver的MAC底層驅動實現
KEIL已經為STM32F4製作好MAC驅動檔案EMAC_STM32F4xx.c。我們這裡將相關實現函式為大家做個說明。
6.5.1 函式GetVersion
函式原型:
static ARM_DRIVER_VERSION GetVersion (void) { return DriverVersion; }
函式描述:
用於獲取當前的MAC驅動版本。
6.5.2 函式GetCapabilities
函式原型:
#ifndef EMAC_CHECKSUM_OFFLOAD #define EMAC_CHECKSUM_OFFLOAD 1 #endif static const ARM_ETH_MAC_CAPABILITIES DriverCapabilities = { (EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_rx_ip4 */ (EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_rx_ip6 */ (EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_rx_udp */ (EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_rx_tcp */ (EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_rx_icmp */ (EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_tx_ip4 */ (EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_tx_ip6 */ (EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_tx_udp */ (EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_tx_tcp */ (EMAC_CHECKSUM_OFFLOAD != 0) ? 1U : 0U, /* checksum_offload_tx_icmp */ (ETH_MII != 0) ? ARM_ETH_INTERFACE_MII : ARM_ETH_INTERFACE_RMII, /* media_interface */ 0U, /* mac_address */ 1U, /* event_rx_frame */ 1U, /* event_tx_frame */ 1U, /* event_wakeup */ (EMAC_TIME_STAMP != 0) ? 1U : 0U /* precision_timer */ #if (defined(ARM_ETH_MAC_API_VERSION) && (ARM_ETH_MAC_API_VERSION >= 0x201U)) , 0U /* reserved bits */ #endif }; static ARM_ETH_MAC_CAPABILITIES GetCapabilities (void) { return DriverCapabilities; }
函式描述:
用於獲取MAC的硬體功能。
從當前的巨集定義來看,支援傳送和接收的IP4,IP6,UDP,TCP和ICMP的硬體校驗和計算。
6.5.3 函式Initialize
函式原型:
static int32_t Initialize (ARM_ETH_MAC_SignalEvent_t cb_event) { #if defined(RTE_DEVICE_FRAMEWORK_CLASSIC) GPIO_InitTypeDef GPIO_InitStruct; const ETH_PIN *io; #endif /* 使能SYSCFG時鐘 */ RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; #if (ETH_MII == 0) SYSCFG->PMC |= SYSCFG_PMC_MII_RMII_SEL; #else SYSCFG->PMC &= ~SYSCFG_PMC_MII_RMII_SEL; #endif #if defined(RTE_DEVICE_FRAMEWORK_CLASSIC) /* 配置乙太網引腳 */ GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; GPIO_InitStruct.Alternate = GPIO_AF11_ETH; for (io = eth_pins; io != ð_pins[sizeof(eth_pins)/sizeof(ETH_PIN)]; io++) { Enable_GPIO_Clock (io->port); GPIO_InitStruct.Pin = io->pin; HAL_GPIO_Init (io->port, &GPIO_InitStruct); } #else heth.Instance = ETH; #endif /* 清空控制結構體 */ memset ((void *)&Emac, 0, sizeof (EMAC_CTRL)); Emac.cb_event = cb_event; Emac.flags = EMAC_FLAG_INIT; return ARM_DRIVER_OK; }
函式描述:
用於初始化MAC,配置乙太網引腳,註冊回撥函式。
函式引數:
- 第1個引數用於註冊回撥函式。
- 返回值,固定返回ARM_DRIVER_OK。
6.5.4 函式Uninitialize
函式原型:
static int32_t Uninitialize (void) { #if defined(RTE_DEVICE_FRAMEWORK_CLASSIC) const ETH_PIN *io; /* 復位乙太網引腳配置 */ for (io = eth_pins; io != ð_pins[sizeof(eth_pins)/sizeof(ETH_PIN)]; io++) { HAL_GPIO_DeInit(io->port, io->pin); } #else heth.Instance = NULL; #endif Emac.flags &= ~EMAC_FLAG_INIT; return ARM_DRIVER_OK; }
函式描述:
復位初始化。
函式引數:
- 返回值,固定返回ARM_DRIVER_OK。
6.5.5 函式PowerControl
函式原型:
static int32_t PowerControl (ARM_POWER_STATE state) { uint32_t hclk, clkdiv; if ((state != ARM_POWER_OFF) && (state != ARM_POWER_FULL) && (state != ARM_POWER_LOW)) { return ARM_DRIVER_ERROR_UNSUPPORTED; } switch (state) { /* 關閉電源 */ case ARM_POWER_OFF: 內容較多,省略未寫 break; /* 低功耗 */ case ARM_POWER_LOW: return ARM_DRIVER_ERROR_UNSUPPORTED; /* 上電 */ case ARM_POWER_FULL: 內容較多,省略未寫 break; } return ARM_DRIVER_OK; }
函式描述:
用於控制MAC的上電和斷電。
函式引數:
- 第1個引數是MAC配置
- ARM_POWER_OFF 表示斷電,程式此處做了MAC復位。
- ARM_POWER_FULL 表示上電,程式此處初始化MAC。
- ARM_POWER_LOW 表示低功耗,程式此處不支援。
- 返回值,設定正確返回ARM_DRIVER_OK,設定錯誤返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_UNSUPPORTED表示不支援。
6.5.6 函式GetMacAddress
函式原型:
static int32_t GetMacAddress (ARM_ETH_MAC_ADDR *ptr_addr) { uint32_t val; if (ptr_addr == NULL) { return ARM_DRIVER_ERROR_PARAMETER; } if ((Emac.flags & EMAC_FLAG_POWER) == 0U) { return ARM_DRIVER_ERROR; } val = ETH->MACA0HR; ptr_addr->b[5] = (uint8_t)(val >> 8); ptr_addr->b[4] = (uint8_t)(val); val = ETH->MACA0LR; ptr_addr->b[3] = (uint8_t)(val >> 24); ptr_addr->b[2] = (uint8_t)(val >> 16); ptr_addr->b[1] = (uint8_t)(val >> 8); ptr_addr->b[0] = (uint8_t)(val); return ARM_DRIVER_OK; }
函式描述:
用於獲取MAC地址。
函式引數:
- 第1個引數用於儲存獲取的MAC地址。
- 返回值,設定正確返回ARM_DRIVER_OK,設定錯誤返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_PARAMETER表示引數錯誤。
6.5.7 函式SetMacAddress
函式原型:
static int32_t SetMacAddress (const ARM_ETH_MAC_ADDR *ptr_addr) { if (ptr_addr == NULL) { return ARM_DRIVER_ERROR_PARAMETER; } if ((Emac.flags & EMAC_FLAG_POWER) == 0U) { return ARM_DRIVER_ERROR; } /* Set Ethernet MAC Address registers */ ETH->MACA0HR = ((uint32_t)ptr_addr->b[5] << 8) | (uint32_t)ptr_addr->b[4]; ETH->MACA0LR = ((uint32_t)ptr_addr->b[3] << 24) | ((uint32_t)ptr_addr->b[2] << 16) | ((uint32_t)ptr_addr->b[1] << 8) | (uint32_t)ptr_addr->b[0]; return ARM_DRIVER_OK; }
函式描述:
用於設定本身的MAC地址。包含這個MAC地址的乙太網幀才會被這個晶片所接受。也可以通過函式ARM_ETH_MAC_SetAddressFilter設定接收其它的MAC地址。除此之外,還可以通過函式ARM_ETH_MAC_Control 的引數ARM_ETH_MAC_CONFIGURE設定廣播和組播。
函式引數:
- 第1個引數是MAC地址。
- 返回值,設定正確返回ARM_DRIVER_OK,設定錯誤返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_PARAMETER表示引數錯誤。
6.5.8 函式SetAddressFilter
函式原型:
static int32_t SetAddressFilter (const ARM_ETH_MAC_ADDR *ptr_addr, uint32_t num_addr) { uint32_t crc; if ((ptr_addr == NULL) && (num_addr != 0)) { return ARM_DRIVER_ERROR_PARAMETER; } if ((Emac.flags & EMAC_FLAG_POWER) == 0U) { return ARM_DRIVER_ERROR; } /* 使用單播過濾前三個MAC */ ETH->MACFFR &= ~(ETH_MACFFR_HPF | ETH_MACFFR_HM); ETH->MACHTHR = 0U; ETH->MACHTLR = 0U; if (num_addr == 0U) { ETH->MACA1HR = 0U; ETH->MACA1LR = 0U; ETH->MACA2HR = 0U; ETH->MACA2LR = 0U; ETH->MACA3HR = 0U; ETH->MACA3LR = 0U; return ARM_DRIVER_OK; } ETH->MACA1HR = ((uint32_t)ptr_addr->b[5] << 8) | (uint32_t)ptr_addr->b[4] | ETH_MACA1HR_AE; ETH->MACA1LR = ((uint32_t)ptr_addr->b[3] << 24) | ((uint32_t)ptr_addr->b[2] << 16) | ((uint32_t)ptr_addr->b[1] << 8) | (uint32_t)ptr_addr->b[0]; num_addr--; if (num_addr == 0U) { ETH->MACA2HR = 0U; ETH->MACA2LR = 0U; ETH->MACA3HR = 0U; ETH->MACA3LR = 0U; return ARM_DRIVER_OK; } ptr_addr++; ETH->MACA2HR = ((uint32_t)ptr_addr->b[5] << 8) | (uint32_t)ptr_addr->b[4] | ETH_MACA2HR_AE; ETH->MACA2LR = ((uint32_t)ptr_addr->b[3] << 24) | ((uint32_t)ptr_addr->b[2] << 16) | ((uint32_t)ptr_addr->b[1] << 8) | (uint32_t)ptr_addr->b[0]; num_addr--; if (num_addr == 0U) { ETH->MACA3HR = 0U; ETH->MACA3LR = 0U; return ARM_DRIVER_OK; } ptr_addr++; ETH->MACA3HR = ((uint32_t)ptr_addr->b[5] << 8) | (uint32_t)ptr_addr->b[4] | ETH_MACA3HR_AE; ETH->MACA3LR = ((uint32_t)ptr_addr->b[3] << 24) | ((uint32_t)ptr_addr->b[2] << 16) | ((uint32_t)ptr_addr->b[1] << 8) | (uint32_t)ptr_addr->b[0]; num_addr--; if (num_addr == 0U) { return ARM_DRIVER_OK; } ptr_addr++; /* 計算剩餘MAC地址的64bit Hash表 */ for ( ; num_addr; ptr_addr++, num_addr--) { crc = crc32_data (&ptr_addr->b[0], 6U) >> 26; if (crc & 0x20U) { ETH->MACHTHR |= (1U << (crc & 0x1FU)); } else { ETH->MACHTLR |= (1U << crc); } } /* 使能單播和Hash地址過濾 */ ETH->MACFFR |= ETH_MACFFR_HPF | ETH_MACFFR_HM; return ARM_DRIVER_OK; }
函式描述:
用於乙太網MAC接收地址過濾,通過這個函式可以設定此裝置可以接收到的MAC地址(裝置本身MAC以外的地址)。MAC還可以通過函式ARM_ETH_MAC_Control 的引數ARM_ETH_MAC_CONFIGURE設定廣播和組播。
函式引數:
- 第1個引數是MAC地址列表。
- 第2個引數是MAC地址個數。
- 返回值,設定正確返回ARM_DRIVER_OK,設定錯誤返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_PARAMETER表示引數錯誤。
6.5.9 函式SendFrame
函式原型:
static int32_t SendFrame (const uint8_t *frame, uint32_t len, uint32_t flags) { uint8_t *dst = Emac.frame_end; uint32_t ctrl; if ((frame == NULL) || (len == 0U)) { return ARM_DRIVER_ERROR_PARAMETER; } if ((Emac.flags & EMAC_FLAG_POWER) == 0U) { return ARM_DRIVER_ERROR; } if (dst == NULL) { /* 啟動新的傳輸幀 */ if (tx_desc[Emac.tx_index].CtrlStat & DMA_TX_OWN) { /* 傳輸忙 */ return ARM_DRIVER_ERROR_BUSY; } dst = tx_desc[Emac.tx_index].Addr; tx_desc[Emac.tx_index].Size = len; } else { /* 分步傳輸 */ tx_desc[Emac.tx_index].Size += len; } /* 快速複製資料到ETH-DMA */ for ( ; len > 7U; dst += 8, frame += 8, len -= 8U) { __UNALIGNED_UINT32_WRITE(&dst[0], __UNALIGNED_UINT32_READ(&frame[0])); __UNALIGNED_UINT32_WRITE(&dst[4], __UNALIGNED_UINT32_READ(&frame[4])); } /* 複製剩餘位元組 */ for ( ; len > 1U; dst += 2, frame += 2, len -= 2U) { __UNALIGNED_UINT16_WRITE(&dst[0], __UNALIGNED_UINT16_READ(&frame[0])); } if (len > 0U) { dst++[0] = frame++[0]; } if (flags & ARM_ETH_MAC_TX_FRAME_FRAGMENT) { /* 還有資料,記錄當前寫入位置 */ Emac.frame_end = dst; return ARM_DRIVER_OK; } /* 幀就緒,傳送給DMA */ ctrl = tx_desc[Emac.tx_index].CtrlStat & ~DMA_TX_CIC; #if (EMAC_CHECKSUM_OFFLOAD != 0) if (Emac.tx_cks_offload) { /* The following is a workaround for EMAC silicon problem: */ /* "Incorrect layer 3 (L3) checksum is inserted in the sent */ /* IPv4 fragmented packets." */ /* Description: */ /* When automatic checksum insertion is enabled and the packet */ /* is IPv4 frame fragment, then the MAC may incorrectly insert */ /* checksum into the packet. This corrupts the payload data */ /* and generates checksum errors at the receiver. */ uint16_t prot = __UNALIGNED_UINT16_READ(&tx_desc[Emac.tx_index].Addr[12]); uint16_t frag = __UNALIGNED_UINT16_READ(&tx_desc[Emac.tx_index].Addr[20]); if ((prot == 0x0008) && (frag & 0xFF3F)) { /* Insert only IP header checksum in fragmented frame */ ctrl |= DMA_TX_CIC_IP; } else { /* Insert IP header and payload checksums (TCP,UDP,ICMP) */ ctrl |= DMA_TX_CIC; } } #endif ctrl &= ~(DMA_TX_IC | DMA_TX_TTSE); if (flags & ARM_ETH_MAC_TX_FRAME_EVENT) { ctrl |= DMA_TX_IC; } #if (EMAC_TIME_STAMP != 0) if (flags & ARM_ETH_MAC_TX_FRAME_TIMESTAMP) { ctrl |= DMA_TX_TTSE; } Emac.tx_ts_index = Emac.tx_index; #endif tx_desc[Emac.tx_index].CtrlStat = ctrl | DMA_TX_OWN; Emac.tx_index++; if (Emac.tx_index == NUM_TX_BUF) { Emac.tx_index = 0U; } Emac.frame_end = NULL; /* 啟動幀傳輸 */ ETH->DMASR = ETH_DMASR_TPSS; ETH->DMATPDR = 0U; return ARM_DRIVER_OK; }
函式描述:
用於控制乙太網幀資料的傳送。此函式會將使用者要傳送的資料存入到乙太網DMA緩衝裡面,而不必等待發送完成,只要有緩衝,就可以繼續往裡面存資料。
函式引數:
- 第1個引數是要傳送的資料地址。
- 第2個引數是傳送的位元組數。
- 第3個引數支援的配置如下:
6.5.10 函式ReadFrame
函式原型:
static int32_t ReadFrame (uint8_t *frame, uint32_t len) { uint8_t const *src = rx_desc[Emac.rx_index].Addr; int32_t cnt = (int32_t)len; if ((frame == NULL) && (len != 0U)) { return ARM_DRIVER_ERROR_PARAMETER; } if ((Emac.flags & EMAC_FLAG_POWER) == 0U) { return ARM_DRIVER_ERROR; } /* 快速複製資料到幀緩衝 */ for ( ; len > 7U; frame += 8, src += 8, len -= 8U) { __UNALIGNED_UINT32_WRITE(&frame[0], __UNALIGNED_UINT32_READ(&src[0])); __UNALIGNED_UINT32_WRITE(&frame[4], __UNALIGNED_UINT32_READ(&src[4])); } /* 複製剩餘7位元組 */ for ( ; len > 1U; frame += 2, src += 2, len -= 2U) { __UNALIGNED_UINT16_WRITE(&frame[0], __UNALIGNED_UINT16_READ(&src[0])); } if (len > 0U) { frame[0] = src[0]; } /* 設定此塊到ETH-DMA */ rx_desc[Emac.rx_index].Stat = DMA_RX_OWN; Emac.rx_index++; if (Emac.rx_index == NUM_RX_BUF) { Emac.rx_index = 0; } if (ETH->DMASR & ETH_DMASR_RBUS) { /* 沒有緩衝,釋放 */ ETH->DMASR = ETH_DMASR_RBUS; ETH->DMARPDR = 0; } return (cnt); }
函式描述:
用於讀取乙太網幀資料。
函式引數:
- 第1個引數是讀取資料的儲存地址。
- 第2個引數儲存資料的緩衝大小。
- 返回值,返回數值大於0,表示讀取的位元組數,返回數值小於0表示出錯。
注意事項:
呼叫此函式前,需要先呼叫函式ARM_ETH_MAC_Control (ARM_ETH_MAC_CONTROL_RX , 1)使能接收。
6.5.11 函式GetRxFrameSize
函式原型:
static uint32_t GetRxFrameSize (void) { uint32_t stat = rx_desc[Emac.rx_index].Stat; if ((Emac.flags & EMAC_FLAG_POWER) == 0U) { return (0U); } if (stat & DMA_RX_OWN) { /* DMA使用中 */ return (0U); } if (((stat & DMA_RX_ES) != 0) || ((stat & DMA_RX_FS) == 0) || ((stat & DMA_RX_LS) == 0)) { /* 錯誤,塊無效 */ return (0xFFFFFFFFU); } return (((stat & DMA_RX_FL) >> 16) - 4U); }
函式描述:
用於獲取接收到的幀大小,此函式會在ARM_ETH_MAC_ReadFrame之前被呼叫。
函式引數:
- 返回值,返回接收到的資料大小。
注意事項:
幀大小包括MAC地址和接收到資料。此函式返回數值0表示接收緩衝區裡面沒有資料,如果接收到的數資料大於最大的幀大小或者小於最小的幀大小,都將被函式ARM_ETH_MAC_ReadFrame放棄。
6.5.12 函式GetRxFrameTime
函式原型:
static int32_t GetRxFrameTime (ARM_ETH_MAC_TIME *time) { #if (EMAC_TIME_STAMP) RX_Desc *rxd = &rx_desc[Emac.rx_index]; if ((Emac.flags & EMAC_FLAG_POWER) == 0U) { return ARM_DRIVER_ERROR; } if (rxd->Stat & DMA_RX_OWN) { /* DMA使用中 */ return ARM_DRIVER_ERROR_BUSY; } time->ns = rxd->TimeLo; time->sec = rxd->TimeHi; return ARM_DRIVER_OK; #else (void)time; return ARM_DRIVER_ERROR; #endif }
函式描述:
用於獲取乙太網接收幀時間戳。
函式引數:
- 第1個引數用於儲存獲取的乙太網傳送幀時間戳。
- 回值,設定正確返回ARM_DRIVER_OK,設定錯誤返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_UNSUPPORTED表示不支援。
注意事項:
必須在呼叫函式ARM_ETH_MAC_ReadFrame前,呼叫此函式。
6.5.13 函式GetTxFrameSize
函式原型:
static int32_t GetTxFrameTime (ARM_ETH_MAC_TIME *time) { #if (EMAC_TIME_STAMP) TX_Desc *txd = &tx_desc[Emac.tx_ts_index]; if ((Emac.flags & EMAC_FLAG_POWER) == 0U) { return ARM_DRIVER_ERROR; } if (txd->CtrlStat & DMA_RX_OWN) { /* DMA忙 */ return ARM_DRIVER_ERROR_BUSY; } if ((txd->CtrlStat & DMA_TX_TTSS) == 0) { /* 驅動錯誤,傳送時間戳不可用 */ return ARM_DRIVER_ERROR; } time->ns = txd->TimeLo; time->sec = txd->TimeHi; return ARM_DRIVER_OK; #else (void)time; return ARM_DRIVER_ERROR; #endif }
函式描述:
用於獲取乙太網傳送幀時間戳。
函式引數:
- 第1個引數用於儲存返回的時間戳。
- 返回值,設定正確返回ARM_DRIVER_OK,設定錯誤返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_UNSUPPORTED表示不支援。
6.5.14 函式ControlTimer
函式原型:
static int32_t ControlTimer (uint32_t control, ARM_ETH_MAC_TIME *time) { #if (EMAC_TIME_STAMP != 0) if ((Emac.flags & EMAC_FLAG_POWER) == 0U) { return ARM_DRIVER_ERROR; } if ((control != ARM_ETH_MAC_TIMER_GET_TIME) && (control != ARM_ETH_MAC_TIMER_SET_TIME) && (control != ARM_ETH_MAC_TIMER_INC_TIME) && (control != ARM_ETH_MAC_TIMER_DEC_TIME) && (control != ARM_ETH_MAC_TIMER_SET_ALARM) && (control != ARM_ETH_MAC_TIMER_ADJUST_CLOCK)) { return ARM_DRIVER_ERROR_PARAMETER; } switch (control) { case ARM_ETH_MAC_TIMER_GET_TIME: /* 獲取當前時間 */ time->sec = ETH->PTPTSHR; time->ns = ETH->PTPTSLR; break; case ARM_ETH_MAC_TIMER_SET_TIME: /* 設定新時間*/ ETH->PTPTSHUR = time->sec; ETH->PTPTSLUR = time->ns; /* 初始TS */ ETH->PTPTSCR |= ETH_PTPTSCR_TSSTI; break; case ARM_ETH_MAC_TIMER_INC_TIME: /* 增加當前時間 */ ETH->PTPTSHUR = time->sec; ETH->PTPTSLUR = time->ns; /* 更新 */ ETH->PTPTSCR |= ETH_PTPTSCR_TSSTU; break; case ARM_ETH_MAC_TIMER_DEC_TIME: /* 減少當前時間 */ ETH->PTPTSHUR = time->sec; ETH->PTPTSLUR = time->ns | 0x80000000U; /* 更新 */ ETH->PTPTSCR |= ETH_PTPTSCR_TSSTU; break; case ARM_ETH_MAC_TIMER_SET_ALARM: /* 設定鬧鐘時間 */ ETH->PTPTTHR = time->sec; ETH->PTPTTLR = time->ns; /* 使能PTP控制中的時間戳中斷 */ ETH->PTPTSCR |= ETH_PTPTSCR_TSITE; if (time->sec || time->ns) { /* 使能時間戳觸發中斷 */ ETH->MACIMR &= ~ETH_MACIMR_TSTIM; } else { /* 禁能時間戳觸發中斷 Disable */ ETH->MACIMR |= ETH_MACIMR_TSTIM; } break; case ARM_ETH_MAC_TIMER_ADJUST_CLOCK: /* 調整當前時間,精確校準 */ /* 校準因子Q31 (0x80000000 = 1.000000000) */ ETH->PTPTSAR = (uint32_t)(((uint64_t)time->ns * ETH->PTPTSAR) >> 31); /* 精確的TS時鐘校準 */ ETH->PTPTSCR |= ETH_PTPTSCR_TSARU; break; } return ARM_DRIVER_OK; #else (void)control; (void)time; return ARM_DRIVER_ERROR; #endif }
函式描述:
高精度定時器控制。
函式引數:
- 第1個引數是高精度定時器配置選項,支援的配置如下:
- 第2個引數設定時間。
- 返回值,設定正確返回ARM_DRIVER_OK,設定錯誤返回ARM_DRIVER_ERROR,而ARM_DRIVER_ERROR_UNSUPPORTED表示不支援。
6.5.15 函式Control
函式原型:
static int32_t Control (uint32_t control, uint32_t arg) { uint32_t maccr; uint32_t dmaomr; uint32_t macffr; if ((Emac.flags & EMAC_FLAG_POWER) == 0U) { return ARM_DRIVER_ERROR; } if ((control != ARM_ETH_MAC_CONFIGURE) && (control != ARM_ETH_MAC_CONTROL_TX) && (control != ARM_ETH_MAC_CONTROL_RX) && (control != ARM_ETH_MAC_FLUSH) && (control != ARM_ETH_MAC_SLEEP) && (control != ARM_ETH_MAC_VLAN_FILTER)) { return ARM_DRIVER_ERROR_PARAMETER; } switch (control) { case ARM_ETH_MAC_CONFIGURE: maccr = ETH->MACCR & ~(ETH_MACCR_FES | ETH_MACCR_DM | ETH_MACCR_LM | ETH_MACCR_IPCO); /* 配置100Mbps或者10Mbps模式 */ switch (arg & ARM_ETH_MAC_SPEED_Msk) { case ARM_ETH_MAC_SPEED_10M: #if (ETH_MII == 0) /* RMII Half Duplex Colision detection does not work */ maccr |= ETH_MACCR_DM; #endif break; case ARM_ETH_SPEED_100M: maccr |= ETH_MACCR_FES; break; default: return ARM_DRIVER_ERROR_UNSUPPORTED; } /* 配置全雙工或者半雙工模式 */ switch (arg & ARM_ETH_MAC_DUPLEX_Msk) { case ARM_ETH_MAC_DUPLEX_FULL: maccr |= ETH_MACCR_DM; break; case ARM_ETH_MAC_DUPLEX_HALF: break; default: return ARM_DRIVER_ERROR; } /* 配置迴環模式 */ if (arg & ARM_ETH_MAC_LOOPBACK) { maccr |= ETH_MACCR_LM; } dmaomr = ETH->DMAOMR & ~(ETH_DMAOMR_RSF| ETH_DMAOMR_TSF); #if (EMAC_CHECKSUM_OFFLOAD != 0) /* 使能接收校驗和驗證 */ if (arg & ARM_ETH_MAC_CHECKSUM_OFFLOAD_RX) { maccr |= ETH_MACCR_IPCO; dmaomr |= ETH_DMAOMR_RSF; } /* 使能傳送校驗和產生 */ if (arg & ARM_ETH_MAC_CHECKSUM_OFFLOAD_TX) { dmaomr |= ETH_DMAOMR_TSF; Emac.tx_cks_offload = true; } else { Emac.tx_cks_offload = false; } #else if ((arg & ARM_ETH_MAC_CHECKSUM_OFFLOAD_RX) || (arg & ARM_ETH_MAC_CHECKSUM_OFFLOAD_TX)) { /* 驅動程式禁止了硬體校驗和 */ return ARM_DRIVER_ERROR; } #endif ETH->DMAOMR = dmaomr; ETH->MACCR = maccr; macffr = ETH->MACFFR & ~(ETH_MACFFR_PM | ETH_MACFFR_PAM | ETH_MACFFR_BFD); /* 使能廣播幀接收 */ if ((arg & ARM_ETH_MAC_ADDRESS_BROADCAST) == 0) { macffr |= ETH_MACFFR_BFD; } /* 使能組播幀接收 */ if (arg & ARM_ETH_MAC_ADDRESS_MULTICAST) { macffr |= ETH_MACFFR_PAM; } /* 設定無過濾,所有幀都可以接收 */ if (arg & ARM_ETH_MAC_ADDRESS_ALL) { macffr |= ETH_MACFFR_PM; } ETH->MACFFR = macffr; break; case ARM_ETH_MAC_CONTROL_TX: /* 使能或者禁止MAC傳送 */ maccr = ETH->MACCR & ~ETH_MACCR_TE; dmaomr = ETH->DMAOMR & ~ETH_DMAOMR_ST; if (arg != 0) { init_dma (); maccr |= ETH_MACCR_TE; dmaomr |= ETH_DMAOMR_ST; } ETH->MACCR = maccr; ETH->DMAOMR = dmaomr; break; case ARM_ETH_MAC_CONTROL_RX: /* 使能或者禁止MAC接收 */ maccr = ETH->MACCR & ~ETH_MACCR_RE; dmaomr = ETH->DMAOMR & ~ETH_DMAOMR_SR; if (arg != 0) { init_dma (); maccr |= ETH_MACCR_RE; dmaomr |= ETH_DMAOMR_SR; } ETH->MACCR = maccr; ETH->DMAOMR = dmaomr; break; case ARM_ETH_MAC_FLUSH: /* 清空傳送或者接收緩衝 */ if (arg & ARM_ETH_MAC_FLUSH_RX) { } if (arg & ARM_ETH_MAC_FLUSH_TX) { ETH->DMAOMR |= ETH_DMAOMR_FTF; } break; case ARM_ETH_MAC_VLAN_FILTER: /* 配置VLAN過濾 */ ETH->MACVLANTR = arg; break; } return ARM_DRIVER_OK; }
函式描述:
用於MAC的配置
函式引數:
- 第1個引數支援的配置如下
- 第2個引數針對第1個引數做的具體配置。
- ARM_ETH_MAC_CONFIGURE 支援的配置:
-
- ARM_ETH_MAC_CONTROL_TX
0表示禁止傳送,1表示使能傳送。
-
- ARM_ETH_MAC_CONTROL_RX
0表示禁止接收,1表示使能接收。
-
- ARM_ETH_MAC_FLUSH支援的配置:
ARM_ETH_MAC_FLUSH_RX 表示接收清空。
ARM_ETH_MAC_FLUSH_TX 表示傳送清空。
-
- VLAN濾波器支援的配置:
6.5.16 函式PHY_Read
函式原型:
static int32_t PHY_Read (uint8_t phy_addr, uint8_t reg_addr, uint16_t *data) { uint32_t val, tick; if ((Emac.flags & EMAC_FLAG_POWER) == 0U) { return ARM_DRIVER_ERROR; } val = ETH->MACMIIAR & ETH_MACMIIAR_CR; ETH->MACMIIAR = val | ETH_MACMIIAR_MB | ((uint32_t)phy_addr << 11) | ((uint32_t)reg_addr << 6) ; /* 等待操作完成 */ tick = HAL_GetTick(); do { if ((ETH->MACMIIAR & ETH_MACMIIAR_MB) == 0U) { break; } } while ((HAL_GetTick() - tick) < PHY_TIMEOUT); if ((ETH->MACMIIAR & ETH_MACMIIAR_MB) == 0U) { *data = ETH->MACMIIDR & ETH_MACMIIDR_MD; return ARM_DRIVER_OK; } return ARM_DRIVER_ERROR_TIMEOUT; }
函式描述:
用於乙太網PHY晶片的讀操作。
函式引數:
- 第1個引數是PHY地址。
- 第2個引數是暫存器地址。
- 第3個引數是暫存器寫入的資料。
- 返回值,操作正確返回ARM_DRIVER_OK,操作錯誤返回ARM_DRIVER_ERROR。
6.5.17 函式PHY_Write
函式原型:
static int32_t PHY_Write (uint8_t phy_addr, uint8_t reg_addr, uint16_t data) { uint32_t val, tick; if ((Emac.flags & EMAC_FLAG_POWER) == 0U) { return ARM_DRIVER_ERROR; } ETH->MACMIIDR = data; val = ETH->MACMIIAR & ETH_MACMIIAR_CR; ETH->MACMIIAR = val | ETH_MACMIIAR_MB | ETH_MACMIIAR_MW | ((uint32_t)phy_addr << 11) | ((uint32_t)reg_addr << 6) ; /* 等待操作完成 */ tick = HAL_GetTick(); do { if ((ETH->MACMIIAR & ETH_MACMIIAR_MB) == 0U) { break; } } while ((HAL_GetTick() - tick) < PHY_TIMEOUT); if ((ETH->MACMIIAR & ETH_MACMIIAR_MB) == 0U) { return ARM_DRIVER_OK; } return ARM_DRIVER_ERROR_TIMEOUT; }
函式描述:
用於乙太網PHY晶片的寫操作。
函式引數:
- 第1個引數是PHY地址。
- 第2個引數是暫存器地址。
- 第3個引數是暫存器寫入的資料。
- 返回值,操作正確返回ARM_DRIVER_OK,操作錯誤返回ARM_DRIVER_ERROR。
6.6 總結
本章節就為大家講解這麼多,主要是為學習下個章節RL-TCPnet的移植做準備。學完本章後,務必將STM32參考手冊中MAC章節讀一遍。