通过lwip2.0.2 PPP协议与GPRS模块实现网络通讯

一、基本概念

1、网络协议

GPRS无线数据传输的最低层,即物理层是通过RS232串口及GPRS模块组成的,然后是数据链路层。其中涉及到PPP协议实现过程。数据链路层上面是网络层,其后是传输层,TCP/IP其中IP协议属于网络层协议,而UDP、TCP都属于传输层协议传输层上方的是包括会话层、表示层、应用层等。
针对LWIP来看,硬件结构可以分为网卡或者串口两种物理层架构,如果用到网卡将使用netif文件夹下的 etharp.c、ethernetif.c如果使用串口等串行通讯,则使用netif/ppp文件夹下的文件。例如PPPoS。利用gprs等网络模块的使用pppos协议,要注意与pppoe区别:
PPP的典型步骤为:
    1、LCP:链路控制协议,主要商议转义规则和选择客户授权协议,典型4个报文
    2、PAP或CHAP:使用其中一种协议申请授权,典型2个报文
    3、IPCP:获取IP地址,DNS地址等(注意没有网关和子网掩码),典型3个或5个报文。
而PPP的典型步骤为:
    1、PPPOE的发现阶段:典型4个报文,目的是获取对方的MAC地址和确定一个sessionid。
    2、PPPOE的会话阶段:包含上述所有PPP步骤。
由此可见,PPPOE是在以太网链路上通过发现阶段打通一条PPP链接,再进行PPP协议。
完整的链路可以归结为:物理层(GPRS模块、串口)——数据链路层(PPP协议簇-PPPoS)——网络层(IP协议)

2、移植

具体移植事项按照官方的移植文档。粗略翻译如下:

1、全局变量

控制块:struct ppp_pcb_s(ppp.h-312行)–>typedef struct ppp_pcb_s ppp_pcb(ppp.h-154行)–>ppp_pcb *ppp(程序中用户定义);
通信协议接口:struct netif ppp_netif;

2、PPP status callback PPP状态回调函数

PPP状态回调函数是lwip内核线程在PPP状态改变时候调用的。PPP状态回调函数例子:

static void status_cb(ppp_pcb *pcb, int err_code, void *ctx) {
  struct netif *pppif = ppp_netif(pcb);
  LWIP_UNUSED_ARG(ctx);

  switch(err_code) {
    case PPPERR_NONE: {
#if LWIP_DNS
      const ip_addr_t *ns;
#endif /* LWIP_DNS */
      printf("status_cb: Connected\n");
#if PPP_IPV4_SUPPORT
      printf("   our_ipaddr  = %s\n", ipaddr_ntoa(&pppif->ip_addr));
      printf("   his_ipaddr  = %s\n", ipaddr_ntoa(&pppif->gw));
      printf("   netmask     = %s\n", ipaddr_ntoa(&pppif->netmask));
#if LWIP_DNS
      ns = dns_getserver(0);
      printf("   dns1        = %s\n", ipaddr_ntoa(ns));
      ns = dns_getserver(1);
      printf("   dns2        = %s\n", ipaddr_ntoa(ns));
#endif /* LWIP_DNS */
#endif /* PPP_IPV4_SUPPORT */
#if PPP_IPV6_SUPPORT
      printf("   our6_ipaddr = %s\n", ip6addr_ntoa(netif_ip6_addr(pppif, 0)));
#endif /* PPP_IPV6_SUPPORT */
      break;
    }
    case PPPERR_PARAM: {
      printf("status_cb: Invalid parameter\n");
      break;
    }
    case PPPERR_OPEN: {
      printf("status_cb: Unable to open PPP session\n");
      break;
    }
    case PPPERR_DEVICE: {
      printf("status_cb: Invalid I/O device for PPP\n");
      break;
    } -
    case PPPERR_ALLOC: {
      printf("status_cb: Unable to allocate resources\n");
      break;
    }
    case PPPERR_USER: {
      printf("status_cb: User interrupt\n");
      break;
    }
    case PPPERR_CONNECT: {
      printf("status_cb: Connection lost\n");
      break;
    }
    case PPPERR_AUTHFAIL: {
      printf("status_cb: Failed authentication challenge\n");
      break;
    }
    case PPPERR_PROTOCOL: {
      printf("status_cb: Failed to meet protocol\n");
      break;
    }
    case PPPERR_PEERDEAD: {
      printf("status_cb: Connection timeout\n");
      break;
    }
    case PPPERR_IDLETIMEOUT: {
      printf("status_cb: Idle Timeout\n");
      break;
    }
    case PPPERR_CONNECTTIME: {
      printf("status_cb: Max connect time reached\n");
      break;
    }
    case PPPERR_LOOPBACK: {
      printf("status_cb: Loopback detected\n");
      break;
    }
    default: {
      printf("status_cb: Unknown error code %d\n", err_code);
      break;
    }
  }

/*
 * This should be in the switch case, this is put outside of the switch
 * case for example readability.
 */

  if (err_code == PPPERR_NONE) {
    return;
  }

  /* ppp_close() was previously called, don't reconnect */
  if (err_code == PPPERR_USER) {
    /* ppp_free(); -- can be called here */
    return;
  } 

  /*
   * Try to reconnect in 30 seconds, if you need a modem chatscript you have
   * to do a much better signaling here ;-)
   */30秒内继续链接,如果你需要一个调制调解器,你必须有更好的信号。
  ppp_connect(pcb, 30);
  /* OR ppp_listen(pcb); */
}

