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.rhkb.cn/news/132345.html

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

相关文章

ChatGPT 网站合集/NovelAI tag生成器/Novel资源大全

ChatGPT 网站合集 https://github.com/xx025/carrot NovelAI tag生成器 https://wolfchen.top/tag/ Novel资源大全 https://wolfchen.top/tag/doc.html 简单地说&#xff0c;Stable Diffusion被修改后做出了NovelAI&#xff0c;NovelAI离家出走便有了Naifu Naifu简单好上手&am…

基于红黑树对map和set容器的封装

本章代码gitee仓库&#xff1a;map和set模拟实现、stl_map_set_tree源码 文章目录 &#x1f431;1. 红黑树的泛型&#x1f408;1.1 红黑树节点&#x1f408;1.2 红黑树迭代器&#x1f408;1.3 仿函数 &#x1f42f;2. 对set的封装&#x1f984;3. 对map的封装 &#x1f431;1. …

CRM系统销售自动化功能如何提高销售效率

销售效率对企业的盈利能力有着至关重要的联系。提高销售效率&#xff0c;就是要提高销售人员的工作效率和销售转化率。那么&#xff0c;企业如何提高销售效率呢&#xff1f;CRM销售自动化功能可以帮助企业实现这一目标。 一、线索管理 线索是指有潜在购买意向的客户&#xff…

基于STM32F407ZET6的环境温湿度监控系统(粤嵌GEC-M4)

注意使用事项&#xff1a; 开发板如下 由于外部晶振是8M&#xff0c;需要修改setup和stm32f4头文件的晶振值。 操作如下&#xff1a; system_stm32f4xx.c的254行 #define PLL_M 8stm32f4xx.h的127行 #define HSE_VALUE ((uint32_t)8000000) /*!< Value of the Ex…

2023/9/14 -- C++/QT

作业&#xff1a; 仿照Vector实现MyVector&#xff0c;最主要实现二倍扩容 #include <iostream>using namespace std;template <typename T> class MyVector { private:T *data;size_t size;size_t V_capacity; public://无参构造MyVector():data(nullptr),size(…

【深度学习 AIGC】stablediffusion-infinity 在无界限画布中输出绘画 Outpainting

代码&#xff1a;https://github.com/lkwq007/stablediffusion-infinity/tree/master 启动环境&#xff1a; git clone --recurse-submodules https://github.com/lkwq007/stablediffusion-infinity cd stablediffusion-infinity conda env create -f environment.yml conda …

如何开启Win10虚拟机Hyper-V功能

操作步骤: 使用前提&#xff1a; 1、确保系统是 Windows 10 专业版/企业版/教育版&#xff0c;且必须是64位操作系统才支持。 提示&#xff1a;Win10家庭版不支持hyper-v。 2、使用Hyper-V需要cpu支持虚拟化并处于开启状态。 3、硬件要求及如何验证硬件兼容性&#xff1a; 硬件…

什么是云存储,从对象存储说起?

在《存储系统形态之争,从块存储到统一存储》一文中我们提到了对象存储的概念,知道目前很多企业级存储都是支持对象存储的,比如EMC、NetApp和华为等。以EMC的对象存储为例,其最早在1998年就已经具备成熟的产品了,到目前已经有二十多年的历史了。如图是关于对象存储主要产品…

SpringMVC之JSR303和拦截器

一.什么是JSR303 二.JSR303 常用注解 作用 使用 导入pom.xml 在实体类相对应的属性中增加注解用来指定校验 在hpjyController里面新加以下代码 修改eidt.jsp 测试结果 ​编辑 二.拦截器 什么是拦截器 拦截器与过滤器的区别 应用场景日志记录&#xff1a;拦截器可以用…

图解第一类曲线积分与第二类曲线积分的关系

图解第一类曲线积分与第二类曲线积分的关系 笔记相关内容&#xff1a; 1.曲线积分&#xff08;Line Integral&#xff09; 2.向量场中的曲线积分、环量、通量 第一类曲线积分&#xff08;对弧长 d s ds ds进行积分&#xff09;(无方向性) 物理意义&#xff1a; f ( x , y ) f(…

