使用无OS的LWIP进行UDP通信

  • Post author:
  • Post category:嵌入式 / 所有文章
  • Post comments:0评论
  • Post last modified:2025年1月6日
  • Reading time:6 mins read
无OS的LWIP仅能使用RAW API,其中常用的与UDP相关的有这些: RAW API函数 描述
udp_new Creates a new UDP pcb which can be used for UDP communication. The pcb is not active until it has either been bound to a local address or connected to a remote address.
创建一个新的 UDP PCB(协议控制块),可以用于 UDP 通信。该 PCB 在绑定到本地地址或连接到远程地址之前是非活动状态。
udp_remove Removes and deallocates the pcb.
移除并释放 PCB。
udp_bind Bind an UDP PCB.
绑定一个 UDP PCB。
udp_connect Sets the remote end of the pcb. This function does not generate any network traffic, but only sets the remote address of the pcb.
设置 PCB的远端地址。此函数不会产生任何网络流量,只是设置 PCB 的远程地址。
udp_disconnect Remove the remote end of the pcb. This function does not generate any network traffic, but only removes the remote address of the pcb.
移除 PCB的远端地址。此函数不会产生任何网络流量,只是移除 PCB 的远程地址。
udp_send Sends the pbuf p using UDP. The pbuf is not deallocated.
使用 UDP 发送 pbuf p。pbuf 不会被释放。
udp_sendto Send data to a specified address using UDP.
使用 UDP 向指定地址发送数据。
udp_recv Set a receive callback for a UDP PCB. This callback will be called when receiving a datagram for the pcb.
为一个UDP PCB设置一个接收回调函数。当接收到一个数据报时,这个回调函数将被调用。

其中PCB(协议控制块)就是一个用于UDP通信的对象。pbuf是以链表形式存在的数据包,接受的数据就是以pbuf形式储存,也需要先将数据装填进pbuf中再进行发送。

直接发送数据

让我们从简单粗暴的使用udp_sendto发送数据开始。

添加两个所需的头文件
code

需要PCB和pbuf的指针,targetIp、要发送的信息以及辅助变量。
code

创建新PCB
code

创建并填充pbuf并定期发送
code

虽然我不理解但是在之前的调试中我发现,udp_sendto可能会对pbuf进行修改:如果把填充pbuf放到while外面,也就是只填充一次之后一直发送这同一个包。那么从第二次发送开始,发送的内容前面会多出来一坨我也不知道是什么的东西。所以我放在了while中,每次发送重新分配空间。

整个main函数:

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */
  struct udp_pcb *upcb = NULL;
  struct pbuf *sendPbuf = NULL;
  ip_addr_t targetIP;           //< 存放目标IP地址
  const char* message = "Tick!\n";
  int i = 0,j = 0;

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
  HAL_Delay(100);

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART3_UART_Init();
  MX_USB_OTG_FS_PCD_Init();
  MX_LWIP_Init();
  /* USER CODE BEGIN 2 */

  upcb = udp_new();
  //< 创建新upcb

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    MX_LWIP_Process();

    if (i>100)
    {
        sendPbuf = pbuf_alloc(PBUF_TRANSPORT, strlen(message), PBUF_RAM);
        //< 为pbuf分配空间

        memset(sendPbuf->payload,0,sendPbuf->len);
        //< 清空pbuf数据
        memcpy(sendPbuf->payload, message, strlen(message));
        //< 填充数据

        IP4_ADDR( &targetIP, 192, 168, 1, 40);
        udp_sendto(upcb, sendPbuf, &targetIP, 51087);
        pbuf_free(sendPbuf);

        i = 0;
    }
    if (j>10000)
    {
        i++;
        j = 0;
    }
    j++;
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

编译烧录测试,使用软件监听上述代码设置的51087端口,我们可以看到:
test

以非连接态通信

即使用udp_bind作为服务器接收任何任意发来的信息并回复。

