一、DM9051NP 介紹
DM9051NP SPI接口以太网模块是联杰国际(DAVICOM)为了方便嵌入式ARM、MCU单片机系统进行以太网通信而开发出的解决方案。DM9051NP芯片是带有行业标准串行外设接口(Serial Peripheral Interface,SPI)的独立以太网控制器。DM9051NP符合IEEE 802.3 规范,支持以DMA 模式来传输,以实现数据传送快速。DM9051NP通过1个中断引脚和SPI接口来进行与主控制器MCU单片机的通信,数据传输规格为10/100 M。
・Package:32支接脚封装,QFN.
・IEEE 802.3az Energy Efficient Ethernet (EEE)
・Built-in integrated 3.3V to 1.8V regulator
・远程唤醒 (WOL)
・并行线/交叉线自动切换 HP Auto-MDIX
・Support 光口界面
・具有16KB SRAM静态随机存取记忆
・EMI (Class B) and HBM ESD Rating 8KV
・工业温度规范: –40℃ to +85℃
・功耗:(100/10 M) => 429/561 mW
・连续工作温度<60℃
二、驱动程序说明
1.DM9051的寄存器读写方式说明
由于DM9051NP以太网口芯片采用的是SPI串行接口模式,其对内部寄存器读写的规则是先发操作码bit 7 (set 0 or 1) + 寄存器地址<后7bit>,再发送欲操作数据(bit 0~8)。通过不同操作码来判别操作时读寄存器(缓存区)还是写寄存器(缓冲区)或是其它。
2.DM9051 模式设定说明:
(1)设定DM9051 PHY Mode,DM9051 支持以下 Speed 和 全/半双工设定
・DM9051_AUTO Auto negotiation
・DM9051_10MHD 10MHz, Half duplex
・DM9051_10MFD 10MHz, Full duplex
・DM9051_100MHD 100MHz, Half duplex
・DM9051_100MFD 100MHz, Full duplex
enum DM9051_PHY_mode { DM9051_10MHD = 0, DM9051_100MHD = 1, DM9051_10MFD = 4, DM9051_100MFD = 5, DM9051_10M = 6, DM9051_AUTO = 8, DM9051_1M_HPNA = 0x10 };
以上模式透过SPI写入Register,可参考DM9051 Driver。
(2)选择DM9051 Chip Type ,目前使用的 TYPE_DM9051
enum DM9051_TYPE { TYPE_DM9000E, TYPE_DM9000A, TYPE_DM9000B, TYPE_DM9051 };
(3)在上述建立Mode 和 Tyep 表后,放入struct DM9051_eth,以便后续设定使用。
struct DM9051_eth { /* 设定目前使用的网络芯片类型 */ enum DM9051_TYPE type; /* 设定PHY使用的 Speed and Duplex */ enum DM9051_PHY_mode mode; /* Interrupt Mask Resgister 设定值 */ uint8_t imr_all; /* packet I or II */ uint8_t packet_cnt; /* queued packet (packet II)*/ uint16_t queue_packet_len; /* interface MAC address info. */ uint8_t dev_addr[6]; }; static struct DM9051_eth DM9051_device;
3.DM9051驱动程序说明
(1)MCU SPI Configure
如同上述介绍的DM9051NP以太网口芯片采用的是SPI串行接口模式,首先第一步先确认MCU有支持SPI,再来配置SPI Master的相关设定,在这以STM32F103ZE SPI1 设定为参考,其他MCU可直接参考MCU的SPI example 设定。
Source code :
void DM9051_SPI_Configuration(void) { SPI_InitTypeDef SPI_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; DMA_InitTypeDef DMA_InitStructure; /* 使能 SPI1 时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, Enable); /* 找出MCU SPI Pin,在这以配置 SPI1 pins为例: SCK, MISO and MOSI */ GPIO_InitStructure.GPIO_Pin = DM9051_SPI_SCK_PIN | DM9051_SPI_MISO_PIN | DM9051_SPI_MOSI_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(DM9051_SPI_PIN_GROUP, &GPIO_InitStructure); /* Configure I/O for DM9051 Chip select */ GPIO_InitStructure.GPIO_Pin = DM9051_SPI_CS_PIN; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(DM9051_SPI_CS_GROUP, &GPIO_InitStructure); GPIO_SetBits(DM9051_SPI_PIN_GROUP, DM9051_SPI_SCK_PIN | DM9051_SPI_MISO_PIN | DM9051_SPI_MOSI_PIN); /* SPI1 为Master 相关设定参考 */ /* 设置SPI单向或者双向的数据模式:SPI设置为双线双向全双工 */ SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; /* 设置SPI工作模式:设置为主SPI */ SPI_InitStructure.SPI_Mode = SPI_Mode_Master; /* 设置SPI的数据大小:SPI发送接收8位帧结构 */ SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; /* 选择了串行时钟的稳态:时钟拉低 */ SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; /* 资料捕获于第一个时钟沿 */ SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; /* NSS 信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制 */ SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; /* 定义波特率预分频的值:波特率预分频值为2 */ SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; /* 指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始 */ SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; /* CRC值计算的多项式 */ SPI_InitStructure.SPI_CRCPolynomial = 7; /* 根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器 */ SPI_Init(DM9051_SPI, &SPI_InitStructure); /* 使能 DM9051_SPI */ SPI_Cmd(DM9051_SPI, ENABLE); }
以上 STM32F103ZE SPI1 相关配置,需注意的设定如下:
/* 资料捕获于第一个时钟沿 */ SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; /* NSS 信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制 */ SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI 的Master、Slave 必须配置成相同的时序模式,此部分参考MCU SPI mode 和DM9051 SPI mode 相对应的设定。
(2) MCU SPI读写一字节程序和读写DM9051 Register & Memory 说明
a. 此程序可参考STM32 SPI example 或网络上搜寻相关写法参考
static const __inline uint8_t SPI_DM9051_SendByte(uint8_t byte) { /* 检查指定的SPI标志位设置与否:发送缓存空 */ while (SPI_I2S_GetFlagStatus(DM9051_SPI, SPI_I2S_FLAG_TXE) == 0); /* 通过外设SPIx 发送一个数据 */ SPI_I2S_SendData(DM9051_SPI, byte); /* 判断接收是否完成 */ while (SPI_I2S_GetFlagStatus(DM9051_SPI, SPI_I2S_FLAG_RXNE) == 0); /* 返回通过SPIx最近接收的数据 */ return SPI_I2S_ReceiveData(DM9051_SPI); }
在撰写完SPI读写一个字节程序后,即可撰写MCU 读写DM9051 Register和Memory function。
b. MCU读取DM9051 Register
uint8_t DM9051_Read_Reg(uint8_t Reg_Off) { uint8_t spi_data; #ifdef DM9051_INT /* 关闭 MCU 所有Interrupt */ INT_ALL_DISABLE(); #endif //DM9051_INT /* 使能 SPI CSS,SPI 开始送收*/ SPI_DM9051_CS_LOW(); /* SPI transfer DM9051 Read-Command and Reg. offset. */ SPI_DM9051_SendByte(Reg_Off); /* Dummy for read register value. */ spi_data = SPI_DM9051_SendByte(0x0); /* 禁止 SPI CSS,完成操作*/ SPI_DM9051_CS_HIGH(); #ifdef DM9051_INT /* 打开 MCU 所有Interrupt */ INT_ALL_ENABLE(); #endif // DM9051_INT return spi_data; }
c. MCU写入DM9051 Register
void DM9051_Write_Reg(uint8_t Reg_Off, uint8_t spi_data) { uint8_t cmdaddr; cmdaddr = (Reg_Off | 0x80); #ifdef DM9051_INT /* 关闭 MCU 所有Interrupt */ INT_ALL_DISABLE(); #endif //DM9051_INT /* 使能 SPI CSS,SPI 开始送收*/ SPI_DM9051_CS_LOW(); /* SPI transfer DM9051 Read-Command and Reg. offset. */ SPI_DM9051_SendByte(cmdaddr); /* SPI1 写操作数据 */ SPI_DM9051_SendByte(spi_data); /* 禁止 SPI CSS,完成操作*/ SPI_DM9051_CS_HIGH(); #ifdef DM9051_INT /* 打开 MCU 所有Interrupt */ INT_ALL_ENABLE(); #endif //DM9051_INT return; }
d. MCU 读取DM9051 Memory
void DM9051_Read_Mem(uint8_t* pu8data, uint16_t datalen) { uint32_t i; uint8_t burstcmd = SPI_RD_BURST; // Read SPI_Data_Array back from the slave #ifdef DM9051_INT INT_ALL_DISABLE(); #endif //DM9051_INT /* 使能 SPI CSS,SPI 开始送收*/ SPI_DM9051_CS_LOW(); /* 发送读取指令 */ SPI_DM9051_SendByte(burstcmd); /* 读取接收缓存数据 */ for(i=0; i < datalen; i++) { pu8data[i] = SPI_DM9051_SendByte(0x0); } /* 禁止 SPI CSS,完成操作*/ SPI_DM9051_CS_HIGH(); #ifdef DM9051_INT INT_ALL_ENABLE(); #endif //DM9051_INT }
e. MCU 写入DM9051 Memory
void DM9051_Write_Mem(uint8_t* pu8data, uint16_t datalen) { uint32_t i; uint8_t burstcmd = SPI_WR_BURST; // Send the array to the slave #ifdef DM9051_INT INT_ALL_DISABLE(); #endif // DM9051_INT /* 使能 SPI CSS,SPI 开始送收*/ SPI_DM9051_CS_LOW(); /* 发送写入指令 */ SPI_DM9051_SendByte(burstcmd); /* 发送写入缓存资料 */ for(i=0; i< datalen; i++) { SPI_DM9051_SendByte(pu8data[i]); } /* 禁止 SPI CSS,完成操作*/ SPI_DM9051_CS_HIGH(); #ifdef DM9051_INT INT_ALL_ENABLE(); #endif //DM9051_INT }
1.前言
1-1网卡芯片介绍
DM9051NP SPI接口网卡芯片是为了方便MCU单片机系统进行以太网通信而开发出的解决方案。DM9051NP芯片是带有行业标准串列外设接口(Serial Peripheral Interface,SPI)的独立以太网控制器。DM9051NP符合IEEE 802.3 规范,它还支持以DMA 模式來传输,以实现资料传送快速。DM9051NP通过1个中断引脚和SPI接口來进行与主控制器/MCU单片机的通信,资料传输规格为10/100 M。
-
• Package:32支接脚封装,QFN.
-
• IEEE 802.3az Energy Efficient Ethernet (EEE)
-
• Built-in integrated 3.3V to 1.8V regulator
-
• 远程唤醒 (WOL)
-
• 并行线/交叉线自动切换 (HP Auto-MDIX)
-
• Support 光口界面
-
• 具有16KB SRAM静态随机存取记忆
-
• EMI (Class B) and HBM ESD Rating 8KV
-
• 工业温度规范: –40℃ to +85℃
-
• 功率:(100/10 M) => 429/561 mW
-
• 连续工作温度 <60℃
1-2 工程代码&demo原理图
2.搭建实验环境
先讲一下如何搭建实验环境。我们STM32F103ZET6+DM9051作为一个网络终端通过网线跟PC直连(DM9051支持自动翻转功能(Auto-MDIX),不用区分双绞线并行线)。例如调试PC机的IP地址如下图所示。
(圖a)
路由器的IP地址为192.168.1.1。那么开发板的IP地址可以设定为 192.168.1.2到192.168.1.255。在这里,我们把开发板IP设为192.168.1.51。
3.硬件和软件说明
3-1 硬件环境
【百为开发板】
百为开发板上外接DM9051NP模块,DM9051NP通过SPI接口控制内部寄存器,并有中断输出接口。STM32通过SPI1和DM9051NP相连。具体接口如下:
DM9051 | STM32 | |
SPI_CS(Chip_Select) | Pin01 | Pin26(PC0) |
SPI_MOSI | Pin03 | Pin43(PA7) |
SPI_MISO | Pin05 | Pin42(PA6) |
CPI_CLK | Pin07 | Pin41(PA5) |
Interrpt | Pin02 | Pin97(PC7) |
由于SPI上同时挂载其他SPI从设备,所有初始化的过程中需要通过操作CS埠禁止其他SPI从设备。CS片选将DM9051NP相连拉低,其他从设备拉高即可。
(圖b)
(圖c)
图b,c:物理连接图
3-2 SPI配置
3-2-1. 接线部分
(圖d)
SPI信号线说明,通常SPI通过4个引脚与外部器件相连:
-
1. MISO : 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
-
2. MOSI : 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
-
3. SCK : 串口时钟,作为主设备的输出,从设备的输入
-
4. NSS : 从设备选择。这是一个可选的引脚,用来选择主/从设备。它的功能是用来作为“片选引脚”,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。
3-2-2. 软件配置部分
-
1. 首先开启SPI 1的时钟
-
2. 配置SPI 1 GPIO PIN,使用的pin還有SPI接線參考3-1。
-
3. 配置DMA,簡單介紹一下:
(1).DMA是AMBA的先进高性能总线(AHB)上的设备,它有2个AHB端口:一个是从端口,用于配置DMA,另一个是主端口,使得DMA可以在不同的从设备之间传输数据。
(2).DMA的作用是在没有Cortex-M3核心的干预下,在后台完成数据传输。在传输数据的过程中,主处理器可以执行其它任务,只有在整个数据块传输结束后,需要处理这些数据时才会中断主处理器的操作。它可以在对系统性能产生较小影响的情况下,实现大量数据的传输。
-
4. SPI_DMA的通信过程
(1).设置外设地址
(2).设置存储器地址
(3).设置传输数据量
(4).设置通道的配置信息
(5).使能DMA通道,启动传输
(6).发送时,在每次TXE被设置为’1’时发出DMA请求,DMA控制器则写数据至SPI_DR寄存器,TXE标志因此而被清除。
(7).接收时,在每次RXNE被设置为’1’时发出DMA请求,DMA控制器则从SPI_DR寄存器读出数据,RXNE标志因此而被清除。
DM9051 SPI1 DMA 配置如下:
void DM9051_Configuration(void) { SPI_InitTypeDef SPI_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; DMA_InitTypeDef DMA_InitStructure; /* Enable SPI1, DMA and GPIO clocks */ #ifdef SPI_DMA //Enable DMA RCC_AHBPeriphClockCmd(SPI_MASTER_DMA_CLK, ENABLE); // if enable SPI_DMA Configure DMA clock #endif //SPI_DMA RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC | RCC_APB2Periph_GPIOF, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); // enable SPI1 clock #ifdef DMA_INT /* NVIC configuration */ NVIC_Configuration(); #endif //DMA_INT /* Configure SPI1 pins: SCK, MISO and MOSI */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); /* Configure I/O for DM9051 Chip select */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_DM9051_CS; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIO_DM9051_CS, &GPIO_InitStructure); #if 1 /* Configure pins: SPI FLASH CS --------------------------------------------*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_SetBits(GPIOB, GPIO_Pin_2); #endif #if 1 /* Configure pins: LCD TOUCH CS --------------------------------------------*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOF, &GPIO_InitStructure); GPIO_SetBits(GPIOF, GPIO_Pin_10); #endif //0 /* Deselect the FLASH: Chip Select high */ //SPI_DM9051_CS_HIGH(); /* SPI1 configuration */ SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_InitStructure); #ifdef SPI_DMA /* SPI_MASTER_Rx_DMA_Channel configuration ---------------------------------*/ DMA_DeInit(SPI_MASTER_Rx_DMA_Channel); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SPI_MASTER_DR_Base; //DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SPI_MASTER_Buffer_Rx; #ifdef uIP_NOOS DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)uip_buf; #endif DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //DMA_InitStructure.DMA_BufferSize = BufferSize; //RX length DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(SPI_MASTER_Rx_DMA_Channel, &DMA_InitStructure); /* SPI_MASTER_Tx_DMA_Channel configuration ---------------------------------*/ DMA_DeInit(SPI_MASTER_Tx_DMA_Channel); DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)SPI_MASTER_DR_Base; //DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SPI_MASTER_Buffer_Tx; #ifdef uIP_NOOS DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)uip_buf; #endif DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; DMA_Init(SPI_MASTER_Tx_DMA_Channel, &DMA_InitStructure); #ifdef DMA_INT /* Enable DMA1 Channel2,3 Transfer Complete interrupt */ DMA_ITConfig(SPI_MASTER_Rx_DMA_Channel, DMA_IT_TC, ENABLE); DMA_ITConfig(SPI_MASTER_Tx_DMA_Channel, DMA_IT_TC, ENABLE); #endif //DMA_INT //DMA_Cmd(SPI_MASTER_Rx_DMA_Channel, ENABLE); //DMA_Cmd(SPI_MASTER_Tx_DMA_Channel, ENABLE); #endif //SPI_DMA /* Enable SPI1 */ SPI_Cmd(SPI1, ENABLE); #ifdef DM9051_INT /* configure PC7 as external interrupt */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; GPIO_Init(GPIOC, &GPIO_InitStructure); /* Connect DM9051 EXTI Line to GPIOC Pin 7 */ GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource7); /* Configure DM9051 EXTI Line to generate an interrupt on falling edge */ EXTI_InitStructure.EXTI_Line = EXTI_Line7; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); /* Clear DM9051 EXTI line pending bit */ EXTI_ClearITPendingBit(EXTI_Line7); /* Enable the EXTI7 Interrupt */ NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); #endif //DM9051_INT }
4.网卡驱动
4-1 网卡介绍
网卡采用DAVICOM DM9051NP,DM9051相關特性與介紹可参考 DM9051NP SPI網卡介紹,在上一章節介紹如何配置STM32 SPI & DMA,在配置完成後,需配置DM9051透過SPI介面送收data,相關代碼如下:
static __inline u8 SPI_DM9051_SendByte(u8 byte) { /* Loop while DR register in not emplty */ while (SPI_I2S_GetFlagStatus(DM9051_SPI, SPI_I2S_FLAG_TXE) == 0); /* Send byte through the SPI1 peripheral */ SPI_I2S_SendData(DM9051_SPI, byte); /* Wait to receive a byte */ SPI_I2S_GetFlagStatus(DM9051_SPI, SPI_I2S_FLAG_RXNE); /* Return the byte read from the SPI bus */ return SPI_I2S_ReceiveData(DM9051_SPI); }
4-2 Read/Write Register
在上一章配置好SPI read/write data function後,在此章節開始介紹如何透過SPI 來讀寫DM9051 Register & Memory,介紹如下:
A. 首先介紹Read Register,流程如下:
1. Reset CS(Chip Select) , GPIO pin pull low (Falling edge)
2. SPI send MOSI Reg Offset command.
3. SPI send 0x0 ceate a clock for Slave send data .
4. Set CS(Chip Select) GPIO pin pull High.
5. return rx data
相關代碼參考如下:
static u8 DM9051_Read_Reg(u8 Reg_Off) { u8 spi_data; SPI_DM9051_CS_LOW(); //讀取之前先將 Chip Select pull low /* SPI transfer DM9051 Read-Command and Reg. offset. */ SPI_DM9051_SendByte(Reg_Off); //Read command + Register offset address spi_data = SPI_DM9051_SendByte(0x0); //Dummy for read register value. SPI_DM9051_CS_HIGH();//讀取完後再把chip Select pull High return spi_data;//返回Data }
B. 再來介紹Write Register 流程部分,如下:
1.Reset CS(Chip Select) ,GPIOC PIN pull low.
2. SPI send MOSI Reg_Offset | 0x80(disable DM9051 interrupt) value.
3. SPI send MOSI spi_data value .
4. Set CS(Chip Select) GPIOC PIN to high.
相關代碼參考如下:
static void DM9051_Write_Reg(u8 Reg_Off, u8 spi_data) { u8 cmdaddr; cmdaddr = (Reg_Off | 0x80); SPI_DM9051_CS_LOW(); /* SPI transfer DM9051 Read-Command and Reg. offset. */ SPI_DM9051_SendByte(cmdaddr); //Read command + Register offset address SPI_DM9051_SendByte(spi_data); // SPI_DM9051_CS_HIGH(); return; }
4-3 Read/Write Memory
在上一章節介紹如何讀寫DM9051暫存器後,相信如何使用SPI讀寫有一定的了解了,在這章節將講解如何讀寫DM9051 Memory,先簡單說明一下為何需要讀寫Memory,DM9051具有16K-byte SRAM這個特性,可以將要送收data暫存在SRAM,在透過SPI接口對DM9051下command將存放在Memory裡的data 搬出來,讀寫Memory相關流程與代碼如下:
A. 首先Read Memory流程:
-
1. Reset GPIOPIN low。
-
2. Send SPI interrup SPI write buffer command(Read Memory Register = 0x72)。
-
3. 此部分可以設定單一使用SPI 或SPI + DMA mode兩種設定如下:
(1) 只使用SPI mode ,透過SPI interface RX Memory data讀取出來
放到buffer。
(2) 使用SPI DMA mode ,將data 丟到DMA CMAR 和 設定data length to CNDTR。
-
1. 啟用DMA Channel。
-
2. 啟用 SPI TX Request command 開始送收data。
-
3. Dummy TX 主要是產生一個SPI clock讓DM9051送Data給Master, 因為DM9051 Chip 沒有DMA功能,透過送一個SPI Clock來觸發SPI 接收RX Packet。
-
4. 判斷等待data 完全送完,等待data完全送完後清除DMA Channel 等待標記。
-
5. Set GPIO PIN to high。
-
6. 關閉DMA Channel。
-
相關代碼參考如下:
static void DM9051_Read_Mem(u8* pu8data, u16 datalen) { #ifndef SPI_DMA u32 i; #endif //SPI_DMA uint32_t mccr; #ifdef DMA_INT uint8_t err; #endif //DMA_INT // Read SPI_Data_Array back from the slave u8 burstcmd = SPI_RD_BURST; SPI_DM9051_CS_LOW(); SPI_DM9051_SendByte(burstcmd); #ifndef SPI_DMA //if no enable SPI DMA for(i=0; iCMAR = (uint32_t)pu8data; ((DMA_Channel_TypeDef *)SPI_MASTER_Rx_DMA_Channel)->CNDTR = (uint32_t)datalen; DMA_Cmd(SPI_MASTER_Rx_DMA_Channel, ENABLE); SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, ENABLE); //RX DMA request, start move data /* Dummy TX to generate SPI clock for RX*/ mccr = ((DMA_Channel_TypeDef *)SPI_MASTER_Tx_DMA_Channel)->CCR; //Save DMA channel 3 configuration register ((DMA_Channel_TypeDef *)SPI_MASTER_Tx_DMA_Channel)->CCR = (mccr & 0xffffff31); // Peripheral & memory doesn't auto increment. Interrupt disable ((DMA_Channel_TypeDef *)SPI_MASTER_Tx_DMA_Channel)->CMAR = (uint32_t)pu8data; ((DMA_Channel_TypeDef *)SPI_MASTER_Tx_DMA_Channel)->CNDTR = (uint32_t)datalen; DMA_ITConfig(DMA1_Channel3, DMA_IT_TC, DISABLE); //Diable Interrupt DMA_Cmd(SPI_MASTER_Tx_DMA_Channel, ENABLE); SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE); //TX DMA request, start move data #ifdef DMA_INT OSSemPend(DM9051_rxdma_flag, 0,&err); #else while(!DMA_GetFlagStatus(SPI_MASTER_Rx_DMA_FLAG)); while(!DMA_GetFlagStatus(SPI_MASTER_Tx_DMA_FLAG)); #endif //DMA_INT DMA_ClearFlag(SPI_MASTER_Rx_DMA_FLAG); DMA_ClearFlag(SPI_MASTER_Tx_DMA_FLAG); DMA_Cmd(SPI_MASTER_Rx_DMA_Channel, DISABLE); /* Restore Tx setting */ DMA_Cmd(SPI_MASTER_Tx_DMA_Channel, DISABLE); ((DMA_Channel_TypeDef *)SPI_MASTER_Tx_DMA_Channel)->CCR = mccr; DMA_ITConfig(DMA1_Channel3, DMA_IT_TC, ENABLE); #endif //SPI_DMA SPI_DM9051_CS_HIGH(); }
B. Write Memory流程:
-
1. Reset GPIO PIN to low。
-
2. Send SPI interface write memory buffer 。
-
3. 判斷使用SPI mode或SPI DMA mode。
-
4. 此部分可以設定單一使用SPI 或SPI + DMA mode兩種設定如下:
(1) 假設只使用SPI mode ,透過SPI interface send TX Packet to FIFO。
(2) 使用SPI DMA mode ,將data 丟到DMA CMAR和 設定data length to CNDTR。
a. 啟用DMA Channel。
b. 啟用 SPI TX Request Channel send packet。
c. 判斷等待data 完全送完,等待data完全送完後清除DMA Channel 的等待標記。
d. Set GPIO PIN to high。
e. 關閉DMA Channel。
相關代碼參考如下:
static void DM9051_Write_Mem(u8* pu8data, u16 datalen) { #ifndef SPI_DMA uint32_t i; #endif //SPI_DMA #ifdef DMA_INT uint8_t err; #endif //DMA_INT /* Send the array to the slave */ u8 burstcmd = SPI_WR_BURST; SPI_DM9051_CS_LOW(); SPI_DM9051_SendByte(burstcmd); #ifndef SPI_DMA for(i=0; i< datalen; i++) { SPI_DM9051_SendByte(pu8data[i]); } SPI_DM9051_CS_HIGH(); #else TxComplete = 0; ((DMA_Channel_TypeDef *)SPI_MASTER_Tx_DMA_Channel)->CMAR = (uint32_t)pu8data; ((DMA_Channel_TypeDef *)SPI_MASTER_Tx_DMA_Channel)->CNDTR = (uint32_t)datalen; DMA_Cmd(SPI_MASTER_Tx_DMA_Channel, ENABLE); SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, ENABLE); //TX DMA request, start move data #ifdef DMA_INT OSSemPend(DM9051_txdma_flag, 0,&err); #else while(!DMA_GetFlagStatus(SPI_MASTER_Tx_DMA_FLAG)); #endif //DMA_INT DMA_ClearFlag(SPI_MASTER_Tx_DMA_FLAG); DMA_Cmd(SPI_MASTER_Tx_DMA_Channel, DISABLE); #endif //SPI_DMA SPI_DM9051_CS_HIGH(); }
以上都完成後,接下來就是DM9051_Init部份了,此部副可直接參考project 裡的Driver 裡面有相關配置。
如何使用DM9051,虽然DM9051使用复杂但是深入理解两点即可:
第一点如何通过SPI发送命令和数据。
第二点理解DM9051的缓冲区,在发送以太网和接受以太网数据报的过程中,DM9051会帮助用户做些额外的工作,例如发送时自动填充SFD,在读取接收缓冲区数据时会包含若干状态信息,包括数据报长度和CRC校验结果等。
4-4 DM9051 Received Packet
在上一章節介紹完read / write memory後,在這我們開始講解DM9051是如何 送收封包packet ,首先先說明 RX packet 的流程,RX的部分最主要的是會將收進來的data 返回在uip_len。
主要流程:
-
1. 首先判斷 DM9051 MRCMDX register 是不是為0或1,假如不為0或1則重置設備。
-
2. 計算FIFO pointer,判斷是否超過 0x3fff ,則需回歸繞到 0x0c00 起始位置
-
3. 將收到的packet返回rx_status和rx_len,再將rx_len丟到uip_buf,完成後繼續判斷rx_status 有無錯誤,沒有的話的就返回rx_len。
相關代碼參考如下:
uint16_t DM9051_rx(void) { uint8_t rxbyte; uint16_t calc_MRR; uint16_t rx_len = 0; /* Check packet ready or not */ rxbyte = DM9051_Read_Reg(DM9051_MRCMDX); rxbyte = DM9051_Read_Reg(DM9051_MRCMDX); /* if rxbyte != 1 or 0 reset device */ if ((rxbyte != 1) && (rxbyte != 0)) { printf("rxbyte = %02X.\r\n", rxbyte); DM9051_TRACE2("DM9051 rx: rx error, stop device \r\n"); /* Reset RX FIFO pointer */ DM9051_Write_Reg(DM9051_RCR, RCR_DEFAULT); //RX disable DM9051_Write_Reg(DM9051_MPCR, 0x01); //Reset RX FIFO pointer _DM9051_Delay(2); DM9051_Write_Reg(DM9051_RCR, (RCR_DEFAULT | RCR_RXEN)); //RX Enable /* restore receive interrupt */ DM9051_device.imr_all |= IMR_PRM; //DM9051_device.imr_all = IMR_DEFAULT; DM9051_Write_Reg(DM9051_IMR, DM9051_device.imr_all); return 0; } if (rxbyte) { uint16_t rx_status; //uint16_t* data = 0; uint8_t ReceiveData[4]; #ifdef FifoPointCheck calc_MRR = (DM9051_Read_Reg(DM9051_MRRH) << 8) + DM9051_Read_Reg(DM9051_MRRL); //Save RX SRAM pointer #endif //FifoPointCheck DM9051_Read_Reg(DM9051_MRCMDX); //dummy read DM9051_Read_Mem(ReceiveData, 4); rx_status = ReceiveData[0] + (ReceiveData[1] << 8); rx_len = ReceiveData[2] + (ReceiveData[3] << 8); DM9051_Read_Mem((u8_t *)uip_buf, rx_len); #ifdef FifoPointCheck /* if over 0x3fff, return 0x0c00 */ calc_MRR += (rx_len + 4); if(calc_MRR > 0x3fff) calc_MRR -= 0x3400; if(calc_MRR != ((DM9051_Read_Reg(DM9051_MRRH) << 8) + DM9051_Read_Reg(DM9051_MRRL))) { printf("DM9K MRR Error!!\r\n"); printf("Predicut RX Read pointer = 0x%X, Current pointer = 0x%X\r\n", calc_MRR, ((DM9051_Read_Reg(DM9051_MRRH) << 8) + DM9051_Read_Reg(DM9051_MRRL))); /* if pointer error, move pointer to next packet header */ DM9051_Write_Reg(DM9051_MRRH, (calc_MRR >> 8) & 0xff); DM9051_Write_Reg(DM9051_MRRL, calc_MRR & 0xff); } #endif if ((rx_status & 0xbf00) || (rx_len < 0x40) || (rx_len > DM9051_PKT_MAX) ) { DM9051_TRACE2("rx error: status %04x, rx_len: %d \r\n", rx_status, rx_len); if (rx_status & 0x100) { DM9051_TRACE2("rx fifo error \r\n"); } if (rx_status & 0x200) { DM9051_TRACE2("rx crc error \r\n"); } if (rx_status & 0x8000) { DM9051_TRACE2("rx length error \r\n"); } if (rx_len > DM9051_PKT_MAX) { DM9051_TRACE2("rx length too big \r\n"); /* RESET device */ //_DM9051_WriteReg(DM9051_NCR, NCR_RST); //_DM9051_Delay_ms(5); } } } return rx_len; }
4-5 DM9051 Transmitted Packet
講解完RX後,接著說明 DM9051 TX流程:
-
1. 等待DM9051 TX Control Register Ready
-
2. 計算FIFO Pointer,若是超過 0x0bff ,則需回歸繞到 0x0000 起始位置
-
3. 寫入data 至 FIFO Poniter
-
4. 送出data ,在完成後清除 TX request
-
5. 判斷FIFO Pointer有無亂掉,有的話往前移8 bit
相關代碼參考如下:
u32 DM9051_tx(void) { uint16_t calc_MWR; uint16_t SendLength; while(DM9051_Read_Reg(DM9051_TCR) & DM9051_TCR_SET) { _DM9051_Delay(5); } #ifdef FifoPointCheck /* 計算下一個傳送的指針位 , 若接收長度為奇數,需加一對齊偶字節 */ /* 若是超過 0x0bff ,則需回歸繞到 0x0000 起始位置 */ calc_MWR = (DM9051_Read_Reg(DM9051_MWRH) << 8) + DM9051_Read_Reg(DM9051_MWRL); calc_MWR += SendLength; //printf("calc_MWR = 0x%X\r\n", calc_MWR); //if(SendLength & 0x01) calc_MWR++; if(calc_MWR > 0x0bff) calc_MWR -= 0x0c00; #endif /* Move data to DM9051 TX RAM */ //_DM9051_WriteRegAddr(DM9051_MWCMD); //Write data to FIFO DM9051_Write_Reg(DM9051_TXPLL, uip_len & 0xff); DM9051_Write_Reg(DM9051_TXPLH, (uip_len >> 8) & 0xff); DM9051_Write_Mem((u8_t*)uip_buf, SendLength); #if 0 printf("Tx Read Pointer H = 0x%02X.\r\n", DM9051_Read_Reg(DM9051_TRPAH)); printf("Tx Read Pointer L = 0x%02X.\r\n", DM9051_Read_Reg(DM9051_TRPAL)); printf("DM9051_MWRH H = 0x%02X.\r\n", DM9051_Read_Reg(DM9051_MWRH)); printf("DM9051_MWRL L = 0x%02X.\r\n", DM9051_Read_Reg(DM9051_MWRL)); #endif //0 //Write data to FIFO DM9051_Write_Reg(DM9051_TXPLL, q->tot_len & 0xff); DM9051_Write_Reg(DM9051_TXPLH, (q->tot_len >> 8) & 0xff); DM9051_Write_Mem((u8_t*)q->payload, q->tot_len); /* Issue TX polling command */ DM9051_Write_Reg(DM9051_TCR, TCR_TXREQ); /* Cleared after TX complete */ #ifdef FifoPointCheck if(calc_MWR != ((DM9051_Read_Reg(DM9051_MWRH) << 8) + DM9051_Read_Reg(DM9051_MWRL))) { printf("DM9K MWR Error!! calc_MWR = 0x%X, SendLength = 0x%x\r\n", calc_MWR, SendLength); printf("MWRH = 0x%X, MWRL = 0x%X\r\n", DM9051_Read_Reg(DM9051_MWRH), DM9051_Read_Reg(DM9051_MWRL)); /*Ι͏̼ѷƘ࠹Ҏ̼ѷҾɬŕŀΓׇѥƝʺƝYǬ٭ */ DM9051_Write_Reg(DM9051_MWRH, (calc_MWR >> 8) & 0xff); DM9051_Write_Reg(DM9051_MWRL, calc_MWR & 0xff); } #endif //FifoPointCheck return 0; }
5.uIP基本结构与配置
5-1 首先介绍uIP协议部分
uIP协议栈提供了我们很多接口函数,这些函数在 uip.h 中定义,为了减少函数调用造成的额外支出,大部分接口函数以宏命令实现的,uIP提供的接口函数有:
-
1. 初始化uIP协定栈:uip_init()
-
2. 处理输入包:uip_input()
-
3. 处理周期计时事件:uip_periodic()
-
4. 开始监听埠:uip_listen()
-
5. 连接到远程主机:uip_connect()
-
6. 接收到连接请求:uip_connected()
-
7. 主动关闭连接:uip_close()
-
8. 连接被关闭:uip_closed()
-
9.发出去的数据被应答:uip_acked()
-
10. 在当前连接发送数据:uip_send()
-
11. 在当前连接上收到新的数据:uip_newdata()
-
12. 告诉对方要停止连接:uip_stop()
-
13. 连接被意外终止:uip_aborted()
在使用uIP的时候,一般通过如下顺序:
(1).实现接口函数(回调函数)UIP_APPCALL。 该函数是我们使用uIP最关键的部分,它是uIP和应用程序的接口,我们必须根据自己的需要,在该函数做各种处理,而做这些处理的触发条件,就是前面提到的uIP提供的那些接口函数,如uip_newdata、uip_acked、uip_closed等等。另外,如果是UDP,那么还需要实现UIP_UDP_APPCALL回调函数。
(2).调用tapdev_init函数,先初始化网卡。 此步先初始化网卡,配置MAC地址、ethernet mode (10/100 rate ),为uIP和网络通信做好准备。
(3).调用uip_init函数,初始化uIP协定栈。 此步主要用于uip自身的初始化,我们直接调用就是。
(4).等待DHCP 取得IP或设置固定IP地址、网关以及屏蔽等。 在初始化网卡完成后,DHCP会send discover packet 来取得IP,如果DHCP time out则会设定固定IP。这个和计算机上网差不多,只不过我们这里是通过uip_ipaddr、uip_sethostaddr、uip_setdraddr和uip_setnetmask等函数实现。
(5).设置Applocation监听埠 uIP根据你设定的不同监听埠,实现不同的服务,比如我们实现Web Server就监听80埠(浏览器默认的埠是80埠),凡是发现80端口的数据,都通过Web Server的APPCALL函数处理。根据自己的需要设置不同的监听埠。不过uIP有本地埠(lport)和远程埠(rport)之分,如果是做服务端,我们通过监听本地埠(lport)实现;如果是做客户端,则需要去连接远程埠(rport)。
(6).处理uIP事件 最后,uIP通过uip_polling函数轮询处理uIP事件。该函数必须插入到用户的主循环里面(也就是必须每隔一定时间调用一次)。
5-2 uIP基本结构
uIP的代码编写需要遵守一定的结构,而且这种结构最好保持稳定(保持不变)。这个结构主要做以下几个部分任务。
-
1. 初始化系統、網卡驅動、uIP protocol等
-
2. 取的DHCP IP,或固定IP
-
3. 获得以太网数据报
-
4. 处理ARP报文
-
5. 处理IP报文
-
6. 定期处理TCP和UDP连接
-
7. 定期更新ARP缓冲区
相關代碼參考如下:
int main(void) { #ifndef __DHCPC_H__ uip_ipaddr_t ipaddr; #endif //__DHCPC_H__ int i; struct timer periodic_timer, arp_timer; USART_Configuration(); /* Setting polling and arp timer */ timer_set(&periodic_timer, CLOCK_SECOND / 2); //500ms timer_set(&arp_timer, CLOCK_SECOND * 10); // 10sec /* Init ethernet driver */ tapdev_init(); /* Init uIP portocol */ uip_init(); uip_arp_init(); // Clear arp table. #ifdef __DHCPC_H__ /* setup the dhcp renew timer the make the first request */ timer_set(&dhcp_timer, CLOCK_SECOND * 600); /* Init Dhcpc */ dhcpc_init(&uip_ethaddr, 6); //dhcpc_request(); #else uip_ipaddr(ipaddr, 192,168,7,51); //Setting Host IP address uip_sethostaddr(ipaddr); uip_ipaddr(ipaddr, 192,168,7,1); //Setting Default Gateway uip_setdraddr(ipaddr); uip_ipaddr(ipaddr, 255,255,255,0); //Setting Network Mask uip_setnetmask(ipaddr); /* Display system information */ printf("\r\n---------------------------------------------\r\n"); printf("Network chip: DAVICOM DM9051 \r\n"); printf("MAC Address: %02X:%02X:%02X:%02X:%02X:%02X \r\n", uip_ethaddr.addr[0], uip_ethaddr.addr[1], uip_ethaddr.addr[2], uip_ethaddr.addr[3], uip_ethaddr.addr[4], uip_ethaddr.addr[5]); uip_gethostaddr(ipaddr); //get host IP printf("Host IP Address: %d.%d.%d.%d \r\n", uip_ipaddr1(ipaddr), uip_ipaddr2(ipaddr), uip_ipaddr3(ipaddr), uip_ipaddr4(ipaddr)); uip_getnetmask(ipaddr); //get Get netmask printf("Network Mask: %d.%d.%d.%d \r\n", uip_ipaddr1(ipaddr), uip_ipaddr2(ipaddr), uip_ipaddr3(ipaddr), uip_ipaddr4(ipaddr)); uip_getdraddr(ipaddr); // get getway printf("Gateway IP Address: %d.%d.%d.%d \r\n", uip_ipaddr1(ipaddr), uip_ipaddr2(ipaddr), uip_ipaddr3(ipaddr), uip_ipaddr4(ipaddr)); printf("---------------------------------------------\r\n"); #endif //__DHCPC_H__ httpd_init(); //Init TCP task /*Initial and start system tick time = 1ms */ SysTick_Config(SystemCoreClock / 1000); while(1) { uip_len = tapdev_read(); //reveive packet, and return uip_len if(uip_len > 0) { if(BUF->type == htons(UIP_ETHTYPE_IP)) { //uip_arp_ipin(); //Removed by Spenser uip_input(); // uip_process(UIP_DATA) /* If the above function invocation resulted in data that should be sent out on the network, the global variable uip_len is set to a value > 0. */ if(uip_len > 0) { uip_arp_out(); tapdev_send(); } } else if(BUF->type == htons(UIP_ETHTYPE_ARP)) { uip_arp_arpin(); /* If the above function invocation resulted in data that should be sent out on the network, the global variable uip_len is set to a value > 0. */ if(uip_len > 0) { tapdev_send(); } } } else if(timer_expired(&periodic_timer)) { timer_reset(&periodic_timer); for(i = 0; i < UIP_CONNS; i++) { uip_periodic(i); /* If the above function invocation resulted in data that should be sent out on the network, the global variable uip_len is set to a value > 0. */ if(uip_len > 0) { uip_arp_out(); tapdev_send(); } } #if UIP_UDP for(i = 0; i < UIP_UDP_CONNS; i++) { uip_udp_periodic(i); /* If the above function invocation resulted in data that should be sent out on the network, the global variable uip_len is set to a value > 0. */ if(uip_len > 0) { uip_arp_out(); tapdev_send(); } } #endif /* UIP_UDP */ /* Call the ARP timer function every 10 seconds. */ if(timer_expired(&arp_timer)) { timer_reset(&arp_timer); uip_arp_timer(); } } #ifdef __DHCPC_H__ else if (timer_expired(&dhcp_timer)) { // for now turn off the led when we start the dhcp process dhcpc_renew(); timer_reset(&dhcp_timer); } #endif //__DHCPC_H__ } }
簡單說明一下uip buffer和 調適驅動 :
1. #define BUF ((struct uip_eth_hdr *)&uip_buf[0]) 指向uIP缓冲区,强制类型转化为uip_eth_hdr结构体,uip_eth_hdr即为以太网首部结构,6字节目标MAC地址 6字节源MAC地址 2字节类型。
2. tapdev_init();tapdev_read();tapdev_send(); 三个函数为以太网操作函数,只有tapdev_read有返回值,其他函数即无输入参数也无返回参数。这三个函数便是DM9051操作的三个封装,DM9051发送或接收直接操作uIP的两个全局变量uip_buf和uip_len。tapdev.c具体代码如下:
/* Init DM9051 */ void tapdev_init(void) { DM9051_init(); } /* received data return uip_len */ unsigned int tapdev_read(void) { return DM9051_rx(); } /* send data */ void tapdev_send(void) { DM9051_tx(); }
5-3 uIP配置部分
-
1. IP地址配置
-
2. MAC地址配置
IP地址设置包括,本地IP地址,网关地址和子网掩码。可参考6-2。
MAC的地址较为特殊,由于DM9051本身没有唯一的EUI-48(俗称MAC地址)地址,所以EUI-48地址需要手动配置。该地址不但应用于DM9051也应用于uIP。相关设定MAC Address代码如下介绍:
#define emacETHADDR0 0x00 #define emacETHADDR1 0x60 #define emacETHADDR2 0x6e #define emacETHADDR3 0x90 #define emacETHADDR4 0x51 #define emacETHADDR5 0x02 #if UIP_FIXEDETHADDR // if open UIP_FIXEDETHADDR const struct uip_eth_addr uip_ethaddr = {{emacETHADDR0, emacETHADDR1, emacETHADDR2, emacETHADDR3, emacETHADDR4, emacETHADDR5}}; #else struct uip_eth_addr uip_ethaddr = {{0,0x6A,0x60,0x90,0x51,0}}; #endif
另外补充:DM9051 默认先从EEPROM读取MAC地址,如无挂载EEPROM,则通过软件设定。IP地址也是先透过DHCP获取,如获取不到则采取代码设定固定值。
5-4 uip-conf.h部分
uip-conf部分说明三点
-
1. 如果不熟悉请保留默认参数,例如UIP_CONF_MAX_CONNECTIONS等
-
2. 如果设置UIP_CONF_LOGGING为1,请添加void uip_log(char *m){}
-
3. 必须包含用户任务头文件,且放在该头文件的最后。例如添加#include "example1.h"。这样做的主要目的是定义uip_tcp_appstate_t和UIP_APPCALL两个关键参数。具体代码如下:
#ifndef __UIP_CONF_H #define __UIP_CONF_H #includetypedef uint8_t u8_t; typedef uint16_t u16_t; typedef unsigned short uip_stats_t; /* 最大TCP连接数 */ #define UIP_CONF_MAX_CONNECTIONS 10 /* 最大埠监听数 */ #define UIP_CONF_MAX_LISTENPORTS 10 /* uIP 缓存大小*/ #define UIP_CONF_BUFFER_SIZE 1500 /* CPU字节顺序 */ #define UIP_CONF_BYTE_ORDER UIP_LITTLE_ENDIAN /* 日志开关 */ #define UIP_CONF_LOGGING 1 /* UDP支援开关*/ #define UIP_CONF_UDP 0 /* UDP校验和开关 */ #define UIP_CONF_UDP_CHECKSUMS 1 /* uIP统计开关 */ #define UIP_CONF_STATISTICS 1 // 加入用户任务头文件,请修改 #include "example1.h" #endif
5-5 一个简单有效的Clock Tick
uIP协议栈处理过程需要一个定时配合,该定时器实际为一个软件定时器,定时器说明uIP处理若干周期性任务,例如处理TCP连接重传,定时更新ARP缓冲表等。设计定时器的方法很多,在这里推荐uIP原作者的timer模块。timer模块的原理类似于MCU硬件中的比较匹配原理,timer模块中有一个全部变量counter,每次MCU发生某个定时器中断时累加1,如果某个任务需要使用定时器服务,在该任务中声明一个timer(在该任务中为全局变量),并记录此时的counter值。判断溢出可查询当前的counter和被记录的counter的差值,如果差值超过间隔值那么软件定时器timer溢出(类似于发生比较匹配中断)。软件定时器的主要作用有两个。第一,更新TCP或UDP连接,第二,更新ARP缓冲区(ARP表)。虽然uIP在功能上比LwIP简单的多,但是LwIP也有类似的部分(或者说完全一样)。详细代码修改clock-arch.c如下:
#include "clock-arch.h" #include "stm32f10x.h" extern __IO int32_t g_RunTime; /*---------------------------------------------------------------------------*/ clock_time_t clock_time(void) { return g_RunTime; } /*---------------------------------------------------------------------------*/
修改clock-arch.h如下
typedef int clock_time_t; #define CLOCK_CONF_SECOND 100
作用是:clock_time 用于配置系统产生滴答的间隔,當前的开发板系统时钟跑的是72MHz,即每隔720,000,000/100产生一次系统滴答,一个系统滴答是10ms, CLOCK_SECOND (这里定义为100) 个clock tick即为1S。
使用stm32 System Tick中断代码,stm32f10x_it.c修改如下:
__IO int32_t g_RunTime = 0; void SysTick_Handler(void) { static uint8_t s_count = 0; if (++s_count >= 10) { s_count = 0; g_RunTime++; /* every 10ms add 1 */ if (g_RunTime == 0x80000000) { g_RunTime = 0; } } }
6.案例——最简单的TCP echo程序
先来一个最简单的TCP程序。uIP作为server,IP地址为192.168.1.15。PC机做client,IP地址为192.168.1.10X。
(1)在网络调试助手中,选择以太网通信种类为client(表示PC机为Client),IP地址输入192.168.1.15,端口号输入1234。最后点击连接。
(2)在发送区域输入任意内容,点击发送数据。
(3)观察返回结果,是否和发送数据相同。
为了实现该功能新建example1.c和example1.两个文件。代码如下:
#include "example1.h" #include "uip.h" #include#include #include void example1_init(void) { uip_listen(HTONS(1234)); } void example1_appcall(void) { if( uip_newdata() ) { // 输出远程IP和埠号 printf("remote ip addr:%d.%d.%d.%d\r\n", (uip_conn->ripaddr[0]) & 0X00ff, (uip_conn->ripaddr[0]) >> 8, (uip_conn->ripaddr[1]) & 0X00ff, (uip_conn->ripaddr[1]) >> 8 ); printf("remote ip port:%d\r\n",HTONS(uip_conn->rport)); // TCP ECHO uip_send(uip_appdata,uip_len); } }
图e TCP Echo实验结果
代码做如下分析:
1. uip_listen(HTONS(1234));侦听1234埠,
2. uip_newdata()即查询uip_buf中是否有新数据,如果返回1的话,表示接收到新数据。
3. uip_send(uip_appdata,uip_len);uip_send为发送数据报函数
4. uip_appdata指向用户数据,所谓用户数据即TCP负载数据,例如网络调试助手发送XX,那么uip_appdata指向xukai871105.
5. uip_len为用户数据长度,若串口调试助手发送XX,那么uip_len为11。
7. web页面控制
1. 打开IE浏览器,地址栏输入DM9051的IP地址:
http://192.168.x.x/
(圖f)
回车后打开下面控制页面:
(圖g)
2. 網頁下可以看到MCU跟IP基本訊息(如:MAC、IP address),最下面有LED Test,可通过网页控制STM32板上的LED灯。
LED Test說明,在按下 ON 的時候,GPIOF6 腳位LED 燈會亮,按下OFF後會滅掉,Flash 代表閃爍,會從GPIOF6 ~9 LED順序亮滅,網址列上會顯示LED1跟LED0,1代表亮,0但表滅,2代表閃爍。
(圖h)
(圖i)
具体code如下介紹:
(1).稍微介绍一下web相关,另外标出埠、IP、GPIO/LED的修改位置。
在void main()中,系統初始化完成後加入httpd_init(); 來初始化webserver port,預設web 用80 port。
(2).在初始化完成後,在uip-conf.h最下面將#include "webserver.h"的注釋拿掉,這樣UIP APPCALL 就能對應到 httpdappcall。
(3).首先我們先在main.c初始 LED燈GPIO PIN,代碼如下:
void LedInit(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOF | RCC_APB2Periph_GPIOF, ENABLE); /*初始化 GPIOF的 Pin_6为推挽输出*/ GPIO_InitStructure.GPIO_Pin=GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOF,&GPIO_InitStructure); /*初始化 GPIOF的 Pin_7为推挽输出*/ GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOF,&GPIO_InitStructure); /*初始化 GPIOF的 Pin_8为推挽输出*/ GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOF,&GPIO_InitStructure); /*初始化 GPIOF的 Pin_9为推挽输出*/ GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; GPIO_Init(GPIOF,&GPIO_InitStructure); }
初始化完成後,在webserver目錄下新增web_led.c和web_led.h檔,代碼參考如下:
(1).首先設定LED 亮滅delay 時間:
void Delay(uint32_t times) { while(times--) { uint32_t i; for (i=0; i<0xffff; i++); } }
(2).接下來設定LED 亮滅順序,我們使用ascii碼來判斷亮滅,
void Set_LED_mode(char lkkcode) { //int i; /* 0代表LED6滅, 1代表LED6亮, 2代表LED6~9依序閃爍 */ if(lkkcode == ('0')) { GPIO_ResetBits(GPIOF,GPIO_Pin_6); }else if (lkkcode == '1'){ GPIO_SetBits(GPIOF,GPIO_Pin_6); }else if(lkkcode == '2') { //for(i = 0 ; i< 30 ; ++i) { GPIO_SetBits(GPIOF,GPIO_Pin_6); Delay(25); GPIO_ResetBits(GPIOF,GPIO_Pin_6); Delay(25); GPIO_SetBits(GPIOF,GPIO_Pin_7); Delay(25); GPIO_ResetBits(GPIOF,GPIO_Pin_7); Delay(25); GPIO_SetBits(GPIOF,GPIO_Pin_8); Delay(25); GPIO_ResetBits(GPIOF,GPIO_Pin_8); Delay(25); GPIO_SetBits(GPIOF,GPIO_Pin_9); Delay(25); GPIO_ResetBits(GPIOF,GPIO_Pin_9); Delay(25); } } }
(3).設定好初始化LED GPIO後,在網址列輸入IP後會顯預設的示網頁,網頁內容為html放在app/webserver/ httpd-fs目錄下,主要為index.html,如想要修改顯示使用者網頁有兩種方式:
(4).修改代碼參考如下:
1.直接修改index.html。
2.將使用者的html 放入httpd-fs目錄下,然後在handle_input修改
3.判斷LED亮滅,s->inputbuf array中填入ascii碼或其他使用者想實現的判斷,如s->inputbuf[3] = 'L','E','D'。
static PT_THREAD(handle_input(struct httpd_state *s)) { PSOCK_BEGIN(&s->sin); PSOCK_READTO(&s->sin, ISO_space); if(strncmp(s->inputbuf, http_get, 4) != 0) { PSOCK_CLOSE_EXIT(&s->sin); } PSOCK_READTO(&s->sin, ISO_space); if(s->inputbuf[0] != ISO_slash) { PSOCK_CLOSE_EXIT(&s->sin); } if(s->inputbuf[1] == ISO_space) { //strncpy(s->filename, http_index_html, sizeof(s->filename)); //修改成想要顯示的html檔,如下: strncpy(s->filename, http_webMain_html, sizeof(s->filename)); } /* Control led , 0 = OFF, 1 = ON, 2 = Flash */ #if 1 //Joseph add else if (s->inputbuf[3] == 'L','E','D' && ((s->inputbuf[4] == '0') || (s->inputbuf[4] == '1') || (s->inputbuf[4] == '2'))){ Set_LED_mode(s->inputbuf[4]); s->inputbuf[4]= 0; //strncpy(s->filename, "/home.html", 10); strncpy(s->filename, http_webMain_html, sizeof(s->filename)); } #endif else { s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0; strncpy(s->filename, &s->inputbuf[0], sizeof(s->filename)); } /* httpd_log_file(uip_conn->ripaddr, s->filename);*/ s->state = STATE_OUTPUT; while(1) { PSOCK_READTO(&s->sin, ISO_nl); if(strncmp(s->inputbuf, http_referer, 8) == 0) { s->inputbuf[PSOCK_DATALEN(&s->sin) - 2] = 0; /* httpd_log(&s->inputbuf[9]);*/ } } PSOCK_END(&s->sin); }
(5).上面介紹完網頁和控制LED燈後,接著說明如何在網頁上顯示目前的MAC、IP address等,在httpd-cgi.c檔下新增sys_ stats function,代碼參考如下:
static unsigned short generate_sys_stats(void *arg) { struct httpd_state *s = (struct httpd_state *)arg; return snprintf((char *)uip_appdata, UIP_APPDATA_SIZE, "\ MAC Address : %02x:%02x:%02x:%02x:%02x:%02x \ IP Address : %d.%d.%d.%d \ Network Mask : %d.%d.%d.%d \r\n", /* MAC address */ (unsigned int)uip_ethaddr.addr[0], (unsigned int)uip_ethaddr.addr[1], (unsigned int)uip_ethaddr.addr[2], (unsigned int)uip_ethaddr.addr[3], (unsigned int)uip_ethaddr.addr[4], (unsigned int)uip_ethaddr.addr[5], /* IP address */ (uip_hostaddr[0]&0xff), ((uip_hostaddr[0] >> 8)&0xff), (uip_hostaddr[1]&0xff), ((uip_hostaddr[1] >> 8)&0xff), /* network mask */ (uip_netmask[0]&0xff), ((uip_netmask[0] >> 8)&0xff), (uip_netmask[1]&0xff), ((uip_netmask[1] >> 8)&0xff), /* Getway address */ (uip_draddr[0]&0xff), ((uip_draddr[0] >> 8)&0xff), (uip_draddr[1]&0xff), ((uip_draddr[1] >> 8)&0xff) ); } static PT_THREAD(sys_stats(struct httpd_state *s, char *ptr)) { PSOCK_BEGIN(&s->sout); //for(s->count = 0; s->count < UIP_CONNS; ++s->count) { if((uip_conns[s->count].tcpstateflags & UIP_TS_MASK) != UIP_CLOSED) { PSOCK_GENERATOR_SEND(&s->sout, generate_sys_stats, s); } //} PSOCK_END(&s->sout); Getway : %d.%d.%d.%d
8.测试结果
接上网线后:
首先设置PC端IP与DM9051 在同一段内;
1.运行-cmd
输入 ping 192.168.x.x 测试是否能ping通;
输入 ping 192.168.x.x -n 100 测试连通丢包率;ctrl+c停止;
(圖j)
2.查看确认物理地址
输入 arp -a 192.168.x.x 回车;显示物理地址。
(圖k)
3.速率测试结果:
注:所用工具软件为:网络调试助手+IPOP。
(圖l)
(圖m)
A. 利用网络调试助手循环发送档,让输出达到最大值从而测试DM9051的最大速率;
B. 由结果可以看出速率,而平均1587Kbits/s=199Kb/s的速率算是比较稳定的了。
-
4.使用iperf頻寬测试结果大約有8 . 5M多:
(圖n)
9.移植相关
移植第一步:实现在unix/tapdev.c里面的三个函数。首先是tapdev_init函数,该函数用于初始化网卡(也就是我们的DM9051),通过这个函数实现网卡初始化。其次,是tapdev_read函数,该函数用于从网卡读取一包数据,将读到的数据存放在uip_buf里面,数据长度返回给uip_len。最后,是tapdev_send函数,该函数用于向网卡发送一包数据,将全局缓存区uip_buf里面的数据发送出去(长度为uip_len)。其实这三个函数就是实现最底层的网卡操作。
第二步,因为uIP协议栈需要使用时钟,为TCP和ARP的定时器服务,因此我们需要STM32提供一个定时器做时钟,提供10ms计时(假设clock-arch.h里面的CLOCK_CONF_SECOND为100),通过clock-arch.c里面的clock_time函数返回给uIP使用。
第三步,配置uip-conf.h里面的宏定义选项。主要用于设置TCP最大连接数、TCP监听埠数、CPU大小端模式等,这个大家根据自己需要配置即可。 通过以上3步的修改,我们基本上就完成了uIP的移植。
通过以上3步的修改,我们基本上就完成了uIP的移植。
1.写网卡驱动程序,与具体硬件相关。这一步比较费点时间,不过好在大部分网卡芯片的驱动程序都有代码借鉴或移植。驱动需要提供三个函数:tapdev_init():网卡初始化函数,初始化网卡的工作模式。tapdev_read(void):读包函数。将网卡收到的数据放入全局缓存区uip_buf 中,返回包的长度,给uip_len。void tapdev_send(void):发包函数。将全局缓存区uip_buf 里的数据(长度放在uip_len 中)发送出去。
2.uipopt.h/uip-conf.h 是配置文件,用来设置本地的IP 地址、网关地址、MAC 地址、全局缓冲区的大小、支持的最大连接数、侦听数、ARP 表大小等。可以根据需要配置。
3.还需要更改些什么?
在此附上一个UIP移植供各位參考:从零开始使用uIP
前言
DM9051NP SPI介面Pin count比乙太網PHY的RMII mode數量少,PCBA佈局走線可更精簡,而DM9051NP driver方面,由於source code已出現在ESP IDF中,可見可靠度以及資料送收性能得到ESP32肯定。在ESP32 SPI clock 20MHz情況下,DM9051NP能充分發揮乙太網資料送收性能,相關 Iperf 測試數據,請參考第5章節。
1. DM9051 ESP32 12K開發版介紹
此DM9051 ESP32 12K(以下簡稱 『開發版』)是基於安可信ESP-12K來設計,以SPI介面轉乙太網IC:DM9051NP,為ESP-12K (核心模組:ESP32-S2) 增加了Ethernet功能,其中,DM9051NP只佔用了4根GPIO,原本ESP32參考設計為乙太網PHY,在RMII模式下,需佔用10根GPIO!
同樣地,DM9051NP也適合用在ESP32-WROOM32(核心模組:ESP32)的乙太網應用上,在有限數量GPIO介面,使用DM9051NP SPI介面轉乙太網IC,空出來的GPIO能讓MCU更方便開發。
1.1 ESP 12K模組介紹
ESP32-S2 提供豐富的介面,包括SPI,I2S,UART,I2C,LED PWM,LCD, Camera 介面,ADC,DAC,觸摸感測器,溫度感測器和多達43 個GPIO。
ESP-12 K 是由安信可科技開發的Wi-Fi 模組,該模組核心處理器ESP32-S2 是一款高集成度的低功耗Wi-Fi 系統級芯片 (SoC),專為物聯網 (IoT)、移動設備、可穿戴電子設備、智慧家居等各種應用而設計。
1.2 DM9051NP網卡芯片介紹
DM9051NP SPI介面IC是為了方便物聯網行業進行乙太網通信而開發出的解決方案。DM9051NP是帶有標準串列外設介面(Serial Peripheral Interface,SPI)的獨立乙太網控制器。DM9051NP符合IEEE 802.3 規範,還能以DMA 模式來傳輸,實現資料快速傳送。DM9051NP通過1個interrupt和SPI介面來進行與MCU的通訊,資料傳輸規格為10/100 M。
- Package:32支接腳封裝,QFN
- IEEE 802.3az Energy Efficient Ethernet (EEE)
- 遠程喚醒 (WOL)
- Support fiber mode.
- EMI (Class B) and HBM ESD Rating 8KV
- 工業溫度規範:–40℃ to +85℃
2. 實驗環境
2.1 應用:訪問網頁
此開發版利用HTTP協議,完成Wi-Fi轉傳乙太網的透傳功能,而兩端數據包進行雙向即時轉傳。
在透傳功能運行時,將開發版當作一個小網關 (無線熱點Wi-Fi AP),讓手機或平板等透過無線連上線,開發版會要求輸入密碼,待登入後,此時DM9051NP會釋放出ESP32 AP access point(eth2ap的功能),當手機連線後,不是開發版負責分發DHCP動態IP給手機,是router的DHCP Server進行動態IP分配,讓DM9051NP取得IP,接著ESP32-DM9051-eth2ap進行數據透傳,成功連線並訪問網頁。
如果router沒有DHCP Server功能的話,使用者可對手機設置靜態IP,也可訪問網頁。
3. 硬體部分
3.1 硬體環境
此開發版規格如下:
主要組件 | 功能描述 |
P1 | DC 5V電源插座 |
J28 J29 | 選擇 USB (默認設置) or DC電源插座來供電 |
J28 ON: 5V 電源由 USB (J1)供應 J28 OFF: 5V電源由 DC插座 (P1)供應 | |
J29 ON: 5V電源由 DC插座 (P1)供應 J29 OFF: 5V電源由 USB (J1)供應 | |
J27 J30 | 選擇 USB (默認設置) or DC電源插座來為DM9051 以及 ESP 12K (ESP32-S2)供電 |
J27 ON: 5V電源由 USB (J1)供電給DM9051 J27 OFF: 5V電源由 DC插座 (P1)供電給DM9051 | |
J30 ON: 5V電源由 DC插座 (P1)供電給ESP 12K(ESP32-S2) J30 OFF: 5V電源由 USB (J1)供電給ESP32-S2 | |
JP4 LED1 LED2 | RJ45 網路介面插座為10M以及/100Mbps Ethernet,插座有LED1 與LED2燈號。 (LED 燈號模式1: 默認設置) |
LED1 :綠燈表示 (Link / Active) ON: 連線中 OFF: 不是連線中 Flash: 資料傳送中 | |
LED2 :黃燈(當連線中時,為網速燈號 ) ON: 100M Full duplex OFF: 10M Full duplex | |
U3 | DM9051 |
U2 | U2 ESP32-S2 Module : ESP-12K |
SW1 | 啟動按鍵 |
SW2 | 重啟按鍵 |
LED4 | LED燈號自訂義 (GPIO2) |
LED5 | 電源燈號 |
J3 | ESP32-S2 I/O |
J2 | ESP32-S2 I/O |
J1 | Micro USB 插座 ( USB 5V供電 以及 通信) |
表 3.1.1 |
3.2 SPI硬體接線部分
ESP32-S2 & DM9051NP的硬體設計:
DM9051NP通過SPI介面控制內部Register,並有interrupt輸出介面。ESP32-S2通過SPI1和DM9051NP相連,參考設計如下:
DM9051NP | ESP32 S2 | |
SPI_CS | Pin17 | Pin13 |
SPI_CLK | Pin18 | Pin16 |
SPI_MOSI | Pin19 | Pin14 |
SPI_MISO | Pin20 | Pin15 |
SPI_INT | Pin24 | Pin18 |
表3.2.1 |
在ESP32官方 routine code: ESP IDF examples/ethernet/中,介紹了DM9051NP SPI介面的硬體設計方法,下面鏈結網址的說明文檔README.md中,有建議線路的設計。請同學注意,此開發版的DM9051NP與ESP32-S2,基於走線以及IC擺放位置考量,做成如圖3.2.1/圖3.2.2的設計變更。 github.com/espressif/esp-idf/tree/1d7068e4be430edd92bb63f2d922036dcf5c3cc1/examples/ethernet
4. 軟體部分
4.1 ESP32 的軟體開發環境:ESP
此開發手冊使用 ESP-IDF (Espressif IoT Development Framework) 來開發乙太網口通訊,配置相關功能表,compiler以及下載routine code到 ESP32 S2。
對於如何搭建ESP32 開發環境,請各位同學參考ESP32官方搭建教學,這裡寫得很詳細,會帶著同學完成:
https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/get-started/index.html
(1) HTTP簡介:
HTTP協議是Hyper Text Transfer Protocol超文字傳輸協定的縮寫,基於TCP傳輸層協議進行通信,採用Client端與Server端架構,屬於應用層協定。
ESP IDF有提供軟體模組使用和設計文檔,讓同學運用ESP-IDF的各項功能,此開發版即是在ESP IDF範例(Example)中構建HTTP的應用程式。
(2) ESP IDF的HTTP介面:
ESP IDF自帶的HTTP介面的使用, ESP HTTP模組提供了完整的API以支援HTTP的應用,而source code,請同學參考IDF目錄下的test_http_client.c,其路徑為esp-idf\components\esp_http_client\test
(3) HTTP請求:
HTTP請求格式是Client端往Server端發送請求動作,告知Server自己的要求。
(4) HTTP報文:
HTTP報文是HTTP應用程式之間傳輸的資料塊,HTTP報文分為HTTP請求報文和HTTP回應報文。
對於HTTP請求與報文的詳細內容,在這裡就先不談了,已經有其他先進在各論壇做了詳細介紹,有興趣的同學可上網自行學習。
(5) ESP32的HTTP介面介紹:
請同學直接參考ESP IDF的官方指南,那邊介紹得更多更詳細,相關網址如下:
https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/index.html
4.3 有線乙太網轉Wi-Fi的AP路由功能:ETH2AP
(1) 介紹:
ESP32提供官方routine code (ETH2AP) ,也就是乙太網+Wi-Fi熱點AP,各位同學可在ESP IDF範例 (esp-ide/examples/Ethernet)找到,鏈結網址如下:
https://github.com/espressif/esp-idf/tree/1d7068e/examples/ethernet/eth2ap
(2) 如何使用ETH2AP example:
步驟1:初始化有線乙太網以及無線熱點模式(Wi-Fi AP mode)
步驟2:乙太網接入router,開啟DHCP server功能
步驟3:開啟 ESP32的Wi-Fi AP
步驟4:無線設備 (智能手機) 連接 ESP32的Wi-Fi
*詳細內容,建議各位同學參考這位先進的文章:
https://blog.csdn.net/Lovely_him/article/details/117754768
4.4 DM9051NP驅動
DM9051NP driver的source code 參考如下:
static void emac_dm9051_task(void *arg) { emac_dm9051_t *emac = (emac_dm9051_t *)arg; uint8_t status = 0; uint8_t *buffer = NULL; uint32_t length = 0; while (1) { // block indefinitely until some task notifies me ulTaskNotifyTake(pdTRUE, portMAX_DELAY); /* 清除中斷狀態 */ dm9051_register_read(emac, DM9051_ISR, &status); dm9051_register_write(emac, DM9051_ISR, status); /* 收到封包 */ if (status & ISR_PR) { do { length = ETH_MAX_PACKET_SIZE; buffer = heap_caps_malloc(length, MALLOC_CAP_DMA); if (!buffer) { ESP_LOGE(TAG, "no mem for receive buffer"); } else if (emac->parent.receive(&emac->parent, buffer, &length) == ESP_OK) { /*將緩衝buffter堆疊在TCP/IP層*/ if (length) { emac->eth->stack_input(emac->eth, buffer, length); } else { free(buffer); } } else { free(buffer); } } while (emac->packets_remain); } } vTaskDelete(NULL); }
*ESP IDF 的DM9051NP source code 完整鏈結:
https://github.com/espressif/esp-idf/blob/master/components/esp_eth/src/esp_eth_mac_dm9051.c
4.5 其他筆記
有些同學反應,之前他們從Github論壇下載DM9051NP sample code,執行後會出現連上線後,有斷線情況。經過分析試驗後,其原因是原本ESP32的polling間隔時間為50ms,要改成10ms。相關修改建議如下:
(1) ESP32 polling的間隔為改成10ms”, 將ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(50));改成Line 257 vTaskDelay(pdMS_TO_TICKS(10));
(2) 請刪除原本example的這一行代碼/components/esp_eth/src/esp_eth_mac_dm9051.c
(3) 增加此行代碼 "dm9051_register_write(emac, DM9051_ISR, 0xFF);"到 emac_dm9051_task() function的部分。
(4) 增加此行代碼 "dm9051_register_write(emac, DM9051_ISR, 0xFF);" 到emac_dm9051_receive() function. 的部分。
5. 有線乙太網轉Wi-Fi的AP功能演示
5.1 測試架構與需要設備如下
訪問網頁---路由---DM9051NP_ESP 12K模塊 <<<>>>手機
註:請依照當時的網路環境,同學需要對路由的WAN口以及LAN口做DHCP server設置。
在圖5.1.1中,手機經由開發版,直接連線到左方的router,此router提供DHCP server。所以,手機向router得到DHCP動態IP後,經過router連線到網際網路。
5.2 ETH2AP功能演示相關訊息
以手機透過WiFi來連上模塊,手機WiFi進行掃描後,可找到開發版的SSID 名稱為+++eth2ap_9051_s2_56_06”,表示開發版的Wi-Fi已經準備好。點選後,輸入Password : 12345678,成功連線後,手機就可透過開發版來上網。
我們可以在調適終端上看到ETH2AP運行後,相關的設置訊息:
開發版接上電源後,看到USB埠列印s2[e2ap]: Ethernet Link Up (如圖5.2.1,ETH2AP’s message in the terminal) 表示乙太網路成功連線。開發版的Wi-Fi AP配置了MAC位址:00:60:6e:90:56:06,而開發版的乙太網也同樣配置了MAC位址00:60:6e:90:56:06。這兩個配置的MAC位址,在之前提到的透傳功能中,在最底層的硬體概念上有它的意義,並且,不會出現在收送的數據包之內容中。接著,開發版運行ETH2AP功能,進行收送數據包,從DM9051NP網口收到數據包,就原封不動地透過Wi-Fi口轉送出去,從Wi-Fi口收到數據包也原封不動由網口轉送出去,此為『透傳』。
5.3 內網測試 (Intranet)
這裡以華碩router (產品型號:ASUS RX3041) 來進行內網測試,圖中手機經由開發版直接連線左方的router,並且從router的DHCP server得到DHCP動態IP。開發版在內網這一段區域,它本身是一個網絡站點,而且是內網的IP支配者。所以,router必定預先有一個靜態IP,由於它是內網的IP支配者 (就是DHCP server) ,所以,會分配動態IP給DHCP Client端 (手機)。經查看華碩router手冊,找到預先配置的靜態IP為192.168.6.1。
此內網測試,在手機成功取得動態IP後,會出Wi-Fi AP已連上的狀態圖案。此時,同學開啟網頁流覽器(Browser as Google)來訪問前述的192.168.6.1,也就是router的web server首頁,會要求同學登入帳戶與密碼,查看router手冊後,得知帳戶是admin,密碼也是admin。登入後,手機即可訪問router的web server首頁,完成ETH2AP內網測試。
5.4 Iperf測試
以Iperf測試DM9051NP乙太網送收性能,在SPI clock 20MHz下,獲得網口性能數據:
TX | 10.34 mbps |
RX | 9.58 mbps |
前言
本文档介绍在M2M通信的应用-4G監控攝像頭,於Cat4模塊上,如何添加DM9051 SPI网卡驱动。而當紅的Cat.1模塊亦可依照本文操作方式加入。
應用簡介
A:4G監控攝像頭:此作法使用ASR1802的SPI接口來接收攝像頭的錄像資料,DM9051NP在這個應用上,相當於一個轉接芯片,架構為:監控攝像頭 ←→ DM9051NP—SPI接口--移遠EC200T(ASR1802)。←→ 4G
B:由於4G模塊的應用普及,不僅是Cat4,當前火熱的Cat1在各行業的應用,如CPE、POS機、智能監控、環境管理或充電桩等,相關應用需要有線以太網口,將數據上傳到遠端後台,進行遠程控制等,都可以採用此相同作法。
1.方案介紹
1.1 4G模組介紹
此4G模組是上海移遠通信EC200T,是一款LTE Cat 4 通信模块,支持最大下行速率150 Mbps和最大上行速率 50 Mbps,EC200T内置丰富的网络协议,集成多个工业标准接口,拓展了其在M2M(机器对机器)通信的应用范围,如安防監控攝像頭、OTT、CPE、路由器、数据卡、平板电脑以及工业级 PDA 等。
上海移遠通信EC200T使用到上海翱捷科技ASR1802,ASR1802為所有竞品中系统最小,采用RTOS與Linux操作系统,采用ASR1802芯片的宽带MiFi终端已经销售到全球各运营商,并获得运营商认证,如中国CMCC,韩国SKK,日本,东南亚,印度Airtel,俄罗斯Beeline,非洲MTN等。
1.2 DM9051NP网卡芯片介绍
DM9051NP SPI接口网卡芯片是为了方便互聯網行業进行以太网通信而开发出的解决方案。DM9051NP芯片是带有行业标准串列外设接口(Serial Peripheral Interface,SPI)的独立以太网控制器。DM9051NP符合IEEE 802.3 规范,它还支持以DMA 模式來传输,以实现资料传送快速。DM9051NP通过1个中断引脚和SPI接口來进行与主控制器/MCU单片机的通信,资料传输规格为10/100 M。
- Package:32支接脚封装,QFN.
- IEEE 802.3az Energy Efficient Ethernet (EEE)
- 远程唤醒 (WOL)
- 并行线/交叉线自动切换 (HP Auto-MDIX)
- Support 光口界面
- EMI (Class B) and HBM ESD Rating 8KV
- 工业温度规范: –40℃ to +85℃
- 连续工作温度 < 60℃
2.实验环境
2.1 應用:Cat.4 ,4G監控攝像頭
硬件架構:攝像頭 ←→ DM9051NP—SPI接口--移遠EC200T(ASR1802) ←→ 4G
2.2 Cat.1 +有線以太網口應用
其他應用 ←→ DM9051NP—SPI接口—ASR3601 ←→ 4G
3 硬件部分
3.1 硬件环境
DM9051NP通过SPI接口控制内部寄存器,并有中断输出接口。 EC200T(ASR1802)通过SPI1和DM9051NP相连。具体接口如下:
DM9051NP | EC200T (ASR1802) | |
SPI_CS | Pin17 | Pin37 |
SPI_MOSI | Pin19 | Pin38 |
SPI_MISO | Pin20 | Pin39 |
CPI_CLK | Pin18 | Pin40 |
INT | Pin24 | Pin18 |
3.2 SPI硬件接线部分
SPI信号线说明,通常SPI通过4个引脚与外部器件相连:
- (1)MISO : 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。
- (2)MOSI : 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。
- (3) SCK : 串口时钟,作为主设备的输出,从设备的输入
- (4) NSS : 从设备选择。这是一个可选的引脚,用来选择主/从设备。它的功能是用来作为“片选引脚”,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。
4.軟件部分
4.1 引言
以DM9051在ASR1802 Linux上導入為例,在Linux核心添加 DM9051驅動,DM9051為SPI介面,需具體實現SPI傳輸功能,DM9051網路數據包送收程序,及依據SPI master特性而擴增的DMA傳輸模式設置。
因為要採用DTS與DMA模式,除了要進行DTS相關的設置之外,也對DM9051驅動加入DMA的相關參數,然後,再來掛載驅動。當驅動跑起來時,通過DTS取得組態設置參數,然後,讓芯片正常運行。
4.2 DM9051NP驅動部分
将DM9051NP驅動壓縮檔解压,然后将整个包放到ql-ol-sdk/ql-ol-crosstool/package目录下,然后在ql-ol-sdk/ql-ol-crosstool目录下make menuconfig;
路径:Kernel modules → SPI Support → kmod-ql_spi_net
以上步骤做完后,在ql-ol-sdk下make -j6 V=99,会在ql-ol-sdk/target目录下生成版本包,将版本包烧进模块,使用insmod /lib/modules/3.10.33/dm9051.ko 加载驱动即可。
加载成功后可看到如下所示:4.2.1 DM9051NP驅動源代碼參考
查看dm9051_init_dm9051函數, 相關內容如下:
iow(db, DM9051_GPCR, GPCR_GEP_CNTL); /* 让GPIO0输出*/ phy4= dm9051_phy_read(dev, 0, MII_ADVERTISE); dm9051_phy_write(dev, 0, MII_ADVERTISE, phy4 | ADVERTISE_PAUSE_CAP); /* dm95 flow-control RX! */ dm9051_phy_read(dev, 0, MII_ADVERTISE); /* Program operating register */ iow(db, DM9051_TCR, 0); /* TX Polling clear清除TX轮询*/ iow(db, DM9051_BPTR, 0x3f); /* Less 3Kb, 200us */ iow(db, DM9051_SMCR, 0); /* Special Mode */ /* clear TX status清除TX狀態 */ iow(db, DM9051_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END); iow(db, DM9051_ISR, ISR_CLR_STATUS); /* Clear interrupt status清除中斷狀態 */ /* Init Driver variable */ db->imr_all = IMR_PAR | IMR_PRM; /* "| IMR_PTM" */ #ifdef JABBER_PACKET_SUPPORT db->rcr_all= RCR_DIS_LONG | RCR_DIS_CRC | RCR_RXEN | RCR_DIS_WATCHDOG_TIMER; #else db->rcr_all= RCR_DIS_LONG | RCR_DIS_CRC | RCR_RXEN; #endif /* * (Set address filter table) * After.call.ndo_open * "kernel_call.ndo_set_multicast_list.later". */ dm9051_fifo_reset(1, NULL, db); // 'NULL' for reset FIFO, and no increase the RST counter int_reg_stop(db); …… >>> 歡迎交流!
4.3 设备树DTS (Device tree source)
(A) 基本概念:在Linux內核源代碼中,存在大量對細節信息描述的代碼。這些代碼在/arch/arm/plat-xxx和/arch/arm/mach-xxx目錄,對內核而言,這些platform設備、resource、i2c_board_info、spi_board_info以及各種硬件的platform_data絕大多數屬於垃圾冗長的代碼。為了解決這一問題,ARM內核版本3.x之後引入了原先在Power PC等其他體系架構已經使用的Flattened Device Tree。
(B) 在我們在進行DM9051NP驅動調試之前,要先進行DM9051NP 驅動的DTS參數設置,讓CPU SPI master能夠對應DM9051NP的設置,操作指南如下:
a. DM9051NP驅動參數設置:依照開發者的需求,寫入conf_ver.h來設置DM9051NP驅動(建議:只編輯conf_verh)
b. 為了完善源代碼架構,請在conf_verh添加下列函數:
#define DM_CONF_APPSRC #define DM_CONF_DBGSRC #define DM_CONF_DTSSRC
c. 創建一個軟件模塊(dm9051.ko),請在conf_verh添加下列函數:
#define DM_CONF_MODULE
d. 當對驅動進行設置時,添加DM_CONF_PHYPOLL是很重要的:
#define DM_CONF_PHYPOLL
e. 當使用DTS設備樹時,請在conf_verh添加下列函數
#define DTS_CONF_YES
說明:在你的DTS描述符中,請定義
- 1) 最高SPI時鐘速度。
- 2) 中斷模式與中斷IRQ號。
- 3) 中斷主動極性。
- 4) SPI bus_num以及 SPI片選(有需要才設置)
f. 如果不使用DTS的話,請改用下列方式,添加到_ver.h”:
*因為不使用DTS,請定義你的SPI時鐘速度: #define DM_CONF_MAX_SPEED_HZ 15600000 *因為不使用DTS,當使用中斷模式時,亦需中斷IRQ數字以及主動極性: #define DM_CONF_INTERRUPT /*設定的中斷號*/ #define DM_CONF_INTERRUPT_IRQ 26 #define DM_CONF_INTERRUPT_LOW_ACTIVE *由於不使用DTS,請依照下列函數來定義SPI bus number。 如果沒有定義的話,SPI bus number將為0。 #define DM_CONF_SPI_BUS_NUMBER 0 *由於不使用DTS,請依照下列函數來定義SPI片選。 如果沒有定義的話,SPI bus number將為0。 #define DM_CONF_SPI_CHIP_SELECT 1
4.3.1 DTS實作
看完了DM9051NP 驅動的DTS參數設置指南之後,讓我們來實作一下:
1.) 配置SPI CLK:
在ql-ol-sdk/ql-ol-uboot/board/Marvell/nezas_p201/nezas_p201.c文件中配置SPI时钟,增加如下代码:
#ifdef CONFIG_ASR_SPI #define APBC_PHYS_BASE (0xd4000000 + 0x015000) #define APBC_SSP0 (APBC_PHYS_BASE + 0x01c) #endif
在ql-ol-sdk/ql-ol-uboot/board/Marvell/nezas_p201/nezas_p201.c文件中配置SPI时钟,增加如下代码:
代码如下:
#ifdef CONFIG_ASR_SPI __raw_writel(0x17, APBC_SSP0); mdelay(1); __raw_writel(0x13, APBC_SSP0); //ssp0 @13M /* bit[6:4] of APBC_SSP0 * 0x0 = 6.5MHz * 0x1 = 13MHz * 0x2 = 26MHz */ #endif
注意: __raw_writel(0x17, APBC_SSP0); 和 __raw_writel(0x13, APBC_SSP0);中当前配置的SPI时钟是13M,如果要设置成26M,则将其中的0x17、0x13改成0x27和0x23。这里如不添加,则默认6.5M。
4.4 DMA模式
這是依據SPI master特性而擴增的應用,各家SOC芯片供應商的實現方式可能略有不同。如何開DM9051 DMA模式來接收與發送數據? 請先在CPU開啟DMA模式,然後DM9051驅動做對應的設置。
(1) 請查看dm9051_probe函數,內容如下:
SubNetwork_SPI_Init(db, 1);
(2) 再查看SubNetwork_SPI_Init函數, 內容如下:
SPI_GPIO_SetupPwrOn(db); SPI_GPIO_Setup(db); //mt_dm9051_pinctrl_init(db->spidev); //or, SPI_GPIO_Set(1); SPI_SPI_Setup(db);
(3) 再查看SPI_SPI_Setup函數, 內容如下:
SPI_PARAM_Set(db); #if DM_DM_CONF_RARE_PROJECTS_DTS_USAGE /*當處於DTS模式時,請在DTS檔中定義SPI最高速度是多少*/ #else db->spidev->max_speed_hz= dm9051_spi_board_devs[0].max_speed_hz; #endif #if DM_DM_CONF_RARE_PROJECTS_DTS_USAGE /*當處於DTS模式時,請在DTS檔中定義SPI最高速度是多少*/ #else db->spidev->mode = SPI_MODE_0; db->spidev->bits_per_word = 8; printk("%s Driver spi_setup()\n", CARDNAME_9051); if(spi_setup(db->spidev)){ printk("[dm95_spi] spi_setup fail\n"); return; } #endif
(4) 再查看SPI_PARAM_Set函數, 內容如下:
struct mt_chip_conf *spi_par = (struct mt_chip_conf *) db->spidev->controller_data; if(!spi_par){ printk("[dm95_spi] spi config fail"); return; } spi_par->setuptime = 15; spi_par->holdtime = 15; spi_par->high_time = 10; spi_par->low_time = 10; spi_par->cs_idletime = 20; spi_par->rx_mlsb = 1; spi_par->tx_mlsb = 1; spi_par->tx_endian = 0; spi_par->rx_endian = 0; spi_par->cpol = 0; spi_par->cpha = 0; #if DMA3_P2_MSEL_MOD spi_par->com_mod = DMA_TRANSFER; #else spi_par->com_mod = FIFO_TRANSFER; #endif spi_par->pause = 0; spi_par->finish_intr = 1; spi_par->deassert = 0; …… >>>歡迎交流!
5.固件燒入
當完成驅動掛載後,即完成一個固件,接下來介紹燒錄固件的過程:
(1) 硬件環境
(2) 短接這個腳,然後上電即可進入下載模式,然後打開SWD燒錄軟件,進行下載,燒錄新的編譯的code(固件,或映像檔)。如下圖所示:
(3) “update.blf”是成為固件之前的一個檔案,從工具編譯出來後產生一個中間檔,然後,中間檔再轉換成固件後,即可燒入。在這個階段的工具中,我們可以看到直接產出來就是固件。
(4) 接下來介紹,完成一個固件後,如何燒入開發版的過程:
- a. 勾選清除,先把ASR1802的flash清除後,再选择我们编译出来的固件,然後燒入。
- b. 选择相应選項並打勾,然后点击绿灯,等待USB设备插入,待開發版透過USB連上PC後,即可下载固件完成。如图5.4。
- c. 利用USB與PC連線,顯示於console,就可鍵入adb shell or adb push文件。並設置到串口的波特率,在PC終端軟件(工具)选115200,才能正常顯示。
6.测试
6.1 局域网测试
使用iperf测试即可:如下图所示
6.2 访问公网测试
将ql_test_api 下载进模块,使用chmod +x ql_test_api,然后执行./ ql_test_api。
拨号成功后,就可以上网了。如下图所示:
拨号可执行文件: ql_test_api
6.3 视频测试
前言
DM9051NP芯片SPI接口占用管脚数量比以太网PHY的RMII/MII少,PCBA版线布局可更精简。而DM9051NP驱动软件方面,由于源代码已集成在ESP IDF中,可见其可靠度以及网口性能得到乐鑫官方认可。 在ESP32既有SPI 时钟31.2MHz的条件下,DM9051NP能充分发挥网口资料送收性能,相关Iperf测试数据,请参考第5章节。
1. DM9051NP + ESP32模块介绍
此模块是基于安可信ESP-12K模块来设计,以DM9051NP SPI接口转以太网芯片为ESP-12K模块(核心模块为ESP32-S2)增加了有线网口功能,其中,DM9051NP只占用了4个GPIO,原本ESP32有线网口参考设计为以太网PHY,在RMII模式下,需佔用10个GPIO ; MII模式下,GPIO占用更多,为16个。
同样地,DM9051NP更适合用ESP32-WROOM32(核心模块为ESP32)的以太网口应用上,在有限GPIO接口的情况之下,使用DM9051NP SPI接口转以太网芯片,空出来的GPIO能让MCU更方便去扩展其他功能。
1.1 ESP 12K模组介绍
ESP32-S2 提供丰富的外设接口,包括SPI,I2S,UART,I2C,LED PWM,LCD 接口,Camera 接口,ADC,DAC,触摸传感器,温度传感器和多达43 个GPIO。
ESP-12K 是由安信可科技开发的Wi-Fi 模块,该模块核心处理器ESP32-S2 是一款高集成度的低功耗Wi-Fi 系统级芯片 (SoC),专为物联网 (IoT)、移动设备、可穿戴电子 设备、智能家居等各种应用而设计。
1.2 DM9051NP网卡芯片介绍
DM9051NP SPI接口网卡芯片是为了方便物联网行业进行以太网通信而开发出的解决方案。DM9051NP芯片是带有行业标准串列外设接口(Serial Peripheral Interface,SPI)的独立以太网控制器。DM9051NP符合IEEE 802.3 规范,它还支持以DMA 模式來传输,以实现资料传送快速。DM9051NP通过1个中断引脚和SPI接口來进行与主控制器/MCU单片机的通信,资料传输规格为10/100 M。
- •Package:32支接脚封装,QFN.
- •IEEE 802.3az Energy Efficient Ethernet (EEE)
- •远程唤醒 (WOL)
- •Support 光口界面
- •EMI (Class B) and HBM ESD Rating 8KV
- •工业温度规范: –40℃ to +85℃
2. 实验环境
2.1应用:访问网页
DM9051 ESP32 12K模块利用HTTP协议,完成无线Wi-Fi转传有线以太网的透传功能,而两端数据包进行双向即时转发。
在透传功能演示时,将模块当作一个小网关(无线热点Wi Fi AP),让手机或平板等透过无线WIFI连上线,模块会要求输入密码,待登入后,此时DM9051NP会释放出ESP32 AP access point(eth2ap的功能),当手机连线模块后,由路由的DHCP Server进行DHCP 的动态IP分配,让DM9051NP取得IP ,接著ESP32-DM9051-eth2ap进行数据透传,成功连线并访问网页。
如果路由沒有DHCP server的话,使用者可对手机设置静态IP,也可访问网页。此应用可用于IPC监控、家庭物联网网关搭建等。
3. 硬件部分
3.1硬件环境
此DM9051 ESP 12K开发版规格如下:
主要组件 | 功能描述 |
P1 | DC 5V电源插座 |
J28 J29 | 选择 USB (默认设置) or DC电源插座来供电 |
J28 ON: 5V 电源由 USB (J1)供应 J28 OFF: 5V电源由 DC插座 (P1)供应 | |
J29 ON: 5V电源由 DC插座 (P1)供应 J29 OFF: 5V电源由 USB (J1)供应 | |
J27 J30 | 选择 USB (默认设置) or DC电源插座来为DM9051 以及 ESP 12K (ESP32-S2)供电 |
J27 ON: 5V电源由 USB (J1)供电给DM9051 J27 OFF: 5V电源由 DC插座 (P1)供电给DM9051 | |
J30 ON: 5V电源由 DC插座 (P1)供电给ESP 12K(ESP32-S2) J30 OFF: 5V电源由 USB (J1)供电给ESP32-S2 | |
JP4 LED1 LED2 | RJ45 网路接口插座为10M以及/100Mbps Ethernet,插座有LED1 与LED2灯号。 (LED 灯号模式1: 默认设置) |
LED1 :绿灯表示 (Link / Active) ON: 连线中 OFF: 不是连线中 Flash: 资料传送中 | |
LED2 :黄灯(当连线中时,为网速灯号 ) ON: 100M Full duplex OFF: 10M Full duplex | |
U3 | DM9051 |
U2 | U2 ESP32-S2 Module : ESP-12K |
SW1 | 启动按键 |
SW2 | 重启按键 |
LED4 | LED灯号自订义 (GPIO2) |
LED5 | 电源灯号 |
J3 | ESP32-S2 I/O |
J2 | ESP32-S2 I/O |
J1 | Micro USB 插座 ( USB 5V供电 以及 通信) |
表 3.1.1 |
3.2 SPI硬件接线部分
ESP32 有线接入以太网方法:
DM9051NP通过SPI接口控制内部寄存器,并有中断输出接口。ESP 12K(ESP32-S2)通过SPI1和DM9051NP相连。具体接口如下:
DM9051NP | ESP32 S2 | |
SPI_CS | Pin17 | Pin13 |
SPI_CLK | Pin18 | Pin16 |
SPI_MOSI | Pin19 | Pin14 |
SPI_MISO | Pin20 | Pin15 |
SPI_INT | Pin24 | Pin18 |
表3.2.1
*在ESP IDF的Ethernet example DM9051NP module:
在ESP32官方例程 examples/ethernet/中,介绍了DM9051NP SPI接口的接线方法,下面链结网址的说明文档README.md中,有说明接线方法。在這裡要提醒,DM9051NP以及ESP32模块,基于走线佈局考量,設計成如图3.2.1/图3.2.2的变更。
https://github.com/espressif/esp-idf/tree/1d7068e4be430edd92bb63f2d922036dcf5c3cc1/examples/ethernet
4. 软件部分
4.1 ESP32 硬件开发的软件环境:ESP IDF
此实作使用 ESP-IDF (Espressif IoT Development Framework) 环境来开发以太网口通讯,配置相关菜单,编译、下载固件至 ESP32 12K模块。对于如何搭建ESP32 硬件开发的软件环境,请各位同学参考上海乐鑫官方搭建教学,这里写得很详细,会手把手带着同学完成开发环境:
https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/get-started/index.html
4.2 HTTP
(1)HTTP简介
HTTP协议是Hyper Text Transfer Protocol超文本传输协议的缩写,基于TCP传输层协议进行通信,采用Client端与Server端架构,属于应用层协议。
ESP IDF有提供软件组件使用和设计文档,让同学们运用ESP-IDF的各项功能,本实作即是在ESP IDF范例(Example)中构建HTTP的应用程序。
(2) ESP IDF的HTTP接口:
ESP IDF自带的HTTP接口的使用,ESP HTTP模块提供了完整的API以支持HTTP的应用,其源代码,请同学参考IDF目录下的test_http_client.c,其路径esp-idf\components\esp_http_client\test
(3) HTTP请求:
HTTP请求格式是Client端往Server端发送请求动作,告知Server自己的要求。
(4) HTTP报文:
HTTP报文是HTTP应用程序之间传输的数据块,HTTP报文分为HTTP请求报文和HTTP响应报文。
对于HTTP请求与报文的详细内容,在这裡就先不谈了,已经有其他先进在各论坛做了详细介绍,有兴趣的同学可上网自行学习。
(5) ESP32的HTTP接口介绍:
请同学直接参考ESP IDF的官方指南,那边介绍得更多更详细,相关网址如下:
https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-reference/index.html
4.3 有线以太网转Wi-Fi的AP路由功能:ETH2AP
(1)介绍
ESP32有提供官方例程,这个例程(ETH2AP)功能是以太网+Wi-Fi热点AP(access point),各位同学可在ESP IDF范例(esp-ide/examples/Ethernet)找到,链结网址如下:
https://github.com/espressif/esp-idf/tree/1d7068e/examples/ethernet/eth2ap
(2)如何使用ETH2AP example
- 步骤1:初始化有线以太网以及无线热点模式(Wi-Fi AP mode: Access point)
- 步骤2:有线以太网接入路由/交换器/PCE,开启DHCP server功能
- 步骤3:开启 ESP32的Wi-Fi AP
- 步骤4:无线设备(智能手机)连接 ESP32的Wi-Fi
*详细内容,建议各位同学参考这位先进的文章:
https://blog.csdn.net/Lovely_him/article/details/117754768
4.4 DM9051NP驱动
DM9051NP驱动源代码参考如下:
static void emac_dm9051_task(void *arg) { emac_dm9051_t *emac = (emac_dm9051_t *)arg; uint8_t status = 0; uint8_t *buffer = NULL; uint32_t length = 0; while (1) { // block indefinitely until some task notifies me ulTaskNotifyTake(pdTRUE, portMAX_DELAY); /* 清除中斷狀態 */ dm9051_register_read(emac, DM9051_ISR, &status); dm9051_register_write(emac, DM9051_ISR, status); /* 收到封包 */ if (status & ISR_PR) { do { length = ETH_MAX_PACKET_SIZE; buffer = heap_caps_malloc(length, MALLOC_CAP_DMA); if (!buffer) { ESP_LOGE(TAG, "no mem for receive buffer"); } else if (emac->parent.receive(&emac->parent, buffer, &length) == ESP_OK) { /*將緩衝buffter堆疊在TCP/IP層*/ if (length) { emac->eth->stack_input(emac->eth, buffer, length); } else { free(buffer); } } else { free(buffer); } } while (emac->packets_remain); } } vTaskDelete(NULL); }
*DM9051NP ESP32 完整驱动代码链结:
https://github.com/espressif/esp-idf/blob/master/components/esp_eth/src/esp_eth_mac_dm9051.c
4.5其他笔记
有些同学反应,之前他们从Github论坛下载DM9051NP sample code,执行后会出现连上线后,有断线情况。经过分析试验后,其原因是原本ESP32轮询的间隔时间为50ms,要改成10ms。相关源代码修改建议如下:
- (1) ESP32 轮询的间隔为改成10ms,将ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(50));改成Line 257 vTaskDelay(pdMS_TO_TICKS(10));
- (2)请删除原本example的这一行代码/components/esp_eth/src/esp_eth_mac_dm9051.c
- (3)增加此行代码 "dm9051_register_write(emac, DM9051_ISR, 0xFF);"到 emac_dm9051_task() function的部分。
- (4)增加此行代码 "dm9051_register_write(emac, DM9051_ISR, 0xFF);" 到emac_dm9051_receive() function. 的部分。
5. 有线以太网转Wi-Fi的AP功能演示
5.1 测试架构与需要设备如下
訪问网页---路由---DM9051NP_ESP 12K模块 <<<--->>>手机
注:依照当时的网路环境,同学需要对路由的WAN口以及LAN口做DHCP server配置。
在图5.1.1中,手机经由模块,直接连线到左方的路由,此路由提供DHCP server。
所以,手机向路由得到DHCP动态IP后,再经过路由连线到网际网路。
5.2 ETH2AP功能演示相关讯息
以智能手机透过无线WiFi来连上模块,手机WiFi进行扫描后,可以找到模块的SSID 名称为“+++eth2ap_9051_s2_56_06”,表示模块的Wi-Fi已经准备好。点选后,输入Password : 12345678,成功连线后,手机可透过模块来上网。
我们可以在调适终端上看到ETH-to-AP运行后,相关的设置讯息:
模块上电后看到USB端口打印s2[e2ap]: Ethernet Link Up (如图5.2.1,ETH-to-AP’s message in the terminal)表示以太网路成功连线。模块的Wi-Fi AP配置了MAC地址:00:60:6e:90:56:06,模块的以太网也同样配置了MAC地址00:60:6e:90:56:06。这两个配置的MAC地址,在之前提到的透传功能中,于最底层硬件概念上,有它的意义,而且不会出现在数据包的收送内容中。接著,模块运行ETH2AP功能,进行收送数据包,从DM9051NP网口收到数据包,就原封不动地透过Wi-Fi口转送出去,从Wi-Fi口收到数据包也原封不动由网口转送出去,此为『透传』。
5.3 内网测试(Intranet)
这裡以华硕路由器(产品型号:ASUS RX3041)来进行内网测试,图中手机经由模块直接连线左方的路由器,并且从路由的DHCP server得到DHCP动态IP。路由器在内网这一段区域,它本身是一个网络站点,而且是内网的IP支配者。所以,路由肯定有一个静态IP,由于它是内网的IP支配者(就是DHCP server),所以,会分配动态IP给DHCP客户端(手机)。经查看华硕路由器手册,找到预先配置的静态IP为192.168.6.1。 此内网测试,在手机成功取得动态IP后,手机会出Wi-Fi AP已连上的状态图案。此时,同学开启网页流览器(Browser)来访问前述的192.168.6.1,也就是路由器的web server首页,路由器会要求同学登入帐户与密码,查看路由器手册后,得知帐户是admin,密码也是admin。登入后,手机即可访问路由器的web server首页,完成ETH-to-AP内网测试。
5.4 Iperf测试
以Iperf测试DM9051NP有线网口性能,在SPI clock 20MHz下,获得下面TCP传输数据:
TX | 10.34 mbps |
RX | 9.58 mbps |
Preface
What good thing do we get from EVB?
Pin counts of DM9051NP chip is less than Ethernet PHY (physical layer). As wired Ethernet solution design for ESP32, DM9051NP chip uses SPI interface to communicate with ESP32 and only takes four ESP32 General Purpose IOs (GPIO). In contrast, some Ethernet solution such as Ethernet PHY needs to occupy 10 ESP32 GPIOs when using RMII mode, for MII mode it uses even more GPIO pins, up to 16. Using DM9051, it is possible to have at least 21 ESP32 GPIOs available for other functions in your application. Another benefit of using DM9051 is that placement & layout of DM9051NP on PCB is simpler than an Ethernet PHY. On the software side, developing tool ESP-IDF by Espressif Inc has officially included source code of DM9051 driver in their SDK. Ethernet throughput can reach performance number of TX:10.34mbps / RX:9.58mbps by iperf on the condition that SPI clock is 20MHz from ESP32 CPU.
1. DM9051 ESP32 EVB
The DM9051 ESP32 EVB, which here after is referred to as ”EVB” is an ESP32-S2 core evaluation board with wired Ethernet connectivity by SPI interface, DM9051NP chip. The EVB was designed to help user to quickly develop their ESP32 boards with wired Ethernet connection. By combining a powerful ESP32-S2 and reliable connectivity of DM9051NP, the EVB gives you an good starting point for designing WiFi bridge adapter to transparently pass data between WiFi and wired Ethernet. This allows you to concentrate on your R&D efforts on your ESP32 application.
2. SPI-to-Ethernet chip DM9051NP
Davicom’s DM9051NP is a cost-effective low pin count Ethernet chip with SPI interface, integrated MAC and 10/100M PHY. It is designed to be used as an Ethernet interface to any MCU (Microcontroller) on the market that is equipped with SPI. The DM9051NP enables designers to create stable and reliable Internet-connected embedded applications with minimal PCB space and cost.
Typical Features of DM9051
- - Package:32pin,QFN.
- - IEEE 802.3az Energy Efficient Ethernet (EEE)
- - Supports wake-on-lan (WOL)
- - Supports fiber mode.
- - EMI (Class B) and HBM ESD Rating 8KV
- - Industrial grade: –40℃ to +85℃
3. Example EVB Operation
3.1 EVB location in the network
3.2 Preparation
- 1. Multi-port Router with the feature “DHCP server”.(Brand #:ASUS RX3041)
- 2. EVB.
- 3. Two Ethernet cables
- 4. Micro USB cable or DC adaptor to provide power to EVB
- 5. Smart phone.
- 6. Laptop
3.3 Operating procedure
- Step1:Powers on the router.
- Step2:Connects EVB to router’s LAN port using Ethernet cable. Second cable is for router’s WAN port to ISP provider’s connection to internet
- Step3:Plug Micro USB cable into EVB. (EVB default power source is from USB). For details, please refer to “Selection of power input” on Table 4.1.1
- Step4:Connect to EVB using any WiFi device. Your WiFi devices should be able to see a SSID “+++eth2ap_9051_s2_56_06” on the list of WiFi devices. That is the SSID which belongs to EVB. Please select it and then enter password:12345678, when asked to enter a password.
- Step5:As WiFi link is established between EVB and your WiFi device, The device can visit Google or other web sites using EVB’s WiFi bridge function, instead of 4G LTE or 5G cell networks.
- a. Please turn to get power from DC jack (P1) while USB connector is unable to supply adequate power to EVB.
- b. In order to prevent mutual interference of two voltages generating from ESP32 module and DM9051NP part, please remove J30.
*Note: Each EVB comes with a specific SSID which is shown on the back side of the EVB.
*In this presentation, we’re evaluating EVB in local network environment. For more details, please goes to Chapter 7 Router configuration (WAN & LAN).
Friendly reminder:
The main function of this example application presents the interaction of between WiFi and wire Ethernet. For demonstration to be successful, only one smart phone or WiFi device can communicates with EVB at the same time.
4.Hardware
4.1 Hardware description of EVB
There are two kinds of boards:
Model 1 is DM9051 ESP32 EVB, model 2 is DM9051 ESP 12K EVB. Both of them have the same DM9051 circuit.
4.1.1 Model 1: DM9051 ESP32 EVB
Power Selection:
In the beginning, please select power input from USB connector (J1) or AC adaptor through DC jack (P1).
4.1.1.1. USB power: USB connector (J1), Jumper on EVB:J27 is ON, J28 ON, J29 OFF and J30 OFF.
Please turn to get power from DC jack (P1) while USB port is unable to supply adequate power to EVB.
4.1.1.2. DC power:AC adaptor through DC jack (P1), jumper on EVB: J27 is OFF, J28 OFF, J29 ON and J30 ON.
As for ESP32 application, USB port will be used for software development.
Table 4.1.1
Key part | Description | |
P1 | Power Jack for DC 5V | |
J27 J28 |
Selection of power input: USB connector or DC jack. Default setting is USB connector. J27 ON:5V from USB (J1) for DM9051 J27 OFF: 5V from DC jack (P1) for DM9051 J28 ON: 5V from USB (J1) J28 OFF: 5V from DC jack (P1) | |
J29 J30 |
J29 ON: 5V from DC jack (P1) J29 OFF: 5V from USB (J1) J30 ON: 5V from DC jack (P1) for ESP32-S2 J30 OFF: 5V from USB (J1) for ESP32-S2 | |
JP4 (LED1 LED2) |
The definition to LED1 and LED2 of RJ45 connector (LED Mode1: Default) LED1 : Green (Status: Link / Active) ON: Link ON OFF: Link OFF Flash: Data processing. LED2 :Yellow (Being status: Speed while linking on ) ON: 100 Full duplex OFF: 10 Full duplex | |
U3 | DM9051NP | |
U2 | Core: ESP32-S2 | |
SW1 | Boot button | |
SW2 | Reset button | |
LED4 | GPIO2 function | |
LED5 | Power on | |
J3 | ESP32-S2 I/O Connector | |
J2 | ESP32-S2 I/O Connector | |
J1 | Micro USB Connector for USB 5V and communication. |
4.1.2 Model 2: DM9051 ESP 12K EVB.
Before you begin, please select power input from USB connector (J1) or AC adaptor through DC jack (P1).
4.1.2.1 USB power: USB connector (J1), Jumper on EVB:J27 is ON, J28 Open, J29 OFF and J30 OFF.
4.1.2.2 DC power:AC adaptor through DC jack (P1), Jumper on EVB:J27 is OFF, J28 Open, J29 ON and J30 ON.
As to ESP32 application, USB port will be used for software developing.
Key part | Description |
P1 | Power Jack for DC 5V |
J29 | Selection of power input: USB connector or DC jack. The default setting is USB connector. J29 ON: 5V from DC jack ( P1) J29 OFF: 5V from USB connector |
J28 | Reversed (Do Not use, please leave it open) |
J27 J30 |
J27 ON: 5V from USB connector for DM9051 J27 OFF: 5V from DC jack (P1) for DM9051 J30 ON: 5V from DC jack (P1) for ESP-12K module J30 OFF: 5V from USB connector for ESP-12K module. |
JP4 (LED1 LED2) |
The definition to LED1 and LED2 of RJ45 connector (LED Mode1: Default) LED1 :Green (Link / Active) ON: LINK ON OFF: LINK OFF Flash: Data Access LED2 :Yellow (Being status:Speed while linking on ) ON: 100 Full duplex OFF: 10 Full duplex |
U3 | DM9051NP |
DSP-12K module (Core:ESP32-S2) |
https://docs.ai-thinker.com/_media/esp32/docs/esp-12k_%E8%A7%84%E6%A0%BC%E4%B9%A6_en.pdf |
USB connector | Micro USB Connector for USB 5V and communication. |
4.2 SPI interface of hardware design
SPI interface of DM9051NP is connected with ESP32-S2. The SPI interface also includes register control and interrupt output. The SPI mode between DM9051NP and ESP32-S2 is SPI mode 1.
DM9051NP | ESP32 S2 | |
SPI_CS | Pin17 | Pin13 |
SPI_CLK | Pin18 | Pin16 |
SPI_MOSI | Pin19 | Pin14 |
SPI_MISO | Pin20 | Pin15 |
SPI_INT | Pin24 | Pin18 |
Table 4.2.1
The developing tool ESP IDF shows Ethernet example (DM9051NP module):
ESP32 official routine code: ESP IDF examples/ethernet/ which has introduced hardware design of DM9051NP SPI interface. Please click on URL for reference circuit.
Davicom’s EVB is a redesigned DM9051NP circuit which are shown in Figure 4.2.1 & 4.2.2
5. Software
5.1 ESP32 development tool:ESP IDF
This part is intended to help you set up the software development environment for the hardware based on the ESP32 chip by Espressif. After that, a simple example will show you how to use ESP-IDF (Espressif IoT Development Framework) for menu configuration, then for building and flashing firmware onto an ESP32 board. For more details, please kindly refer to below URL.
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html
5.2 HTTP
In ESP IDF, Espressif provides developers a plenty of API for Application protocol. Code examples for these API sections are offered in the protocol directory of ESP-IDF examples. Here we’ve applied HTTP client to develop WiFi-to-Ethernet (ETH2AP). That is esp_http_client which provides an API for making HTTP request from ESP-IDF program. Those steps to use this API for an HTTP request, please refer to below URL.
https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/index.html
5.3 WiFi-to-wired Ethernet AP:ETH2AP
1) Here is ETH2AP example code in esp-ide/examples/Ethernet/eth2ap. There is a user guide line to teach us how to use ETH2AP. Please refer to below URL.
https://github.com/espressif/esp-idf/tree/1d7068e/examples/ethernet/eth2ap
5.4 DM9051NP driver
Source code of DM9051NP driver is for your reference.
static void emac_dm9051_task(void *arg) { emac_dm9051_t *emac = (emac_dm9051_t *)arg; uint8_t status = 0; uint8_t *buffer = NULL; uint32_t length = 0; while (1) { block indefinitely until some task notifies me ulTaskNotifyTake(pdTRUE, portMAX_DELAY); /* clear interrupt status */ dm9051_register_read(emac, DM9051_ISR, &status); dm9051_register_write(emac, DM9051_ISR, status); /* packet received */ if (status & ISR_PR) { do { length = ETH_MAX_PACKET_SIZE; buffer = heap_caps_malloc(length, MALLOC_CAP_DMA); if (!buffer) { ESP_LOGE(TAG, "no mem for receive buffer"); } else if (emac->parent.receive(&emac->parent, buffer, &length) == ESP_OK) { /* pass the buffer to stack (e.g. TCP/IP layer) */ if (length) { emac->eth->stack_input(emac->eth, buffer, length); } else { free(buffer); } } else { free(buffer); } } while (emac->packets_remain); } } vTaskDelete(NULL); }
*DM9051NP source code in ESP-IDP, please refer to below URL.
https://github.com/espressif/esp-idf/blob/master/components/esp_eth/src/esp_eth_mac_dm9051.c
5.5 Previous DM9051NP driver code workaround
Some versions of DM9051NP driver which may cause connection failure after linking for sometime.
- 1) Amending ESP32 polling interval to be 10ms. Making this line: ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(50)) to be vTaskDelay(pdMS_TO_TICKS(10)).
- 2) Please remove this line: /components/esp_eth/src/esp_eth_mac_dm9051.c in former DM9051NP driver of Ethernet example.
- 3) Adding up this line:dm9051_register_write(emac, DM9051_ISR, 0xFF) to be a part of emac_dm9051_task() function.
- 4) Adding up this line "dm9051_register_write(emac, DM9051_ISR, 0xFF) to be a part of emac_dm9051_receive() function.
6. Iperf test
Using iperf, the performance of DM9051NP with SPI clock 20MHz goes is:
TX | 10.34 mbps |
RX | 9.58 mbps |
7. Router configuration (WAN & LAN)
* Router settings may be different for different routers