Section 8 has operating system porting LwIP

LwIP can not only run on bare metal, but also in the operating system environment, and in the operating system environment, users can use NETCONN API and Socket API programming, which is easier than RAW API programming. In the operating system environment, this means a multi-threaded environment. Generally speaking, LwIP runs as an independent processing thread, and the user program is also independent as one/multiple threads. In this way, it is independent of each other in the operating system, and with the help of the operating system The IPC communication mechanism can better realize the functional requirements.

At the beginning of the design of LwIP, the designer could not predict what the operating environment of LwIP would be like, and there are so many operating systems in the world that it is impossible to unify them. If LwIP is to run in the operating system environment, then it must generate dependencies, namely LwIP needs to rely on the communication mechanism of the operating system itself, such as semaphores, mutexes, message queues (mailboxes), etc., so the LwIP designer provides a set of interfaces related to the operating system when designing, and the user can use it according to the operating system. In this way, the degree of coupling can be reduced and the LwIP kernel will not be affected by the environment in which it runs. Because users often do not fully understand the operation of the kernel, users only need to perform different operations on the interfaces provided by LwIP when transplanting. The system can be perfected.

Add OS to LwIP

Copy the FreeRTOS source code to the project folder

We first copy the non-operational transplanted code, and then add the source code of the operating system to the project. The source code of the operating system can be obtained from the routines we released to the outside world, or from the official website. Here we take FreeRTOS as an example for porting operate.

First copy the FreeRTOS source code to the project folder, see

insert image description here

Copy the FreeRTOS source code to the project folder

Add FreeRTOS source code to the project group folder

In the previous step, we just put the source code of FreeRTOS in the local project directory, and it has not been added to the group folder in the development environment, so FreeRTOS has not been transplanted into our project.

Next, we create two group folders, FreeRTOS/src and FreeRTOS/port, in the development environment. FreeRTOS/src is used to store all the contents of the src folder, and FreeRTOS/port is used to store the portMemMang folder and portRVDSARM_CM? The contents of the folder.

Then we add the content of FreeRTOS in the project file to the project, and add our FreeRTOS project source code according to the newly created group. To add the files in the MemMang folder in the FreeRTOS/port group, just select one of them. We choose "heap_4.c", which is a memory management source file of FreeRTOS. At the same time, you need to select it in FreeRTOS\port\RVDS\ARM_CM? according to your own development board model.

So far, our FreeRTOS has been added to the project, and the completed effect is shown in the figure.

insert image description here
Figure Add FreeRTOS source code to the project group

Specify the path to the FreeRTOS header files

The source code of FreeRTOS has been added under the group folder of the development environment. When compiling, you need to specify the path of the header file for these source files, otherwise the compilation will report an error. In the source code of FreeRTOS, there are only header files under the two folders of FreeRTOS\include and FreeRTOS\port\RVDS\ARM_CM. You only need to specify the paths of these two header files in the development environment. At the same time, we also copied the header file FreeRTOSConfig.h to the user folder in the root directory of the project, so the user path should also be added to the development environment. The effect after adding the path of the FreeRTOS header file is shown in the figure.

insert image description here
The figure specifies the path of the header file of FreeRTOS in the development environment

So far, the overall project of FreeRTOS has basically been transplanted, and we don't need to modify the FreeRTOS configuration file, just use the header file in our FreeRTOS routine.

Modify stm32f4xx_it.c

The SysTick interrupt service function is a very important function. All the time-related things of FreeRTOS are processed in it. SysTick is a heartbeat clock of FreeRTOS, which drives the operation of FreeRTOS, just like the human heartbeat. If there is no heartbeat, we will It is equivalent to "dead". Similarly, FreeRTOS has no heartbeat, then it will be stuck somewhere, unable to perform task scheduling, and cannot run anything, so we need to implement a FreeRTOS heartbeat clock, FreeRTOS helps us The startup configuration of SysTick has been realized: the vPortSetupTimerInterrupt() function has been implemented in the port.c file, and the common SysTick interrupt service function of FreeRTOS has also been implemented: the xPortSysTickHandler() function has been implemented in the port.c file, so when transplanting We only need to implement the SysTick_Handler() function on our corresponding (STM32) platform in the stm32f4xx_it.c file. FreeRTOS considers a lot for developers. PendSV_Handler() and SVC_Handler() are two very important functions that have been implemented for us. The xPortPendSVHandler() and vPortSVCHandler() functions have been implemented in the port.c file to prevent our own If it cannot be realized, then we need to comment out or delete the two functions PendSV_Handler() and SVC_Handler() in stm32f4xx_it.c. For the specific implementation, see the bold part of the code list.

Code list stm32f4xx_it.c file content

/* Includes ----------------------------------------------------------*/
#include "main.h"
#include "stm32f4xx_it.h"
#include "./usart/bsp_debug_usart.h"

#include "FreeRTOS.h" //FreeRTOS 使用
#include "task.h"

/** @addtogroup STM32F4xx_HAL_Examples
* @{
*/

/** @addtogroup GPIO_EXTI
* @{
*/

