📄 lwippppeasyarm2200smartarm2200.txt
字号:
lwip之ppp在EasyARM2200和SmartARM2200上的移植
************************************************
* lwip之ppp在EasyARM2200和SmartARM2200上的移植 *
************************************************
------ 浅谈《ecos增值包》在二次开发方面的用途
2007/03/27 asdjf@163.com www.armecos.com
ecos本身已经支持openBSD、FreeBSD、lwip三款TCP/IP协议栈,用户只要通过标准的socket接口就可以实现网络编程,但很多网友希望看到底层实现细节,满足好奇心,学习TCP/IP工作原理,虽然有些画蛇添足,不过《ecos增值包》的平台验证功能完全满足这类需求,只要您愿意,完全可以忽略(裁减)掉ecos的协议栈而在应用层自行实现,随心所欲地做自己想做的事情。其他如文件系统等也类似,您也可以旁路掉系统自带API,直接驱动硬件,在应用层实现自己的文件操作函数。或者把自己以前总结的用着顺手的工具集移植到ecos上替换相应函数。这完全取决于您的意愿,ecos不会限制。您有很大的自由度二次开发ecos系统,不想用的部份通通去掉,搞不定的部份让ecos代劳。
有很多文档介绍以太网(感兴趣的话可以看以前写过的《51+8019方案》,《ecos网络驱动》,《ucos+lwip by 杨晔》),这次介绍个网络上比较少见的通过串口ppp实现lwip栈的方案。
------------
| lwip简介 |
------------
lwip是轻量级的TCP/IP协议栈中间件,通过抽象操作系统平台接口,基于它编写的网络应用程序可以跨OS平台使用。操作系统抽象了硬件,中间件又抽象了操作系统,所以应用程序变得更容易移植,减少重复劳动,提高代码质量。因为是轻量级协议栈,所以特别适合用来入门学习。lwip即可以在无操作系统情况下依靠回调函数运行,也可以在有OS情况下以兼容socket插口方式工作或者使用自己的内部接口函数。lwip支持ARP、SLIP、PPP、DHCP、ICMP、IPV4、IPV6、RAW、UDP、TCP、(DNS)等协议。
--------------------------------
| 《ecos增值包》中间件解决方案 |
--------------------------------
现在对设备间连网的需求比较大,在旧有设备上连网需要转换模块,缺点是成本高,二次转换效率低下(模块接口界面烦琐晦涩,需要了解供应商自定义的非通用概念),好处是不用/少量改造现有设备。本方案提供的是在新设备上一体化的解决方法,使用操作系统中间件实现对外互联。因为是中间件,所以用户不必分心在非核心应用上,完全软件解决,器件成本最低;没有二次转换,效率高;比自带协议模块更灵活,可实现特殊控制,如RESET、掉电;可在ecos下实现分级电源管理,降低功耗。
----------------
| lwip移植规划 |
----------------
lwip网络接口支持:以太网、串口SLIP、PPP等,我们选择PPP接口,实现PAP、CHAP认证,实现普通电话、GPRS、CDMA拨号。
上层接口支持:回调、内部自定义、标准socket接口,我们选择标准的socket插口界面。
------------------------ AT -------- /dev/ser1 ---------------------------- socket ------------
| 电话/GPRS/CDMA MODEM | <-----> | 串口 | <-----------> | lwip arch || lwip socket | <-------->| 应用程序 |
------------------------ -------- ---------------------------- ------------
由上图可知:
MODEM和开发板是通过9线串口互联(本方案可以很方便地改造成光纤、PCMCIA、USB等硬件接口界面),关于ecos串口,详见《第六讲 串口操作》及其附录。ecos将串口抽象为设备文件,我们选择串口1连接MODEM,文件名为/dev/ser1,ecos会自动枚举出这个串口,并能探测出是9线串口,自动关联正确的驱动程序。因为缺省配置了128字节缓冲,ecos会自动选择基于中断的驱动方式。用户不必关心寄存器配置,因为16C55X是工业标准,ecos能够识别且有通用驱动程序,由于ecos自动管理串口,所以不会使用户在串口驱动上遇到障碍。用户需要注意的是跳线(TXD、RXD、DCD)要连接好,打开串口设备文件,配置属性、读写串口、关闭,仅此而已。
不同MODEM的AT指令集不完全相同,但都基本符合贺式标准。通过AT指令,用户可以摘挂机、拨号、收发数据、收发短信等。中文短信需要编码,短信有延迟或者丢失现象,关键信息、实时信息不要通过短信传送。还有要注意各地包年包月套餐费用(尤其注意是否多收了莫名其妙的信息费),转发器分布和数量,天线接收灵敏度不同,需要综合考虑。
----------------
| lwip结构分析 |
----------------
lwip移植需要改动的部分如下:
/arch/ OS平台相关层:串口、线程、内存管理、信号量、时间管理等封装函数及初始化。
/include/arch/ 类型定义、大小端等
/include/net_lwip.h 网络参数配置
/include/lwipopts.h lwip选项
/netif/ppp/ppp.c PPP串行输入和输出接口
/netif/ppp/modem.c MODEM驱动
----------------
| lwip移植难点 |
----------------
对TCP/IP原理要熟悉,仔细阅读《TCP/IP详解》一书。
对lwip目录结构和工作流程要特别熟悉。画出数据流路径。
----------------
| lwip移植详述 |
----------------
/arch/sio.c
-------------------------------------------------------------------------------------------
void
sio_send(char c,void * dev)
{
len = 1;
cyg_io_write(*(cyg_io_handle_t*)dev, &c, &len);
}
char
sio_recv(void * dev)
{
char c;
len = 1;
cyg_io_read(*(cyg_io_handle_t *)dev, &c, &len);
return c;
}
int
sio_write(void *dev, char *b, int size)
{
Cyg_ErrNo err;
unsigned int len;
unsigned int tlen = 0;
while(tlen < size){
len = size - tlen;
err = cyg_io_write(*(cyg_io_handle_t*)dev, b+tlen, &len);
if(err != ENOERR){
printf("\
\
\
\
Serial port ERROR!\
\
\
\
");
return 0;
}
cyg_io_get_config(*(cyg_io_handle_t*)dev, CYG_IO_GET_CONFIG_SERIAL_OUTPUT_DRAIN, NULL, 0 ) ; //等待所有数据被发送完
tlen = tlen + len;
}
return size;
}
int
sio_read(void *dev, char *b, int size)
{
int len = size;
cyg_io_read(*(cyg_io_handle_t*)dev, b, &len);
return len;
}
void *
sio_open(int devnum) //devnum此处没有用到
{
int res;
cyg_uint32 len;
cyg_uint16 _scpcr ; //Enable RTS valid
cyg_uint8 _scfcr ; //Enable RTS/CTS control真正使能RTS和CTS
#if LWIP_SLIP
#define SIODEV SLIP_DEV
#elif PPP_SUPPORT
#define SIODEV PPP_DEV
#endif
cyg_serial_info_t serial_info =
CYG_SERIAL_INFO_INIT( SCIF_BAUDRATE,
CYGNUM_SERIAL_STOP_1,
CYGNUM_SERIAL_PARITY_NONE,
CYGNUM_SERIAL_WORD_LENGTH_8,
0 );
len = sizeof(serial_info);
cyg_io_lookup(SIODEV, &ser);
res = cyg_io_set_config(ser, CYG_IO_SET_CONFIG_SERIAL_INFO, &serial_info, &len);
if (res != ENOERR){
//diag_printf("Cannot open %s\
", SIODEV);
}
return &ser;
}
void
sio_read_abort(void * dev)
{
//diag_printf("Abort called\
");
}
-------------------------------------------------------------------------------------------
/arch/sys_arch.c
-------------------------------------------------------------------------------------------
//建立内存池,初始化thread和to。
void sys_init(void)
{
cyg_mempool_var_create(memvar, sizeof(memvar), &var_mempool_h, &var_mempool);
threads = NULL;
to.next = NULL;
}
//创建一个新邮箱。如果内存不够,返回NULL。
sys_mbox_t sys_mbox_new(void)
{
cyg_mbox * mbox;
cyg_handle_t m;
mbox = (cyg_mbox *)cyg_mempool_var_try_alloc(var_mempool_h, sizeof(cyg_mbox));
if(!mbox)
return SYS_MBOX_NULL;
//m和mbox实际是一样的
cyg_mbox_create(&m, mbox);
return m;
}
//销毁邮箱并释放其占用的内存空间。
void sys_mbox_free(sys_mbox_t mbox)
{
if (mbox != SYS_MBOX_NULL) {
cyg_mbox_delete(mbox);
cyg_mempool_var_free(var_mempool_h,(void*)mbox);
}
}
//cyg_mbox_put不能传送NULL,否则cyg_mbox_get将不能区分到底是真实数据还是错误条件。
//但是,lwIP确实在某些适于使用信号量的时候传递NULL。所以要用&dummy_msg代替NULL。
int dummy_msg = 1;
//发送消息到信箱
void sys_mbox_post(sys_mbox_t mbox, void *data)
{
if (!data) //cyg_mbox_put的消息不能为NULL,用&dummy_msg代替。
data = &dummy_msg;
while (cyg_mbox_put(mbox,data) == false);
}
#if 0 //注释用
void
sys_mbox_fetch(sys_mbox_t mbox, void **msg){
void *d;
d = cyg_mbox_get(mbox);
if (msg)
*msg = d;
}
#endif
//超时等待信箱,超时返回-1,正常返回等待ms数。
u32_t sys_arch_mbox_fetch(sys_mbox_t mbox, void **data, u32_t timeout)
{
void *d;
cyg_tick_count_t end_time = 0, start_time = 0;
if (timeout) {//超时等待信箱
start_time = cyg_current_time();
d = cyg_mbox_timed_get(mbox, start_time + msec_to_tick(timeout));
end_time = cyg_current_time();
//超时
if (d == NULL)
return SYS_ARCH_TIMEOUT;
}
else{//永远等待信箱
d = cyg_mbox_get(mbox);
}
if (data) {
if (d == (void *)&dummy_msg)
*data = NULL;
else
*data = d;
}
//返回延时ms数
return tick_to_msec(end_time - start_time);
}
//分配一个新信号量并初始化,如果没有空间可以提供则返回NULL
sys_sem_t sys_sem_new(u8_t count)
{
sys_sem_t sem; //????sys_sem_t*???
//从内存池分配可变长内存块,如无空间则立即返回NULL,否则返回新指针。
sem = (cyg_sem_t *)cyg_mempool_var_try_alloc(var_mempool_h, sizeof(cyg_sem_t));
if(!sem)
return SYS_SEM_NULL;
cyg_semaphore_init(sem, count);
return sem;
}
#if 0 //注释用
void
sys_sem_wait(sys_sem_t sem)
{
cyg_semaphore_wait(sem);
}
void
sys_timeout(u16_t msecs, sys_timeout_handler h, void *arg)
{}
#endif
//超时等待一个信号量,如果超时返回-1,否则返回等待时间.
u32_t sys_arch_sem_wait(sys_sem_t sem, u32_t timeout)
{
cyg_bool_t r;
cyg_tick_count_t end_time = 0, start_time = 0;
if (timeout) {//带延时等待信号量
start_time = cyg_current_time();
r = cyg_semaphore_timed_wait(sem, start_time + msec_to_tick(timeout));
end_time = cyg_current_time();
if (r == false) {//超时
return SYS_ARCH_TIMEOUT;
}
}
else{//永远等待信号量
cyg_semaphore_wait(sem);
}
//收到信号量,返回延时ms数
return tick_to_msec(end_time - start_time);
}
//发送信号量
void sys_sem_signal(sys_sem_t sem)
{
cyg_semaphore_post(sem);
}
//销毁信号量并释放其所占用的内存空间
void sys_sem_free(sys_sem_t sem)
{
//销毁信号量
cyg_semaphore_destroy(sem);
//释放内存,内存池,分配时返回指针
cyg_mempool_var_free(var_mempool_h,(void*)sem);
}
//创建新线程(线程函数,参数,优先级)
//thread_create(优先级,线程实体函数,线程参数,线程名字,堆栈基址,堆栈大小,返回的线程句柄,线程数据存储空间)
sys_thread_t sys_thread_new(void (*function) (void *arg), void *arg,int prio)
{
struct lwip_thread * nt;
void * stack;
static int thread_count = 0;
//可变长度内存分配,内存池,大小
nt = (struct lwip_thread *)cyg_mempool_var_alloc(var_mempool_h, sizeof(struct lwip_thread));
nt->next = threads;
nt->to.next = NULL;
threads = nt;
//堆栈起址为memfix+每个堆栈大小*线程数++
stack = (void *)(memfix+CYGNUM_LWIP_THREAD_STACK_SIZE*thread_count++);
cyg_thread_create(prio, (cyg_thread_entry_t *)function, (cyg_addrword_t)arg,
(char *)arg , stack, CYGNUM_LWIP_THREAD_STACK_SIZE, &(nt->th), &(nt->t) );
cyg_thread_resume(nt->th);
return NULL;
}
//返回当前线程的timeouts信息结构指针
struct sys_timeouts *sys_arch_timeouts(void)
{
cyg_handle_t ct;
struct lwip_thread *t;
ct = cyg_thread_self();
for(t = threads; t; t = t->next)
if (t->th == ct)
return &(t->to);
return &to;
}
-------------------------------------------------------------------------------------------
/include/net_lwip.h
-------------------------------------------------------------------------------------------
//sys_arch.c
#define CYGNUM_LWIP_VARMEMPOOL_SIZE 600*1024 //可变长内存池实体
#define CYGNUM_LWIP_APP_THREADS 15 //应用程序线程数
#define CYGNUM_LWIP_THREAD_STACK_SIZE 20*1024 //线程堆栈空间大小
/* ---------- 存储器选项 ---------- */
//MEM_ALIGNMENT:应被设置为被编译lwIP的目标CPU的字节排列。
//4字节排列--->MEM_ALIGNMENT=4
//2字节排列--->MEM_ALIGNMENT=2
#define CYGPKG_LWIP_MEM_ALIGNMENT 4 //?????
//MEM_SIZE:内存堆的大小。如果应用程序要发送许多需要内存拷贝的数据,这个值应被设置得大些。
#define CYGPKG_LWIP_MEM_SIZE 600*1024 //400k
//MEMP_NUM_PBUF:memp结构的pbufs数目。如果应用程序要发送许多ROM类型(或者其他静态内存)以外的数据,这个值应被设置得大些。
#define CYGPKG_LWIP_MEMP_NUM_PBUF 200
//MEMP_NUM_UDP_PCB:UDP协议控制块数目。每个活动的UDP“连接”对应一个。
#define CYGPKG_LWIP_MEMP_NUM_UDP_PCB 30
//MEMP_NUM_TCP_PCB:同时活动的TCP连接数
#define CYGPKG_LWIP_MEMP_NUM_TCP_PCB 30
//MEMP_NUM_TCP_PCB_LISTEN:监听TCP连接数
#define CYGPKG_LWIP_MEMP_NUM_TCP_PCB_LISTEN 40
//MEMP_NUM_TCP_SEG:同时排队TCP分段数
#define CYGPKG_LWIP_MEMP_NUM_TCP_SEG 300
//MEMP_NUM_SYS_TIMEOUT:同时活动超时数
#define CYGPKG_LWIP_MEMP_NUM_SYS_TIMEOUT 60
//以下四个选项只用于顺序API,如果应用程序只使用原始API,他们应被置0。
//MEMP_NUM_NETBUF:结构netbuf数
#define CYGPKG_LWIP_MEMP_NUM_NETBUF 200
//MEMP_NUM_NETCONN:结构netconn数
#define CYGPKG_LWIP_MEMP_NUM_NETCONN 200
//MEMP_NUM_APIMSG:结构api_msg数,用于在TCP/IP栈和顺序程序间通信。
#define CYGPKG_LWIP_MEMP_NUM_APIMSG 300
//MEMP_NUM_TCPIPMSG:结构tcpip_msg数,用于顺序API通信和收包。详见src/api/tcpip.c。
#define CYGPKG_LWIP_MEMP_NUM_TCPIP_MSG 300
/* ---------- Pbuf 选项 ---------- */
//PBUF_POOL_SIZE:pbuf池中缓冲区个数
#define CYGPKG_LWIP_PBUF_POOL_SIZE 300
//PBUF_POOL_BUFSIZE:pbuf池中每个pbuf的大小
#define CYGPKG_LWIP_PBUF_POOL_BUFSIZE 1500
//PBUF_LINK_HLEN:应该分配给一个链路级包头的字节数
#define CYGPKG_LWIP_PBUF_LINK_HLEN 100
/* ---------- TCP 选项 ---------- */
#define CYGPKG_LWIP_TCP 1
#define CYGPKG_LWIP_TCP_TTL 255
//如果TCP需要排队顺序乱掉的分片那么置1。如果你的设备内存有限,那么定义为0。
#define CYGPKG_LWIP_TCP_QUEUE_OOSEQ 1
//TCP 最大分段长度
#define CYGPKG_LWIP_TCP_MSS 1500
//TCP 发送器缓冲区空间(字节)
#define CYGPKG_LWIP_TCP_SND_BUF 1024*300 //设成20会造成链路断开,可能CDMA对窗口内数据大小有限制。抑或是队列长度要大于50。
//TCP发送器缓冲区空间(pbufs)。这个值必须至少=2*TCP_SND_BUF/TCP_MSS以便正常工作。实测最好保持2倍关系,不要大于2倍。怀疑注释有误,不是至少,是至多。
#define CYGPKG_LWIP_TCP_SND_QUEUELEN 1200
//TCP接收窗口
#define CYGPKG_LWIP_TCP_WND 1024*300
//最大重发数据分段数
#define CYGPKG_LWIP_TCP_MAXRTX 3
//最大重发SYN段数
#define CYGPKG_LWIP_TCP_SYNMAXRTX 6
/* ---------- ARP 选项 ---------- */
#define CYGPKG_LWIP_ARP_TABLE_SIZE 10
/* ---------- IP 选项 ---------- */
//如果你希望拥有在多个网络接口间进行IP包转发的能力,那么定义IP_FORWARD为1。
//如果你要在只有一个网络接口的设备上运行lwIP的话,定义它为0。
#define CYGPKG_LWIP_IP_FORWARD 0
//如果为1,IP选项被允许(但不解析)。如果为0,所有带IP选项的包均被抛掉。
#define CYGPKG_LWIP_IP_OPTIONS 1
/* ---------- ICMP 选项 ---------- */
#define CYGPKG_LWIP_ICMP_TTL 255
/* ---------- DHCP 选项 ---------- */
//如果你需要DHCP端口配置,那么定义LWIP_DHCP为1
#define CYGPKG_LWIP_DHCP 0
//置1,如果你需要在给定地址上进行ARP检测(推荐)
#define CYGPKG_LWIP_DHCP_DOES_ARP_CHECK 1
/* ---------- UDP 选项 ---------- */
#define CYGPKG_LWIP_UDP 1
#define CYGPKG_LWIP_UDP_TTL 255
/* ---------- RAW 插口支持 ---------- */
#define CYGPKG_LWIP_RAW 1
/* ---------- SLIP 选项 --------- */
//#define CYGPKG_LWIP_SLIP 0
#define CYGPKG_LWIP_SLIP_DEV "/dev/ser1"
//#define CYGPKG_LWIP_LOOPIF 0
/* ---------- PPP 选项 --------- */
#define CYGPKG_LWIP_PPP 1
#define CYGPKG_LWIP_PPP_DEV "/dev/ser1"
#define CYGPKG_LWIP_PPP_PAP_AUTH 1
#define CYGPKG_LWIP_PPP_CHAP_AUTH 1
/* --------- 线程优先级 ----------*/
#define CYGPKG_LWIP_TCPIP_THREAD_PRIORITY 4
#define CYGPKG_LWIP_SLIPIF_THREAD_PRIORITY 8
#define CYGPKG_LWIP_PPP_THREAD_PRIORITY 4
/* ---------- 统计选项 ---------- */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -