Lập trình nhúng

Giao thức truyền thông trên STM32 (Phần 10): Ethernet và TCP/IP – MAC, PHY, RMII, LwIP và TCP echo server

Ethernet trên STM32 từ kiến trúc MAC, PHY, RMII, cấu hình LwIP và DHCP đến một TCP echo server dùng netconn, kèm bảng thông lượng tính toán và các lỗi hay gặp. Phần 10 của series giao thức truyền thông trên STM32.

10 phút đọc
Giao thức truyền thông trên STM32 (Phần 10): Ethernet và TCP/IP – MAC, PHY, RMII, LwIP và TCP echo server

Ghi chú: sơ đồ trong bài do Capitlab vẽ để minh hoạ. Số liệu trong các bảng là giá trị tính theo công thức, cột "Đo thực tế" sẽ được cập nhật khi đo trên phần cứng. Mã nguồn soạn theo tài liệu HAL và datasheet, chưa chạy thử trên phần cứng, nên hãy kiểm tra lại trước khi dùng cho sản phẩm.

Có Ethernet, thiết bị có địa chỉ IP và làm được những việc như mở trang cấu hình trên trình duyệt, gửi MQTT, chạy Modbus TCP, cập nhật firmware qua mạng. Cái giá là phần cứng nhiều hơn (MAC, PHY, đầu nối có biến áp) và một ngăn xếp giao thức phần mềm khá nặng.

Bài này giải thích các khối ghép với nhau ra sao, rồi dựng một TCP echo server. Echo server không làm được gì nhiều, nhưng nó là điểm xuất phát cho mọi ứng dụng mạng khác: chạy được echo nghĩa là phần cứng, driver và ngăn xếp đều đã ổn.

1. Kiến trúc

Các lớp từ ứng dụng, TCP/UDP, IP, driver ETH, MAC, PHY, biến áp RJ45, chia thành phần mềm và phần cứng

Hình 1. Ngăn xếp Ethernet trên STM32. Phần mềm gồm LwIP và driver ETH, phần cứng gồm MAC trong chip, PHY và biến áp.

Đi từ dưới lên có ba lớp.

PHY là chip tầng vật lý, ví dụ LAN8742A, DP83848, KSZ8081. Nó nối với đầu RJ45 có biến áp và chuyển tín hiệu ra cáp xoắn đôi 10/100 Mbit/s.

MAC là khối ngoại vi có sẵn trong một số dòng STM32 (F4, F7, H7, H5, U5...). MAC nói chuyện với PHY qua MII hoặc RMII, và dùng DMA để đưa khung vào bộ nhớ.

Trên cùng là ngăn xếp TCP/IP, phần mềm lo ARP, IP, ICMP (ping), UDP, TCP và DHCP. Trên STM32, lựa chọn phổ biến là LwIP, có sẵn trong Cube và thường chạy cùng FreeRTOS.

Dòng STM32 không có MAC vẫn lên mạng được nhờ chip Ethernet giao tiếp SPI. W5500 có sẵn ngăn xếp TCP/IP bằng phần cứng, ENC28J60 thì cần ngăn xếp phần mềm. Cả hai đều chậm hơn MAC tích hợp.

1.1. Kết nối RMII

Các tín hiệu RMII giữa MAC của STM32 và PHY: REF_CLK, TXD0 TXD1, TX_EN, RXD0 RXD1, CRS_DV, MDC, MDIO

Hình 2. RMII dùng 7 tín hiệu dữ liệu và 2 tín hiệu quản lý (MDC, MDIO).

RMII gồm REF_CLK (50 MHz, do PHY hoặc MCU cấp tuỳ thiết kế), TXD0/TXD1, TX_EN, RXD0/RXD1, CRS_DV, cùng cặp MDC/MDIO để MCU đọc trạng thái link và tốc độ từ PHY. Đây là tín hiệu 50 MHz, nên đường mạch phải ngắn và cùng độ dài. Thiết kế mẫu của các board Nucleo và Discovery là tài liệu tham khảo tốt. MII dùng nhiều chân hơn (clock 25 MHz, 4 bit mỗi chiều) nên ít được chọn.

