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:
ethernetif_input(struct netif *netif): Função de alto nível que serve como ponto de entrada para pacotes recebidos.- Esta função chama
pbuf *low_level_input(struct netif *netif)para ler os dados brutos do hardware. - Após obter o
pbuf(buffer de pacote LWIP), ele extrai o cabeçalho Ethernet. - 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, geralmentenetif->input(p, netif), que no caso de Ethernet aponta paraethernet_input. ethernet_input(localizada emetharp.c) verifica novamente o tipo do cabeçalho Ethernet:- Para
ETHTYPE_ARP: Chamaetharp_arp_input()para processar solicitações/respostas ARP. - Para
ETHTYPE_IP: Chamaip_input()para iniciar o processamento do pacote IP.
- Para
ip_input()(emip_input.c) analisa o cabeçalho IP e, com base no protocolo (camponexthdr):IP_PROTO_UDP: Encaminha paraudp_input().IP_PROTO_TCP: Encaminha paratcp_input().IP_PROTO_ICMP: Encaminha paraicmp_input().
- 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
.cdo 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/includeelwip/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.