STM32F103VET6基于ENC28J60移植LWIP1.4.1(标准库,无RTOS)

目录

  • 环境
  • 引脚连接
  • 1.准备LWIP
  • 2.新建arch
  • 3.网卡驱动
  • 4.新建分组
  • 5.项目头文件路径
  • 6.LWIIP头文件编写
  • 7.ethernetif.c
    • void low_level_init(struct netif *netif)
    • err_t low_level_output(struct netif *netif, struct pbuf *p)`
    • struct pbuf *low_level_input(struct netif *netif)
    • void ethernetif_input(struct netif *netif)
  • 8.sys_now
  • 9.初始化函数
  • 10.主函数
  • 注意
  • 测试
  • 结果
  • 源码

本文用于记录STM32F103VET6基于ENC28J60移植LWIP1.4.1。

有了ENC28J60与LWIP之后,网络5层里除了应用层都完成了。本文使用ping测试移植结果,不进行应用层开发。

按照本文移植成功的话,使用网线连接网卡与电脑,将电脑以太网IP设置在与网卡IP同一网段下,电脑应该可以ping通单片机。

网上要么是用μCOS3,要么STM32F4,要么不用这个网卡。笔者自学被搞得哇哇大叫。因此写本文记录过程。

笔者仅做移植记录,详细原理不做讲解。

环境

  1. STM32F103VET6(野火指南者)
  2. LWIP 1.4.1
  3. 网卡为ENC28J60
  4. 一根网线

引脚连接

PB1 — INT
GND — GND
PA4 — CS
PA5 — SCL
PA6 — SO
PA7 — SI
PE5 — RST
5V — VCC

1.准备LWIP

准备一个标准库项目,下载LWIP1.4.1源码。
源码下载好后,解压,复制其中的src目录,粘贴到项目路径下,更改路径名为LWIP。
点我下载
在这里插入图片描述

2.新建arch

在LWIP文件夹下,新建文件夹arch,并在其中新建三个文件:cc.h、lwipopts.h、perf.h备用。
在这里插入图片描述

在这里插入图片描述

3.网卡驱动

本文基于网卡ENC28J60。它的驱动代码我参考了 这位大佬的博客
原来的驱动是HAL库编写的,我跑起来有问题。我把它改成标准库(其实也就改了SPI读写一字节那一块)却能直接跑了。

//enc28j60.c
#include "enc28j60.h"
static void ENC28J60_GPIO_Init(){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOE,ENABLE);GPIO_InitTypeDef gpio_init;gpio_init.GPIO_Mode = GPIO_Mode_Out_PP;gpio_init.GPIO_Speed = GPIO_Speed_50MHz;gpio_init.GPIO_Pin = ENC28J60_CS_PIN;GPIO_Init(ENC28J60_CS_PORT,&gpio_init);gpio_init.GPIO_Pin = GPIO_Pin_5;GPIO_Init(GPIOE,&gpio_init);gpio_init.GPIO_Pin = GPIO_Pin_0;GPIO_Init(GPIOB,&gpio_init);gpio_init.GPIO_Pin = GPIO_Pin_5;gpio_init.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_Init(GPIOA,&gpio_init);gpio_init.GPIO_Pin = GPIO_Pin_6;GPIO_Init(GPIOA,&gpio_init);gpio_init.GPIO_Pin = GPIO_Pin_7;GPIO_Init(GPIOA,&gpio_init);GPIO_SetBits(ENC28J60_CS_PORT,ENC28J60_CS_PIN);GPIO_SetBits(GPIOB,GPIO_Pin_0);
}static void ENC28J60_SPI1_Init(){RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);SPI_InitTypeDef spi_init;spi_init.SPI_NSS = SPI_NSS_Soft;spi_init.SPI_Direction = SPI_Direction_2Lines_FullDuplex;spi_init.SPI_Mode = SPI_Mode_Master;spi_init.SPI_DataSize = SPI_DataSize_8b;spi_init.SPI_CPOL = SPI_CPOL_Low;spi_init.SPI_CPHA = SPI_CPHA_1Edge;spi_init.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;spi_init.SPI_FirstBit = SPI_FirstBit_MSB;spi_init.SPI_CRCPolynomial = 7;SPI_Init(SPI1,&spi_init);SPI_Cmd(SPI1,ENABLE);
}static void ENC28J60_EXTI_Init(){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO,ENABLE);NVIC_InitTypeDef nvic_init;nvic_init.NVIC_IRQChannel = EXTI1_IRQn;nvic_init.NVIC_IRQChannelPreemptionPriority = 1;nvic_init.NVIC_IRQChannelSubPriority = 1;nvic_init.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&nvic_init);GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource1);EXTI_InitTypeDef exti_init;exti_init.EXTI_Line = EXTI_Line1;exti_init.EXTI_Mode = EXTI_Mode_Interrupt;exti_init.EXTI_Trigger = EXTI_Trigger_Falling;exti_init.EXTI_LineCmd = ENABLE;EXTI_Init(&exti_init);GPIO_InitTypeDef gpio_init;gpio_init.GPIO_Mode = GPIO_Mode_IPU;gpio_init.GPIO_Pin = GPIO_Pin_1;gpio_init.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB,&gpio_init);
}static void ENC28J60_Reset(){GPIO_ResetBits(GPIOE,GPIO_Pin_5);uint16_t t = 0x1fff;while(t--);GPIO_SetBits(GPIOE,GPIO_Pin_5);
}static unsigned char W25Q64_SendByte(uint8_t byte){while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) == RESET);SPI_I2S_SendData(SPI1,byte);while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) == RESET);return SPI_I2S_ReceiveData(SPI1);
}static unsigned char	SPI1_ReadWrite(unsigned char writedat){unsigned char r = W25Q64_SendByte(writedat);return r;
}void Enc28j60_Init(void)
{ENC28J60_GPIO_Init();ENC28J60_SPI1_Init();ENC28J60_EXTI_Init();ENC28J60_Reset();
}static unsigned char Enc28j60Bank;
static unsigned int NextPacketPtr;unsigned char enc28j60ReadOp(unsigned char op, unsigned char address)
{unsigned char dat = 0;ENC28J60_CSL();dat = op | (address & ADDR_MASK);SPI1_ReadWrite(dat);dat = SPI1_ReadWrite(0xFF);// do dummy read if needed (for mac and mii, see datasheet page 29)if(address & 0x80){dat = SPI1_ReadWrite(0xFF);}// release CSENC28J60_CSH();return dat;
}void enc28j60WriteOp(unsigned char op, unsigned char address, unsigned char data)
{unsigned char dat = 0;ENC28J60_CSL();// issue write commanddat = op | (address & ADDR_MASK);SPI1_ReadWrite(dat);// write datadat = data;SPI1_ReadWrite(dat);ENC28J60_CSH();
}void enc28j60ReadBuffer(unsigned int len, unsigned char* data)
{ENC28J60_CSL();// issue read commandSPI1_ReadWrite(ENC28J60_READ_BUF_MEM);while(len){len--;// read data*data = (unsigned char)SPI1_ReadWrite(0);data++;}*data='\0';ENC28J60_CSH();
}void enc28j60WriteBuffer(unsigned int len, unsigned char* data)
{ENC28J60_CSL();// issue write commandSPI1_ReadWrite(ENC28J60_WRITE_BUF_MEM);while(len){len--;SPI1_ReadWrite(*data);data++;}ENC28J60_CSH();
}void enc28j60SetBank(unsigned char address)
{// set the bank (if needed)if((address & BANK_MASK) != Enc28j60Bank){// set the bankenc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, (ECON1_BSEL1|ECON1_BSEL0));enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, (address & BANK_MASK)>>5);Enc28j60Bank = (address & BANK_MASK);}
}unsigned char enc28j60Read(unsigned char address)
{// set the bankenc28j60SetBank(address);// do the readreturn enc28j60ReadOp(ENC28J60_READ_CTRL_REG, address);
}void enc28j60Write(unsigned char address, unsigned char data)
{// set the bankenc28j60SetBank(address);// do the writeenc28j60WriteOp(ENC28J60_WRITE_CTRL_REG, address, data);
}void enc28j60PhyWrite(unsigned char address, unsigned int data)
{// set the PHY register addressenc28j60Write(MIREGADR, address);// write the PHY dataenc28j60Write(MIWRL, data);enc28j60Write(MIWRH, data>>8);// wait until the PHY write completeswhile(enc28j60Read(MISTAT) & MISTAT_BUSY){//Del_10us(1);//_nop_();}
}void enc28j60clkout(unsigned char clk)
{//setup clkout: 2 is 12.5MHz:enc28j60Write(ECOCON, clk & 0x7);
}void enc28j60Init(unsigned char* macaddr)
{   ENC28J60_CSH();	      // perform system resetenc28j60WriteOp(ENC28J60_SOFT_RESET, 0, ENC28J60_SOFT_RESET);// check CLKRDY bit to see if reset is complete// The CLKRDY does not work. See Rev. B4 Silicon Errata point. Just wait.//while(!(enc28j60Read(ESTAT) & ESTAT_CLKRDY));// do bank 0 stuff// initialize receive buffer// 16-bit transfers, must write low byte first// set receive buffer start address	   NextPacketPtr = RXSTART_INIT;// Rx start    enc28j60Write(ERXSTL, RXSTART_INIT&0xFF);	 enc28j60Write(ERXSTH, RXSTART_INIT>>8);// set receive pointer address     enc28j60Write(ERXRDPTL, RXSTART_INIT&0xFF);enc28j60Write(ERXRDPTH, RXSTART_INIT>>8);// RX endenc28j60Write(ERXNDL, RXSTOP_INIT&0xFF);enc28j60Write(ERXNDH, RXSTOP_INIT>>8);// TX start	  1500enc28j60Write(ETXSTL, TXSTART_INIT&0xFF);enc28j60Write(ETXSTH, TXSTART_INIT>>8);// TX endenc28j60Write(ETXNDL, TXSTOP_INIT&0xFF);enc28j60Write(ETXNDH, TXSTOP_INIT>>8);// do bank 1 stuff, packet filter:// For broadcast packets we allow only ARP packtets// All other packets should be unicast only for our mac (MAADR)//// The pattern to match on is therefore// Type     ETH.DST// ARP      BROADCAST// 06 08 -- ff ff ff ff ff ff -> ip checksum for theses bytes=f7f9// in binary these poitions are:11 0000 0011 1111// This is hex 303F->EPMM0=0x3f,EPMM1=0x30enc28j60Write(ERXFCON, ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN);enc28j60Write(EPMM0, 0x3f);enc28j60Write(EPMM1, 0x30);enc28j60Write(EPMCSL, 0xf9);enc28j60Write(EPMCSH, 0xf7);    enc28j60Write(MACON1, MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS);// bring MAC out of reset enc28j60Write(MACON2, 0x00);enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, MACON3, MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN|MACON3_FULDPX);// set inter-frame gap (non-back-to-back)enc28j60Write(MAIPGL, 0x12);enc28j60Write(MAIPGH, 0x0C);// set inter-frame gap (back-to-back)enc28j60Write(MABBIPG, 0x15);// Set the maximum packet size which the controller will accept// Do not send packets longer than MAX_FRAMELEN:enc28j60Write(MAMXFLL, MAX_FRAMELEN&0xFF);	enc28j60Write(MAMXFLH, MAX_FRAMELEN>>8);// do bank 3 stuff// write MAC address// NOTE: MAC address in ENC28J60 is byte-backwardenc28j60Write(MAADR5, macaddr[0]);	enc28j60Write(MAADR4, macaddr[1]);enc28j60Write(MAADR3, macaddr[2]);enc28j60Write(MAADR2, macaddr[3]);enc28j60Write(MAADR1, macaddr[4]);enc28j60Write(MAADR0, macaddr[5]);//配置PHY为全双工  LEDB为拉电流enc28j60PhyWrite(PHCON1, PHCON1_PDPXMD);    // no loopback of transmitted framesenc28j60PhyWrite(PHCON2, PHCON2_HDLDIS);// switch to bank 0    enc28j60SetBank(ECON1);// enable interrutpsenc28j60WriteOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE);// enable packet receptionenc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_RXEN);
}// read the revision of the chip:
unsigned char enc28j60getrev(void)
{//在EREVID 内也存储了版本信息。 EREVID 是一个只读控//制寄存器,包含一个5 位标识符,用来标识器件特定硅片//的版本号return(enc28j60Read(EREVID));
}void enc28j60PacketSend(unsigned int len, unsigned char* packet)
{// Set the write pointer to start of transmit buffer areaenc28j60Write(EWRPTL, TXSTART_INIT&0xFF);enc28j60Write(EWRPTH, TXSTART_INIT>>8);// Set the TXND pointer to correspond to the packet size givenenc28j60Write(ETXNDL, (TXSTART_INIT+len)&0xFF);enc28j60Write(ETXNDH, (TXSTART_INIT+len)>>8);// write per-packet control byte (0x00 means use macon3 settings)enc28j60WriteOp(ENC28J60_WRITE_BUF_MEM, 0, 0x00);// copy the packet into the transmit bufferenc28j60WriteBuffer(len, packet);// send the contents of the transmit buffer onto the networkenc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON1, ECON1_TXRTS);// Reset the transmit logic problem. See Rev. B4 Silicon Errata point 12.if( (enc28j60Read(EIR) & EIR_TXERIF) ){enc28j60WriteOp(ENC28J60_BIT_FIELD_CLR, ECON1, ECON1_TXRTS);}
}// Gets a packet from the network receive buffer, if one is available.
// The packet will by headed by an ethernet header.
//      maxlen  The maximum acceptable length of a retrieved packet.
//      packet  Pointer where packet data should be stored.
// Returns: Packet length in bytes if a packet was retrieved, zero otherwise.
unsigned int enc28j60PacketReceive(unsigned int maxlen, unsigned char* packet)
{unsigned int rxstat;unsigned int len;// check if a packet has been received and buffered//if( !(enc28j60Read(EIR) & EIR_PKTIF) ){// The above does not work. See Rev. B4 Silicon Errata point 6.if( enc28j60Read(EPKTCNT) ==0 )  //收到的以太网数据包长度{return(0);}// Set the read pointer to the start of the received packet		 缓冲器读指针enc28j60Write(ERDPTL, (NextPacketPtr));enc28j60Write(ERDPTH, (NextPacketPtr)>>8);// read the next packet pointerNextPacketPtr  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);NextPacketPtr |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;// read the packet length (see datasheet page 43)len  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);len |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;len-=4; //remove the CRC count// read the receive status (see datasheet page 43)rxstat  = enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0);rxstat |= enc28j60ReadOp(ENC28J60_READ_BUF_MEM, 0)<<8;// limit retrieve lengthif (len>maxlen-1){len=maxlen-1;}// check CRC and symbol errors (see datasheet page 44, table 7-3):// The ERXFCON.CRCEN is set by default. Normally we should not// need to check this.if ((rxstat & 0x80)==0){// invalidlen=0;}else{// copy the packet from the receive bufferenc28j60ReadBuffer(len, packet);}// Move the RX read pointer to the start of the next received packet// This frees the memory we just read outenc28j60Write(ERXRDPTL, (NextPacketPtr));enc28j60Write(ERXRDPTH, (NextPacketPtr)>>8);// decrement the packet counter indicate we are done with this packetenc28j60WriteOp(ENC28J60_BIT_FIELD_SET, ECON2, ECON2_PKTDEC);return(len);
}
//enc28j60.h
#ifndef __ENC28J60_H__
#define __ENC28J60_H__
#include "stm32f10x.h"#define ADDR_MASK        0x1F
#define BANK_MASK        0x60
#define SPRD_MASK        0x80
// All-bank registers
#define EIE              0x1B
#define EIR              0x1C
#define ESTAT            0x1D
#define ECON2            0x1E
#define ECON1            0x1F
// Bank 0 registers
#define ERDPTL           (0x00|0x00)
#define ERDPTH           (0x01|0x00)
#define EWRPTL           (0x02|0x00)
#define EWRPTH           (0x03|0x00)
#define ETXSTL           (0x04|0x00)
#define ETXSTH           (0x05|0x00)
#define ETXNDL           (0x06|0x00)
#define ETXNDH           (0x07|0x00)
#define ERXSTL           (0x08|0x00)
#define ERXSTH           (0x09|0x00)
#define ERXNDL           (0x0A|0x00)
#define ERXNDH           (0x0B|0x00)
//ERXWRPTH:ERXWRPTL 寄存器定义硬件向FIFO 中
//的哪个位置写入其接收到的字节。 指针是只读的,在成
//功接收到一个数据包后,硬件会自动更新指针。 指针可
//用于判断FIFO 内剩余空间的大小。
#define ERXRDPTL         (0x0C|0x00)
#define ERXRDPTH         (0x0D|0x00)
#define ERXWRPTL         (0x0E|0x00)
#define ERXWRPTH         (0x0F|0x00)
#define EDMASTL          (0x10|0x00)
#define EDMASTH          (0x11|0x00)
#define EDMANDL          (0x12|0x00)
#define EDMANDH          (0x13|0x00)
#define EDMADSTL         (0x14|0x00)
#define EDMADSTH         (0x15|0x00)
#define EDMACSL          (0x16|0x00)
#define EDMACSH          (0x17|0x00)
// Bank 1 registers
#define EHT0             (0x00|0x20)
#define EHT1             (0x01|0x20)
#define EHT2             (0x02|0x20)
#define EHT3             (0x03|0x20)
#define EHT4             (0x04|0x20)
#define EHT5             (0x05|0x20)
#define EHT6             (0x06|0x20)
#define EHT7             (0x07|0x20)
#define EPMM0            (0x08|0x20)
#define EPMM1            (0x09|0x20)
#define EPMM2            (0x0A|0x20)
#define EPMM3            (0x0B|0x20)
#define EPMM4            (0x0C|0x20)
#define EPMM5            (0x0D|0x20)
#define EPMM6            (0x0E|0x20)
#define EPMM7            (0x0F|0x20)
#define EPMCSL           (0x10|0x20)
#define EPMCSH           (0x11|0x20)
#define EPMOL            (0x14|0x20)
#define EPMOH            (0x15|0x20)
#define EWOLIE           (0x16|0x20)
#define EWOLIR           (0x17|0x20)
#define ERXFCON          (0x18|0x20)
#define EPKTCNT          (0x19|0x20)
// Bank 2 registers
#define MACON1           (0x00|0x40|0x80)
#define MACON2           (0x01|0x40|0x80)
#define MACON3           (0x02|0x40|0x80)
#define MACON4           (0x03|0x40|0x80)
#define MABBIPG          (0x04|0x40|0x80)
#define MAIPGL           (0x06|0x40|0x80)
#define MAIPGH           (0x07|0x40|0x80)
#define MACLCON1         (0x08|0x40|0x80)
#define MACLCON2         (0x09|0x40|0x80)
#define MAMXFLL          (0x0A|0x40|0x80)
#define MAMXFLH          (0x0B|0x40|0x80)
#define MAPHSUP          (0x0D|0x40|0x80)
#define MICON            (0x11|0x40|0x80)
#define MICMD            (0x12|0x40|0x80)
#define MIREGADR         (0x14|0x40|0x80)
#define MIWRL            (0x16|0x40|0x80)
#define MIWRH            (0x17|0x40|0x80)
#define MIRDL            (0x18|0x40|0x80)
#define MIRDH            (0x19|0x40|0x80)
// Bank 3 registers
#define MAADR1           (0x00|0x60|0x80)
#define MAADR0           (0x01|0x60|0x80)
#define MAADR3           (0x02|0x60|0x80)
#define MAADR2           (0x03|0x60|0x80)
#define MAADR5           (0x04|0x60|0x80)
#define MAADR4           (0x05|0x60|0x80)
#define EBSTSD           (0x06|0x60)
#define EBSTCON          (0x07|0x60)
#define EBSTCSL          (0x08|0x60)
#define EBSTCSH          (0x09|0x60)
#define MISTAT           (0x0A|0x60|0x80)
#define EREVID           (0x12|0x60)
#define ECOCON           (0x15|0x60)
#define EFLOCON          (0x17|0x60)
#define EPAUSL           (0x18|0x60)
#define EPAUSH           (0x19|0x60)
// PHY registers
#define PHCON1           0x00
#define PHSTAT1          0x01
#define PHHID1           0x02
#define PHHID2           0x03
#define PHCON2           0x10
#define PHSTAT2          0x11
#define PHIE             0x12
#define PHIR             0x13
#define PHLCON           0x14// ENC28J60 ERXFCON Register Bit Definitions
#define ERXFCON_UCEN     0x80
#define ERXFCON_ANDOR    0x40
#define ERXFCON_CRCEN    0x20
#define ERXFCON_PMEN     0x10
#define ERXFCON_MPEN     0x08
#define ERXFCON_HTEN     0x04
#define ERXFCON_MCEN     0x02
#define ERXFCON_BCEN     0x01
// ENC28J60 EIE Register Bit Definitions
#define EIE_INTIE        0x80
#define EIE_PKTIE        0x40
#define EIE_DMAIE        0x20
#define EIE_LINKIE       0x10
#define EIE_TXIE         0x08
#define EIE_WOLIE        0x04
#define EIE_TXERIE       0x02
#define EIE_RXERIE       0x01
// ENC28J60 EIR Register Bit Definitions
#define EIR_PKTIF        0x40
#define EIR_DMAIF        0x20
#define EIR_LINKIF       0x10
#define EIR_TXIF         0x08
#define EIR_WOLIF        0x04
#define EIR_TXERIF       0x02
#define EIR_RXERIF       0x01
// ENC28J60 ESTAT Register Bit Definitions
#define ESTAT_INT        0x80
#define ESTAT_LATECOL    0x10
#define ESTAT_RXBUSY     0x04
#define ESTAT_TXABRT     0x02
#define ESTAT_CLKRDY     0x01
// ENC28J60 ECON2 Register Bit Definitions
#define ECON2_AUTOINC    0x80
#define ECON2_PKTDEC     0x40
#define ECON2_PWRSV      0x20
#define ECON2_VRPS       0x08
// ENC28J60 ECON1 Register Bit Definitions
#define ECON1_TXRST      0x80
#define ECON1_RXRST      0x40
#define ECON1_DMAST      0x20
#define ECON1_CSUMEN     0x10
#define ECON1_TXRTS      0x08
#define ECON1_RXEN       0x04
#define ECON1_BSEL1      0x02
#define ECON1_BSEL0      0x01
// ENC28J60 MACON1 Register Bit Definitions
#define MACON1_LOOPBK    0x10
#define MACON1_TXPAUS    0x08
#define MACON1_RXPAUS    0x04
#define MACON1_PASSALL   0x02
#define MACON1_MARXEN    0x01
// ENC28J60 MACON2 Register Bit Definitions
#define MACON2_MARST     0x80
#define MACON2_RNDRST    0x40
#define MACON2_MARXRST   0x08
#define MACON2_RFUNRST   0x04
#define MACON2_MATXRST   0x02
#define MACON2_TFUNRST   0x01
// ENC28J60 MACON3 Register Bit Definitions
#define MACON3_PADCFG2   0x80
#define MACON3_PADCFG1   0x40
#define MACON3_PADCFG0   0x20
#define MACON3_TXCRCEN   0x10
#define MACON3_PHDRLEN   0x08
#define MACON3_HFRMLEN   0x04
#define MACON3_FRMLNEN   0x02
#define MACON3_FULDPX    0x01
// ENC28J60 MICMD Register Bit Definitions
#define MICMD_MIISCAN    0x02
#define MICMD_MIIRD      0x01
// ENC28J60 MISTAT Register Bit Definitions
#define MISTAT_NVALID    0x04
#define MISTAT_SCAN      0x02
#define MISTAT_BUSY      0x01
// ENC28J60 PHY PHCON1 Register Bit Definitions
#define PHCON1_PRST      0x8000
#define PHCON1_PLOOPBK   0x4000
#define PHCON1_PPWRSV    0x0800
#define PHCON1_PDPXMD    0x0100
// ENC28J60 PHY PHSTAT1 Register Bit Definitions
#define PHSTAT1_PFDPX    0x1000
#define PHSTAT1_PHDPX    0x0800
#define PHSTAT1_LLSTAT   0x0004
#define PHSTAT1_JBSTAT   0x0002
// ENC28J60 PHY PHCON2 Register Bit Definitions
#define PHCON2_FRCLINK   0x4000
#define PHCON2_TXDIS     0x2000
#define PHCON2_JABBER    0x0400
#define PHCON2_HDLDIS    0x0100// ENC28J60 Packet Control Byte Bit Definitions
#define PKTCTRL_PHUGEEN  0x08
#define PKTCTRL_PPADEN   0x04
#define PKTCTRL_PCRCEN   0x02
#define PKTCTRL_POVERRIDE 0x01// SPI operation codes
#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// The RXSTART_INIT should be zero. See Rev. B4 Silicon Errata
// buffer boundaries applied to internal 8K ram
// the entire available packet buffer space is allocated
//
// start with recbuf at 0/
#define RXSTART_INIT     0x0
// receive buffer end
#define RXSTOP_INIT      (0x1FFF-0x0600-1)
// start TX buffer at 0x1FFF-0x0600, pace for one full ethernet frame (~1500 bytes)
#define TXSTART_INIT     (0x1FFF-0x0600)
// stp TX buffer at end of mem
#define TXSTOP_INIT      0x1FFF
//
// max frame length which the conroller will accept:
#define        MAX_FRAMELEN        1500        // (note: maximum ethernet frame length would be 1518)
//#define MAX_FRAMELEN     600#define 	ENC28J60_CS_PORT	GPIOA
#define 	ENC28J60_CS_PIN		GPIO_Pin_4													/* ENC28J60片选线 */
#define 	ENC28J60_CSL()		\GPIO_ResetBits(ENC28J60_CS_PORT, ENC28J60_CS_PIN)				/* 拉低片选 */
#define 	ENC28J60_CSH()		\GPIO_SetBits(ENC28J60_CS_PORT, ENC28J60_CS_PIN)				/* 拉高片选 */void Enc28j60_Init(void);
unsigned char enc28j60ReadOp(unsigned char op, unsigned char address);
void 	enc28j60WriteOp(unsigned char op, unsigned char address, unsigned char data);
void 	enc28j60ReadBuffer(unsigned int len, unsigned char* data);
void 	enc28j60WriteBuffer(unsigned int len, unsigned char* data);
void 	enc28j60SetBank(unsigned char address);
unsigned char enc28j60Read(unsigned char address);
void 	enc28j60Write(unsigned char address, unsigned char data);
void 	enc28j60PhyWrite(unsigned char address, unsigned int data);
void 	enc28j60clkout(unsigned char clk);
void 	enc28j60Init(unsigned char* macaddr);
unsigned char enc28j60getrev(void);
void 	enc28j60PacketSend(unsigned int len, unsigned char* packet);
unsigned int enc28j60PacketReceive(unsigned int maxlen, unsigned char* packet);
#endif

更加详细的说明请参考原博客。

4.新建分组

打开空STM32项目、新建组lwip-arch、lwip-netif、lwip-core、lwip-core-ipv4。
lwip-arch添加cc.h、lwipopts.h、perf.h、网卡驱动文件
lwip-netif添加ethernetif.c、etharp.c、slipif.c
lwip-core添加core全部文件,lwip-core-ipv4添加core/ipv4全部文件

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5.项目头文件路径

包含LWIP以及LWIP一些子文件夹路径。看图:
在这里插入图片描述

6.LWIIP头文件编写

接下来编写前面提到的三个头文件cc.h、lwipopts.h、perf.h

首先是perf.h,这个文件很简单

//perf.h
#ifndef __PERF_H__
#define __PERF_H__#define PERF_START
#define PERF_STOP(x)
#endif

下面是cc.h,该文件提供了变量类型声明与调试宏定义

//cc.h
#ifndef __CC_H__
#define __CC_H__
#include "stdio.h"typedef unsigned   char    	u8_t;    /* Unsigned 8 bit quantity         */
typedef signed     char    	s8_t;    /* Signed    8 bit quantity        */
typedef unsigned   short   	u16_t;   /* Unsigned 16 bit quantity        */
typedef signed     short   	s16_t;   /* Signed   16 bit quantity        */
typedef unsigned   int    	u32_t;   /* Unsigned 32 bit quantity        */
typedef signed     int    	s32_t;   /* Signed   32 bit quantity        */
typedef unsigned   int 		 	mem_ptr_t;            /* Unsigned 32 bit quantity        */
typedef unsigned   int 		 	sys_prot_t;/* define compiler specific symbols */
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_STRUCT 
#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_END/*--------------macros--------------------------------------------------------*/
#define LWIP_DEBUG
#define LWIP_PLATFORM_DIAG(x) {printf(x);}
#define LWIP_PLATFORM_ASSERT(x) {printf(x);while(1);}
#define LWIP_ERROR(message,expression,handler) \do{\if (!(expression)) {printf(message);handler;}\}while(0)/*---define (sn)printf formatters for these lwip types, for lwip DEBUG/STATS--*/
#define U16_F "u"
#define S16_F "d"
#define X16_F "x"
#define U32_F "u"
#define S32_F "d"
#define X32_F "x"#define LWIP_PROVIDE_ERRNO
#define BYTE_ORDER LITTLE_ENDIAN
extern u32_t sys_now(void);;
#endif /* __CC_H__ */

最后是lwipopts.h,用于重定义opt.h中默认的宏。

#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__
//不使用RTOS
#define NO_SYS                  1//不适用RTOS时,不使用这些API
#define LWIP_SOCKET             0
#define LWIP_NETCONN            0//LWIP的内存大小
#define MEM_ALIGNMENT           4  
#define MEM_SIZE                10*1024//TCP发送缓存与最长报文段长度
#define TCP_SND_BUF             4000
#define TCP_MSS                 1000//调试功能
#define ETHARP_DEBUG 	LWIP_DBG_ON
#define ICMP_DEBUG    LWIP_DBG_ON#endif /* __LWIPOPTS_H__ */

7.ethernetif.c

这个文件用于向LWIP封装网卡驱动。在前面的网卡驱动那一节已经实现了网卡的驱动,其中有三个函数:

void 	enc28j60Init(unsigned char* macaddr);//网卡初始配置void 	enc28j60PacketSend(unsigned int len, unsigned char* packet);//发送unsigned int enc28j60PacketReceive(unsigned int maxlen, unsigned char* packet);//接收

本文件就是封装这三个函数,向LWIP实现以下几个函数:

void low_level_init(struct netif *netif)
err_t low_level_output(struct netif *netif, struct pbuf *p)
struct pbuf *low_level_input(struct netif *netif)
void ethernetif_input(struct netif *netif)
err_t ethernetif_init(struct netif *netif)

打开LWIP的ethernetif.c文件,发现官方已经为我们用0宏定义写好了这五个函数的框架其中ethernetif_init可以直接使用不修改,其他的要自己实现。
其他函数的实现我参考了《嵌入式网络那些事——STM32物联实战-朱升林-2015年版》这本书。

void low_level_init(struct netif *netif)

static unsigned char MyMacID[6] = {'M','y','L','W','I','P'};
static void low_level_init(struct netif *netif)
{struct ethernetif *ethernetif = netif->state;netif->hwaddr_len = ETHARP_HWADDR_LEN;//自定义网卡MAC地址for(int i = 0 ; i < ETHARP_HWADDR_LEN ; i++)netif->hwaddr[i] = MyMacID[i];netif->mtu = 1500;netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP; //在原来的框架上,调用网卡寄存器初始化函数enc28j60Init(MyMacID);
}

err_t low_level_output(struct netif *netif, struct pbuf *p)`

