- Sebagai langkah pertama untuk membangun TCP/IP stack sendiri di mikrokontroler, penulis menghubungkan STM32F401 Nucleo dengan shield Wiznet W5100 dan mencoba mengirim frame Ethernet
- Tanpa memakai fitur TCP/IP hardware milik W5100, penulis hanya menggunakan mode MAC Raw, sehingga chip hanya menangani pemrosesan level rendah yang dibutuhkan untuk transmisi frame
- Hambatan pertama berawal dari wiring SPI pada Arduino Ethernet shield yang tidak cocok dengan Nucleo; setelah memastikan routing header ICSP, masalah diselesaikan dengan modifikasi wiring pada board
- Setelah itu, melalui respons MISO yang aneh yang tampaknya disebabkan masalah timing chip select dan paket sampah di Wireshark, logic analyzer dan perbandingan dengan implementasi acuan menjadi alat debugging utama
- Penyebab akhirnya adalah bug pada
w5100_write16()yang menulis byte kedua kembali ke alamat yang sama, dan berkat pembuatan alat analisis CSV SPI capture, penulis akhirnya berhasil mengirim paket dengan benar
Titik awal untuk membuat TCP/IP stack sendiri
- Tujuannya adalah memulai seri “Networking from scratch”, yaitu mengimplementasikan TCP/IP stack dari nol di atas mikrokontroler
- Hasil permukaan pada tahap ini adalah pengiriman paket Ethernet pertama, tetapi fokus sebenarnya ada pada proses melacak bug yang muncul di antara hardware dan driver
- Board yang digunakan adalah board pengembangan Nucleo berbasis STM32F401
- ARM Cortex-M4
- Beroperasi hingga 84MHz
- RAM 96KiB
- Dinilai memiliki memori yang cukup untuk menyimpan beberapa paket
Peran Ethernet dan W5100
- Ethernet bukan sekadar port atau format frame, melainkan keluarga teknologi dan standar yang mencakup hardware physical layer, metode sinyal, penanganan collision pada bus, hingga penataan frame
- Karena pemrosesan sinyal Ethernet kompleks, biasanya ASIC khusus menerima data level frame dan menangani pemrosesan sinyal listrik pada kabel
- Proyek ini menggunakan Arduino Ethernet shield yang berisi chip Wiznet W5100
- Board yang digunakan adalah klon murah, sehingga perlu modifikasi agar berfungsi normal
- W5100 adalah chip yang menanamkan hardware TCP/IP stack di dalam ASIC Ethernet
- Menyediakan 4 “socket”
- Dapat dikonfigurasi pada level TCP, UDP, IP, dan “MAC Raw”
- Karena tujuannya mengimplementasikan TCP/IP stack sendiri, fitur TCP/IP milik W5100 tidak digunakan; hanya satu socket yang dipakai dalam mode MAC Raw
- Pengguna menyerahkan Ethernet frame
- W5100 melakukan transmisi sebenarnya
- Preamble dan start-of-frame marker adalah elemen level listrik, sehingga ditangani oleh chip
- CRC 32-bit juga dihitung oleh W5100
Masalah 1: Sinyal SPI tidak sampai ke W5100
- Pertukaran data dengan W5100 dilakukan melalui SPI
- MOSI: output dari mikrokontroler sebagai chip utama
- MISO: output dari W5100
- clock: clock acuan data
- chip select: sinyal yang memberi tahu bahwa komunikasi sedang dilakukan dengan chip subordinat
- Datasheet W5100 mendefinisikan protokol perintah 4 byte di atas SPI
- 1 byte operation
- 2 byte alamat 16-bit big-endian
- 1 byte value
- Operation adalah tulis
0xf0atau baca0x0f- Nilai lain tidak valid dan harus diabaikan
- Setiap kali W5100 melakukan clock out byte, ia mengembalikan nilai yang sudah diketahui melalui MISO, sehingga masalah komunikasi mudah diperiksa
- Pada perintah baca, byte ke-4 menjadi nilai yang dibaca dari alamat yang ditentukan
- Awalnya, meski perintah dikirim melalui MOSI, nilai sampah terlihat di MISO
- Penyebabnya adalah desain Arduino Ethernet shield yang merutekan sinyal SPI bukan ke header standar Arduino, melainkan ke header ICSP 6-pin
- Pada board Arduino resmi, sinyal SPI pada ICSP dan header standar tersambung secara internal, sehingga tidak bermasalah
- Board Nucleo tidak memiliki header ICSP, sehingga sinyal SPI yang dikirim tidak mencapai W5100
- Dengan multimeter, penulis mengukur resistansi antara power rail dan sinyal SPI untuk memastikan masalah koneksi
- Jika resistansinya tak terhingga, itu berarti kabel/jalur terputus
- Dengan solder dan kawat tembaga berenamel, sinyal antara Nucleo dan W5100 disambungkan secara langsung
Masalah 2: Timing chip select dan respons MISO yang aneh
- Setelah sinyal SPI benar-benar tersambung, komunikasi dengan W5100 menjadi mungkin, dan tahap berikutnya diimplementasikan
- Konfigurasi W5100 untuk raw Ethernet transmission
- Konfigurasi MAC address
- Konfigurasi segmen memori TX/RX
- Menulis Ethernet frame uji ke memori TX dan memicu transmisi
- Nucleo dan shield dihubungkan ke laptop dengan kabel CAT5 lalu Wireshark dijalankan, tetapi tidak ada paket yang terlihat
- Masalah low-level tidak memiliki pesan error atau stack trace, dan sulit dilacak karena bentuknya seperti “elektron sudah digoyang-goyangkan, tetapi hal yang diharapkan tidak terjadi”
- Untuk debugging, digunakan logic analyser
- Mengambil sampel transisi high/low sinyal digital pada beberapa channel
- Software menafsirkan kumpulan sinyal seperti SPI bus dan menampilkan byte transaksi
- Dapat diekspor ke format terstruktur seperti CSV
- Perangkat yang digunakan adalah Saleae Logic 8
- Analyser murah di kisaran €10 juga bisa diintegrasikan dengan Saleae Logic2, tetapi ada tradeoff dalam operasi yang benar dan konsisten
- Perintah SPI pertama tampak normal
0xf0 0x00 0x00 0x80- Mengatur bit paling tinggi pada Mode Register di alamat
0x0000untuk memicu software reset - MISO memberikan respons normal dari
0x00sampai0x03
- Pada perintah baca berikutnya, muncul respons yang aneh
- MOSI:
0x0f 0x00 0x00 0x00 - MISO:
0x03 0xff 0xff 0xff
- MOSI:
- Karena
0x03adalah nilai terakhir dari transaction sebelumnya, dicurigai ada masalah state internal W5100 atau timing - SPI clock digunakan jauh di bawah batas maksimum datasheet sekitar 14MHz, sehingga tidak dianggap sebagai penyebab
- Sebaliknya, penulis menduga chip select berubah ke high terlalu cepat sehingga chip masuk ke state buruk, lalu menambahkan delay beberapa mikrodetik sebelum perubahan state
- Setelah itu respons MISO menjadi normal
- Membaca ulang nilai konfigurasi juga menghasilkan nilai yang masuk akal
- Penyebab pasti masalah ini belum sepenuhnya dipahami
- Batasan terkait chip select pada SPI timing diagram halaman 66 datasheet sebenarnya sudah dipenuhi
- Kondisi batas yang tepat bisa diselidiki lagi, tetapi untuk saat ini kemajuan proyek diprioritaskan
Masalah 3: Paket sampah yang muncul di Wireshark
- Saat Wireshark dijalankan lagi, paket memang muncul, tetapi bukan paket yang dimaksud
- Pada saat mikrokontroler mengirim perintah transmit, raw Ethernet packet yang jauh lebih besar daripada yang dimasukkan semula muncul dalam keadaan terisi data sampah
- Rencana untuk mengimplementasikan hanya berdasarkan datasheet dan spesifikasi dibatalkan, dan pada tahap ini diputuskan untuk membandingkannya dengan implementasi yang sudah terbukti berjalan
- Arduino berguna untuk membuat implementasi acuan dengan cepat
- Dengan Arduino, library, dan sekitar 5 baris kode saja, perilaku kompleks dapat diverifikasi
- Mencari library pengiriman raw Ethernet packet memakan waktu lebih lama
- Kebanyakan pengguna Arduino tidak berusaha mengimplementasikan fungsi jaringan dari nol
- Library resmi telah menghapus dukungan pengiriman raw packet dari public API
- Ditemukan proyek GitHub W5100MacRaw yang melakukan pengiriman dan penerimaan packet pada tahap minimal
- Source tersebut dibandingkan dengan implementasi sendiri, tetapi perbedaan yang langsung terlihat tidak menentukan
- Urutan register read/write berbeda
- Ada read/write yang hanya terdapat di salah satu sisi
- Biasanya, jika ada ketergantungan urutan, hal itu akan disebutkan dalam datasheet
- Meski urutan read/write disamakan dengan implementasi acuan, Wireshark masih menampilkan paket sampah
Bug sebenarnya yang terungkap oleh alat kecil
- Strategi berikutnya adalah menulis alat yang mem-parsing SPI capture CSV dari Saleae Logic2 dan menampilkannya kembali sebagai daftar register read/write
- Alat dibuat dengan Python, dan total panjangnya sedikit lebih dari 200 baris
- Sebagian besar adalah nama dan alamat register yang disalin dari datasheet
- Pembuatannya memakan waktu sekitar 1 jam
- Argument parsing juga ditambahkan agar input dan output jelas
- SPI capture diambil dari implementasi acuan Arduino dan implementasi sendiri, diekspor sebagai CSV, lalu diproses dengan alat tersebut dan dibandingkan menggunakan diff
- Masalahnya ada pada fungsi helper
w5100_write16(u16 address, u16 value)- Banyak register W5100 adalah nilai 16-bit
- Format perintah hanya bisa menulis 8-bit sekali waktu, sehingga harus dipecah menjadi high/low byte
- Fungsi ini menulis byte kedua kembali ke alamat yang sama, bukan ke
address + 1
- Log acuan Arduino menulis masing-masing ke
S0_TX_WR0danS0_TX_WR1S0_TX_WR0 [0x0424] 0x00S0_TX_WR1 [0x0425] 0x3c
- Log milik penulis menulis keduanya ke
S0_TX_WR0 [0x0424]S0_TX_WR0 [0x0424] 0x00S0_TX_WR0 [0x0424] 0x3c
- Register ini adalah Socket 0 transmit write pointer
- W5100 mengharapkan alur membaca register 16-bit ini secara berurutan
- Menulis byte ke memori TX
- Menulis kembali write pointer baru
- Lalu mengirim socket send command
- Karena send dijalankan tanpa menulis byte kedua, chip masuk ke state aneh yang tidak terdefinisi, dan di Wireshark tampak seperti paket dengan hasil acak
- Setelah fungsi diperbaiki, paket uji tampil dengan benar di Wireshark
Nilai waktu untuk membuat alat debugging
- Pengiriman paket Ethernet pertama itu sendiri bukan pencapaian raksasa, tetapi dalam proyek ini merupakan momen yang bisa diterima sebagai keberhasilan yang jelas
- Menelusuri kembali bug terasa menyenangkan dalam proyek pribadi, dan menghabiskan waktu untuk membuat alat serta menjelajahi ruang debugging hampir selalu bernilai
- Di sebagian lingkungan profesional, ada kecenderungan menganggap pekerjaan yang tidak secara langsung menghasilkan deliverable sebagai pemborosan
- Ketika proses pengembangan dipecah-pecah ala JIRA, pekerjaan yang tidak langsung mengisi checkbox cenderung dinilai rendah
- Saat sistem belum cukup dipahami, membuat alat bisa sama pentingnya dengan menulis test, bahkan kadang lebih penting
- Debugging hampir mirip dengan implementasi metode ilmiah
- Mengumpulkan data
- Membuat prediksi
- Memverifikasi prediksi dengan eksperimen
- Memperbarui prediksi berdasarkan data baru
- Setelah ini, proyek bergerak ke masalah pada tingkat abstraksi yang lebih tinggi
- Salah paham terhadap RFC
- Menulis multitasking code
- Berlanjut dengan bug-bug baru
1 komentar
Komentar Hacker News
Kemampuan membuat tool kecil sendiri adalah sebuah superpower, dan sering kali menjadi inti dari apa yang biasa disebut programmer 10x.
Sayangnya, keterampilan seperti ini biasanya bekerja diam-diam di tempat yang tidak terlihat.
Namun bagi orang yang punya rasa ingin tahu dan memikirkan efek tingkat kedua atau fitur yang sebentar lagi akan dibutuhkan, bekerja dengan developer yang tidak melakukan itu bisa cukup menyakitkan.
Seseorang yang baru-baru ini bekerja denganku bukan orang yang buruk, tetapi aku sedang membereskan proyek yang ia implementasikan 100% persis seperti perintah tiket. Proyek baru yang dimaksudkan untuk menggantikan masalah pada software lama ternyata tetap mempertahankan semua masalah lama karena alasan yang sama.
Meski begitu, istilah developer 10x agak terasa seperti buzzword. Istilah itu mengingatkanku pada developer nekat yang “menyelesaikan pekerjaan” tanpa pengawasan tetapi meninggalkan biaya besar, lalu semuanya menjadi tersilo sehingga nanti, ketika orang lain menyentuhnya atau orang itu pergi, semuanya runtuh seperti rumah kartu.
Idealnya semua orang diberi waktu eksplorasi, tetapi ketika manajemen atas berkata “ini harus selesai lebih cepat”, dibutuhkan manajer yang akan mempertahankan hal itu dengan tegas. Situasinya makin sulit jika manajer tim lain berkata, “Apakah tim itu boleh merekrut orang tambahan untuk menebus waktu yang hilang? Kami juga membutuhkan tenaga itu.”
Pada akhirnya dibutuhkan sistem di tingkat organisasi, dan bahkan Google pun meninggalkan 20% time.
Solusi yang “tidak etis” adalah memasukkan sedikit waktu semacam ini ke dalam estimasi pengembangan.
Membuat tool sendiri memang berguna dalam situasi yang tepat, tetapi menurutku developer yang produktif justru menulis kode sesedikit mungkin dan memanfaatkan yang sudah ada. Dalam contoh artikel ini, tidak ada yang perlu membuat TCP stack sendiri dari nol karena sudah ada implementasi yang sangat baik.
Namun membuatnya sendiri bisa jadi cara terbaik untuk memperoleh pemahaman mendalam, dan pemahaman mendalam adalah salah satu unsur dari developer 10x yang nyaris mitologis itu. Hanya saja, aku tidak akan berharap pemberi kerja mau membayar proses tersebut.
Biasanya kalau bisa selesai dalam sehari, aku lakukan saja. Belum pernah salah dan belum pernah terasa sia-sia. Dalam skenario terburuk, nanti kode itu tinggal kusalin-tempel ke tempat lain.
Lalu ia mulai meminta agar tool itu bisa dipakai juga oleh non-programmer, dan tiba-tiba tool-tool tersebut menjadi pekerjaan yang jauh lebih besar dari perkiraan.
Judulnya cukup ambigu; tulisan ini adalah awal dari seri tentang membuat stack TCP/IP dan Ethernet framing untuk mikrokontroler dari nol.
Penulis memakai chip W5100 yang bisa memproses TCP/IP sendiri, tetapi chip itu juga mendukung cara menyerahkan frame Ethernet yang sudah dibuat sebelumnya. Namun preamble dan perhitungan CRC ditangani oleh chip.
Sebagian besar tulisan membahas proses berkomunikasi dengan chip itu sendiri dan mengirim paket uji. Sepertinya paketnya di-hardcode, tetapi tidak disebutkan secara eksplisit dalam tulisan.
Secara pribadi, aku berharap isinya tentang bit banging Ethernet di hardware yang benar-benar tidak masuk akal.
Salah satu triknya adalah memodifikasi board PHY + MagJack umum agar RP2040 membuat sinyal clock. Dengan begitu sinyalnya tersinkronisasi dan tidak perlu melakukan oversampling RMII.
Kalau hanya butuh 10Mb/s, cara yang lebih kotor pun bisa. Aku masih menunggu terminal kaca “modern” yang menggabungkan output video DVI/HDMI dan Ethernet pada RP2040. Telnet saja sudah cukup; SSH mungkin terlalu berat.
Baru-baru ini aku mengambil perpindahan karier yang agak tidak biasa ke rekayasa FPGA yang berpusat pada Ethernet.
Perjalanannya menarik, dan pada akhirnya aku sampai merancang Hard MAC IP sendiri dan mengirim paket melalui custom PHY IP.
Sangat kurekomendasikan bagi siapa pun yang ingin mencoba tantangan ini dalam “hard mode”. Networking terlalu banyak diabstraksikan dari pengguna, jadi sangat berharga memahami bagaimana kartu Ethernet, modem, dan switch menyusun serta membongkar tiap bagian paket, dan bagaimana PHY/PCS memulihkan sinyal di link.
Untuk debugging dan verifikasi, kami membuat versi simulasi dengan Verilator yang bisa dihubungkan ke perangkat TUN/TAP Linux, sehingga bisa diakses langsung dari mesin developer tanpa menguasai hardware fisik. Ini sangat berguna karena hardware-nya hanya ada satu.
Pengetahuanku tentang networking hanya sampai pemahaman yang sangat dasar tentang model OSI, tetapi aku ingin menggali dunia networking lebih dalam.
Ini pertama kalinya aku melihat reinterpretasi MOSI/MISO. Alih-alih master out/slave in, disebut main out/subordinate in.
Dengan begitu nama pin tetap bisa disebut MOSI/MISO, jadi sepertinya aku akan memakainya juga. Alternatif COPI/CIPO (controller out/peripheral in) tidak terlalu melekat di kepala.
Saya agak heran mengapa penulis memakai shield Ethernet W5100 dan STM32F401. Dengan tingkat kemudahan yang mirip, kalau memakai board STM32F407, sudah ada Ethernet MAC bawaan, dan bisa dikembangkan bersama board Ethernet PHY yang murah
Ada banyak contoh proyek Ethernet, dan pengembangannya semudah STM32F401
Selain itu, menurut saya penjelasan bahwa “karena kompleksitas pemrosesan sinyal Ethernet, biasanya digunakan ASIC khusus” umumnya kurang tepat dalam konteks mikrokontroler. Dalam banyak kasus, fungsi Ethernet hadir sebagai periferal bawaan di dalam mikrokontroler, seperti STM32F407 atau ESP32
Memakai chip yang punya periferal Ethernet bawaan jelas lebih masuk akal. Hanya saja, itu juga berarti menukar kompleksitas konfigurasi W5100 dengan kompleksitas konfigurasi periferal ST
Kode networking-nya sudah mengabstraksikan chip fisik di balik antarmuka driver (semacam read/write/ioctl), jadi proses porting seharusnya cukup sederhana
Dalam seri ini saya akan mencoba meninjau STM32F407
Ethernet menangani frame, bukan paket
Paket adalah konsep IP
Namun RFC 791 adalah dokumen dari masa ketika jaringan L2 di bawahnya kemungkinan besar adalah ARPAnet yang memakai paket 128 byte
Saat ini pembedaan ini seharusnya praktis tidak berarti. Kalau Anda mengandalkan fragmentasi IP untuk mengirim datagram IP besar sebagai beberapa paket L2, berarti ada sesuatu yang salah. IPv6 bahkan tidak mendukung fragmentasi di dalam jaringan
Dalam praktik, seiring waktu perbedaannya hampir menghilang. Biasanya satu frame Ethernet memuat satu datagram IP, dan semua orang menyebut semuanya paket saja
https://en.wikipedia.org/wiki/Ethernet_frame
Jika ingin mencoba Ethernet kabel di mikrokontroler, beberapa board STM32 Nucleo yang lebih besar punya Ethernet 100Mbps bawaan dan cukup murah, sekitar 25 dolar
Penilaian terhadap perangkat lunak STM32Cube memang beragam, tetapi ia bisa membuatkan contoh komunikasi Ethernet yang berfungsi
https://www.st.com/en/evaluation-tools/nucleo-f439zi.html
MQTT, klien dan server HTTP, dan lainnya tersedia bersama paketnya
Terakhir kali saya memakai Cube-MX, pengalaman keseluruhannya sangat tidak menyenangkan. Kalau memakai STM32 lagi, saya mungkin akan lebih memilih stm32-hal atau libopencm3. Tool itu sendiri dan kode yang dihasilkannya punya berbagai bug buruk dan edge case, sampai saya menghabiskan beberapa hari untuk debugging. Mungkin sekarang sudah membaik
https://github.com/egnor/wt32-eth01
https://liliputing.com/waveshare-esp32-p4-nano-is-a-tiny-ris...
Saya baru saja melihat bahwa ada juga modul yang lebih murah. Yang seperti WT32-ETH01 mungkin kinerjanya lebih rendah
Jika ingin menulis sendiri network stack di Linux, Anda bisa memakai
socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))dan bekerja pada tingkat abstraksi yang tidak jauh berbeda dari tulisan iniPaket
SOCK_RAWdikirim ke dan dari driver perangkat tanpa mengubah data paket. Saat menerima, alamat diparse dan diteruskan sebagai struktur alamat standarsockaddr_llSaat mengirim, buffer yang diberikan pengguna harus berisi header lapisan fisik, dan paket tersebut akan masuk tanpa perubahan ke antrean driver jaringan pada interface yang ditentukan oleh alamat tujuan
https://man7.org/linux/man-pages/man7/packet.7.html
Ini menambahkan interface virtual ke network stack, seperti interface VPN. Sebaliknya, packet socket memungkinkan Anda berkomunikasi langsung dengan interface fisik
Tepatnya, sepertinya harus disebut frame Ethernet
Bagus sekali. Saya baru saja menghabiskan 16 jam kerja terakhir untuk pekerjaan kebalikannya: implementasi yang memparse Ethernet II (termasuk VLAN), IPv4+6, dan UDP sampai ke protokol IP otomotif
Tujuannya adalah memahami stream capture bus proprietari, dan stream itu membawa frame Ethernet yang bersarang di dalam frame Ethernet, dan seterusnya; saya menerimanya lewat raw socket
Untuk pekerjaan seperti ini, Wireshark dan ChatGPT benar-benar sangat berharga