3、Creating a new PPPoS session创建一个新的PPPoS会话。

In lwIP, PPPoS is not PPPoSONET, in lwIP PPPoS is PPPoSerial.
在lwip中,PPPoS不是PPPoSONET,而是串口

#include "netif/ppp/pppos.h"
  • PPPoS serial output callback PPPoS的串口输出回调函数
  • ppp_pcb, PPP control blockPPP控制块
  • data, buffer to write to serial port串口写入缓冲
  • len, length of the data buffer数据缓冲长度
  • ctx, optional user-provided callback context pointer用户定义可以回调的上下文指针
  • Return value: len if write succeed如果成功返回写入的长度
static u32_t output_cb(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx) {
  return uart_write(UART, data, len);
}

/*
* Create a new PPPoS interface创建一个新的PPPoS接口
* ppp_netif, netif to use for this PPP link, i.e. PPP IP interface 这次用到的网络接口 例如PPP IP接口
* output_cb, PPPoS serial output callback
* status_cb, PPP status callback, called on PPP status change (up, down, …)
* ctx_cb, optional user-provided callback context pointer
*/

ppp = pppos_create(&ppp_netif,output_cb, status_cb, ctx_cb);//这是初始化创建ppp接口的

6、Closing PPP connection 关闭PPP/*

  • Initiate the end of the PPP session, without carrier lost signal
  • (nocarrier=0), meaning a clean shutdown of PPP protocols.
  • You can call this function at anytime.
    */
u8_t nocarrier = 0;
ppp_close(ppp, nocarrier);

/*
* Then you must wait your status_cb() to be called, it may takes from a few
* seconds to several tens of seconds depending on the current PPP state.
*/

/*
* Freeing a PPP connection
* ========================
*/

/*
* Free the PPP control block, can only be called if PPP session is in the
* dead state (i.e. disconnected). You need to call ppp_close() before.
*/

ppp_free(ppp);

三、PPPoS input path (raw API, IRQ safe API, TCPIP API) PPPoS 输入通道

Received data on serial port should be sent to lwIP using the pppos_input() function or the pppos_input_tcpip() function.
从串口接收到的数据应该通过pppos_input()函数或者pppos_input_tcpip函数发送到lwip。

If NO_SYS is 1 and if PPP_INPROC_IRQ_SAFE is 0 (the default), pppos_input() is not IRQ safe and then MUST only be called inside your main loop.
如果不使用操作系统,并且PPP_INPROC_IRQ_SAFE为0,pppos_input()函数不能在中断中调用,只能在main()主函数中调用。

Whatever the NO_SYS value, if PPP_INPROC_IRQ_SAFE is 1, pppos_input() is IRQ safe and can be safely called from an interrupt context, using that is going to reduce your need of buffer if pppos_input() is called byte after byte in your RX serial interrupt.
无论是否使用操作系统,如果PPP_INPROC_IRQ_SAFE是1的话,pppos_input()是可以在中断中调用的。这样做可以在串口收中断中一个字节一个字节的调用,降低了buffer容量的要求。

