程序下载链接:百度网盘 请输入提取码(提取码:k6tz)
【重要说明】
连接方式一(推荐):
电脑有线网卡断开,无线网卡连无线路由器,无线网卡配置成自动获取IP地址。
板子的ENC28J60也连无线路由器,main函数的net_config参数为1(自动获取IP地址)。
连接方式二:
电脑的有线网卡连板子的ENC28J60,无线网卡必须断开。
Windows系统默认情况下不支持多网卡路由,除非自己在命令行里面用命令配置好路由表。如果此时无线网卡连了无线路由器,那么所有的数据包都会从无线网卡出去,不会经过有线网卡,导致电脑ping不通板子。
板子main函数里面net_config参数为0(在程序里面配置IP地址)。
电脑的有线网卡也要手工配置IP地址,不能自动获取IP地址。手工配置的IP地址必须和板子是一个网段。
单片机的串口打印非常重要,是跟踪程序流程的重要手段。
硬件没有调通之前,一定不能舍弃串口!!!!!
一、概述
以太网芯片简介
ENC28J60是一款10Mbps速率的以太网MAC+PHY芯片,和单片机的通信接口为SPI,SPI最高时钟频率为20MHz。
ENC28J60支持半双工和全双工模式,但是不支持自动协商。在支持自动协商的网络环境中,ENC28J60默认的工作模式是半双工模式。
另外,STM32本身有一个ETH外设,这个外设采用的接口是MII或RMII,不是SPI,所以不能连接ENC28J60芯片,这次我们用不到这个ETH外设。
STM32本身的ETH外设相当于MAC,通常要外接一个PHY芯片(如DP83848)。DP83848是一款100Mbps速率的以太网PHY芯片(同时也支持10Mbps速率模式),支持半双工和全双工模式,而且支持自动协商,插在支持自动协商的网络环境中可以协商到100Mbps全双工模式。
ENC28J60芯片内部集成了MAC和PHY,所以不再需要单片机的MAC了,直接用SPI通信就能搞定了。
芯片封装
ENC28J60有四种封装:两列直插(ENC28J60-I/SP)、SOIC型贴片(ENC28J60-I/SO)、SSOP型贴片(ENC28J60/SS)、QFN型贴片(ENC28J60-I/ML)。
请注意SOIC型和SSOP型虽然都是两列贴片, 但是一个尺寸大,一个尺寸小,不要搞错了!
电路图
以下6个引脚要和单片机I/O口相连。前四个接单片机的SPI接口,后面两个为普通I/O口可以任接。
引脚 | I/O口 |
---|---|
SPI1_NSS | PA4 |
SPI1_SCK | PA5 |
SPI1_MISO | PA6 |
SPI1_MOSI | PA7 |
ENC28J60_INT(中断) | PA1 |
ENC28J60_RST(复位) | PB9 |
网口的型号是HanRun HR911105A。请注意网口灯的颜色。LED_LINK要接绿色的灯,灯亮表示插了网线,灯灭表示没有插网线。LED_ACT要接黄色的灯,闪烁一次表示传输了一个数据包,平时灯不亮。
正常情况下,即使单片机没有写任何程序,插上网线后绿灯也要亮,并且收到数据包时黄灯也要闪烁。如果灯不亮,就说明电路有问题。注意一下Y4晶振是一个25MHz的无源晶振,不要接错接成有源的了。
二、建立基于HAL库的Keil工程
去ST官网下载STM32F1的HAL库源码包:STM32CubeF1 - STM32Cube MCU Package for STM32F1 series (HAL, Low-Layer APIs and CMSIS, USB, TCP/IP, File system, RTOS, Graphic - and examples running on ST boards) - STMicroelectronics
下载STM32CubeF1 1.8.0和Patch_CubeF1 1.8.4(补丁)两个压缩包。
下载下来后,解压en.stm32cubef1_v1.8.0.zip,然后再解压en.patch_cubef1_v1-8-4_v1.8.4.zip。
两个压缩包要解压到同一个目录,遇到相同文件名时选择覆盖文件,使两个STM32Cube_FW_F1_V1.8.0文件夹合并在一起:
在一个空白文件夹中建立新的Keil工程,选择STM32F103ZE芯片,在弹出的Manage Run-Time Environment窗口直接点击OK。
在建好的Keil工程里面新建一个STM32F1xx_HAL_Driver文件夹:
将解压出来的STM32Cube_FW_F1_V1.8.0/Drivers/STM32F1xx_HAL_Driver/Inc文件夹复制到工程中的STM32F1xx_HAL_Driver文件夹:
将工程里面的STM32F1xx_HAL_Driver/Inc中凡是以_template后缀结尾的h文件,全部重命名,删掉_template后缀:
stm32_assert_template.h重命名为stm32_assert.h。
stm32f1xx_hal_conf_template.h重命名为stm32f1xx_hal_conf.h。
在工程里面建立STM32F1xx_HAL_Driver/Src文件夹,将STM32Cube_FW_F1_V1.8.0/Drivers/STM32F1xx_HAL_Driver/Src里面需要用到的外设的C文件复制过去。请注意STM32Cube_FW_F1_V1.8.0/Drivers/STM32F1xx_HAL_Driver/Src/Legacy不用复制,这是用于兼容旧版HAL库代码的文件。
复制以下文件到工程的STM32F1xx_HAL_Driver文件夹中:
STM32Cube_FW_F1_V1.8.0/Drivers/CMSIS/Device/ST/STM32F1xx/Include/stm32f1xx.h
STM32Cube_FW_F1_V1.8.0/Drivers/CMSIS/Device/ST/STM32F1xx/Include/stm32f103xe.h(因为STM32F103ZE属于STM32F103xE这一类,是High Density器件)
STM32Cube_FW_F1_V1.8.0/Drivers/CMSIS/Device/ST/STM32F1xx/Include/system_stm32f1xx.h
STM32Cube_FW_F1_V1.8.0/Drivers/CMSIS/Device/ST/STM32F1xx/Source/Templates/system_stm32f1xx.c
STM32Cube_FW_F1_V1.8.0/Drivers/CMSIS/Device/ST/STM32F1xx/Source/Templates/arm/startup_stm32f103xe.s
STM32Cube_FW_F1_V1.8.0/Drivers/CMSIS/Core/Include/cmsis_armcc.h
STM32Cube_FW_F1_V1.8.0/Drivers/CMSIS/Core/Include/cmsis_compiler.h
STM32Cube_FW_F1_V1.8.0/Drivers/CMSIS/Core/Include/cmsis_version.h
STM32Cube_FW_F1_V1.8.0/Drivers/CMSIS/Core/Include/core_cm3.h
在Keil工程中新建一个STM32F1xx_HAL_Driver组,将STM32F1xx_HAL_Driver/Src中的所有c文件,STM32F1xx_HAL_Driver中的所有c文件和s文件添加到组里面:
在工程属性的C/C++选项卡中定义STM32F103xE USE_FULL_ASSERT USE_HAL_DRIVER这三个宏,头文件包含路径添加STM32F1xx_HAL_Driver和STM32F1xx_HAL_Driver/Inc这两个文件夹。
请不要在Target选项卡中勾选Use MicroLIB。
在Source Group 1下添加main.c和common.c两个文件,内容如下。
main.c:
#include <stdio.h>
#include <stm32f1xx.h>
#include "common.h"int main(void)
{HAL_Init();clock_init();usart_init(115200);printf("STM32F103ZE ENC28J60\n");printf("SystemCoreClock=%u\n", SystemCoreClock);while (1){}
}
common.c:
#include <stdio.h>
#include <stdlib.h>
#include <stm32f1xx.h>
#include "common.h"#pragma import(__use_no_semihosting) // 禁用半主机模式 (不然调用printf就会进HardFault)FILE __stdout = {1};
FILE __stderr = {2};
UART_HandleTypeDef huart1;/* main函数返回时执行的函数 */
void _sys_exit(int returncode)
{printf("Exited! returncode=%d\n", returncode);while (1);
}void _ttywrch(int ch)
{if (ch == '\n')HAL_UART_Transmit(&huart1, (uint8_t *)"\r\n", 2, HAL_MAX_DELAY);elseHAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
}/* HAL库参数错误警告 */
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{printf("%s: file %s on line %d\r\n", __FUNCTION__, file, line);abort();
}
#endif/* 配置系统时钟 */
void clock_init(void)
{HAL_StatusTypeDef status;RCC_ClkInitTypeDef clk = {0};RCC_OscInitTypeDef osc = {0};// 启动HSE晶振, 并用PLL倍频9倍osc.OscillatorType = RCC_OSCILLATORTYPE_HSE;osc.HSEState = RCC_HSE_ON;osc.PLL.PLLMUL = RCC_PLL_MUL9;osc.PLL.PLLSource = RCC_PLLSOURCE_HSE;osc.PLL.PLLState = RCC_PLL_ON;status = HAL_RCC_OscConfig(&osc);// 若HSE启动失败, 则改用HSI, 并用PLL倍频8倍if (status != HAL_OK){osc.HSEState = RCC_HSE_OFF;osc.PLL.PLLMUL = RCC_PLL_MUL16;osc.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;HAL_RCC_OscConfig(&osc);}// 设置ADC时钟分频系数__HAL_RCC_ADC_CONFIG(RCC_ADCPCLK2_DIV6);// 将PLL设为系统时钟clk.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;clk.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;clk.AHBCLKDivider = RCC_SYSCLK_DIV1;clk.APB1CLKDivider = RCC_HCLK_DIV2;clk.APB2CLKDivider = RCC_HCLK_DIV1;HAL_RCC_ClockConfig(&clk, FLASH_LATENCY_2);
}/* 显示数据块的十六进制内容 */
void dump_data(const void *data, int len)
{const uint8_t *p = data;while (len--)printf("%02X", *p++);printf("\n");
}/* printf和perror重定向到串口 */
int fputc(int ch, FILE *fp)
{if (fp->handle == 1 || fp->handle == 2){_ttywrch(ch);return ch;}return EOF;
}/* 初始化串口 */
void usart_init(int baud_rate)
{GPIO_InitTypeDef gpio;__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_USART1_CLK_ENABLE();gpio.Mode = GPIO_MODE_AF_PP;gpio.Pin = GPIO_PIN_9;gpio.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA, &gpio);huart1.Instance = USART1;huart1.Init.BaudRate = baud_rate;huart1.Init.Mode = UART_MODE_TX_RX;HAL_UART_Init(&huart1);
}void HardFault_Handler(void)
{printf("Hard Error!\n");while (1);
}void SysTick_Handler(void)
{HAL_IncTick();
}
其中包含的头文件common.h的内容如下:
#ifndef _COMMON_H
#define _COMMON_Hextern UART_HandleTypeDef huart1;struct __FILE
{int handle;
};void clock_init(void);
void dump_data(const void *data, int len);
void usart_init(int baud_rate);#endif
三、编写ENC28J60初始化和收发数据包的函数
ENC28J60.h:
#ifndef _ENC28J60_H
#define _ENC28J60_H/* 公共寄存器 */
#define ENC28J60_EIE 0x1b
#define ENC28J60_EIR 0x1c
#define ENC28J60_ESTAT 0x1d
#define ENC28J60_ECON2 0x1e
#define ENC28J60_ECON1 0x1f
#define ENC28J60_IS_COMMON_REG(n) ((n) >= ENC28J60_EIE && (n) <= ENC28J60_ECON1)/* Bank0寄存器 */
#define ENC28J60_ERDPTL 0x00
#define ENC28J60_ERDPTH 0x01
#define ENC28J60_EWRPTL 0x02
#define ENC28J60_EWRPTH 0x03
#define ENC28J60_ETXSTL 0x04
#define ENC28J60_ETXSTH 0x05
#define ENC28J60_ETXNDL 0x06
#define ENC28J60_ETXNDH 0x07
#define ENC28J60_ERXSTL 0x08
#define ENC28J60_ERXSTH 0x09
#define ENC28J60_ERXNDL 0x0a
#define ENC28J60_ERXNDH 0x0b
#define ENC28J60_ERXRDPTL 0x0c
#define ENC28J60_ERXRDPTH 0x0d
#define ENC28J60_ERXWRPTL 0x0e
#define ENC28J60_ERXWRPTH 0x0f
#define ENC28J60_EDMASTL 0x10
#define ENC28J60_EDMASTH 0x11
#define ENC28J60_EDMANDL 0x12
#define ENC28J60_EDMANDH 0x13
#define ENC28J60_EDMADSTL 0x14
#define ENC28J60_EDMADSTH 0x15
#define ENC28J60_EDMACSL 0x16
#define ENC28J60_EDMACSH 0x17/* Bank1寄存器 */
#define ENC28J60_EHT0 0x20
#define ENC28J60_EHT1 0x21
#define ENC28J60_EHT2 0x22
#define ENC28J60_EHT3 0x23
#define ENC28J60_EHT4 0x24
#define ENC28J60_EHT5 0x25
#define ENC28J60_EHT6 0x26
#define ENC28J60_EHT7 0x27
#define ENC28J60_EPMM0 0x28
#define ENC28J60_EPMM1 0x29
#define ENC28J60_EPMM2 0x2a
#define ENC28J60_EPMM3 0x2b
#define ENC28J60_EPMM4 0x2c
#define ENC28J60_EPMM5 0x2d
#define ENC28J60_EPMM6 0x2e
#define ENC28J60_EPMM7 0x2f
#define ENC28J60_EPMCSL 0x30
#define ENC28J60_EPMCSH 0x31
#define ENC28J60_EPMOL 0x34
#define ENC28J60_EPMOH 0x35
#define ENC28J60_ERXFCON 0x38
#define ENC28J60_EPKTCNT 0x39/* Bank2寄存器 */
#define ENC28J60_MACON1 0x40
#define ENC28J60_MACON3 0x42
#define ENC28J60_MACON4 0x43
#define ENC28J60_MABBIPG 0x44
#define ENC28J60_MAIPGL 0x46
#define ENC28J60_MAIPGH 0x47
#define ENC28J60_MACLCON1 0x48
#define ENC28J60_MACLCON2 0x49
#define ENC28J60_MAMXFLL 0x4a
#define ENC28J60_MAMXFLH 0x4b
#define ENC28J60_MICMD 0x52
#define ENC28J60_MIREGADR 0x54
#define ENC28J60_MIWRL 0x56
#define ENC28J60_MIWRH 0x57
#define ENC28J60_MIRDL 0x58
#define ENC28J60_MIRDH 0x59/* Bank3寄存器 */
#define ENC28J60_MAADR5 0x60
#define ENC28J60_MAADR6 0x61
#define ENC28J60_MAADR3 0x62
#define ENC28J60_MAADR4 0x63
#define ENC28J60_MAADR1 0x64
#define ENC28J60_MAADR2 0x65
#define ENC28J60_EBSTSD 0x66
#define ENC28J60_EBSTCON 0x67
#define ENC28J60_EBSTCSL 0x68
#define ENC28J60_EBSTCSH 0x69
#define ENC28J60_MISTAT 0x6a
#define ENC28J60_EREVID 0x72
#define ENC28J60_ECOCON 0x75
#define ENC28J60_EFLOCON 0x77
#define ENC28J60_EPAUSL 0x78
#define ENC28J60_EPAUSH 0x79/* PHY寄存器 */
#define ENC28J60_PHCON1 0x80
#define ENC28J60_PHSTAT1 0x81
#define ENC28J60_PHID1 0x82
#define ENC28J60_PHID2 0x83
#define ENC28J60_PHCON2 0x90
#define ENC28J60_PHSTAT2 0x91
#define ENC28J60_PHIE 0x92
#define ENC28J60_PHIR 0x93
#define ENC28J60_PHLCON 0x94/* 常用的寄存器位 */
// EIE: 0x1b
#define ENC28J60_EIE_INTIE 0x80
#define ENC28J60_EIE_PKTIE 0x40
#define ENC28J60_EIE_DMAIE 0x20
#define ENC28J60_EIE_LINKIE 0x10
#define ENC28J60_EIE_TXIE 0x08
#define ENC28J60_EIE_TXERIE 0x02
#define ENC28J60_EIE_RXERIE 0x01
// EIR: 0x1c
#define ENC28J60_EIR_PKTIF 0x40
#define ENC28J60_EIR_DMAIF 0x20
#define ENC28J60_EIR_LINKIF 0x10
#define ENC28J60_EIR_TXIF 0x08
#define ENC28J60_EIR_TXERIF 0x02
#define ENC28J60_EIR_RXERIF 0x01
// ESTAT: 0x1d
#define ENC28J60_ESTAT_CLKRDY 0x01
// ECON2: 0x1e
#define ENC28J60_ECON2_PKTDEC 0x40
// ECON1: 0x1f
#define ENC28J60_ECON1_TXRST 0x80
#define ENC28J60_ECON1_RXRST 0x40
#define ENC28J60_ECON1_DMAST 0x20
#define ENC28J60_ECON1_CSUMEN 0x10
#define ENC28J60_ECON1_TXRTS 0x08
#define ENC28J60_ECON1_RXEN 0x04
#define ENC28J60_ECON1_BSEL 0x03
#define ENC28J60_ECON1_BSEL_Pos 0
// ERXFCON: 0x38
#define ENC28J60_ERXFCON_UCEN 0x80
#define ENC28J60_ERXFCON_ANDOR 0x40
#define ENC28J60_ERXFCON_CRCEN 0x20
#define ENC28J60_ERXFCON_PMEN 0x10
#define ENC28J60_ERXFCON_MPEN 0x08
#define ENC28J60_ERXFCON_HTEN 0x04
#define ENC28J60_ERXFCON_MCEN 0x02
#define ENC28J60_ERXFCON_BCEN 0x01
// MACON1: 0x40
#define ENC28J60_MACON1_TXPAUS 0x08
#define ENC28J60_MACON1_RXPAUS 0x04
#define ENC28J60_MACON1_MARXEN 0x01
// MACON3: 0x42
#define ENC28J60_MACON3_PADCFG 0xe0
#define ENC28J60_MACON3_PADCFG_Pos 5
#define ENC28J60_MACON3_TXCRCEN 0x10
#define ENC28J60_MACON3_FRMLNEN 0x02
#define ENC28J60_MACON3_FULDPX 0x01
// MICMD: 0x52
#define ENC28J60_MICMD_MIISCAN 0x02
#define ENC28J60_MICMD_MIIRD 0x01
// MISTAT: 0x6a
#define ENC28J60_MISTAT_BUSY 0x01
// PHCON1: 0x80
#define ENC28J60_PHCON1_PDPXMD 0x0100
// PHSTAT1: 0x81
#define ENC28J60_PHSTAT1_LLSTAT 0x04
#define ENC28J60_PHSTAT1_JBSTAT 0x02
// PHCON2: 0x90
#define ENC28J60_PHCON2_HDLDIS 0x0100
// PHSTAT2: 0x91
#define ENC28J60_PHSTAT2_LSTAT 0x0400
// PHIE: 0x92
#define ENC28J60_PHIE_PLNKIE 0x10
#define ENC28J60_PHIE_PGEIE 0x02
// PHIR: 0x93
#define ENC28J60_PHIR_PLNKIF 0x10
#define ENC28J60_PHIR_PGIF 0x02
// PHLCON: 0x94
#define ENC28J60_PHLCON_LACFG_Pos 8
#define ENC28J60_PHLCON_LBCFG_Pos 4
#define ENC28J60_PHLCON_LFRQ_Pos 2
#define ENC28J60_PHLCON_STRCH 0x02/* 指令集 */
#define ENC28J60_READ_CTRL_REG 0x00 // 读控制寄存器
#define ENC28J60_READ_BUF_MEM 0x3a // 读缓冲区
#define ENC28J60_WRITE_CTRL_REG 0x40 // 写控制寄存器
#define ENC28J60_WRITE_BUF_MEM 0x7a // 写缓冲区
#define ENC28J60_BIT_FIELD_SET 0x80 // 位域置一
#define ENC28J60_BIT_FIELD_CLR 0xa0 // 位域清零
#define ENC28J60_SOFT_RESET 0xff // 系统复位/* 选项 */
#define ENC28J60_MAXPACKETLEN 1518 // 以太网数据包最大长度
#define ENC28J60_RXSTART 0x0000 // 接收缓冲区起始地址
#define ENC28J60_RXEND 0x1a0d // 接收缓冲区结束地址
#define ENC28J60_TXSTART 0x1a0e // 发送缓冲区起始地址, 缓冲区大小为1514+8字节 (发送时不用填写CRC)
#define ENC28J60_TXEND 0x1fff // 发送缓冲区结束地址#define ENC28J60_ClearRegisterBits(addr, bits) ENC28J60_SetRegister(addr, bits, 0)
#define ENC28J60_GetPacketCount() ENC28J60_ReadRegister(ENC28J60_EPKTCNT) // 获取收到的数据包个数
#define ENC28J60_ReadMemory(buffer, size) ENC28J60_Execute(ENC28J60_READ_BUF_MEM, 0, buffer, -(size))
#define ENC28J60_SetRegisterBits(addr, bits) ENC28J60_SetRegister(addr, bits, 1)
#define ENC28J60_WriteMemory(buffer, size) ENC28J60_Execute(ENC28J60_WRITE_BUF_MEM, 0, (void *)(buffer), size)int ENC28J60_BeginReception(void);
int ENC28J60_BeginTransmission(int len);
void ENC28J60_EndReception(void);
void ENC28J60_EndTransmission(void);
void ENC28J60_Execute(uint8_t opcode, uint8_t argument, void *data, int len);
int ENC28J60_GetITStatus(void);
int ENC28J60_GetNextPacketPointer(void);
void ENC28J60_Init(uint8_t mac_addr[6]);
uint16_t ENC28J60_ReadRegister(uint8_t addr);
void ENC28J60_SelectBank(uint8_t bank);
void ENC28J60_SetRegister(uint8_t addr, uint8_t bits, uint8_t value);
void ENC28J60_WriteRegister(uint8_t addr, uint16_t value);#endif
ENC28J60内部缓冲区大小为8KB。前面的部分用来接收数据,后面的部分用来发送数据。正常情况下以太网数据包的最大长度为1518字节(含CRC)。发送数据包时不需要填写CRC(占4字节),但需要预留1字节的控制字节和7字节的Status Vector,加起来发送缓冲区占用的空间就是(1518-4)+(1+7)=1522字节,地址范围为0x1a0e(偶数)~0x1fff(奇数)。其余部分是接收缓冲区,地址范围为0x0000~0x1a0d(奇数)。
ENC28J60.c:
#include <stdio.h>
#include <stdlib.h>
#include <stm32f1xx.h>
#include "ENC28J60.h"#define CS_0 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET)
#define CS_1 HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)
#define INT (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_1) == GPIO_PIN_SET)
#define RST_0 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_RESET)
#define RST_1 HAL_GPIO_WritePin(GPIOB, GPIO_PIN_9, GPIO_PIN_SET)SPI_HandleTypeDef hspi1;
static uint8_t enc28j60_bank;
static uint16_t enc28j60_next_packet;/* 开始接收数据包 */
// 返回收到的数据包的长度
int ENC28J60_BeginReception(void)
{int len;uint8_t info[6];// 设置缓冲区读指针ENC28J60_WriteRegister(ENC28J60_ERDPTL, enc28j60_next_packet & 0xff);ENC28J60_WriteRegister(ENC28J60_ERDPTH, enc28j60_next_packet >> 8);// 读取Next Packet Pointer和Receive Status VectorENC28J60_ReadMemory(info, sizeof(info));enc28j60_next_packet = info[0] | (info[1] << 8); // 下一个数据包的位置len = info[2] | (info[3] << 8); // 数据包的长度len -= 4; // 去掉CRC的长度return len;
}/* 开始发送数据包 */
int ENC28J60_BeginTransmission(int len)
{int capacity = ENC28J60_TXEND - ENC28J60_TXSTART + 1;uint8_t data = 0;// 判断发送缓冲区的容量是否足够if (len <= 0)return -1;else if (len + 8 > capacity) // Control和Status Vector要占8字节{printf("%s: packet is too big!\n", __FUNCTION__);return -1;}// 设置发送缓冲区起始地址ENC28J60_WriteRegister(ENC28J60_ETXSTL, ENC28J60_TXSTART & 0xff);ENC28J60_WriteRegister(ENC28J60_ETXSTH, ENC28J60_TXSTART >> 8);// 设置发送缓冲区结束地址ENC28J60_WriteRegister(ENC28J60_ETXNDL, (ENC28J60_TXSTART + len) & 0xff);ENC28J60_WriteRegister(ENC28J60_ETXNDH, (ENC28J60_TXSTART + len) >> 8);// 设置缓冲区写指针ENC28J60_WriteRegister(ENC28J60_EWRPTL, ENC28J60_TXSTART & 0xff);ENC28J60_WriteRegister(ENC28J60_EWRPTH, ENC28J60_TXSTART >> 8);// 写入控制字节(Control): 控制字节为0x00, 表示使用MACON3寄存器的设置ENC28J60_WriteMemory(&data, 1);return 0;
}/* 结束接收当前数据包 */
void ENC28J60_EndReception(void)
{// 移动接收缓冲区读指针ENC28J60_WriteRegister(ENC28J60_ERXRDPTL, enc28j60_next_packet & 0xff);ENC28J60_WriteRegister(ENC28J60_ERXRDPTH, enc28j60_next_packet >> 8);// 数据包个数减1ENC28J60_SetRegisterBits(ENC28J60_ECON2, ENC28J60_ECON2_PKTDEC);
}/* 结束发送当前数据包 */
void ENC28J60_EndTransmission(void)
{uint32_t ticks;// 请求发送ENC28J60_SetRegisterBits(ENC28J60_ECON1, ENC28J60_ECON1_TXRTS);// 等待发送完毕ticks = HAL_GetTick();while (ENC28J60_ReadRegister(ENC28J60_ECON1) & ENC28J60_ECON1_TXRTS){if (HAL_GetTick() - ticks > 1000){// 超时, 取消发送并复位发送逻辑// 见ENC28J60勘误手册的10. Module: Transmit Logicprintf("%s: timeout!\n", __FUNCTION__);ENC28J60_ClearRegisterBits(ENC28J60_ECON1, ENC28J60_ECON1_TXRTS);ENC28J60_SetRegisterBits(ENC28J60_ECON1, ENC28J60_ECON1_TXRST);ENC28J60_ClearRegisterBits(ENC28J60_ECON1, ENC28J60_ECON1_TXRST);break;}}
}/* 执行SPI命令 */
// len>0: 发送数据; len<0: 接收数据; len=0: 无数据
void ENC28J60_Execute(uint8_t opcode, uint8_t argument, void *data, int len)
{uint8_t byte0 = opcode | (argument & 0x1f);CS_0;HAL_SPI_Transmit(&hspi1, &byte0, 1, HAL_MAX_DELAY);if (data != NULL){if (len > 0)HAL_SPI_Transmit(&hspi1, data, len, HAL_MAX_DELAY);else if (len < 0)HAL_SPI_Receive(&hspi1, data, -len, HAL_MAX_DELAY);}CS_1;
}/* 判断中断引脚的电平是否有效 */
int ENC28J60_GetITStatus(void)
{return !INT;
}/* 获取下一个数据包在缓冲区中的位置 */
int ENC28J60_GetNextPacketPointer(void)
{return enc28j60_next_packet;
}/* 初始化 */
void ENC28J60_Init(uint8_t mac_addr[6])
{uint16_t regs[3];GPIO_InitTypeDef gpio;__HAL_RCC_GPIOA_CLK_ENABLE();__HAL_RCC_GPIOB_CLK_ENABLE();__HAL_RCC_SPI1_CLK_ENABLE();// ENC28J60_INT: PA1, SPI1_MISO: PA6gpio.Mode = GPIO_MODE_INPUT;gpio.Pin = GPIO_PIN_1 | GPIO_PIN_6;gpio.Pull = GPIO_NOPULL;HAL_GPIO_Init(GPIOA, &gpio);// SPI1_NSS: PA4gpio.Mode = GPIO_MODE_OUTPUT_PP;gpio.Pin = GPIO_PIN_4;gpio.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA, &gpio);// SPI1_SCK: PA5, SPI1_MOSI: PA7gpio.Mode = GPIO_MODE_AF_PP;gpio.Pin = GPIO_PIN_5 | GPIO_PIN_7;gpio.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOA, &gpio);// ENC28J60_RST: PB9gpio.Mode = GPIO_MODE_OUTPUT_PP;gpio.Pin = GPIO_PIN_9;gpio.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOB, &gpio);// 初始化SPIhspi1.Instance = SPI1;hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32; // 72MHz/32=2.25MHzhspi1.Init.Mode = SPI_MODE_MASTER;hspi1.Init.NSS = SPI_NSS_SOFT;HAL_SPI_Init(&hspi1);// 复位RST_0;enc28j60_bank = 0;enc28j60_next_packet = ENC28J60_RXSTART;HAL_Delay(10);RST_1;while ((ENC28J60_ReadRegister(ENC28J60_ESTAT) & ENC28J60_ESTAT_CLKRDY) == 0); /* 6.4 Waiting for OST */// 读PHY寄存器, 和手册上标明的默认值对比// 如果不正确, 说明SPI时钟频率太高regs[0] = ENC28J60_ReadRegister(ENC28J60_PHID1);regs[1] = ENC28J60_ReadRegister(ENC28J60_PHID2);regs[2] = ENC28J60_ReadRegister(ENC28J60_PHLCON);printf("ENC28J60 ID: 0x%04x 0x%04x\n", regs[0], regs[1]);if (regs[0] != 0x83 || regs[1] != 0x1400 || (regs[2] & 0xfffe) != 0x3422){printf("Failed to read ENC28J60 registers!\n");printf("SPI frequency may be too high.\n");abort();}/* 6.1 Receive Buffer */// 设置接收缓冲区起始地址ENC28J60_WriteRegister(ENC28J60_ERXSTL, ENC28J60_RXSTART & 0xff);ENC28J60_WriteRegister(ENC28J60_ERXSTH, ENC28J60_RXSTART >> 8);// 设置接收缓冲区读指针ENC28J60_WriteRegister(ENC28J60_ERXRDPTL, ENC28J60_RXSTART & 0xff);ENC28J60_WriteRegister(ENC28J60_ERXRDPTH, ENC28J60_RXSTART >> 8);// 设置接收缓冲区结束地址ENC28J60_WriteRegister(ENC28J60_ERXNDL, ENC28J60_RXEND & 0xff);ENC28J60_WriteRegister(ENC28J60_ERXNDH, ENC28J60_RXEND >> 8);/* 6.3 Receive Filters */// 接收目的MAC地址和本机匹配的单播帧// 丢弃CRC校验不通过的帧// 接收所有多播帧和广播帧ENC28J60_WriteRegister(ENC28J60_ERXFCON, ENC28J60_ERXFCON_UCEN | ENC28J60_ERXFCON_CRCEN | ENC28J60_ERXFCON_MCEN | ENC28J60_ERXFCON_BCEN);/* 6.5.1 打开MAC接收 */// 允许发送暂停控制帧// 当接收到暂停控制帧时停止发送ENC28J60_WriteRegister(ENC28J60_MACON1, ENC28J60_MACON1_TXPAUS | ENC28J60_MACON1_RXPAUS | ENC28J60_MACON1_MARXEN);/* 6.5.2 */// 填充所有VLAN短帧至64字节, 填充所有其他帧至60字节// 发送时自动添加CRC校验值// 不允许收发长度超过MAMXFL值的帧// 收发时自动检查type/length字段的值// MAC配置为全双工模式ENC28J60_WriteRegister(ENC28J60_MACON3, (5 << ENC28J60_MACON3_PADCFG_Pos) | ENC28J60_MACON3_TXCRCEN | ENC28J60_MACON3_FRMLNEN | ENC28J60_MACON3_FULDPX);// PHY配置为全双工模式// 请注意: PDPXMD位的默认值取决于LEDB的接法ENC28J60_WriteRegister(ENC28J60_PHCON1, ENC28J60_PHCON1_PDPXMD);/* 6.5.4 配置最大帧长度 */ENC28J60_WriteRegister(ENC28J60_MAMXFLL, ENC28J60_MAXPACKETLEN & 0xff);ENC28J60_WriteRegister(ENC28J60_MAMXFLH, ENC28J60_MAXPACKETLEN >> 8);/* 6.5.5 配置Back-to-Back Inter-Packet Gap */ENC28J60_WriteRegister(ENC28J60_MABBIPG, 0x15);/* 6.5.6 配置Non-Back-to-Back Inter-Packet Gap */ENC28J60_WriteRegister(ENC28J60_MAIPGL, 0x12);ENC28J60_WriteRegister(ENC28J60_MAIPGH, 0x0c);/* 6.5.9 配置MAC地址 */assert_param((mac_addr[0] & 1) == 0); // MAC地址必须为单播地址ENC28J60_WriteRegister(ENC28J60_MAADR1, mac_addr[0]);ENC28J60_WriteRegister(ENC28J60_MAADR2, mac_addr[1]);ENC28J60_WriteRegister(ENC28J60_MAADR3, mac_addr[2]);ENC28J60_WriteRegister(ENC28J60_MAADR4, mac_addr[3]);ENC28J60_WriteRegister(ENC28J60_MAADR5, mac_addr[4]);ENC28J60_WriteRegister(ENC28J60_MAADR6, mac_addr[5]);/* 其他配置 */// LEDA(绿灯)显示是否插了网线// LEDB(黄灯)显示数据包收发状态// LED闪烁时长为tMSTRCH=70msENC28J60_WriteRegister(ENC28J60_PHLCON, (4 << ENC28J60_PHLCON_LACFG_Pos) | (7 << ENC28J60_PHLCON_LBCFG_Pos) | (1 << ENC28J60_PHLCON_LFRQ_Pos) | ENC28J60_PHLCON_STRCH);// 禁止半双工回环ENC28J60_WriteRegister(ENC28J60_PHCON2, ENC28J60_PHCON2_HDLDIS);// 打开中断: 全局中断, 接收中断, 网线插拔中断, 发送出错中断, 接收出错中断ENC28J60_WriteRegister(ENC28J60_EIE, ENC28J60_EIE_INTIE | ENC28J60_EIE_PKTIE | ENC28J60_EIE_LINKIE | ENC28J60_EIE_TXERIE | ENC28J60_EIE_RXERIE);ENC28J60_WriteRegister(ENC28J60_PHIE, ENC28J60_PHIE_PLNKIE | ENC28J60_PHIE_PGEIE);// 允许将收到的数据包放入bufferENC28J60_WriteRegister(ENC28J60_ECON1, ENC28J60_ECON1_RXEN);
}/* 读寄存器 */
uint16_t ENC28J60_ReadRegister(uint8_t addr)
{uint16_t value = 0;if ((addr & 0x80) == 0){// 读控制寄存器if (!ENC28J60_IS_COMMON_REG(addr))ENC28J60_SelectBank(addr >> 5);ENC28J60_Execute(ENC28J60_READ_CTRL_REG, addr & 0x1f, &value, -1);}else{// 读PHY寄存器ENC28J60_WriteRegister(ENC28J60_MIREGADR, addr & 0x1f);ENC28J60_WriteRegister(ENC28J60_MICMD, ENC28J60_MICMD_MIIRD);while (ENC28J60_ReadRegister(ENC28J60_MISTAT) & ENC28J60_MISTAT_BUSY);ENC28J60_WriteRegister(ENC28J60_MICMD, 0);value = ENC28J60_ReadRegister(ENC28J60_MIRDL);value |= ENC28J60_ReadRegister(ENC28J60_MIRDH) << 8;}return value;
}/* 选择寄存器区域 */
void ENC28J60_SelectBank(uint8_t bank)
{uint16_t value;assert_param(bank < 4);if (enc28j60_bank != bank){value = ENC28J60_ReadRegister(ENC28J60_ECON1);assert_param((value & ENC28J60_ECON1_BSEL) == (enc28j60_bank << ENC28J60_ECON1_BSEL_Pos));value &= ~ENC28J60_ECON1_BSEL;value |= (bank << ENC28J60_ECON1_BSEL_Pos) & ENC28J60_ECON1_BSEL;ENC28J60_WriteRegister(ENC28J60_ECON1, value);}
}/* 将寄存器的某些位置1或清0 */
// 请注意: 只有名称以E开头的寄存器才能使用这个函数
void ENC28J60_SetRegister(uint8_t addr, uint8_t bits, uint8_t value)
{assert_param((addr & 0x80) == 0); // 不允许为PHY寄存器if (!ENC28J60_IS_COMMON_REG(addr))ENC28J60_SelectBank(addr >> 5);if (value == 0)ENC28J60_Execute(ENC28J60_BIT_FIELD_CLR, addr & 0x1f, &bits, 1);elseENC28J60_Execute(ENC28J60_BIT_FIELD_SET, addr & 0x1f, &bits, 1);if (addr == ENC28J60_ECON1){bits = ENC28J60_ReadRegister(ENC28J60_ECON1);enc28j60_bank = (bits & ENC28J60_ECON1_BSEL) >> ENC28J60_ECON1_BSEL_Pos;}
}/* 写寄存器 */
void ENC28J60_WriteRegister(uint8_t addr, uint16_t value)
{if ((addr & 0x80) == 0){// 写控制寄存器if (ENC28J60_IS_COMMON_REG(addr)){if (addr == ENC28J60_ECON1)enc28j60_bank = (value & ENC28J60_ECON1_BSEL) >> ENC28J60_ECON1_BSEL_Pos;}elseENC28J60_SelectBank(addr >> 5);ENC28J60_Execute(ENC28J60_WRITE_CTRL_REG, addr & 0x1f, &value, 1);}else{// 写PHY寄存器ENC28J60_WriteRegister(ENC28J60_MIREGADR, addr & 0x1f);ENC28J60_WriteRegister(ENC28J60_MIWRL, value & 0xff);ENC28J60_WriteRegister(ENC28J60_MIWRH, value >> 8);while (ENC28J60_ReadRegister(ENC28J60_MISTAT) & ENC28J60_MISTAT_BUSY);}
}
在main.c里面测试接收数据包、检测网线插拔:
#include <stdio.h>
#include <stm32f1xx.h>
#include "common.h"
#include "ENC28J60.h"int main(void)
{uint8_t mac[] = {0x00, 0x12, 0x34, 0x56, 0x78, 0x90};uint16_t status;HAL_Init();clock_init();usart_init(115200);printf("STM32F103ZE ENC28J60\n");printf("SystemCoreClock=%u\n", SystemCoreClock);ENC28J60_Init(mac);while (1){if (ENC28J60_GetITStatus()){status = ENC28J60_ReadRegister(ENC28J60_EIR);if (status & ENC28J60_EIR_PKTIF){printf("[Recv] len=%d, ", ENC28J60_BeginReception());printf("next=%d\n", ENC28J60_GetNextPacketPointer());ENC28J60_EndReception();}if (status & ENC28J60_EIR_LINKIF){ENC28J60_ReadRegister(ENC28J60_PHIR);if (ENC28J60_ReadRegister(ENC28J60_PHSTAT2) & ENC28J60_PHSTAT2_LSTAT)printf("Link is up!\n");elseprintf("Link is down!\n");}if (status & ENC28J60_EIR_TXERIF){printf("ENC28J60 Tx error!\n");ENC28J60_ClearRegisterBits(ENC28J60_EIR, ENC28J60_EIR_TXERIF);}if (status & ENC28J60_EIR_RXERIF){printf("ENC28J60 Rx error!\n");ENC28J60_ClearRegisterBits(ENC28J60_EIR, ENC28J60_EIR_RXERIF);}}}
}
运行程序,能正确响应网线插拔, 并且还能看到收到的每个数据包的大小(len),next是下一个数据包在缓冲区中的位置。
四、移植lwip-2.1.3协议栈
添加lwip库文件
在lwip官网下载lwip-2.1.3的压缩包:lwip-2.1.3.zip和contrib-2.1.0.zip。
下载好了之后,在工程里面建立一个lwip-2.1.3文件夹,然后将lwip-2.1.3.zip里面的以下文件复制到工程的lwip-2.1.3文件夹中:
lwip-2.1.3/src/include/*
lwip-2.1.3/src/core/*
lwip-2.1.3/src/netif/ethernet.c
lwip-2.1.3/src/apps/http/*
lwip-2.1.3/src/apps/netbiosns/*
contrib-2.1.0.zip里面需要复制的文件是contrib-2.1.0/examples/ethernetif/ethernetif.c,复制到工程的lwip-2.1.3/src/netif里面去。
现在把工程lwip-2.1.3文件夹里面所有的*.c文件都添加到工程中。注意lwip-2.1.3/src/apps/http目录下只添加fs.c和httpd.c这两个文件,其他的不添加,如下图所示。
将lwip-2.1.3/include添加到头文件包含路径中:
为了避免warning: #2532-D: support for trigraphs is disabled这个警告,应该在Misc Controls栏填入--trigraphs。
编写lwip-2.1.3/include/arch/cc.h,内容如下:
#ifndef LWIP_ARCH_CC_H
#define LWIP_ARCH_CC_H#define LWIP_RAND() ((u32_t)rand())
#define PACK_STRUCT_BEGIN __packed // struct前的__packed#endif
编写lwip-2.1.3/include/lwipopts.h,内容如下:
#ifndef LWIP_LWIPOPTS_H
#define LWIP_LWIPOPTS_H#define NO_SYS 1 // 无操作系统
#define SYS_LIGHTWEIGHT_PROT 0 // 不进行临界区保护#define LWIP_NETCONN 0
#define LWIP_SOCKET 0#define MEM_ALIGNMENT 4 // STM32单片机是32位的单片机, 因此是4字节对齐的
#define MEM_SIZE 10240 // lwip的mem_malloc函数使用的堆内存的大小// 配置TCP
#define TCP_MSS 1500
#define LWIP_TCP_SACK_OUT 1 // 允许选择性确认// 配置DHCP
#define LWIP_DHCP 1
#define LWIP_NETIF_HOSTNAME 1// 配置DNS
#define LWIP_DNS 1// 广播包过滤器
// 如果打开了这个过滤器, 那么就需要在套接字上设置SOF_BROADCAST选项才能收发广播数据包
//#define IP_SOF_BROADCAST 1
//#define IP_SOF_BROADCAST_RECV 1// 配置IPv6
#define LWIP_IPV6 1
#define LWIP_ND6_RDNSS_MAX_DNS_SERVERS LWIP_DNS // 允许SLAAC获取DNS服务器的地址#endif
为了避免以下两个编译错误,应该在common.c中实现fflush函数,这个函数是由LWIP_ASSERT调用的。
.\Objects\enc28j60.axf: Error: L6200E: Symbol __stdout multiply defined (by stdio_streams.o and common.o).
.\Objects\enc28j60.axf: Error: L6200E: Symbol __stderr multiply defined (by stdio_streams.o and common.o).
/* 刷新输出缓冲区 */
// LWIP_ASSERT会调用此函数
int fflush(FILE *stream)
{return 0;
}
另外还需要实现sys_now函数,否则会出现下面的编译错误:
.\Objects\enc28j60.axf: Error: L6218E: Undefined symbol sys_now (referred from timeouts.o).
/* 获取系统时间毫秒数 (lwip协议栈要求实现的函数) */
// 该函数必须保证: 除非定时器溢出, 否则后获取的时间必须大于先获取的时间
uint32_t sys_now(void)
{return HAL_GetTick();
}
实现后在common.h中声明一下:uint32_t sys_now(void);
与ENC28J60网络接口绑定
网口初始化函数
将enc28j60和lwip绑定是由lwip-2.1.3/netif/ethernetif.c完成的。
打开这个文件后,首先要将
#if 0 /* don't build, this is only a skeleton, see previous comment */
改为
#if 1
然后修改low_level_init函数,在里面指定网卡MAC地址00:12:34:56:78:90,然后调用刚才我们编写的ENC28J60_Init初始化函数。请注意MAC地址的第一个字节必须为偶数,因为如果是奇数的话这个MAC地址就是一个多播地址,这是不允许的!
netif->flags要去掉NETIF_FLAG_LINK_UP选项,使网口的初始状态为未连接。增加NETIF_FLAG_MLD6选项,这样电脑才能ping通板子的IPv6地址。
netif->mtu=1500指的是网络层数据的最大长度,而前面说的1518字节是以太网数据包的最大大小。以太网数据包=目的地址(6字节)+源地址(6字节)+类型(2字节)+网络层数据(n字节)+CRC校验码(4字节),18+n≤1518,所以n≤1500。
// 包含头文件
#include <netif/ethernetif.h>
#include "../ENC28J60.h"static void
low_level_init(struct netif *netif)
{//struct ethernetif *ethernetif = netif->state;/* set MAC hardware address length */netif->hwaddr_len = ETHARP_HWADDR_LEN;/* set MAC hardware address */// 指定网卡MAC地址netif->hwaddr[0] = 0x00;netif->hwaddr[1] = 0x12;netif->hwaddr[2] = 0x34;netif->hwaddr[3] = 0x56;netif->hwaddr[4] = 0x78;netif->hwaddr[5] = 0x90;printf("MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n", netif->hwaddr[0], netif->hwaddr[1], netif->hwaddr[2], netif->hwaddr[3], netif->hwaddr[4], netif->hwaddr[5]);/* maximum transfer unit */netif->mtu = 1500;/* device capabilities *//* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP; // 网卡默认状态为: 未连接netif->flags |= NETIF_FLAG_MLD6; // 启用IPv6多播 (必须要启用这个选项, 才能ping通IPv6地址)#if LWIP_IPV6 && LWIP_IPV6_MLD/** For hardware/netifs that implement MAC filtering.* All-nodes link-local is handled by default, so we must let the hardware know* to allow multicast packets in.* Should set mld_mac_filter previously. */if (netif->mld_mac_filter != NULL) {ip6_addr_t ip6_allnodes_ll;ip6_addr_set_allnodes_linklocal(&ip6_allnodes_ll);netif->mld_mac_filter(netif, &ip6_allnodes_ll, NETIF_ADD_MAC_FILTER);}
#endif /* LWIP_IPV6 && LWIP_IPV6_MLD *//* Do whatever else is needed to initialize interface. */ENC28J60_Init(netif->hwaddr); // 初始化网口
}
新建lwip-2.1.3/include/netif/ethernetif.h头文件,内容如下:
#ifndef ETHERNETIF_H
#define ETHERNETIF_H#ifndef _BV
#define _BV(n) (1ull << (n))
#endiferr_t ethernetif_init(struct netif *netif);
void ethernetif_input(struct netif *netif);#endif
里面声明了ethernetif_init和ethernetif_input函数,这两个函数将会在main.c中使用,所以必须在头文件中声明。
回到刚才的ethernetif.c文件,我们需要将下面这句话的static关键字去掉,文件里面一共有两处:
/* Forward declarations. */
static void ethernetif_input(struct netif *netif);
static void
ethernetif_input(struct netif *netif)
文件最底部的ethernetif_init函数里面有一句netif->hostname = "lwip",这个设置的是板子在路由器管理页面中的显示名称。可以设置成STM32F103ZE_ENC28J60,或者其他自己喜欢的名字。
#if LWIP_NETIF_HOSTNAME/* Initialize interface hostname */netif->hostname = "STM32F103ZE_ENC28J60";
#endif /* LWIP_NETIF_HOSTNAME */
数据包发送函数
先调用ENC28J60_BeginTransmission函数指定要发送的数据包的大小,如果数据包太大发送不了的话,函数的返回值ret就会等于-1,然后下方return就要改成ERR_MEM。
如果ret=0那么就用ENC28J60_WriteMemory函数将p里面的数据拷贝到ENC28J60的发送缓冲区,然后用ENC28J60_EndTransmission函数发送出去。
static err_t
low_level_output(struct netif *netif, struct pbuf *p)
{//struct ethernetif *ethernetif = netif->state;struct pbuf *q;int ret;#if ETH_PAD_SIZEpbuf_remove_header(p, ETH_PAD_SIZE); /* drop the padding word */
#endifprintf("[Send] len=%u\n", p->tot_len);ret = ENC28J60_BeginTransmission(p->tot_len);if (ret == 0) {for (q = p; q != NULL; q = q->next) {/* Send the data from the pbuf to the interface, one pbuf at atime. The size of the data in each pbuf is kept in the ->lenvariable. */ENC28J60_WriteMemory(q->payload, q->len);}ENC28J60_EndTransmission();MIB2_STATS_NETIF_ADD(netif, ifoutoctets, p->tot_len);if (((u8_t *)p->payload)[0] & 1) {/* broadcast or multicast packet*/MIB2_STATS_NETIF_INC(netif, ifoutnucastpkts);} else {/* unicast packet */MIB2_STATS_NETIF_INC(netif, ifoutucastpkts);}/* increase ifoutdiscards or ifouterrors on error */}#if ETH_PAD_SIZEpbuf_add_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endifLINK_STATS_INC(link.xmit);return (ret == 0) ? ERR_OK : ERR_MEM;
}
数据包接收函数
先调用ENC28J60_BeginReception函数获取收到的数据包的大小,然后开辟内存,用ENC28J60_ReadMemory函数从ENC28J60的接收缓冲区读取数据,读完之后调用ENC28J60_EndReception函数结束读取。
如果内存开辟失败,则不读取数据,直接调用ENC28J60_EndReception函数结束。
static struct pbuf *
low_level_input(struct netif *netif)
{//struct ethernetif *ethernetif = netif->state;struct pbuf *p, *q;u16_t len;int next;/* Obtain the size of the packet and put it into the "len"variable. */len = ENC28J60_BeginReception();next = ENC28J60_GetNextPacketPointer();printf("[Recv] len=%u, next=%d\n", len, next);#if ETH_PAD_SIZElen += ETH_PAD_SIZE; /* allow room for Ethernet padding */
#endif/* We allocate a pbuf chain of pbufs from the pool. */p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL);if (p != NULL) {#if ETH_PAD_SIZEpbuf_remove_header(p, ETH_PAD_SIZE); /* drop the padding word */
#endif/* We iterate over the pbuf chain until we have read the entire* packet into the pbuf. */for (q = p; q != NULL; q = q->next) {/* Read enough bytes to fill this pbuf in the chain. The* available data in the pbuf is given by the q->len* variable.* This does not necessarily have to be a memcpy, you can also preallocate* pbufs for a DMA-enabled MAC and after receiving truncate it to the* actually received size. In this case, ensure the tot_len member of the* pbuf is the sum of the chained pbuf len members.*/ENC28J60_ReadMemory(q->payload, q->len);}ENC28J60_EndReception();MIB2_STATS_NETIF_ADD(netif, ifinoctets, p->tot_len);if (((u8_t *)p->payload)[0] & 1) {/* broadcast or multicast packet*/MIB2_STATS_NETIF_INC(netif, ifinnucastpkts);} else {/* unicast packet*/MIB2_STATS_NETIF_INC(netif, ifinucastpkts);}
#if ETH_PAD_SIZEpbuf_add_header(p, ETH_PAD_SIZE); /* reclaim the padding word */
#endifLINK_STATS_INC(link.recv);} else {ENC28J60_EndReception(); // drop packetLINK_STATS_INC(link.memerr);LINK_STATS_INC(link.drop);MIB2_STATS_NETIF_INC(netif, ifindiscards);}return p;
}
修改主函数
现在我们可以修改main.c里面的main函数,初始化lwip,设置板子IP地址了。
#include <lwip/apps/httpd.h>
#include <lwip/apps/netbiosns.h>
#include <lwip/dhcp.h>
#include <lwip/dns.h>
#include <lwip/init.h>
#include <lwip/netif.h>
#include <lwip/timeouts.h>
#include <netif/ethernetif.h>
#include <stdio.h>
#include <stm32f1xx.h>
#include "common.h"
#include "ENC28J60.h"static struct netif netif_enc28j60;/* 显示板子获取到的IP地址 */
static void display_ip(void)
{const ip_addr_t *addr;static uint8_t ip_displayed = 0;static uint8_t ip6_displayed = 0;int i, ip_present;int dns = 0;if (netif_dhcp_data(&netif_enc28j60) == NULL)ip_present = 1; // 使用静态IP地址else if (dhcp_supplied_address(&netif_enc28j60))ip_present = 2; // 使用DHCP获得IP地址, 且已成功获取到IP地址elseip_present = 0; // 使用DHCP获得IP地址, 且还没有获取到IP地址// 显示IPv4地址if (ip_present){if (ip_displayed == 0){ip_displayed = 1;if (ip_present == 2)printf("DHCP supplied address!\n");printf("IP address: %s\n", ipaddr_ntoa(&netif_enc28j60.ip_addr));printf("Subnet mask: %s\n", ipaddr_ntoa(&netif_enc28j60.netmask));printf("Default gateway: %s\n", ipaddr_ntoa(&netif_enc28j60.gw));dns = 1;}}elseip_displayed = 0;// 显示IPv6地址for (i = 1; i < LWIP_IPV6_NUM_ADDRESSES; i++) // 0号地址是本地链路地址, 不需要显示{if (ip6_addr_isvalid(netif_ip6_addr_state(&netif_enc28j60, i))){if ((ip6_displayed & _BV(i)) == 0){ip6_displayed |= _BV(i);printf("IPv6 address %d: %s\n", i, ipaddr_ntoa(netif_ip_addr6(&netif_enc28j60, i)));dns = 1;}}elseip6_displayed &= ~_BV(i);}// 显示DNS服务器地址// 在lwip中, IPv4 DHCP和IPv6 SLAAC获取到的DNS地址会互相覆盖if (dns){addr = dns_getserver(0);if (ip_addr_isany(addr))return;printf("DNS Server: %s", ipaddr_ntoa(addr));addr = dns_getserver(1);if (!ip_addr_isany(addr))printf(" %s", ipaddr_ntoa(addr));printf("\n");}
}/* 配置板子的IP地址 */
// use_dhcp=0: 静态配置
// use_dhcp=1: 自动从路由器获取
static void net_config(int use_dhcp)
{ip4_addr_t ipaddr, netmask, gw;// 将ENC28J60网卡添加到lwipif (use_dhcp)netif_add_noaddr(&netif_enc28j60, NULL, ethernetif_init, netif_input); // 添加网卡, 但不配置IP地址else{IP4_ADDR(&ipaddr, 192, 168, 1, 20); // IP地址IP4_ADDR(&netmask, 255, 255, 255, 0); // 子网掩码IP4_ADDR(&gw, 192, 168, 1, 1); // 默认网关netif_add(&netif_enc28j60, &ipaddr, &netmask, &gw, NULL, ethernetif_init, netif_input); // 添加网卡}netif_set_default(&netif_enc28j60); // 设为默认网卡netif_set_up(&netif_enc28j60); // 启用网卡// 启动DHCP服务器if (use_dhcp)dhcp_start(&netif_enc28j60);// 创建IPv6本地链路地址, 并从路由器获取公网IPv6地址netif_create_ip6_linklocal_address(&netif_enc28j60, 1);printf("IPv6 link-local address: %s\n", ipaddr_ntoa(netif_ip_addr6(&netif_enc28j60, 0)));netif_set_ip6_autoconfig_enabled((struct netif *)(uintptr_t)&netif_enc28j60, 1);
}int main(void)
{uint16_t status;HAL_Init();clock_init();usart_init(115200);printf("STM32F103ZE ENC28J60\n");printf("SystemCoreClock=%u\n", SystemCoreClock);lwip_init();net_config(1);httpd_init(); // 启动网页服务器netbiosns_init();netbiosns_set_name("STM32F103ZE"); // 设置设备名while (1){if (ENC28J60_GetITStatus()){status = ENC28J60_ReadRegister(ENC28J60_EIR);if (status & ENC28J60_EIR_LINKIF){// 网络连接状态发生变化, 通知lwipENC28J60_ReadRegister(ENC28J60_PHIR);if (ENC28J60_ReadRegister(ENC28J60_PHSTAT2) & ENC28J60_PHSTAT2_LSTAT){// 已插入网线printf("Link is up!\n");netif_set_link_up(&netif_enc28j60);}else{// 已拔出网线printf("Link is down!\n");netif_set_link_down(&netif_enc28j60);}}if (status & ENC28J60_EIR_PKTIF){// 处理收到的数据包while (ENC28J60_GetPacketCount() != 0)ethernetif_input(&netif_enc28j60);}if (status & ENC28J60_EIR_TXERIF){// 发送出错printf("ENC28J60 Tx error!\n");ENC28J60_ClearRegisterBits(ENC28J60_EIR, ENC28J60_EIR_TXERIF);}if (status & ENC28J60_EIR_RXERIF){// 接收出错 (通常是因为接收缓冲区不够了)printf("ENC28J60 Rx error!\n");ENC28J60_ClearRegisterBits(ENC28J60_EIR, ENC28J60_EIR_RXERIF);}}// 如果获取到了IP地址就显示display_ip();// lwip内部定时处理sys_check_timeouts();}
}
一个struct netif变量代表一个网络接口。注意一下netif_set_up/down和netif_set_link_up/down的区别,netif_set_up/down指的是启用或禁用网络接口,而netif_set_link_up/down指的是通知lwip网络连接已连上或断开。
sys_check_timeouts函数是lwip内部的定时处理函数,只要sys_now()函数正常工作,后调用的返回值永远大于先调用的返回值(除非32位数溢出),就没有问题。
裸机环境下,初始化lwip的函数是lwip_init()。netif_add()添加网卡时最后一个参数填的是netif_input,也可以填ethernet_input,是一样的。netif_input只是多了一个网络接口类型的判断,是以太网网络接口的话最终还是会调用ethernet_input,否则如果是PPP点对点接口则调用的是ip_input。
netif_add的倒数第二个参数是ethernetif.c里面定义的网口初始化函数ethernetif_init。倒数第三个参数是给网口初始化函数传递的自定义参数,可传递任意数据,在网口初始化函数中可通过netif->state读取到。
如果是带操作系统的环境下,初始化lwip的函数就必须换成tcpip_init(),netif_add()的最后一个参数必须换成tcpip_input。所有的raw API函数(包括像netif_add这样的函数)在非tcpip_thread线程外使用,使用前都必须调用LOCK_TCPIP_CORE(),使用后必须调用UNLOCK_TCPIP_CORE()。
我们之前在ethernetif.c的low_level_init函数中去掉了netif->flags的NETIF_FLAG_LINK_UP选项,所以网卡默认状态是未连接状态。
netif_add添加网卡后,只要netif_set_up启用了网卡,就可以马上调用dhcp_start启动DHCP服务器,不需要等到连上网络后netif_set_link_up再启动DHCP。
此外,netif_set_link_up函数内部会调用dhcp_network_changed,进而调用dhcp_reboot,所以网络断开后再连接,DHCP会自动重新获取IP地址,不需要自己再去调用dhcp_start。
需要注意的是,dhcp_start只是启动DHCP服务器,函数返回时,IP地址还没有获取到。在裸机环境下,用dhcp_supplied_address判断是否获取到IP地址,应该在main函数的while(1)主循环里面进行。dhcp_start后马上用dhcp_supplied_address判断,判断结果肯定是没有获取到。
五、最终效果
板子能正常收发数据,并从路由器获取到IPv4地址、IPv6地址和DNS服务器的地址:
电脑能ping通板子的IPv4地址和设备名:
电脑能ping通板子的IPv6本地链路地址和公网地址:
电脑能用浏览器访问板子上的网页服务器,并且一直按住F5刷新也没问题:
网线拔出后又重新插上,板子也能自动识别到,DHCP也能重新获取IP地址。
获取到的DNS服务器的地址也有可能是IPv6地址。(想要禁止获取IPv6 DNS地址,只允许获取IPv4 DNS地址的话就需要在lwipopts.h里面去掉LWIP_ND6_RDNSS_MAX_DNS_SERVERS选项)
在路由器管理页面中看到的设备名是ethernetif.c的ethernetif_init函数的netif->hostname指定的名称: