นำ Rust Geodesy ดำดิ่งสู่ไมโครคอนโทรลเลอร์ ESP32

Rust Geodesy คือไลบรารีสำหรับงานยีออเดซี

ตอนที่แล้วผมคอมไพล์โค้ดของ Rust ที่ใช้ไลบรารี Rust Geodesy หรือเรียกสั้นๆว่า RG ถึงแม้ผู้พัฒนา RG จะย้ำว่าไม่ใช่มาแทนไลบรารี PROJ ก็ตาม แต่ผมก็คิดว่าถ้าใช้ภาษา Rust การนำ RG มาใช้แทน PROJ ถือว่าเป็นตัวเลือกที่ดีที่สุด การไปใช้ PROJ ใน Rust มีความยุ่งยากที่จะต้องคอมไพล์ซอร์สโค้ดภาษาซี/ซีพลัสพลัสมาใช้งาน

ผมได้มีโอกาสใช้ไมโครคอนโทรลเลอร์หลายตัวเพื่อสร้างอุปกรณ์ให้ทำงานบางอย่างเช่นบทความที่ผมเคยเขียนถึงอุปกรณ์ส่งระดับน้ำจากสถานีวัดระดับน้ำขึ้นคลาวด์ฟรีโดยใช้ไมโครคอนโทรลเลอร์ ESP32 และโดยเขียนโค้ดด้วยภาษาซี

ทำไมต้องใช้ Rust บนไมโครคอนโทรลเลอร์

ผมตั้งโจทย์ว่าถ้ามีการนำภาษา Rust ไปใช้กับ ESP32 แทนที่จะเป็นภาษาซีหรือไมโครไพทอน ในโครงงานที่ต้องต่อกับอุปกรณ์ RTK GNSS อาจจะมีความต้องการในการแปลงค่าพิกัดกริดยูทีเอ็ม (UTM) หรือทีเอ็ม (TM) ไปยังค่าพิกัดภูมิศาสตร์ (Geographic coordinates) การใช้ Rust แทนภาษาซีก็ด้วยเหตุผลเรื่องลดหน่วยความจำที่ผิดพลาด ส่วนการนำ Rust ไปแทนไมโครไพทอนอาจจะด้วยเหตุผลเรื่องความเร็วในการทำงานของไมโครคอนโทรลเลอร์ เป็นอีกทางเลือกหนึ่งครับ

เตรียม Toolchain สำหรับ ESP32

เนื่องจากการคอมไพล์โค้ดภาษา Rust ไปใช้กับ ESP2 ที่มีสถาปัตยกรรมเป็นซีพียู Xtensa จำเป็นต้องมี toolchain เป็นพิเศษ ที่ออกแบบโดย Espressif ผู้ผลิตไมโครคอนโทรลเลอร์ ESP32 นั่นเอง เนื่องจากขั้นตอนการติดตั้งเพื่อใช้บน WSL2 ในวินโดส์ค่อนข้างยาวและมีรายละเอียดมาก ผมจะไม่ขอลงในที่นี้ ติดตามขั้นตอนการติดตั้งได้ที่ลิ๊งค์นี้

เริ่มต้นสร้างโครงการจากเทมเพลตของ ESP32

ใช้ WSL2 ใน home ผมมีไดเรคทอรี ~/esp/rust-esp ผมจะสร้างโครงการใหม่จากที่นี่ ใช้คำสั่ง cargo generate -a https://github.com/esp-rs/esp-idf-template cargo แล้วจะมีพร้อมพ์ถามชื่อโครงการ ใส่ rust-geodesy2 กด Enter ไปสองครั้ง

pbrobo@pbrworkstation:~/esp/rust-esp$ ls -l
total 24
drwxr-xr-x 8 pbrobo pbrobo 4096 Sep 23 11:17 esp32blink
drwxr-xr-x 7 pbrobo pbrobo 4096 Sep 22 21:43 esp32hello
drwxr-xr-x 8 pbrobo pbrobo 4096 Sep 29 20:14 mini-proj
drwxr-xr-x 8 pbrobo pbrobo 4096 Sep 29 20:08 proj941
drwxr-xr-x 8 pbrobo pbrobo 4096 Oct 15 14:30 rust-geodesy
drwxr-xr-x 8 pbrobo pbrobo 4096 Oct  4 07:26 ssd1306
pbrobo@pbrworkstation:~/esp/rust-esp$ cargo generate -a https://github.com/esp-rs/esp-idf-template cargo
⚠️   Favorite `https://github.com/esp-rs/esp-idf-template` not found in config, using it as a git repository: https://github.com/esp-rs/esp-idf-template
🤷   Project Name: rust-geodesy2
🔧   Destination: /home/pbrobo/esp/rust-esp/rust-geodesy2 ...
🔧   project-name: rust-geodesy2 ...
🔧   Generating template ...
✔ 🤷   Which MCU to target? · esp32
✔ 🤷   Configure advanced template options? · false
🔧   Moving generated files into: `/home/pbrobo/esp/rust-esp/rust-geodesy2`...
🔧   Initializing a fresh Git repository
✨   Done! New project created /home/pbrobo/esp/rust-esp/rust-geodesy2
pbrobo@pbrworkstation:~/esp/rust-esp$

