跳转至

嵌入式以太网编程

概述

嵌入式以太网编程涉及TCP/IP协议栈的实现和应用,是物联网和工业控制系统的核心技术。

网络协议栈

TCP/IP模型

Text Only
┌─────────────────────────────┐
│      应用层 (Application)    │  HTTP, MQTT, CoAP
├─────────────────────────────┤
│      传输层 (Transport)      │  TCP, UDP
├─────────────────────────────┤
│      网络层 (Network)        │  IP, ICMP, ARP
├─────────────────────────────┤
│    数据链路层 (Data Link)    │  Ethernet, MAC
├─────────────────────────────┤
│      物理层 (Physical)       │  PHY
└─────────────────────────────┘

LwIP协议栈

LwIP(Lightweight IP)是专为嵌入式系统设计的轻量级TCP/IP协议栈。

初始化LwIP

C
#include "lwip/init.h"
#include "lwip/netif.h"
#include "netif/ethernetif.h"

static struct netif netif;

void lwip_init(void)
{
    ip4_addr_t ipaddr, netmask, gw;
    
    IP4_ADDR(&ipaddr, 192, 168, 1, 100);
    IP4_ADDR(&netmask, 255, 255, 255, 0);
    IP4_ADDR(&gw, 192, 168, 1, 1);
    
    lwip_init();
    
    netif_add(&netif, &ipaddr, &netmask, &gw, NULL, 
              ethernetif_init, ethernet_input);
    netif_set_up(&netif);
    netif_set_default(&netif);
}

TCP客户端

C
#include "lwip/sockets.h"

int tcp_client_connect(const char *host, int port)
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    inet_pton(AF_INET, host, &addr.sin_addr);
    
    if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        close(sock);
        return -1;
    }
    
    return sock;
}

void tcp_client_example(void)
{
    int sock = tcp_client_connect("192.168.1.1", 80);
    
    // 发送HTTP请求
    const char *request = "GET / HTTP/1.1\r\nHost: 192.168.1.1\r\n\r\n";
    send(sock, request, strlen(request), 0);
    
    // 接收响应
    char buf[1024];
    int len = recv(sock, buf, sizeof(buf), 0);
    
    close(sock);
}

TCP服务器

C
void tcp_server_example(void)
{
    int server_sock = socket(AF_INET, SOCK_STREAM, 0);
    
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    addr.sin_addr.s_addr = INADDR_ANY;
    
    bind(server_sock, (struct sockaddr *)&addr, sizeof(addr));
    listen(server_sock, 5);
    
    while (1) {
        struct sockaddr_in client_addr;
        socklen_t addr_len = sizeof(client_addr);
        int client_sock = accept(server_sock, 
                                 (struct sockaddr *)&client_addr, 
                                 &addr_len);
        
        // 处理客户端
        char buf[1024];
        int len = recv(client_sock, buf, sizeof(buf), 0);
        if (len > 0) {
            send(client_sock, buf, len, 0);
        }
        
        close(client_sock);
    }
}

UDP通信

C
void udp_example(void)
{
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    
    struct sockaddr_in local_addr;
    local_addr.sin_family = AF_INET;
    local_addr.sin_port = htons(12345);
    local_addr.sin_addr.s_addr = INADDR_ANY;
    bind(sock, (struct sockaddr *)&local_addr, sizeof(local_addr));
    
    // 发送UDP数据
    struct sockaddr_in remote_addr;
    remote_addr.sin_family = AF_INET;
    remote_addr.sin_port = htons(12346);
    inet_pton(AF_INET, "192.168.1.2", &remote_addr.sin_addr);
    
    const char *data = "Hello UDP";
    sendto(sock, data, strlen(data), 0,
           (struct sockaddr *)&remote_addr, sizeof(remote_addr));
    
    // 接收UDP数据
    char buf[1024];
    struct sockaddr_in from_addr;
    socklen_t from_len = sizeof(from_addr);
    int len = recvfrom(sock, buf, sizeof(buf), 0,
                       (struct sockaddr *)&from_addr, &from_len);
}

MQTT客户端

使用Paho MQTT

C
#include "MQTTClient.h"

#define ADDRESS     "tcp://192.168.1.1:1883"
#define CLIENTID    "EmbeddedClient"
#define TOPIC       "sensor/data"
#define QOS         1
#define TIMEOUT     10000L

void mqtt_publish(void)
{
    MQTTClient client;
    MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
    MQTTClient_message pubmsg = MQTTClient_message_initializer;
    
    MQTTClient_create(&client, ADDRESS, CLIENTID, 
                      MQTTCLIENT_PERSISTENCE_NONE, NULL);
    
    conn_opts.keepAliveInterval = 20;
    conn_opts.cleansession = 1;
    
    if (MQTTClient_connect(client, &conn_opts) == MQTTCLIENT_SUCCESS) {
        pubmsg.payload = "Hello MQTT";
        pubmsg.payloadlen = strlen("Hello MQTT");
        pubmsg.qos = QOS;
        pubmsg.retained = 0;
        
        MQTTClient_publishMessage(client, TOPIC, &pubmsg, NULL);
        
        MQTTClient_disconnect(client, TIMEOUT);
    }
    
    MQTTClient_destroy(&client);
}

