STM32F407平台实现以太网LWIP+FreeRTOS移植。
以太网(ETH)简介
借助以太网外设,STM32F4xx 可以通过以太网按照 IEEE 802.3-2002 标准发送和接收数据。以太网提供了可配置、灵活的外设,用以满足客户的各种应用需求。它支持与外部物理层(PHY) 相连的两个工业标准接口:默认情况下使用的介质独立接口 (MII)(在 IEEE 802.3 规范中定义)和简化介质独立接口 (RMII),应用于多种领域,例如交换机、网络接口卡等。
主要特性
- MAC 模块对以下系列的系统使用 LAN CSMA/CD 子层:数据速率为 10 Mbit/s 和 100 Mbit/s 的基带系统和宽带系统。支持半双工和全双工工作模式。冲突检测访问方法仅适用于半双工工作模式。支持 MAC 控制帧子层。
- 符合IEEE 802.3的MII和RMII接口
- 使用MDIO接口配置和管理最多32个PHY设备
- 可编程帧长度,支持高达 16 KB 的巨型帧
- 可编程帧间隔(40-96 位时间,以 8 为步长)
- 支持多种灵活的地址过滤模式
- 支持以太网帧时间戳(请参见 IEEE 1588-2008)。每个帧的发送或接收状态下给出 64 位时间戳
- DMA 具有独立的发送和接收引擎以及相应的 CSR(控制和状态寄存器)空间。发送引擎将数据从系统存储器传送到 Tx FIFO,而接收引擎将数据从 Rx FIFO 传送到系统存储器。DMA可以在 CPU 完全不干预的情况下,通过描述符有效地将数据从源传送到目标。DMA 专为面向包的数据传送(如以太网中的帧)而设计。该控制器经过编程后,可在完成帧发送和接收传送操作时以及其它正常/错误条件下产生 CPU 中断。
LWIP协议栈
LWIP是TCP/IP协议栈的一个开放源代码实现,它由瑞士计算机科学院的Adam Dunkels等开发,目的是减少内存使用率和代码空间大小,因此LWIP适用于运行在资源受限的嵌入式系统环境中。LWIP可以在几百字节或几十KB的RAM空间运行。LWIP既可以移植到操作系统上运行,也可以在无操作系统下独立运行。
LWIP在STM32上的源代码移植
1、core文件:LWIP内核源代码
文件 | 说明 |
---|
dhcp.c | 包含DHCP(动态主机配置协议)客户端的实现代码 | dns.c | 包含DNS(域名系统)客户端的实现代码 | init.c | 包含了LWIP协议栈初始化密切相关的函数,以及一些协议栈配置信息的检查和输出 | mem.c | 协议栈内存堆管理函数的实现代码 | memp.c | 协议栈内存池管理函数的实现代码 | netif.c | 包含协议栈网络接口管理的相关函数,协议栈每个接口用一个相对应的结构进行描述 | pbuf.c | 包含协议栈内核使用的数据包管理函数,用于协议栈层次间的数据传递,避免数据拷贝 | raw.c | 原始套接字的实现代码,可以通过该文件中的函数直接操纵IP层数据包 | stats.c | 包含协议栈内部数据统计与显示的函数,比如内存使用状况、邮箱、信号量等信息 | sys.c | 实现对操作系统模拟层的封装,为协议栈提供统一的邮箱、 信号量操作函数。如果开发者需要使用协议栈的sequential API和socket API, 则必须使用底层操作系统提供的邮箱与信号量机制,这时内核要求移植者提供一个称为sys_ arch. c的操作系统模拟层文件,这个文件主要完成对操作系统中邮箱与信号量函数的封装。而sys. c文件的功能是将sys_ arch. c中的函数再次封装,以得到具有协议栈特色的邮箱、信号量操作函数。所谓特色,就是在这些还数中加入一种机制,以实现协议栈中各个定时事件的正确处理。不使用sequential API和socket API时,LWIP 的编程方式是基于回调机制的,因此不需要任何邮箱和信号量机制。 | tcp.c | 包含TCP控制块操作的函数,也包含了TCP定时处理函数 | tcp_in.c | 包含TCP协议数据接收、处理相关的函数,以及最重要的TCP状态机函数 | tcp_out.c | 包含TCP协议数据发送相关函数,例如数据包发送,超时重传函数等 | udp.c | 包含实现UDP协议的相关函数,包括控制块管理、数据包发送函数、数据包接收函数等 |
2、core/ipv4文件:IPv4标准与IP层数据包相关代码
文件 | 说明 |
---|
autoip.c | 包含IP地址自动配置相关函数,若主机从DHCF服务器处获职IP地址失败,则此时主机可以选择启动AUTOIP功能来配置自身的IP地址。AUTOIP将主机配置为169.254.0.0/16中的某个地址,并提供一套完整的机制来避免IP地址冲突 | icmp.c | 包含ICMP (网际控制报文协议) 协议的实现代码。ICMP协议为IE数据包传递过程中的差错报告、差错纠正以及目的地址可达性测试提供了支持,常见的Ping命令就属于ICMP应用中的一种。 | igmp.c | 包含IGMP (网络组管理协议)协议的实现代码。IGMP为网络中的多播数据传输提供了支持,主机加入某个多播组后,可以接收到改组的UDP多播数据。 | inet.c | 包含I层使用到的一-些功能函数的定义,如r地址的转换、网络字节序与主机字节序转换等 | inet_chksum.c | 包含对IP数据包校验相关的函数 | ip.c | 包含IPv4协议实现的相关函数,如数据包的接收、递交、发送等 | ip_ addr.c | 包含-个判断IP地址是否为广播地址的函数 | ip_frag.c | 提供了IP层数据包分片与重组相关的函数 |
3、api文件:包含sequential API和socket API
文件 | 说明 |
---|
api_lib.c | 包含sequential API函数的实现,主要包含预留给用户的编程接口 | api.msg.c | 包含sequential API函数的实现,主要包含API消息的封装和处理函数 | netbuf.c | 包含上层数据包管理函数的实现 | netdb.c | 包含与主机名字转换相关的函数,主要在socket中被使用到 | netlfapi.c | 包含上层网络接口管理函数的实现 | sockets.c | 包含socket API函数的实现 | tcpip.c | 提供了上层API与协议栈内核交互的函徽,它是整个上层API功能得以实现的一个枢纽,其实现的功能可以理解为:从API函数处接收消息,然后将消息递交给内核函数,内核函数根据消息做出相应的处理。 |
4、netif文件:包含与底层网络接口相关的文件
文件 | 说明 |
---|
etharp.c | 包含ARP协议的实现代码。主要用来实现主机以太网物理地址到IP地址的映射。 | ethernetif.c | 包含了与以太网网卡密切相关的初始化、发送、接收等函数的实现。这个文件夹中的函数并不能使用,它们都是一个框架性的结构,移植者需要根据自己使用的网卡特性来完成这些函数。 | loopif.c | 为协议栈提供回环网络接口功能。使用这个接口可以实现本地两个进程之间的数据传递。 | slipif.c | SLIP (串行链路IP) ,提供一种在串行链路上传送IP数据包的函数定义,移植者需要根据自己使用的串行线路特性来实现这些函数。 |
修改代码部分
本项目参考自正点原子探索者开发板的网络实验7 NETCONN_UDP实验工程,原工程使用的是ucos_ii+lwip的工程,这里更改为FreeRTOS+lwip的工程并修改。
1、初始化部分
因为原来的工程是初始化时候必须插网线,不插网线初始化就会失败,本工程是把lwip初始化分为两部分,首先是初始化对应的GPIO口和时钟,这部分完成后才可以读取PHY的基本状态寄存器的值。
void LAN8720_Port_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOC|RCC_AHB1Periph_GPIOG|RCC_AHB1Periph_GPIOD, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
SYSCFG_ETH_MediaInterfaceConfig(SYSCFG_ETH_MediaInterface_RMII);
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);
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);
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);
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;
delay_ms(50);
LAN8720_RST=1;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_ETH_MAC | RCC_AHB1Periph_ETH_MAC_Tx |RCC_AHB1Periph_ETH_MAC_Rx, ENABLE);
}
然后在创建的lwip_task任务中查询PHY的BSR寄存器的Link Status状态位,当检测到网络连接时再进行tcpip内核初始化和添加网口等操作,如果开启了DHCP服务则在初始化完成后创建dhcp_task的任务。一旦DHCP成功之后,除非系统复位重新初始化,否则不需要重新开启DHCP,即使网线拔下再插入也不会影响继续通信。
void lwip_task(void *pvParameters)
{
while(1)
{
if(Check_Network_connent())
{
if(lwip_comm_init())
{
LCD_ShowString(30,210,200,16,16,"LWIP Init Falied!");
}
#if LWIP_DHCP
lwip_comm_dhcp_creat();
#endif
vTaskSuspend(LWIP_Task_Handler);
}
LED1 = !LED1;
vTaskDelay(200);
}
}
u8 Check_Network_connent(void)
{
if (ETH_ReadPHYRegister(LAN8720_PHY_ADDRESS, PHY_BSR) & PHY_Linked_Status)
return 1;
else
return 0;
}
2、sys_arch.c文件
操作系统模拟层是LWIP协议栈的一部分,它存在的目的是方便将LWIP移植到不同的操作系统上,它为操作系统和LWIP协议栈之间提供一个接口桥梁,当用户移植到一个新的操作系统的时候,只需要修改操作系统模拟层的各函数即可。Sys_arch.txt文件给出了详细的说明。总的来说,操作系统模拟层主要完成了与信号量、消息邮箱机制、线程相关的功能。 typedef xSemaphoreHandle sys_sem_t; //LWIP信号量类型定义 typedef xQueueHandle sys_mbox_t; //LWIP消息队列类型定义 typedef xTaskHandle sys_thread_t; //LWIP线程类型定义
#include "lwip/debug.h"
#include "lwip/def.h"
#include "lwip/lwip_sys.h"
#include "lwip/mem.h"
#include "lwip/stats.h"
#include "FreeRTOS.h"
#include "task.h"
#include "lwip/lwip_timers.h"
xTaskHandle xTaskGetCurrentTaskHandle( void ) PRIVILEGED_FUNCTION;
struct timeoutlist
{
struct sys_timeouts timeouts;
xTaskHandle pid;
};
#define SYS_THREAD_MAX 6
static struct timeoutlist s_timeoutlist[SYS_THREAD_MAX];
static u16_t s_nextthread = 0;
err_t sys_mbox_new(sys_mbox_t *mbox, int size)
{
(void ) size;
*mbox = xQueueCreate( archMESG_QUEUE_LENGTH, sizeof( void * ) );
#if SYS_STATS
++lwip_stats.sys.mbox.used;
if (lwip_stats.sys.mbox.max < lwip_stats.sys.mbox.used) {
lwip_stats.sys.mbox.max = lwip_stats.sys.mbox.used;
}
#endif
if (*mbox == NULL)
return ERR_MEM;
return ERR_OK;
}
void sys_mbox_free(sys_mbox_t *mbox)
{
if( uxQueueMessagesWaiting( *mbox ) )
{
portNOP();
#if SYS_STATS
lwip_stats.sys.mbox.err++;
#endif
}
vQueueDelete( *mbox );
#if SYS_STATS
--lwip_stats.sys.mbox.used;
#endif
}
void sys_mbox_post(sys_mbox_t *mbox, void *data)
{
while ( xQueueSendToBack(*mbox, &data, portMAX_DELAY ) != pdTRUE ){}
}
err_t sys_mbox_trypost(sys_mbox_t *mbox, void *msg)
{
err_t result;
if ( xQueueSendFromISR( *mbox, &msg, 0 ) == pdPASS )
{
result = ERR_OK;
}
else {
result = ERR_MEM;
#if SYS_STATS
lwip_stats.sys.mbox.err++;
#endif
}
return result;
}
u32_t sys_arch_mbox_fetch(sys_mbox_t *mbox, void **msg, u32_t timeout)
{
void *dummyptr;
portTickType StartTime, EndTime, Elapsed;
StartTime = xTaskGetTickCount();
if ( msg == NULL )
{
msg = &dummyptr;
}
if ( timeout != 0 )
{
if ( pdTRUE == xQueueReceive( *mbox, &(*msg), timeout / portTICK_RATE_MS ) )
{
EndTime = xTaskGetTickCount();
Elapsed = (EndTime - StartTime) * portTICK_RATE_MS;
return ( Elapsed );
}
else
{
*msg = NULL;
return SYS_ARCH_TIMEOUT;
}
}
else
{
while( pdTRUE != xQueueReceive( *mbox, &(*msg), portMAX_DELAY ) ){}
EndTime = xTaskGetTickCount();
Elapsed = (EndTime - StartTime) * portTICK_RATE_MS;
return ( Elapsed );
}
}
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;
}
}
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;
}
err_t sys_sem_new(sys_sem_t *sem, u8_t count)
{
vSemaphoreCreateBinary(*sem );
if(*sem == NULL)
{
#if SYS_STATS
++lwip_stats.sys.sem.err;
#endif
return ERR_MEM;
}
if(count == 0)
{
xSemaphoreTake(*sem,1);
}
#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
return ERR_OK;
}
*/该函数是一个阻塞函数,(等待一个信号量)调用该函数的线程在形参timeout指定的时间内被阻塞。若timeout为0,则调用
该函数的线程将一直被阻塞,直到等待的信号量被释放。当该函数取到信号量时,它将返回取得该信号量所占用的时间。*/
u32_t sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
{
portTickType StartTime, EndTime, Elapsed;
StartTime = xTaskGetTickCount();
if( timeout != 0)
{
if( xSemaphoreTake( *sem, timeout / portTICK_RATE_MS ) == pdTRUE )
{
EndTime = xTaskGetTickCount();
Elapsed = (EndTime - StartTime) * portTICK_RATE_MS;
return (Elapsed);
}
else
{
return SYS_ARCH_TIMEOUT;
}
}
else
{
while( xSemaphoreTake(*sem, portMAX_DELAY) != pdTRUE){}
EndTime = xTaskGetTickCount();
Elapsed = (EndTime - StartTime) * portTICK_RATE_MS;
return ( Elapsed );
}
}
void sys_sem_signal(sys_sem_t *sem)
{
xSemaphoreGive(*sem);
}
void sys_sem_free(sys_sem_t *sem)
{
#if SYS_STATS
--lwip_stats.sys.sem.used;
#endif
vQueueDelete(*sem);
}
int sys_sem_valid(sys_sem_t *sem)
{
if (*sem == SYS_SEM_NULL)
return 0;
else
return 1;
}
void sys_sem_set_invalid(sys_sem_t *sem)
{
*sem = SYS_SEM_NULL;
}
void sys_init(void)
{
int i;
for(i = 0; i < SYS_THREAD_MAX; i++)
{
s_timeoutlist[i].pid = 0;
s_timeoutlist[i].timeouts.next = NULL;
}
s_nextthread = 0;
}
struct sys_timeouts *sys_arch_timeouts(void)
{
int i;
xTaskHandle pid;
struct timeoutlist *tl;
pid = xTaskGetCurrentTaskHandle();
for(i = 0; i < s_nextthread; i++)
{
tl = &(s_timeoutlist[i]);
if(tl->pid == pid)
{
return &(tl->timeouts);
}
}
return NULL;
}
#if LWIP_COMPAT_MUTEX == 0
err_t sys_mutex_new(sys_mutex_t *mutex) {
*mutex = xSemaphoreCreateMutex();
if(*mutex == NULL)
{
#if SYS_STATS
++lwip_stats.sys.mutex.err;
#endif
return ERR_MEM;
}
#if SYS_STATS
++lwip_stats.sys.mutex.used;
if (lwip_stats.sys.mutex.max < lwip_stats.sys.mutex.used) {
lwip_stats.sys.mutex.max = lwip_stats.sys.mutex.used;
}
#endif
return ERR_OK;
}
void sys_mutex_free(sys_mutex_t *mutex)
{
#if SYS_STATS
--lwip_stats.sys.mutex.used;
#endif
vQueueDelete(*mutex);
}
void sys_mutex_lock(sys_mutex_t *mutex)
{
sys_arch_sem_wait(*mutex, 0);
}
void sys_mutex_unlock(sys_mutex_t *mutex)
{
xSemaphoreGive(*mutex);
}
#endif
sys_thread_t sys_thread_new(const char *name, lwip_thread_fn thread , void *arg, int stacksize, int prio)
{
xTaskHandle CreatedTask;
int result;
if ( s_nextthread < SYS_THREAD_MAX )
{
result = xTaskCreate( thread, ( portCHAR * ) name, stacksize, arg, prio, &CreatedTask );
s_timeoutlist[s_nextthread++].pid = CreatedTask;
if(result == pdPASS)
{
return CreatedTask;
}
else
{
return NULL;
}
}
else
{
return NULL;
}
}
sys_prot_t sys_arch_protect(void)
{
taskENTER_CRITICAL_FROM_ISR();
return 1;
}
void sys_arch_unprotect(sys_prot_t pval)
{
taskEXIT_CRITICAL_FROM_ISR(pval);
}
void sys_assert( const char *msg )
{
( void ) msg;
vPortEnterCritical( );
for(;;)
;
}
3、UDP通信线程
#define UDP_TASK_PRIO 6
#define UDP_STK_SIZE 300
TaskHandle_t UDP_Task_Handler;
void udp_thread(void *pdata);
u8 udp_demo_recvbuf[UDP_DEMO_RX_BUFSIZE];
const u8 *udp_demo_sendbuf="Explorer STM32F407 NETCONN UDP demo send data\r\n";
u8 udp_flag;
void udp_thread(void *pdata)
{
err_t err;
static struct netconn *udpconn;
static struct netbuf *recvbuf;
static struct netbuf *sentbuf;
struct ip_addr destipaddr;
u32 data_len = 0;
struct pbuf *q;
LWIP_UNUSED_ARG(pdata);
udpconn = netconn_new(NETCONN_UDP);
udpconn->recv_timeout = 10;
if(udpconn != NULL)
{
err = netconn_bind(udpconn,IP_ADDR_ANY,UDP_DEMO_PORT);
IP4_ADDR(&destipaddr,lwipdev.remoteip[0],lwipdev.remoteip[1], lwipdev.remoteip[2],lwipdev.remoteip[3]);
netconn_connect(udpconn,&destipaddr,UDP_DEMO_PORT);
if(err == ERR_OK)
{
while(1)
{
if((udp_flag & LWIP_SEND_DATA) == LWIP_SEND_DATA)
{
sentbuf = netbuf_new();
netbuf_alloc(sentbuf,strlen((char *)udp_demo_sendbuf));
memcpy(sentbuf->p->payload,(void*)udp_demo_sendbuf,strlen((char*)udp_demo_sendbuf));
err = netconn_send(udpconn,sentbuf);
if(err != ERR_OK)
{
printf("发送失败\r\n");
netbuf_delete(sentbuf);
}
udp_flag &= ~LWIP_SEND_DATA;
netbuf_delete(sentbuf);
}
netconn_recv(udpconn,&recvbuf);
if(recvbuf != NULL)
{
taskENTER_CRITICAL();
memset(udp_demo_recvbuf,0,UDP_DEMO_RX_BUFSIZE);
for(q=recvbuf->p;q!=NULL;q=q->next)
{
if(q->len > (UDP_DEMO_RX_BUFSIZE-data_len)) memcpy(udp_demo_recvbuf+data_len,q->payload,(UDP_DEMO_RX_BUFSIZE-data_len));
else memcpy(udp_demo_recvbuf+data_len,q->payload,q->len);
data_len += q->len;
if(data_len > UDP_DEMO_RX_BUFSIZE) break;
}
taskEXIT_CRITICAL();
data_len=0;
printf("%s\r\n",udp_demo_recvbuf);
netbuf_delete(recvbuf);
}else vTaskDelay(5);
}
}else printf("UDP绑定失败\r\n");
}else printf("UDP连接创建失败\r\n");
}
void udp_demo_init(void)
{
taskENTER_CRITICAL();
xTaskCreate((TaskFunction_t )udp_thread,
(const char* )"UDP_Thread",
(uint16_t )UDP_STK_SIZE,
(void* )NULL,
(UBaseType_t )UDP_TASK_PRIO,
(TaskHandle_t* )&UDP_Task_Handler);
taskEXIT_CRITICAL();
}
4、以太网配置
因为原来的以太网中断设置为最高优先级0,而我们给FreeRTOS系统可管理的最高中断优先级设置的是5,会导致port.c文件的configASSERT调用断言,需要对其进行更改。
代码下载链接: STM32F4+FreeRTOS+LwIP.zip.
|