2. Thông lượng lý thuyết

Ngoài phần dữ liệu, mỗi khung Ethernet trên dây còn có tiền đồng bộ 8 byte, tiêu đề 14 byte, FCS 4 byte và khoảng nghỉ giữa khung (IFG) 12 byte. Bảng tính, chưa phải số đo:

Đại lượng Giá trị
Khung nhỏ nhất (64 byte) cộng 20 byte phụ trội 84 byte = 672 bit
Thời gian một khung nhỏ nhất ở 100 Mbit/s 6,72 µs
Số khung nhỏ nhất tối đa mỗi giây (100 Mbit/s) 148 810
Số khung nhỏ nhất tối đa mỗi giây (10 Mbit/s) 14 881
Một khung TCP đầy (MSS 1460 byte) trên dây 1538 byte
Thời gian một khung TCP đầy ở 100 Mbit/s 123 µs
Thông lượng dữ liệu TCP tối đa lý thuyết (100 Mbit/s) khoảng 94,9 Mbit/s ≈ 11,9 MB/s

94,9 Mbit/s là trần cho dữ liệu ứng dụng trong một luồng TCP đầy gói, sau khi trừ 38 byte phụ trội Ethernet (tiền đồng bộ, tiêu đề, FCS, IFG), 20 byte tiêu đề IP và 20 byte tiêu đề TCP. STM32 thực tế đạt thấp hơn nhiều vì CPU, kích thước cửa sổ TCP và bộ nhớ đệm đều là giới hạn. Con số thật phải đo bằng iperf.

3. Cấu hình trong STM32CubeIDE

Bước Thiết lập Ghi chú
1 Bật ETH, chế độ RMII Tên chế độ phụ thuộc dòng chip, kiểm tra chân khớp mạch
2 Bật LWIP Chọn DHCP hoặc địa chỉ IP tĩnh
3 Địa chỉ MAC duy nhất Nếu nhiều thiết bị, mỗi cái một địa chỉ
4 Bật FreeRTOS (CMSIS_V2) Cần nếu dùng API socket hoặc netconn. Không RTOS dùng API raw
5 Cấu hình PHY Địa chỉ PHY, driver đúng chip PHY trên mạch
6 Kiểm tra clock REF_CLK 50 MHz ổn định, MCU đủ xung nhịp
7 Bộ nhớ LwIP Kích thước heap, số lượng và độ lớn pool bộ đệm gói (PBUF)

Cắm cáp xong, việc đầu tiên là ping từ máy tính tới IP của STM32. Ping được nghĩa là PHY, MAC, driver và LwIP đều đã chạy. Nếu dùng DHCP, IP do router cấp, nên in nó ra UART để biết.

3.1. DHCP

DHCP: STM32 gửi Discover, server trả Offer, STM32 gửi Request, server trả Ack

Hình 3. Bốn bước DHCP để thiết bị nhận địa chỉ IP. LwIP tự làm toàn bộ khi bật DHCP.

Thiết bị phát quảng bá DISCOVER, server trả OFFER đề nghị một địa chỉ, thiết bị gửi REQUEST để nhận địa chỉ đó, và server trả ACK kèm thời hạn thuê.

DHCP chỉ thành công khi link đã lên. Nếu DHCP chạy trước lúc cắm cáp, nó sẽ thất bại và phải chạy lại. Mỗi lần rút rồi cắm lại cáp cũng có thể cần khởi động lại DHCP.

4. Mã nguồn: TCP echo server dùng netconn

Đoạn này chạy trong một task FreeRTOS, sau khi LwIP đã khởi tạo và có IP:

#include "lwip/api.h"