订阅消息

C
void messageArrived(void *context, char *topicName, 
                    int topicLen, MQTTClient_message *message)
{
    printf("Message arrived: %.*s\n", message->payloadlen, 
           (char *)message->payload);
    
    MQTTClient_freeMessage(&message);
    MQTTClient_free(topicName);
}

void mqtt_subscribe(void)
{
    MQTTClient client;
    MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
    
    MQTTClient_create(&client, ADDRESS, CLIENTID,
                      MQTTCLIENT_PERSISTENCE_NONE, NULL);
    
    MQTTClient_setCallbacks(client, NULL, NULL, messageArrived, NULL);
    
    conn_opts.keepAliveInterval = 20;
    conn_opts.cleansession = 1;
    
    if (MQTTClient_connect(client, &conn_opts) == MQTTCLIENT_SUCCESS) {
        MQTTClient_subscribe(client, TOPIC, QOS);
        
        // 保持连接,等待消息
        while (1) {
            // 可以在这里做其他工作
            sleep(1);
        }
    }
}

HTTP客户端

简单HTTP GET

C
#include "lwip/sockets.h"

void http_get(const char *host, const char *path)
{
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(80);
    inet_pton(AF_INET, host, &addr.sin_addr);
    
    connect(sock, (struct sockaddr *)&addr, sizeof(addr));
    
    // 发送请求
    char request[256];
    snprintf(request, sizeof(request),
             "GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n",
             path, host);
    send(sock, request, strlen(request), 0);
    
    // 接收响应
    char response[4096];
    int total = 0, len;
    while ((len = recv(sock, response + total, 
                       sizeof(response) - total - 1, 0)) > 0) {
        total += len;
    }
    response[total] = '\0';
    
    close(sock);
}

网络配置

DHCP客户端

C
#include "lwip/dhcp.h"

void start_dhcp(void)
{
    dhcp_start(&netif);
    
    // 等待获取IP
    while (!dhcp_supplied_address(&netif)) {
        // 处理LwIP定时任务
        sys_check_timeouts();
        sleep(1);
    }
    
    printf("IP: %s\n", ipaddr_ntoa(&netif.ip_addr));
}

静态IP配置

C
void set_static_ip(void)
{
    ip4_addr_t ipaddr, netmask, gw;
    
    IP4_ADDR(&ipaddr, 192, 168, 1, 100);
    IP4_ADDR(&netmask, 255, 255, 255, 0);
    IP4_ADDR(&gw, 192, 168, 1, 1);
    
    netif_set_addr(&netif, &ipaddr, &netmask, &gw);
}

STM32以太网

HAL库初始化

C
#include "stm32f4xx_hal.h"

ETH_HandleTypeDef heth;

void ETH_Init(void)
{
    heth.Instance = ETH;
    heth.Init.AutoNegotiation = ETH_AUTONEGOTIATION_ENABLE;
    heth.Init.Speed = ETH_SPEED_100M;
    heth.Init.DuplexMode = ETH_MODE_FULLDUPLEX;
    heth.Init.PhyAddress = 0;
    heth.Init.RxMode = ETH_RXINTERRUPT_MODE;
    heth.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE;
    heth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII;
    
    HAL_ETH_Init(&heth);
}

以太网中断处理

C
void ETH_IRQHandler(void)
{
    HAL_ETH_IRQHandler(&heth);
}

void HAL_ETH_RxCpltCallback(ETH_HandleTypeDef *heth)
{
    // 接收完成,通知LwIP处理
    ethernetif_input(&netif);
}

ESP32以太网

C
#include "esp_eth.h"
#include "esp_event.h"

void eth_init(void)
{
    // 初始化MAC
    eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
    esp_eth_mac_t *mac = esp_eth_mac_new_dm9051(&mac_config);
    
    // 初始化PHY
    eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
    esp_eth_phy_t *phy = esp_eth_phy_new_dm9051(&phy_config);
    
    // 初始化以太网
    esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy);
    esp_eth_handle_t eth_handle;
    esp_eth_driver_install(&config, &eth_handle);
    
    // 启动以太网
    esp_eth_start(eth_handle);
}

网络调试

ping工具

C
1
2
3
4
5
6
7
8
#include "lwip/icmp.h"
#include "lwip/raw.h"

void ping_send(const char *target)
{
    // 构造ICMP Echo请求
    // 发送并等待响应
}

netstat

Bash
1
2
3
4
# 查看网络连接
cat /proc/net/tcp
cat /proc/net/udp
cat /proc/net/raw

参考资料