จากนั้น cd เข้าไดเรคทอรีของโครงการจากคำสั่ง cd rust-geodesy2 และเพิ่มไลบรารีหรือเครท (crate) geodesy ด้วยคำสั่ง cargo add geodesy

pbrobo@pbrworkstation:~/esp/rust-esp$ cd rust-geodesy2
pbrobo@pbrworkstation:~/esp/rust-esp/rust-geodesy2$ cargo add geodesy
    Updating crates.io index
      Adding geodesy v0.13.0 to dependencies
             Features:
             + anyhow
             + binary
             + clap
             + clap-verbosity-flag
             + dirs
             + env_logger
             + with_plain
             - js
    Updating crates.io index
     Locking 229 packages to latest compatible versions
      Adding bindgen v0.69.5 (latest: v0.70.1)
      Adding bitflags v1.3.2 (latest: v2.6.0)
      Adding cargo_toml v0.15.3 (latest: v0.20.5)
      Adding embedded-hal v0.2.7 (latest: v1.0.0)
      Adding globwalk v0.8.1 (latest: v0.9.1)
      Adding heck v0.4.1 (latest: v0.5.0)
      Adding idna v0.5.0 (latest: v1.0.2)
      Adding itertools v0.12.1 (latest: v0.13.0)
      Adding linux-raw-sys v0.4.14 (latest: v0.6.5)
      Adding nb v0.1.3 (latest: v1.1.0)
      Adding rustc-hash v1.1.0 (latest: v2.0.0)
      Adding strum v0.24.1 (latest: v0.26.3)
      Adding strum v0.25.0 (latest: v0.26.3)
      Adding strum_macros v0.24.3 (latest: v0.26.4)
      Adding strum_macros v0.25.3 (latest: v0.26.4)
      Adding syn v1.0.109 (latest: v2.0.81)
      Adding toml v0.7.8 (latest: v0.8.19)
      Adding toml_edit v0.19.15 (latest: v0.22.22)
      Adding wasi v0.11.0+wasi-snapshot-preview1 (latest: v0.13.3+wasi-0.2.2)
      Adding which v4.4.2 (latest: v6.0.3)
      Adding windows-core v0.52.0 (latest: v0.58.0)
      Adding windows-sys v0.48.0 (latest: v0.59.0)
      Adding windows-sys v0.52.0 (latest: v0.59.0)
      Adding windows-targets v0.48.5 (latest: v0.52.6)
      Adding windows_aarch64_gnullvm v0.48.5 (latest: v0.52.6)
      Adding windows_aarch64_msvc v0.48.5 (latest: v0.52.6)
      Adding windows_i686_gnu v0.48.5 (latest: v0.52.6)
      Adding windows_i686_msvc v0.48.5 (latest: v0.52.6)
      Adding windows_x86_64_gnu v0.48.5 (latest: v0.52.6)
      Adding windows_x86_64_gnullvm v0.48.5 (latest: v0.52.6)
      Adding windows_x86_64_msvc v0.48.5 (latest: v0.52.6)
      Adding winnow v0.5.40 (latest: v0.6.20)
pbrobo@pbrworkstation:~/esp/rust-esp/rust-geodesy2$

ผมใช้ VSCode เรียกคำสั่ง code . เพื่อเรียก IDE

pbrobo@pbrworkstation:~/esp/rust-esp/rust-geodesy2$ code .

จะเห็น VSCode ประมาณนี้ จะมีโค้ดเทมเพลทที่ main() เราจะแก้ไขโค้ดนี้ทีหลัง

แก้ไขไฟล์ Cargo.toml

ต่อไปดูไฟล์ที่สำคัญมากคือไฟล์ Cargo.toml

เนื้อหา Cargo.toml เป็นไฟล์สำคัญในโปรเจ็กต์ Rust ที่ใช้ในการกำหนดการตั้งค่าและการจัดการพแพ็คเกจ (packages) สำหรับโปรเจ็กต์ ไฟล์นี้ถูกใช้โดย Cargo ซึ่งเป็นเครื่องมือจัดการโปรเจ็กต์และแพ็คเกจของ Rust ช่วยให้การจัดการโค้ด การติดตั้งไลบรารี และการสร้างโปรเจ็กต์เป็นไปอย่างราบรื่นและมีประสิทธิภาพ

