Configuração e Portabilidade do LWIP em Plataformas Embarcadas

A integração do stack de protocolos LWIP (Lightweight IP) em sistemas embarcados é um passo fundamental para adicionar capacidades de rede. Este guia detalha as etapas essenciais para configurar e adaptar o LWIP ao seu hardware específico, desde a inicialização básica até a manipulação de pacotes e a configuração de serviços TCP.

Componentes Essenciais do LWIP e Fluxo de Dados

O processo de inicialização do LWIP é iniciado com a chamada à função lwip_init(), que prepara o ambiente interno do stack. Após a inicialização do stack, a interface de rede precisa ser configurada e registrada.

Configuração da Interface de Rede

A função ethernetif_config() é responsável por definir os parâmetros da interface de rede, como endereço IP, máscara de sub-rede e gateway, e por registrá-la no LWIP. Esta função também invoca a inicialização da camada de hardware (low_level_init) e associa as funções de entrada e saída de pacotes.

void configurar_interface_ethernet(void)
{
  struct ip_addr endereco_ip;
  struct ip_addr mascara_sub_rede;
  struct ip_addr gateway_padrao;

  /* Definição padrão do endereço IP */
  IP4_ADDR(&endereco_ip, 192, 168, 5, 15);
  IP4_ADDR(&mascara_sub_rede, 255, 255, 255, 0);
  IP4_ADDR(&gateway_padrao, 192, 168, 5, 1);

  /* Adiciona a interface de rede ao stack LWIP */
  netif_add(&interface_de_rede_eth, &endereco_ip, &mascara_sub_rede, &gateway_padrao, NULL, &inicializar_ethernet_if, &entrada_ethernet_pacote);

  /* Define esta como a interface de rede padrão */
  netif_set_default(&interface_de_rede_eth);
  netif_set_up(&interface_de_rede_eth);

  printf("Interface de rede Ethernet registrada com sucesso.\r\n");
  printf("IP: %s\r\n", ipaddr_ntoa(&interface_de_rede_eth.ip_addr));
  printf("Máscara: %s\r\n", ipaddr_ntoa(&interface_de_rede_eth.netmask));
  printf("Gateway: %s\r\n", ipaddr_ntoa(&interface_de_rede_eth.gw));
}

Fluxo de Entrada de Pacotes

O tratamento de pacotes recebidos é orquestrado pela função de entrada da interface, como ethernetif_input(). Esta função é tipicamente chamada em um loop principal ou em uma rotina de interrupção após a recepção de um pacote Ethernet pelo hardware.

O processo de entrada de pacotes segue uma cadeia lógica:

  1. ethernetif_input(struct netif *netif): Função de alto nível que serve como ponto de entrada para pacotes recebidos.
  2. Esta função chama pbuf *low_level_input(struct netif *netif) para ler os dados brutos do hardware.
  3. Após obter o pbuf (buffer de pacote LWIP), ele extrai o cabeçalho Ethernet.
  4. Com base no tipo do cabeçalho Ethernet (por exemplo, ETHTYPE_IP, ETHTYPE_ARP), o pacote é encaminhado para a função de entrada específica do protocolo, geralmente netif->input(p, netif), que no caso de Ethernet aponta para ethernet_input.
  5. ethernet_input (localizada em etharp.c) verifica novamente o tipo do cabeçalho Ethernet:
    • Para ETHTYPE_ARP: Chama etharp_arp_input() para processar solicitações/respostas ARP.
    • Para ETHTYPE_IP: Chama ip_input() para iniciar o processamento do pacote IP.
  6. ip_input() (em ip_input.c) analisa o cabeçalho IP e, com base no protocolo (campo nexthdr):
    • IP_PROTO_UDP: Encaminha para udp_input().
    • IP_PROTO_TCP: Encaminha para tcp_input().
    • IP_PROTO_ICMP: Encaminha para icmp_input().
  7. As funções de protocolo específico (udp_input, tcp_input, icmp_input) realizam o processamento final do pacote, entregando-o à aplicação apropriada ou respondendo conforme necessário.

Etapas para a Portabilidade do LWIP

1. Obtenção do Código-Fonte do LWIP

O primeiro passo é adquirir o código-fonte do LWIP. Isso pode ser feito baixando-o do repositório oficial do projeto ou utilizando uma versão já adaptada para uma plataforma similar, muitas vezes fornecida por fabricantes de microcontroladores.

2. Integração ao Projeto de Desenvolvimento

Após obter o LWIP, os arquivos devem ser adicionados ao seu ambiente de desenvolvimento (IDE, como Keil MDK, STM32CubeIDE, IAR Embedded Workbench, etc.).

  • Copie a estrutura de pastas do LWIP (lwip/src, lwip/include, etc.) para o diretório do seu projeto.
  • Adicione os arquivos .c do LWIP aos grupos/pastas correspondentes no seu projeto.
  • Configure os caminhos de inclusão (include paths) para que o compilador encontre os arquivos de cabeçalho do LWIP (lwip/src/include e lwip/src/include/lwip, por exemplo).

