1. 序言
今天跟大家简单分享 LWIP + RTThread 的移植注意事项,记得以前刚接触 LWIP 那会,是跟着野火的教程一起走,而大部分 LWIP 移植教程都是以 freeRTOS 为主,本着支持 RTThread 的想法,在当时就想着移植 LWIP 到 RTThread 上,没想到翻车了,sockets 连接总是连接不上而 netconn 就可以,后面就放弃了…………,直到现在,把它捡起来! 本文仅简单描述其中需要注意的事项,需要对 RTThread 和 LWIP 有一定的了解,详细的移植教程无法做到一一细说,还是建议大家去看看野火的教程《LwIP 应用开发实战指南》和 《RT-Thread 内核实现与应用开发实战指南》,都是不错的入门书籍(本文也引用了一些资料)。 基于STM32 + RTThread Nano 3.1.3 + LWIP 2.1.3,当然直接使用全功能的RT-Thread IoT 就可以直接拥有丰富的网络协议,但是对于一些仅需要简单的TCP/UDP应用,也是一个不错的移植选择方案。
2. 实现
实现phy的底层驱动文件。 移植RTThread Nano 到工程。 移植LWIP文件到工程,使用STM32CubeMX勾选LWIP协议,参照生成的工程实现ethernetif.c文件。 移植sys_arch.c文件(非常重要)。
#include <lwip/stats.h>
#include <lwip/debug.h>
#include <string.h>
#include "lwip/def.h"
#include "lwip/sys.h"
#include <lwip/opt.h>
#include <lwip/arch.h>
#include "tcpip.h"
#include "rtthread.h" uint32_t mbox_count= 0 ; err_t sys_mbox_new ( sys_mbox_t * mbox, int size)
{ char s1[ ] = "RT_MBX" ; char s2= mbox_count+ '0' ; char * s3= s1+ s2; mbox_count++ ; * mbox = rt_mb_create ( s3, ( rt_ubase_t) size, RT_IPC_FLAG_PRIO) ; if ( * mbox == NULL ) { return ERR_MEM; } return ERR_OK;
} void sys_mbox_free ( sys_mbox_t * mbox)
{ rt_mb_delete ( * mbox) ;
} void sys_mbox_post ( sys_mbox_t * mbox, void * msg)
{ rt_base_t ret; while ( ERR_OK != rt_mb_send_wait ( * mbox, ( rt_uint32_t) msg, RT_WAITING_FOREVER) ) ;
} err_t sys_mbox_trypost ( sys_mbox_t * mbox, void * msg)
{ rt_base_t ret; ret = rt_mb_send ( * mbox, ( rt_uint32_t) msg) ; if ( ret == RT_EOK) { return ERR_OK; } else { return ERR_MEM; }
} err_t sys_mbox_trypost_fromisr ( sys_mbox_t * mbox, void * msg)
{ return sys_mbox_trypost ( mbox, msg) ;
} u32_t sys_arch_mbox_fetch ( sys_mbox_t * mbox, void * * msg, u32_t timeout_ms)
{ rt_base_t ret; void * msg_dummy; if ( ! msg) { msg = & msg_dummy; } if ( ! timeout_ms) { ret = rt_mb_recv ( * mbox, ( rt_uint32_t* ) msg, RT_WAITING_FOREVER) ; } else { rt_tick_t timeout_ticks = timeout_ms; ret = rt_mb_recv ( * mbox, ( rt_uint32_t* ) msg, timeout_ticks) ; if ( ret != RT_EOK) { * msg = NULL ; return SYS_ARCH_TIMEOUT; } } return 1 ;
} u32_t sys_arch_mbox_tryfetch ( sys_mbox_t * mbox, void * * msg)
{ rt_base_t ret; void * msg_dummy; if ( ! msg) { msg = & msg_dummy; } ret = rt_mb_recv ( * mbox, ( rt_uint32_t* ) & ( * msg) , 0 ) ; if ( ret != RT_EOK) { * msg = NULL ; return SYS_MBOX_EMPTY; } return 1 ;
} int sys_mbox_valid ( sys_mbox_t * mbox)
{ if ( * mbox == NULL ) return 0 ; else return 1 ;
} void sys_mbox_set_invalid ( sys_mbox_t * mbox)
{ * mbox = NULL ;
} uint32_t sem_count= 0 ; err_t sys_sem_new ( sys_sem_t * sem, u8_t initial_count)
{ char s1[ ] = "RT_SEM" ; char s2= sem_count+ '0' ; char * s3= s1+ s2; sem_count++ ; * sem = rt_sem_create ( s3, 1 , RT_IPC_FLAG_PRIO) ; if ( * sem == NULL ) { return ERR_MEM; } if ( initial_count == 0 ) { rt_sem_trytake ( * sem) ; } else if ( initial_count == 1 ) { rt_base_t ret = rt_sem_release ( * sem) ; } return ERR_OK;
} u32_t sys_arch_sem_wait ( sys_sem_t * sem, u32_t timeout_ms)
{ rt_base_t ret; if ( ! timeout_ms) { ret = rt_sem_take ( * sem, RT_WAITING_FOREVER) ; } else { rt_tick_t timeout_ticks = timeout_ms; ret = rt_sem_take ( * sem, timeout_ticks) ; if ( ret != RT_EOK) { return SYS_ARCH_TIMEOUT; } } return 1 ;
} void sys_sem_signal ( sys_sem_t * sem)
{ rt_base_t ret; ret = rt_sem_release ( * sem) ;
} void sys_sem_free ( sys_sem_t * sem)
{ rt_sem_delete ( * sem) ;
} int sys_sem_valid ( sys_sem_t * sem)
{ if ( * sem == NULL ) return 0 ; else return 1 ;
} void sys_sem_set_invalid ( sys_sem_t * sem)
{ * sem = NULL ;
} #if LWIP_COMPAT_MUTEX == 0 uint32_t mutex_count= 0 ; err_t sys_mutex_new ( sys_mutex_t * mutex)
{ char s1[ ] = "RT_MUTEX" ; char s2= mutex_count+ '0' ; char * s3= s1+ s2; mutex_count++ ; * mutex = rt_mutex_create ( s3, RT_IPC_FLAG_PRIO) ; if ( * mutex == NULL ) { return ERR_MEM; } return ERR_OK;
} void sys_mutex_free ( sys_mutex_t * mutex)
{ rt_mutex_delete ( * mutex) ;
} void sys_mutex_lock ( sys_mutex_t * mutex)
{ rt_base_t ret; ret = rt_mutex_take ( * mutex, RT_WAITING_FOREVER) ;
} void sys_mutex_unlock ( sys_mutex_t * mutex)
{ rt_base_t ret; ret = rt_mutex_release ( * mutex) ;
} #endif sys_thread_t sys_thread_new ( const char * name, lwip_thread_fn thread, void * arg, int stacksize, int prio)
{ rt_base_t ret; sys_thread_t lwip_thread; size_t rtos_stacksize; rtos_stacksize = ( size_t) stacksize; lwip_thread = rt_thread_create ( name, thread, arg, rtos_stacksize, prio, 50 ) ; rt_thread_startup ( lwip_thread) ; return lwip_thread;
}
#if SYS_LIGHTWEIGHT_PROT == 1 sys_mutex_t lwip_sys_mutex; void sys_init ( void )
{ lwip_sys_mutex = rt_mutex_create ( "RT_MUTEX_PRO" , RT_IPC_FLAG_PRIO) ;
} sys_prot_t sys_arch_protect ( void )
{ rt_base_t ret; ret = rt_mutex_take ( lwip_sys_mutex, RT_WAITING_FOREVER) ; return 1 ;
} void sys_arch_unprotect ( sys_prot_t pval)
{ LWIP_UNUSED_ARG ( pval) ; rt_base_t ret; ret = rt_mutex_release ( lwip_sys_mutex) ; }
#endif u32_t sys_now ( void )
{ return rt_tick_get ( ) ;
} u32_t sys_jiffies ( void )
{ return rt_tick_get ( ) ;
}
由于RTThread线程启动的特殊性,main函数是在一个线程里面被调用执行的,如果在main函数里面创建更高优先级的线程,就会马上执行高优先级线程,且一般我们会把网口的处理接收ethernetif_input单独作为一个线程来运行,通过ETH外设中断发送信号量使接收线程ethernetif_input运行,因此,我们需要在初始化LWIP内核和网口的初始化时,禁止中断和线程调度,避免LWIP内核未完成初始化时,ethernetif_input线程运行或发生中断过程中使用了内核信号量,但内核并没初始化出信号量,就会发生错误。
rt_base_t ret = rt_hw_interrupt_disable ( ) ;
. . . . .
LAN8720_Init ( ) ;
tcpip_init ( NULL , NULL ) ;
. . . .
rt_hw_interrupt_enable ( ret) ;
一般使用的线程优先级,让LWIP内核线程反应更快。 宏:SYS_LIGHTWEIGHT_PROT,开启后并用互斥量实现,能够保护内核在申请内存时安全。(sys_init初始化互斥量、sys_arch_protect申请互斥量、sys_arch_unprotect释放互斥量) 宏:LWIP_TCPIP_CORE_LOCKING,不开启时用户线程发送邮箱信息到tcpip线程,tcpip线程再来处理回调,如下图,这增加了系统开销(线程切换,从用户线程切换到tcpip线程),若开启后用户线程会申请互斥量,保护用户线程的运行,直接调用回调函数。建议开启。