[package]
name = "rust-geodesy2"
version = "0.1.0"
authors = ["pbrobo"]
edition = "2021"
resolver = "2"
rust-version = "1.77"

[[bin]]
name = "rust-geodesy2"
harness = false # do not use the built in cargo test harness -> resolve rust-analyzer errors

[profile.release]
opt-level = "s"

[profile.dev]
debug = true    # Symbols are nice and they don't increase the size on Flash
opt-level = "z"

[features]
default = ["std", "embassy", "esp-idf-svc/native"]

pio = ["esp-idf-svc/pio"]
std = ["alloc", "esp-idf-svc/binstart", "esp-idf-svc/std"]
alloc = ["esp-idf-svc/alloc"]
nightly = ["esp-idf-svc/nightly"]
experimental = ["esp-idf-svc/experimental"]
embassy = ["esp-idf-svc/embassy-sync", "esp-idf-svc/critical-section", "esp-idf-svc/embassy-time-driver"]

[dependencies]
log = { version = "0.4", default-features = false }
esp-idf-svc = { version = "0.49", default-features = false }
geodesy = "0.13.0"

[build-dependencies]
embuild = "0.32.0"

แก้ไขไฟล์ sdkconfig.defaults

ไฟล์นี้จะกำหนดขนาด stack ไว้ ถ้าไม่แก้พบว่ารันโปรแกรมแล้วแครช เตือนว่า stack overflow ทั้งที่โค้ดไม่น่าจะมีอะไรผิด

# Rust often needs a bit of an extra main task stack size compared to C (the default is 3K)
# change from defaults 8000 to 9000
CONFIG_ESP_MAIN_TASK_STACK_SIZE=9000

# Use this to set FreeRTOS kernel tick frequency to 1000 Hz (100 Hz by default).
# This allows to use 1 ms granularity for thread sleeps (10 ms by default).
CONFIG_FREERTOS_HZ=1000

# Workaround for https://github.com/espressif/esp-idf/issues/7631
#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=n
#CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=n

โค้ดทดสอบการแปลงพิกัดไปกลับจากค่าพิกัดกริดยูทีเอ็ม WGS84 เป็น Indian 1975

คลิกไฟล์ src/main.rs แล้วแก้ไขโค้ดดังนี้

use geodesy::prelude::*;

fn print_coordinates(coords: &[Coor3D; 2]) {
    for (i, &coord) in coords.iter().enumerate() {
        log::info!("Point {}, coordinates: {:?}", i+1, coord);
    }
}

fn main() -> Result<(), Box<Error>> {
    // It is necessary to call this function once. Otherwise some patches to the runtime
    // implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
    esp_idf_svc::sys::link_patches();

    // Bind the log crate to the ESP Logging facilities
    esp_idf_svc::log::EspLogger::initialize_default();

    log::info!("Hello, ESP32 Rust Geodesy!");
    let pipeline = "inv utm zone=47 ellps=WGS84
             | cart ellps=WGS84
             | helmert translation=-204.4798,-837.8940,-294.7765
             | cart inv ellps=evrst30
             | utm zone=47 ellps=evrst30";

    let mut ctx = Minimal::new();
    let op = ctx.op(pipeline)?;
    log::info!("Test forward pipeline of RG: ");
    let ne1 = Coor3D::raw(685063.5075, 1521137.2111, 0.0);
    let ne2 = Coor3D::raw(681899.5402, 1685083.1430, 0.0);
    let coor1 = [ne1, ne2];
    log::info!("\nFrom grid coordinates of WGS84 UTM zone 47: ");
    print_coordinates(&coor1);

    let mut coor2: [Coor3D; 2] = coor1.clone();
    ctx.apply(op, Fwd, &mut coor2)?;
    log::info!("\nTo grid coordinates of Thailand Indian1975 UTM Zone 47:");
    print_coordinates(&coor2);
    log::info!("\n\n=========Round the trip back!==============");
    log::info!("Test inverse pipeline of RG: ");
    log::info!("\nFrom grid coordinates of Thailand Indian1975 UTM Zone 47:");
    let coor3: [Coor3D; 2] = coor2.clone();
    print_coordinates(&coor3);
    let mut coor4: [Coor3D; 2] = coor3.clone();
    ctx.apply(op, Inv, &mut coor4)?;
    log::info!("\nTo grid coordinates of WGS84 UTM zone 47:");
    print_coordinates(&coor4);
    
    Ok(())
}

อธิบายการแปลงพิกัดผ่านสายอักขระ Pipeline

ดูโค้ดเริ่มแรกกำหนดให้สายอักขระ pipeline เก็บเข้าตัวแปร “pipeline” สร้างตัวแปร ctx ด้วย let mut ctx = Minimal::new(); แล้วไปกำหนด ctx.op พารามิเตอร์ด้วยตัวแปร “pipeline” ด้วย let op = ctx.op(pipeline)?;

    let pipeline = "inv utm zone=47 ellps=WGS84
             | cart ellps=WGS84
             | helmert translation=-204.4798,-837.8940,-294.7765
             | cart inv ellps=evrst30
             | utm zone=47 ellps=evrst30";

    let mut ctx = Minimal::new();
    let op = ctx.op(pipeline)?;