if NO_SYS is 0, the thread safe way outside an interrupt context is to use the pppos_input_tcpip() function to pass input data to the lwIP core thread using the TCPIP API. This is thread safe in all cases but you should avoid passing data byte after byte because it uses heavy locking (mailbox) and it allocates pbuf, better fill them !
如果不使用操作系统,想在中断外以线程安全的方式把数据传输到lwip内核的方式是使用TCPIP的API中pppos_input_tcpip()函数。
在所有情况下,这是线程安全的,但是您应该避免字节的传递,因为它使用了严格锁(邮箱),并且它分配了pbuf,更好的填充它们!

if NO_SYS is 0 and if PPP_INPROC_IRQ_SAFE is 1, you may also use pppos_input() from an RX thread, however pppos_input() is not thread safe by itself. You can do that BUT you should NEVER call pppos_connect(), pppos_listen() and ppp_free() if pppos_input() can still be running, doing this is NOT thread safe at all. Using PPP_INPROC_IRQ_SAFE from an RX thread is discouraged unless you really know what you are doing, your move ;-)
如果使用操作系统并且PPP_INPROC_IRQ_SAFE是1的话。你可能在串口接收线程中使用pppos_input()函数,但是pppos_input()本身不是线程安全的。
你可以这么用但是一定注意如果pppos_input()在运行的时候,就不能使用pppos_connect(),pppos_listen(),和ppp_free()。在串口接收线程中,不鼓励使用PPP_INPROC_IRQ_SAFE,除非你真的知道你在干什么,你的移动?

void pppos_input(ppp, buffer, buffer_len);
or
void pppos_input_tcpip(ppp, buffer, buffer_len);

Thread safe PPP API (PPPAPI)

There is a thread safe API for all corresponding ppp_* functions, you have to enable LWIP_PPP_API in your lwipopts.h file, then see include/netif/ppp/pppapi.h, this is actually pretty obvious.

补充移植函数

uint32_t output_cb(ppp_pcb *pcb, u8_t *data, uint32_t len, void *ctx) {

    uint32_t n;
    n=USART_SendBuffer(USART1,data,len);//串口输出函数,自己编写
    return n;
}

uint8_t lwip_comm_init(void)
{
    tcpip_init(NULL,NULL);//调用内核函数,完成内核的初始化
    ppp = pppos_create(&ppp_netif,output_cb,link_status_cb, &erraddr8);创建pppos接口
    erraddr8=pppapi_set_default(ppp);//把PPPOS设置为内核默认数据传输路径
    return erraddr8;
}

void link_status_cb(ppp_pcb *pcb, int err_code, void *ctx)
{
按照status_cb编写
}

串口接收函数

移植的主要部分就是串口接收部分,完成的功能和网卡驱动的功能是一样的,把接收的数据存入buffer,判断是否接收到完整帧,然后把接收到的完整帧数据传输给内核。
我最初使用的是串口接收中断中判断是否接收到帧头帧尾:0x7e,然后判断是否接收到完整帧,如果接收到完整帧用pppos_input()递交给内核,功能实现成功,但是使用过程中发现数据接收不稳定。证明流程没有问题,只是实现方式有问题。
后来进行优化,另开辟一个任务,专门查询是否接收完成,如果接收完成用

pppos_input_tcpip(ppp, buffer, buffer_len)

递交给内核。另外串口中断部分使用双缓冲数组方式保证数据的顺利接收和实时性。实际使用良好。

PPP初始化流程

1、创建网络接口:
创建一个全局的网络接口:struct netif pppnetif;
2、创建PPP控制块并初始化
用pppos_create()创建PPP控制块:ppppcb,并把网络接口、串口输出回调函数等赋值给ppppcb。
3、设置默认网络接口
用pppapi_ppp_set_default(ppp)把PPP设置为默认的网络接口。
4、设置用户及密码
如果用到验证,用ppp_set_auth();函数设置用户名及密码
5、启动ppp_connect();
ppp_connect(ppp,holdoff);
因为在用pppos_create()创建ppp_pcb的时候已经把pppos_connect()的回调函数地址赋值给ppppcb了,所以启用ppp_connect()后就是直接启用pppos。

系统初始化流程

1、延时函数及串口初始化
初始化SYSTICK。
初始化串口及PPPoS端口。
2、ucosii系统初始化 OSInit();
3、LWIP初始化

你可能感兴趣的:(STM32代码)