先写一个回调函数,这里我直接原样发回去。

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
static void UDPCallback(void *arg,struct udp_pcb *upcb,struct pbuf *revBuf,const ip_addr_t *addr,u16_t port)
{
  udp_sendto(upcb, revBuf, addr, port);
  pbuf_free(revBuf);
}
/* USER CODE END 0 */

然后在main中绑定本地端口和接收回调函数。
code

完整的main函数:

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */
  struct udp_pcb *upcb = NULL;

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
  HAL_Delay(100);

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART3_UART_Init();
  MX_USB_OTG_FS_PCD_Init();
  MX_LWIP_Init();
  /* USER CODE BEGIN 2 */

  upcb = udp_new();
  //< 创建新upcb

  udp_bind(upcb, IP_ADDR_ANY, 8081);
  //< 绑定本地ip和端口
  udp_recv(upcb,UDPCallback, NULL);
  //< 绑定回调函数
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    MX_LWIP_Process();

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

编译烧录测试,用测试软件向MCU发送UDP包,可以收到回复:
test

我另使用了手机一起进行了测试,在非连接态,当有多个任意地址的设备向MCU发送数据包时,都能进入回调函数并回复。

这里有一点可以注意到,回调函数的形参const ip_addr_t *addr,u16_t port存储的是数据包的发送方的地址和端口,所以这个函数的功能才是原路发回:从电脑(192.168.1.40)的56050端口发向MCU(192.168.1.118)的8081端口,回复则是从MCU(192.168.1.118)的8081端口发向从电脑(192.168.1.40)的56050端口,测试软件左侧的本地端口可以设为任意值。

以连接态通信

即使用udp_connect作为客户端对特定地址进行通信。

使用udp_connect绑定远程地址后,即可直接使用udp_send向绑定的远程地址发送数据,且仅当接收到远程地址发来的数据包时进入回调函数。

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */
  struct udp_pcb *upcb = NULL;
  struct pbuf *sendPbuf = NULL;
  ip_addr_t targetIP;           //< 存放目标IP地址
  const char* message = "Tick!\n";
  int i = 0,j = 0;

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
  HAL_Delay(100);

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART3_UART_Init();
  MX_USB_OTG_FS_PCD_Init();
  MX_LWIP_Init();
  /* USER CODE BEGIN 2 */

  upcb = udp_new();
  //< 创建新upcb

  udp_recv(upcb,UDPCallback, NULL);
  //< 绑定回调函数
  IP4_ADDR( &targetIP, 192, 168, 1, 40);
  udp_connect(upcb, &targetIP, 51087);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    MX_LWIP_Process();

    if (i>100)
    {
        sendPbuf = pbuf_alloc(PBUF_TRANSPORT, strlen(message), PBUF_RAM);
        //< 为pbuf分配空间

        memset(sendPbuf->payload,0,sendPbuf->len);
        //< 清空pbuf数据
        memcpy(sendPbuf->payload, message, strlen(message));
        //< 填充数据

        udp_send(upcb, sendPbuf);
        pbuf_free(sendPbuf);

        i = 0;
    }
    if (j>10000)
    {
        i++;
        j = 0;
    }
    j++;
      /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

测试结果:
test

可以看到随机分配了一个62510端口,而我们向这个端口发送test也可以收到回复。我另使用了手机一起进行了测试,在连接态,仅有我的电脑(192.168.1.40)发送数据有回复。

在连接态也可以使用udp_bind绑定本地端口,这时候就是从固定的端口而不是随机的端口发送数据了,且依旧仅对绑定的远程地址的数据有响应。

有的教程可能会说udp_sendto相当于先udp_connect再udp_send,这也是不准确的。udp_connect是设置PCB的远端地址和端口,udp_sendto是向(可能是不同的)指定远端地址和端口发送信息但是不修改PCB绑定的远端地址和端口。

发表回复