ส่วน pipeline จะขออธิบายพอสังเขปดังนี้

  • inv utm zone=47 ellps=WGS84 ถ้าไม่ใส่ inv จะเป็นคำนวณค่าพิกัดภูมิศาสตร์ไปหาค่าพิกัดยูทีเอ็มบนทรงรี WGS84 พอใส่ inv จะเป็นการคำนวณกลับคือจากค่าพิกัดยูทีเอ็มไปยังค่าพิกัดภูมิศาสตร์ในรูปแบบเรเดียน
  • cart ellps=WGS84 จะเป็นการแปลงค่าพิกัดภูมิศาสตร์(เรเดียน) ไปยังค่าพิกัดคาร์ทีเซียนบนทรงรี WGS84
  • helmert translation=-204.4798,-837.8940,-294.7765 ใช้สูตร Helmert ในการแปลงพิกัด 3 มิติในอวกาศเพื่อเลื่อนไปตาม X, Y และ Z ตามตัวเลข ตัวเลขชุดนี้ตัวเลขที่ได้จากกรมแผนที่ทหาร เมื่อเลื่อนพิกัดไปแล้วจะกลายเป็นระบบพิกัดคาร์ทีเซียนของทรงรี Everest 1830
  • cart inv ellps=evrst30 คำนวณค่าพิกัดคาร์ทีเซียนบนทรงรี Everest 1830 ไปเป็นค่าพิกัดภูมิศาสตร์ (เรเดียน)
  • utm zone=47 ellps=evrst30 แปลงพิกัดจากค่าพิกัดภูมิศาสตร์ (เรเดียน) ไปเป็นค่าพิกัดยูทีเอ็มบนทรงรี Everest 1830 ก็คือค่าพิกัดบนพื้นหลักฐาน Indian 1975 ที่ใช้งานกันอยู่ในประเทศไทย

ค่าพิกัดนำเข้าสามมิติ (Coor3D)

ในการแปลงพิกัดต้องใช้ระบบพิกัดคาร์ทีเซียนที่เป็น 3D ดังนั้นการนำค่าพิกัดเข้าจะต้องกำหนดเป็น Coor3D ผมลองใช้ Coor2D พบว่าผลลัพธ์ที่ได้มาไม่ถูกต้อง

    let ne1 = Coor3D::raw(685063.5075, 1521137.2111, 0.0);
    let ne2 = Coor3D::raw(681899.5402, 1685083.1430, 0.0);
    let coor1 = [ne1, ne2];

โค้ดที่สำคัญอีกบรรทัดคือ จะเป็นการคำนวณ โดยเรียกใช้ operator จากตัวแปร op และคำนวณไปข้างหน้าด้วยคีย์ Fwd และใช้ค่าพิกัดจากตัวแปร coor2 การใช้ &mut นำหน้าคือตัวแปรสามารถถูกแก้ไขค่าได้ ค่าเริ่มต้นจะเป็นค่าพิกัดยูทีเอ็ม WGS84/UTM Zone 47 สุดท้ายค่าที่คำนวณออกมาคือค่าพิกัดยูทีเอ็ม Indian 1975/UTM Zone 47

ctx.apply(op, Fwd, &mut coor2)?;

คอมไพล์และบิวท์โค้ด

เมื่อพร้อมแล้วทำการคอมไพล์โค้ดด้วยคำสั่ง cargo +esp build ต้องรอสักพักใหญ่ๆใช้เวลานานพอสมควร

