目录
- 前言
- 一、移植准备工作
- 二、以太网固件库与驱动
-
- [2.1 固件库文件添加](#2.1 固件库文件添加)
- [2.2 库文件修改](#2.2 库文件修改)
- [2.3 添加网卡驱动](#2.3 添加网卡驱动)
- [三、LWIP 数据包和网络接口管理](#三、LWIP 数据包和网络接口管理)
-
- [3.1 添加LWIP源文件](#3.1 添加LWIP源文件)
- [3.2 Lwip文件修改](#3.2 Lwip文件修改)
-
- [3.2.1 修改cc.h](#3.2.1 修改cc.h)
- [3.2.2 修改lwipopts.h](#3.2.2 修改lwipopts.h)
- [3.2.3 修改icmp.c](#3.2.3 修改icmp.c)
- [3.2.4 修改sys_arch.h和sys_arch.c](#3.2.4 修改sys_arch.h和sys_arch.c)
- [3.2.5 修改ethernetif.h和ethernetif.c](#3.2.5 修改ethernetif.h和ethernetif.c)
- 四、添加应用程序
-
- [4.1 添加Lwip应用](#4.1 添加Lwip应用)
- [4.2 添加FreeRTOS应用](#4.2 添加FreeRTOS应用)
前言
介绍一下基于FreeRTOS的LWIP移植,平台为STM32F407标准库的Keil环境。LWIP的相关介绍见以下链接:https://blog.csdn.net/weixin_44567668/article/details/139619797
一、移植准备工作
移植文件需要LWIP源码 及FreeRTOS标准库工程
-
FreeRTOS标准库工程
移植需要一个已经移植好FreeRTOS的STM32标准库工程,大概如下图所示
FreeRTOS的移植参考以下链接:https://blog.csdn.net/weixin_44567668/article/details/135419275
-
Lwip源码
Lwip源码可以从官网获取,LWIP官网链接:http://savannah.nongnu.org/projects/lwip/
点击Download Area 就可以下载源码,我们选择contrib-1.4.1.zip 和lwip-1.4.1.zip ,不用最新版本是因为标准库缺少对应最新版本的以太网句柄
ETH_HandleTypeDef
,如果是HAL库版本可以用最新版本的Lwip源码
此时由于一些配置和移植需要,还要下载一下ST官方移植示例,ST官方参考工程链接:https://www.st.com/en/embedded-software/stsw-stm32070.html
二、以太网固件库与驱动
2.1 固件库文件添加
从ST例程里找到以太网库STM32F4x7_ETH_Driver ,并将其复制到我们工程文件里
然后在keil工程里添加进来stm32f4x7_eth.c
和stm32f4xx_syscfg.c
2.2 库文件修改
stm32f4x7_eth_conf.h
的修改
在STM32F4x7_ETH_Driver\inc 很容易找到stm32f4x7_eth_conf_template.h
,里面定义了一些关于操作PHY芯片的信息,有关PHY芯片LAN8720的相关介绍见前言链接。此时我们将stm32f4x7_eth_conf_template.h
重命名为stm32f4x7_eth_conf.h
,然后将里面改成LAN8720对应的配置
c
#ifndef __STM32F4x7_ETH_CONF_H
#define __STM32F4x7_ETH_CONF_H
#include "stm32f4xx.h"
#define USE_ENHANCED_DMA_DESCRIPTORS
//如果使用自己定义的延时函数的话就注销掉下面一行代码,否则使用
//默认的低精度延时函数
//#define USE_Delay //使用默认延时函数,因此注销掉
#ifdef USE_Delay
#include "main.h"
#define _eth_delay_ Delay //Delay为用户自己提供的高精度延时函数
#else
#define _eth_delay_ ETH_Delay //默认的_eth_delay功能函数延时精度差
#endif
#ifdef CUSTOM_DRIVER_BUFFERS_CONFIG
//重新定义以太网接收和发送缓冲区的大小和数量
#define ETH_RX_BUF_SIZE ETH_MAX_PACKET_SIZE //接收缓冲区的大小
#define ETH_TX_BUF_SIZE ETH_MAX_PACKET_SIZE //发送缓冲区的大小
#define ETH_RXBUFNB 20 //接收缓冲区数量
#define ETH_TXBUFNB 5 //发送缓冲区数量
#endif
//*******************PHY配置块*******************
#ifdef USE_Delay
#define PHY_RESET_DELAY ((uint32_t)0x000000FF) //PHY复位延时
#define PHY_CONFIG_DELAY ((uint32_t)0x00000FFF) //PHY配置延时
#define ETH_REG_WRITE_DELAY ((uint32_t)0x00000001) //向以太网寄存器写数据时的延时
#else
#define PHY_RESET_DELAY ((uint32_t)0x000FFFFF) //PHY复位延时
#define PHY_CONFIG_DELAY ((uint32_t)0x00FFFFFF) //PHY配置延时
#define ETH_REG_WRITE_DELAY ((uint32_t)0x0000FFFF) //向以太网寄存器写数据时的延时
#endif
//LAN8720 PHY芯片的状态寄存器
#define PHY_SR ((uint16_t)31) //LAN8720的PHY状态寄存器地址
#define PHY_SPEED_STATUS ((uint16_t)0x0004) //LAN8720 PHY速度值掩码
#define PHY_DUPLEX_STATUS ((uint16_t)0x00010) //LAN8720 PHY连接状态值掩码
#endif
stm32f4x7_eth.c
的修改
在 stm32f4x7_eth.c 文件中针对不同的平台定义了四个数组:Rx_Buff[]、Tx_Buff[]、DMARxDscrTab[]和 DMATxDscrTab[],这四个数组占用了大量的 RAM。我们在这里将这四个变量屏蔽掉,如图所示
2.3 添加网卡驱动
网卡驱动主要是对以太网的一些配置,主要是一些初始化工作,这里新建两个文件为ethernet.h
和ethernet.c
ethernet.h
头文件
c
#ifndef __LAN8720_H
#define __LAN8720_H
#include "delay.h"
#include "stm32f4x7_eth.h"
#define LAN8720_PHY_ADDRESS 0x00 //LAN8720 PHY芯片地址.
#define LAN8720_RST PDout(3) //LAN8720复位引脚
/* MAC ADDRESS*/
#define MAC_ADDR0 02
#define MAC_ADDR1 00
#define MAC_ADDR2 00
#define MAC_ADDR3 00
#define MAC_ADDR4 00
#define MAC_ADDR5 00
/*Static IP ADDRESS*/
#define IP_ADDR0 192
#define IP_ADDR1 168
#define IP_ADDR2 0
#define IP_ADDR3 10
/*NETMASK*/
#define NETMASK_ADDR0 255
#define NETMASK_ADDR1 255
#define NETMASK_ADDR2 255
#define NETMASK_ADDR3 0
/*Gateway Address*/
#define GW_ADDR0 192
#define GW_ADDR1 168
#define GW_ADDR2 0
#define GW_ADDR3 1
extern ETH_DMADESCTypeDef *DMARxDscrTab; //以太网DMA接收描述符数据结构体指针
extern ETH_DMADESCTypeDef *DMATxDscrTab; //以太网DMA发送描述符数据结构体指针
extern uint8_t *Rx_Buff; //以太网底层驱动接收buffers指针
extern uint8_t *Tx_Buff; //以太网底层驱动发送buffers指针
extern ETH_DMADESCTypeDef *DMATxDescToSet; //DMA发送描述符追踪指针
extern ETH_DMADESCTypeDef *DMARxDescToGet; //DMA接收描述符追踪指针
extern ETH_DMA_Rx_Frame_infos *DMA_RX_FRAME_infos; //DMA最后接收到的帧信息指针
u8 LAN8720_Init(void);
u8 ethernet_chip_get_speed(void);
u8 ETH_MACDMA_Config(void);
FrameTypeDef ETH_Rx_Packet(void);
u8 ETH_Tx_Packet(u16 FrameLength);
uint8_t ethernet_mem_malloc(void);
void ETH_Mem_Free(void);
#endif
ethernet.c
在ethernet.c中下面几个函数,如表所示。
函数 | 说明 |
---|---|
LAN8720_Init() | LAN8720 初始化函数 |
ETHERNET_NVICConfiguration() | 以太网 DMA 中断优先级配置 |
ethernet_chip_get_speed() | 获取当前连接速度和双工状态 |
ETH_MACDMA_Config() | 以太网 MAC 和 DMA 配置函数 |
ETH_IRQHandler() | 以太网 DMA 接收中断服务函数 |
ETH_Rx_Packet() | 从网卡中接收数据包 |
ETH_Tx_Packet() | 从网卡发送数据包 |
ETH_Mem_Malloc() | Rx_Buff[]、Tx_Buff[]、DMARxDscrTab[]和DMATxDscrTab[]这四个数组申请内存 |
ETH_Mem_Free() | 释 放 Rx_Buff[] 、Tx_Buff[] 、DMARxDscrTab[]和DMATxDscrTab[]这四个数组的内存 |
其中ETH_Rx_Packet()
和ETH_Tx_Packet()
主要替代库函数ETH_Prepare_Transmit_Descriptors()
和ETH_Get_Received_Frame_interrupt()
,同样是为了减小内存开销。全部代码如下
c
#include "ethernet.h"
#include "stm32f4x7_eth.h"
#include "usart.h"
#include "delay.h"
#include "malloc.h"
#include "FreeRTOS.h"
#include "semphr.h"
#include "task.h"
ETH_DMADESCTypeDef *DMARxDscrTab; //以太网DMA接收描述符数据结构体指针
ETH_DMADESCTypeDef *DMATxDscrTab; //以太网DMA发送描述符数据结构体指针
uint8_t *Rx_Buff; //以太网底层驱动接收buffers指针
uint8_t *Tx_Buff; //以太网底层驱动发送buffers指针
extern xSemaphoreHandle s_xSemaphore;
static void ETHERNET_NVICConfiguration(void);
//------------------------------- BSP -------------------------------------//
//LAN8720初始化
//返回值:0,成功;
// 其他,失败
u8 LAN8720_Init(void)
{
u8 rval=0;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOG|RCC_AHB1Periph_GPIOD, ENABLE);//使能GPIO时钟 RMII接口
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); //使能SYSCFG时钟
SYSCFG_ETH_MediaInterfaceConfig(SYSCFG_ETH_MediaInterface_RMII); //MAC和PHY之间使用RMII接口
/*网络引脚设置 RMII接口
ETH_MDIO -------------------------> PA2
ETH_MDC --------------------------> PC1
ETH_RMII_REF_CLK------------------> PA1
ETH_RMII_CRS_DV ------------------> PA7
ETH_RMII_RXD0 --------------------> PC4
ETH_RMII_RXD1 --------------------> PC5
ETH_RMII_TX_EN -------------------> PG11
ETH_RMII_TXD0 --------------------> PG13
ETH_RMII_TXD1 --------------------> PG14
ETH_RESET-------------------------> PD3*/
//配置PA1 PA2 PA7
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource1, GPIO_AF_ETH); //引脚复用到网络接口上
GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_ETH);
GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_ETH);
//配置PC1,PC4 and PC5
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_4 | GPIO_Pin_5;
GPIO_Init(GPIOC, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource1, GPIO_AF_ETH); //引脚复用到网络接口上
GPIO_PinAFConfig(GPIOC, GPIO_PinSource4, GPIO_AF_ETH);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource5, GPIO_AF_ETH);
//配置PG11, PG14 and PG13
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_13 | GPIO_Pin_14;
GPIO_Init(GPIOG, &GPIO_InitStructure);
GPIO_PinAFConfig(GPIOG, GPIO_PinSource11, GPIO_AF_ETH);
GPIO_PinAFConfig(GPIOG, GPIO_PinSource13, GPIO_AF_ETH);
GPIO_PinAFConfig(GPIOG, GPIO_PinSource14, GPIO_AF_ETH);
//配置PD3为推完输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推完输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL ;
GPIO_Init(GPIOD, &GPIO_InitStructure);
LAN8720_RST=0; //硬件复位LAN8720
delay_ms(50);
LAN8720_RST=1; //复位结束
ETHERNET_NVICConfiguration(); //设置中断优先级
rval=ETH_MACDMA_Config(); //配置MAC及DMA
return !rval; //ETH的规则为:0,失败;1,成功;所以要取反一下
}
//以太网中断分组配置
void ETHERNET_NVICConfiguration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = ETH_IRQn; //以太网中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0X06; //中断寄存器组2最高优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0X00;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
//得到8720的速度模式
//返回值:
//001:10M半双工
//101:10M全双工
//010:100M半双工
//110:100M全双工
//其他:错误.
u8 ethernet_chip_get_speed(void)
{
u8 speed;
speed=((ETH_ReadPHYRegister(0x00,31)&0x1C)>>2); //从LAN8720的31号寄存器中读取网络速度和双工模式
return speed;
}
/
//以下部分为STM32F407网卡配置/接口函数.
//初始化ETH MAC层及DMA配置
//返回值:ETH_ERROR,发送失败(0)
// ETH_SUCCESS,发送成功(1)
u8 ETH_MACDMA_Config(void)
{
u8 rval;
ETH_InitTypeDef ETH_InitStructure;
//使能以太网时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC | RCC_AHB1Periph_ETH_MAC_Tx |RCC_AHB1Periph_ETH_MAC_Rx, ENABLE);
ETH_DeInit(); //AHB总线重启以太网
ETH_SoftwareReset(); //软件重启网络
while (ETH_GetSoftwareResetStatus() == SET);//等待软件重启网络完成
ETH_StructInit(Ð_InitStructure); //初始化网络为默认值
///网络MAC参数设置
ETH_InitStructure.ETH_AutoNegotiation = ETH_AutoNegotiation_Enable; //开启网络自适应功能
ETH_InitStructure.ETH_LoopbackMode = ETH_LoopbackMode_Disable; //关闭反馈
ETH_InitStructure.ETH_RetryTransmission = ETH_RetryTransmission_Disable; //关闭重传功能
ETH_InitStructure.ETH_AutomaticPadCRCStrip = ETH_AutomaticPadCRCStrip_Disable; //关闭自动去除PDA/CRC功能
ETH_InitStructure.ETH_ReceiveAll = ETH_ReceiveAll_Disable; //关闭接收所有的帧
ETH_InitStructure.ETH_BroadcastFramesReception = ETH_BroadcastFramesReception_Enable;//允许接收所有广播帧
ETH_InitStructure.ETH_PromiscuousMode = ETH_PromiscuousMode_Disable; //关闭混合模式的地址过滤
ETH_InitStructure.ETH_MulticastFramesFilter = ETH_MulticastFramesFilter_Perfect;//对于组播地址使用完美地址过滤
ETH_InitStructure.ETH_UnicastFramesFilter = ETH_UnicastFramesFilter_Perfect; //对单播地址使用完美地址过滤
#ifdef CHECKSUM_BY_HARDWARE
ETH_InitStructure.ETH_ChecksumOffload = ETH_ChecksumOffload_Enable; //开启ipv4和TCP/UDP/ICMP的帧校验和卸载
#endif
//当我们使用帧校验和卸载功能的时候,一定要使能存储转发模式,存储转发模式中要保证整个帧存储在FIFO中,
//这样MAC能插入/识别出帧校验值,当真校验正确的时候DMA就可以处理帧,否则就丢弃掉该帧
ETH_InitStructure.ETH_DropTCPIPChecksumErrorFrame = ETH_DropTCPIPChecksumErrorFrame_Enable; //开启丢弃TCP/IP错误帧
ETH_InitStructure.ETH_ReceiveStoreForward = ETH_ReceiveStoreForward_Enable; //开启接收数据的存储转发模式
ETH_InitStructure.ETH_TransmitStoreForward = ETH_TransmitStoreForward_Enable; //开启发送数据的存储转发模式
ETH_InitStructure.ETH_ForwardErrorFrames = ETH_ForwardErrorFrames_Disable; //禁止转发错误帧
ETH_InitStructure.ETH_ForwardUndersizedGoodFrames = ETH_ForwardUndersizedGoodFrames_Disable; //不转发过小的好帧
ETH_InitStructure.ETH_SecondFrameOperate = ETH_SecondFrameOperate_Enable; //打开处理第二帧功能
ETH_InitStructure.ETH_AddressAlignedBeats = ETH_AddressAlignedBeats_Enable; //开启DMA传输的地址对齐功能
ETH_InitStructure.ETH_FixedBurst = ETH_FixedBurst_Enable; //开启固定突发功能
ETH_InitStructure.ETH_RxDMABurstLength = ETH_RxDMABurstLength_32Beat; //DMA发送的最大突发长度为32个节拍
ETH_InitStructure.ETH_TxDMABurstLength = ETH_TxDMABurstLength_32Beat; //DMA接收的最大突发长度为32个节拍
ETH_InitStructure.ETH_DMAArbitration = ETH_DMAArbitration_RoundRobin_RxTx_2_1;
rval=ETH_Init(Ð_InitStructure,LAN8720_PHY_ADDRESS); //配置ETH
if(rval==ETH_SUCCESS)//配置成功
{
ETH_DMAITConfig(ETH_DMA_IT_NIS|ETH_DMA_IT_R,ENABLE); //使能以太网接收中断
}
return rval;
}
//以太网中断服务函数
void ETH_IRQHandler(void)
{
portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
// if ( ETH_GetDMAFlagStatus(ETH_DMA_FLAG_R) == SET)
if(ETH_GetRxPktSize(DMARxDescToGet)!=0) //检测是否收到数据包
{
xSemaphoreGiveFromISR( s_xSemaphore, &xHigherPriorityTaskWoken );
}
ETH_DMAClearITPendingBit(ETH_DMA_IT_R);
ETH_DMAClearITPendingBit(ETH_DMA_IT_NIS);
/* Switch tasks if necessary. */
if( xHigherPriorityTaskWoken != pdFALSE )
{
portEND_SWITCHING_ISR( xHigherPriorityTaskWoken );
}
}
//----------------------------- 库函数重定义 ----------------------------------//
//接收一个网卡数据包
//返回值:网络数据包帧结构体
FrameTypeDef ETH_Rx_Packet(void)
{
u32 framelength=0;
FrameTypeDef frame={0,0};
//检查当前描述符,是否属于ETHERNET DMA(设置的时候)/CPU(复位的时候)
if((DMARxDescToGet->StatusÐ_DMARxDesc_OWN)!=(u32)RESET)
{
frame.length=ETH_ERROR;
if ((ETH->DMASRÐ_DMASR_RBUS)!=(u32)RESET)
{
ETH->DMASR = ETH_DMASR_RBUS;//清除ETH DMA的RBUS位
ETH->DMARPDR=0;//恢复DMA接收
}
return frame;//错误,OWN位被设置了
}
if(((DMARxDescToGet->StatusÐ_DMARxDesc_ES)==(u32)RESET)&&
((DMARxDescToGet->Status & ETH_DMARxDesc_LS)!=(u32)RESET)&&
((DMARxDescToGet->Status & ETH_DMARxDesc_FS)!=(u32)RESET))
{
framelength=((DMARxDescToGet->StatusÐ_DMARxDesc_FL)>>ETH_DMARxDesc_FrameLengthShift)-4;//得到接收包帧长度(不包含4字节CRC)
frame.buffer = DMARxDescToGet->Buffer1Addr;//得到包数据所在的位置
}else framelength=ETH_ERROR;//错误
frame.length=framelength;
frame.descriptor=DMARxDescToGet;
//更新ETH DMA全局Rx描述符为下一个Rx描述符
//为下一次buffer读取设置下一个DMA Rx描述符
DMARxDescToGet=(ETH_DMADESCTypeDef*)(DMARxDescToGet->Buffer2NextDescAddr);
return frame;
}
//发送一个网卡数据包
//FrameLength:数据包长度
//返回值:ETH_ERROR,发送失败(0)
// ETH_SUCCESS,发送成功(1)
u8 ETH_Tx_Packet(u16 FrameLength)
{
//检查当前描述符,是否属于ETHERNET DMA(设置的时候)/CPU(复位的时候)
if((DMATxDescToSet->StatusÐ_DMATxDesc_OWN)!=(u32)RESET)return ETH_ERROR;//错误,OWN位被设置了
DMATxDescToSet->ControlBufferSize=(FrameLengthÐ_DMATxDesc_TBS1);//设置帧长度,bits[12:0]
DMATxDescToSet->Status|=ETH_DMATxDesc_LS|ETH_DMATxDesc_FS;//设置最后一个和第一个位段置位(1个描述符传输一帧)
DMATxDescToSet->Status|=ETH_DMATxDesc_OWN;//设置Tx描述符的OWN位,buffer重归ETH DMA
if((ETH->DMASRÐ_DMASR_TBUS)!=(u32)RESET)//当Tx Buffer不可用位(TBUS)被设置的时候,重置它.恢复传输
{
ETH->DMASR=ETH_DMASR_TBUS;//重置ETH DMA TBUS位
ETH->DMATPDR=0;//恢复DMA发送
}
//更新ETH DMA全局Tx描述符为下一个Tx描述符
//为下一次buffer发送设置下一个DMA Tx描述符
DMATxDescToSet=(ETH_DMADESCTypeDef*)(DMATxDescToSet->Buffer2NextDescAddr);
return ETH_SUCCESS;
}
//-------------------------------- 内存申请 ----------------------------------//
//释放ETH 底层驱动申请的内存
void ETH_Mem_Free(void)
{
myfree(SRAMIN,DMARxDscrTab);//释放内存
myfree(SRAMIN,DMATxDscrTab);//释放内存
myfree(SRAMIN,Rx_Buff); //释放内存
myfree(SRAMIN,Tx_Buff); //释放内存
}
//为ETH底层驱动申请内存
//返回值:0,正常
// 其他,失败
uint8_t ETH_Mem_Malloc(void)
{
DMARxDscrTab=mymalloc(SRAMIN,ETH_RXBUFNB*sizeof(ETH_DMADESCTypeDef));//申请内存
DMATxDscrTab=mymalloc(SRAMIN,ETH_TXBUFNB*sizeof(ETH_DMADESCTypeDef));//申请内存
Rx_Buff=mymalloc(SRAMIN,ETH_RX_BUF_SIZE*ETH_RXBUFNB); //申请内存
Tx_Buff=mymalloc(SRAMIN,ETH_TX_BUF_SIZE*ETH_TXBUFNB); //申请内存
if(!DMARxDscrTab||!DMATxDscrTab||!Rx_Buff||!Tx_Buff)
{
ETH_Mem_Free();
return 1; //申请失败
}
return 0; //申请成功
}
此时编译会有一个报错,提示未定义变量s_xSemaphore
,这是后面Lwip移植ethernetif.c
时定义的一个信号量,这时候可以不用管,也可以先屏蔽
三、LWIP 数据包和网络接口管理
3.1 添加LWIP源文件
在工程目录下新建文件夹LWIP ,然后把下载的lwip-1.4.1\src 文件夹复制进去,然后再添加arch文件夹
然后我们需要将中间文件添加到arch 文件夹,总共5个文件cc.h
、lwipopts.h
、perf.h
、sys_arch.h
和sys_arch.c
。这些都可以在ST参考例程STM32F4x7_ETH_LwIP_V1.1.0 里找到,由于都需要修改,建议直接复制附件工程或者在Keil里新建
然后将src\api 文件夹里所有文件,src\core 里的c文件和ipv4 里的所有文件,以及src\netif 里的ethernetif.c
和etharp.c
添加到Keil工程
以及sys_arch.c
,添加进来如下
最后将头文件加进来
3.2 Lwip文件修改
3.2.1 修改cc.h
cc.h 主要完成了协议栈内部使用的数据类型的定义,如果使用操作系统的话还有临界代码区保护等等
c
#ifndef __CC_H__
#define __CC_H__
//#include "cpu.h"
#include "stdio.h"
/*-------------data type------------------------------------------------------*/
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 long u32_t; /* Unsigned 32 bit quantity */
typedef signed long s32_t; /* Signed 32 bit quantity */
typedef u32_t mem_ptr_t; /* Unsigned 32 bit quantity */
typedef int sys_prot_t;
/*-------------critical region protection (depends on uC/OS-II setting)-------*/
#define BYTE_ORDER LITTLE_ENDIAN //小端模式
#if OS_CRITICAL_METHOD == 1
#define SYS_ARCH_DECL_PROTECT(lev)
#define SYS_ARCH_PROTECT(lev) CPU_INT_DIS()
#define SYS_ARCH_UNPROTECT(lev) CPU_INT_EN()
#endif
#if OS_CRITICAL_METHOD == 3 //method 3 is used in this port.
#define SYS_ARCH_DECL_PROTECT(lev) u32_t lev
#define SYS_ARCH_PROTECT(lev) lev = OS_CPU_SR_Save()
#define SYS_ARCH_UNPROTECT(lev) OS_CPU_SR_Restore(lev)
#endif
/*----------------------------------------------------------------------------*/
/* define compiler specific symbols */
#if defined (__ICCARM__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#define PACK_STRUCT_USE_INCLUDES
#elif defined (__CC_ARM)
#define PACK_STRUCT_BEGIN __packed
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__GNUC__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT __attribute__ ((__packed__))
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#elif defined (__TASKING__)
#define PACK_STRUCT_BEGIN
#define PACK_STRUCT_STRUCT
#define PACK_STRUCT_END
#define PACK_STRUCT_FIELD(x) x
#endif
/*---define (sn)printf formatters for these lwip types, for lwip DEBUG/STATS--*/
#define U16_F "4d"
#define S16_F "4d"
#define X16_F "4x"
#define U32_F "8ld"
#define S32_F "8ld"
#define X32_F "8lx"
/*--------------macros--------------------------------------------------------*/
#ifndef LWIP_PLATFORM_ASSERT
#define LWIP_PLATFORM_ASSERT(x) \
do \
{ printf("Assertion \"%s\" failed at line %d in %s\r\n", x, __LINE__, __FILE__); \
} while(0)
#endif
#ifndef LWIP_PLATFORM_DIAG
#define LWIP_PLATFORM_DIAG(x) do {printf x;} while(0)
#endif
#endif /* __CC_H__ */
3.2.2 修改lwipopts.h
在 LWIP 的源码中有个opt.h
的文件,这个文件是裁剪和配置 LWIP 的,不过我们最好不要直接在 opt.h 里面做修改,而是在lwipopts.h
里进行来裁剪与配置
c
#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__
#define SYS_LIGHTWEIGHT_PROT 1
//NO_SYS==1:不使用操作系统
#define NO_SYS 0 //不使用操作系统
//使用4字节对齐模式
#define MEM_ALIGNMENT 4
//MEM_SIZE:heap内存的大小,如果在应用中有大量数据发送的话这个值最好设置大一点
#define MEM_SIZE 16000 //内存堆大小
//MEMP_NUM_PBUF:memp结构的pbuf数量,如果应用从ROM或者静态存储区发送大量数据时,这个值应该设置大一点
#define MEMP_NUM_PBUF 10
//MEMP_NUM_UDP_PCB:UDP协议控制块(PCB)数量.每个活动的UDP"连接"需要一个PCB.
#define MEMP_NUM_UDP_PCB 6
//MEMP_NUM_TCP_PCB:同时建立激活的TCP数量
#define MEMP_NUM_TCP_PCB 10
//MEMP_NUM_TCP_PCB_LISTEN:能够监听的TCP连接数量
#define MEMP_NUM_TCP_PCB_LISTEN 6
//MEMP_NUM_TCP_SEG:最多同时在队列中的TCP段数量
#define MEMP_NUM_TCP_SEG 15
//MEMP_NUM_SYS_TIMEOUT:能够同时激活的timeout个数
#define MEMP_NUM_SYS_TIMEOUT 8
/* ---------- Pbuf选项---------- */
//PBUF_POOL_SIZE:pbuf内存池个数.
#define PBUF_POOL_SIZE 20
//PBUF_POOL_BUFSIZE:每个pbuf内存池大小.
#define PBUF_POOL_BUFSIZE 512
/* ---------- TCP选项---------- */
#define LWIP_TCP 1 //为1是使用TCP
#define TCP_TTL 255//生存时间
/*当TCP的数据段超出队列时的控制位,当设备的内存过小的时候此项应为0*/
#define TCP_QUEUE_OOSEQ 0
//最大TCP分段
#define TCP_MSS (1500 - 40) //TCP_MSS = (MTU - IP报头大小 - TCP报头大小
//TCP发送缓冲区大小(bytes).
#define TCP_SND_BUF (4*TCP_MSS)
//TCP_SND_QUEUELEN: TCP发送缓冲区大小(pbuf).这个值最小为(2 * TCP_SND_BUF/TCP_MSS)
#define TCP_SND_QUEUELEN (2* TCP_SND_BUF/TCP_MSS)
//TCP发送窗口
#define TCP_WND (2*TCP_MSS)
/* ---------- ICMP选项---------- */
#define LWIP_ICMP 1 //使用ICMP协议
/* ---------- DHCP选项---------- */
//当使用DHCP时此位应该为1,LwIP 0.5.1版本中没有DHCP服务.
#define LWIP_DHCP 1
/* ---------- UDP选项 ---------- */
#define LWIP_UDP 1 //使用UDP服务
#define UDP_TTL 255 //UDP数据包生存时间
/* ---------- Statistics options ---------- */
#define LWIP_STATS 0
#define LWIP_PROVIDE_ERRNO 1
/* ---------- 链接回调选项 ---------- */
/* WIP_NETIF_LINK_CALLBACK==1:支持来自接口的回调函数
每当链接改变(例如,向下链接)
*/
#define LWIP_NETIF_LINK_CALLBACK 0
//STM32F4x7允许通过硬件识别和计算IP,UDP和ICMP的帧校验和
#define CHECKSUM_BY_HARDWARE //定义CHECKSUM_BY_HARDWARE,使用硬件帧校验
#ifdef CHECKSUM_BY_HARDWARE
//CHECKSUM_GEN_IP==0: 硬件生成IP数据包的帧校验和
#define CHECKSUM_GEN_IP 0
//CHECKSUM_GEN_UDP==0: 硬件生成UDP数据包的帧校验和
#define CHECKSUM_GEN_UDP 0
//CHECKSUM_GEN_TCP==0: 硬件生成TCP数据包的帧校验和
#define CHECKSUM_GEN_TCP 0
//CHECKSUM_CHECK_IP==0: 硬件检查输入的IP数据包帧校验和
#define CHECKSUM_CHECK_IP 0
//CHECKSUM_CHECK_UDP==0: 硬件检查输入的UDP数据包帧校验和
#define CHECKSUM_CHECK_UDP 0
//CHECKSUM_CHECK_TCP==0: 硬件检查输入的TCP数据包帧校验和
#define CHECKSUM_CHECK_TCP 0
#else
//CHECKSUM_GEN_IP==1: 软件生成IP数据包帧校验和
#define CHECKSUM_GEN_IP 1
// CHECKSUM_GEN_UDP==1: 软件生成UDOP数据包帧校验和
#define CHECKSUM_GEN_UDP 1
//CHECKSUM_GEN_TCP==1: 软件生成TCP数据包帧校验和
#define CHECKSUM_GEN_TCP 1
// CHECKSUM_CHECK_IP==1: 软件检查输入的IP数据包帧校验和
#define CHECKSUM_CHECK_IP 1
// CHECKSUM_CHECK_UDP==1: 软件检查输入的UDP数据包帧校验和
#define CHECKSUM_CHECK_UDP 1
//CHECKSUM_CHECK_TCP==1: 软件检查输入的TCP数据包帧校验和
#define CHECKSUM_CHECK_TCP 1
#endif
/*
----------------------------------------------
---------- SequentialAPI选项----------
----------------------------------------------
*/
//LWIP_NETCONN==1:使能NETCON函数(要求使用api_lib.c)
#define LWIP_NETCONN 1
/*
------------------------------------
---------- Socket API选项----------
------------------------------------
*/
//LWIP_SOCKET==1:使能Socket API(要求使用sockets.c)
#define LWIP_SOCKET 1
#define LWIP_COMPAT_MUTEX 1
#define LWIP_SO_RCVTIMEO 1 //通过定义LWIP_SO_RCVTIMEO使能netconn结构体中recv_timeout,使用recv_timeout可以避免阻塞线程
/*
----------------------------------------
---------- Lwip调试选项----------
----------------------------------------
*/
//#define LWIP_DEBUG 1 //开启DEBUG选项
#define ICMP_DEBUG LWIP_DBG_OFF //开启/关闭ICMPdebug
/*
------------------------------------
---------- httpd options ----------
------------------------------------
*/
/** Set this to 1 to include "fsdata_custom.c" instead of "fsdata.c" for the
* file system (to prevent changing the file included in CVS) */
#define HTTPD_USE_CUSTOM_FSDATA 1
#endif /* __LWIPOPTS_H__ */
3.2.3 修改icmp.c
我们需要修改 icmp.c 文档使其支持硬件帧校验,修改部分如图所示。图中红框部分为 icmp.c 的源码,我们将其注销掉,蓝框部分是我们需要添加进去的代码,这部分代码是由 ST提供的。
c
#ifdef CHECKSUM_BY_HARDWARE
iecho->chksum = 0;
#else
/* adjust the checksum */
if (iecho->chksum >= htons(0xffff - (ICMP_ECHO << 8))) {
iecho->chksum += htons(ICMP_ECHO << 8) + 1;
} else {
iecho->chksum += htons(ICMP_ECHO << 8);
}
#endif
3.2.4 修改sys_arch.h和sys_arch.c
这两个文件主要为协议栈提供邮箱、信号量等机制,实际上就是对RTOS的机制进行一次封装
sys_arch.h
c
#ifndef __ARCH_SYS_ARCH_H__
#define __ARCH_SYS_ARCH_H__
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
void sys_arch_msleep(uint32_t delay_ms);
#define sys_msleep(ms) sys_arch_msleep(ms)
#define SYS_MBOX_NULL (xQueueHandle)0
#define SYS_SEM_NULL (xSemaphoreHandle)0
typedef xSemaphoreHandle sys_sem_t;
typedef xSemaphoreHandle sys_mutex_t;
typedef xQueueHandle sys_mbox_t;
typedef xTaskHandle sys_thread_t;
#endif
sys_arch.c
c
#include "lwip/debug.h"
#include "lwip/def.h"
#include "lwip/sys.h"
#include "lwip/mem.h"
#include "lwip/stats.h"
#include "FreeRTOS.h"
#include "task.h"
//u32_t sys_jiffies(void)
//{
// return xTaskGetTickCount();
//}
//static u16_t s_nextthread = 0;
/* Initialize this module (see description in sys.h) */
void sys_init(void)
{
// keep track of how many threads have been created
// s_nextthread = 0;
}
#if SYS_LIGHTWEIGHT_PROT
sys_prot_t sys_arch_protect(void)
{
vPortEnterCritical();
return 1;
}
void sys_arch_unprotect(sys_prot_t pval)
{
( void ) pval;
vPortExitCritical();
}
#endif /* SYS_LIGHTWEIGHT_PROT */
void sys_sem_signal(sys_sem_t *sem)
{
xSemaphoreGive(*sem);
}
err_t sys_sem_new(sys_sem_t *sem, u8_t count)
{
/* 创建 sem */
if(count <= 1)
{
*sem = xSemaphoreCreateBinary();
if(count == 1)
{
sys_sem_signal(sem);
}
else
{
xSemaphoreTake(*sem,1);
}
}
else
*sem = xSemaphoreCreateCounting(count,count);
#if SYS_STATS
++lwip_stats.sys.sem.used;
if (lwip_stats.sys.sem.max < lwip_stats.sys.sem.used) {
lwip_stats.sys.sem.max = lwip_stats.sys.sem.used;
}
#endif /* SYS_STATS */
if(*sem != SYS_SEM_NULL)
return ERR_OK;
else
{
#if SYS_STATS
++lwip_stats.sys.sem.err;
#endif /* SYS_STATS */
printf("[sys_arch]:new sem fail!\n");
return ERR_MEM;
}
}
u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
{
BaseType_t ret;
portTickType StartTime;
StartTime = xTaskGetTickCount();
if(!timeout) {
/* wait infinite */
ret = xSemaphoreTake(*sem, portMAX_DELAY);
} else {
ret = xSemaphoreTake(*sem, timeout / portTICK_RATE_MS);
}
if (ret == pdTRUE)
{
return ((xTaskGetTickCount()-StartTime)*portTICK_RATE_MS);
}
else
{
/* timed out */
return SYS_ARCH_TIMEOUT;
}
}
void sys_sem_free(sys_sem_t *sem)
{
#if SYS_STATS
--lwip_stats.sys.sem.used;
#endif /* SYS_STATS */
/* 删除 sem */
vSemaphoreDelete(*sem);
*sem = SYS_SEM_NULL;
}
int sys_sem_valid(sys_sem_t *sem)
{
return (*sem != SYS_SEM_NULL);
}
void sys_sem_set_invalid(sys_sem_t *sem)
{
*sem = SYS_SEM_NULL;
}
err_t sys_mbox_new(sys_mbox_t *mbox, int size)
{
(void ) size;
*mbox = xQueueCreate((UBaseType_t)size, sizeof(void *));
if(*mbox == NULL) {
return ERR_MEM;
}
return ERR_OK;
}
void sys_mbox_post(sys_mbox_t *mbox, void *msg)
{
while(xQueueSendToBack(*mbox, &msg, portMAX_DELAY) != pdTRUE);
}
// Try to post the "msg" to the mailbox.
err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
{
if(xQueueSend(*mbox,&msg,0) == pdPASS)
return ERR_OK;
else
return ERR_MEM;
}
u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)
{
BaseType_t ret;
void *msg_dummy;
portTickType StartTime;
StartTime = xTaskGetTickCount();
if (!msg) {
msg = &msg_dummy;
}
if (!timeout) {
/* wait infinite */
ret = xQueueReceive(*mbox, &(*msg), portMAX_DELAY);
} else {
ret = xQueueReceive(*mbox, &(*msg), timeout / portTICK_RATE_MS);
}
if (ret == pdTRUE)
{
return ((xTaskGetTickCount() - StartTime)*portTICK_PERIOD_MS);
}
else
{
/* timed out */
*msg = NULL;
return SYS_ARCH_TIMEOUT;
}
}
u32_t sys_arch_mbox_tryfetch(sys_mbox_t *mbox, void **msg)
{
void *dummyptr;
if ( msg == NULL )
{
msg = &dummyptr;
}
if ( pdTRUE == xQueueReceive( *mbox, &(*msg), 0 ) )
{
return ERR_OK;
}
else
{
return SYS_MBOX_EMPTY;
}
}
void sys_mbox_free(sys_mbox_t *mbox)
{
if( uxQueueMessagesWaiting( *mbox ) )
{
/* Line for breakpoint. Should never break here! */
portNOP();
#if SYS_STATS
lwip_stats.sys.mbox.err++;
#endif /* SYS_STATS */
// TODO notify the user of failure.
}
vQueueDelete(*mbox);
#if SYS_STATS
--lwip_stats.sys.mbox.used;
#endif /* SYS_STATS */
}
int sys_mbox_valid(sys_mbox_t *mbox)
{
if (*mbox == SYS_MBOX_NULL)
return 0;
else
return 1;
}
void sys_mbox_set_invalid(sys_mbox_t *mbox)
{
*mbox = SYS_MBOX_NULL;
}
sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread, void *arg, int stacksize, int prio)
{
xTaskHandle rtos_task;
BaseType_t ret;
ret = xTaskCreate(thread, name, stacksize, arg, prio, &rtos_task);
if(ret != pdPASS)
{
return NULL;
}
return rtos_task;
}
void sys_arch_msleep(uint32_t delay_ms)
{
TickType_t delay_ticks = delay_ms / portTICK_RATE_MS;
vTaskDelay(delay_ticks);
}
//为LWIP提供计时
u32_t sys_now(void){
return xTaskGetTickCount() * portTICK_PERIOD_MS;
}
函数功能如下表
函数名称 | 功能 |
---|---|
sys_mbox_new | 创建消息邮箱 |
sys_mbox_free | 删除一个邮箱 |
sys_mbox_post | 向邮箱投递消息,阻塞 |
sys_mbox_trypost | 尝试向邮箱投递消息,不阻塞 |
sys_arch_mbox_fetch | 获取消息,阻塞 |
sys_arch_mbox_tryfetch | 尝试获取消息,不阻塞 |
sys_mbox_valid | 检查一个邮箱是否有效 |
sys_mbox_set_invalid | 设置一个邮箱无效 |
sys_sem_new | 创建一个信号量 |
sys_arch_sem_wait | 等待一个信号量 |
sys_sem_signal | 释放一个信号量 |
sys_sem_free | 删除一个信号量 |
sys_sem_valid | 查询一个信号量是否有效 |
sys_sem_set_invalid | 设置一个信号量无效 |
sys_thread_new | 创建进程 |
sys_init | 初始化操作系统模拟层 |
sys_msleep | LWIP 延时函数 |
sys_now | 获取当前系统时间 |
3.2.5 修改ethernetif.h和ethernetif.c
ethernetif.h
c
#ifndef __ETHERNETIF_H__
#define __ETHERNETIF_H__
#include "lwip/err.h"
#include "lwip/netif.h"
#define NETIF_IN_TASK_STACK_SIZE ( 1024 )
#define NETIF_IN_TASK_PRIORITY ( 2 )
//网卡的名字
#define IFNAME0 'e'
#define IFNAME1 'n'
err_t ethernetif_init(struct netif *netif);
#endif
ethernetif.c
该文件为网卡驱动文件,用来描述硬件网卡的收发函数及相关信息。该文件定义的函数如下表所示:
函数 | 描述 |
---|---|
low_level_init() | 底层初始化 |
low_level_output() | 数据包发送函数 |
low_level_input() | 获取数据报函数 |
ethernetif_input() | 处理数据包函数 |
ethernetif_init() | 网卡初始化 |
c
#include "netif/ethernetif.h"
#include "ethernet.h"
#include "lwip_app.h"
#include "netif/etharp.h"
#include "lwip/sys.h"
#include "string.h"
#include "FreeRTOS.h"
#include "semphr.h"
#include "task.h"
/* 定义一个信号量 */
xSemaphoreHandle s_xSemaphore = NULL;
/* Forward declarations. */
void ethernetif_input(void *pParams);
//由ethernetif_init()调用用于初始化硬件
//netif:网卡结构体指针
//返回值:ERR_OK,正常
// 其他,失败
static void low_level_init(struct netif *netif)
{
#ifdef CHECKSUM_BY_HARDWARE
int i;
#endif
netif->hwaddr_len = ETHARP_HWADDR_LEN; //设置MAC地址长度,为6个字节
//初始化MAC地址,设置什么地址由用户自己设置,但是不能与网络中其他设备MAC地址重复
netif->hwaddr[0]=MAC_ADDR0;
netif->hwaddr[1]=MAC_ADDR1;
netif->hwaddr[2]=MAC_ADDR2;
netif->hwaddr[3]=MAC_ADDR3;
netif->hwaddr[4]=MAC_ADDR4;
netif->hwaddr[5]=MAC_ADDR5;
netif->mtu=1500; //最大允许传输单元,允许该网卡广播和ARP功能
netif->flags = NETIF_FLAG_BROADCAST|NETIF_FLAG_ETHARP|NETIF_FLAG_LINK_UP;
ETH_MACAddressConfig(ETH_MAC_Address0, netif->hwaddr); //向STM32F4的MAC地址寄存器中写入MAC地址
ETH_DMATxDescChainInit(DMATxDscrTab, Tx_Buff, ETH_TXBUFNB);
ETH_DMARxDescChainInit(DMARxDscrTab, Rx_Buff, ETH_RXBUFNB);
#ifdef CHECKSUM_BY_HARDWARE //使用硬件帧校验
for(i=0;i<ETH_TXBUFNB;i++) //使能TCP,UDP和ICMP的发送帧校验,TCP,UDP和ICMP的接收帧校验在DMA中配置了
{
ETH_DMATxDescChecksumInsertionConfig(&DMATxDscrTab[i], ETH_DMATxDesc_ChecksumTCPUDPICMPFull);
}
#endif
/* 创建一个信号量 */
if(s_xSemaphore == NULL)
{
s_xSemaphore = xSemaphoreCreateBinary();
xSemaphoreTake( s_xSemaphore, 0);
}
/* 创建处理ETH_MAC的任务 */
sys_thread_new("eth_thread",
ethernetif_input, /* 任务入口函数 */
netif, /* 任务入口函数参数 */
NETIF_IN_TASK_STACK_SIZE,/* 任务栈大小 */
NETIF_IN_TASK_PRIORITY); /* 任务的优先级 */
ETH_Start(); //开启MAC和DMA
}
//用于发送数据包的最底层函数(lwip通过netif->linkoutput指向该函数)
//netif:网卡结构体指针
//p:pbuf数据结构体指针
//返回值:ERR_OK,发送正常
// ERR_MEM,发送失败
static err_t low_level_output(struct netif *netif, struct pbuf *p)
{
u8 res;
struct pbuf *q;
int l = 0;
u8 *buffer=(u8 *)(DMATxDescToSet->Buffer1Addr); //获取当前要发送的DMA描述符中的缓冲区地址
for(q=p;q!=NULL;q=q->next)
{
memcpy((u8_t*)&buffer[l], q->payload, q->len);
l=l+q->len;
}
res=ETH_Tx_Packet(l); //调用ETH_Tx_Packet函数发送数据
if(res==ETH_ERROR)return ERR_MEM;//返回错误状态
return ERR_OK;
}
///用于接收数据包的最底层函数
//neitif:网卡结构体指针
//返回值:pbuf数据结构体指针
static struct pbuf * low_level_input(struct netif *netif)
{
struct pbuf *p= NULL, *q;
u16_t len;
int l =0;
FrameTypeDef frame;
u8 *buffer;
frame=ETH_Rx_Packet();
len=frame.length;//得到包大小
buffer=(u8 *)frame.buffer;//得到包数据地址
if(len > 0)
{
p=pbuf_alloc(PBUF_RAW,len,PBUF_POOL);//pbufs内存池分配pbuf
}
if(p!=NULL)
{
/* 将接收描述符中Rx Buffer的数据拷贝到pbuf中 */
for(q=p;q!=NULL;q=q->next)
{
memcpy((u8_t*)q->payload,(u8_t*)&buffer[l], q->len);
l=l+q->len;
}
}
frame.descriptor->Status=ETH_DMARxDesc_OWN;//设置Rx描述符OWN位,buffer重归ETH DMA
if((ETH->DMASRÐ_DMASR_RBUS)!=(u32)RESET)//当Rx Buffer不可用位(RBUS)被设置的时候,重置它.恢复传输
{
ETH->DMASR=ETH_DMASR_RBUS;//重置ETH DMA RBUS位
ETH->DMARPDR=0;//恢复DMA接收
}
return p;
}
void ethernetif_input(void *pParams)
{
struct netif *netif;
struct pbuf *p = NULL;
netif = (struct netif*) pParams;
LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
while(1)
{
if (xSemaphoreTake( s_xSemaphore, portMAX_DELAY)==pdTRUE)
{
/* move received packet into a new pbuf */
taskENTER_CRITICAL();
TRY_GET_NEXT_FRAME:
p = low_level_input( netif );//调用low_level_input函数接收数据
taskEXIT_CRITICAL();
if(p != NULL)
{
taskENTER_CRITICAL();
if (ERR_OK != netif->input( p, netif))//调用netif结构体中的input字段(一个函数)来处理数据包
{
LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
pbuf_free(p);
p = NULL;
}
else
{
xSemaphoreTake( s_xSemaphore, 0);
goto TRY_GET_NEXT_FRAME;
}
taskEXIT_CRITICAL();
}
}
}
}
//使用low_level_init()函数来初始化网络
//netif:网卡结构体指针
//返回值:ERR_OK,正常
// 其他,失败
err_t ethernetif_init(struct netif *netif)
{
LWIP_ASSERT("netif!=NULL",(netif!=NULL));
#if LWIP_NETIF_HOSTNAME //LWIP_NETIF_HOSTNAME
netif->hostname="lwip"; //初始化名称
#endif
netif->name[0]=IFNAME0; //初始化变量netif的name字段
netif->name[1]=IFNAME1; //在文件外定义这里不用关心具体值
netif->output=etharp_output;//IP层发送数据包函数
netif->linkoutput=low_level_output;//ARP模块发送数据包函数
low_level_init(netif); //底层硬件初始化函数
return ERR_OK;
}
这里就能看到2.3小节报错未定义变量xSemaphoreHandle s_xSemaphore = NULL 在这里定义了。此时再编译发现两个报警,不用管
四、添加应用程序
4.1 添加Lwip应用
lwip_app.h
c
#ifndef _LWIP_COMM_H
#define _LWIP_COMM_H
#include "ethernet.h"
#define LWIP_MAX_DHCP_TRIES 4 //DHCP服务器最大重试次数
typedef void (*display_fn)(uint8_t index);
//lwip控制结构体
typedef struct
{
uint8_t mac[6]; /* MAC地址 */
uint8_t remoteip[4]; /* 远端主机IP地址 */
uint8_t ip[4]; /* 本机IP地址 */
uint8_t netmask[4]; /* 子网掩码 */
uint8_t gateway[4]; /* 默认网关的IP地址 */
uint8_t dhcpstatus; /* dhcp状态 */
/* 0, 未获取DHCP地址;*/
/* 1, 进入DHCP获取状态*/
/* 2, 成功获取DHCP地址*/
/* 0XFF,获取失败 */
uint8_t link_status; /* 连接状态 */
display_fn lwip_display_fn; /* 显示函数指针 */
}__lwip_dev;
extern __lwip_dev g_lwipdev; //lwip控制结构体
uint8_t lwip_comm_init(void);
#endif
lwip_app.c
主要是初始化和开启DHCP
c
#include "lwip_app.h"
#include "netif/etharp.h"
#include "lwip/dhcp.h"
#include "lwip/mem.h"
#include "lwip/memp.h"
#include "lwip/init.h"
#include "ethernetif.h"
#include "lwip/timers.h"
#include "lwip/tcp_impl.h"
#include "lwip/ip_frag.h"
#include "lwip/tcpip.h"
#include "malloc.h"
#include "delay.h"
#include "usart.h"
#include <stdio.h>
__lwip_dev g_lwipdev; //lwip控制结构体
struct netif g_lwip_netif; //定义一个全局的网络接口
/* DHCP线程配置 */
#define LWIP_DHCP_TASK_PRIO 4 /* 任务优先级 */
#define LWIP_DHCP_STK_SIZE 128 * 2 /* 任务堆栈大小 */
void lwip_periodic_handle(void *argument); /* DHCP线程 */
//extern u32 memp_get_memorysize(void); //在memp.c里面定义
//extern u8_t *memp_memory; //在memp.c里面定义.
//extern u8_t *ram_heap; //在mem.c里面定义.
u32 TCPTimer=0; //TCP查询计时器
u32 ARPTimer=0; //ARP查询计时器
u32 lwip_localtime; //lwip本地时间计数器,单位:ms
#if LWIP_DHCP
u32 DHCPfineTimer=0; //DHCP精细处理计时器
u32 DHCPcoarseTimer=0; //DHCP粗糙处理计时器
#endif
//lwip 默认IP设置
//lwipx:lwip控制结构体指针
void lwip_comm_default_ip_set(__lwip_dev *lwipx)
{
u32 sn0;
sn0=*(vu32*)(0x1FFF7A10);//获取STM32的唯一ID的前24位作为MAC地址后三字节
//默认远端IP为:192.168.1.100
lwipx->remoteip[0]=192;
lwipx->remoteip[1]=168;
lwipx->remoteip[2]=1;
lwipx->remoteip[3]=104;
//MAC地址设置(高三字节固定为:2.0.0,低三字节用STM32唯一ID)
lwipx->mac[0]=2;//高三字节(IEEE称之为组织唯一ID,OUI)地址固定为:2.0.0
lwipx->mac[1]=0;
lwipx->mac[2]=0;
lwipx->mac[3]=(sn0>>16)&0XFF;//低三字节用STM32的唯一ID
lwipx->mac[4]=(sn0>>8)&0XFFF;;
lwipx->mac[5]=sn0&0XFF;
//默认本地IP为:192.168.1.30
lwipx->ip[0]=192;
lwipx->ip[1]=168;
lwipx->ip[2]=1;
lwipx->ip[3]=30;
//默认子网掩码:255.255.255.0
lwipx->netmask[0]=255;
lwipx->netmask[1]=255;
lwipx->netmask[2]=255;
lwipx->netmask[3]=0;
//默认网关:192.168.1.1
lwipx->gateway[0]=192;
lwipx->gateway[1]=168;
lwipx->gateway[2]=1;
lwipx->gateway[3]=1;
lwipx->dhcpstatus=0;//没有DHCP
}
/* 如果使能DHCP */
#if LWIP_DHCP
/**
* @breif DHCP进程
* @param argument:传入的形参
* @retval 无
*/
void lwip_periodic_handle(void *argument)
{
u32 ip=0,netmask=0,gw=0;
dhcp_start(&g_lwip_netif);//开启DHCP
g_lwipdev.dhcpstatus=0; //正在DHCP
printf("正在查找DHCP服务器,请稍等...........\r\n");
while(1)
{
printf("正在获取地址...\r\n");
ip=g_lwip_netif.ip_addr.addr; //读取新IP地址
netmask=g_lwip_netif.netmask.addr;//读取子网掩码
gw=g_lwip_netif.gw.addr; //读取默认网关
if(ip!=0) //当正确读取到IP地址的时候
{
g_lwipdev.dhcpstatus=2; //DHCP成功
printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n",g_lwipdev.mac[0],g_lwipdev.mac[1],g_lwipdev.mac[2],g_lwipdev.mac[3],g_lwipdev.mac[4],g_lwipdev.mac[5]);
//解析出通过DHCP获取到的IP地址
g_lwipdev.ip[3]=(uint8_t)(ip>>24);
g_lwipdev.ip[2]=(uint8_t)(ip>>16);
g_lwipdev.ip[1]=(uint8_t)(ip>>8);
g_lwipdev.ip[0]=(uint8_t)(ip);
printf("通过DHCP获取到IP地址..............%d.%d.%d.%d\r\n",g_lwipdev.ip[0],g_lwipdev.ip[1],g_lwipdev.ip[2],g_lwipdev.ip[3]);
//解析通过DHCP获取到的子网掩码地址
g_lwipdev.netmask[3]=(uint8_t)(netmask>>24);
g_lwipdev.netmask[2]=(uint8_t)(netmask>>16);
g_lwipdev.netmask[1]=(uint8_t)(netmask>>8);
g_lwipdev.netmask[0]=(uint8_t)(netmask);
printf("通过DHCP获取到子网掩码............%d.%d.%d.%d\r\n",g_lwipdev.netmask[0],g_lwipdev.netmask[1],g_lwipdev.netmask[2],g_lwipdev.netmask[3]);
//解析出通过DHCP获取到的默认网关
g_lwipdev.gateway[3]=(uint8_t)(gw>>24);
g_lwipdev.gateway[2]=(uint8_t)(gw>>16);
g_lwipdev.gateway[1]=(uint8_t)(gw>>8);
g_lwipdev.gateway[0]=(uint8_t)(gw);
printf("通过DHCP获取到的默认网关..........%d.%d.%d.%d\r\n",g_lwipdev.gateway[0],g_lwipdev.gateway[1],g_lwipdev.gateway[2],g_lwipdev.gateway[3]);
break;
}else if(g_lwip_netif.dhcp->tries>LWIP_MAX_DHCP_TRIES) //通过DHCP服务获取IP地址失败,且超过最大尝试次数
{
g_lwipdev.dhcpstatus=0XFF;//DHCP失败.
//使用静态IP地址
IP4_ADDR(&(g_lwip_netif.ip_addr),g_lwipdev.ip[0],g_lwipdev.ip[1],g_lwipdev.ip[2],g_lwipdev.ip[3]);
IP4_ADDR(&(g_lwip_netif.netmask),g_lwipdev.netmask[0],g_lwipdev.netmask[1],g_lwipdev.netmask[2],g_lwipdev.netmask[3]);
IP4_ADDR(&(g_lwip_netif.gw),g_lwipdev.gateway[0],g_lwipdev.gateway[1],g_lwipdev.gateway[2],g_lwipdev.gateway[3]);
printf("DHCP服务超时,使用静态IP地址!\r\n");
printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n",g_lwipdev.mac[0],g_lwipdev.mac[1],g_lwipdev.mac[2],g_lwipdev.mac[3],g_lwipdev.mac[4],g_lwipdev.mac[5]);
printf("静态IP地址........................%d.%d.%d.%d\r\n",g_lwipdev.ip[0],g_lwipdev.ip[1],g_lwipdev.ip[2],g_lwipdev.ip[3]);
printf("子网掩码..........................%d.%d.%d.%d\r\n",g_lwipdev.netmask[0],g_lwipdev.netmask[1],g_lwipdev.netmask[2],g_lwipdev.netmask[3]);
printf("默认网关..........................%d.%d.%d.%d\r\n",g_lwipdev.gateway[0],g_lwipdev.gateway[1],g_lwipdev.gateway[2],g_lwipdev.gateway[3]);
break;
}
delay_ms(250); //延时250ms
}
dhcp_stop(&g_lwip_netif); //关闭DHCP
}
#endif
//LWIP初始化(LWIP启动的时候使用)
//返回值:0,成功
// 1,内存错误
// 2,LAN8720初始化失败
// 3,网卡添加失败.
u8 lwip_comm_init(void)
{
struct netif *netif_init_flag; //调用netif_add()函数时的返回值,用于判断网络初始化是否成功
struct ip_addr ipaddr; //ip地址
struct ip_addr netmask; //子网掩码
struct ip_addr gw; //默认网关
if(ETH_Mem_Malloc())return 1; //内存申请失败
if(LAN8720_Init())return 2; //初始化LAN8720失败
tcpip_init(NULL,NULL); //初始化tcp ip内核,该函数里面会创建tcpip_thread内核任务
lwip_comm_default_ip_set(&g_lwipdev); //设置默认IP等信息
#if LWIP_DHCP //使用动态IP
ipaddr.addr = 0;
netmask.addr = 0;
gw.addr = 0;
#else //使用静态IP
IP4_ADDR(&ipaddr,g_lwipdev.ip[0],g_lwipdev.ip[1],g_lwipdev.ip[2],g_lwipdev.ip[3]);
IP4_ADDR(&netmask,g_lwipdev.netmask[0],g_lwipdev.netmask[1] ,g_lwipdev.netmask[2],g_lwipdev.netmask[3]);
IP4_ADDR(&gw,g_lwipdev.gateway[0],g_lwipdev.gateway[1],g_lwipdev.gateway[2],g_lwipdev.gateway[3]);
printf("网卡en的MAC地址为:................%d.%d.%d.%d.%d.%d\r\n",g_lwipdev.mac[0],g_lwipdev.mac[1],g_lwipdev.mac[2],g_lwipdev.mac[3],g_lwipdev.mac[4],g_lwipdev.mac[5]);
printf("静态IP地址........................%d.%d.%d.%d\r\n",g_lwipdev.ip[0],g_lwipdev.ip[1],g_lwipdev.ip[2],g_lwipdev.ip[3]);
printf("子网掩码..........................%d.%d.%d.%d\r\n",g_lwipdev.netmask[0],g_lwipdev.netmask[1],g_lwipdev.netmask[2],g_lwipdev.netmask[3]);
printf("默认网关..........................%d.%d.%d.%d\r\n",g_lwipdev.gateway[0],g_lwipdev.gateway[1],g_lwipdev.gateway[2],g_lwipdev.gateway[3]);
#endif
netif_init_flag=netif_add(&g_lwip_netif,&ipaddr,&netmask,&gw,NULL,ðernetif_init,&tcpip_input);//向网卡列表中添加一个网口
#if LWIP_DHCP //如果使用DHCP的话
g_lwipdev.dhcpstatus=0; //DHCP标记为0
dhcp_start(&g_lwip_netif); //开启DHCP服务
#endif
if(netif_init_flag==NULL)return 3;//网卡添加失败
else//网口添加成功后,设置netif为默认值,并且打开netif网口
{
netif_set_default(&g_lwip_netif); //设置netif为默认网口
if (netif_is_link_up(&g_lwip_netif))
{
netif_set_up(&g_lwip_netif); /* 打开netif网口 */
}
else
{
netif_set_down(&g_lwip_netif);
}
}
#if LWIP_DHCP /* 如果使用DHCP的话 */
g_lwipdev.dhcpstatus = 0; /* DHCP标记为0 */
/* DHCP轮询任务 */
sys_thread_new("eth_dhcp",
lwip_periodic_handle, /* 任务入口函数 */
&g_lwip_netif, /* 任务入口函数参数 */
LWIP_DHCP_STK_SIZE, /* 任务栈大小 */
LWIP_DHCP_TASK_PRIO); /* 任务的优先级 */
#endif
return 0;//操作OK.
}
4.2 添加FreeRTOS应用
在主函数main.c
里创建FreeRTOS任务
c
#include "main.h"
#include "malloc.h"
#include "FreeRTOS.h"
#include "task.h"
#include "key.h"
#include "led.h"
#include "lwip_app.h"
//------------------------ 任务相关定义 ---------------------//
//任务优先级
#define START_TASK_PRIO 5
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务优先级
#define KEY_TASK_PRIO 10
//任务堆栈大小
#define KEY_STK_SIZE 128
//任务句柄
TaskHandle_t KeyTask_Handler;
//任务优先级
#define TASK1_TASK_PRIO 11
//任务堆栈大小
#define TASK1_STK_SIZE 128
//任务句柄
TaskHandle_t LedTask_Handler;
//------------------------ 任务函数 ---------------------//
//led任务函数
void led_task(void *pvParameters)
{
u8 task1_num=0;
while(1)
{
task1_num++; //任务执1行次数加1 注意task1_num1加到255的时候会清零!!
LED0=!LED0;
printf("任务1已经执行:%d次\r\n",task1_num);
vTaskDelay(1000); //延时1s,也就是1000个时钟节拍
}
}
//key任务函数
void key_task(void *pvParameters)
{
u8 key;
while(1)
{
key=KEY_Scan(0);
switch(key)
{
case KEY0_PRES:
vTaskSuspend(LedTask_Handler);//挂起任务1
printf("挂起任务1的运行!\r\n");
break;
case KEY1_PRES:
vTaskResume(LedTask_Handler); //恢复任务1
printf("恢复任务1的运行!\r\n");
break;
}
vTaskDelay(10); //延时10ms
}
}
//开始任务任务函数
void start_task(void *pvParameters)
{
uint8_t speed;
speed = ethernet_chip_get_speed(); /* 得到网速 */
printf("speed:%d",speed);
while (lwip_comm_init() != 0)
{
delay_ms(500);
}
taskENTER_CRITICAL(); //进入临界区
//创建KEY任务
xTaskCreate((TaskFunction_t )key_task,
(const char* )"key_task",
(uint16_t )KEY_STK_SIZE,
(void* )NULL,
(UBaseType_t )KEY_TASK_PRIO,
(TaskHandle_t* )&KeyTask_Handler);
//创建TASK1任务
xTaskCreate((TaskFunction_t )led_task,
(const char* )"task1_task",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_TASK_PRIO,
(TaskHandle_t* )&LedTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
uart_init(115200);
delay_init(168);
KEY_Init();
LED_Init();
my_mem_init(SRAMIN); //初始化内部内存池
my_mem_init(SRAMCCM); //初始化CCM内存池
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}