
เรียนรู้ Linux Device Tree ตั้งแต่พื้นฐานถึง Overlays ขั้นสูง ครอบคลุมไวยากรณ์ DTS โครงสร้างโหนด Bindings และการกำหนดค่าบอร์ดฮาร์ดแวร์ที่ออกแบบเอง
Device Tree (DT) คือโครงสร้างข้อมูลและภาษาที่ใช้ในระบบ Linux แบบฝังตัว เพื่ออธิบายโครงสร้างฮาร์ดแวร์ของระบบให้กับเคอร์เนลในระหว่างการบูต โดยแทนที่วิธีการเดิมที่เขียนข้อมูลเฉพาะบอร์ดลงในซอร์สโค้ดของเคอร์เนลโดยตรง Device Tree เขียนในรูปแบบ Device Tree Source (DTS) และคอมไพล์เป็นรูปแบบไบนารี Device Tree Blob (DTB) โดย Device Tree Compiler (dtc) โดยจะอธิบายถึงโปรเซสเซอร์ หน่วยความจำ บัส อุปกรณ์ต่อพ่วง ตัวควบคุมการขัดจังหวะ นาฬิกา GPIO และการเชื่อมต่อระหว่างกันในโครงสร้างแบบลำดับชั้น บูตโหลดเดอร์ (เช่น U-Boot) จะส่ง DTB ไปยังเคอร์เนล Linux ในระหว่างการบูต ทำให้ไบนารีเคอร์เนลเดียวสามารถรองรับแพลตฟอร์มฮาร์ดแวร์หลายแพลตฟอร์มได้ง่ายๆ เพียงแค่เปลี่ยนไฟล์ DTB Device Tree เป็นสิ่งจำเป็นสำหรับแพลตฟอร์ม Linux ARM และ ARM64 และยังใช้โดย Zephyr RTOS ด้วย การเข้าใจโครงสร้าง Device Tree นั้นจำเป็นอย่างยิ่งสำหรับวิศวกร Linux ฝังตัวทุกคนที่พัฒนาแพ็คเกจสนับสนุนบอร์ดแบบกำหนดเอง (BSP) เพิ่มการสนับสนุนอุปกรณ์ต่อพ่วง หรือกำหนดค่าการมัลติเพล็กซ์พินและโดเมนพลังงานบนแพลตฟอร์ม System-on-Chip (SoC)
/* Basic Device Tree Source (DTS) structure */
/dts-v1/;
/ { /* Root node */
model = "Custom IoT Board";
compatible = "mycompany,iot-board", "ti,am335x";
#address-cells = <1>;
#size-cells = <1>;
chosen {
bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2";
stdout-path = &uart0;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512MB DDR */
};
/* I2C bus with sensor */
&i2c1 {
status = "okay";
clock-frequency = <400000>;
temperature-sensor@48 {
compatible = "ti,tmp102";
reg = <0x48>;
interrupt-parent = <&gpio1>;
interrupts = <15 IRQ_TYPE_EDGE_FALLING>;
};
};
/* SPI bus with flash */
&spi0 {
status = "okay";
flash@0 {
compatible = "jedec,spi-nor";
reg = <0>;
spi-max-frequency = <40000000>;
#address-cells = <1>;
#size-cells = <1>;
partition@0 {
label = "bootloader";
reg = <0x0 0x80000>;
};
};
};
};การกำหนดค่า Device Tree Bindings กำหนดกฎสำหรับการอธิบายฮาร์ดแวร์ประเภทใดประเภทหนึ่งใน Device Tree แต่ละ Binding จะระบุคุณสมบัติที่จำเป็นและคุณสมบัติเสริมสำหรับโหนดอุปกรณ์ รวมถึงสตริง "compatible" ที่จับคู่อุปกรณ์กับไดรเวอร์เคอร์เนล Bindings มีเอกสารอยู่ในเคอร์เนล Linux ภายใต้ Documentation/devicetree/bindings/ ในรูปแบบ YAML (dt-schema) เมื่อคุณเขียนโหนด Device Tree สำหรับเซ็นเซอร์ ตัวควบคุม LED หรืออุปกรณ์ต่อพ่วงใดๆ คุณต้องปฏิบัติตามข้อกำหนด Binding สำหรับอุปกรณ์นั้น คุณสมบัติ "compatible" มีความสำคัญที่สุด เพราะจะบอกเคอร์เนลว่าควรโหลดไดรเวอร์ใด สตริง compatible หลายรายการช่วยให้สามารถใช้ตัวเลือกสำรองได้: compatible = "myvendor,sensor-v2", "generic-sensor" หมายความว่าเคอร์เนลจะค้นหาไดรเวอร์ที่ตรงกับ "myvendor,sensor-v2" ก่อน และจะใช้ "generic-sensor" เป็นตัวเลือกสำรองหากไม่พบ
Device Tree Overlays (DTO) ช่วยให้สามารถแก้ไขโครงสร้างพื้นฐานของอุปกรณ์ (Device Tree) ได้ในขณะทำงานโดยไม่ต้องคอมไพล์ใหม่ Overlays จะถูกคอมไพล์เป็นไฟล์ DTBO และนำไปใช้โดยบูตโหลดเดอร์หรือในขณะทำงาน (บนแพลตฟอร์มที่รองรับ ConfigFS) กรณีการใช้งานทั่วไป ได้แก่ การเปิดหรือปิดใช้งานอุปกรณ์ต่อพ่วง การกำหนดค่าบอร์ดขยาย (เช่น Raspberry Pi HAT หรือ BeagleBone capes) และการเปลี่ยนแปลงการมัลติเพล็กซ์พินสำหรับฮาร์ดแวร์รูปแบบต่างๆ ในการใช้งานจริง Overlays ช่วยให้ DTB พื้นฐานเพียงไฟล์เดียวรองรับฮาร์ดแวร์หลายรุ่น ลดจำนวนอิมเมจเฟิร์มแวร์ที่ต้องดูแลและทดสอบ
/* Device Tree Overlay example - enable SPI display */
/dts-v1/;
/plugin/;
&spi1 {
status = "okay";
#address-cells = <1>;
#size-cells = <0>;
display@0 {
compatible = "sitronix,st7789v";
reg = <0>;
spi-max-frequency = <32000000>;
dc-gpios = <&gpio0 25 GPIO_ACTIVE_HIGH>;
reset-gpios = <&gpio0 24 GPIO_ACTIVE_HIGH>;
width = <240>;
height = <320>;
rotation = <90>;
};
};เครื่องมือและเทคนิคที่จำเป็นสำหรับการแก้ไขปัญหาเกี่ยวกับโครงสร้างข้อมูลอุปกรณ์:
การสร้าง Device Tree (DTS) สำหรับบอร์ดแบบกำหนดเองเริ่มต้นด้วยการระบุ SoC และรวมไฟล์ DTSI พื้นฐาน ซึ่งกำหนดคอร์ของโปรเซสเซอร์ บัสภายใน และอุปกรณ์ต่อพ่วงบนชิป จากนั้น DTS ของบอร์ดของคุณจะเปิดใช้งานอุปกรณ์ต่อพ่วงเฉพาะ กำหนดการมัลติเพล็กซ์พินผ่านระบบย่อย pinctrl อธิบายอุปกรณ์ภายนอกที่เชื่อมต่อผ่านบัส I2C, SPI และ UART กำหนดค่าตัวควบคุมพลังงานและแหล่งสัญญาณนาฬิกา และตั้งค่าแผนที่หน่วยความจำ ขั้นตอนการทำงานโดยทั่วไปคือ: ศึกษาคู่มืออ้างอิง SoC ตรวจสอบไฟล์ DTS ของบอร์ดที่คล้ายกันใน Kernel Tree สร้าง DTS ของบอร์ดของคุณด้วยการรวมที่เหมาะสม คอมไพล์ด้วย dtc และทดสอบและปรับปรุงอย่างต่อเนื่อง ให้ความสนใจเป็นพิเศษกับความขัดแย้งในการมัลติเพล็กซ์พิน พินทางกายภาพเดียวกันอาจสามารถกำหนดเส้นทางไปยังฟังก์ชันอุปกรณ์ต่อพ่วงหลายฟังก์ชันได้ และการกำหนดค่าที่ไม่ถูกต้องจะทำให้เกิดความล้มเหลวโดยไม่แสดงข้อผิดพลาด
ประเด็นสำคัญ: Device Tree คือโครงสร้างข้อมูลแบบลำดับชั้นที่อธิบายโครงสร้างฮาร์ดแวร์ให้กับเคอร์เนล Linux ในระหว่างการบูต โดยแทนที่โค้ดเฉพาะบอร์ดที่เขียนแบบตายตัว Device Tree เขียนในรูปแบบ DTS และคอมไพล์เป็นไบนารี DTB ทำให้ไบนารีเคอร์เนลเดียวสามารถรองรับแพลตฟอร์มฮาร์ดแวร์ได้หลายแพลตฟอร์ม Overlay ช่วยให้สามารถเปลี่ยนแปลงการกำหนดค่าฮาร์ดแวร์ในขณะทำงานได้โดยไม่ต้องคอมไพล์ใหม่
ที่ EmbedCrest เราได้พัฒนาแพ็คเกจสนับสนุนบอร์ด (Board Support Package) แบบครบวงจรสำหรับบอร์ดเกตเวย์อุตสาหกรรมแบบกำหนดเอง โดยใช้ชิป NXP i.MX8M Mini SoC บอร์ดนี้ประกอบด้วยพอร์ตอีเธอร์เน็ต 3 พอร์ต (2x RGMII ผ่าน AR8035 PHYs + 1x USB-Ethernet ผ่าน LAN9514), ตัวรับส่งสัญญาณ RS-485 บน UART3, โมเด็ม Quectel EC25 LTE บน USB, ตัวรักษาความปลอดภัย ATECC608B บน I2C2 และ FPGA แบบกำหนดเอง (Lattice MachXO3) บน SPI1 สำหรับการประมวลผลโปรโตคอลอุตสาหกรรม การสร้าง Device Tree ต้องใช้เวลาพัฒนาแบบวนซ้ำ 3 สัปดาห์ เราเริ่มต้นจาก NXP EVK DTS (imx8mm-evk.dts) สร้าง DTS เฉพาะบอร์ดของเราด้วยการกำหนดค่า pinmux ที่ถูกต้องโดยใช้ i.MX Pins Tool อธิบายอุปกรณ์ต่อพ่วงแต่ละตัวด้วยสตริงที่เข้ากันได้และที่อยู่รีจิสเตอร์ที่เหมาะสม และตรวจสอบแต่ละโหนดทีละตัวโดยใช้การตรวจสอบ dtc และการทดสอบการบูต ส่วนที่ท้าทายที่สุดคือการกำหนดค่าการหน่วงเวลา RGMII สำหรับ PHY AR8035 อย่างถูกต้อง ซึ่งต้องปรับแต่งคุณสมบัติ rgmii-rxc-dly-en และ rgmii-txc-dly-en ผ่านการทดสอบแบบวนซ้ำด้วยการวัดปริมาณงาน iperf3 ที่การตั้งค่าการหน่วงเวลาสัญญาณนาฬิกาที่แตกต่างกัน
ข้อผิดพลาดที่พบบ่อยที่สุดในไฟล์ Device Tree คือความขัดแย้งในการกำหนดพินมัลติเพล็กซ์: การกำหนดพินทางกายภาพเดียวกันให้กับอุปกรณ์ต่อพ่วงสองตัวที่แตกต่างกัน SoC จะเลือกใช้อุปกรณ์ต่อพ่วงที่กำหนดค่าล่าสุดโดยไม่แจ้งให้ทราบล่วงหน้า ทำให้อุปกรณ์ต่อพ่วงตัวแรกทำงานล้มเหลวโดยไม่มีข้อความแสดงข้อผิดพลาดที่ชัดเจน ใช้เครื่องมือวางแผนพินของผู้ผลิต SoC (NXP Pins Tool, TI SysConfig, STM32CubeMX) เพื่อตรวจจับความขัดแย้งก่อนที่จะเขียนไฟล์ DTS ประการที่สอง คุณสมบัติ interrupt-parent และ interrupts ที่ไม่ถูกต้องทำให้การตรวจสอบอุปกรณ์สำเร็จ แต่ไม่สร้างการขัดจังหวะ ตรวจสอบการกำหนดค่าการขัดจังหวะด้วยคำสั่ง cat /proc/interrupts และทดสอบด้วยการเรียกใช้การขัดจังหวะด้วยตนเอง ประการที่สาม ข้อผิดพลาดในการกำหนดค่าสัญญาณนาฬิกาจะแสดงออกมาในรูปของอุปกรณ์ต่อพ่วงที่ทำงานที่ความถี่ไม่ถูกต้องหรือไม่ทำงานเลย ตรวจสอบ assigned-clocks และ assigned-clock-rates กับไฟล์ clock tree ในคู่มืออ้างอิงของ SoC ประการที่สี่ การลืมตั้งค่า status = "okay" ทำให้อุปกรณ์ต่อพ่วงที่อธิบายไว้อย่างถูกต้องถูกปิดใช้งาน ตามธรรมเนียมแล้ว ไฟล์ DTSI ของ SoC จะอธิบายอุปกรณ์ต่อพ่วงทั้งหมดด้วย status = "disabled" และไฟล์ DTS ของบอร์ดจะเปิดใช้งานอุปกรณ์ต่อพ่วงที่ใช้งานอยู่บางส่วน
การใช้ Device Tree Overlay ช่วยให้ภาพเฟิร์มแวร์พื้นฐานเพียงภาพเดียวสามารถรองรับฮาร์ดแวร์หลายรุ่นได้ ลดความซับซ้อนในการผลิตและการบำรุงรักษา ในสถานการณ์การผลิต ลองพิจารณาผลิตภัณฑ์ตระกูลหนึ่งที่มีสามรุ่น ได้แก่ รุ่นพื้นฐานที่มีเฉพาะ RS-485 รุ่นระดับกลางที่เพิ่ม BLE ผ่านโมดูลที่เชื่อมต่อผ่าน UART และรุ่นพรีเมียมที่เพิ่ม LTE ผ่านโมเด็ม USB สร้าง DTS พื้นฐานโดยปิดใช้งานอุปกรณ์ต่อพ่วงเฉพาะรุ่นทั้งหมด จากนั้นสร้างไฟล์ DTBO โอเวอร์เลย์สามไฟล์ที่เปิดใช้งานอุปกรณ์ต่อพ่วงที่เหมาะสมสำหรับแต่ละรุ่น ในระหว่างการผลิต ให้เขียนตัวระบุรุ่นลงใน EEPROM หรือฟิวส์ OTP และกำหนดค่า U-Boot ให้เลือกโอเวอร์เลย์ที่เกี่ยวข้องในเวลาบูตโดยใช้คำสั่ง fdt apply วิธีการนี้ช่วยลดจำนวนภาพเฟิร์มแวร์จาก 3 เหลือเพียง 1 ภาพพื้นฐาน + 3 โอเวอร์เลย์ขนาดเล็ก ทำให้ไปป์ไลน์ CI/CD ระบบอัปเดต OTA และการทดสอบการผลิตง่ายขึ้น Zephyr RTOS ใช้กลไกโอเวอร์เลย์เดียวกัน (ไฟล์ app.overlay) ดังนั้นวิธีการนี้จึงใช้ได้กับโครงการ MCU ที่กำหนดเป้าหมายบอร์ดที่มีฮาร์ดแวร์หลายรุ่นเช่นกัน