pbrobo@pbrworkstation:~/esp/rust-esp/rust-geodesy2$ cargo +esp build
  Downloaded prettyplease v0.2.24
  Downloaded cc v1.1.31
  Downloaded rustversion v1.0.18
  Downloaded critical-section v1.2.0
  Downloaded serde_json v1.0.132
  Downloaded bindgen v0.69.5
  Downloaded syn v2.0.81
  Downloaded 7 crates (823.5 KB) in 0.64s
   Compiling compiler_builtins v0.1.109
   Compiling core v0.0.0 (/home/pbrobo/.rustup/toolchains/esp/lib/rustlib/src/rust/library/core)
   Compiling libc v0.2.155
   Compiling memchr v2.5.0
   Compiling std v0.0.0 (/home/pbrobo/.rustup/toolchains/esp/lib/rustlib/src/rust/library/std)
   Compiling proc-macro2 v1.0.88
   Compiling unicode-ident v1.0.13
   Compiling memchr v2.7.4
   Compiling cfg-if v1.0.0
   Compiling serde v1.0.210
   Compiling libc v0.2.161
   Compiling bitflags v2.6.0
   Compiling crossbeam-utils v0.8.20
   Compiling thiserror v1.0.64
   Compiling regex-syntax v0.8.5
   Compiling rustix v0.38.37
   Compiling anyhow v1.0.90
   Compiling glob v0.3.1
   Compiling cfg_aliases v0.2.1
   Compiling nix v0.29.0
   Compiling rustversion v1.0.18
   Compiling aho-corasick v1.1.3
   Compiling prettyplease v0.2.24
   Compiling log v0.4.22
   Compiling linux-raw-sys v0.4.14
   Compiling quote v1.0.37
   Compiling syn v1.0.109
   Compiling crossbeam-epoch v0.9.18
   Compiling clang-sys v1.8.1
   Compiling syn v2.0.81
   Compiling bstr v1.10.0
   Compiling shlex v1.3.0
   Compiling minimal-lexical v0.2.1
   Compiling serde_json v1.0.132
   Compiling same-file v1.0.6
   Compiling either v1.13.0
   Compiling nom v7.1.3
   Compiling walkdir v2.5.0
   Compiling crossbeam-deque v0.8.5
   Compiling libloading v0.8.5
   Compiling cvt v0.1.2
   Compiling itoa v1.0.11
   Compiling ryu v1.0.18
   Compiling bindgen v0.69.5
   Compiling once_cell v1.20.2
   Compiling home v0.5.9
   Compiling heck v0.4.1
   Compiling itertools v0.12.1
   Compiling regex-automata v0.4.8
   Compiling cc v1.1.31
   Compiling which v4.4.2
   Compiling cexpr v0.6.0
   Compiling fs_at v0.2.1
   Compiling rustc-hash v1.1.0
   Compiling fastrand v2.1.1
   Compiling normpath v1.3.0
   Compiling lazy_static v1.5.0
   Compiling bitflags v1.3.2
   Compiling lazycell v1.3.0
   Compiling remove_dir_all v0.8.4
   Compiling tempfile v3.13.0
   Compiling cmake v0.1.51
   Compiling filetime v0.2.25
   Compiling semver v1.0.23
   Compiling camino v1.1.9
   Compiling ident_case v1.0.1
   Compiling autocfg v1.4.0
   Compiling globset v0.4.15
   Compiling regex v1.11.0
   Compiling fnv v1.0.7
   Compiling embedded-io-async v0.6.1
   Compiling darling_core v0.20.10
   Compiling ignore v0.4.23
   Compiling heapless v0.8.0
   Compiling unicode-xid v0.2.6
   Compiling num-traits v0.2.19
   Compiling iana-time-zone v0.1.61
   Compiling embassy-time-driver v0.1.0
   Compiling const_format_proc_macros v0.2.33
   Compiling globwalk v0.8.1
   Compiling embassy-sync v0.6.0
   Compiling version_check v0.9.5
   Compiling chrono v0.4.38
   Compiling strum_macros v0.24.3
   Compiling heck v0.5.0
   Compiling embedded-hal-async v1.0.0
   Compiling uncased v0.9.10
   Compiling litrs v0.4.1
   Compiling document-features v0.2.10
   Compiling strum v0.24.1
   Compiling serde_derive v1.0.210
   Compiling thiserror-impl v1.0.64
   Compiling clap_derive v4.5.18
   Compiling build-time v0.1.3
   Compiling num_enum_derive v0.7.3
   Compiling darling_macro v0.20.10
   Compiling darling v0.20.10
   Compiling enumset_derive v0.10.0
   Compiling rustc-std-workspace-core v1.99.0 (/home/pbrobo/.rustup/toolchains/esp/lib/rustlib/src/rust/library/rustc-std-workspace-core)
   Compiling alloc v0.0.0 (/home/pbrobo/.rustup/toolchains/esp/lib/rustlib/src/rust/library/alloc)
   Compiling unwind v0.0.0 (/home/pbrobo/.rustup/toolchains/esp/lib/rustlib/src/rust/library/unwind)
   Compiling adler v1.0.2
   Compiling rustc-demangle v0.1.24
   Compiling rustc-std-workspace-alloc v1.99.0 (/home/pbrobo/.rustup/toolchains/esp/lib/rustlib/src/rust/library/rustc-std-workspace-alloc)
   Compiling panic_unwind v0.0.0 (/home/pbrobo/.rustup/toolchains/esp/lib/rustlib/src/rust/library/panic_unwind)
   Compiling panic_abort v0.0.0 (/home/pbrobo/.rustup/toolchains/esp/lib/rustlib/src/rust/library/panic_abort)
   Compiling gimli v0.29.0
   Compiling hashbrown v0.14.5
   Compiling std_detect v0.1.5 (/home/pbrobo/.rustup/toolchains/esp/lib/rustlib/src/rust/library/stdarch/crates/std_detect)
   Compiling miniz_oxide v0.7.4
   Compiling object v0.36.0
   Compiling cargo-platform v0.1.8
   Compiling envy v0.4.2
   Compiling addr2line v0.22.0
   Compiling embuild v0.32.0
   Compiling cargo_metadata v0.18.1
   Compiling esp-idf-sys v0.35.0
   Compiling esp-idf-hal v0.44.1
   Compiling esp-idf-svc v0.49.1
   Compiling rust-geodesy2 v0.1.0 (/home/pbrobo/esp/rust-esp/rust-geodesy2)
   Compiling proc_macro v0.0.0 (/home/pbrobo/.rustup/toolchains/esp/lib/rustlib/src/rust/library/proc_macro)
   Compiling utf8parse v0.2.2
   Compiling is_terminal_polyfill v1.70.1
   Compiling byteorder v1.5.0
   Compiling anstyle v1.0.8
   Compiling anstyle-parse v0.2.5
   Compiling colorchoice v1.0.2
   Compiling nb v1.1.0
   Compiling anstyle-query v1.1.1
   Compiling hash32 v0.3.1
   Compiling anstream v0.6.15
   Compiling embedded-io v0.6.1
   Compiling stable_deref_trait v1.2.0
   Compiling futures-core v0.3.31
   Compiling pin-project-lite v0.2.14
   Compiling futures-task v0.3.31
   Compiling clap_lex v0.7.2
   Compiling embedded-hal v1.0.0
   Compiling pin-utils v0.1.0
   Compiling strsim v0.11.1
   Compiling futures-util v0.3.31
   Compiling nb v0.1.3
   Compiling clap_builder v4.5.20
   Compiling enumset v1.1.5
   Compiling critical-section v1.2.0
   Compiling option-ext v0.2.0
   Compiling void v1.0.2
   Compiling const_format v0.2.33
   Compiling embedded-hal v0.2.7
   Compiling dirs-sys v0.4.1
   Compiling embedded-hal-nb v1.0.0
   Compiling embedded-can v0.4.1
   Compiling getrandom v0.2.15
   Compiling humantime v2.1.0
   Compiling atomic-waker v1.1.2
   Compiling num_enum v0.7.3
   Compiling uuid v1.11.0
   Compiling dirs v5.0.1
   Compiling float_eq v1.0.1
   Compiling embassy-futures v0.1.1
   Compiling clap v4.5.20
   Compiling clap-verbosity-flag v2.2.2
   Compiling env_filter v0.1.2
   Compiling embedded-svc v0.28.0
   Compiling env_logger v0.11.5
   Compiling geodesy v0.13.0
    Finished `dev` profile [optimized + debuginfo] target(s) in 3m 27s