/* Private typedef ----------------------------------------------*/
/* Private define ------------------------------------------------*/
/* Private macro -------------------------------------------------*/
/* Private variables --------------------------------------------*/
/* Private function prototypes -------------------------------------*/
/* Private functions ---------------------------------------------*/
/*******************************************************************/
/* Cortex-M4 Processor Exceptions Handlers */
/*****************************************************************/

/**
* @brief This function handles NMI exception.
* @param None
* @retval None
*/
void NMI_Handler(void)
{
    
    
}

/**
* @brief This function handles Hard Fault exception.
* @param None
* @retval None
*/
//void HardFault_Handler(void)
//{
    
    
// /* Go to infinite loop when Hard Fault exception occurs */
// while (1)
// {
    
    
// }
//}

/**
* @brief This function handles Memory Manage exception.
* @param None
* @retval None
*/
void MemManage_Handler(void)
{
    
    
	/* Go to infinite loop when Memory Manage exception occurs */
	while (1)
	{
    
    
	}
}
/**
* @brief This function handles Bus Fault exception.
* @param None
* @retval None
*/
void BusFault_Handler(void)
{
    
    
	/* Go to infinite loop when Bus Fault exception occurs */
	while (1)
	{
    
    
	}
}
/**
* @brief This function handles Usage Fault exception.
* @param None
* @retval None
*/
void UsageFault_Handler(void)
{
    
    
	/* Go to infinite loop when Usage Fault exception occurs */
	while (1)
	{
    
    
	}
}
/**
* @brief This function handles Debug Monitor exception.
* @param None
* @retval None
*/
void DebugMon_Handler(void)
{
    
    
}

/**
* @brief This function handles SysTick Handler.
* @param None
* @retval None
*/
extern void xPortSysTickHandler(void);

void SysTick_Handler(void)
{
    
    
	uint32_t ulReturn;
	/* 进入临界段,临界段可以嵌套*/
	ulReturn = taskENTER_CRITICAL_FROM_ISR();
	
	HAL_IncTick();
#if (INCLUDE_xTaskGetSchedulerState == 1 )
	if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
	{
    
    
#endif /* INCLUDE_xTaskGetSchedulerState */
		xPortSysTickHandler();
#if (INCLUDE_xTaskGetSchedulerState == 1 )
	}
#endif /* INCLUDE_xTaskGetSchedulerState */

	/* 退出临界段*/
	taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}

At this point, the steps of adding FreeRTOS to the LwIP bare-metal project are basically completed, and there is basically no error when compiling. The following is the official use of this project to transplant LwIP and the operating system, because it needs to be based on the characteristics of the operating system. Modify many interface files.

The configuration that needs to be added to the lwipopts.h file

The role of the lwipopts.h file was also mentioned in the previous chapters, and now to transplant in the operating system, we must first take the project with the added operating system over and modify the lwipopts.h file. The most important macro definition of this file It is NO_SYS, we define it as 0 to use the operating system, of course, when using the operating system, we generally use NETCONNAPI and Socket API programming, then we need to define the macro LWIP_NETCONN and LWIP_SOCKET as 1, which means enabling these two For API programming, simply modify lwipopts.h, and then add some macro definitions for thread running. For the parts that must be modified, see the bold part of the code list, and other macro definitions can be modified according to the actual situation.

Code listing lwipopts.h file

#ifndef __LWIPOPTS_H__
#define __LWIPOPTS_H__
/**
* SYS_LIGHTWEIGHT_PROT==1: if you want inter-task protection for certain
* critical regions during buffer allocation, deallocation and memory
* allocation and deallocation.
*/
#define SYS_LIGHTWEIGHT_PROT 1

/**
* NO_SYS==1: Provides VERY minimal functionality. Otherwise,
* use lwIP facilities.
*/
#define NO_SYS 0

/**
* NO_SYS_NO_TIMERS==1: Drop support for sys_timeout when NO_SYS==1
* Mainly for compatibility to old versions.
*/
#define NO_SYS_NO_TIMERS 0

/* ---------- Memory options ---------- */
/* MEM_ALIGNMENT: should be set to the alignment of the CPU for which
lwIP is compiled. 4 byte alignment -> define MEM_ALIGNMENT to 4, 2
byte alignment -> define MEM_ALIGNMENT to 2. */
#define MEM_ALIGNMENT 4

/* MEM_SIZE: the size of the heap memory. If the application will send
a lot of data that needs to be copied, this should be set high. */
#define MEM_SIZE (15*1024)

/* MEMP_NUM_PBUF: the number of memp struct pbufs. If the application
sends a lot of data out of ROM (or other static memory), this
should be set high. */
#define MEMP_NUM_PBUF 25

/* MEMP_NUM_UDP_PCB: the number of UDP protocol control blocks. One
per active UDP "connection". */
#define MEMP_NUM_UDP_PCB 4
/* MEMP_NUM_TCP_PCB: the number of simulatenously active TCP
connections. */
#define MEMP_NUM_TCP_PCB 6
/* MEMP_NUM_TCP_PCB_LISTEN: the number of listening TCP
connections. */
#define MEMP_NUM_TCP_PCB_LISTEN 6
/* MEMP_NUM_TCP_SEG: the number of simultaneously queued TCP
segments. */
#define MEMP_NUM_TCP_SEG 150
/* MEMP_NUM_SYS_TIMEOUT: the number of simulateously active
timeouts. */
#define MEMP_NUM_SYS_TIMEOUT 6