3. Adaptação das Funções Essenciais

3.1. void lwip_init(void)

Esta função é a porta de entrada para a inicialização do stack LWIP. Basta chamá-la no início do seu programa, geralmente após a inicialização do sistema operacional em tempo real (RTOS), se houver, ou no main().

3.2. void configurar_interface_ethernet(void)

Conforme explicado anteriormente, esta função define os parâmetros de rede da sua interface. Certifique-se de que os endereços IP, máscara e gateway sejam apropriados para sua rede. A variável global interface_de_rede_eth representa a instância da sua interface de rede.

3.3. static void inicializar_ethernet_if(struct netif *netif)

Esta função é chamada internamente por netif_add e é crucial para a inicialização de baixo nível da interface de rede. Você deve personalizar o endereço MAC e a função de inicialização do seu controlador Ethernet.

static void
inicializar_ethernet_if(struct netif *netif)
{
    /* Define o comprimento do endereço MAC */
    netif->hwaddr_len = ETHARP_HWADDR_LEN;

    /* Define o endereço MAC do hardware (exemplo) */
    netif->hwaddr[0] = 0x00;
    netif->hwaddr[1] = 0x80;
    netif->hwaddr[2] = 0xE1;
    netif->hwaddr[3] = 0x01;
    netif->hwaddr[4] = 0x02;
    netif->hwaddr[5] = 0x03;

    /* Unidade de Transmissão Máxima (MTU) */
    netif->mtu = 1500;

    /* Capacidades da interface */
    netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;

    /* Inicializa o controlador Ethernet específico do seu hardware */
    inicializar_controlador_ethernet_hw();
}

A função inicializar_controlador_ethernet_hw() deve ser a sua rotina para configurar o periférico GMAC (Ethernet MAC) e o PHY (Physical Layer Transceiver) do seu microcontrolador.

3.4. low_level_output()

Esta função é a interface do LWIP para o hardware de envio de pacotes. Ela recebe um pbuf contendo o pacote a ser enviado e deve transferir os dados para o controlador Ethernet para transmissão física. Você precisará substituir o corpo desta função pela sua implementação de envio de dados Ethernet.

3.5. low_level_input()

Em contraste, low_level_input() é a interface do LWIP para a recepção de pacotes do hardware. Ela deve ler um pacote completo do controlader Ethernet e encapsulá-lo em um pbuf LWIP. A implementação desta função deve interagir com os buffers de recepção do seu hardware Ethernet.

3.6. Chamada de ethernetif_input()

Uma vez que as funções de baixo nível e a configuração da interface estejam prontas, você deve chamar ethernetif_input(&interface_de_rede_eth) periodicamente em seu loop principal, ou preferencialmente, no contexto de uma interrupção de recepção de Ethernet, para processar os pacotes recebidos.

Configurando um Servidor TCP

Para estabelecer comunicação TCP, é necessário criar e cofnigurar um bloco de controle de protocolo (PCB) TCP, vinculá-lo a uma porta e habilitá-lo para escuta. O LWIP gerencia as conexões e invoca callbacks definidos pelo usuário para eventos como novas conexões ou dados recebidos.

int iniciar_servidor_tcp_echo()
{
    struct tcp_pcb *pcb_servidor;
    err_t erro;
    unsigned short porta_servico = 7000;

    /* Cria uma nova estrutura TCP PCB */
    pcb_servidor = tcp_new();
    if (!pcb_servidor) {
        printf("Erro ao criar PCB. Sem memória.\n\r");
        return -1;
    }

    /* Vincula o PCB ao endereço IP 'qualquer' e à porta especificada */
    erro = tcp_bind(pcb_servidor, IP_ADDR_ANY, porta_servico);
    if (erro != ERR_OK) {
        printf("Não foi possível vincular à porta %d: erro = %d\n\r", porta_servico, erro);
        tcp_close(pcb_servidor); // Liberar recursos em caso de falha
        return -2;
    }

    /* Não precisamos de argumentos para as funções de callback neste exemplo */
    tcp_arg(pcb_servidor, NULL);

    /* Começa a escutar por novas conexões */
    pcb_servidor = tcp_listen(pcb_servidor);
    if (!pcb_servidor) {
        printf("Sem memória ao tentar tcp_listen.\n\r");
        return -3;
    }

    /* Especifica a função de callback a ser usada para conexões de entrada */
    tcp_accept(pcb_servidor, callback_aceitar_conexao);

    printf("\r\n-----Servidor TCP echo iniciado na porta %d-----\n\r", porta_servico);

    return 0;
}

Após a execução deste código, o servidor TCP estará ativo. Quando ethernetif_input() processar um pacote TCP destinado à porta 7000, e os cabeçalhos IP estiverem corretos, o LWIP invocará a função callback_aceitar_conexao() para gerenciar a nova conexão e os dados subsequentes.

Tags: lwIP TCP/IP Rede Embarcada ethernet Microcontrolador

Publicado em 7-24 20:24