STM32F103VET6基于ENC28J60移植LWIP1.4.1(标准库,无RTOS)
作者:小教学发布时间:2023-09-24分类:程序开发学习浏览:69
目录
- 环境
- 引脚连接
- 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,要么不用这个网卡。笔者自学被搞得哇哇大叫。因此写本文记录过程。
笔者仅做移植记录,详细原理不做讲解。
环境
- STM32F103VET6(野火指南者)
- LWIP 1.4.1
- 网卡为ENC28J60
- 一根网线
引脚连接
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通
源码
点击我获得源码
- 上一篇:string类
- 下一篇:PostMan——安装使用教程(图文详解)
- 程序开发学习排行
-
- 1鸿蒙HarmonyOS:Web组件网页白屏检测
- 2HTTPS协议是安全传输,为啥还要再加密?
- 3HarmonyOS鸿蒙应用开发——数据持久化Preferences
- 4记解决MaterialButton背景颜色与设置值不同
- 5鸿蒙HarmonyOS实战-ArkUI组件(RelativeContainer)
- 6鸿蒙HarmonyOS实战-ArkUI组件(Stack)
- 7鸿蒙HarmonyOS实战-ArkUI组件(GridRow/GridCol)
- 8[Android][NDK][Cmake]一文搞懂Android项目中的Cmake
- 9鸿蒙HarmonyOS实战-ArkUI组件(mediaquery)
- 最近发表
-
- WooCommerce最好的WordPress常用插件下载博客插件模块的相关产品
- 羊驼机器人最好的WordPress常用插件下载博客插件模块
- IP信息记录器最好的WordPress常用插件下载博客插件模块
- Linkly for WooCommerce最好的WordPress常用插件下载博客插件模块
- 元素聚合器Forms最好的WordPress常用插件下载博客插件模块
- Promaker Chat 最好的WordPress通用插件下载 博客插件模块
- 自动更新发布日期最好的WordPress常用插件下载博客插件模块
- WordPress官方最好的获取回复WordPress常用插件下载博客插件模块
- Img to rss最好的wordpress常用插件下载博客插件模块
- WPMozo为Elementor最好的WordPress常用插件下载博客插件模块添加精简版