/* ---------- Pbuf options ---------- */
/* PBUF_POOL_SIZE: the number of buffers in the pbuf pool. */
#define PBUF_POOL_SIZE 45
/* PBUF_POOL_BUFSIZE: the size of each pbuf in the pbuf pool. */
#define PBUF_POOL_BUFSIZE \
LWIP_MEM_ALIGN_SIZE(TCP_MSS+40+PBUF_LINK_ENCAPSULATION_HLEN+PBUF_LINK_HLEN)

/* ---------- TCP options ---------- */
#define LWIP_TCP 1
#define TCP_TTL 255

/* Controls if TCP should queue segments that arrive out of
order. Define to 0 if your device is low on memory. */
#define TCP_QUEUE_OOSEQ 0

/* TCP Maximum segment size. */
#define TCP_MSS (1500 - 40)

/* TCP sender buffer space (bytes). */
#define TCP_SND_BUF (10*TCP_MSS)

/* TCP_SND_QUEUELEN: TCP sender buffer space (pbufs). This must be at least
as much as (2 * TCP_SND_BUF/TCP_MSS) for things to work. */

#define TCP_SND_QUEUELEN (8* TCP_SND_BUF/TCP_MSS)

/* TCP receive window. */
#define TCP_WND (11*TCP_MSS)

/* ---------- ICMP options ---------- */
#define LWIP_ICMP 1

/* ---------- DHCP options ---------- */
/* Define LWIP_DHCP to 1 if you want DHCP configuration of
interfaces. DHCP is not implemented in lwIP 0.5.1, however, so
turning this on does currently not work. */
#define LWIP_DHCP 1

/* ---------- UDP options ---------- */
#define LWIP_UDP 1
#define UDP_TTL 255
/* ---------- Statistics options ---------- */
#define LWIP_STATS 0
#define LWIP_PROVIDE_ERRNO 1
/* ---------- link callback options ---------- */
/* LWIP_NETIF_LINK_CALLBACK==1: Support a callback function from an␣
,→interface
* whenever the link changes (i.e., link down)
*/
#define LWIP_NETIF_LINK_CALLBACK 0
/*
--------------------------------------
---------- Checksum options ----------
--------------------------------------
*/
/* The STM32F4x7 allows computing and verifying the IP,
UDP, TCP and ICMP checksums by hardware:
- To use this feature let the following define uncommented.
- To disable it and process by CPU comment the the checksum.
*/
#define CHECKSUM_BY_HARDWARE
#ifdef CHECKSUM_BY_HARDWARE
/* CHECKSUM_GEN_IP==0: Generate checksums by hardware for outgoing IP packets.*/
#define CHECKSUM_GEN_IP 0
/* CHECKSUM_GEN_UDP==0: Generate checksums by hardware for outgoing UDP packets.*/
#define CHECKSUM_GEN_UDP 0
/* CHECKSUM_GEN_TCP==0: Generate checksums by hardware for outgoing TCP packets.*/
#define CHECKSUM_GEN_TCP 0
/* CHECKSUM_CHECK_IP==0: Check checksums by hardware for incoming IP packets.*/
#define CHECKSUM_CHECK_IP 0
/* CHECKSUM_CHECK_UDP==0: Check checksums by hardware for incoming UDP packets.*/
#define CHECKSUM_CHECK_UDP 0
/* CHECKSUM_CHECK_TCP==0: Check checksums by hardware for incoming TCP packets.*/
#define CHECKSUM_CHECK_TCP 0
/*CHECKSUM_CHECK_ICMP==0: Check checksums by hardware for incoming ICMP packets.*/
#define CHECKSUM_GEN_ICMP 0
#else
/* CHECKSUM_GEN_IP==1: Generate checksums in software for outgoing IP packets.*/
#define CHECKSUM_GEN_IP 1
/* CHECKSUM_GEN_UDP==1: Generate checksums in software for outgoing UDP packets.*/
#define CHECKSUM_GEN_UDP 1
/* CHECKSUM_GEN_TCP==1: Generate checksums in software for outgoing TCP packets.*/
#define CHECKSUM_GEN_TCP 1
/* CHECKSUM_CHECK_IP==1: Check checksums in software for incoming IP packets.*/
#define CHECKSUM_CHECK_IP 1
/* CHECKSUM_CHECK_UDP==1: Check checksums in software for incoming UDP packets.*/
#define CHECKSUM_CHECK_UDP 1
/* CHECKSUM_CHECK_TCP==1: Check checksums in software for incoming TCP packets.*/
#define CHECKSUM_CHECK_TCP 1
/*CHECKSUM_CHECK_ICMP==1: Check checksums by hardware for incoming ICMP packets.*/
#define CHECKSUM_GEN_ICMP 1
#endif