void tcp_echo_task(void *arg)
{
    struct netconn *listener = netconn_new(NETCONN_TCP);
    netconn_bind(listener, IP_ADDR_ANY, 7);       // cổng echo
    netconn_listen(listener);

    for (;;) {
        struct netconn *client;
        if (netconn_accept(listener, &client) != ERR_OK) continue;

        struct netbuf *buf;
        while (netconn_recv(client, &buf) == ERR_OK) {
            void *data; u16_t len;
            do {
                netbuf_data(buf, &data, &len);
                netconn_write(client, data, len, NETCONN_COPY);   // gửi lại nguyên văn
            } while (netbuf_next(buf) >= 0);
            netbuf_delete(buf);
        }
        netconn_close(client);
        netconn_delete(client);
    }
}

Từ máy tính, thử bằng ncat <ip> 7 hoặc telnet <ip> 7: gõ gì thì nhận lại đúng cái đó. Mã trên chỉ phục vụ một kết nối mỗi lúc. Muốn nhiều kết nối đồng thời, tạo một task cho mỗi kết nối, hoặc dùng API socket với select.

4.1. Từ echo sang Modbus TCP

Đổi cổng sang 502, bóc tiêu đề MBAP 7 byte, xử lý PDU như ở phần 7, rồi gửi phản hồi với cùng Transaction ID. Khung mẫu đọc 2 thanh ghi: 00 01 00 00 00 06 01 03 00 00 00 02.

4.2. Công cụ kiểm tra trên máy tính

ping <ip> để kiểm tra mạng và độ trễ cơ bản. ncat <ip> 7 để thử echo. Wireshark để xem toàn bộ ARP, DHCP và bắt tay TCP giữa máy tính và STM32. iperf để đo thông lượng TCP, với một iperf server chạy trên STM32 (LwIP có ví dụ sẵn).

5. Lưu ý khi thiết kế

LwIP cần heap và pool bộ đệm gói (PBUF). Cấp ít quá thì rớt gói, nhiều quá thì tốn RAM. Cân đối theo RAM của chip và kích thước gói lớn nhất cần nhận.

Trên dòng có cache như F7 và H7, vùng nhớ DMA của MAC (bộ mô tả và bộ đệm gói) phải nằm ở vùng không cache, hoặc được cấu hình bằng MPU. Nếu không, dữ liệu gói sai một cách khó hiểu, lúc đúng lúc sai.

Theo dõi trạng thái link của PHY qua MDIO để biết khi nào cáp bị rút hoặc cắm lại, cập nhật trạng thái giao diện mạng, và chạy lại DHCP khi link lên.

Task của LwIP (tcpip_thread) và task nhận gói của driver cần ưu tiên đủ cao so với các task khác, nếu không sẽ rớt gói mỗi khi CPU bận.

Thiết bị lên mạng thì phải tính đến bảo mật: xác thực, mã hoá (TLS), cập nhật firmware an toàn. Đóng các cổng không dùng, và không đưa Modbus TCP thẳng ra Internet.

6. Lỗi thường gặp

Triệu chứng Nguyên nhân thường gặp Cách xử lý
Không ping được Sai chân RMII, sai địa chỉ PHY, thiếu REF_CLK 50 MHz, không có link (cáp, biến áp, đèn LED link) Kiểm tra từng bước: đèn link, REF_CLK bằng oscilloscope, đọc thanh ghi PHY qua MDIO
Ping được nhưng rớt gói, chậm Thiếu bộ nhớ PBUF, ưu tiên task thấp, vấn đề cache DMA Tăng pool, tăng ưu tiên task, đặt bộ đệm DMA vào vùng không cache
Không nhận được IP từ DHCP Link chưa lên khi DHCP chạy, router chặn Chờ link lên rồi mới bật DHCP, thử IP tĩnh để loại trừ
Trùng địa chỉ MAC hoặc IP Nhiều thiết bị dùng cùng giá trị mặc định Đặt MAC duy nhất cho từng thiết bị
Treo sau một thời gian Tràn heap hoặc stack của task LwIP Tăng kích thước stack, kiểm tra cấu hình FreeRTOS, dùng thống kê heap
Kết nối được nhưng ngắt ngay Đóng kết nối sớm, thiếu netconn_close đúng chỗ, không gọi netbuf_delete Kiểm tra vòng nhận và giải phóng bộ đệm
Chạy tốt trên bàn, lỗi khi cắm qua switch Tự động thương lượng (autoneg) lỗi, cáp kém Kiểm tra link speed/duplex, đổi cáp