static unsigned char MySendbuf[1500];
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{struct pbuf *q = NULL;unsigned int templen = 0;for (q = p ; q != NULL ; q = q->next){memcpy(&MySendbuf[templen],q->payload,q->len);templen += q->len;if (templen > 1500 || templen > p->tot_len) return ERR_BUF;}if (templen == p->tot_len){enc28j60PacketSend(templen,MySendbuf);return ERR_OK;}return ERR_BUF;
}

struct pbuf *low_level_input(struct netif *netif)

static unsigned char MyRecvbuf[1500];
static struct pbuf *low_level_input(struct netif *netif)
{struct pbuf *p=NULL, *q=NULL;u16_t len = 0,i = 0;len = enc28j60PacketReceive(1500,MyRecvbuf);if (!len) return NULL;p = pbuf_alloc(PBUF_RAW,len,PBUF_RAM);if (!p) return NULL;q = p;while(q != NULL){memcpy(q->payload,&MyRecvbuf[i],q->len);i += q->len;q = q->next;if(i>=len) break;}return p;
}

void ethernetif_input(struct netif *netif)

void ethernetif_input(struct netif *netif)
{struct ethernetif *ethernetif;struct eth_hdr *ethhdr;struct pbuf *p;ethernetif = netif->state;p = low_level_input(netif);if (p == NULL) return;ethhdr = p->payload;switch (htons(ethhdr->type)) {case ETHTYPE_IP:case ETHTYPE_ARP:
#if PPPOE_SUPPORT/* PPPoE packet? */case ETHTYPE_PPPOEDISC:case ETHTYPE_PPPOE:
#endif /* PPPOE_SUPPORT */if (netif->input(p, netif)!=ERR_OK){ LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));pbuf_free(p);p = NULL;}break;default:pbuf_free(p);p = NULL;break;}
}

8.sys_now

LWIP需要定时功能(ARP、TCP都有定时需求),而LWIP需要用户实现一个unsigned int sys_now(void)的函数来返回当前运行时间。

有RTOS基础的都知道RTOS都会有一个计时变量,在各自的RTOS的SysTick中断函数中自增。

现在我们没有用RTOS,但是也要提供这样的函数。因此我们在中断文件stm32f10x_it.c中,实现这些功能:

#include "stm32f10x_it.h"
unsigned int lwip_localtime;
void SysTick_Handler(void)
{lwip_localtime++;
}unsigned int sys_now(void){return lwip_localtime;
}void EXTI1_IRQHandler(){if(EXTI_GetITStatus(EXTI_Line1) != RESET) {EXTI_ClearITPendingBit(EXTI_Line1);}
}

这里为什么会有一个外部中断函数?

网卡驱动函数里面初始化了网卡的INT引脚,因此必须根据你的引脚连接,实现对应的外部中断函数

否则测试的时候,一旦接收到数据,触发中断,系统会因为没有重定义外部中断函数而死循环(重要!笔者被弄过好几次)。

9.初始化函数

LWIP移植时要实现的函数与要修改的地方就这些。在开始使用LWIP前,就像其他很多东西一样,要进行LWIP初始化。
写一个函数完成:

//main.c
struct netif enc28j60_netif;
err_t ethernetif_init(struct netif* netif);
void ethernetif_input(struct netif *netif);void LWIP_Init(void){NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);//网卡的GPIO、中断等初始化函数,你也可以放在enc28j60Init最前面Enc28j60_Init();//简单延时uint32_t t = 0x001fffff;while(t--);//开启SystickSysTick_Config(SystemCoreClock/1000);//LWIP初始化,设置网关、IP(这里是192.168.1.114)、掩码struct ip_addr ipaddr,netmask,gw;lwip_init();IP4_ADDR(&gw,192,168,1,1);IP4_ADDR(&ipaddr,192,168,1,114);IP4_ADDR(&netmask,255,255,255,0);netif_add(&enc28j60_netif,&ipaddr,&netmask,&gw,NULL,ethernetif_init,ethernet_input);netif_set_default(&enc28j60_netif);netif_set_up(&enc28j60_netif);
}