pbrobo@pbrworkstation:~/esp/rust-esp/rust-geodesy2$

รันโค้ด

ถ้าคอมไพล์แล้วไม่มี error ก็พร้อมที่จะแฟลชไบนารีโค้ดลงเม็ม (flash) ของ ESP32 เมื่อถึงตรงนี้แล้วจะต้องเตรียมสาย USB เสียบเข้ากับ ESP32 การทำให้ WSL2 มองเห็นสายเป็น serial ค่อนข้างจะยุ่งนิดให้อ่านลิ๊งค์ที่ให้ในตอนต้นบทความ ช่วงท้ายๆของลิ๊งค์จะเน้นเรื่องนี้

เมื่อแฟลชเสร็จแล้วโปรแกรมจะทำการรันบน ESP32 ผลการรันดูได้ที่ terminal

pbrobo@pbrworkstation:~/esp/rust-esp/rust-geodesy2$ cargo run
    Finished `dev` profile [optimized + debuginfo] target(s) in 0.38s
     Running `espflash flash --monitor target/xtensa-esp32-espidf/debug/rust-geodesy2`
[2024-10-20T10:30:54Z INFO ] 🚀 A new version of espflash is available: v3.2.0
[2024-10-20T10:30:54Z INFO ] Serial port: '/dev/ttyUSB0'
[2024-10-20T10:30:54Z INFO ] Connecting...
[2024-10-20T10:30:54Z INFO ] Using flash stub
Chip type:         esp32 (revision v3.1)
Crystal frequency: 40 MHz
Flash size:        4MB
Features:          WiFi, BT, Dual Core, 240MHz, Coding Scheme None
MAC address:       88:13:bf:07:b8:98
App/part. size:    850,720/4,128,768 bytes, 20.60%
[2024-10-20T10:30:55Z INFO ] Segment at address '0x1000' has not changed, skipping write
[2024-10-20T10:30:55Z INFO ] Segment at address '0x8000' has not changed, skipping write
[2024-10-20T10:30:56Z INFO ] Segment at address '0x10000' has not changed, skipping write
[2024-10-20T10:30:56Z INFO ] Flashing has completed!
Commands:
    CTRL+R    Reset chip
    CTRL+C    Exit