/*
----------------------------------------------
---------- Sequential layer options ----------
----------------------------------------------
*/
/**
* LWIP_NETCONN==1: Enable Netconn API (require to use api_lib.c)
*/
#define LWIP_NETCONN 1

/*
------------------------------------
---------- Socket options ----------
------------------------------------
*/
/**
* LWIP_SOCKET==1: Enable Socket API (require to use sockets.c)
*/
#define LWIP_SOCKET 1

/*
---------------------------------
---------- OS options ----------
---------------------------------
*/
#define DEFAULT_UDP_RECVMBOX_SIZE 10
#define DEFAULT_TCP_RECVMBOX_SIZE 10
#define DEFAULT_ACCEPTMBOX_SIZE 10
#define DEFAULT_THREAD_STACKSIZE 1024
#define TCPIP_THREAD_NAME "lwip"
#define TCPIP_THREAD_STACKSIZE 512
#define TCPIP_MBOX_SIZE 8
#define TCPIP_THREAD_PRIO 3

Writing sys_arch.c/h file

Under the operating system environment, the core of LwIP transplantation is to write the interface files sys_arch.c and sys_arch.h related to the operating system. These two files can be created by yourself or obtained from the contrib package. The paths are "contrib-2.1.0 \ports\freertos" and "contrib-2.1.0\ports\freertos\includearch", users must provide corresponding interfaces for the protocol stack according to the functions of the operating system when transplanting, such as mailboxes (because this transplant uses FreeRTOS as an example , FreeRTOS does not have the concept of mailboxes, but message queues can be used instead. In order to cater to the naming in LwIP, mailboxes are used below), semaphores, mutexes, etc. These IPC communication mechanisms are to ensure the communication between the kernel and the upper API interface The basic guarantee is also the inheritance of the kernel implementation management. At the same time, all the function frameworks that the user needs to implement are declared in the sys.h file. See the table for details of these functions.

Tables require user-implemented functions

name Attributes Function file in
sys_sem_t type of data Pointer type, pointing to the system semaphore sys_arch.h
sys_mutex_t type of data Pointer type, pointing to the system mutex sys_arch.h
sys_mbox_t type of data Pointer type, pointing to the system mailbox sys_arch.h
sys_thread_t type of data Pointer type, pointing to the system task sys_arch.h
SYS_MBOX_NULL macro definition Empty value for system mailbox sys_arch.h
SYS_SEM_NULL macro definition Null value for system semaphore sys_arch.h
SYS_MRTEX_NULL macro definition Null value for system mutex sys_arch.h
sys_now function core clock sys_arch.c
sys_init function Initialize the system sys_arch.c
sys_arch_protect function Enter the critical section sys_arch.c
sys_arch_unprotect function exit critical section sys_arch.c
sys_sem_new function create a semaphore sys_arch.c
sys_sem_free function delete a semaphore sys_arch.c
sys_sem_valid function Determine whether the semaphore is valid sys_arch.c
sys_sem_set_invalid function Set the semaphore to an invalid state sys_arch.c
sys_arch_sem_wait function wait for a semaphore sys_arch.c
sys_sem_signal function release a semaphore sys_arch.c
sys_mutex_new function create a mutex sys_arch.c
sys_mutex_free function delete a mutex sys_arch.c
sys_mutex_set_invalid function Set mutex to invalid state sys_arch.c
sys_mutex_lock function acquire a mutex sys_arch.c
sys_mutex_unlock function release a mutex sys_arch.c
sys_mbox_new function create a mailbox sys_arch.c
sys_mbox_free function delete a mailbox sys_arch.c
sys_mbox_valid function Determine whether the mailbox is valid sys_arch.c
sys_mbox_set_invalid function Set mailbox to invalid state sys_arch.c
sys_mbox_post function Send a message to the mailbox, has been blocked sys_arch.c
sys_mbox_trypost function Send a message to a mailbox, non-blocking sys_arch.c
sys_mbox_trypost_fromisr function Send a message to a mailbox in an interrupt sys_arch.c
sys_arch_mbox_fetch function get message from mailbox, block sys_arch.c
sys_arch_mbox_tryfetch function Get message from mailbox, non-blocking sys_arch.c
sys_thread_new function create a thread sys_arch.c

Seeing so many functions, is your head too big? In fact, the implementation of these functions is very simple. First, let me explain the implementation of the mailbox function. In LwIP, the data interaction between the user code and the protocol stack is through the mailbox. The mailbox is essentially a pointer to the data. The API passes the pointer to the kernel, and the kernel accesses the data through this pointer, and then processes it. The kernel transfers data to the user code by passing a pointer through the mailbox.

在操作系统环境下,LwIP 会作为一个线程运行,线程的名字叫tcpip_thread,在初始化LwIP 的时候,内核就会自动创建这个线程,并且在线程运行的时候阻塞在邮箱上,等待数据进行处理,这个邮箱数据的来源可能在底层网卡接收到的数据或者上层应用程序的数据,总之,tcpip_thread线程在获取到邮箱中的数据时候,就会退出阻塞态,去处理数据,在处理完毕数据后又进入阻塞态中等待数据的到来,如此反复。