10.主函数

本文仅实现PING功能,不实现应用层功能。因此这样写:

int main(void){//串口初始化,看这文章的应该没有不会用printf串口打印的吧(Usart_1_Config();printf("\r\n 这是一个无OS移植LWIP实验 \r\n现在可以ping试试");//调用上面的初始化函数LWIP_Init();//循环调用ethernetif_input与sys_check_timeoutswhile(1){ethernetif_input(&enc28j60_netif);sys_check_timeouts();} 
}

至此,代码部分全部编写完成。

注意

1.正如前文所述,网卡驱动初始化了中断。一旦收到数据,对应的引脚(看你连接)触发中断。如果不重写中断函数,触发中断时会进入死循环。

2、直接编译时,KEIL5会报500多个警告(调试相关),因此这样做:
点击魔术棒 -> C/C++ -> MiscControls,在里面输入下面这一段文字:

--diag_suppress=1,1295,174,167,111,128,177,550

消除很多没用的警告。
在这里插入图片描述
3. ENC28J60模块的VCC引脚要接5V,3.3V经过测试无法正常运行。

测试

已知前文中,网卡初始化成这样:

网关   192.168.1.1
IP地址 192.168.1.114
掩码   255.255.255.0

控制面板 -> 网络和Internet -> 网络连接 -> 右键以太网 -> 属性
这样设置:
以太网IP为192.168.1.x
掩码为255.255.255.0
网关192.168.1.1
(总之和单片机在一个网段)
在这里插入图片描述