ets Jul 29 2019 12:21:46

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:2
load:0x3fff0030,len:7104
load:0x40078000,len:15576
load:0x40080400,len:4
0x40080400 - _invalid_pc_placeholder
    at ??:??
ho 8 tail 4 room 4
load:0x40080404,len:3876
entry 0x4008064c
I (31) boot: ESP-IDF v5.1-beta1-378-gea5e0ff298-dirt 2nd stage bootloader
I (31) boot: compile time Jun  7 2023 07:48:23
I (33) boot: Multicore bootloader
I (37) boot: chip revision: v3.1
I (41) boot.esp32: SPI Speed      : 40MHz
I (46) boot.esp32: SPI Mode       : DIO
I (50) boot.esp32: SPI Flash Size : 4MB
I (55) boot: Enabling RNG early entropy source...
I (60) boot: Partition Table:
I (64) boot: ## Label            Usage          Type ST Offset   Length
I (71) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (79) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (86) boot:  2 factory          factory app      00 00 00010000 003f0000
I (94) boot: End of partition table
I (98) esp_image: segment 0: paddr=00010020 vaddr=3f400020 size=34d20h (216352) map
I (184) esp_image: segment 1: paddr=00044d48 vaddr=3ffb0000 size=02158h (  8536) load
I (188) esp_image: segment 2: paddr=00046ea8 vaddr=40080000 size=09170h ( 37232) load
I (206) esp_image: segment 3: paddr=00050020 vaddr=400d0020 size=8cda4h (576932) map
I (414) esp_image: segment 4: paddr=000dcdcc vaddr=40089170 size=02d28h ( 11560) load
I (425) boot: Loaded app from partition at offset 0x10000
I (426) boot: Disabling RNG early entropy source...
I (437) cpu_start: Multicore app
I (446) cpu_start: Pro cpu start user code
I (446) cpu_start: cpu freq: 160000000 Hz
I (446) cpu_start: Application information:
I (449) cpu_start: Project name:     libespidf
I (454) cpu_start: App version:      1
I (458) cpu_start: Compile time:     Oct 20 2024 16:35:45
I (464) cpu_start: ELF file SHA256:  000000000...
I (470) cpu_start: ESP-IDF:          v5.2.2
I (475) cpu_start: Min chip rev:     v0.0
I (479) cpu_start: Max chip rev:     v3.99
I (484) cpu_start: Chip rev:         v3.1
I (489) heap_init: Initializing. RAM available for dynamic allocation:
I (496) heap_init: At 3FFAE6E0 len 00001920 (6 KiB): DRAM
I (502) heap_init: At 3FFB2AC0 len 0002D540 (181 KiB): DRAM
I (508) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
I (515) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
I (521) heap_init: At 4008BE98 len 00014168 (80 KiB): IRAM
I (529) spi_flash: detected chip: generic
I (532) spi_flash: flash io: dio
W (536) pcnt(legacy): legacy driver is deprecated, please migrate to `driver/pulse_cnt.h`
W (545) timer_group: legacy driver is deprecated, please migrate to `driver/gptimer.h`
I (554) main_task: Started on CPU0
I (558) main_task: Calling app_main()
I (564) rust_geodesy2: Hello, ESP32 Rust Geodesy!
I (723) rust_geodesy2: Test forward pipeline of RG:
I (724) rust_geodesy2:
From grid coordinates of WGS84 UTM zone 47:
I (725) rust_geodesy2: Point 1, coordinates: Coor3D([685063.5075, 1521137.2111, 0.0])
I (734) rust_geodesy2: Point 2, coordinates: Coor3D([681899.5402, 1685083.143, 0.0])
I (750) rust_geodesy2:
To grid coordinates of Thailand Indian1975 UTM Zone 47:
I (750) rust_geodesy2: Point 1, coordinates: Coor3D([685396.1990916887, 1520834.3178692292, 17.46010040061179])
I (761) rust_geodesy2: Point 2, coordinates: Coor3D([682232.3647343164, 1684780.1251680395, 12.757210301214014])
I (772) rust_geodesy2:

=========Round the trip back!==============
I (779) rust_geodesy2: Test inverse pipeline of RG:
I (785) rust_geodesy2:
From grid coordinates of Thailand Indian1975 UTM Zone 47:
I (793) rust_geodesy2: Point 1, coordinates: Coor3D([685396.1990916887, 1520834.3178692292, 17.46010040061179])
I (804) rust_geodesy2: Point 2, coordinates: Coor3D([682232.3647343164, 1684780.1251680395, 12.757210301214014])
I (821) rust_geodesy2:
To grid coordinates of WGS84 UTM zone 47:
I (822) rust_geodesy2: Point 1, coordinates: Coor3D([685063.5074999997, 1521137.2110999997, 0.0])
I (831) rust_geodesy2: Point 2, coordinates: Coor3D([681899.5401999967, 1685083.143, -1.000184868473265e-9])
I (848) main_task: Returned from app_main()
pbrobo@pbrworkstation:~/esp/rust-esp/rust-geodesy2$