信号量与互斥量的实现为内核提供同步与互斥的机制,比如当用户想要发送一个数据的时候,就会调用上层API 接口,API 接口就会去先发送一个数据给内核去处理,然后尝试获取一个信号量,因为此时是没有信号量的,所以就会阻塞用户线程;内核在知道用户想要发送数据后,就会调用对应的网卡去发送数据,当数据发送完成后就释放一个信号量告知用户线程发送完成,这样子用户线程就得以继续执行。

所以这些函数的接口都必须由用户实现,下面具体看看这些函数的实现,具体见代码清单。

代码清单 sys_arch.c 文件内容

#include "debug.h"

#include <lwip/opt.h>
#include <lwip/arch.h>

#include "tcpip.h"
#include "lwip/init.h"
#include "lwip/netif.h"
#include "lwip/sio.h"
#include "ethernetif.h"

#if !NO_SYS
#include "sys_arch.h"
#endif
#include <lwip/stats.h>
#include <lwip/debug.h>
#include <lwip/sys.h>

#include <string.h>

int errno;

u32_t lwip_sys_now;

struct sys_timeouts
{
    
    
	struct sys_timeo *next;
};

struct timeoutlist
{
    
    
	struct sys_timeouts timeouts;
	xTaskHandle pid;
};

#define SYS_THREAD_MAX 4

static struct timeoutlist s_timeoutlist[SYS_THREAD_MAX];

static u16_t s_nextthread = 0;

u32_t
sys_jiffies(void)
{
    
    
	lwip_sys_now = xTaskGetTickCount();
	return lwip_sys_now;
}
u32_t
sys_now(void)
{
    
    
	lwip_sys_now = xTaskGetTickCount();
	return lwip_sys_now;
}
void
sys_init(void)
{
    
    
	int i;
	// Initialize the the per-thread sys_timeouts structures
	// make sure there are no valid pids in the list
	for (i = 0; i < SYS_THREAD_MAX; i++)
	{
    
    
		s_timeoutlist[i].pid = 0;
		s_timeoutlist[i].timeouts.next = NULL;
	}
	// keep track of how many threads have been created
	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;
}

sys_prot_t sys_arch_protect(void)
{
    
    
	vPortEnterCritical(); //进入临界段
	return 1;
}

void sys_arch_unprotect(sys_prot_t pval)
{
    
    
	( void ) pval;
	vPortExitCritical(); //退出临界段
}

#if !NO_SYS

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
		*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; //创建成功返回ERR_OK
	else
	{
    
    
#if SYS_STATS
		++lwip_stats.sys.sem.err;
#endif /* SYS_STATS */
		printf("[sys_arch]:new sem fail!\n");
		return ERR_MEM;
	}
}

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; //信号量设置为无效
}

/*
如果timeout 参数不为零,则返回值为
等待信号量所花费的毫秒数。如果
信号量未在指定时间内发出信号,返回值为
SYS_ARCH_TIMEOUT。如果线程不必等待信号量
该函数返回零。*/
u32_t
sys_arch_sem_wait(sys_sem_t *sem, u32_t timeout)
{
    
    
	u32_t wait_tick = 0;
	u32_t start_tick = 0 ;
	
	//看看信号量是否有效
	if (*sem == SYS_SEM_NULL)
		return SYS_ARCH_TIMEOUT;
		
	//首先获取开始等待信号量的时钟节拍
	start_tick = xTaskGetTickCount();
	//timeout != 0,需要将ms 换成系统的时钟节拍
	if (timeout != 0)
	{
    
    
		//将ms 转换成时钟节拍
		wait_tick = timeout / portTICK_PERIOD_MS;
		if (wait_tick == 0)
			wait_tick = 1;
	}
	else
		wait_tick = portMAX_DELAY; //一直阻塞
		
	//等待成功,计算等待的时间,否则就表示等待超时
	if (xSemaphoreTake(*sem, wait_tick) == pdTRUE)
		return ((xTaskGetTickCount()-start_tick)*portTICK_RATE_MS);
	else
		return SYS_ARCH_TIMEOUT;
}

void
sys_sem_signal(sys_sem_t *sem)
{
    
    
	if (xSemaphoreGive( *sem ) != pdTRUE) //释放信号量
		printf("[sys_arch]:sem signal fail!\n");
}

err_t
sys_mutex_new(sys_mutex_t *mutex)
{
    
    
	/* 创建sem */
	*mutex = xSemaphoreCreateMutex(); //创建互斥量
	if (*mutex != SYS_MRTEX_NULL)
	return ERR_OK; //创建成功返回ERR_OK
	else
	{
    
    
		printf("[sys_arch]:new mutex fail!\n");
		return ERR_MEM;
	}
}

void
sys_mutex_free(sys_mutex_t *mutex)
{
    
    
	vSemaphoreDelete(*mutex); //删除互斥量
}

void
sys_mutex_set_invalid(sys_mutex_t *mutex)
{
    
    
	*mutex = SYS_MRTEX_NULL; //设置互斥量为无效
}