7. Hỏi đáp

Ping được mà TCP không kết nối là sao? Ping dùng ICMP, chỉ cần tầng IP chạy. TCP cần thêm bộ đệm đủ lớn, task LwIP chạy đúng, và tường lửa trên máy tính không chặn. Mở Wireshark xem bắt tay TCP có hoàn tất không.

Nên dùng netconn, socket hay raw API? Raw API nhanh và tiết kiệm nhất nhưng khó viết, vì là kiểu hướng sự kiện, không chặn. Netconn và socket dễ viết hơn (kiểu chặn) nhưng cần RTOS. Mới bắt đầu thì nên dùng netconn hoặc socket.

Cần bao nhiêu RAM? Tuỳ cấu hình. LwIP trên STM32 thường dùng từ vài chục KB đến hơn 100 KB cho heap và pool gói. Chọn chip đủ RAM và theo dõi bằng thống kê heap.

W5500 hay MAC tích hợp? W5500 nối qua SPI và tự chạy ngăn xếp TCP/IP bên trong, nên đỡ RAM và đỡ công viết phần mềm, nhưng tốc độ bị SPI giới hạn (phần 2). MAC tích hợp nhanh và linh hoạt hơn, đổi lại cần PHY và thiết kế phần cứng kỹ hơn.

8. Kế hoạch đo thực tế

Cột "Đo thực tế" để trống cho đến khi chạy trên phần cứng.

Thử nghiệm Cách đo Giá trị tính toán Đo thực tế
Thời gian từ cấp nguồn đến khi nhận IP (DHCP) Đo từ lúc reset đến khi in ra IP Không có số tính (điền sau)
Độ trễ ping ping nhiều lần, ghi trung bình và lớn nhất Phụ thuộc mạng, thường dưới vài ms (điền sau)
Thông lượng TCP iperf giữa máy tính và STM32 ở cả hai chiều Trần lý thuyết 94,9 Mbit/s (điền sau)
Tải CPU khi chạy thông lượng tối đa Chân GPIO trong task idle hoặc thống kê RTOS Không có số tính (điền sau)
Độ trễ vòng echo TCP với gói nhỏ Script đo thời gian gửi nhận Phụ thuộc mạng (điền sau)
Số kết nối đồng thời tối đa Mở nhiều kết nối tới server, ghi lúc lỗi Phụ thuộc bộ nhớ (điền sau)
Phân tích gói Wireshark: ARP, DHCP, TCP handshake Đủ bốn bước DHCP (điền sau)
Hồi phục sau khi rút cắm cáp Rút cáp 10 giây rồi cắm lại, đo thời gian nối lại Phụ thuộc xử lý link (điền sau)

Kết luận

Ping được là mốc đầu tiên cho thấy phần cứng đã ổn. Trên F7 và H7, nhớ xử lý cache cho vùng nhớ DMA. Bộ nhớ của LwIP cần được tính toán theo ứng dụng, đừng để nguyên mặc định.

Đây là phần cuối của series, đi từ một dây UART qua SPI, I²C, CAN, LIN, RS-485, Modbus, 1-Wire, USB đến TCP/IP. Các giao thức này thường được dùng kết hợp. Một ví dụ quen thuộc là gateway Modbus TCP sang RTU: STM32 nhận yêu cầu qua Ethernet rồi chuyển xuống bus RS-485.

Tài liệu tham khảo

  • Tài liệu ST về LwIP trên STM32 (AN3966 cho F4/F2) và Ethernet trong reference manual của dòng chip bạn dùng
  • Tài liệu LwIP (savannah.nongnu.org/projects/lwip)
  • Datasheet PHY (LAN8742A, DP83848...)