联系我们
简单又实用的WordPress网站制作教学
当前位置:网站首页 > 程序开发学习 > 正文

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

作者:小教学发布时间:2023-09-24分类:程序开发学习浏览:69


导读:目录环境引脚连接1.准备LWIP2.新建arch3.网卡驱动4.新建分组5.项目头文件路径6.LWIIP头文件编写7.ethernetif.cvoidlow_le...

目录

  • 环境
  • 引脚连接
  • 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 CS
    ENC28J60_CSH();
    return dat;
}

void enc28j60WriteOp(unsigned char op, unsigned char address, unsigned char data)
{
    unsigned char dat = 0;
    ENC28J60_CSL();
    // issue write command
    dat = op | (address & ADDR_MASK);
    SPI1_ReadWrite(dat);
    // write data
    dat = data;
    SPI1_ReadWrite(dat);
    ENC28J60_CSH();
}

void enc28j60ReadBuffer(unsigned int len, unsigned char* data)
{
    ENC28J60_CSL();
    // issue read command
    SPI1_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 command
    SPI1_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 bank
        enc28j60WriteOp(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 bank
    enc28j60SetBank(address);
    // do the read
    return enc28j60ReadOp(ENC28J60_READ_CTRL_REG, address);
}

void enc28j60Write(unsigned char address, unsigned char data)
{
    // set the bank
    enc28j60SetBank(address);
    // do the write
    enc28j60WriteOp(ENC28J60_WRITE_CTRL_REG, address, data);
}

void enc28j60PhyWrite(unsigned char address, unsigned int data)
{
    // set the PHY register address
    enc28j60Write(MIREGADR, address);
    // write the PHY data
    enc28j60Write(MIWRL, data);
    enc28j60Write(MIWRH, data>>8);
    // wait until the PHY write completes
    while(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 reset
    enc28j60WriteOp(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 end
    enc28j60Write(ERXNDL, RXSTOP_INIT&0xFF);
    enc28j60Write(ERXNDH, RXSTOP_INIT>>8);
    // TX start	  1500
    enc28j60Write(ETXSTL, TXSTART_INIT&0xFF);
    enc28j60Write(ETXSTH, TXSTART_INIT>>8);
    // TX end
    enc28j60Write(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=0x30
    
    enc28j60Write(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-backward
    enc28j60Write(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 frames
    enc28j60PhyWrite(PHCON2, PHCON2_HDLDIS);

    // switch to bank 0    
    enc28j60SetBank(ECON1);

    // enable interrutps
    enc28j60WriteOp(ENC28J60_BIT_FIELD_SET, EIE, EIE_INTIE|EIE_PKTIE);

    // enable packet reception
    enc28j60WriteOp(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 area
    enc28j60Write(EWRPTL, TXSTART_INIT&0xFF);
    enc28j60Write(EWRPTH, TXSTART_INIT>>8);
    
    // Set the TXND pointer to correspond to the packet size given
    enc28j60Write(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 buffer
    enc28j60WriteBuffer(len, packet);
    
    // send the contents of the transmit buffer onto the network
    enc28j60WriteOp(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 pointer
    NextPacketPtr  = 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 length
    if (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)
    {
        // invalid
        len=0;
    }
    else
    {
        // copy the packet from the receive buffer
        enc28j60ReadBuffer(len, packet);
    }
    // Move the RX read pointer to the start of the next received packet
    // This frees the memory we just read out
    enc28j60Write(ERXRDPTL, (NextPacketPtr));
    enc28j60Write(ERXRDPTH, (NextPacketPtr)>>8);
    
    // decrement the packet counter indicate we are done with this packet
    enc28j60WriteOp(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--);
	//开启Systick
	SysTick_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_timeouts
	while(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通
在这里插入图片描述

源码

点击获得源码





程序开发学习排行
最近发表
网站分类
标签列表