void
sys_mutex_lock(sys_mutex_t *mutex)
{
    
    
	xSemaphoreTake(*mutex,/* 互斥量句柄*/
	portMAX_DELAY); /* 等待时间*/
}

void
sys_mutex_unlock(sys_mutex_t *mutex)
{
    
    
	xSemaphoreGive( *mutex );//给出互斥量
}

sys_thread_t
sys_thread_new(const char *name, lwip_thread_fn function,
		void *arg, int stacksize, int prio)
{
    
    
	sys_thread_t handle = NULL;
	BaseType_t xReturn = pdPASS;
	/* 创建一个线程*/
	xReturn = xTaskCreate((TaskFunction_t )function, /* 线程入口函数*/
						(const char* )name,/* 线程名字*/
						(uint16_t )stacksize, /* 线程栈大小*/
						(void* )arg,/* 线程入口函数参数*/
						(UBaseType_t )prio, /* 线程的优先级*/
						(TaskHandle_t* )&handle);/* 线程控制块指针*/
	if (xReturn != pdPASS)
	{
    
    
		printf("[sys_arch]:create task fail!err:%#lx\n",xReturn);
		return NULL;
	}
	return handle;
}

err_t
sys_mbox_new(sys_mbox_t *mbox, int size)
{
    
    
	/* 创建一个邮箱*/
	*mbox = xQueueCreate((UBaseType_t ) size,/* 邮箱的长度*/
						(UBaseType_t ) 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 /* SYS_STATS */
	if (NULL == *mbox)
		return ERR_MEM; // 创建成功返回ERR_OK
		
	return ERR_OK;
}

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 */
	}
	
	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; //设置有效为无效状态
}

void
sys_mbox_post(sys_mbox_t *q, void *msg)
{
    
    
	while (xQueueSend( *q, /* 邮箱的句柄*/
						&msg,/* 发送的消息内容*/
						portMAX_DELAY) != pdTRUE); /* 等待时间*/
}

err_t
sys_mbox_trypost(sys_mbox_t *q, void *msg)
{
    
    
	if (xQueueSend(*q,&msg,0) == pdPASS) //尝试发送一个消息,非阻塞发送
		return ERR_OK;
	else
		return ERR_MEM;
}

err_t
sys_mbox_trypost_fromisr(sys_mbox_t *q, void *msg)
{
    
    
	uint32_t ulReturn;
	err_t err = ERR_MEM;
	BaseType_t pxHigherPriorityTaskWoken;
	
	/* 进入临界段,临界段可以嵌套*/
	ulReturn = taskENTER_CRITICAL_FROM_ISR();
	
	if (xQueueSendFromISR(*q,&msg,&pxHigherPriorityTaskWoken)==pdPASS)
	{
    
    
		err = ERR_OK;
	}
	//如果需要的话进行一次线程切换
	portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
	
	/* 退出临界段*/
	taskEXIT_CRITICAL_FROM_ISR( ulReturn );
	
	return err;
}

u32_t
sys_arch_mbox_fetch(sys_mbox_t *q, void **msg, u32_t timeout)
{
    
    
	void *dummyptr;
	u32_t wait_tick = 0;
	u32_t start_tick = 0 ;
	
	if ( msg == NULL ) //看看存储消息的地方是否有效
		msg = &dummyptr;
		
	//首先获取开始等待信号量的时钟节拍
	start_tick = sys_now();
	
	//timeout != 0,需要将ms 换成系统的时钟节拍
	if (timeout != 0)
	{
    
    
		//将ms 转换成时钟节拍
		wait_tick = timeout / portTICK_PERIOD_MS;
		if (wait_tick == 0)
			wait_tick = 1;
	}
	//一直阻塞
	else
		wait_tick = portMAX_DELAY;
		
	//等待成功,计算等待的时间,否则就表示等待超时
	if (xQueueReceive(*q,&(*msg), wait_tick) == pdTRUE)
		return ((sys_now() - start_tick)*portTICK_PERIOD_MS);
	else
	{
    
    
		*msg = NULL;
		return SYS_ARCH_TIMEOUT;
	}
}

u32_t
sys_arch_mbox_tryfetch(sys_mbox_t *q, void **msg)
{
    
    
	void *dummyptr;
	if ( msg == NULL )
		msg = &dummyptr;
		
	//等待成功,计算等待的时间
	if (xQueueReceive(*q,&(*msg), 0) == pdTRUE)
		return ERR_OK;
	else
	return SYS_MBOX_EMPTY;
}
#if LWIP_NETCONN_SEM_PER_THREAD
#error LWIP_NETCONN_SEM_PER_THREAD==1 not supported
#endif /* LWIP_NETCONN_SEM_PER_THREAD */

#endif /* !NO_SYS */