结果

ping 192.168.1.114 正常情况可以PING通
在这里插入图片描述

源码

点击获得源码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/108708.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

UG\NX CAM二次开发 设置安全平面的起点和法向 UF_CAM_set_auto_blank

文章作者:代工 来源网站:NX CAM二次开发专栏 简介: UG\NX CAM二次开发 设置安全平面的起点和法向 UF_CAM_set_auto_blank 效果: 代码: void MyClass::do_it() { //获取加工环境tagtag_t setup_tag=NULL_TAG;UF_SETUP_ask_setup(&setup_tag);//返回当前工序导…

财报解读:打好“新鲜牌”,光明乳业业绩规模能否持续扩大?

近期&#xff0c;各大乳企相继披露了中期业绩&#xff0c;从财报情况来看&#xff0c;伊利、蒙牛、光明仍然稳居营收规模上百亿的第一梯队。 值得一提的是&#xff0c;2023年上半年&#xff0c;乳制品行业的发展并非一帆风顺&#xff0c;牧业端饲料价格处在高位&#xff0c;养…

清水模板和混水模板的区别是什么?

清水模板和混水模板是建筑施工中常用的两种模板类型。它们在结构、外观和使用方面存在一些显著的区别。下面将详细介绍清水模板和混水模板的特点和区别。 1. 材料&#xff1a;清水模板是由高分子有机材料制成的&#xff0c;而混水模板则是由混凝土制成的。由于材料的不同&#…

【论文阅读】MARS:用于自动驾驶的实例感知、模块化和现实模拟器

【论文阅读】MARS&#xff1a;用于自动驾驶的实例感知、模块化和现实模拟器 Abstract1 Introduction2 Method2.1 Scene Representation2.3 Towards Realistic Rendering2.4 Optimization3.1 Photorealistic Rendering3.2 Instance-wise Editing3.3 The blessing of moduler des…

网上管理系统的分析及设计---应用UML建模

目 录 第1章 系统需求 第2章 需求分析 2.1 识别参与者 2.2 识别用例 2.3 用例的事件流描述 第3章 静态结构模型 3.1 定义系统对象 3.2 定义用户界面类 3.3 建立类图 第4章 动态行为模型 4.1 创建系统顺序图&#xff08;协作图&#xff09; 4.2 创建系统…

【Spring Boot】有这一文就够了

作者简介 前言 作者之前写过一个Spring Boot的系列&#xff0c;包含自动装配原理、MVC、安全、监控、集成数据库、集成Redis、日志、定时任务、异步任务等内容&#xff0c;本文将会一文拉通来总结这所有内容&#xff0c;不骗人&#xff0c;一文快速入门Spring Boot。 专栏地址…

Jmeter系列进阶-获取图片验证码(4)

安装工具 通过ocrserver工具识别图片验证码&#xff0c;解压后 .exe双击启动即可。 jmeter中使用 &#xff08;1&#xff09;HTTP请求获取验证码 &#xff08;2&#xff09;在获取验证码图片的接口下面添加监听器》保存响应到文件&#xff1b;如下图&#xff1a; &#x…

易点易动固定资产管理系统:为企业提供高效资产管理的必备利器

在如今竞争激烈的商业环境中&#xff0c;企业对于固定资产的管理显得尤为重要。然而&#xff0c;传统的手工管理方式已经无法满足企业对于高效、准确、智能化管理的需求。因此&#xff0c;引入一款先进的固定资产管理系统是必不可少的。易点易动固定资产管理系统作为一款领先的…

【Spring使用三级缓存解决循环依赖的过程】

testService1和testService2相互依赖 当Spring创建testService1对象时&#xff0c;它会先从一级缓存中查找是否存在testService1的实例。如果缓存中不存在testService1实例&#xff0c;它将继续查找二级缓存中是否存在testService1。如果二级缓存中也不存在testService1实例&…

浅述数据中心供配电系统解决方案及产品选型

安科瑞 华楠 【摘 要】现如今&#xff0c;社会主要领域已从对单个设备的关注转化为对于系统解决方案的关注&#xff0c;数据中心的供应商们也想尽办法去满足所面对的各方面需求。基于此&#xff0c;主要提出了云计算数据中心供配电解决方案&#xff0c;同时还对数据中心供配电…

如何在matlab绘图的标题中添加变量?变量的格式化字符串输出浅析

文章目录 matlab的格式化输出控制符字段宽度、精度和对齐方式的控制matlab的格式化输出总结 matlab的格式化输出控制符 Matlab在画图的时候&#xff0c;采用title函数可以增加标题&#xff0c;该函数的输入是一个字符串&#xff0c;有时候我们想在字符串中添加一些变量&#x…

Linux基础入门

一、操作系统安装方法 1、使用u盘安装 工具&#xff08;前提条件&#xff09;&#xff1a; <1>u盘 <2>镜像文件iso/msdn.itellyou.cn <3>把u盘做成PE&#xff1a;大白菜/老毛桃/winPE/软碟通/ultralSO 设置BIOS&#xff1a;通过u盘启动 安装系统&…