Docker 一键安装Confluence(已支持最新版本)

Docker 一键安装Confluence&#xff08;已支持最新版本&#xff09; 本文用于Confluence在Docker的安装&#xff0c;仅用于记录安装方式Jira 也可以参考这种方式安装&#xff0c;只有细微差别转载请注明来源Linux安装可参考链接Windows安装可查考链接条件允许时&#xff0c;请…

java编辑pdf(itextpdf)

工作上遇到一个小需求&#xff0c;需要在原有的pdf文件上添加一行文字&#xff0c;实现方式如下 引入依赖 <dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.13</version></dependen…

【千万别上当】揭秘又一个割韭菜的项目:明星网红直播切片项目

今天在某乎浏览内容&#xff0c;看到一篇文章《我做了10个某音号&#xff0c;不拍视频&#xff0c;不直播&#xff0c;光靠剪辑明星素材月入8K&#xff0c;给缺钱的朋友推荐一个无脑赚Q的自媒体项目》&#xff0c;点击进去一看&#xff0c;洋洋洒洒估计有一两万字&#xff0c;过…

【SA8295P 源码分析】97 - QNX AIS Camera 框架介绍 及 Camera 工作流程分析

【SA8295P 源码分析】97 - QNX AIS Camera 框架介绍 及 Camera 工作流程分析 一、QNX AIS Server 框架分析二、QNX Hypervisor / Android GVM 方案介绍三、Camera APP 调用流程分析四、QCarCam 状态转换过程介绍五、Camera 加串-解串 硬件链路分析六、摄像头初始化检测过程介绍…

原生js vue react通用的递归函数

1.递归函数的由来 递归函数的由来可以追溯到数学中的递归概念和数学归纳法。 在数学中&#xff0c;递归是指通过定义基本情况和递推公式&#xff0c;将一个问题分解为更简单的、与原问题具有相同结构的子问题&#xff0c;并用子问题的解来构建原问题的解。递归的思想在解决一些…

【JavaScript】在指定dom元素前面创建标签元素

一、基础操作过程 要在指定的DOM元素前面创建标签元素&#xff0c;有以下步骤&#xff1a; 获取指定的DOM元素&#xff1a;使用document.querySelector()或document.getElementById()等方法来获取指定的DOM元素。 const targetElement document.querySelector(#targetElement…

NOA高歌猛进,智驾地图何去何从?

作者|德新 编辑|王博 今年是辅助驾驶的量产大年&#xff0c;尤其高阶智能驾驶&#xff0c;带动了上游需求的爆发。 一些典型的科技赛道&#xff0c;如激光雷达、大算力芯片等环节都从中受益。禾赛科技、地平线近期都公布了产品出货达到新的里程碑。禾赛AT系列产品上半年的出货…

亲测好用-obsidian无法打开插件库安装或更新的解决办法-结合FastGithub

写在前面 经过半年左右时间的使用情况验证该方案稳定可靠。 方案&#xff1a;插件“Plugin Proxy” 软件“FastGithub” 效果&#xff1a; 插件“Plugin Proxy” 下载地址&#xff1a; https://github.com/gslnzfq/obsidian-proxy-server 插件安装&#xff1a; 插件设置为…

多态的笔记

什么是多态 顾名思义多态就是让一个对象拥有不同的形态 举例如下 多态的应用场景&#xff0c;比如你注册的时候&#xff0c;你可能注册为学生&#xff0c;老师&#xff0c;管理员&#xff0c;但是此时你往函数传递的类型是什么呢&#xff0c;此时你无论传递什么都不太合适&am…

Marin说PCB之封装设计系列---(02)--异形焊盘的封装设计总结

每天下班回家看电视本来是一件很美好的事情&#xff0c;可是正当我磕着瓜子看着异人之下的时候&#xff0c;手机突然响起来了&#xff0c;我以为是我们组哪个同事找我呢。一接电话居然是我的老朋友陈世美陈总&#xff0c;江湖人称少妇杀手。给我打电话主要是说他最近遇到一个异…