这些函数都是对操作系统的IPC 通信机制进行简单的封装,在这里用户只需要稍微注意一下sys_arch_sem_wait() 函数与sys_arch_mbox_fetch() 函数,因为LwIP 中使用的时间是以毫秒(ms)为单位的,而操作系统中则以时钟节拍(tick)为单位,那么在返回等待信号量或者邮箱所使用的时间就是要转换成ms,而操作系统并未提供等待这些信息的时间,那么我们可以使用一个折中的方法,在获取的时候开始记录时间戳,在获取结束后再次记录一次时间戳,两次时间戳相减就得到等待的时间,但是需要将这些时间(tick)转换为毫秒,这种做法当然是不精确的,但是对LwIP 来说影响不大。

网卡底层的编写

在无操作性移植的时候,我们的网卡收发数据就是单纯的收发数据,ethernetif_input() 函数就是处理接收网卡数据的,但是使用了操作系统的话,我们一般将接收数据函数独立成为一个网卡接收线程,这样子在收到数据的时候才去处理数据,然后递交给内核线程,所以我们只需要稍作修改即可,将函数转换成线程就行了,并且在初始化网卡的时候创建网卡接收线程。当然,我们也能将发送函数独立成一个线程,我们暂时没有必要去处理它,此处只创建一个网卡接收线程,具体见代码清单。

代码清单 网卡接收线程ethernetif_input()

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();
			p = low_level_input(netif);
			taskEXIT_CRITICAL();
			/* points to packet payload, which starts with an Ethernet header */
		if (p != NULL)
		{
    
    
			taskENTER_CRITICAL();
			/* full packet send to tcpip_thread to process */
			if (netif->input(p, netif) != ERR_OK)
			{
    
    
			LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
				pbuf_free(p);
				p = NULL;
			}
			taskEXIT_CRITICAL();
		}
	}
	}
}

在网卡接收线程中需要留意一下以下内容:网卡接收线程是需要通过信号量机制去接收数据的,一般来说我们都是使用中断的方式去获取网络数据包,当产生中断的时候,我们一般不会在中断中处理数据,而是告诉对应的线程去处理,也就是我们的网卡接收线程去处理数据,那么就会通过信号量进行同步,当网卡接收到了数据就会产生中断释放一个信号量,然后线程从阻塞中恢复,去获取网卡的数据并且向上层递交。

当然我们还需要在中断中对网卡底层进行编写,具体见代码清单

代码清单 网卡中断处理(bsp_eth.c)

void ETH_IRQHandler(void)
{
    
    
	uint32_t ulReturn;
	/* 进入临界段,临界段可以嵌套*/
	ulReturn = taskENTER_CRITICAL_FROM_ISR();
	
	HAL_ETH_IRQHandler(&heth);
	
	/* 退出临界段*/
	taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}

extern xSemaphoreHandle s_xSemaphore;
void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth)
{
    
    
// LED2_TOGGLE;
	portBASE_TYPE xHigherPriorityTaskWoken = pdFALSE;
	xSemaphoreGiveFromISR( s_xSemaphore, &xHigherPriorityTaskWoken );
	portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}

此外我们还需要在网卡初始化的时候创建网卡接收线程与对应的信号量,网卡初始化函数还是low_level_init() 函数,并且初始化的其他信息都未修改,只是添加了线程与信号量的创建,具体见代码清单 加粗部分。

代码清单 网卡初始化函数low_level_init()

static void low_level_init(struct netif *netif)
{
    
    
	HAL_StatusTypeDef hal_eth_init_status;
	
	//初始化bsp—eth
	hal_eth_init_status = Bsp_Eth_Init();
	if (hal_eth_init_status == HAL_OK)
	{
    
    
		/* Set netif link flag */
		netif->flags |= NETIF_FLAG_LINK_UP;
	}
	
#if LWIP_ARP || LWIP_ETHERNET
	
	/* set MAC hardware address length */
	netif->hwaddr_len = ETH_HWADDR_LEN;
	
	/* set MAC hardware address */
	netif->hwaddr[0] = heth.Init.MACAddr[0];
	netif->hwaddr[1] = heth.Init.MACAddr[1];
	netif->hwaddr[2] = heth.Init.MACAddr[2];
	netif->hwaddr[3] = heth.Init.MACAddr[3];
	netif->hwaddr[4] = heth.Init.MACAddr[4];
	netif->hwaddr[5] = heth.Init.MACAddr[5];
	
	/* maximum transfer unit */
	netif->mtu = NETIF_MTU;
	
	/* Accept broadcast address and ARP traffic */
	/* don't set NETIF_FLAG_ETHARP if this device is not an ethernet one */
#if LWIP_ARP
	netif->flags |= NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;
#else
	netif->flags |= NETIF_FLAG_BROADCAST;
#endif /* LWIP_ARP */

	/* USER CODE BEGIN PHY_PRE_CONFIG */

	s_xSemaphore = xSemaphoreCreateCounting(40,0);
	
	/* create the task that handles the ETH_MAC */
	sys_thread_new("ETHIN",
					ethernetif_input, /* 任务入口函数*/
					netif, /* 任务入口函数参数*/
					NETIF_IN_TASK_STACK_SIZE,/* 任务栈大小*/
					NETIF_IN_TASK_PRIORITY); /* 任务的优先级*/
					
#endif /* LWIP_ARP || LWIP_ETHERNET */

	/* Enable MAC and DMA transmission and reception */
	HAL_ETH_Start(&heth);
}

