STM32F407VET6基于FreeRTOS实时操作系统和LAN8720⽹卡移
植LwIP协议栈
本次实验是在STM32F407VET6单⽚机上实现FreeRTOS实时操作系统加LwIP协议栈驱动LAN8720⽹卡,板⼦是购买的最⼩系统开发板,⽹卡是购买的LAN8720模块。使⽤的LwIP内核版本为 lwip-1.4.1,FreeRTOS内核版本为 FreeRTOSv10.2.1。使⽤上⼀篇博客中移植好的FreeRTOS⼯程。
1、STM32F407VET6单⽚机引脚与LAN8720⽹卡的物理连接如下:
a、ETH_RMII_REF_CLK-------> PA1--------->nINT/RETCLK
b、ETH_MDIO --------------------> PA2--------->MDIO
c、ETH_RMII_CRS_DV --------> PA7--------->CRS
d、ETH_RMII_TX_EN ----------> PB11------->TX_EN
e、ETH_RMII_TXD0 ------------> PB12------->TX0
f、ETH_RMII_TXD1 -------------> PB13------->TX1
g、ETH_RESET-------------------> PC0-------->NC
h、ETH_MDC ---------------------> PC1-------->MDC
i、ETH_RMII_RXD0 -------------> PC4-------->RX0
j、ETH_RMII_RXD1 -------------> PC5-------->RX1
注:在⽹卡模块上RESET引脚为NC,未连接,但在实际项⽬中把RESET引脚连接起来使⽤效果更好,LAN8720⽹卡的电源建议加⼀个电源控制,如:三极管或MOS管,在复位前先断⼀下电源,再复位,因为实际中我遇到过软复位不成功的情况。
OK到这⾥,硬件操作完成,接下来就是软件的事情了。
2、LwIP驱动框架
在项⽬中LwIP与FreeRTOS和LAN8720以太⽹之间的⽂件关系如下:
a、FreeRTOS与LwIP协议栈之间主要通过sys_arch.c和sys_arch.h⽂件连接起来的,这两个⽂件中主要实现了对FreeRTOS的API封装。其中sys_arch.c中实现的函数在sys.h头⽂件中全部给声明好了,我们只需实现出来即可。
b、LAN8720以太⽹卡和LwIP协议栈之间主要通过sys_eth.c和sys_eth.h⽂件连接起来的,这两个⽂件主要实现了LwIP协议栈对以太⽹⼝操作的API函数,⽐如,以太⽹的底层收发函数、以太⽹中断等等。
移植LwIP总共需要修改或新建⼋个⽂件,其中上⾯的四个⽂件为主要的接⼝⽂件,其余四个⽂件为辅助⽂件,分别为:cc.h、cpu.h、perf.h、lwipopts.h。
c、cc.h主要完成了LwIP协议栈内部使⽤的数据类型的定义,如果使⽤了操作系统的话,就还包含了代码临界保护的API等等。
d、cpu.h是和CPU相关的⼀个头⽂件,内部主要是定义了CPU⼤⼩端模式。
e、perf.h是和系统测量、统计相关的⽂件,我们不使⽤任何的测量和统计,因此⽂件内部⽆需做什么操作。
f、lwipopts.h是⽤来裁剪和配置LwIP的⽂件,如果我们想要使⽤LwIP 中的什么功能,就只要在这个⽂件中配置就⾏了。
注:上⾯的⼋个⽂件全部保存在LwIP\arch⽬录下,如果⼿上没有这些⽂件时,可以新建⽂件,然后将下⾯的代码拷贝到⽂件中直接使⽤就好了,⽆需做其它修改。
3、LwIP内核源码⽂件拷贝
在⼯程⽬录下新建⼀个LwIP⽂件夹,在LwIP⽂件夹中再新建app、arch两个⽂件夹。然后将下载的LwIP内核源码也直接解压到LwIP⽬录下,如下图所⽰:
(注:arch⽂件夹的名字必须是这个,不能使⽤其它名字,因为在LwIP内核源码中使⽤了该⽬录下的CPU接⼝⽂件,arch⽬录下存放的都是接⼝⽂件,包含和CPU的相关的接⼝⽂件)
然后在arch⽂件中新建如下⼋个⽂件:
4、LwIP移植相关代码的实现如下:
a、cc.h⽂件
#ifndef __CC_H
#define __CC_H
#include "cpu.h"
#include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
//定义与平台⽆关的数据类型
typedef unsigned char u8_t; //⽆符号8位整数
typedef signed char s8_t; //有符号8位整数
typedef unsigned short u16_t; //⽆符号16位整数
typedef signed short s16_t; //有符号16位整数
typedef unsigned long u32_t; //⽆符号32位整数
typedef signed long s32_t; //有符号32位整数
typedef u32_t mem_ptr_t; //内存地址型数据
typedef int sys_prot_t; //临界保护型数据
//使⽤操作系统时的临界区保护
/
/ SCB_ICSR_REG 定义的寄存器是⽤于区分当前是任务级还是中断级的,主要在Enter_Critical()与Exit_Critical()中使⽤#define SCB_ICSR_REG (*((volatile u32_t *)0xE000ED04))
// Enter_Critical()与Exit_Critical()这两个函数是在sys_arch.c中实现的,所以此处加extern声明为外部函数
extern u32_t Enter_Critical(void); // ⽤于声明进⼊保护临界区
extern void Exit_Critical(u32_t lev); // 声明退出保护临界区
#define SYS_ARCH_DECL_PROTECT(lev) u32_t lev
#define SYS_ARCH_PROTECT(lev) lev=Enter_Critical()
#define SYS_ARCH_UNPROTECT(lev) Exit_Critical(lev)
//根据不同的编译器定义⼀些符号
#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
//LWIP⽤printf调试时使⽤到的⼀些类型
#define U16_F "4d"
#define S16_F "4d"
#define X16_F "4x"
#define U32_F "8ld"
#define S32_F "8ld"
#define X32_F "8lx"
//宏定义
#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
b、cpu.h⽂件
#ifndef __CPU_H
#define __CPU_H
#define BYTE_ORDER LITTLE_ENDIAN // STM32单⽚机是⼩端模式
#endif
c、perf.h⽂件
#ifndef __PERF_H
#define __PERF_H
#define PERF_START /* null definition */
#define PERF_STOP(x) /* null definition */
#endif
d、lwipopts.h⽂件
#ifndef __LWIPOPTS_H
#define __LWIPOPTS_H
//线程优先级
#ifndef TCPIP_THREAD_PRIO
#define TCPIP_THREAD_PRIO (configMAX_PRIORITIES - 1) // 定义内核任务的优先级为最⾼优先级
#endif
#undef DEFAULT_THREAD_PRIO
#define DEFAULT_THREAD_PRIO 2
#define SYS_LIGHTWEIGHT_PROT 1 //为1时使⽤实时操作系统的轻量级保护,保护关键代码不被中断打断
#define NO_SYS 0 //使⽤操作系统
#define MEM_ALIGNMENT 4 //使⽤4字节对齐模式
#define MEM_SIZE 16000 //内存堆heap⼤⼩
#define MEMP_NUM_PBUF 20 //MEMP_NUM_PBUF:memp结构的pbuf数量,如果应⽤从ROM或者静态存储区发送⼤量数据时,这个值应该设置⼤⼀点
#define MEMP_NUM_UDP_PCB 6 //MEMP_NUM_UDP_PCB:UDP协议控制块(PCB)数量.每个活动的UDP"连接"需要⼀个PCB.
#define MEMP_NUM_TCP_PCB 10 //MEMP_NUM_TCP_PCB:同时建⽴激活的TCP数量
#define MEMP_NUM_TCP_PCB_LISTEN 6 //MEMP_NUM_TCP_PCB_LISTEN:能够监听的TCP连接数量
#define MEMP_NUM_TCP_SEG 15 //MEMP_NUM_TCP_SEG:最多同时在队列中的TCP段数量
#define MEMP_NUM_SYS_TIMEOUT 8 //MEMP_NUM_SYS_TIMEOUT:能够同时激活的timeout个数
//pbuf选项
#define PBUF_POOL_SIZE 20 //PBUF_POOL_SIZE:pbuf内存池个数
#define PBUF_POOL_BUFSIZE 512 //PBUF_POOL_BUFSIZE:每个pbuf内存池⼤⼩
#define LWIP_TCP 1 //使⽤TCP
#define TCP_TTL 255 //⽣存时间
#undef TCP_QUEUE_OOSEQ
#define TCP_QUEUE_OOSEQ 0 //当TCP的数据段超出队列时的控制位,当设备的内存过⼩的时候此项应为0
#undef TCPIP_MBOX_SIZE
#define TCPIP_MBOX_SIZE MAX_QUEUE_ENTRIES //tcpip创建主线程时的消息邮箱⼤⼩
#undef DEFAULT_TCP_RECVMBOX_SIZE
#define DEFAULT_TCP_RECVMBOX_SIZE MAX_QUEUE_ENTRIES
#undef DEFAULT_ACCEPTMBOX_SIZE
#define DEFAULT_ACCEPTMBOX_SIZE MAX_QUEUE_ENTRIES
#define TCP_MSS (1500 - 40) //最⼤TCP分段,TCP_MSS = (MTU - IP报头⼤⼩ - TCP报头⼤⼩
#define TCP_SND_BUF (4*TCP_MSS) //TCP发送缓冲区⼤⼩(bytes).
#define TCP_SND_QUEUELEN (2* TCP_SND_BUF/TCP_MSS) //TCP_SND_QUEUELEN: TCP发送缓冲区⼤⼩(pbuf).这个值最⼩为(2 * TCP_SND_BUF/TCP_MSS)
#define TCP_WND (2*TCP_MSS) //TCP发送窗⼝
#define LWIP_ICMP 1 //使⽤ICMP协议
#define LWIP_DHCP 0 //禁⽤DHCP
#define LWIP_UDP 1 //使⽤UDP服务
#define UDP_TTL 255 //UDP数据包⽣存时间
#define LWIP_STATS 0
#define LWIP_PROVIDE_ERRNO 1
//帧校验和选项,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
#define LWIP_NETCONN 1 //LWIP_NETCONN==1:使能NETCON函数(要求使⽤api_lib.c)
#define LWIP_SOCKET 1 //LWIP_SOCKET==1:使能Sicket API(要求使⽤sockets.c)
#define LWIP_COMPAT_MUTEX 1
#define LWIP_SO_RCVTIMEO 1 //通过定义LWIP_SO_RCVTIMEO使能netconn结构体中recv_timeout,使⽤recv_timeout可以避免阻塞线程
//有关系统的选项
#define TCPIP_THREAD_STACKSIZE 1000 //内核任务堆栈⼤⼩
#define DEFAULT_UDP_RECVMBOX_SIZE 2000
#define DEFAULT_THREAD_STACKSIZE 512
//LWIP调试选项
#define LWIP_DEBUG 0 //关闭DEBUG选项
#define ICMP_DEBUG LWIP_DBG_OFF //开启/关闭ICMPdebug
#endif
e、sys_arch.h⽂件
#ifndef __SYS_ARCH_H
#define __SYS_ARCH_H
#include "FreeRTOS.h"
#include "semphr.h"
#include "queue.h"
#define MAX_QUEUES 10 // 消息邮箱的数量
#define MAX_QUEUE_ENTRIES 20 // 每个消息邮箱的⼤⼩
// LwIP消息邮箱结构体
typedef struct
{
QueueHandle_t xQueue; // 消息列队
}lwip_mbox;
typedef lwip_mbox sys_mbox_t; // LwIP使⽤的消息邮箱类型
typedef QueueHandle_t sys_mutex_t; // LwIP使⽤的互斥信号量类型
typedef SemaphoreHandle_t sys_sem_t; // LwIP使⽤的信号量类型
typedef unsigned char sys_thread_t; // LwIP线程错误类型
#endif
f、sys_arch.c⽂件
#include "lwip/def.h"
#include "lwip/sys.h"
#include "lwip/mem.h"
#include "sys_arch.h"
const u32_t NullValue; // 定义⼀个常量值,⽤于空指针
/********************************************************
* 函数功能:创建⼀个消息邮箱
* 形参:mbox:消息邮箱
size:邮箱⼤⼩
* 返回值:ERR_OK=创建成功,其他=创建失败
********************************************************/
err_t sys_mbox_new( sys_mbox_t *mbox, int size)
{
if(size > MAX_QUEUE_ENTRIES)
{
size = MAX_QUEUE_ENTRIES; // 消息列队中的消息数⽬检查
}
// 创建消息列队,该消息列队存放指针(4字节)
mbox->xQueue = xQueueCreate(size, sizeof(void *));
if(mbox->xQueue != NULL)单片机printf函数
{
return ERR_OK; // 消息列队创建成功
}
return ERR_MEM; // 消息列队创建失败
}
/********************************************************
* 函数功能:释放并删除⼀个消息邮箱
* 形参:mbox:消息邮箱
* 返回值:⽆
********************************************************/
void sys_mbox_free(sys_mbox_t *mbox)
{
vQueueDelete(mbox->xQueue);
mbox->xQueue = NULL;
}
/********************************************************
* 函数功能:向消息邮箱中发送⼀条消息(等待发送成功才会返回)
* 形参:mbox:消息邮箱
msg:要发送的消息
* 返回值:⽆
********************************************************/
void sys_mbox_post(sys_mbox_t *mbox, void *msg)
{
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if(msg == NULL)
{
msg = (void *)&NullValue; // 空指针的处理⽅式(⽤常量的地址替换)
}
if((SCB_ICSR_REG & 0xFF) == 0) // 线程执⾏
{
while(xQueueSendToBack(mbox->xQueue, &msg, portMAX_DELAY) != pdPASS); // 等待发送成功
}
else // 中断执⾏
{
while(xQueueSendToBackFromISR(mbox->xQueue, &msg, &xHigherPriorityTaskWoken) != pdPASS); portYIELD_FROM_ISR(xHigherPriorityTaskWoken); // 判断是否要进⾏任务切换
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论