ผลลัพธ์การแปลงพิกัดยูทีเอ็ม

ผลลัพธ์การแปลงพิกัดดูได้จาก Terminal ที่ผมแคปมาให้ดูอีกทีชัดๆ

From grid coordinates of WGS84 UTM zone 47:
I (725) rust_geodesy2: Point 1, coordinates: Coor3D([685063.5075, 1521137.2111, 0.0])
I (734) rust_geodesy2: Point 2, coordinates: Coor3D([681899.5402, 1685083.143, 0.0])
I (750) rust_geodesy2:
To grid coordinates of Thailand Indian1975 UTM Zone 47:
I (750) rust_geodesy2: Point 1, coordinates: Coor3D([685396.1990916887, 1520834.3178692292, 17.46010040061179])
I (761) rust_geodesy2: Point 2, coordinates: Coor3D([682232.3647343164, 1684780.1251680395, 12.757210301214014])
I (772) rust_geodesy2:

=========Round the trip back!==============
I (779) rust_geodesy2: Test inverse pipeline of RG:
I (785) rust_geodesy2:
From grid coordinates of Thailand Indian1975 UTM Zone 47:
I (793) rust_geodesy2: Point 1, coordinates: Coor3D([685396.1990916887, 1520834.3178692292, 17.46010040061179])
I (804) rust_geodesy2: Point 2, coordinates: Coor3D([682232.3647343164, 1684780.1251680395, 12.757210301214014])
I (821) rust_geodesy2:
To grid coordinates of WGS84 UTM zone 47:
I (822) rust_geodesy2: Point 1, coordinates: Coor3D([685063.5074999997, 1521137.2110999997, 0.0])
I (831) rust_geodesy2: Point 2, coordinates: Coor3D([681899.5401999967, 1685083.143, -1.000184868473265e-9])

จากที่เรานำเข้าค่าพิกัดยูทีเอ็มบนพื้นหลักฐาน WGS84/UTM Zone 47 โดยจุดทดสอบมีสองจุดคือ

Point NoNorthingEasting
11521137.2111685063.5075
21685083.1430681899.5402
ค่าพิกัดยูทีเอ็มบนพื้นหลักฐาน WGS84/UTM Zone 47

ผลลัพธ์ค่าพิกัดยูทีเอ็มบนพื้นหลักฐาน Indian 1975/UTM Zone 47 ที่ได้จากการคำนวณ

Point NoNorthingEasting
11520834.3179685396.1991
21684780.1252682232.3647
ค่าพิกัดยูทีเอ็มบนพื้นหลักฐาน Indian 1975/UTM Zone 47

ผมลองคำนวณด้วย Surveyro Pocket Tools ที่ใช้ไลบรารี PROJ

Point NoNorthingEasting
11520834.3179685396.1982
21684780.1252682232.3638
ค่าพิกัดยูทีเอ็มบนพื้นหลักฐาน Indian 1975/UTM Zone 47 คำนวณจาก Surveyor Pocket Tools

ไม่มีความความแตกต่างสำหรับค่า Northing ที่ทศนิยมตำแหน่งที่สี่ แต่ค่า Easting จะมีความต่างกันถึง 0.9 มม ก็น่าคิดอยู่ ผมคงต้องไปศึกษาอีกว่าทำไมถึงต่างกันอย่างมีนัยสำคัญในลำดับต่อไป

และอีกอย่างหนึ่งคือการคำนวณย้อนกลับก็พบว่าค่าพิกัดกลับมาเท่ากับค่าพิกัดที่เรากำหนดไว้เป๊ะ แสดงว่าการคำนวณนั้นขั้นตอนถูกต้องอย่างไม่ต้องสงสัย จุดที่น่าสังเกตคือไลบรารี RG บังคับให้ป้อนค่าพิกัดเป็นสามมิติ (Coor3D) ซึ่งผมสมมติค่าระดับไปเป็น 0.0 ตรงนี้อาจจะมีผล

ในตอนนี้ ถ้านำ ESP32 ไปคำนวณค่าพิกัดที่ไม่ต้องการความละเอียดถึงจุดทศนิยมที่ 4 หรือทศนิยมของมิลลิเมตร ก็สามารถนำไปใช้งานได้เลย โปรดติดตามกันตอนต่อไปครับ

Leave a Reply

Your email address will not be published. Required fields are marked *