协议栈初始化

经过上面的移植,我们的底层与操作系统接口都基本移植完毕,想要让LwIP 在操作系统中能跑起来,还需要最后一步,将协议栈进行初始化,前面我们也说了,内核在操作系统中是作为一个线程独立存在的,在初始化的时候,我们不仅要挂载网卡,也要创建tcpip_thread() 线程,当然,这个线程LwIP 会在初始的时候自动创建,而挂载网卡的内容与无操作系统是一样的,就无需过多修改,协议栈初始化的源码具体见代码清单。

代码清单 协议栈初始化

/*Static IP ADDRESS: IP_ADDR0.IP_ADDR1.IP_ADDR2.IP_ADDR3 */
#define IP_ADDR0 192
#define IP_ADDR1 168
#define IP_ADDR2 1
#define IP_ADDR3 122

/*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 1
#define GW_ADDR3 1
/* USER CODE END 0 */

struct netif gnetif;
ip4_addr_t ipaddr;
ip4_addr_t netmask;
ip4_addr_t gw;
uint8_t IP_ADDRESS[4];
uint8_t NETMASK_ADDRESS[4];
uint8_t GATEWAY_ADDRESS[4];

void LwIP_Init(void)
{
    
    
	tcpip_init(NULL, NULL);

	/* IP addresses initialization */
	/* USER CODE BEGIN 0 */
#ifdef USE_DHCP
	ip_addr_set_zero_ip4(&ipaddr);
	ip_addr_set_zero_ip4(&netmask);
	ip_addr_set_zero_ip4(&gw);
#else
	IP4_ADDR(&ipaddr,IP_ADDR0,IP_ADDR1,IP_ADDR2,IP_ADDR3);
	IP4_ADDR(&netmask,NETMASK_ADDR0,NETMASK_ADDR1,NETMASK_ADDR2,NETMASK_ADDR3);
	IP4_ADDR(&gw,GW_ADDR0,GW_ADDR1,GW_ADDR2,GW_ADDR3);
#endif /* USE_DHCP */
	/* USER CODE END 0 */
	/* Initilialize the LwIP stack without RTOS */
	/* add the network interface (IPv4/IPv6) without RTOS */
	netif_add(&gnetif, &ipaddr, &netmask, &gw, NULL,
			&ethernetif_init, &tcpip_input);
			
	/* Registers the default network interface */
	netif_set_default(&gnetif);
	if (netif_is_link_up(&gnetif))
	{
    
    
		/* When the netif is fully configured this function must be called */
		netif_set_up(&gnetif);
	}
	else
	{
    
    
		/* When the netif link is down this function must be called */
		netif_set_down(&gnetif);
	}
}	

In the tcpip_init() function, LwIP will call lwip_init() to initialize the kernel, and create a tcpip_mbox mailbox with the size of TCPIP_MBOX_SIZE, which is used to receive messages from the bottom or upper layer, and the most important thing is to create a tcpip_thread Thread, that is, LwIP runs as an independent thread in the operating system, and all processed data must be processed by this thread, which we will explain later. For the source code of the lwip_init() function, see the code list for details.

Code list lwip_init() source code

void
tcpip_init(tcpip_init_done_fn initfunc, void *arg)
{
    
    
	lwip_init();
	
	tcpip_init_done = initfunc;
	tcpip_init_done_arg = arg;
	if (sys_mbox_new(&tcpip_mbox, TCPIP_MBOX_SIZE) != ERR_OK)
	{
    
    
		LWIP_ASSERT("failed to create tcpip_thread mbox", 0);
		}
#if LWIP_TCPIP_CORE_LOCKING
	if (sys_mutex_new(&lock_tcpip_core) != ERR_OK)
	{
    
    
		LWIP_ASSERT("failed to create lock_tcpip_core", 0);
		}
#endif /* LWIP_TCPIP_CORE_LOCKING */

	sys_thread_new(TCPIP_THREAD_NAME, tcpip_thread, NULL,
				TCPIP_THREAD_STACKSIZE, TCPIP_THREAD_PRIO);
}

For the netif_add() function in the code list, there is a slight difference from the bare metal porting. Its last parameter is tcpip_input, which is a function instead of the original ethernet_input(), because the tcpip_input() function will receive the network card The data packet is packaged into a message, sent to the tcpip_mbox mailbox, and passed to the tcpip_thread thread for processing, but in essence, the ethernet_input() function is called to submit the data packet, which is just a big circle. Therefore, if you want LwIP to be able to For normal operation, the message mechanism is indispensable. We will explain this operation mechanism in detail later.

Use ping to test basic response after porting

After transplanting the operating system and LwIP, we should also use the computer to ping the development version to see if it can be pinged. If it passes, it means that our transplantation is normal, then we can continue to use LwIP to complete more advanced applications , the ping results are shown in the figure.

insert image description here
Figure ping response results


Reference: LwIP Application Development Practical Guide - Based on Wildfire STM32

Guess you like

Origin blog.csdn.net/picassocao/article/details/129243128