{"content":[{"id":84,"title":"axum glitchtip","slug":"axum-glitchtip","body":"---\ntags:\n  - observ\n---\n[ค่าผ่านทาง item ลับพิชิตน้ำท่วม](https://s.shopee.co.th/gQwAxSlsi)\n\nสวัสดีนักเดินทางทุกคน เราหายไปนานเลย ช่วงที่ผ่านมาแอบไปศึกษาของเล่นใหม่ ๆ มา โดยเฉพาะฝั่ง Rust ที่อยากลองเอามาใช้กับงานจริงให้ได้ก่อน แล้วค่อยกลับมาเหลากันต่อ 😆\n\nรอบนี้เราไปลองเล่น **Axum + Sentry SDK + GlitchTip** เพื่อทำ Error Tracking และ Observability ให้กับ Backend ที่เขียนด้วย Rust\n\n![Gemini_Generated_Image_adzfkqadzfkqadzf](https://drive.google.com/thumbnail?id=1FUlwg6-sKczESgIB_lrW5n7bbll9vHI3&sz=w1000)\n\nไหน ๆ ก็ลองจนเริ่มเห็นภาพแล้ว เลยเอามาแบ่งปันกัน เผื่อใครกำลังเล่น Rust Backend อยู่เหมือนกัน จะได้มีของเล่นไว้ทดลองกันอีกชิ้น\n\n## Trace กับ Span คืออะไร?\n\nขอเล่าแบบคร่าว ๆ ก่อน สมมติว่าเรามี HTTP Request เข้ามาหนึ่งรายการ แล้วต้องผ่านหลายขั้นตอน ตั้งแต่รับ Request ตรวจสอบผู้ใช้ ไปจนถึงเรียก Database และส่ง Response กลับไป\n\n![Untitled 25_202610181102_52071](https://drive.google.com/thumbnail?id=1hFFMTvPRwB7rf9nHlw-fk19iY6uQEanv&sz=w1000)\n\n**Trace** คือภาพรวมของการทำงานหนึ่งชุด ส่วน **Span** คือขั้นตอนย่อย ๆ ที่เกิดขึ้นภายในงานนั้น โดยแต่ละ Span สามารถมีความสัมพันธ์แบบ Parent-Child กันได้\n\n## แล้วทำไมต้องมี Observability กับ Error Tracking?\n\nปกติเวลาเขียน Backend เราก็มี Log ไว้ดูอยู่แล้วนี่ แล้วจะเพิ่มของพวกนี้มาทำไมอีก?\n\nคำตอบคือ Log อย่างเดียวอาจไม่พอ โดยเฉพาะเมื่อระบบเริ่มมี Request หลายรายการทำงานพร้อมกัน หรือมี Background Worker ทำงานอยู่เบื้องหลัง\n\n- **Logging** ช่วยบอกว่าเกิดเหตุการณ์อะไรขึ้น\n- **Error Tracking** ช่วยรวบรวม Error เพื่อให้เราตรวจสอบต้นเหตุและติดตามปัญหาที่เกิดซ้ำได้\n- **Observability** ช่วยให้เราเข้าใจพฤติกรรมของระบบจากข้อมูลที่ระบบเปิดเผยออกมา เช่น Logs, Metrics และ Traces\n\nสิ่งที่ผมอยากลองเป็นพิเศษคือการทำให้ Error กับ Trace มีความสัมพันธ์กัน เพื่อที่เวลาเกิดปัญหาจะได้ไม่ต้องนั่งไล่เปิด Log ทีละบรรทัดเหมือนตามหาของที่ทำหล่นไว้ในป่า \n\n![Screenshot 2569-10-18 at 12.38.03](https://drive.google.com/thumbnail?id=1XCibkyQ8joS6PbVVJTWUqwtq6tiacoJx&sz=w1000)\n\nแน่นอนว่าการติดตั้ง SDK ไม่ได้ทำให้ทุกอย่างเกิดขึ้นอัตโนมัติ เรายังต้องจัดการ Request Context, Span และการตั้งค่าการส่งข้อมูลให้เหมาะสมด้วย ซึ่งเป็นส่วนที่สนุกกับการทดลองครั้งนี้มาก\n\n## Rust + Axum + GlitchTip\n\nผมทำ Repository เล็ก ๆ เอาไว้ทดลองเรื่องนี้โดยเฉพาะ ใครสนใจสามารถเข้าไปอ่านโค้ดและนำไปศึกษาต่อได้เลย\n\n🔗 [**GitHub:** กดดู codeeeeeeee](https://github.com/lidm0707/axum_glitchtip) \n\n```toml\nsentry = { version = \"0.49.3\", features = [\"tracing\",\"tower-axum-matched-path\",\"logs\"] }\nsentry-tracing = \"0.49.3\"\ntracing = \"0.1\"\ntracing-subscriber = { version = \"0.3\", features = [\"env-filter\"] }\n```\n\nในโปรเจกต์นี้เราจะได้ลองดูการเชื่อมต่อ Axum กับ Sentry SDK และการส่งข้อมูลไปยัง GlitchTip รวมถึงการทดลองเกี่ยวกับ Tracing และ Span\n\nส่วนการเริ่มต้นใช้งาน Sentry ใน Axum จุดสำคัญคือการ Initialize SDK ตั้งแต่ช่วงเริ่มต้นของ Application ก่อนที่เราจะเริ่มรับ Request เพื่อให้ระบบพร้อมจัดการ Event และ Context ที่เกิดขึ้นระหว่างการทำงาน\n\nตัวอย่างแนวคิดแบบย่อ:\n\n```rust\n// _guard <--ถ้าไอนี้ drop ตัวรับ trace ของ sentry ระเบิดตู้ม\nlet _guard = sentry::init((\n    std::env::var(\"SENTRY_DSN\").unwrap_or_default(),\n    sentry::ClientOptions {\n        release: sentry::release_name!(),\n        ..Default::default()\n    },\n));\n\nlet sentry_layers = ServiceBuilder::new()\n      .layer(NewSentryLayer::new_from_top())\n      .layer(SentryHttpLayer::new().enable_transaction());\n\n// Initialize tracing subscriber\n// Build Axum router\n// Start the HTTP server\n```\n\nตัวอย่างนี้แสดงลำดับการ Initialize แบบคร่าว ๆ เท่านั้น การตั้งค่า Tracing Layer, การสร้าง Transaction และการจัดการ Span ต้องดูเพิ่มเติมตามโค้ดและเวอร์ชันของ SDK ที่ใช้งานจริง\n\nอีกเรื่องที่อยากฝากไว้คือ การส่ง Error สำเร็จไม่ได้แปลว่า Performance Trace จะปรากฏครบเสมอไป ถ้าใครกำลังทดลองแล้วเจอปัญหา Span ไม่ขึ้น ลองไล่ดูตั้งแต่การสร้าง Span ไปจนถึงการส่งข้อมูลและการตั้งค่า Sampling กันได้เลย\n\n```rust\n// อย่าลืมใส่ macro ตัวนีี้ด้วย เพราะถ้าไม่ใส่ span child จะไม่ถูกใส่เข้าไป\n#[tracing::instrument]\nasync fn report_error() -> StatusCode {\n    let err = std::io::Error::other(\"mock failure for k6\");\n    sentry::capture_error(&err);\n    StatusCode::INTERNAL_SERVER_ERROR\n}\n```\n\n## ส่งท้ายก่อนออกเดินทางต่อ\n\nรอบนี้ก็ประมาณนี้ก่อนครับ ใครสนใจ Rust Backend, Axum หรือระบบ Monitoring ลองแวะไปเล่น Repository กันได้ ถือว่าเอามาแบ่งปันเป็นแนวทางศึกษา ไม่ได้บอกว่าเป็นสูตรสำเร็จที่ใช้ได้กับทุกโปรเจกต์นะ\n\nส่วนผมคงต้องขอตัวกลับไปหาอะไรเล่นต่อแล้ว รอบหน้าจะไปลองฝั่ง **ESP และศึกษาด้าน Reverse Engineering** ต่อบ้าง ช่วงนี้ของเล่นเยอะจนเวลาไม่ค่อยพอจริง ๆ 555\n\nไว้เจอกันใหม่ครับนักเดินทาง 👋","status":"published","created_at":"2026-10-18T06:26:47.79757+00:00","updated_at":"2026-10-18T06:26:47.79757+00:00","synced_at":"2026-10-18T06:26:47.79757+00:00"},{"id":77,"title":"ESP32-C3-LCDkit_bevy","slug":"esp32-c3-lcdkit_bevy","body":"[ ตัวเทียบในไทย ](https://s.shopee.co.th/5AsxxzRZwW)\n[ ของแท้ ](https://th.aliexpress.com/item/1005005872040877.html?src=google&src=google&albch=shopping&acnt=615-992-9880&isdl=y&slnk=&plac=&mtctp=&albbt=Google_7_shopping&aff_platform=google&aff_short_key=_oFgTQeV&gclsrc=aw.ds&albagn=888888&ds_e_adid=&ds_e_matchtype=&ds_e_device=c&ds_e_network=x&ds_e_product_group_id=&ds_e_product_id=th1005005872040877&ds_e_product_merchant_id=552159742&ds_e_product_country=TH&ds_e_product_language=th&ds_e_product_channel=online&ds_e_product_store_id=&ds_url_v=2&albcp=23862218563&albag=&isSmbAutoCall=false&needSmbHouyi=false&traffic_server_nav=true&gad_source=1&gad_campaignid=23857727414&gclid=Cj0KCQjwz4LWBhCMARIsAFEG5Mq5Rf00K2mo8PRNyV_c55KAAJ98alguvfz-KEd7SsXu8vKMInCcfHsaAtodEALw_wcB)\n\n![lilidm_with_esp_lcd_kit](https://drive.google.com/thumbnail?id=1Ompvtwu1SkdQ12TC9230wfebHxS_93ta&sz=w1000)\n\n\nสวัสดีนักเดินทางทุกคน วันนี้ก็ได้สำรวจเจอ item ชิ้นใหม่\n\nItem นี้ค่อนข้าง**ซับซ้อน มีทั้งภาพขยับได้ พอกดก็มีเสียงตี้ดๆ แหละ**\n\nก็เลยไปทำการศึกษานั่นก็คือ **ESP32-C3-LCDkit** \n\nitem ชิ้นนี้ที่ให้มาหลักๆ ที่ display lcd 240 * 240, IR , Rotary button , LED 3 color\n\n,speaker และ อื่นๆ. แต่จริงๆมีแค่ หน้าจอกับ ปุ่มบังคับได้ก็ทำอะไรได้เยอะแยะแล้ว\n\nและก็ไปสะดุดตากับ Project ที่ แสดงใน esp official นั้นก็คือ นำ bevy_esc มาใช้ใน project นี้\n[georgik/esp32-conways-game-of-life-rs](https://github.com/georgik/esp32-conways-game-of-life-rs)\n\nเกริ่นกันสักนิ้ด bevy นั้น คือ  game engine ตัวนึงบน rust \n\nเป็น code first แต่ก็มีคนทำ tool มาให้เราจัดการ asset ได้ดีขึ้นแต่ก็สู้ตระกูล game engine\n\nที่มี UI ในการจัดการไม่ได้เลย ขนาดเจ้าตัวยังบอกให้ไป Godot เลย XD\n\n![Screenshot 2569-10-03 at 18.29.20](https://drive.google.com/thumbnail?id=1bTn_yIk_-_fe-3zbC84G3keq07DtovjH&sz=w1000)\n\n[setup ESP ลองอ่านตรงนี้กันดู](https://www.lilidm.com/content?tag_id=28&content_id=25)\n\n\nหลักๆตอนนี้เราจะลอง clone repo มา  flash rom ลง board กัน\n\n\nอันที่เราจะใช้คือ esp32-c3-lcdkit\n\nมาอธิบายส่วนประกอบในนี้แบบหยาบๆกัน \n\n1. เราใช้ esp-hal ในการเป็น api ในการคุยกับ board.\n2. เราใช้ `embedded-graphics` ในการ render ส่วน `mipidsi` ใช้เป็น display driver สำหรับคุยกับ LCD\n3. และเราใช้ bevy ecs เป็นตัวกลาง ในการควบคุม data ของเกมส์\n\nเพราะเราสามารถนำ api จาก hardware set เข้าไปใน bevy ได้ผ่าน Resource\n\nเกมส์นี้จริงๆ เหมือน poc ทดสอบมากกว่า คือจะ rng box ขึ้นมา แล้วจำนวนที่ rng ออกมาจะน้อยลงเรื่อยๆตามคำนวณที่ count ไป และเมื่อกด ปุ่ม reset cout จะกลับไปเป็น 0\n\nตอนไปอ่านแล้วแงะ มีความสงสัยว่า ว่าทำไมใช้ bevy::app.run() ไม่ได้\nด้วยความเป็นเด็กเซนดีเลยลองแงะๆ แปะๆ เข้าไปเสร็จปุ่ม ไม่มี error แล้วกด cargo run ดีกว่า\n\nเท่านั้นแหละตูม `memory allocation of 78848 bytes failed` \n\nถึงว่าทำไมเขาใช้ schedule และ world ใน project นี้ เพราะ  bevy::App ของแถมเยอะ heap ไม่พอ\n\n```rust\nesp_alloc::heap_allocator!(size: HEAP_SIZE); \n## ถ้าอยากใช้ พวก Box , Vec ใดๆ ก็แล้วแต่จะมาใช้ใน heap นี้\n\nlet mut world = World::new();\nlet mut schedule = Schedule::new();\nloop {schedule.run(&mut world);}\n\n```\n\n( ปกติถ้าเราใช้ bevy เราสามารถใช้ bevy::App::new() ได้ )\n\nแต่ถ้าใช้ App::new(); ต้องกำกับชื่อ schedule ด้วยเพราะใน App มี Schedule ให้หลายตัว\nเช่น schedule ชื่อ Update\n\n```rust\napp.add_systems(Update,render_system);\n```\n\nแต่ถ้าเราสร้างเองไม่ต้องกำหนด Update เพราะตอน add เข้า schedule เราจะต้องเป็นการจัดการว่าจะให้เข้า schedule ตัวไหนด้วยตัวเอง\n\n```rust\nschedule.add_systems(render_system)\n```\n\nที่จริงมี display touch screen P4 อีกตัว แต่ก็ต้องไปฝึกฝนและสำรวจต่อก่อน เพราะพลังยังกล้าแกร่งจนใช้ได้คล่องขนาดนั้น XD งั้นวันนี้ขอจบการเล่าสู้กันฟังเพียงเท่านี้บาย","status":"published","created_at":"2026-10-03T11:17:39.051202+00:00","updated_at":"2026-10-03T11:17:39.051202+00:00","synced_at":"2026-10-03T11:17:39.051202+00:00"},{"id":76,"title":"Migrate InfluxDB3","slug":"migrate-influxdb3","body":"---\ntags:\n  - database\n---\n\n[เรือสำหรับการเดินทาง](https://s.shopee.co.th/20vmrzvrQY)\n\n![underbkwater](https://drive.google.com/thumbnail?id=1EW83M5P1S1_CCjCm7hlGA_LRUYjuhs-e&sz=w1000)\n\n\nสวัสดีครับ นักเดินทางทุกวัน ช่วงนี้**ระวังตัว**ในการเดินทางกันด้วย\n\nส่วนตัวผมน่าจะรอด ( การเดินทางผ่านถ้ำบาดาล )\n\nในระหว่างการเดินทาง มีผู้โดยสาร ไป 3 คน 2 ตัวแต่อยู่ดี มี อีก 5 ตัวจอยด้วยเฉย\n\nขอให้นักเดินทางทุกคนผ่านพ้นโดยสวัสดิภาพ\n\nตอนที่การเดินทางกำลังสงบได้มีเวลาทำเรื่องที่ดองมานานแล้ว  นั่นก็คือ การ migrate\n\nInfluxDB3 ที่ลงใน Lxn ของ Proxmox ไป VM ( Virtual Machine ) Debian Docker แทน\n\n![Screenshot 2569-09-27 at 21.22.32](https://drive.google.com/thumbnail?id=138WT9Dgzs1iTUUrw-PwF2ryezl9BnPwW&sz=w1000)\n\nProxmox คือ OS ในการแบ่ง resource ของ hardware และสามารถสร้าง\ntemplate เพื่อใช้ในการทดลอง หรือ ใช้งานได้อย่างสะดวก\n\nเริ่มต้นเลยก็คือทำการสร้าง VM ใน Proxmox จากการ Clone template\n\n![Screenshot 2569-09-27 at 21.23.35](https://drive.google.com/thumbnail?id=1DLK68eaZwXLA0MqCctHdrCHAjp17-oMj&sz=w1000)\n\nต่อมาก็ Install sudo เพราะ Debian ไม่ได้แถมมาให้ เลยต้องสลับไป  root  แล้วลง sudo ก่อน\n\n```rust\nsu -\napt install sudo\nsu \"your user\"\n```\n\n[Docker Install](https://docs.docker.com/engine/install/debian/)\n\nแล้วก็ลง Docker ตามอัธยาศัย \n\nแล้วอย่าง ใช้ docker โดยไม่ต้อง sudo ก็อย่าลืม เพิ่ม user เข้า docker group กันด้วย\n\n```rust\nsudo usermod -aG docker \"your user\"\ngroups\n```\n\nเมื่อเพิ่มเข้า group docker เสร็ิจสิ้นควรเห็นชื่อของ user\n\nต่อมาก็  docker compose up -d \n\n```yaml\n  # docker-compose.yml\n  #\n  # InfluxDB 3 Core (database engine) + Explorer (UI).\n  # Image name correction: the Core engine lives at `influxdb:3-core`\n  # (Docker Official Image), NOT `influxdata/influxdb3` which does not exist.\n\n  services:\n    # One-shot: generate the offline admin token BEFORE core starts.\n    # The server needs the first token via --admin-token-file; the recovery\n    # endpoint can only REGENERATE existing tokens, it cannot create the\n    # first one. Also extracts a plain-text copy for healthchecks.\n    token-bootstrap:\n      image: influxdb:3-core\n      container_name: influxdb3-token-bootstrap\n      pull_policy: always\n      volumes:\n        - ./influxdb-data:/var/lib/influxdb3\n      entrypoint: [\"sh\", \"-c\"]\n      command:\n        - |\n          set -eu\n          if [ -s /var/lib/influxdb3/.admin-token ]; then\n            echo \"[bootstrap] admin token already exists, skipping\"\n            exit 0\n          fi\n          echo \"[bootstrap] generating offline admin token\"\n          influxdb3 create token --admin --offline \\\n            --name admin \\\n            --output-file /var/lib/influxdb3/.admin-token\n          test -s /var/lib/influxdb3/.admin-token || { echo \"[bootstrap] FAILED: token file empty or missing\"; exit 1; }\n          sed -n 's/.*\"token\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p' \\\n            /var/lib/influxdb3/.admin-token | head -n1 \\\n            > /var/lib/influxdb3/.admin-token-plain\n          chmod 600 /var/lib/influxdb3/.admin-token /var/lib/influxdb3/.admin-token-plain\n          echo \"[bootstrap] admin token written\"\n      restart: \"no\"\n\n    core:\n      image: influxdb:3-core\n      container_name: influxdb3-core\n      pull_policy: always\n      depends_on:\n        token-bootstrap:\n          condition: service_completed_successfully\n      environment:\n        INFLUXDB3_OBJECT_STORE: file\n        INFLUXDB3_DATA_DIR: /var/lib/influxdb3\n      command: >\n        serve\n          --node-id node0\n          --object-store file\n          --data-dir /var/lib/influxdb3\n          --admin-token-file /var/lib/influxdb3/.admin-token\n      ports:\n        - \"${CORE_PORT:-8181}:8181\"\n      volumes:\n        - ./influxdb-data:/var/lib/influxdb3\n      restart: unless-stopped\n      healthcheck:\n        test: [\"CMD-SHELL\", \"test -s /var/lib/influxdb3/.admin-token-plain && curl -sf --max-time 2 -H \\\"Authorization: Bearer $$(cat /var/lib/influxdb3/.admin-token-plain)\\\" http://127.0.0.1:8181/health -o /dev/null\"]\n        interval: 10s\n        timeout: 5s\n        retries: 6\n        start_period: 15s\n\n    explorer:\n      image: influxdata/influxdb3-ui:1.9.0\n      container_name: influxdb3-explorer\n      pull_policy: always\n      command: [\"--mode=admin\"]\n      ports:\n        - \"${EXPLORER_PORT:-8888}:8080\"\n      volumes:\n        - ./db:/db:rw\n        - ./config:/app-root/config:ro\n        - ./ssl:/etc/nginx/ssl:ro\n      environment:\n        SESSION_SECRET_KEY: ${SESSION_SECRET_KEY:-changeme123456789012345678901234}\n      restart: unless-stopped\n      depends_on:\n        core:\n          condition: service_healthy\n\n  # Explorer session dir (bind mount) uses UID 1500:\n  #   mkdir -p db config ssl\n  #   sudo chown -R 1500:1500 db\n  \n\n```\n\nและอย่าลืมต้อง chmod influxdb-data ด้วยงั้นจะ error permission\n\nเมื่อตั้งค่า permissionเสร็จสิ้นเราจะสามารถโอนไฟล์ง่ายๆ เพราะเรา ได้ bind /var/lib/influxdb3/node0 กับ ./influxdb-data ไว้แล้ว\nทำให้ scp -r /var/lib/influxdb3/node0 name@192.168.1.endpoint:home/user/influx_dir/influxdb-data/node0 ได้โดยง่าย\n\nแล้วเข้าไปที่ 192.169.1.endpoint:8080 เพื่อ set auth ดู token จาก ./influxdb-data/.admin-token \n\n![Screenshot 2569-09-27 at 21.25.31](https://drive.google.com/thumbnail?id=1Cr2gi4AESXPxgSquxYDGQht04yFmmQ_p&sz=w1000)\n\nพอ set adminเาร็จเราจะสามารถ query check ได้แล้ว\n\n```sql\nSELECT * FROM iox.something\n WHERE time >= '2026-09-11T00:00:00Z' AND time < '2026-09-12T00:00:00Z' \n ORDER BY time DESC LIMIT 100;\n```\n\nพอ query เสร็จเห็นข้อมูลคือว่าเสร็จสิ้นส่วนของ InfluxDB3\nต่อมาคือการ set service rust จาก endpoint ตัวเก่ามาตัวใหม่ครับ \n\nBonus\n\nสิ่งที่เจอคือ เวลาใช้  vm ถ้าเลือก  CPU ผิดอาจจะทำให้ Instruction / CPU Feature บางตัวไม่ได้ ทำให้service บ้างตัวที่ใช้ไม่ทำงาน เช่นตอน create token  จะ err 132 ตลอดเลย \nแก้โดยเปลี่ยน CPU เป็น host\n\n![Screenshot 2569-09-27 at 21.26.14](https://drive.google.com/thumbnail?id=1t6SVsRITSEiWp0fZ31I8zvz7CRMNgcVS&sz=w1000)\n\n[rust insert influx by mqtt](https://github.com/BMyTreee/mqqt_influx_esp)\n\n\n\n\n\n","status":"published","created_at":"2026-09-27T14:20:22.04742+00:00","updated_at":"2026-09-27T14:20:22.04742+00:00","synced_at":"2026-09-27T14:20:22.04742+00:00"},{"id":75,"title":"ESP32-C3-LCDkit","slug":"esp32-c3-lcdkit","body":"---\ntags:\n  - spec_hardware\n---\n[esp32-c3-lcdkit-](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32c3/esp32-c3-lcdkit/user_guide.html)\n*[click now ](https://s.shopee.co.th/19n5naSdH)\n![esp32-c3-lcdkit-block-diagram](https://drive.google.com/thumbnail?id=1jANR3gYk7d4I-QJZjh2MOJ4ul9of6_VC&sz=w1000)","status":"published","created_at":"2026-09-25T04:38:52.216009+00:00","updated_at":"2026-09-25T04:38:52.216009+00:00","synced_at":"2026-09-25T04:38:52.216009+00:00"},{"id":74,"title":"ESP32-C3-DevKit-RUST-2","slug":"esp32-c3-devkit-rust-2","body":"---\ntags:\n  - spec_hardware\n---\n[ESP32-C3-DevKit-RUST-2](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32c3/esp32-c3-devkit-rust-2/user_guide.html)\n\n*[click now ](https://s.shopee.co.th/19n5naSdH)\n\n![esp32-c3-devkit-rust-2-pinlayout](https://drive.google.com/thumbnail?id=1LXUqKfGE_YBj6cxpDfgn39RiRUK721J5&sz=w1000)","status":"published","created_at":"2026-09-25T04:35:36.768975+00:00","updated_at":"2026-09-25T04:35:36.768975+00:00","synced_at":"2026-09-25T04:35:36.768975+00:00"},{"id":73,"title":"basic_stdio","slug":"basic_stdio","body":"---\ntags:\n  - zero-to-hero\n---\n\n[กำลังหา link shopee มาแปะ งงมากทำไม iphone 15 ขายดี](https://s.shopee.co.th/3qNEMHQfSR)\n\n\nวันนี้นึกขรึมว่าจะลองเขียนพวก if macth ดู แต่ลองเขียนไปเขียนมาสนใจ std IO เฉยๆเลย\n\nจะเขียนเก็บไว้เพื่อแชร์ให้เรานักเดินทางหลายได้มาเสพกัน \n\n![Dance](https://drive.google.com/thumbnail?id=19Csd02rKvZ8OUYZxtKUSYD5L9d6wAAs4&sz=w1000)\n\nแต่ตอนแรกเริ่มลองดูว่า เราจะเขียน err handle คล้ายๆ Go ใน rust ได้ไหม \n\n( ถามว่ามีประโยชน์อะไรไหม ไม่รู้แต่อยากลอง XD )\n\n```rust\n\n    if let Some(err) = stdout()\n        .write(format!(\"your typing is  \\n\").as_bytes())\n        .err()\n    {\n        panic!(\"something when wrong {err}\")\n    }\n```\n\nหน้าตาก็ประมาณนี้ แต่ก็ดีตรงมันไม่ nil ข้างในแล้ว ระเบิดนอกจากคนไปใช้พวก panic เองก็อีกเรื่องนึง\n\n\n![Untitled 23_202609192235_01186](https://drive.google.com/thumbnail?id=12y1mRvzIvPzeni9FEV9IaXg88AkGmbH8&sz=w1000)\n\nงั้นเข้ามาเริ่มถ้ายากดีกว่า คนอื่นเริ่มศึกษาจะเริ่มจาก helloworld กันใช่ไหมละ \n\nงั้นเรา เริ่มจาก พิมพ์อะไรก็ได้เลน เดียว ไม่ใช่พิมพ์  “ asdsgewg “ หมายถึงพิมพ์ใช้โปรแกรมใน \n\nterminal ดำๆ นั้นต่างหาก\n\nโดย เราจะ เริ่ม เปิด โปรแกรม → พิมพ์เข้ามา → computer บอกเราว่าเราพิมพ์ อะไร → check จบ task\n\n![Untitled 24_202609192240_49342](https://drive.google.com/thumbnail?id=1D5SzSNAhMLwgvIwLSEzL0IuNtm62yuYS&sz=w1000)\n\n```rust\nuse std::io::{Write, stdin, stdout};\n\nfn main() {\n    loop {\n        let mut text_input = String::new();\n        // read_line จะ ค่าจนถึง /n แล้วก็จะถือว่าจบ\n        stdin()\n            .read_line(&mut text_input)\n            .err()\n            .map(|err| panic!(\"Stdin err is {err:#?}\"));\n\t\t\t\t\n\t\t\t\t// แล้วเอาถ้านั้นมา แปลงร่างในกล่องของ stdout ก่อนเตรียมส่งออกไป\n        stdout()\n            .write(format!(\"your typing is {text_input:#?} \\n\").as_bytes())\n            .err()\n            .map(|err| panic!(\"Stdout err is {err:#?}\"));\n\n\t\t\t\t// ยิ่งมันออกไป\n        stdout()\n            .flush()\n            .err()\n            .map(|err| panic!(\"Flush err is {err:#?}\"));\n\t\t\t\t// ถึงตรง หลายคนบอกทำไมไม่ใช้ print แทน stdout จริงๆแทนไ้ดแต่ไม่เท่ห์\n        println!(\"done\");\n    }\n}\n\n// ถ้าให้ clean  สร้าง function run ไว้ก็ได้ให้\n// fn run -> resulte<(),err>\n// แล้ว run.map err จะได้ใช้ feature ? ของ ภาษาได้\n```\n\nของแถม\nตอนนั่งเล่น ก็ลองฝึก debug ของ zed ไปด้วย ก็เจอ พฤติกรรมของ String คล้าย Hashmap อย่างนึงคือ\n\nตอน Allocate Memory ตอนแรก String เปล่า จะได้ ความยาว 0 และความจุ\n\n( String { prt , len:0 , capacity:0} )  \nแต่พอมีคนมา Allocate แล้ว เช่น ใส่ H ไปหนึ่งตัว  len:1 capacity:8 เหมือนจองไว้ให้เผื่อเลย\n\nความรู้สึกเหมือนตอนอ่าน Grokking Algorithmes ช่วงที่ HashMap มีการ Deallocate บ่อยๆ ถ้าถึงจุดนึง\nมันจะ double size แล้วมันจะช้าลง\n","status":"published","created_at":"2026-09-19T15:50:07.308593+00:00","updated_at":"2026-09-19T15:50:07.308593+00:00","synced_at":"2026-09-19T15:50:07.308593+00:00"},{"id":72,"title":"sqlx","slug":"sqlx","body":"---\ntags:\n  - database\n---\n\n[powerbank น่าสนใจ1](https://s.shopee.co.th/8V8uE43HYQ)\n[powerbank น่าสนใจ2](https://s.shopee.co.th/9V1RPvGo8X)\nสวัสดีเหล่านักเดินงาน วันนี้เรากลับมาค้นหาในเรื่องที่ติดไว้นานแล้วนั้นก็คือ sqlx \nมันคือ lib ของ rust ที่ใช้ในการจัดการกับ database ที่เป็น raw query style\n\n![lildimsqlx](https://drive.google.com/thumbnail?id=1jHFp1mdLDIpsMHEs20fWlAa3usAIDLNR&sz=w1000)\n\nแล้วตัวนี้มันดีกว่าตัวอื่นอย่างไร ถ้าโดยรวมไม่ต่างจากตัวอื่นมากเท่าไหร่\nเพราะตัวที่คล้ายกัน เช่น toasty  หรือ SeaORM ที่เป็นประเภท async fn ก็ทำงานคล้ายๆ กัน\nแต่ มีสิ่งนึงที่น่าสนใจคือใน SQLx คือ **Compile-time checked queries**\n\n```rust\nsqlx::query_as!( User, r#\" \n                           SELECT id, username, role \n                           FROM users WHERE id = $1 \n                        \"#\n                           , user_id )\n```\n\nSQLx สามารถช่วยตรวจสอบตั้งแต่ตอน compile ว่า\n\n- SQL query เขียนถูกต้องไหม\n- column ที่ query ออกมาตรงกับ struct หรือเปล่า\n- type ที่ส่งเข้า query ตรงกับที่ database ต้องการไหม\n- type ที่ได้กลับมาตรงกับ Rust type หรือไม่\n\nซึ่งจริง ๆ แล้วมันไม่ได้ทำให้ SQLx \"ดีกว่าทุกตัว\"\n\nแต่ส่วนตัวผมชอบแนวคิดนี้มากกว่า \n\nและนี่แหละที่ทำให้เกิดปัญหาเพิ่มขึ้นมาตอนทำ CI/CD\n\nเพราะถ้า SQLx ต้องตรวจ query กับ Database ตอน compile แล้ว CI ของเราจะทำอย่างไร ถ้าไม่มี Database ให้ connect?\n\nแนวทางที่ SQLx แนะนำคือใช้ **SQLx CLI** เพื่อ prepare query และสร้างไฟล์ `.sqlx`\n\nไฟล์นี้จะเก็บ metadata ของ query เอาไว้ ทำให้ตอน compile แบบ offline ไม่จำเป็นต้อง connect Database จริง\n\nประมาณว่า\n\n> \"ตอนนี้ Database ไม่อยู่ก็ไม่เป็นไร เราจำ schema ของ query นี้ไว้แล้ว\"\n> \n\nแต่ก็มีอีกเรื่องที่ต้องระวัง\n\nถ้า code ของเราไม่ได้จัดการ migration ของ Database แล้ว schema ใน Database กับ metadata ใน `.sqlx` ไม่ตรงกัน งานก็มีสิทธิ์แตกได้เหมือนกัน 💀\n\nถ้าใช้ migration ของ SQLx ก็สามารถเขียนประมาณนี้\n\n```rust\n  sqlx::migrate!(\"./migrations\").run(&pool).await?;\n```\n\nและถ้า build ใน Docker แบบ offline ก็อย่าลืมเอา `.sqlx` เข้าไปใน image ด้วย รวมถึงตั้งค่าให้ SQLx compile แบบ offline\n\n```\nCOPY .sqlx .sqlx\n\nENV SQLX_OFFLINE=true\n```\n\nนอกจากเรื่องนี้แล้ว feature หลัก ๆ ที่ควรมีก็มีครบพอสมควร เช่น\n\n- Connection Pool\n- Transaction\n- Prepared Statement\n- Async database access\n- PostgreSQL / MySQL / SQLite และ database อื่น ๆ\n- Compile-time checked query\n- Offline query metadata\n- Custom type mapping\n- PostgreSQL enum\n\nส่วนตัวปกติใช้แต่ Postgres แล้วชอบใช้ map enum เข้าไปได้เลย\n\nตัวอย่างเช่น\n\n```rust\nstruct SomeTable {\n    col1: String,\n    col2: i32,\n    type_col: TypeInPostgres,\n}\n\n#[derive(Debug, sqlx::Type)]\n#[sqlx(type_name = \"type_in_postgres\", rename_all = \"lowercase\")]\nenum TypeInPostgres {\n    Kind1,\n    Kind2,\n}\n\nlet user = sqlx::query_as!(\n    SomeTable,\n    r#\"\n        SELECT\n            col1,\n            col2,\n            type_in_postgres AS \"type_in_postgres: TypeInPostgres\"\n        FROM sometable\n        WHERE id = $1\n    \"#,\n    col1_id\n)\n.fetch_one(&pool)\n.await?;\n```\n\nตรงนี้คือสิ่งที่รู้สึกว่า Rust + SQLx มันเข้ากันดีมาก\n\nเขียน SQL เอง แต่ยังได้ประโยชน์จาก type checking ของ Rust ไปด้วย\n\nนักเดินทางคนไหนใช้ crate rust ตัวไหนในการจัดการ db บ้างมาแชร์กันได้เลย\n\nขอตัวไปจัดการกับ ESP board แล้วบ๊าย~","status":"published","created_at":"2026-09-13T13:56:30.655181+00:00","updated_at":"2026-09-13T13:56:30.655181+00:00","synced_at":"2026-09-13T13:56:30.655181+00:00"},{"id":71,"title":"influx3-rs","slug":"influx3-rs","body":"---\ntags:\n  - database\n---\n[influx official](https://docs.influxdata.com/influxdb3/enterprise/reference/cli/influxdb3/create/)\n\n[Buy dell Optiplex noww](https://s.shopee.co.th/W6RX6wH2k)\n\nวันก่อนผมไปเอา **ทองแดงถัก (desoldering braid)** มาลองดูดตะกั่ว แต่ปรากฏว่าไม่เวิร์ก 😂\n\nสุดท้าย **Ground ที่มีอยู่จุดเดียวบนบอร์ดพังไปเลย**\n\nแต่กลับมาเรื่องของวันนี้กันดีกว่า\n\nก่อนหน้านี้ผมได้ลองเอา **Sensor** ส่งข้อมูลเข้า Server ที่รันอยู่บน **Proxmox** แล้วก็ให้ AI ช่วยจัดการข้อมูลแบบ vibe code ไปเลย จากนั้นดู Data ว่าในสวนของเราช่วงไหนมีสภาพอากาศและอุณหภูมิเป็นอย่างไรบ้าง\n\nตอนเริ่มทำ ผมก็สงสัยว่า\n\n> **ทำไมเราไม่ใช้ General-purpose Database อย่าง PostgreSQL ไปเลย?**\n> \n\nก็เลยลองทั้ง **PostgreSQL** และ **InfluxDB 3**\n\nแต่พอลอง Query ข้อมูลออกมาจริง ๆ โดยเฉพาะข้อมูลที่เป็น Time Series จำนวนเยอะ ๆ ก็รู้สึกว่า **InfluxDB3 ทำงานได้รวดเร็วกว่าเยอะเลย**\n\nวันนี้เรามาดูกันดีกว่า **InfluxDB3 คืออะไร และมันต่างจาก Database ทั่วไปอย่างไร**\n\n---\n\n\n![lidm_flux](https://drive.google.com/thumbnail?id=1ImiTMouNPDnEmmrhihAWSt726sMyV5K9&sz=w1000)\n\n## InfluxDB3 คืออะไร?\n\n**InfluxDB3 คือ Database ที่ออกแบบมาโดยมี Time Series เป็นงานหลัก**\n\nแนวคิดหลักคือ\n\n> **ถ้าข้อมูลถูกเขียนเข้ามาเป็นล้าน ๆ จุดตามเวลา เราจะเก็บและ Query มันอย่างไรให้ Efficient?**\n> \n\nอย่างข้อมูลจาก Sensor ถ้าเราเก็บทุก ๆ 1 วินาที\n\nSensor 1 ตัวก็สามารถสร้างข้อมูลได้ถึง **86,400 จุดต่อวัน**\n\nถ้ามี Sensor หลายตัว และเก็บข้อมูลต่อเนื่องเป็นเดือนหรือเป็นปี จำนวนข้อมูลก็จะเพิ่มขึ้นอย่างรวดเร็ว\n\nและเวลาที่เราต้องการวิเคราะห์ข้อมูล เราอาจไม่ได้ต้องการข้อมูลทุกจุดแบบ Raw Data เสมอไป\n\nเช่น\n\n- อุณหภูมิเฉลี่ยในแต่ละชั่วโมง\n- อุณหภูมิสูงสุดของแต่ละวัน\n- ความชื้นในช่วงเวลา 08:00–12:00\n- เปรียบเทียบอุณหภูมิของแต่ละวัน\n- ดู Trend ของข้อมูลย้อนหลังหลายเดือน\n\nนี่คือปัญหาที่ Time Series Database ถูกออกแบบมาเพื่อจัดการโดยเฉพาะ\n\n---\n\n## InfluxDB3 ทำงานอย่างไร?\n\nถ้ามองแบบง่าย ๆ InfluxDB3 มี Flow หลัก ๆ อยู่ 2 ส่วนคือ\n\n**Write และ Read**\n\nเวลามีข้อมูลเข้ามา ระบบจะรับข้อมูลและจัดการผ่าน Storage Engine ของมัน ก่อนที่จะจัดเก็บข้อมูลในรูปแบบที่เหมาะกับการ Query และวิเคราะห์ข้อมูลจำนวนมาก\n\nหนึ่งในจุดสำคัญของ InfluxDB3 คือการใช้ **Apache Parquet** เป็นรูปแบบข้อมูลหลักสำหรับการจัดเก็บ\n\nแล้วทำไมต้องเป็น Parquet?\n\nเพราะ Parquet เป็น **Columnar Storage Format**\n\nแทนที่จะเก็บข้อมูลเป็นแถวทั้งหมด ระบบสามารถอ่านเฉพาะ Column ที่เราต้องการได้\n\nสมมติเรามีข้อมูล\n\n```\ntime\ntemperature\nhumidity\npressure\nlight\n```\n\nแต่ Query ของเราต้องการแค่\n\n```sql\nSELECT time, temperature\n```\n\nระบบก็สามารถทำงานกับเฉพาะข้อมูลที่เกี่ยวข้องได้ โดยไม่จำเป็นต้องอ่านทุก Column\n\nอีกอย่างคือข้อมูลสามารถถูกจัดแบ่งออกเป็นหลายไฟล์ตามช่วงของข้อมูล ทำให้ตอน Query ระบบสามารถ **ข้ามข้อมูลหรือไฟล์ที่ไม่เกี่ยวข้องกับช่วงเวลาที่เราต้องการ** ได้\n\nตรงนี้สำคัญมากสำหรับ Time Series ที่มีข้อมูลจำนวนมากๆได้ดี\n\n---\n\n# แล้วใน Rust ใช้อย่างไร?\n\nสำหรับ Rust เราสามารถใช้ Crate:\n\ninfluxdb3 บน crates.io\n\nตัว Client จะใช้สำหรับสื่อสารกับ InfluxDB 3 ผ่าน HTTP และเราสามารถกำหนด Token เพื่อใช้ Authentication ได้\n\n## สร้าง Client\n\n```rust\nlet client = InfluxDbClientBuilder::new()\n    .server_endpoint(\"http://localhost:8181\")\n    .token(\"<a-very-long-token>\")\n    .database(\"your-database\")\n    .build()\n    .expect(\"client builder: endpoint/token/database all set\");\n```\n\nจากนั้นเราก็สามารถใช้ `client` สำหรับเขียนและ Query ข้อมูลได้\n\n---\n\n# เขียนข้อมูล\n\nเราสามารถสร้าง Data Point แล้วส่งเข้า InfluxDB3 ได้ เช่น\n\n```rust\nlet data_point = DataPointBuilder::new()\n    .table(\"sensor\")\n    .tag(\"province\", \"bkk\")\n    .tag(\"country\", \"thailand\")\n    .field(\"temperature\", FieldDataType::Float(40.00))\n    .field(\"humidity\", FieldDataType::Integer(51))\n    .datetime(Utc::now())\n    .build()\n    .expect(\"failed to build data point\");\n\nmatch client.write_one(data_point).await {\n    Ok(cluster_uuid_opt) => {\n        println!(\n            \"Writing successful: cluster_uuid = {:?}\",\n            cluster_uuid_opt\n        );\n    }\n    Err(error_detail) => {\n        println!(\"Failure: {:?}\", error_detail);\n    }\n}\n```\n\nตรงนี้เราจะเห็น Concept สำคัญของ InfluxDB คือ\n\n- **Table** → กลุ่มข้อมูล\n- **Tag** → ข้อมูลที่ใช้สำหรับระบุ/Filter\n- **Field** → ค่าที่ต้องการเก็บ\n- **Time** → เวลาของข้อมูล\n\nสำหรับ Sensor ก็จะเหมาะมาก เช่น\n\n```\nTable: sensor\n\nTags:\n  location = garden\n  device = esp32-01\n\nFields:\n  temperature = 40.0\n  humidity = 51\n\nTime:\n  2026-09-06 20:00:00\n```\n\n---\n\n# แล้วต้อง `CREATE TABLE` ก่อนหรือเปล่า?\n\nตรงนี้เป็นอีกจุดที่ **InfluxDB 3 ต่างจาก PostgreSQL**\n\nโดยทั่วไปเรา **ไม่จำเป็นต้อง `CREATE TABLE` ก่อนเขียนข้อมูล**\n\nเราเพียงระบุชื่อ Table ในตอน Write เช่น\n\n```rust\n.table(\"sensor\")\n```\n\nพร้อมกับส่งข้อมูลเข้าไป\n\nสมมติว่าใน Database ของเรายังไม่มี Table ชื่อ `sensor`\n\nเราส่ง Data Point เข้าไปแบบนี้\n\n```\ntime        = 2026-09-06T19:00:00Z\nsensor_id   = \"S01\"\ntemperature = 31.2\nhumidity    = 72\n```\n\nConceptually Flow จะประมาณนี้\n\n```\nWrite Data\n    │\n    ▼\ntable = \"sensor\"\n    │\n    ▼\nยังไม่มี table \"sensor\"\n    │\n    ▼\nInfluxDB3 สร้าง table/schema\n    │\n    ▼\nบันทึกข้อมูล\n```\n\nดังนั้นเราไม่จำเป็นต้องทำแบบ PostgreSQL ที่ต้องสร้าง Table ก่อน เช่น\n\n```sql\nCREATE TABLE sensor (\n    time TIMESTAMPTZ,\n    sensor_id TEXT,\n    temperature DOUBLE PRECISION,\n    humidity INTEGER\n);\n```\n\nแล้วค่อย\n\n```sql\nINSERT INTO sensor (...)\nVALUES (...);\n```\n\nใน InfluxDB3 เราสามารถเริ่มจากการ **Write Data เข้าไปเลย**\n\n```rust\nlet data_point = DataPointBuilder::new()\n    .table(\"sensor\")\n    .tag(\"sensor_id\", \"S01\")\n    .field(\"temperature\", FieldDataType::Float(31.2))\n    .field(\"humidity\", FieldDataType::Integer(72))\n    .datetime(Utc::now())\n    .build()\n    .expect(\"failed to build data point\");\n\nclient.write_one(data_point).await?;\n```\n\nครั้งแรกที่เขียน `sensor` เข้าไป ระบบจะจัดการ Table และ Schema ที่เกี่ยวข้องให้\n\nจากนั้นเมื่อ Sensor ส่งข้อมูลเข้ามาอีก\n\n```\nESP32\n  │\n  │ temperature = 31.5\n  │ humidity = 73\n  ▼\nInfluxDB 3\n  │\n  ▼\ntable = \"sensor\"\n  │\n  ▼\nมี table อยู่แล้ว\n  │\n  ▼\nเขียน data เพิ่ม\n```\n\n**ไม่ต้องสร้าง Table ใหม่ทุกครั้งที่ Sensor ส่งข้อมูล**\n\nนี่ทำให้ Workflow ของ Sensor ค่อนข้างง่ายมาก\n\n```\nSensor\n   │\n   ▼\nWrite Data\n   │\n   ▼\nTable \"sensor\"\n   │\n   ├── ไม่มี → สร้างให้\n   │\n   └── มีแล้ว → เขียนเพิ่ม\n```\n\nและที่สำคัญคือ เราไม่ต้องมานั่งสร้าง Table ใหม่ทุกครั้งที่เพิ่ม Sensor ตัวใหม่ด้วย เพียงออกแบบ Tag/Field ให้เหมาะกับข้อมูลที่เราต้องการเก็บ\n\n---\n\n# อ่านข้อมูล\n\nทีนี้เวลาต้องการ Query เราสามารถใช้ SQL ได้\n\n```rust\nmatch client\n    .query_raw(\n        ResponseFormat::Csv,\n        None,\n        \"SELECT time, temperature, humidity FROM sensor\",\n    )\n    .await\n{\n    Ok(raw_csv_response) => {\n        for row in raw_csv_response.lines() {\n            println!(\"{}\", row);\n        }\n    }\n    Err(error_detail) => {\n        println!(\"Failure: {:?}\", error_detail);\n    }\n}\n```\n\nจุดที่ผมชอบคือ **เราไม่จำเป็นต้องเรียน Query Language ใหม่ทั้งหมด**\n\nเพราะ InfluxDB 3 รองรับ **SQL** ทำให้สามารถ Query ข้อมูล Time Series ด้วย Syntax ที่คุ้นเคยได้\n\nเช่น\n\n```sql\nSELECT\n    time,\n    temperature,\n    humidity\nFROM sensor\nWHERE time >= now() - INTERVAL '1 day'\nORDER BY time;\n```\n\nหรือถ้าอยากดูค่าเฉลี่ยของอุณหภูมิเป็นช่วง ๆ ก็สามารถใช้ SQL เพื่อทำ Aggregation ได้\n\n---\n\n# แล้วถ้าเอาไปรันบน Docker จะต้องทำอย่างไร\n\nสำหรับการทดลองบน Server หรือ Home Lab ผมสามารถรัน InfluxDB3 ด้วย Docker Compose ได้\n\n```yaml\nservices:\n  influxdb3-core:\n    image: influxdb:3-core\n    ports:\n      - \"127.0.0.1:${INFLUX3_CORE_PORT:-8181}:8181\"\n\n    command:\n      [\n        influxdb3,\n        serve,\n        --node-id=node0,\n        --object-store=file,\n        --data-dir=/var/lib/influxdb3/data,\n        --admin-token-file=/secrets/admin-token.json,\n      ]\n\n    volumes:\n      - ./data/influxdb3:/var/lib/influxdb3/data\n      - ./secrets/admin-token.json:/secrets/admin-token.json:ro\n\n    environment:\n      INFLUX3_ADMIN_TOKEN: ${INFLUX3_ADMIN_TOKEN}\n\n    healthcheck:\n      test:\n        [\n          CMD-SHELL,\n          \"curl -fsS -H \\\"Authorization: Bearer $INFLUX3_ADMIN_TOKEN\\\" http://localhost:8181/health || exit 1\",\n        ]\n      interval: 5s\n      timeout: 3s\n      retries: 12\n\n    restart: unless-stopped\n\n  explorer:\n    image: influxdata/influxdb3-ui:1.9.0\n    ports:\n      - \"127.0.0.1:${EXPLORER_PORT:-8888}:8080\"\n\n    environment:\n      SESSION_SECRET_KEY: ${EXPLORER_SESSION_SECRET_KEY}\n\n    volumes:\n      - ./config:/app-root/config:ro\n      - ./db:/db\n\n    depends_on:\n      influxdb3-core:\n        condition: service_healthy\n\n    restart: unless-stopped\n```\n\nแล้วเราก็สามารถเอา\n\n```\nESP32\n  │\n  │ Sensor Data\n  ▼\nServer / Proxmox\n  │\n  ▼\nInfluxDB 3\n  │\n  ├── Raw Time Series Data\n  │\n  └── SQL Query\n          │\n          ▼\n       Grafana\n          │\n          ▼\n   วิเคราะห์สภาพสวน\n```\n\nมาต่อกันได้\n\nและนี่แหละคือเหตุผลที่ผมเริ่มสนใจว่า\n\n> **ถ้าเรามี Sensor ที่ยิงข้อมูลทุกวินาทีเป็นเดือน ๆ หรือเป็นปี เราจะออกแบบ Database อย่างไรให้ Query ข้อมูลพวกนี้ได้เร็ว และไม่ต้องอ่านข้อมูลที่ไม่จำเป็นขึ้นมาทั้งหมด?**\n> \n\nตรงนี้แหละที่ทำให้ **Time Series Database อย่าง InfluxDB3** น่าสนใจมากกว่าเอา General-purpose Database มาใช้แบบตรง ๆ","status":"published","created_at":"2026-09-06T13:59:46.318652+00:00","updated_at":"2026-09-06T13:59:46.318652+00:00","synced_at":"2026-09-06T13:59:46.318652+00:00"},{"id":68,"title":"no_std","slug":"no_std","body":"---\ntags:\n  - eps-rs\n---\n\n[prepare your head in natural](https://s.shopee.co.th/1qbdeRMbLV)\n\n[The Embedonomicon](https://docs.rust-embedded.org/embedonomicon/smallest-no-std.html)\nสวัสดีนักเดินทางทุกท่าน วันนี้เราย้อนกลับทางเดิมกันนิดหน่อย ตอนแรกก็คิดอยู่ว่า จะไปทางไหนดี\nเลี้ยวซ้ายไปก็ FreeRTOS เลี้ยวขวาไปก็ Hardware Abstraction Layer\n\nก็เลยคิดว่า...\n\n**หันหลังกลับดีกว่า** แล้วเดินกลับมาที่ #![no_std] #![no_main]\n\n![esp32-ep13](https://drive.google.com/thumbnail?id=1cS83WAXyv2Swf01TKzr_ON2YFBBYmpSE&sz=w1000)\n\n\nแล้วทำไมเราถึงต้องเดินมาทางนี้ เวลาเราเขียน Rust แบบปกติ เรามักจะใช้ `std` หรือ Standard Library ซึ่งช่วยจัดการเรื่องต่าง ๆ ให้เราเยอะมาก ไม่ว่าจะเป็นการทำงานกับระบบปฏิบัติการ การจัดการไฟล์ network หรือสิ่งอำนวยความสะดวกต่าง ๆ\n\nและโปรแกรม Rust ปกติก็มักจะมี fn main() เป็น entry point ของโปรแกรม\n\nแต่พอเราเดินเข้ามาในโลกของ **Microcontroller Unit ( MCU )** สิ่งต่าง ๆ ก็เริ่มไม่เหมือนเดิม\n\nMCU ส่วนใหญ่ไม่มี Operating System แบบที่เราเจอบน PC\n\nไม่มี filesystem ให้ใช้แบบปกติ ไม่มี processไม่มี environment แบบที่ `std` คาดหวัง\n\nดังนั้น embedded Rust จึงมักจะใช้ #![no_std]\n\nซึ่งเป็นการบอก compiler ว่า\n\n> \"โปรแกรมนี้ไม่ต้องใช้ Rust Standard Library\"\n> \n\nแต่ไม่ได้หมายความว่า Rust หายไปทั้งหมดนะครับ\n\nเพราะเรายังสามารถใช้ **Rust Core Library (`core`)** ได้อยู่\n\nเช่น primitive types, `Option`, `Result`, iterator บางส่วน รวมถึง functionality พื้นฐานอีกมากมาย\n\nทีนี้ปัญหาต่อมาก็คือ\n\nถ้าเราไม่ใช้ environment แบบโปรแกรม Rust ปกติ แล้ว `main()` จะไปอยู่ตรงไหน?\n\nตรงนี้เองที่เรามักจะเห็น #![no_main]\n\nเพราะ embedded program ไม่ได้เริ่มต้นจาก `main()` แบบเดียวกับ application บน OS\n\nตัว MCU จะเริ่มต้นจาก startup code / reset handler และ framework หรือ runtime ที่เราใช้จะเป็นตัวกำหนด entry point ที่เหมาะสมให้เรา\n\nแต่พอมาถึงตรงนี้ก็มีปัญหาใหม่\n\n**แล้วถ้าโปรแกรม `panic!` ล่ะ?**\n\nเวลา Rust เกิด panic มันต้องมีวิธีจัดการกับ panic\n\nโดยเฉพาะในเรื่องของ **panic strategy ตรงนี้ตรงหมอบตามตรง ไม่สันทัดเท่าไหร่\nแต่ที่อ่านแล้วรู้ก็คือ จะมี 2 แบบ**\n\n- `unwind`\n- `abort`\n\nในโปรแกรมทั่วไป เราสามารถใช้ **stack unwinding** ได้\n\nเมื่อเกิด panic ระบบสามารถเดินย้อนกลับผ่าน stack เพื่อทำ cleanup ของสิ่งต่าง ๆ ก่อนที่จะจบการทำงาน\n\nแต่ใน embedded system เรามักไม่ต้องการสิ่งนี้\n\nเพราะการทำ stack unwinding ต้องมี runtime support และมี overhead เพิ่มขึ้น ทั้งในด้าน code size และ memory\n\nและ MCU หลาย ๆ environment ก็ไม่ได้มี runtime infrastructure ที่จำเป็นสำหรับการทำ unwinding แบบ application ทั่วไป\n\nดังนั้น embedded Rust จึงมักเลือก\n\n```toml\n[profile.release]\npanic = \"abort\"\n```\n\n> \"ถ้าเกิด panic ก็ไม่ต้องพยายาม unwind stack\n> \n\nและใน `no_std` เราต้องกำหนด **panic handler** ว่าถ้า panic ระบบของเราจะตอบสนองอย่างไร\n\nตัวอย่างเช่น\n\n```rust\n#[panic_handler]\nfn panic(_info: &PanicInfo) -> ! {\n    loop {}\n}\n```\n\nเมื่อเกิด panic ก็เข้ามาที่ handler ตัวนี้\n\nจากนั้นเราจะเลือกได้ว่าอยากให้ system ทำอะไรต่อ\n\nเช่น\n\n- หยุดอยู่ตรงนั้น\n- loop ไปเรื่อย ๆ\n- ส่ง log ออกมา\n- ทำ hardware safe state\n- หรือสั่ง MCU reset\n\nแล้วถ้าไหนลองเดินตามทางก่อนหน้านี้ที่เราลองสำรวจกันไป\nทำไมพวก framework esp ไม่เห็นมี เห็น panic strategy เลย\nเพราะเขาทำมาให้แล้วไงละ ใครอยากรู้ ลอง setup framwork\nแล้ว run \n\n```jsx\n   cargo rustc --bin YOURPROJECT -- --print cfg | grep panic\n```\n\nแล้วจะเห็น Building panic=\"abort” ออกมาครับ\nสายแล้วยังไม่ได้เอาบัดกรีออกเลยไปแล้ว บาย","status":"published","created_at":"2026-08-30T09:05:13.837761+00:00","updated_at":"2026-08-30T09:05:13.837761+00:00","synced_at":"2026-08-30T09:05:13.837761+00:00"},{"id":67,"title":"lcd1602a_barpower","slug":"lcd1602a_barpower","body":"---\ntags:\n  - eps-rs\n---\n\n*[click now ](https://s.shopee.co.th/19n5naSdH)\n*[git](https://github.com/lilidm-lab/lcd1602a_used/blob/main/src/bin/main.rs)\n\nสวัสดีนักเดินทางทุกท่าน ก่อนหน้านี้ไป วุ่นกับการรถติดกล้องวิ่งในบ้านมา\nสุดท้ายติดปัญหาเรื่องการทำโครงกับวางบอร์ด เลยพักไว้ก่อน\nแผนคือจะวื้อ อคิริคมาตัดเอง ดูว่าจะรอดไหม\n\nวันนี้เรามาลองอะไรสนุกๆกัน \n\nนั้นก็คือ สร้าง แถบพลัง โดยใช้  จอเขียว lcd เจ้าเก่า กับ ปุ่มหมุน potentiometer กัน\nสงสัยกันไหม ทำไมต้องเรียก potentiomenter เพราะผมท่องมายังไงละ 555\n\nเลยไปคุยกับ GLM bros ว่า เขียน driver i2c lcd1602a ให้หน่อย คือรหัสของตัวมันนั้นแหละ\n\n![c9e18d9d-eb86-4e58-9c47-1d5cabf3e58b](https://drive.google.com/thumbnail?id=1dpyiqV5Glgpxxtq421XpdvZ8NDfLNV4G&sz=w1000)\n\n\nเวลาใช้ lcd ตัวนั้น อารมณ์ คล้ายๆ เวลาเราใช้ CPU เลย\n\nแบบ\n\ni2c  [ method  write ]   ⇒ [ R ] ⇒[ U ] ⇒[ S ] = [ T ] ⇒ [ STOP ]\n\nคือต้องส่งคำสั่งไป แล้วทำอะไร ก็อีกทีนึง หลายๆ  lib ก็อำนวยตรงนี้ให้แล้ว\nแต่ไม่แน่ใจใน esp-rs ที่เป็น no-std มีไหม แต่ GLM รับจบ\n\nต่อมาคือ ต้อง รับ anclog จาก ปุ่มหมุนมา เราก็ให้ GLM bros ทำlib ให้แต่ จริงๆ impl เองก็ได้\nเพราะส่วน analog ไม่ค่อยมีอะไร บอก แค่ เช็คกระแส  0 - 3.3 v เฉยๆ  จากอุปกรณ์ ที่ส่งมา\n\nจากนั้นเราก็ไป วาดลงใน driver lcd ที่ bros เราจัดไว้ให้\n\n![Untitled](https://drive.google.com/thumbnail?id=12_OqNd2Juyd02AZROGurZJKFlF13tnUC&sz=w1000)\n\n```jsx\n\nfn draw_bar(lcd: &mut Lcd, percent: u8) {\n    lcd.set_cursor(ROW_BAR, 0).unwrap();\n\n    let filled = percent as u32 * BAR_SEGMENTS / PERCENT_MAX;\n    let full_cells = filled / GLYPH_COLUMNS as u32;\n    let partial = (filled % GLYPH_COLUMNS as u32) as u8;\n    let mut used = full_cells;\n\n    for _ in 0..full_cells {\n        lcd.write_char(bar_char(GLYPH_COLUMNS)).unwrap();\n    }\n    if partial > 0 {\n        lcd.write_char(bar_char(partial)).unwrap();\n        used += 1;\n    }\n    for _ in used..BAR_CELLS {\n        lcd.write_char(' ').unwrap();\n    }\n}\n```\n\nทีนี้เวลา loop แสดงผล ก็ได้ แท่งพลัง หรรษา มาแล้ว\n\nhttps://github.com/lilidm-lab/lcd1602a_used\n\nแต่ก็เจอปัญหาหลายอย่างเหมือนกัน.\n\n1.  lcd1602a ถ้าต่อ i2c  มาให้ addr จะมี 3 ตัวทำไม่ได้แล้วเหมือนกัน แต่ใส่ไม่ถูกจะ error\n2. พวก settup ของ rust มันจะเป็น file เช่น toolchain .cargo ที่ pre script แบบ  yarn dev run อะไรแบบนั้น ไว้ แล้ว มันจะมี ต่อ prot probe กับ flash —monitor คือ 2 ตัวนี้ จะใช้ไม่เหมือนกัน\nเพราะ probe จำไม่ได้ ต่อผ่านอะไร จะเป็น  flash + monitor ในตัว จะดีกว่า flash —monitor ที่คุยผ่าน uart0 แต่ probe จะในบอร์ดใหม่ๆ สะมากกว่า แล้ว AI จะไปเมาหมัดนัวๆ แถวๆ นั้น XD\n3. แล้ว potentiometer ปุ่มหมุน ได้ค่าแปลกออกมาไม่รู้เสียไหม แต่ วัด r แล้วได้ 1.5 - 2.5 แต่ สุด 2.5 ตั้งแต่บิดยังไม่สุด wtf มากๆ\n\n\n\n\n\n\n","status":"published","created_at":"2026-08-22T15:11:16.083664+00:00","updated_at":"2026-08-22T15:11:16.083664+00:00","synced_at":"2026-08-22T15:11:16.083664+00:00"},{"id":66,"title":"quick sort","slug":"quick-sort","body":"---\ntags:\n  - RUST\n---\n\n[kino](https://thailand.kinokuniya.com/Grokking_Algorithms_(2ND)/bw/9781633438538?srsltid=AfmBOop12V_zOH0L1gmqGzitqIh_Tin-_xLOCVoFx6afIxsrFicgyeRZ)\n\n[shopee  ไม่เหมือนเล่มบนนะ แต่เป็นภาษาไทย](https://s.shopee.co.th/8AUwpUIDQO)\n\n*กลับมากันอีกแล้วในดองอ่านหนังสือ Grokking Algorithms* พอถึงวันว่างๆก็เริ่มอ่านต่อ จนไปเจอเรื่อง **Quick Sort พออ่านจบก็คันไม้คันมือว่าจะทำใน rust style อย่างไรดี**\n\nถ้าปกติเราต้องการเรียงข้อมูล เราสามารถใช้ method ที่มีมาให้ เช่น `arr.sort()` เพื่อเรียงจากน้อยไปมากหรือมากไปน้อย\n\nแต่ผมเกิดสงสัยว่า สมมติเรามีข้อมูล `[1, 2, 3, 4]` ระบบรู้ได้อย่างไรว่าเลขไหนมากที่สุดหรือน้อยที่สุด?\n\nสุดท้ายแล้ว เราก็ต้องนำค่าต่าง ๆ มาเปรียบเทียบกันอยู่ดี เช่น `1 < 2`, `2 < 3`, `3 < 4`\n\nจนทำให้สนใจในหลักการทำงาน Quick sort ว่ามีการทำงานอย่างไร\n\n\n![quick sort](https://drive.google.com/thumbnail?id=1q0N5FTAPW9eGtS0WRCGCWYYzM-5ognJW&sz=w1000)\n\n\nสมมติ ว่าเรามีค่า  [ 5 , 7 ,3 , 1 , 10 ]\n\nแล้วเวลาที่เราจะเรียงเรา จะนำตัวแรก มา เทียบกับ ตัวที่เหลือ แล้ว แบ่ง left , right ไปเรื่อยๆ\n\nเช่น\n\n5 compare [ 7 , 3 , 1 , 10 ]\n\nleft = [ 3 , 1  ]\nright = [ 7 , 10 ] \n\nแล้วก็ทำแบบนี้ไปเรื่อยๆ จน [ ] ที่ไปเช็คเหลือตัวเดียว\n\nพอเสร็จแล้วก็เอา ค่าทุกตัวมาต่อแถวกัน ก็ถือว่าจบ\n\nที่ฮาก็คือ ตอนทำเกี่ยวกับ array เราจะใช้ iter() ตอนนั้น ผมกำลังนั่งกินกาแฟอยู่\n\nแล้วลอง &mut [ i32 ] .into_iter() เพราะ ในใจคิดว่า ตอนใช้ into_iter() \nจะกลืนกิน ( consume ) &mut [ i32 ] แล้วได้ i32 มาใช้ใน  loop แต่ไม่ ผมกลับได้ &mut i32 ธรรมดามาครับผม\n\nไม่แน่ใจถูกต้องแค่ไหน ปกติจะจำว่า into_iter เป็นการส่ง  owner เข้าไป แต่ที่คิดไม่ถึงคือ\n\nถ้า เทียบ &mut [ i32 ] เป็น  ( - -) เป็นหน้าคน แล้วเวลาส่ง ( - -) เข้า into_iter หลังจาก \n( - -)  ใช้ที่อื่นไม่ได้แล้วอันนี้  OK แปลเวลาแกะ ( - -) ออก มันจะได้สิ่งที่คาดหวังว่าจะได้ i32\nแต่มันจะได้ &mut i32 มาแทน\n\n```jsx\narr: &mut [i32]\n       │\n       │ move\n       ▼\n    iterator\n       │\n       ├── &mut i32\n       ├── &mut i32\n       └── &mut i32\n\nตัว array จริง ๆ ยังอยู่\n[i32, i32, i32]\n```\n\nตอนแรกด่า complier อวดฉลาดไปแล้ว ขอโทษครับผมไม่เข้าใจเอง 5555 XD\n\n\n\n[github](https://github.com/lidm0707/play_algor/blob/main/examples/quick_sort.rs)","status":"published","created_at":"2026-08-15T13:29:23.310868+00:00","updated_at":"2026-08-15T13:29:23.310868+00:00","synced_at":"2026-08-15T13:29:23.310868+00:00"},{"id":65,"title":"setting_std_xtensa","slug":"setting_std_xtensa","body":"---\ntags:\n  - eps-rs\n  - esp_setup\n---\nสวัสดีเหล่านักเดินทางทุกคน ก่อนหน้านี้เราไปเล่น risc-v จะส่วนใหญ่ และเข้าได้ดีกับ rust มาก\nและเริ่ม project โดยไม่ต้อง ตั้งอะไรเยอะ\n\n#[esp32 s3](https://s.shopee.co.th/AKZeaxfjCB)\n\nวันนี้เรามาลองเริ่ม Project  esp32 s3 (Xtensa Devices) โดยใช้ std (standard libraries) กันนี้ดีกว่า\n\n\n\n![ep11](https://drive.google.com/thumbnail?id=1W8PfO5zE7moTiU0qRaMlxYKfJlhGwvC6&sz=w1000)\n\n\n\nก่อนอื่นเลย เราต้อง install หลายตัวหน่อย\n\n1. cargo install cargo-generate\n2. cargo generate esp-rs/esp-idf-template cargo\n3. rustup toolchain install 1.95.0 \nrustup toolchain list\n4. brew install llvm libuv (หรือ os อื่นๆลองหาดู)\n5. cargo install espup\nespup install\nsource $HOME/export-esp.sh\n\nทีนี้เราก็เตรียมตัวขั้นต้นเพื่อเข้าไปใน Project กันได้แล้ว\n\nมาดูกันดีกว่าเราใช้ IDE อะไร\n\n.zed/settings.json \n\n```jsx\n{\n  \"lsp\": {\n    \"rust-analyzer\": {\n      \"binary\": {\n        \"path\": \"~/.rustup/toolchains/1.95.0-aarch64-apple-darwin/bin/rust-analyzer\"\n      },\n      \"initialization_options\": {\n        \"cargo\": {\n          \"allTargets\": false\n        },\n        \"check\": {\n          \"allTargets\": false\n        }\n      }\n    }\n  }\n}\n```\n\n.vscode/settings.json\n\n```jsx\n{\n    \"rust-analyzer.server.path\": \"~/.rustup/toolchains/1.95.0-aarch64-apple-darwin/bin/rust-analyzer\",\n    \"rust-analyzer.cargo.allTargets\": false,\n    \"rust-analyzer.check.allTargets\": false,\n}\n```\n\npath หาโดย rustup which rust-analyzer --toolchain 1.95.0\n\nตั้ง project สำเสร็จแล้วงั้นมาดู  code ใช่เช่นนิดหน่อยดีกว่า\n\nอย่าลืม Hardware Abstraction Layer ด้วย\n \n\n```jsx\n**cargo add esp_idf_hal**\n```\n\nจากที่เราเคยผ่านหูผ่านตาก่อนออกเดินทางเราจะเข้าถึงอุปกรณ์ได้ต้องผ่าน peripherals\n\n```jsx\n let peripherals = Peripherals::take()?;\n```\n\nเราก็สามารถใช้อุปกรณ์ต่างภายใน Xtensa ได้แล้ว\n\n```jsx\nlet gpio2 = peripherals.pins.gpio2;\n```\n\nถือว่าระบบปิดของ Xtensa เหนื่อยใช่ได้ ก็ขอตัวไปปแอบอู้หลับก่อนครับ Bye\n","status":"published","created_at":"2026-08-09T14:16:14.979232+00:00","updated_at":"2026-08-09T14:16:14.979232+00:00","synced_at":"2026-08-09T14:16:14.979232+00:00"},{"id":64,"title":"spi","slug":"spi","body":"---\ntags:\n  - eps-rs\n---\nสวัสดีเหล่านักเดินทาง วันก่อยเราเผชิญกับหลายสถานที่เราตอนสื่อกับหลากหลายอุปกรณ์กันไปแล้ว\nงั้นวันนี้   …. [board esp32 c3 สุดถูกๆๆๆ](https://s.shopee.co.th/3qKWysljao) \n\nก็เจอเหมือนเดิม เอ้าจะพูดทำไม ต้องบอกก่อนเลยว่าใน micontroller มีหลากหลาย วิธีในการใช้งาน\nแล้ววิธีแต่ละอย่างมีนัก สำรวจได้คิดค้นมาแล้ว ว่าในบางสถานะการเราจำต้องใช้ เช่น i2c, uart …\n\n\n![esp-ep10](https://drive.google.com/thumbnail?id=1k-Xy16vVQISLcEZ8R9d5kDKMnRQW8QcV&sz=w1000)\n\n\nวันนี้เราจะมาเจอกับอีกหนุ่งตัวคือ SPI (Serial Peripheral Interface) \nเป็นโปรโตคอลสื่อสารแบบ **Serial** ที่ออกแบบมาเพื่อให้ **Microcontroller (Master)** สื่อสารกับ **Peripheral (Slave)** \n\n---\n\n# หลักการทำงานของ SPI\n\nSPI ประกอบด้วย 4 สายหลัก\n\n```\nMaster                     Slave\n+---------+               +---------+\n|         |--- MOSI ----->|         |\n|         |<--- MISO -----|         |\n|         |--- SCK ------>|         |\n|         |--- CS ------->|         |\n+---------+               +---------+\n```\n\nแต่ละสายมีหน้าที่ดังนี้\n\n| สาย | ชื่อ | หน้าที่ |\n| --- | --- | --- |\n| MOSI | Master Out Slave In | Master ส่งข้อมูลไป Slave |\n| MISO | Master In Slave Out | Slave ส่งข้อมูลกลับ Master |\n| SCK | Serial Clock | Clock ที่ Master สร้าง |\n| CS | Chip Select | เลือกว่า Slave ตัวไหนทำงาน |\n\n---\n\nข้อเด่นของ SPI คือ\n\n**ส่งและรับพร้อมกัน ( Full Duplex )**\n\nทุกครั้งที่ Clock เดิน Master ส่ง 1 bit  และ Slave ส่ง 1 bit\n\nตัวอย่าง\n\n```\nClock\n\nMaster MOSI\n10110010\n\nSlave MISO\n00101111\n```\n\n---\n\n# CS (Chip Select)\n\nหากมี Slave หลายตัว\n\n```\n                 Flash\n                   |\nCS1 ---------------+\n\n                 OLED\n                   |\nCS2 ---------------+\n\n                 Sensor\n                   |\nCS3 ---------------+\n\n        MOSI  MISO  SCK\nMaster =====================\n```\n\nสาย MOSI MISO SCK ใช้ร่วมกันทั้งหมด Master เลือกอุปกรณ์ด้วย CS\n\nตัวอย่าง\n\n```\nCS Flash  = LOW\nCS OLED   = HIGH\nCS Sensor = HIGH\n```\n\n---\n\n# transfer()\n\nหลาย HAL มี API\n\n```rust\nspi.transfer(&mut buffer)?;\n```\n\nBuffer เดียวทำหน้าที่\n\n- เป็นข้อมูลที่จะส่ง\n- รับข้อมูลกลับมาทับ\n\nก่อน\n\n```\n68 69 2D 62\n```\n\nหลัง\n\n```\n48 65 6C 6C\n```\n\nข้อมูลเดิมถูกเขียนทับ\n\nข้อดีคือ\n\nไม่ต้องใช้ RAM สองก้อน\n\n---\n\n# SPI ไม่มี Packet\n\nSPI ไม่รู้จัก String,  Packet, Message, JSON\n\nมันรู้แค่ 1010110010101100...\n\nดังนั้นทั้งสองฝั่งต้องตกลงกันเอง เช่น\n\nส่ง 16 Byte ทุกครั้ง หรือ อื่นๆ ความรู้สึกเหมือนเราออกแบบ protocol เอง\n\n---\n\n# SPI ต่างจาก UART อย่างไร\n\nUART\n\n```\nTX -----------------> RX\nRX <----------------- TX\n```\n\nไม่มี Clock แต่ละฝั่งต้องตั้ง Baudrate ให้ตรงกัน เช่น 115200 ทั้งสองฝั่งจึงคุยกันได้\n\n---\n\nSPI\n\n```\nMaster                     Slave\n+---------+               +---------+\n|         |--- MOSI ----->|         |\n|         |<--- MISO -----|         |\n|         |--- SCK ------>|         |\n|         |--- CS ------->|         |\n+---------+               +---------+\n```\n\nMaster สร้าง Clock\n\nSlave ไม่ต้องตั้ง Baudrate\n\nSlave เพียงรอ Clock\n\nจึงสื่อสารได้เร็วกว่า UART\n\n### Note ถ้าใช้ esp-rs ใน ฝั่ง slave จะเจอ\n\n- **`Mode`** = กำหนดรูปแบบของสัญญาณ Clock (CPOL/CPHA) ซึ่ง **Master และ Slave ต้องตั้งเหมือนกัน**\n- **`DMA`** = ฮาร์ดแวร์ที่ย้ายข้อมูลระหว่าง RAM กับ SPI โดยตรง ช่วยลดภาระ CPU และทำให้รับส่งข้อมูลจำนวนมากได้มีประสิทธิภาพกว่า โดยเฉพาะฝั่ง Slave ที่ต้องพร้อมรับข้อมูลได้ทันทีเมื่อ Master เริ่มธุรกรรม\n\nการเดินทางของเรายังไม่สิ้น ครั้งจะเป็นอะไรสุ่มเจอกันได้เลยไปก่อนแล้ว Bye\n\n","status":"published","created_at":"2026-08-01T08:10:45.811968+00:00","updated_at":"2026-08-01T08:10:45.811968+00:00","synced_at":"2026-08-01T08:10:45.811968+00:00"},{"id":63,"title":"recursive","slug":"recursive","body":"---\ntags:\n  - RUST\n---\n\n\n[kino](https://thailand.kinokuniya.com/Grokking_Algorithms_(2ND)/bw/9781633438538?srsltid=AfmBOop12V_zOH0L1gmqGzitqIh_Tin-_xLOCVoFx6afIxsrFicgyeRZ)\n\n[shopee  ไม่เหมือนเล่มบนนะ แต่เป็นภาษาไทย](https://s.shopee.co.th/8AUwpUIDQO)\n\nวันนี้อ่าน Grokking Algorithms ไปเจอหัวข้อ recursive \nแล้วอยากลองว่า zero copy หน้าตาจะออกมาท่าไหน\nแต่สุดท้ายแล้วต้อง total เป็น cache อยู่ดี ถถถถ\n\n\n[github](https://github.com/lidm0707/play_algor/blob/main/examples/recursive.rs)\n![zed ide ](https://drive.google.com/thumbnail?id=1dOoH0zQYiZkOfbJE60nQZ6Y5AV2E7HS5&sz=w1000)\n\n\n\nการที่เราใช้ recursive จะเป็นการ call fn ตัวเองไปเรื่อยๆ จะทำให้  mem ส่วนของ stack จะค่อยๆสูงขึ้น\nอันนี้เล่าให้รู้เฉยๆ ลองไปหาอ่านกันเองนะ 555 เพราะ ถ้าเอา performance loop น่าจะดีกว่าในส่วนนี้\n\nแต่มามองในแง่การคิด หรือ การอ่านง่ายแล้ว revursive น่าจะกว่าเหมาะสมกว่า เพราะทำเหมือนเดิมไปเรื่อยจนถึงจุดจุดนึง\nแล้วเราจะบอกให้หยุดแล้วเด้งออกมาเลย\n\n\nเช่น บอกให้หา กุญแจในเขาวงกตให้หน่อย แล้วมันจะทำไปเรื่อย แล้วเจอกุญแจปุ่มมันจะวิ่งย้อนกลับมาบอกเราเลย\n\n","status":"published","created_at":"2026-08-01T07:46:51.739969+00:00","updated_at":"2026-08-01T07:46:51.739969+00:00","synced_at":"2026-08-01T07:46:51.739969+00:00"},{"id":62,"title":"ESP32-S3 CAM N16R8","slug":"esp32-s3-cam-n16r8","body":"---\ntags:\n  - spec_hardware\n---\n## [buy now](https://s.shopee.co.th/5LAcIK1DDv)\n\n![cb0022a019eb5e3284d670bf1c5b86cac39b4446](https://drive.google.com/thumbnail?id=1qFsVQZOY6Y_6zCJGbI-4oPXlUxPKg3fg&sz=w1000)\n\n[original](https://forum.arduino.cc/t/chinese-esp32-s3-cam/1318290)\n\n","status":"published","created_at":"2026-07-26T14:00:42.144637+00:00","updated_at":"2026-07-26T14:00:42.144637+00:00","synced_at":"2026-07-26T14:00:42.144637+00:00"},{"id":61,"title":"esp32-3c-super-mini","slug":"esp32-3c-super-mini","body":"---\ntags:\n  - spec_hardware\n---\n## [Buy Nowww](https://s.shopee.co.th/6VMZeTtnV1)\n\n\n![va](https://drive.google.com/thumbnail?id=14aRTqDxa5c0cE8SGivWc-ct-xwJQwKJm&sz=w1000)\n\n[original](https://www.artronshop.co.th/product/601/esp32-c3-super-mini-%E0%B8%9A%E0%B8%AD%E0%B8%A3%E0%B9%8C%E0%B8%94-esp32-c3-%E0%B8%88%E0%B8%B4%E0%B9%8B%E0%B8%A7)\n\n","status":"published","created_at":"2026-07-26T13:31:10.793907+00:00","updated_at":"2026-07-26T13:31:10.793907+00:00","synced_at":"2026-07-26T13:31:10.793907+00:00"},{"id":60,"title":"UART","slug":"uart","body":"---\ntags:\n  - eps-rs\n---\n## [Esp32 ถูกๆ](https://s.shopee.co.th/6VMZ5OjmGr)\n\nสวัสดีเหล่านักเดินทาง วันนี้มีเครื่องมืออีกตัวที่น่าสนใจ เคยสงสัยกันไหมเวลา\nที่เราจะให้ board สื่อสารกันจะทำอย่างไร จะต่อโทรจิตกัน หรือเปล่า หรือต่อสายตรงกันไปเลย\n**งั้นตามกันมาดูว่า เราจะให้ board สื่อสารกันได้อย่างไร** \n\n![esp-esp9](https://drive.google.com/thumbnail?id=1kvugOq7DnxcXWfZGcXs57UZonQ7tfNwE&sz=w1000)\n\nUART ย่อมาจาก **Universal Asynchronous Receiver/Transmitter**\n\nหน้าที่ของมันมีเพียงอย่างเดียว คือ **รับ (Receive) และส่ง (Transmit) ข้อมูลผ่านสายสัญญาณแบบ Serial**\n\nแต่ UART ไม่ได้รู้จักคำว่า String หรือ JSON มันรู้จักเพียง **Byte** และแปลง Byte เหล่านั้นให้กลายเป็นสัญญาณไฟฟ้า (0 และ 1) เพื่อส่งไปยังอุปกรณ์อีกฝั่ง\n\nตัวอย่างเช่น\n\n```\nHello\n\n↓\n\n48 65 6C 6C 6F\n\n↓\n\n01001000 01100101 ...\n```\n\n---\n\n## UART ทำงานอย่างไร\n\nอุปกรณ์สองตัวจะเชื่อมต่อกันผ่าน 3 สายหลัก\n\n```\nESP32 A              ESP32 B\n\nTX  ------------->   RX\nRX  <-------------   TX\nGND --------------   GND\n```\n\n- **TX (Transmit)** สำหรับส่งข้อมูล\n- **RX (Receive)** สำหรับรับข้อมูล\n- **GND** เป็นกราวด์ร่วมกัน\n\nเมื่อฝั่ง A ส่งข้อมูลผ่าน TX ข้อมูลจะเดินทางไปยัง RX ของฝั่ง B ทีละ Byte\n\n---\n\n## UART เห็นข้อมูลเป็นอะไร\n\nหากโปรแกรมส่งข้อความ\n\n```\nHello\n```\n\nUART จะเห็นเป็น\n\n```\n48 65 6C 6C 6F\n```\n\nดังนั้น UART จึงส่งได้ทุกอย่าง ไม่ว่าจะเป็นข้อความ ตัวเลข หรือข้อมูลจากเซ็นเซอร์ เพราะท้ายที่สุดแล้ว ทุกอย่างคือชุดของ Byte\n\n---\n\n## ใช้งาน UART ด้วย esp-hal\n\nบน ESP32 ที่ใช้ `esp-hal` เราเริ่มต้นด้วยการสร้าง UART และกำหนด Baud Rate\n\n```rust\nuse esp_hal::uart::{Config, Uart};\n\nlet mut uart = Uart::new(\n    peripherals.UART0,\n    Config::default().with_baudrate(115_200),\n)?\n.with_tx(peripherals.GPIO21)\n.with_rx(peripherals.GPIO20);\n```\n\nหลังจากนั้นก็สามารถส่งข้อความได้ทันที\n\n```rust\n\nuart.write(\"Hello UART!\")?;\n```\n\nหรือรับข้อมูลจากอีกฝั่ง\n\n```rust\nlet mut buf = [0u8; 32];\n\nlet n = uart.read(&mut buf)?;\n```\n\nภาพรวมการทำงานจะเป็นดังนี้\n\n```\n**Program → write() → UART (TX) ═══════► UART (RX) → read() → Program**\n```\n\nเราเพียงเขียนข้อมูลลงใน UART และอ่านข้อมูลจาก UART ส่วนการแปลงเป็นสัญญาณไฟฟ้าและการส่งผ่านสาย จะเป็นหน้าที่ของฮาร์ดแวร์ UART ทั้งหมด\n\n⚠️ **ก่อนออกเดินทางต่อ มีเรื่องหนึ่งที่ต้องระวัง**\n\nในตัวอย่างเราใช้ `UART0` ซึ่งเป็นเส้นทางที่ ESP32 ใช้พูดคุยกับคอมพิวเตอร์เวลาทำการ **Flash Firmware** และเปิด **Serial Monitor**\n\nถ้าเราเอา `UART0` ไปใช้งานกับอุปกรณ์อื่นในเวลาเดียวกัน เส้นทางนี้อาจเกิดการชนกัน ทำให้ Flash โปรแกรมไม่ผ่าน หรือเชื่อมต่อกับบอร์ดไม่ได้\n\nเพราะฉะนั้น เวลาพัฒนาโปรเจกต์จริง หลายคนจึงเลือกใช้ `UART1` หรือ `UART2` (ถ้าชิปรองรับ) สำหรับสื่อสารกับอุปกรณ์ภายนอก และเก็บ `UART0` ไว้สำหรับ Debug และ Flash โปรแกรม\n\n\n","status":"published","created_at":"2026-07-26T04:42:24.617776+00:00","updated_at":"2026-07-26T04:42:24.617776+00:00","synced_at":"2026-07-26T04:42:24.617776+00:00"},{"id":59,"title":"esp32_wroom","slug":"esp32_wroom","body":"---\ntags:\n  - spec_hardware\n---\n[Buy nowwwwww](https://s.shopee.co.th/70Iooh5hdW)\n\nThe ESP32-WROOM is the original and most widely used ESP32 module from Espressif. It is based on the first-generation ESP32 chip and is suitable for general-purpose IoT and embedded applications.\n\n\n![ESP-WROOM-32-Dev-Module-pinout](https://drive.google.com/thumbnail?id=1-qAkLbOXorox6QWZQImHj_Na0o7F8Xs3&sz=w1000)\n\n[Original](https://esp32io.com/#google_vignette)\n\n","status":"published","created_at":"2026-07-25T15:49:59.907729+00:00","updated_at":"2026-07-25T15:49:59.907729+00:00","synced_at":"2026-07-25T15:49:59.907729+00:00"},{"id":58,"title":"i2c","slug":"i","body":"---\ntags:\n  - eps-rs\n---\n\n  \n\n\n![ic2](https://drive.google.com/thumbnail?id=1bVwO1Ae1LGCTrXTIGAMu7aSzlT7OsdVM&sz=w1000)\n\n\nหลายคนเริ่มใช้ LCD I2C แล้วก็เสียบสาย SDA/SCL ใช้งานได้เลย แต่พอถามว่า \"ทำไมต้องมี Pull-up?\" หรือ \"Open-Drain คืออะไร?\" \nมักจะเริ่มงงกันแล้ว ส่วนตัวก็งงเหมือนกันตอนแรก แต่ จริง ๆ แล้ว หลักการของ I2C ง่ายกว่าที่คิด\n\nGPIO ปกติ (Push-Pull) มีทรานซิสเตอร์ 2 ตัว [board esp32 c3 สุดถูกๆๆๆ](https://s.shopee.co.th/3qKWysljao) \n\nHIGH → ต่อขาเข้ากับ 3.3V\nLOW → ต่อขาเข้ากับ GND\n\n\nแต่ I2C ใช้ Open-Drain\n\nมีทรานซิสเตอร์แค่ตัวเดียว!\n\nนั่นแปลว่าอุปกรณ์ทำได้แค่\n\n✅ ดึงสายลง GND (LOW)\n\nหรือ\n\n✅ ปล่อยสาย (Release)\n\nแล้วใครทำให้เป็น HIGH?\n\nหลายคนเข้าใจว่า ESP32 ส่งไฟ 3.3V ออกมาที่ SDA เพื่อให้เป็น HIGH\nจริง ๆ ไม่ใช่ครับ ESP32 แค่ \"ปล่อยสาย\" ส่วน Pull-up Resistor ต่างหากที่ดึงสายขึ้นเป็น 3.3V\n\nนึกภาพง่าย ๆ\n\n🪢 มีเชือกเส้นหนึ่ง\n\n 3.3V\n       │\n    [4.7kΩ]\n       │\n      SDA ●──────── ESP32\n                │\n            Transistor\n                │\n               GND\n\nPull-up = คนที่คอยดึงเชือกขึ้น\nESP32 = คนที่ดึงเชือกลง\nLCD = อีกคนที่ก็ดึงเชือกลงได้\n\nถ้าไม่มีใครจับ เชือกก็อยู่ด้านบน (HIGH)\n\nแต่ถ้ามีใครสักคนดึง เชือกก็ลงทันที (LOW)\n\nเพราะทุกอุปกรณ์ ดึงลงได้อย่างเดียว จึงไม่มีเหตุการณ์ที่อุปกรณ์ตัวหนึ่งพยายามดันเป็น HIGH ในขณะที่อีกตัวดึงเป็น LOW ทำให้ I2C แชร์สาย SDA/SCL ระหว่างอุปกรณ์หลายตัวได้อย่างปลอดภัย\n\n![wire](https://drive.google.com/thumbnail?id=1pfJZ_jkjs1JDYYk8f8uKC_rCNM8UlZ3C&sz=w1000)\n\n\nอ่านมาถึงตรงนี้หลายๆคนสงสัย แน่ว่า \nSDA (Serial Data Line) / SCL (Serial Clock Line) คืออะไร\n\nตามชื่อเลย คือ SDA ไว้รับส่ง data\nส่วนอันนี้หลายคนจะงงหน่อยคือ scl จะเป็นตัวไว้นัดกับอุปกรณ์ต่อพวงทุกตัวว่า จะคุยที่ความถี่เท่าไหร่\nเพราะถ้าไม่ระบุไว้จะคุยกันไม่รู้เรื่อง\n\n\nส่วนนึงที่ผมงงนานอยู่คือ ดึงลงดึงขึ้นของ sda ว่าไฟจะวิ่งยังไง และ data จะเขียนอ่านอย่างไร\nแต่เรื่องไฟเราอธิบายไปแล้วข้างต้น แต่เรื่อง data จะเป็นประมาณว่า\nถ้าต้องการส่ง 0 ก็จะดึง SDA ลง GND ทำให้สายเป็น LOW\nถ้าต้องการส่ง 1 ก็จะปล่อย SDA แล้ว Pull-up จะดึงสายกลับเป็น HIGH\nส่วนอุปกรณ์ที่เป็นผู้รับจะไม่ได้ไปดึงสาย แต่จะคอยอ่านค่าของ SDA ในจังหวะที่ SCL เป็น HIGH จึงรู้ได้ว่าบิตนั้นเป็น 0 หรือ 1\n\nSCL : _|‾|_|‾|_|‾|_|‾|_\nSDA : 0   0   0   0\n0000\n\nSDA : 1   0   1   1\n1011\n\nใครอ่านมาถึงตรงนี้ได้สุดยอดเลยครับ แต่ครั้งจะเป็นอะไรมาติดตามได้ในอาทิตย์หน้า\nจะพยายามทำลงให้ไ้ดทุกอาทิตย์ครับ บาย XDD\n\n\nword\nดึงขึ้น (Pull-up) → HIGH\nดึงลง (Pull-down / Pull Low) → LOW\n\n\n\n[esp32 i2c](https://docs.espressif.com/projects/rust/esp-hal/1.1.1/esp32c3/esp_hal/i2c/master/index.html)","status":"published","created_at":"2026-07-18T12:14:54.925247+00:00","updated_at":"2026-07-18T12:14:54.925247+00:00","synced_at":"2026-07-18T12:14:54.925247+00:00"},{"id":57,"title":"Asynchronous","slug":"asynchronous","body":"---\ntags:\n  - 100excer\n---\nสวัสดีทุกคนมาถึงน่าบทสรุปสุดท้ายของ 100 excercises to learn rsut กันแล้ว ต้องบอกก่อนผมไม่ได้ทำแบบละเอียดทุกอันแค่พยายามทำให้เข้าใจ concept และปรับไปใช้มากกว่า แต่ในที่สุดเรื่องของวันนี้ก็เป็นเรื่องยากเกือบที่สุดของ Rust เหมือนกัน นั้นก็คือ !!!\n\n![705921217_122132581005065881_3357351853491938616_n](https://drive.google.com/thumbnail?id=1lRAGA0dhw_w5MSZGKjGEtKyJ6kQmEhLh&sz=w1000)\n\nAsynchronous functions\n\nเกริ่นคร่าวๆ ก่อนว่าทำไมมันขึ้นยาก\n1. ที่เห็นชัดเจนคือsignature ของ มัน\nเพราะ ปกติ Rust ไม่ได้ Native async ตั้งแต่แรกมันจะ แปลง asnyc\nเป็น Future object ก่อนแล้วเราก็ไปหาที่ เรียกมันอีกทีว่ามันทำงานเสร็จยัง\n\nถ้าให้คิดก็เหมือนกล่องในสายผ่าน แล้วค่อยให้คนเปิดเช็คดูว่ากล่องไหน OK แล้วก็ยกออกไป\n\n2. พวกกฎของ Rust ต่างๆจะเริ่ม งุ่ม งง กันเพราะ เวลา เป็น Future object แล้วเราสามรถนำไป เช็คที่ไหนก็ได้ เช่น ยกไปอีกที่ให้คนในนั้นวนเช็คกันแต่ในสายพานหลัก ก็ สร้างกล่องมาใหม่มาเรื่อยๆ\n\nเพราะแบบนี้ละตอนยกกล่องใหม่ไปอีกที่มันต้องรู้ด้วยว่าใครเป็นเจ้าของ หรือว่ากล่องนี้ต้องอางอิงกล่องอื่นไหม ( send + sync ) \n\nมาดูที่น่าสนใจดีกว่า ว่า เจ้า  async ของเราแตกต่างกับคนนอื่นยังไง\n1. จะแปลงเป็น Future object ให้เราจัดการเอาเอง\n2. เวลา call จะไม่ใช้ .await จะไม่ทำงาน เพราเป็น lazy function.\n( ถ้า.await มันจะบล็อค next line ต้องใช้ พวกJoin มาช่วย ทำ call หลายๆ ตัวไม่ บล็อคกัน )\n3. เราสามารถเอา runtime มาต่อเองใน main ได้เอง\n( เหมือนที่ที่เราใช้ check Future )\n\nเราสามารถปรับแต่งได้ว่า Future เราจะทำงานใน sigel thread vs multi thread\n\nพวกที่น่าสนใจแต่ไม่เอามาใส่\nคือ  blocking task, sync lock, cancel task.\nสรุปย่อคือ \n1. เราทำ spawn block task แล้วตัวนั้น compute นานจะไป block ตัวอื่น\n2. เวลา mutex lock มีโอกาส deadlockได้บ้างกรณีถ้าใช้ไม่ถูก\n3. ยกเลิก task ส่ง sinal ดีกว่า drop ตรงๆ\n\nss นี้ก็จบลงแต่เพียงเท่านี้ โอกาสต่อไปจะทำอะไรต่อติดตามกันได้เลยครับ\n\ntrait Future {\n   type Output;\n      fn poll(\n        self: Pin<&mut Self>,\n        cx: &mut Context<'_>\n      ) -> Poll<Self::Output>;\n}\n\n- Poll::Pending: the future is not ready yet. You need to call poll again later.\n- Poll::Ready(value): the future has finished. value is the result of the computation, of type Self::Output.\n\n\n[100-exercises](https://rust-exercises.com/100-exercises/08_futures/03_runtime.html)","status":"published","created_at":"2026-07-14T08:02:53.680547+00:00","updated_at":"2026-07-14T08:02:53.680547+00:00","synced_at":"2026-07-14T08:02:53.680547+00:00"},{"id":56,"title":"Interior","slug":"interior","body":"---\ntags:\n  - 100excer\n---\nครั้งก่อนเราเขียนถึง channel ใน 100 exercises\n\nจะมีเรื่องเกี่ยว Interior mutability หรือที่ผมตั้งชื่อให้ก็คือ wrap unsafe can mut XD\nถึงพวก พวกๆ wrap นี้จะมีคุณสมบัติที่ สามารถส่ง ข้าม thread ได้ (trait send)\nแต่ ปัญหาของเพื่อน ownership เหมือนเคย\n\n![701708489_122131364799065881_7048982758884355239_n](https://drive.google.com/thumbnail?id=1dFiVpl9e5fL0Pd0I58kcZwAVk9KYKS6A&sz=w1000)\n\nจริงๆปัญหาไม่ได้เกิดจากเพื่อนรักเรา แต่ที่อ่านมาไม่แน่ใจถูกต้องไหม\nเวลาที่เราจะ lock อะไรสักอย่าง ( บอกก่อน wrap ที่เราคุยกันวันนี้มี ความสามารถ lock ห้ามให้คนอื่นแก้ไข)\nแล้ว บาง OS ← ไม่ชัวร์ จะบอกว่าเราว่าเวลาที่จะ lock ต้องทำใน thread ตัวเองเท่านั้น\nเพราะเวลาเรา lockเราจะได้ เพื่อนใหม่ ที่ชื่อว่า Guard มา\nจากข้างต้นถ้าเรา โยน Guard ข้าม Thread จะทำให้บาง OS แจ้ง Undefiend Behavior\n\nถึงตรงนี้ละเพื่อนรัก ownership เข้ามาทักทายเราว่า ถ้า โยน wrap ไปเลย \nใน Rust ก็จะ drop ตัวนั้นเมื่อจบ scope แล้วเอาไปใช้ต่อไม่ได้ \nเพราะเวลาเราใช้ เราก็อยากจะส่ง wrap นั้นไปหลายที่ เพราะทำงานแบบ ขนานกัน\nแต่มันทำงานได้แค่ scope เดียวก็ไม่สะดวกใช่ไหม เราเลยต้องหาวิธีการส่งownership ข้าม theard โดยใช้ Arc (Atomically Reference) สามารถ ให้เรา เอา wrap เราไปห่ออีกทีได้ (ห่อเยอะไปไหน) หน้าตาจะแบบ Arc<Wrap<T>> \nแล้วทีนี้ wrap ของเรานั้นจะมี ความสามารถ  send + sync (trait)\nและสามารถส่ง ownership และ reference ข้ามกันได้แล้ว ( Arc → shared ownership )\n\nArc หน้าที่หลักคือ\n1 - สามารถ Clone  อ้างอิงขึ้นของข้างในตัวในได้\n2 - ownership จะไม่ถูก drop จนกว่าทุกคนจะไม่ใช้แล้ว( นับเหลือ 0 drop )\n\nจากทั้งหมดนี้จึงทำให้ ของที่เราส่งเพื่อทำงานร่วมกันนั้น ปลอดภัยจาก data race และ Undefiend Behavior\n\nแต่ต้องพึ่งระวังด้วย ว่าของข้างใน wrap อะไรก็ตามถ้าชั้นในไม่มี trait นั้นชั้นนอกก็จะไม่มีเหมือนกัน\n\nArc<Rc<T>> พัง\n\nเพราะ Rc<T>: !Send + !Sync\n\n[100-exercises](https://rust-exercises.com/100-exercises/07_threads/11_locks.html)\n\nจากที่ดู 100 exercises น่าจะทำต่อได้อีก 1 ถึง 2 post ก็น่าจะจบแล้ว แต่ยังคิดไม่ออกทำไรต่อดี เพราะช่วงนี้สนใจ risc-v\n","status":"published","created_at":"2026-07-14T08:01:40.661953+00:00","updated_at":"2026-07-14T08:01:40.661953+00:00","synced_at":"2026-07-14T08:01:40.661953+00:00"},{"id":55,"title":"channel","slug":"channel","body":"---\ntags:\n  - 100excer\n---\nถึงช่วงท้ายๆของ ศึก 100 exercises แล้ว ถ้าใครเขียน program มาระดับนึงจะรู้จักเรื่อง channel กัน\nแต่ถึงตอนนี้การเขียนนั้นเรายกไปให้ AI จนเกือบหมดแล้วก็ตาม แต่ก็คือเป็นความรู้คิดหัวแล้วกัน\n\n![689477415_122130655803065881_9144922842760592926_n](https://drive.google.com/thumbnail?id=14bfWdzgAGkIJjP6l7doS-fdDrlAHpKcs&sz=w1000)\n\nตอนที่เราทำ thread หรือ spawn อะไรสักอย่างขึ้นมาเราจะใช้แล้ว ทำลายมันทิ้งไป แต่ในการทำบ้างอย่าง\nเราก็ต้องการให้มันอยู่นานๆ เช่นใน 100 exercises นี้ จะใช้เป็น ระบบ Ticket และมันอยู่ควบคุมตัวอื่นๆตลอดเวลา\nหรือ มองง่ายๆ เป็นระบบ queue ก็ได้ ถ้าเป็นนี้เราจะไปทำแบบ  list [ …. ] แล้ว Lock บลาๆ แล้วกดัน muti working ไหม มันก็ทำได้แหละ แต่ Rust provide tool มาให้แล้ว คือ multi-producer, single-consumer (mpsc) ( ของ tokio น่าจะดีกว่า ) เราไม่ต้องไป ทำต่างๆ แค่ใช้ send + receive ก็พอ มันก็จะยัดเข้า queue ให้\n\nlet (sender, receiver) = channel();\n\nโดย เอา  receiver อยู่ใน main process ที่ทำงานตลอด แบบ loop เพื่อค่อยรับงานเข้ามาทำ\nแล้ว sender clone เข้าไปใน thread ที่เรา spawn เพื่อส่งงานเข้าให้คนอื่นทำ\n\none receiver but multiple senders เพราะมันเป็น queue เลยไม่ต้องห่วงเรื่อง race condition\n\nแต่มีเรื่องที่น่าสนใจต่อจาก เพราะ ใน send(&self) แล้ว  qeueue ที่เรายัดเขาไป จะเพิ่มค่าอย่างไร เพราะไม่ได้ &mut เข้าไปนิ ที่ไปตบตีกระชากหัว AI กับนั่งวนอ่านหลายๆ เกี่ยวกับ\n\nInterior mutability \nมันจะเกี่ยวกับ ถ้าเรา สามารถ ทำตัวครอบที่อ้างอะไรบ้างอย่างแล้ว\nที่ไม่สามารถแก้ได้มาแก้ข้างในตัวที่ครอบได้ \n\nแต่เราต้องเพิ่มมุมมองเกี่ยวกับ &T และ &mut T ก่อน\nเพราะ มันไม่ได้บอกแค่ว่าตัวนี้ อ้างอิงแบบแก้ได้ หรือแก่ไม่ได้ แต่จริงๆมะนอก \nตัวนี้ แชร์แบบรรมดา หรือ แชร์แบบพิเศษใส่ไข่ \n\nแต่จริงแล้ว &T  ไม่ได้ การันตีว่าจะไม่เปลี่ยนเสมอไป\nโดย ใช้ UnsafeCell ข้างใน Send ที่เป็น wraper  อีกทีนึง\nที่เห็นเป็น unsafe จริงๆแล้ว rust ออกแบบมาให้ safe \nโดยที่ wraper นั้นต้องยึดตาม rule ของ rust อยู่ดี\n\nโดยปกติ UnsafeCell จะอยู่ใน smart pointer ที่ share ข้าม thread เช่น\nMutate, Rwlock .\n\nของแถม\nchannel ของเราสามาารถ ตั้ง capacity เป็น Bounded ได้ด้วยนะ\n\nในบทความของเขา จะมีตัวอย่าง Act patter ด้วย สั้นๆ คือ แทนใส่ recv ของ คนส่งไปใน send ด้วยเพื่อให้รับแล้วส่งกลับได้\n\nคล้ายๆ Act ของ network เลย ( มันไปต่อพวก actor ได้อีก XD) 🦀\n\n\n[100-exercises](https://rust-exercises.com/100-exercises/07_threads/09_bounded.html)\n","status":"published","created_at":"2026-07-14T08:00:19.839739+00:00","updated_at":"2026-07-14T08:00:19.839739+00:00","synced_at":"2026-07-14T08:00:19.839739+00:00"},{"id":54,"title":"thread","slug":"thread","body":"---\ntags:\n  - 100excer\n---\nไหนๆวันหยุดแล้วเลยอ่านทำความเข้าใจเพิ่มครับ \nมาถึงจุดนี้แล้วการพูดถึงเรื่อง thread เรื่องนี้ถ้าไครเริ่มาศึกษาได้ไม่นนาน อาจจะ หลงไปกับ async ได้เหมือนกัน\nเมื่อก่อนผม เข้า async I/O กับเรื่องของ thread cpu core เป็นแบบเดียวกันมาตลอด แต่พอพูดคุยศึกษาเองมาเรื่อยๆ ก็เห็นภาพมากขึ้นมามันแตกต่างกันอย่างไร\n\nส่วนตัวมอง async คือพอ cpu มีช่วงว่าง (เช่น รอ I/O) ก็สลับไปทำงานอื่นก่อน  (เพราะบางงานมันมีช่วงที่ รอ อยู่)\n\nส่วนของ multi thread จะเป็นการบอก os ขอ core หน่อยจะทำงาน แล้ว os ก็จะไปจัดการหา core มาให้ใช้\n\n![688271024_122129371071065881_1087221040110521000_n](https://drive.google.com/thumbnail?id=1AeFXTZAS9CWjZ_y56sjhf72sLSwiOEOI&sz=w1000)\n\nถ้ามองแบบนี้มันต่างกันใช่ไหมครับ แต่เวลาทำงานบางทีมันเร็วมากคนคิดว่า ทำงานแบบขนานกัน\nหรือ ตอนที่เราไม่ได้ await ใน javascript มันก็ทำงานข้ามส่วนนั้นไปเลย เลยทำให้เกิดความสับสนได้\n\nแล้วทำไมเข้าไม่ spawn thread ไปเลยละเพราะยังไงทำงานนแบบขนาน น่าจะเร็วกว่านิ\n\nถ้าบอกว่ามี 2 คนช่วยกันทำงานที่สามารถ แจกงานให้กันได้ก็ใช้ครับ แต่จุดไม่ใช่จุดสำคัญ\nเพราะเวลาที่เรา spawn thread จะมีการใช้ ทรัพยากรค่อนข้างเยอะ การใช้ async\n\nสมมุติลองคิดว่าใช้ I/O เล็กๆรับ request แล้วทุก request spwan thread โดยโยนให้ syscall มันก็จะลำบากแปลก เพราะใช้นิดเดียวสร้างสะใหญ่เลย แทนทีจะไปรอคิวรอเวลา หรือคิดแทบจะไม่ต้องต่อเลยก็ได้เพราะมันเร็วมาก\n\nเกริ่นมาสะยาว แต่จริงเราจะ พวกกับ std ของ Rust ใช้งาน กับ thread ครับ\n\nโดยส่วนที่น่าสนใจผมคิดว่ามี 3 อันที่อยากมาเหลาให้ฟังครับ\n1. spawn thread\n2. scrope thread\n3. ‘static life time ← ไอมันมาจากไหน เกี่ยวครับรอก๋อน\n\nโดยผมพยายามไม่เอา code มาใส่ครับคิดว่า เข้าใจ concept มันดีกว่า\n\n- thread::spawn แล้ว join ใน main เพื่อรอให้ thread นั้นทำงานจบ \nถ้าไม่สั่งจะ  main จบก่อนแล้ว thread โดน kill หรือ เป็น function ที่สั่ง spawn อีกทีจะเกิด outlive function \n- แต่ถ้าเป็น thread::scrope คือ scrope.spawn กี่ตัวก็ตาม ถ้าจะออกจาก scrope { … } rust จะรอทุกตัวทำงานเสร็จถึงออกจาก scrope นี้\n\nหลักการใช้สองตัวนี้น่าจะมีเท่านี้ แต่ๆ\n\n thread rust ที่มันนาสนใจงุมง่งงงงวยเพราะ ownership นั้นแหละครับ 5555\nสงสัยไหมครับ ว่าเรา spawn thread แล้ว ไครเป็น owner \n\nโดยปกติแล้ว  การใช้ thread ใน rust จะบังคับ move อยู่แล้ว\n\nthread ⇒ move {  some logic  } เพราะถ้าไม่ ประกาศ Move จะเป็นการ reference & แล้วมีโอกาสที่\nthread จะจบหลัง main ได้ทำให้ reference ไป use after free ได้ แล้วก็ตูม นี้ละครับ rust สะบายใจแบบนี้\n\nuse std::thread;\nfn some_run() {\n  let person = “johne”;\n   let handle = thread::spawn( move || {\n   println!(\"Hello from a thread! {johne}\");\n});\n\n}\n\nv (stack) : ------>\nthread     : —------------->\n\nแต่ thread::scrope สามารถ reference &  ได้ เพราะ thread ต้องจบก่อนออก scrope\n\nlet v = vec![1, 2, 3];\nlet midpoint = v.len() / 2;\n\nstd::thread::scope(|scope| {\n  scope.spawn( || {\n   let first = &v[..midpoint];\n   println!(\"Here's the first half of v: {first:?}\");\n });\n  scope.spawn(|| {\n   let second = &v[midpoint..];\n   println!(\"Here's the second half of v: {second:?}\");\n });\n});\n\nprintln!(\"Here's v: {v:?}\");\n\nv         : ----------->\nscope     :     ----->\nthread    :     ----->\n\nเอ้า ไหน ‘static lifetime ของผมครับพี่ งั้นมาดูกันดีกว่า …\n\nปกติหลายคนจะเข้าใจว่า 'static มันอยู่ใน data หรือ rodata\nซึ่งก็ ถูกครึ่งเดียว\n\nจริง ๆ แล้ว\n'static ไม่ได้หมายถึง “อยู่ memory ตรงไหน”\nแต่มันหมายถึง “อยู่ได้นานพอ (ทั้งโปรแกรม)”\n\nเช่น:\n\nlet some_char: &'static str = \"a\";\n\"a\" อยู่ใน rodata \nเลยอยู่ได้ทั้งโปรแกรม \n→ อันนี้เป็น 'static จริง\n\nแต่จุดที่งงคือ…\n\nตัวแปร some_char เอง  อยู่บน stack\nมันแค่ “ชี้ไป” ที่ข้อมูล 'static เท่านั้น\n\nทีนี้ปัญหาไม่ได้อยู่ที่ \"a\" เลย\nเพราะ \"a\" อยู่ยาวอยู่แล้ว\n\n❗ ปัญหาอยู่ที่ “ตัวแปร some_char”\n\nถ้าเราเขียนแบบนี้:\n\nthread::spawn(|| {\n   println!(\"{some_char}\");\n});\n\nสิ่งที่เกิดขึ้นคือ:\n\nclosure ไป ยืม (&) some_char\nแต่ some_char อยู่ใน stack ของ function นี้ \n\nถ้า function จบ\n→ some_char หาย (stack ถูก pop)\n\nแต่ thread อาจยังทำงานอยู่\n\nตู้ม มีโอกาสอ้างถึงค่าที่หายไปแล้ว (use-after-free)\n\nRust เลยไม่ยอมไม่ใช่เพราะมันไม่รู้จัก 'static strแต่เพราะมันไม่เชื่อว่า ตัวแปรที่ถือ reference นั้นจะอยู่รอด\n\nทางแก้คือ:\nthread::spawn(move || {\n   println!(\"{some_char}\");\n});\n\nmove จะย้ายค่า some_char เข้าไปใน thread\n→ thread เป็น owner เอง\n→ ไม่ต้องพึ่ง stack ของข้างนอก\n\nวันนี้ก็จบประมาณนี้ละกันครับไครสนไต่ศึกษาหรืออ่านต่อได้ที่ลิงค์ด้านล่างเลยครับ\n\n\n\nthread\nspawn → อิสระ → ต้อง 'static\nscope → ถูกคุม → borrow ได้\nlifetime\n\nRust ไม่ได้ดูว่า “ข้อมูลอยู่ไหน”\nแต่ดูว่า “ใครจะตายก่อน”\n\nasync vs thread\nasync → scale ดี (งานเล็กเยอะ)\nthread → แรงจริง (CPU work)\n\n'static = reference ที่ valid ได้ตลอดทั้งโปรแกรม\n\n[100-exercises](https://rust-exercises.com/100-exercises/07_threads/04_scoped_threads.html)\n\n","status":"published","created_at":"2026-07-14T07:59:12.137437+00:00","updated_at":"2026-07-14T07:59:12.137437+00:00","synced_at":"2026-07-14T07:59:12.137437+00:00"},{"id":53,"title":"HashMap","slug":"hashmap","body":"---\ntags:\n  - 100excer\n---\nวันนี้เราจะมาเหลาเกี่ยวกับ Map กัน ไม่ใช่ .map() ที่เคยเห็นมานะ\nมันคือ map Key ,Value ของ Rust ที่เหมือนกับ Record ใน Typescript\n\n![678932124_122128326069065881_3899087371918568695_n](https://drive.google.com/thumbnail?id=1DRXJaL_8N-AwqDs-387BYCGYceIrU37e&sz=w1000)\n\nแต่จริงๆใน 100 example มี  2 เรื่องที่ส่วนตัวคิดว่า รู้ก็ดีไม่รู้ก็ได้คือ\n\n1. two state ของ struct\nคือ ถ้าเรามี  struct ที่บอกว่า  id อาจจะมีหรือไม่ก็ได้บ้างคนอาจจะใช่เป็น Option<id>\nแต่จริงเราแยก Struct ตามการใช้งานได้เช่น ReadData หรือ WriteData จะลดการกำกวมลง\n\n2. index คือ trait\nlet  a = [”dog”,”cat”]\na[1] = “dog”\nที่เราใช้ 1 เป็น index ได้เพราะ rust ทำ auto trait ให้แล้ว แต่เราสามารถไปทำเองลองแงะใน example กันดู\n\nเรากลับมาที่ Map ของเรากันดีกว่า หลักที่เราจะมาดูกันนมี 2 ตัวคือ\nHashMap กับ BtreeMap\n\nถ้าพูดแบบง่ายที่สุดเลยคือ \n- HashMap เอา Key ไปทำอะไรสักอย่างเพื่อให้ได้ Index มา แต่ไม่ การันตี ตำแหน่ง  Dog  → somehash → 19\n- BtreeMap จะเอา Key มาแบ่งเป็นช่วง [ 10 | 20 | 30 ] จะสามารถ เรียง และเข้าแต่ละช่วงได้\n\nต่อมาทำไม  HashMap ต้องมี Eq  \nบ้างทีที่ Hash Key มาเป็น Index มีโอกาสที่ Index จะซ้ำกัน ต้องเช็ค Hash ด้วย เลยต้องใช้ Eq ในการเปรียบเทียบ แต่เรา แค่ HashMap::new() เราไม่รู้เลยว่ามันจะจำเป็ต้องใช้ Eq trait ใน HashMap\n\nมีอันนึงน่าสนใจ เพราะตอนแรกงงการใช้งาน HashMap คุยกับ AI ไปมา แล้ว AI บอกว่าเวลา\nHashMap จองพื้นที่จะจองทีละ 16 ตัวแล้ว *2 ขยายๆไปเรื่อยๆ\nตัวนี้ส่วนตัวไม่ได้ศึกษาลงไปลึกเอาเป็นรู้ไว้ใช้เก็บไข่เข้า Mimic ละกัน XD\n\nแล้วขาดไม่ได้เลย BtreeMap เราเรียงได้ใช่ไหม มันก็มี Ord trait เหมือนกัน\nแล้วมีเกล็ดเล็กน่าสนใจคือ ทำไมต้องมี PartialOrd ทั้งที่มันเรียงได้ควรจะ compare ได้ทั้ง\nเพราะ บ้างValue มี Option หรือ ตัวเลขอาจจะเป็น NaN ได้\n\n\n[100-exercises](https://rust-exercises.com/100-exercises/06_ticket_management/16_btreemap.html)\n[cheats](https://cheats.rs/)\n\n","status":"published","created_at":"2026-07-14T07:57:48.545408+00:00","updated_at":"2026-07-14T07:57:48.545408+00:00","synced_at":"2026-07-14T07:57:48.545408+00:00"},{"id":52,"title":"Array","slug":"array","body":"---\ntags:\n  - 100excer\n---\nตัวเองจะchallenge อ่าน 100 example ให้จบอีกนิดใกล้แล้วว\nมาถึง Array จนได้ ในหลายๆภาษา programing  Array คือ การเก็บข้อมูลรูปแบบนึงที่เอาข้อมูลมาเรียงต่อกัน\n\n| 1 | 2 | 3 |\n\n โดยจะมี จำนวนตัวสมาชิกที่แน่นอน หน้าตาใน code ก็จะเป็นประมาณนี้\nlet numbers: [u32; 3] = [1, 2, 3];\n\n![672686463_122126507907065881_107532964498604492_n](https://drive.google.com/thumbnail?id=1rpKm1dUVLcBZzJy0IdYdSIIYx8uTavIh&sz=w1000)\n\nบอกว่ามี integer ประเภท u32 อยู่ 3 ตัว แต่ถ้าเขียนหลายภาษามาน่าจะสงสัยว่า เราจะ เพิ่มสมาชิกเข้าไปจะทำอย่างไร ใน Rust ใช้ Array แบบ dynamic ที่ชื่อว่า Vec ที่จะสามารถขยาย อนาคตได้\nแต่เป็นว่า มันสามารถเพิ่มลดได้แบบนี้ ก็เสียของมันคือ การที่ขนาดเพิ่มขึ้น\nจำเป็นต้อง  ยกเลิกการจอง (deallocate) แล้วไปจองใหม่ที่มีพื้นที่ติดกันเท่ากับตัวที่จะใช้\nซึ่งกระบวนการนี้ค่อนข้างหนัก แล้วลองดูๆ มันจะคล้ายๆ String เลย หรืออีกชื่อคือ Vec<u8> นั้นเอง\nนั้นแหละครับ ทำไม String แพงไม่แพ้ Vec.\n\nแต่ถ้าค่อนข้างมันใจว่าส่วนใหญ่แล้วใช้ไม่เกิน แต่เผื่อไว้อาจจะเกินก็ได้ \nสามารถ ระบุขนาดล่วงหน้าได้โดยใช้ \n\nlet mut v = Vec::with_capacity(3); \n\nแต่ถ้าใช้เกิน ก็ต้องเข้ากระบวนการเดิมอยู่ดี\n\nจริงๆ ถ้าเป็นใน rust จะ คำสั่ง iter() ที่ค่อยถ้าว่า array หรือ vec นั้น สามารถเลื่อนไปตัวนั้นไป มาให้อยู่อยู่แล้ว\n\ntrait Iterator {\ntype Item;\nfn next(&mut self) -> OptionSelf::Item;\n}\n\nแต่เวลาเราใช้เรามักจะเห็น 2 แบบคือ\nloop แบบมี \nx in list.into_iter()  หรือ x in list\nจริงใช้ได้ทั้ง 2 แบบเพราะ เป็น feature ของ rust ที่อำนวยความสะดวกให้เรา\n\nแต่ที่น่าสนใจคือ มันจะมี iter 2 แบบ\n- into_iter()    อันนี้คือใช้เสร็จแล้วไปใช้ต่อไม่ได้ เพราะถูกเอา owner ไปแล้ว\n- iter()            อันนี้จะเหมือนการ ยืมมาใช้ (  &something  )\nตัวนี้รู้ไว้ใช้ว่า เพราะอาจะเจอตอนที่ complier ด่าเราว่า  owner ไม่ถูก มันตายไปแล้วกลับไปแก้ด้วยยยย\nอะไรประมาณนั้น\n\nต่อมาจะพูดถึงนิดหน่อยก็ พวก Combinators ส่วนตัวชอบเรียกว่า chain function\nใน  iter() เราสามารถ dot chain function เพื่อทำอะไรบ้างอย่างไร เช่น\niter().filter เอา A ที่ไม่ใช้ Dog ออกไปใน data list ของเรา Dog นั้นก็จะหายไปแล้วแต่\nเวลาเราจะเอามากลับมาสรุป จำเป็นต้องบอก rust ด้วยว่า ค่าที่ทำเสร็จแล้วเป็นหน้าตาแบบไหน\n\nนั้นเลยต้องใช้ collect ที่เป็นตัวกำหลังว่า ถ้าที่ทำเป็นเสร็จจะมีหน้าตาอย่างไร\n\nตัวอย่างก็ สามารถกำหนดได้ ที่ var หรือ generic ของ collect ก็ได้\n\nvar : Vec<u32> = [..].iter().map.collect\nvar = [..].iter().map.collect::<Vec<u32> \n\nแล้วส่วนสุดท้ายจพูดถึงพวกนี้ก็คือ Vec และ Array สามารถกลายร่างไปมากันได้\n\nuse std::convert::TryInto;\n\nfn main() {\nlet mut a = Vec::new();\na.push(String::from(\"a\"));\n\n   let b: [String; 1] = a.try_into().expect(\"wrong length\");\n   println!(\"{:?}\", b);\n\n   let c: Vec<String> = b.into();\n   println!(\"{:?}\", c);\n\n}\n\n\n[cheats](https://cheats.rs/)\n[100-exercises](https://rust-exercises.com/100-exercises/06_ticket_management/10_slices.html)\n\n","status":"published","created_at":"2026-07-14T07:56:19.58309+00:00","updated_at":"2026-07-14T07:56:19.58309+00:00","synced_at":"2026-07-14T07:56:19.58309+00:00"},{"id":51,"title":"Error","slug":"error","body":"---\ntags:\n  - 100excer\n---\nมาถึงอีกตัวหนึ่งที่เป็น เสาหลัก rust กันแล้วนั้นคือ Error\nตามธรรมเนียมของ rust Error ถือว่าเป็น Trait (จำเป็นพฤติกรรมละกัน) ตัวนึงเหมือนกัน\nที่บอกว่า ถ้า กลุ่มของ ข้อมูลชุดนี้ fail ขึ้นมาจะมีสภาพเป็นอย่างไร\n\n![671957756_122125765377065881_3354982971692395048_n](https://drive.google.com/thumbnail?id=1GiXjAzllyKtHCMATeJJ6VmaJXSBTglEu&sz=w1000)\n\nโดย super trait ของ Error คือ\n\nDisplay และ Debug ← เป็น trait ของ formatting โดย\n- Display ต้อง พิมพ์ impl Display for …. เอง เป็นการแสดงข้อความที่เราต้องการให้เห็น\n- Debug จะสามารถ Derive (ไอตัวยึกยื้อบน struct) ได้เพราะจะคาย ออกมาทั้งก้อนเลย \nเช่น Err Dog { sound: Moew};\n\nแล้วผมสมมุติเราไป impl Error เองหมดมันก็จะไม่ค่อยสะดวก เราเลยมีสิ่งเล็กๆที่เรียกว่า …\n\nthiserror ที่สามารถ ไปแปะไว้บนตัว enum error ของเราได้เลย\n\n#[derive(thiserror::Error, Debug)]\nenum TicketNewError {\n   #[error(\"{0}\")]\n   TitleError(String),\n   #[error(\"{0}\")]\n   DescriptionError(String),\n}\n\nจากที่ดูค่อนข้างสวยงาม (เฉพาะคนเขียน Rust มอง)\nทุกคนจะมองหาว่าไหน Display ที่ต้อง impl เอง ไม่ๆที่นี้เราไม่ทำแบบนั้น\nthiserror ได้เอาไปซ่อนใน #[error()] เรียบร้องแล้วเราแต่พิมพ์ไปว่าอยากจะโชว์อะไรออกไป\nส่วน Enum แต่ละ ที่บอกว่าเก็บ type อะไรนั้น ตอนเวลาใช่เราสามารถระบุไปที่หลังเพื่อให้ \nthiserror ไปประกอบเป็น Display เอาเองได้เลยย\n\nเห็นไหมเราใช่ enum กับเครื่องมือของ Rust ทำให้การ จัดการ Error ง่ายขึ้นเยอะ\nหรือจริงๆ ขี้เกียจก็ไป anyhow ก็ได้นะแต่ใน exercise 100 วันในช่วงนี้ไม่ได้มีบอก\n\nแล้วอีกตัวที่น่าสนใจคือ การเอา error ตัวก่อนหน้าออก คือ Error::source\nจะมี #[source] และ #[from] ผมยังใช้ไม่บ่อยเลยไม่แน่ใจลายละเอียดลึกๆ เท่าไหร่\n\nแต่เห็นบอก ว่า #[from] มี source + from ให้เลย\n\nส่วนตัวเห็นภาพเป็น การส่ง error ออกมาต่อกันทีละชั้นได้\n\n\n[100-exercises](https://rust-exercises.com/100-exercises/05_ticket_v2/14_source.html)","status":"published","created_at":"2026-07-14T07:54:36.543119+00:00","updated_at":"2026-07-14T07:54:36.543119+00:00","synced_at":"2026-07-14T07:54:36.543119+00:00"},{"id":50,"title":"Wrap type","slug":"wrap-type","body":"---\ntags:\n  - 100excer\n---\nWrap type (เรียกเองส่วนตัวมากๆ)\n   ใน rust จะเป็นภาษาที่ ค่อนข้าง stick เรื่องของการใช้ type มาก ไม่รวมถึง ไวยกรณ์ที่ไม่คุ้นหน้าคุ้นตากันเท่าไหร่\nการใช้ wrap( อะไรก็ตามที่อยากใช้ ) เข้ามาเพื่อความสะดวกสบายมากขึ้น\n\n![663251663_122124387345065881_6205148377431448099_n](https://drive.google.com/thumbnail?id=1h_zl548FnPChS8nYgn6rgtBcGgcegFD0&sz=w1000)\n\nจริงๆ แล้วรวมมากใน Rust จะ wrap แล้ว auto impl มาใช้เข้าแล้ว แต่มาดูกันว่าตัวไหนที่เราจะเห็นกันบ่อยๆ**\n\nNullability\n   เปิด มาคิดกันว่า point null exception ใช่ไหมเดียวก่อนเพราะ ที่นี้เราไม่ได้ทำแบบนั้น เพราะว่า ใน Rust  ในการรับมือกับ Null นั้นเราใช้ถ้าที่ เรีนกว่า \n\nenum Option<T> {\n    Some(T),\n    None,\n}\n\nเห็นแบบนี้ นั้นแหละครับ แปลว่าเราต้อง handle ทั้ง ขาที่มี Value ไม่มีด้วย ในที่ผ่านๆมาเราใช้ match จะเห็นได้ค่อนข้างชัดตอนใช้ match\n\nlet foo = Some(’a’);\nlet bur = match foo {\n  Some(word) ⇒ word,\n  None ⇒ ‘f’,\n}\n\nจะทำให้เรามั่นใจว่า เราจะไม่เจอ point null อีกต่อไป\n\nต่อมาที่เจอไม่กว่ามาคือ Fallibility \n  คือการที่ค่าเกิดการผิดพลาด ใน Rust จะใช้ Result<T, E> เป็นตัว handle เราจะเห็นได้บ่อนต้องสร้าง signature ของ function.\n\nfn doing () → Result<u32,Error>;\n\nหน้าตาจริงๆ ของ type นี้คือ\nenum Result<T, E> {\n   Ok(T),\n   Err(E),\n}\n\nคือเราจะบอกว่า function นี้อาจจะเกิดได้ สำเร็จหรือไม่ก็ได้ เตรียมรับมือด้วยนะ อะไรแบบนั้น\n\nที่เราพูด เรื่อง wrap type ไปเพราะเราปูมาถึงประเอกของเราที่ควรใช่ให้ถูกที่คือ unwrap \n\nเวลาที่เราใช้กับ Option หรือ Result ถ้าใช้ .unwrap() เวลาที่ เป็น None หรือ Error จะ panic ทันทั แล้วโปรแกรมก็จะตู้ม หยุดทำงาน แล้วทำไมเราต้องการให้ โปรแกรมหยุดทำงานละ จะมีหลายๆ Case แต่ที่ใช้บ่อย เช่น\n\nตอน ตั้งค่าการทำงานครั้งแรก แล้วตั้งไม่ครบก็จะให้เปิดการทำงานของโปรแกรมไม่ได้\n\nstart(std::env::var(\"key\")).expect(”key not found”);\n\nก็จะ panic ออกมาแต่เห็นอะไรไม่เหมือนเดิมใช่ไหมนั้น แปล expect จะดีกว่า unwrap นิดหน่อยตรง โยนข้อความ ออกมาได้\n\nมาถึงตรงสังเกตุกันไหมว่า ใน rust ค่อนข้างใช้ generic type ค่อนข้างเยอะเลย เพราะสะดวกในการใช้งานสำครับ lang ที่เป็น static type แบบ Rust ไม่ต้องกำหนดอะไรเยอะแยะล่วงหน้าแต่ก็ได้ ความสะดวกสบายในแบบ dynamic และเป็น type safe ไปในตัว (จริงก็ไม่ได้ dynamic หรอก เพราะเวลาที่ complier มันสแกนจะ gen code ให้เรา แต่เราแต่รู้สึก dynamic เฉยๆ ถ้าอย่าง dynamic จริงๆ จะพวก dyn ตอนประกาศ trait อยู่ แต่จริงๆไม่ได้ Dynnamic ขนาดนั้นนน) \n\nจริงในส่วนนี้จะมีพวก Error เข้ามาเกี่ยวด้วยแต่ส่วนตัวคิดว่าเก็บไว้หัวข้อแยกดีกว่าน่าจะสวยและอ่านง่ายกว่า\n\nแล้วจะ tuple ในแบบฝึกมา เทียบกับ option ด้วย ที่ว่า option คล้าย tuple (1,2) เพราะมี 2 ตำแหน่งประมาณนั้น\nส่วนตัวคิดว่ามีให้พูดต่อแต่ยังไม่น่าสนใจเกี่ยวหัวข้อข้างบนนั้น เลยตัดทิ้งไป\n\nเดินทางมาถึงจนครึ่งทางของแบบฝึกหัดแล้ว XD 5555\n\n[cheats](https://cheats.rs/)\n\n[100-exercises](https://rust-exercises.com/100-exercises/05_ticket_v2/06_fallibility.html)","status":"published","created_at":"2026-07-14T07:53:12.439472+00:00","updated_at":"2026-07-14T07:53:12.439472+00:00","synced_at":"2026-07-14T07:53:12.439472+00:00"},{"id":49,"title":"Enum","slug":"enum","body":"---\ntags:\n  - 100excer\n---\nRust จะมี โครงสร้าง data type แบบนนึงที่สามารถระบุได้ว่าภายในนั้น สามารถเปลี่ยนเป็นอะไรได้บ้าง\nนั้นก็คือ Enums\n\n![659184177_122123080551065881_6287357711015880775_n](https://drive.google.com/thumbnail?id=1Ht62tKaMSXr3YV2ETDoVvLgdTc7Bm23O&sz=w1000)\n\nตัว Enums จะคล้ายการ hold struct ไว้ในตัวโดยเลือกใช้ได้ว่าจะเลือกตัวไหนออก \n\nEnum State {\n  Waiting\n  Doing { agent : String }\n  Finished\n}\n\nถ้าทำแบบนี้จะเปลือง Memory ไหมมีเพราะมีการใช้หลายๆตัว (อาจจคิดว่าจองเหมือน array) จริงๆต้องบอกว่า เป็นการบอกว่า State สามารถเป็นอะไรได้บ้างมันจะจองพื้นของตัวที่ใหญ่ที่สุดไว้ เช่น\n\nWaiting 1 btye\nDoing { agent : String } 25 btye\nFinished 1 btye\n\nแปลว่า พื้นที่ของ Enum ตัวที่ที่จองจะ จอง meta data + max size have possible\n\nต่อมา มี Enum แล้วตัวชูโรงอีกตัวคือ  match เป็นการจับคู่ว่า ถ้า ถ้านั้นตรงกับ Enum ตัวไหนจะให้ทำอะไรเช่น\n\nmatch State {\n  Waiting ⇒ “wait”.to_string() ,\n  Doing { agent } ⇒ agent ,\n  Finished ⇒ “Finished”.to_string() \n}\n\nแต่ถ้าเราสนใจแต่อันเดียว สามารถทำได้ 2 ท่าคือ\n\nmatch State {\n  Waiting ⇒ “wait”.to_string() ,\n  _ ⇒ “ Idk”\n}\n\nหรือ\n\nif let State::Waiting = state {\n  “wait”.to_string() ,\n}\n\nเพราะ ปกติถ้าเรา ไม่ได้ handle ทุกขาที่เป็นไปได้ complier จะไม่ให้ผ่าน\n\nStyle\nBoth if let and let/else are idiomatic Rust constructs.\n\n[cheats](https://cheats.rs/)\n\n[100-exercises](https://rust-exercises.com/100-exercises/05_ticket_v2/04_if_let.html)","status":"published","created_at":"2026-07-14T07:51:35.309084+00:00","updated_at":"2026-07-14T07:51:35.309084+00:00","synced_at":"2026-07-14T07:51:35.309084+00:00"},{"id":48,"title":"auto trait","slug":"auto-trait","body":"---\ntags:\n  - 100excer\n---\nauto trait\n\ntrait หรือ เรียกอีกอย่างว่า Interface ก็ได้ ใน rust นี้ใช้ trait เป็น feature เยอะมากๆ\nแล้วหลายๆอย่างก็ implement มาให้เรียบร้อยแล้ว\nและ ที่น่าสนใจหลายๆ trait ไม่ได้ใช้เพื่อ implement เท่านั้น\n\n![655935559_122121651507065881_674629286950496735_n](https://drive.google.com/thumbnail?id=1TGGUM2ZXkljOdnpc1A47A6aDkghZt_km&sz=w1000)\n\nณ post นี้เราจะพูดถึง\n\nFrom trait   ← may be  DTO FROM X INTO Y\nClone trait ← maybe Colne all data in stack and heap\nDeref trait ← maybe  some type can use instead such as &String , &str\nSized trait  ← auto mark for compiler about fix size if dynamic size = ?Size\nCopy trait ← auto in what size is complier know.  subtrait of Clone\nDrop trait ← auto if use drop dosen’t copy or another way using copy can’t drop. \n\nFrom\n\nจาก X  แปลงเป็น Y\nเช่นตัวอย่างจาก U32 แปลงเป็น WrappingU32\n\nimpl From<u32> for WrappingU32 {\n  fn from(value: u32) -> Self {\n     WrappingU32 { inner: value }\n  }\n}\n\n—-—-—-—-—-—-—-—-—-—-—-—-—-\n\nlet MyNumber : WrappingU32 = 16u32.into();\nจริงๆก็มีถ้าอื่นอีก แต่จำง่ายว่าแปลงจากไป Impl\nและถ้า Impl From หรือ Into จะ auto impl ตรงกันข้ามให้ \ncomplier อวดฉลาด XD\n(ถ้าอยาก handle error must use TryFrom some thing like that)\n\nClone\n\nอันนี้น่าคุ้นชิดกัน เช่น\nlet me  = “jojo”.to_string();\nlet another_me = me.clone();\n\nตัวนี้จะ สร้าง stack ที่ชี้ไป heap ของ string และ heap ใหม่ทั้งหมดเลย เรียกว่าเปลืองๆ\n(แต่อย่าเพิ่งกลัว clone ไป ถ้าใช้กับ พวก smart point มันจะใช้แค่ stack แต่ละไว้ก่อน)\n\nและพวก สามหน่อ mark trait for complier Sized Copy และ Drop\nปกติมันจะ Impl Sized for Something { // dosen’t any impl just mark for complier }\nพวกนี้ใช้ตามที่พิมพ์ไว้ข้างต้นเลย แต่จะขยายความให้\n\nส่วนใหญ่แล้ว Sized Copy Drop  complier จะเป็นคน auto impl ให้อยู่แล้ว\nเช่น string ออก scrope { …  } จะ drop แต่สังเก-ตุ ดีๆ string เป็น ?Size ( Dynamic ) เลยมี Drop ให้\nแต่พวก Copy ไม่สามารถใช้ Drop ได้คือพวก Fix Size เช่น u32 .\n\nfn main() {\n  let x:u32 =3;\n  move || { let r = x;};\n  println!(\"{}\", x);\n}\n\nถ้าเป็น String ระเบิดไปแล้วว ; x จะถูก copy เข้า scrope ใหม่โดย move แต่ไม่ moev จะเป็น reference\n\nส่วน Deref trait  จะเรียกว่า ตัวช่วย ในการใช้ type ที่คล้ายๆกันแทนกันตอน reference ก็ได้เช่น &string และ &str;\n\nfn main() {\n  let s = \"a\".to_string();\n  if &\"a\" == &s {\n     println!(\"true\");\n  }\n}\n\nจริงๆคิดว่าจะเล่า Generics and associated types ไปเลยดีไหม แต่คิดว่าไว้หลังๆ เลยดีกว่า\nเพราะ Generics คิดว่าใช้บ่อยอาจจะแยกมา ส่วน associated types ก็มีบ้างแต่เอามารวมกันน่าจะงงมากๆ XD\n\n[100-exercises](https://rust-exercises.com/100-exercises/04_traits/14_outro.html)","status":"published","created_at":"2026-07-14T07:49:59.884101+00:00","updated_at":"2026-07-14T07:49:59.884101+00:00","synced_at":"2026-07-14T07:49:59.884101+00:00"},{"id":47,"title":"String slices","slug":"string-slices","body":"---\ntags:\n  - 100excer\n---\nวันนี้มาคุยเรื่อง String slices กัน จริงๆ อ่านไปถึง copy แล้วดูแล้วเนื้อหามันเยอะเลยเอาเรื่อง String slices ก่อนละกัน\n\nString slices เรียกอีกชื่อก็คือ **string literals** นั้นแล  ส่วนตัวจำว่า String slices  คือ สไลด์คำบ้างมาบางส่วนเพื่อดูจึงไม่สามารถแก้ไข (mut) คำได้\n\n![651005456_122120988129065881_8302833320870411494_n](https://drive.google.com/thumbnail?id=1_wPW2SPyVPefjX1LByGQfS6GOaRRsfEQ&sz=w1000)\n\nเพื่อมีคนยังไม่รู้ ทำไมใช้คำว่า string  มันก็หลาย array นั้นแหละ คือการเอา Btye มาเรียงต่อกันเป็นเชือก \n[ A - B - C - D - E ] หรือเรียกว่า array of btye ก็ได้\n\nกลับเข้าเรื่อง String slices ในแบบฝึกหัดก่อนๆ เราจะ พูด ถึง String ธรรมดาไป คือพวก “something”.to_string()\n\nจะมี โครง สร้าง คือ [ pointer , len , capacity ]  บอกได้ว่าอยู่ตรงไหน ตอนนี้คำยาวเท่าไหร่ และ สามารถเก็บได้กี่คำ\n\nแล้วพอมาเป็น &str ( **string literals** ) [ pointer , len ]  ก็แค่ชี้ตรงๆ ไปส่วนหนึ่งของ string โดยที่เปลี่ยนแปลงไม่ได้ เช่น  CDE จาก  ABCDE เลยไม่จำเป็นต้องมี capacity\n\nแล้วเวลา **string literals is dead มันก็ได้ไม่ได้ drop string ตัวนั้นไป เพราะว่าไม่ได้เป็น owner ของ string นั้น แค่ยืมมา โชว์เฉยๆ**\n\nต่อไปน่าจะทำ พวก trait maker complier เรื่องนี้อ่านตอนแรกๆก็งงเหมือนกัน ว่า เอ้า พวกนั้น  impl trait แล้วเอาไปใช้ทำอะไร ออ จริงๆ ไว้ให้  complier รู้ว่า struct นี้มันทำได้ \n\n[100-exercises](https://rust-exercises.com/100-exercises/04_traits/06_str_slice.html#string-slices)","status":"published","created_at":"2026-07-14T07:48:40.294173+00:00","updated_at":"2026-07-14T07:48:40.294173+00:00","synced_at":"2026-07-14T07:48:40.294173+00:00"},{"id":46,"title":"Trait","slug":"trait","body":"---\ntags:\n  - 100excer\n---\n4.1. Trait\n4.2. Orphan rule\n4.3. Operator overloading\n4.4. Derive macros\n4.5. Trait bounds\n\n\n![646885609_122120237979065881_3150594728204375275_n](https://drive.google.com/thumbnail?id=18SLOQdNfcwnBuN1Vx4tIWv2esvs0JB0m&sz=w1000)\n\nช่วยตั้งใจอยากจบอันนี้อย่างจริงจัง แต่ไม่ได้รีบจนไม่ได้คุณภาพเลยคิดว่าถ้าว่างก็ถยอยทำความเข้าใจ\nแล้วค่อยๆลงตามทที่ไหว\n\nแต่มัดตัวข้อพวกนี้เข้าด้วยกันเลยเพราะว่ามันน่าจะเข้าใจง่าย\n\nจริงๆ แล้ว Rust ถือ functional programing เช่นเดียวกับ Go แต่ผมจะไม่พูดถึง GO ละไว้ในฐานที่เข้าใจ\nแต่ Struct ใน Rust ดันสามารถมี Interface ได้คล้ายๆ OOP โดยใช้ abstract function เป็น signature สัญญาว่าหน้าตาของ method จะรับและส่งค่ากลับแบบนี้แน่ๆ\n\ntrait DoA {\n  fn a_walk() → something;\n  fn a_look();\n} \n\nตอนใช้ก็ impl DoA for A {} แล้วก็ไปเขียน function ต่อกันเอง\n\nแล้ว Trait มันช่วยอะไรบ้าง เอาตามที่อยู่ในบทเรียน 100 exercise\n\nเอาตัวที่เห็นได้ชัดๆ ใช้กันบ่อยคือ Trait bounds เป็นการกำลัง พฤติกรรมของ input ว่าต้องมีอะไรบ้าง เช่น\n\ntrait Count {\n  fn medium(&self) →self\n}\n\nimpl Count for i32 {\n  […]\n}\n\nimpl Count for i64 {\n  […]\n}\n\nfn wait_for(number: T + Count)\n\nจากตัวอย่ง waite_for เราจะรับ type อะไรก็ได้ที่ Impl Count ไปก็พอ\n\nส่วน Derive macros ก็ใช้บ่อย เราสามารถใช้ flag ตัวนี้ในการช่วย impl trait และ auto gen code ให้เราได้ เช่น\n\n#[derive(PartialEq)]\n  struct Ticket {\n  title: String,\n  description: String,\n  status: String\n}\n\nแล้วจะแตกตัวเป็น\n\nimpl ::core::cmp::PartialEq for Ticket {\n#[inline]\n  fn eq(&self, other: &Ticket) -> bool {\n   self.title == other.title\n   && self.description == other.description\n   && self.status == other.status\n  }\n}\n\nถ้าอยากไปลองศึกษาเพิ่มเติมใช้ [`cargo-expand`](https://github.com/dtolnay/cargo-expand)\n\nในการช่วย ดูตอนมัน gen code ให้ได้\n\nและ ตัวที่คุณอาจจะไม่ได้สงสัยหรือคิดถึงมัน คือ Orphan rule จะช่วยทำให้การใช้ Trait ของเรายากขึ้น แต่จะไม่ปวดหัวหลังจากที่ impl ไปแล้ว เพราะ มันจำกัดให้เราว่า ต้องมี \n” type หรือ trait “ \nอย่างน้อย 1 อย่างเป็นของเรา ถึงจะ impl ได้\n\nเช่น\n\nimpl Display for U32\ncomplier จะระเบิดแล้วด่าเราทันที แต่แก้ไ้ด้วยการ เอา ไป wrap \nimpl Display for MyU32\n\nกฎนี้จะลดอาการปวดหัวจากที่ impl กระโดดไปกระโดดมาได้\nเช่น เอา external type และ  trait มาใช้ทั้งหมดเลย แล้วมา Impl ไปมาระหว่างกัน\nแต่จริงๆ ถ้าใช้ wrap เยอะก็อาจจะเกิดขึ้นได้แต่อย่างน้อยมันก็อยู่ใน crate ของเรา\n\nสุดท้าย Operator overloading ไม่มีอะไรมาก ก็แค่ พวก + - * / ของ rust มันคือ trait \nแต่ rust ทำ แปลง สัญลักษณ์พวกนี้ให้แบบ อัตโนมัติเช่น\n\na == b จะเป็น a.eq(b); เป็นต้น\n\n[100-exercisest](https://rust-exercises.com/100-exercises/04_traits/01_trait.html)\n\n","status":"published","created_at":"2026-07-14T07:47:09.226425+00:00","updated_at":"2026-07-14T07:47:09.226425+00:00","synced_at":"2026-07-14T07:47:09.226425+00:00"},{"id":45,"title":"Destructors","slug":"destructors","body":"---\ntags:\n  - 100excer\n---\nDestructors อ่านไม่รู้คำแปลเลยเดา ว่า สลายโตว้ ?\n\n![640106936_122119116165065881_6056476008008135817_n](https://drive.google.com/thumbnail?id=1SKPcOCbyM4sv47O4jT0uoj9UE4UOmUmG&sz=w1000)\n\nde -> ไม่\nstruct -> โครง ??\nเติม tor 🤣\n\nแต่ใน บทความเกี่ยวกับการ var ที่ถือ Owner จบ scope แล้วถูก drop (dead)\n\nในที่นี้จะเรียก dead ละกัน เพราะใน rust จะมี lifetime อยู่จะได้พูดง่ายๆ อยู่หรือตาย 5555\n\nก่อนอื่นบทที่แล้วจะคุยกันเกี่ยวกับเรื่องของ Heap Stack บลาๆ\nแต่ปัญหาคือ Heap มันเคลียร์ค่าของตัวเองแบบ auto ไม่ได้เลยต้องมีวิธีต่างๆ\n\nของ Rust เรื่องใช้ ระบบ ownership คือ จบ scope สั่ง drop\n\nfn main() {\n   // `x` is not yet in scope here\n   let y = \"Hello\".to_string();\n   let x = \"World\".to_string(); // <-- x's scope starts here...\n   let h = \"!\".to_string(); //   |\n} //  <-------------- ...and ends here\n\nถ้าอย่างนั้นต้องรู้ก่อนว่า Scope คืออะไร \n1. the block (i.e. the code between {}) \n2. ย้าย owner ให้ตัวอื่น \nownership of the variable is transferred to someone else (e.g. a function or another variable)\n\nถ้ายัง งงใน ข้อ 2 คือถ้า ไม่มี & หรือคำสั่งในการช่วยยืม\nพอเอาไปใช้มันจะ take owner ไปเลย ( พวกที่อยู่ใน heap นะ )\n เช่น\n\nlet s = \"owner\".to_string;\nlet s2 = &s;  <- ยืมมาไม่ได้ take owner\n\nแต่ let s2 = s; จะ take มาเลย\n\nจำประมาณนี้ก่อนดีกว่าเพราะจริงมีอีกหลายท่าที่เช่นกับ ownership\n\nหรือของ function\nfn compute(t: String) {\n   // Do something [...]\n}\n\nfn main() {\n    let s = \"Hello\".to_string(); // <-- s's scope starts here...\n                //                    | \n    compute(s); // <------------------- ..and ends here\n                //   because `s` is moved into `compute`\n}\n\nต่อมาเพื่อ var ต่าง out of scope\nRust จะเรียกใช้ Destructors ในการสั่ง std::mem::drop\n\nส่วนตัวคิดว่าทำความเข้าใจแค่นี้ไปก่อนเพราะจะถยอยวิธีการต่างในบทต่อๆไปอยู่ดี\n\nhttps://rust-exercises.com/100-exercises/03_ticket_v1/11_destructor.html#destructors\n\nอันต่อไป \nWrapping up\nอย่าลืมไปเล่นกัน และกดดาวน์ให้คนที่ Git ให้เราศึกษากันด้วย 🤣\n\n[100-exercises](https://rust-exercises.com/100-exercises/03_ticket_v1/12_outro.html)\n\n","status":"published","created_at":"2026-07-14T07:45:54.077464+00:00","updated_at":"2026-07-14T07:45:54.077464+00:00","synced_at":"2026-07-14T07:45:54.077464+00:00"},{"id":44,"title":"References","slug":"references","body":"---\ntags:\n  - 100excer\n---\nวันนี้จะสบายๆหน่อย เพราะ &References ใน example นี้มีไม่เยอะ\nReferences คือชี้ไปตำแหน่งของ memory ( as a pointer ) \n\n![639349950_122119017825065881_5468423463010787346_n](https://drive.google.com/thumbnail?id=1OwrD-PQG980e1LaUYdlqcwSsBOo9yIMd&sz=w1000)\n\nที่คุ้นๆตากันคือ &str , &mut what_do_you_want\n\nขนาดของ & = 8 bytes \n\nแล้วอย่าลืมเข้าไปทำแบบฝึกหัดกันด้วยเพราะ\n\nNot all pointers point to the heap\nThe example above should clarify one thing: not all pointers point to the heap.\nThey just point to a memory location, which may be on the heap, but doesn't have to be.\n\n[100-exercises](https://rust-exercises.com/100-exercises/03_ticket_v1/10_references_in_memory.html#not-all-pointers-point-to-the-heap)\n\n","status":"published","created_at":"2026-07-14T07:44:38.954221+00:00","updated_at":"2026-07-14T07:44:38.954221+00:00","synced_at":"2026-07-14T07:44:38.954221+00:00"},{"id":43,"title":"stack","slug":"stack","body":"---\ntags:\n  - 100excer\n---\nวันนี้ก็มาถึงจนได้ วันที่เราพูดถึง Heap\nเรามี stack แล้วเราจะมี Heap ไปทำไม\n\n![640371483_122118920085065881_8041463593851518114_n](https://drive.google.com/thumbnail?id=1t0L99zjS6Vra6W_gi1pYzgWfiLlkqPV3&sz=w1000)\n\nปัญหาของ stack \nคือต้องรู้ size ของข้อมูลถึงจะใช้ เพราะ\nเราต้องจองขนาดให้พอดีกับข้อมูลเปะๆ และ stack ต้องการเรียงลำดับด้วย ในการคืนค่าแบบเข้ามาทีหลังแต่คืนก่อน ทำให้ความในการปรับขนาดหายไป\n\nแต่ Heap สามารถจองขนาดได้ยืดหยุ่นและไม่ต้องเรียงตามลำดับ\nจึงเป็นสาเหตุนึงที่ Heap ทำงานได้ช้ากว่า stack ในการหาที่ว่างในการจอง การล้างข้อมูลที่ไม่ใช้ออกการ Heap\n\nตัวอย่าง\n\nlet mut s = String::with_capacity(5);\n\nString จองขนาดไว้ 5 bytes เราจะไปจอง Mem ใน heap และถ้าจองสำเร็จ\nจะ คืนค่าใน stack เพื่อชี้ไปว่าข้อมูลอยู่ที่ไหนใน heap\n** heap จะไม่ ยกเลิกการจองอัตโนมัติ จึงเลยทีการใช้ garbage collector\nเพื่อชวยในการทำส่วนนี้ แต่ใน RUST มีอีกวิธีการคือใช้ กฎ ownership เพื่อให้ทำกระบวนล้าง heapได้อย่างมีประสิทธิภาพมากขึ้น\n \nStack =  | pointer | length | capacity | \npointer คือ ที่อยู่ใน heap\nlength คือ ความยาวของ string\ncapacity คือ ความจุที่จองไว้\n\nและในแบบฝึกหัดได้ ลอง \n\ns.push(\"hey\");\n\nเข้าไป length = 3 , capacity = 5\nเพราะ string ยาว 3 bytes แต่เราจองพื้นที่ใน heap ไป 5 bytes\n\n============================================\n\nusize = integer can have a different size. Most modern machines use either a 32-bit or a 64-bit address space.\n\nNo std::mem::size_of for the heap\n\n[100-exercises](https://rust-exercises.com/100-exercises/03_ticket_v1/09_heap.html)\n","status":"published","created_at":"2026-07-14T07:41:28.06596+00:00","updated_at":"2026-07-14T07:41:28.06596+00:00","synced_at":"2026-07-14T07:41:28.06596+00:00"},{"id":42,"title":"Memory layout","slug":"memory-layout","body":"---\ntags:\n  - 100excer\n---\nMemory layout คือเรื่องสำคัญเรื่องนึง\nที่เป็นสาเหตุที่ต้องมี ownership ขึ้นมาด้วย\nเพราะการใช้ heap เราต้องจัดการจองหรือเอาออกเองงั้นมันค้างไปตลอด\n\n![637423601_122118744909065881_8504142771900355694_n](https://drive.google.com/thumbnail?id=1nUu-AMkK16A5xToLsF534FdYVC6DyI3K&sz=w1000)\n\nแต่วันนี้เราจะมาพูดถึง stack กัน\nทำไม stack ถึงต่างกับ heap\n\nstack จะแบบเข้ามาทีหลังแต่ออกไปก่อน ( lifo )\nอารมณ์แบบเราใส่น้ำแข็งเข้าแก้วแล้วเทออก\n\nเพราะเวลาเราเรียกใช้ function พวก data ของ funciton\nจะถูกวางซ้อนกันขึ้นมา แล้วตัวไหนใช้เสร็จก็ถูก ดึงออกไป\nทำไม บอกว่าเป็น lifo\n\n** That stack frame stores the function's arguments, local variables and a few \"bookkeeping\" values.\nWhen the function returns, the stack frame is popped off the stack1. **\n\nตัวอย่าง\n\nfn1(fn2(fn3())))\n\nเวลาเรียกเข้า stack frame\n=> fn1\n=> fn2\n=> fn3\n\nส่วนของ fn3 ใช้เสร็จออกไป แล้วค่อยๆไล่ไปจนถึงตัวแรก\n** จริงๆจำนี้ง่าย แต่อาจจะไม่ถูกหลักการทั้งหมด แต่ในขั้นก็เพียงพอแล้ว\n\nแล้วทำไม stack ถึง เร็วกว่า heap ถ้าพูดแบบง่ายๆคือ\nมันอยู่ติดกันและรู้ลำดับ แปลว่าทำเสร็จปุบปับเอาออกได้เลย\n\nแต่ของ heap อาจจะต้องไปหาที่ เพื่อจอง ไม่ได้แบบใส่เข้าไปได้เลยโดยไม่ต้องคิด\n\nแล้วอะไรสามารถใช้ stack ได้ ?\nข้อมูลอะไรก็ได้ที่เราสามารถคำนวณตอน compile time ได้\nเช่น u32 เรารู้ ขนาดแน่นอน (u32::max เราจะเท่านั้นเสมอถึงใช้ไม่ครบก็ตาม) \n\nในแบบฝึกหัดมี tool ที่น่าสนใจคือ\n\nFor a u8, for example:\n\n// We'll explain this funny-looking syntax (`::<u8>`) later on.\n// Ignore it for now.\nassert_eq!(std::mem::size_of::<u8>(), 1);\n1 makes sense, because a u8 is 8 bits long, or 1 byte.\n\n[100-exercises](hhttps://rust-exercises.com/100-exercises/03_ticket_v1/08_stack.html#memory-layout)\n\n","status":"published","created_at":"2026-07-14T07:40:15.813941+00:00","updated_at":"2026-07-14T07:40:15.813941+00:00","synced_at":"2026-07-14T07:40:15.813941+00:00"},{"id":41,"title":"Mutable","slug":"mutable","body":"---\ntags:\n  - 100excer\n---\nMutable ตอนนี้มาดูการที่เราจะเปลี่ยนค่าของ feild กันบ้าง\n ถ้าใช้ Pub feild ก็จะเป็นอีกแบบแต่\nวันนี้เราจะลอง Private Feild ที่ เปลี่ยนโดยใช้ funciton อีกที\n\n![636726666_122118649137065881_862487564025391578_n](https://drive.google.com/thumbnail?id=1IPkskik_y_g4vTUzLLNdYQHEY1ZFF_vH&sz=w1000)\n\n1. Taking self as input.  \nโยนตัวเองและ Ownership ไป set ใน var ใหม่\nข้อดีสามารถ chain ได้   let z = a.b.c.e;\n\nตัวอย่าง\n\nimpl Ticket {\n    pub fn set_title(mut self, new_title: String) -> Self {\n        // Validate the new title [...]\n        self.title = new_title;\n        self\n    }\n}\n\nlet ticket = ticket\n    .set_title(\"New title\".into())\n    .set_description(\"New description\".into())\n    .set_status(\"In Progress\".into());\n\n2. Taking &mut self as input.\nค่าจะอยู่ที่เดิมเสมอเพราะยืมมา\n\nตัวอย่าง\n\nlet mut ticket = Ticket::new(\n    \"Title\".into(),\n    \"Description\".into(),\n    \"To-Do\".into()\n);\n\nticket.set_title(\"New title\".into());\nticket.set_description(\"New description\".into());\nticket.set_status(\"In Progress\".into());\n\nimpl Ticket {\n    pub fn set_title(&mut self, new_title: String) {\n        // Validate the new title [...]\n        \n        self.title = new_title;\n    }\n}\n\n\n[100-exercises](https://rust-exercises.com/100-exercises/03_ticket_v1/07_setters.html)","status":"published","created_at":"2026-07-14T07:37:19.813292+00:00","updated_at":"2026-07-14T07:37:19.813292+00:00","synced_at":"2026-07-14T07:37:19.813292+00:00"},{"id":40,"title":"Ownership","slug":"ownership","body":"---\ntags:\n  - 100excer\n---\nวันนี้เราเดินทางมาถึง \nOwnership กันแล้วและเพื่อนของมันคือ Borrowing\n\n![634894470_122118562737065881_7813149617012516442_n](https://drive.google.com/thumbnail?id=1JQAIPERD1SJvWiVL-s-4IQ7-wztcSbUN&sz=w1000)\n\nคือ กระบวนการอย่างนึงที่ทำให้ rust ไม่ต้องใช้ garbage collect ในการจัดการ พื้นที่ของ memory ทำให้มีประสิทธิภาพใกล้เคียงกับ ภาษาที่ต้องการจัดการ memmory ด้วยตนเอง แต่แรกกับมากับ กฎที่ต้องข้างปวดหัวทีเดียว\n\nThe perks of Rust's ownership system\n- Data จะไม่ถูกเปลี่ยน ขณะที่กำลังอ่าน\n- Data  จะไม่ถูกอ่าน ขณะที่กำลังเปลี่ยน\n- Data จะไม่สามารถเข้าถึงได้เมื่อถูกทำลาย \n\nผลพ่วงพวกนี้มาจาก ระบบ  borrow checker เหมือนกัน\n\nlet a = \"hello, world\".to_string(); // <- `a` is the owner of the String\nlet b = a;  // <- `b` is now the owner of the String\n\nอีกแบบที่หลายคนเข้ามาอาจจะงงคือ\n\nimpl Ticket {\n    pub fn description(self) -> String {\n        self.description\n    }\n}\n\nถ้า self ไม่มี & แปลว่าไม่ได้ยืม จะทำการ ย้าย ownership ไปอีกตัวทันที แปลว่า\n\nObject ที่ถือ  feild  นั้นเสีย Ownership ไปแล้ว ทั้ง Object ก็เสียไปด้วยเหมือนกัน\n\nทำให้ ถ้า เรียกใช้อีกทีละ ระเบิดตู้ม\n\nเช่น \nlet ticket : impl Ticket = Ticket::new();\nlet description = ticket.description;\n\nticket.do_something <= BOommm!\n\nพักหายใจกันสักพักยังไม่จบ มี Borrowing\nต่อ \n\nจำก็ \n\nReferences are tagged with their privileges1:\n\nImmutable references (&) allow you to read the value, but not to mutate it\nMutable references (&mut) allow you to read and mutate the value\nGoing back to the goals of Rust's ownership system:\n\nData is never mutated while it's being read\nData is never read while it's being mutated\nTo ensure these two properties, Rust has to introduce some restrictions on references:\n\nYou can't have a mutable reference and an immutable reference to the same value at the same time\nYou can't have more than one mutable reference to the same value at the same time\nThe owner can't mutate the value while it's being borrowed\nYou can have as many immutable references as you want, as long as there are no mutable references\n\nเยอะเหมือนกัน\nสรุปคือ\n1. & กี่ตัวก็ได้ถ้าไม่มี &mut\n2. ใน scope เดียวกัน จะ &mut ได้ทีละตัว\n3. & และ &mut พร้อมกันไม่ได้\nที่จริงมีพวก lifetime อีกข้ามไปก่อนละกัน\n\nแล้วเราจะได้ความสามารถ ป้องกันพวก use after free, dangling pointers, double frees, and other memory-related bugs\n\nถ้าไครเขียน C หรือ จัดการ mem มาเองจะพอเข้าใจ แล้วแถม บอกเลย cargo ใน rust สะดวกมากๆ\n\nใครอยากอ่านเพิ่มเติมก็ไปตามกันได้ที่ลิงค์นี้ วันนี้เขียนเยอะ เพราะ บทนี้มันเยอะจริงๆ  XD\n\n[100-exercise](https://rust-exercises.com/s/03_ticket_v1/06_ownership.html#ownership)","status":"published","created_at":"2026-07-14T07:35:35.527184+00:00","updated_at":"2026-07-14T07:35:35.527184+00:00","synced_at":"2026-07-14T07:35:35.527184+00:00"},{"id":39,"title":"Encapsulation","slug":"encapsulation","body":"---\ntags:\n  - 100excer\n---\nกลับมา ต่อ 100 exercise หลังจากหายไป ทำ game jam  XD เพิ่งพักพื้นร่างกายกลับมา\n\n วันนี้มาถึง Encapsulation ( การห่อ แต่อยากเรียกห้องปิดตายมากกว่า )\nเพราะ เวลาจะเรียกอะไรไม่สามารถเรียกตรงๆได้ต้องมี function มาทำการเข้าถึงแทน\n\n![634019229_122118471789065881_7372511413063900099_n](https://drive.google.com/thumbnail?id=1DT5LLr1qadM-AJTbX2pnwLuX_NiEqJCv&sz=w1000)\n\nแต่ทำไมเราต้องอยาก ซ่อน field เพื่อให้เขาไปแก้ไขไม่ได้\nจริงๆก็มีหลาย เหตุผล กันคนใช้รวมกันแล้วพัง, หรือกันเราลืมเอง หรืออื่นๆ\n\nตัวอย่าง \n\nstruct Ticket {\n    title: String\n}\n\nถ้าเราประกาศไปแล้ว จะมาเรียก Ticket.title จะหาไม่เจอทันที\n\nหรือจะสร้าง โดยใช้\n\nstruct Ticket {\n    title: \"show\".into()\n    ^^^ private field\n}\n\nจะ error อัดหน้าเราทันที\nเราอาจจะสร้าง constructors ไว้ตอน new struct เช่น\nlet ticket = Ticket::new(\"show\".into());\n\nและเวลาเข้าถึง field\n\nก็ ticket.get_tile(); ///  fn get_tile() -> String;\n\n\nป.ล. ภาพ พยายามหา Prompt ตามที่คิดออกมาไม่ได้เอารูปนี้ไปละกัน🤣\n\n[100-exercises](https://rust-exercises.com/100-exercises/03_ticket_v1/05_encapsulation.html#encapsulation\n)\n\n","status":"published","created_at":"2026-07-14T07:34:11.496135+00:00","updated_at":"2026-07-14T07:34:11.496135+00:00","synced_at":"2026-07-14T07:34:11.496135+00:00"},{"id":38,"title":"Modules","slug":"modules","body":"---\ntags:\n  - 100excer\n---\nมาถึง Modules คือ \nโครงสร้างของ Project เรามาจะมีเส้นทางไปไหนได้บ้าง หรือเรียกอีกอย่างว่า  Modules tree โดยจุดเริ่มต้น คือ crate (crate root)  หรือถ้าใช้ src/lib.rsจะเป็นอีกแบบ แต่จำ crate root จะง่ายกว่า\n\n![626033000_122116930449065881_6677981505138428680_n](https://drive.google.com/thumbnail?id=16_jtinMmxa4yVZFXrmQYZThx-IQ6jyXw&sz=w1000)\n\nแล้ว  Modules ย่อยเข้าไปจะมีการตั้งได้หลายแบบเช่น \nsrc/<module_name>.rs\nsrc/<module_name>/mod.rs\n\nแล้วลึกลงไปอีก\n[..]/<parent_module>/<module_name>.rs\n[..]/<parent_module>/<module_name>/mod.rs\n\nE.g. src/animals/dog.rs or src/animals/dog/mod.rs if dog is a submodule of animals.\n\nหรือใช้ แบบ inline ที่เจอบ่อยๆในการ test\n\n#[cfg(test)]\nmod tests {\n    // [...]\n}\n\nถ้าจะ imports ไปใช้\nuse crate::module_1::module_2::MyStruct;\nuse crate::module_1::module_2::*;\n\nVisualizing the module tree  \"cargo-modules\"\n\nword\nconstraints\nencapsulation\ndeclaration\nnested\n\n[100-exercises](https://rust-exercises.com/100-exercises/03_ticket_v1/03_modules.html#modules)\n\n","status":"published","created_at":"2026-07-14T07:24:09.849863+00:00","updated_at":"2026-07-14T07:24:09.849863+00:00","synced_at":"2026-07-14T07:24:09.849863+00:00"},{"id":37,"title":"Validation","slug":"validation","body":"---\ntags:\n  - 100excer\n---\nสงสัยไหมว่าปกติแล้วถ้า user ส่งค่าถูกต้องตาม type มา แต่ใส่ชื่อผิดรูปแบบ รายละเอียดยาวไป หรือ ใส่อะไรที่ type ไม่สามารถคุมได้มา เราจะคุมอย่างไร\n\n![625013596_122116824039065881_8745469455474850293_n](https://drive.google.com/thumbnail?id=1sy0A_4FslpVUmDqd8WH0XxTbGp7xviDP&sz=w1000)\n\nจึงมีการ Validation เกิดขึ้นว่า  Field ที่ใช้ถูกต้องหรือไม่ \n\nส่วนของการตรวจมีหลายรูปแบบ เช่น  เรียกใช้ ฟังชั้นตรงๆ, impl แล้วเรียกใช้ หรือ derive แล้วเรียกใช้ก็ได้\n\nส่วนตัว ชอบใช้ crate อันนี้ https://crates.io/crates/validator_derive\n\n[100-exercises](https://rust-exercises.com/100-exercises/03_ticket_v1/02_validation.html)\n\n\n","status":"published","created_at":"2026-07-14T07:22:06.010079+00:00","updated_at":"2026-07-14T07:22:06.010079+00:00","synced_at":"2026-07-14T07:22:06.010079+00:00"},{"id":36,"title":"Structs","slug":"structs","body":"---\ntags:\n  - 100excer\n---\nวันนี้ใน 100-exercises-to-learn-rust มาถึงเรื่องเกี่ยวกับ Structs\nในบทความที่เขาเขียนจะแบ่งหัวข้อได้\n\n![622691130_122116728819065881_3313779633644595434_n](https://drive.google.com/thumbnail?id=1kxIkClxFyToM7OT_hwpgKs77i-OmHGXS&sz=w1000)\n\nDefining a struct  <- ตั้งชื่อ struct\nDefining fields <- บอกว่า struct นั้นเก็บข้อมูลอะไรบ้าง\nInstantiation <- จะ new Struct นั้นอย่างไร อารมณ์ new Object\nAccessing fields <- เข้าถึง ข้อมูลใน Struct ( pub or none pub)\nMethods <- impl .... { } ใช้สร้าง function ให้ struct\nself <- ถ้าใน fn(self) จะทำให้ตัวที่ new มาใหม่ใช้ function นั้นได้\nStatic methods <- ต้องเรียกตรงจาก deine struct เลยเรียกจาก new ไม่ได้ \nEquivalence <- let is_open = Ticket::is_open(ticket);\nตัวสุดท้ายน่าสนใจ คือ ถ้า fn(self) สามารถใช้ ตัวที่ new ยัดแทนได้\n\n[100-exercises](https://rust-exercises.com/100-exercises/03_ticket_v1/01_struct.html#defining-fields)\n\n","status":"published","created_at":"2026-07-14T07:16:20.09671+00:00","updated_at":"2026-07-14T07:16:20.09671+00:00","synced_at":"2026-07-14T07:16:20.09671+00:00"},{"id":35,"title":"Overflow","slug":"overflow","body":"---\ntags:\n  - 100excer\n---\nOverflow is a topic I’m interested in.\n\n![621799980_122116322607065881_498569557492071124_n](https://drive.google.com/thumbnail?id=1ngdYrfce4E6vIhrgGQhzksE7Iadu6Tgj&sz=w1000)\n\nBefore learning about loops, I found that in many programming languages, an overflow causes a panic when a variable exceeds its allowed range.\nRust, however, provides options to handle this behavior:\n\nWith overflow-checks = true in the dev profile, the program panics at runtime on overflow.\n\nWhen set to false, the value wraps around according to the variable’s type.\n\n[100-exercises](https://rust-exercises.com/100-exercises/02_basic_calculator/08_overflow.html)\n","status":"published","created_at":"2026-07-14T07:13:19.323162+00:00","updated_at":"2026-07-14T07:13:19.323162+00:00","synced_at":"2026-07-14T07:13:19.323162+00:00"},{"id":34,"title":"Loops","slug":"loops","body":"---\ntags:\n  - 100excer\n---\nLoops, part 1: while\n\n![621831755_122116067439065881_628234436104550144_n](https://drive.google.com/thumbnail?id=1O1T6KX5E43B8Gz5JHxyKGTyXRSmKBimG&sz=w1000)\n\nPreviously, we learned about recursion, which works by having a function call itself to compute a result.\n\nAnother approach is to solve the problem using a while loop instead.\n\nIn Rust, variables must be marked with mut if they are intended to be mutable.\n\n\n[100-exercises](https://rust-exercises.com/100-exercises/02_basic_calculator/06_while.html)\n","status":"published","created_at":"2026-07-14T07:11:20.278136+00:00","updated_at":"2026-07-14T07:11:20.278136+00:00","synced_at":"2026-07-14T07:11:20.278136+00:00"},{"id":33,"title":"function","slug":"function","body":"---\ntags:\n  - 100excer\n---\nWe cover various topics such as:\n\n![622378991_122115972033065881_6185078883443379425_n](https://drive.google.com/thumbnail?id=17I62qQT-Gr4RRCftEvZz4WXUuASwI_WW&sz=w1000)\n\n- How to define a function\n- How to call a function\n- Which integer types are available in Rust\n- Which arithmetic operators are available for integers\n- How to execute conditional logic using comparisons and if/else expressions\n\nNow, we can implement a factorial function.\n\n[100-exercises](https://rust-exercises.com/100-exercises/02_basic_calculator/05_factorial.html)","status":"published","created_at":"2026-07-14T07:09:07.301167+00:00","updated_at":"2026-07-14T07:09:07.301167+00:00","synced_at":"2026-07-14T07:09:07.301167+00:00"},{"id":32,"title":"panics","slug":"panics","body":"---\ntags:\n  - 100excer\n---\nWe arrived at Panic.\n\n![620160762_122115872391065881_2151550153758826741_n](https://drive.google.com/thumbnail?id=1ClEaAxnFj9HNdnX-Up5CrSwHM3AtnsN3&sz=w1000)\n\nUse panic! only for defined, unrecoverable conditions—misusing it can stop the entire program, especially on platforms like Cloudflare.\n\n\n[100-exercises](https://rust-exercises.com/100-exercises/02_basic_calculator/04_panics.html)\n[cloudflare](https://blog.cloudflare.com/18-november-2025-outage/)\n\n","status":"published","created_at":"2026-07-14T06:53:35.736147+00:00","updated_at":"2026-07-14T06:53:35.736147+00:00","synced_at":"2026-07-14T06:53:35.736147+00:00"},{"id":31,"title":"pulse","slug":"pulse","body":"---\ntags:\n  - eps-rs\n---\n# ควบคุมความสว่าง LED ด้วย PWM [board esp32 c3 สุดถูกๆๆๆ](https://s.shopee.co.th/3qKWysljao) \n\nกลับมาเจอกันอีกครั้ง 👋\n\nครั้งก่อนเราได้รู้จักกับ ADC และสามารถอ่านค่าจาก Potentiometer ได้แล้ว แล้วจะเอาค่าที่อ่านได้ไปทำอะไรต่อ?\n\nวันนี้เราจะใช้ค่าที่ได้จาก ADC ไปควบคุม \"ความสว่างของ LED\"ซึ่งเบื้องหลังไม่ได้ใช้การลดแรงดันไฟฟ้า\n![pulse](https://drive.google.com/thumbnail?id=1UIu46-GnzhdQ8bTdnph6YkVraqWOX3sK&sz=w1000)\nแต่ใช้เทคนิคที่เรียกว่า\n\n> **PWM (Pulse Width Modulation)**\n> \n\n---\n\n## เปิดไฟครึ่งหนึ่งทำได้ไหม?\n\nโลกของ Digital มีแค่\n\n```\nเปิด\n\nปิด\n```\n\nแล้วถ้าอยากให้สว่าง \"ครึ่งหนึ่ง\" ล่ะ?\n\nจริง ๆ แล้ว MCU ไม่สามารถจ่ายไฟ 1.65V ออก GPIO ได้\n\nGPIO มีแค่\n\n```\n3.3V\n\nหรือ\n\n0V\n```\n\nเท่านั้น\n\nแล้วทำไม LED ถึงสว่างได้หลายระดับ? คำตอบคือ...\n\nเราไม่ได้ลดแรงดัน แต่เราเปิด–ปิด LED ให้เร็วมาก\n\n---\n\n## หลอกสายตาด้วยความเร็ว\n\nลองสมมติว่า ภายใน 1 วินาที เราเปิด LED\n\n```\nเปิด\n\nปิด\n\nเปิด\n\nปิด\n\n...\n```\n\nถ้าเปิดปิดแค่ 2 ครั้งต่อวินาที เราจะเห็นมันกระพริบ\n\nแต่ถ้าเปิดปิด 24,000 ครั้งต่อวินาที\n\nตาของเราจะตามไม่ทันอีกต่อไป\n\nสิ่งที่เห็นจึงกลายเป็น\n\n```\nไฟหรี่\n\nไฟสว่าง\n\nไฟสว่างมาก\n```\n\nทั้งที่จริง ๆ แล้ว LED ไม่เคยได้รับแรงดันครึ่งหนึ่งเลย  รับแค่ 3.3V หรือ 0V ตลอดเวลา\n\n---\n\n## PWM คืออะไร\n\nPWM ย่อมาจาก ( **Pulse Width Modulation )**\n\nแปลตรงตัวคือ การปรับ \"ความกว้าง\" ของพัลส์\n\nสิ่งที่เปลี่ยนไม่ใช่แรงดัน\n\nแต่เป็น \"ระยะเวลาที่สัญญาณอยู่ในสถานะ HIGH\"\n\n---\n\n## หนึ่งคาบของ PWM\n\nสมมติเราสร้าง PWM\n\n```\n24 kHz\n```\n\nหมายความว่า\n\n```\n1 วินาที\n\nแบ่งออกเป็น\n\n24,000 คาบ\n```\n\nดังนั้น\n\n```\n1 คาบ = 1 / 24000 ≈ 41.7 µs\n```\n\nภายในเวลาเพียง\n\n```\n41.7 ไมโครวินาที\n```\n\nPWM จะทำงานครบหนึ่งรอบ แล้วเริ่มรอบใหม่ทันที\n\n---\n\n## Duty Cycle คืออะไร\n\nภายในหนึ่งคาบ\n\nสัญญาณสามารถเป็น HIGH ได้หลายระดับ\n\nตัวอย่าง\n\nDuty = 25%\n\n```\n████________\n```\n\nDuty = 75%\n\n```\n████████___\n```\n\nDuty = 100%\n\n```\n████████████\n```\n\nHIGH เกือบทั้งคาบ\n\nLED จึงดูสว่างกว่าเดิม\n\n---\n\n## แล้ว Hardware รู้ได้อย่างไรว่า 25% อยู่ตรงไหน?\n\nตรงนี้เองที่หลายคนสงสัย\n\nภายในวงจร PWM จะมี Timer อยู่ตัวหนึ่ง\n\nสมมติเราตั้งความละเอียดไว้\n\n```\n5-bit\n```\n\nHardware จะมีตัวนับ\n\n```\n0\n\n1\n\n2\n\n...\n\n31\n```\n\nรวมทั้งหมด\n\n```\n32 ระดับ\n```\n\nทุกครั้งที่เริ่มคาบใหม่ Counter จะเริ่มจาก 0\n\nแล้ววิ่งไปจนถึง 31 แล้วเริ่มคาบใหม่ที่ 0 อีกครั้ง\n\n---\n\n## Duty ถูกนำไปใช้อย่างไร\n\nสมมติเราเขียน Duty = 16\n\n```\nCounter\n\n0........15 16........31\n\nHIGH         LOW\n```\n\nเปิด 16 ครั้งใน 1 คาบ นี่คือที่มาของคำว่า Duty Cycle\n\n---\n\n## ทำไมต้องมี Resolution\n\nถ้าใช้ 5-bit จะมีเพียง 32 ระดับ\n\nแต่ถ้าใช้ 10-bit จะมี 1024 ระดับ\n\nยิ่งมีระดับมาก\n\nการปรับความสว่างก็จะยิ่งละเอียด\n\n---\n\nภายในแต่ละคาบ Hardware จะใช้ Counter เปรียบเทียบกับค่า Duty เพื่อกำหนดว่าช่วงไหนควรเป็น HIGH และช่วงไหนควรเป็น LOW\n\nสุดท้ายสายตาของเราจะเห็นเป็น \"ความสว่างเฉลี่ย\" แทนที่จะเห็นการกระพริบของ LED 🦀⚡\n\n\n\n[DOC EPS-RS](https://docs.rs/esp-hal/latest/esp_hal/ledc/index.html)\n\n[ESP video](https://www.youtube.com/watch?v=8YU9NNJBs7c)\n[board esp32 c3 สุดถูกๆๆๆ](https://s.shopee.co.th/3qKWysljao)\nวันนี้ขอลาไปก่อนบาย\n\n","status":"published","created_at":"2026-07-12T10:03:36.272589+00:00","updated_at":"2026-07-12T10:03:36.272589+00:00","synced_at":"2026-07-12T10:03:36.272589+00:00"},{"id":30,"title":"Analog Signal","slug":"analog-signal","body":"---\ntags:\n  - eps-rs\n---\n## อ่านค่า Analog ด้วย ADC\n\nกลับมาเจอกันอีกครั้ง 👋. แต่อย่าลืมกดซื้อหัวชาร์จของกันด้วย จาก [ZMI](https://s.shopee.co.th/4qDo06LI4D)\n\n![esp-ep6](https://drive.google.com/thumbnail?id=1FEkHI2SPAIwbRAN1v5gLb6l8rRhHnXrq&sz=w1000)\n\nก่อนหน้านี้เราได้ลองใช้ **GPIO** กับปุ่มกดกันไปแล้ว ซึ่งเป็นการอ่านค่าแบบ **Digital Input**\n\nโลกของ Digital นั้นเรียบง่ายมาก มันสนใจแค่สองสถานะเท่านั้น\n\n- High (1)\n- Low (0)\n\nเหมือนมีคนถามว่า\n\n> \"เปิดไหม?\"\n> \n\nก็มีแค่ **เปิด** หรือ **ปิด** ไม่มีคำว่า \"เปิดนิดหน่อย\"\n\nแต่โลกความเป็นจริงไม่ได้เป็นแบบนั้น...\n\nเซนเซอร์หลายชนิดไม่ได้ส่งค่าออกมาเป็นแค่ 0 หรือ 1\n\nยกตัวอย่างเช่น\n\n- [Potentiometer (ปุ่มหมุน)](https://s.shopee.co.th/qhfEJL1hl)\n- เซนเซอร์วัดแสง\n- เซนเซอร์วัดอุณหภูมิ\n- เซนเซอร์วัดแรงดัน\n\nอุปกรณ์เหล่านี้จะส่งแรงดันไฟฟ้าที่เปลี่ยนแปลงได้อย่างต่อเนื่อง หรือที่เรียกว่า **Analog Signal**\n\n---\n\n### แล้ว Digital กับ Analog ต่างกันยังไง?\n\nลองนึกถึงสวิตช์ไฟในบ้าน\n\nไม่ว่าคุณจะกดแรงหรือกดเบา สุดท้ายมันก็มีแค่\n\n- เปิด\n- ปิด\n\nDigital ก็ทำงานแบบเดียวกัน\n\nถ้าแรงดันสูงกว่า Threshold ก็ถือว่าเป็น High\n\nต่ำกว่าก็เป็น Low\n\nมันไม่สนใจว่าแรงดันจะเป็น 2.1V หรือ 3.2V เพราะสุดท้ายผลลัพธ์ก็ยังเป็นเลข **1** เหมือนเดิม\n\nแต่ Analog ต่างออกไป...\n\nมันสนใจ \"ระดับ\" ของแรงดันจริง ๆ\n\nเช่น\n\n- 0.4V\n- 1.2V\n- 2.7V\n- 3.1V\n\nทุกค่ามีความหมาย\n\nเหมือนกับการหมุนปุ่มเพิ่มเสียง เราไม่ได้ต้องการแค่ \"เบา\" กับ \"ดัง\" แต่ต้องการระดับเสียงทุกระดับระหว่างนั้นด้วย\n\n---\n\n### แล้ว MCU อ่านแรงดันไฟฟ้าได้เลยหรือเปล่า?\n\nคำตอบคือ...\n\n**ไม่ได้**\n\nMicrocontroller เข้าใจแต่ตัวเลข\n\nดังนั้นจึงต้องมีฮาร์ดแวร์อีกตัวหนึ่งช่วยแปลงแรงดันไฟฟ้าให้กลายเป็นตัวเลขก่อน\n\nตัวนั้นก็คือ\n\n**ADC (Analog-to-Digital Converter)**\n\nสำหรับ ESP32-C3 ADC มีความละเอียด **12-bit**\n\nนั่นหมายถึงแรงดันไฟฟ้าจะถูกแบ่งออกเป็น **4096 ระดับ**\n\nตั้งแต่\n\n```\n0 - 4095\n```\n\nแรงดันยิ่งสูง\n\nค่าที่อ่านได้ก็จะยิ่งมาก\n\n---\n\n### วันนี้เราจะลองกับ Potentiometer\n\nอุปกรณ์ที่ใช้วันนี้คือ **Potentiometer** หรือปุ่มหมุน\n\nเวลาเราหมุนปุ่ม มันจะเปลี่ยนแรงดันไฟฟ้าที่ส่งเข้า ADC\n\nADC ก็จะเปลี่ยนแรงดันนั้นให้กลายเป็นตัวเลข\n\nแค่นี้เราก็สามารถรู้ได้แล้วว่า ปุ่มหมุนอยู่ตำแหน่งไหน\n\n---\n\n## สร้างการตั้งค่า ADC\n\n```rust\nlet mut adc_config = AdcConfig::new();\n```\n\nเริ่มจากสร้าง Configuration สำหรับ ADC ก่อน\n\nตอนนี้ยังไม่ได้เปิดใช้งาน ADC แต่อย่างใด\n\nเป็นเพียงการเตรียมค่าต่าง ๆ เอาไว้\n\n---\n\n## เปิดใช้งานขา Analog\n\n```rust\nlet mut pot =\n    adc_config.enable_pin(peripherals.GPIO2, Attenuation::_11dB);\n```\n\nบรรทัดนี้ทำอยู่สองอย่าง\n\n- กำหนดให้ `GPIO2` ใช้งานเป็นขา Analog\n- ตั้งค่า `Attenuation` เป็น `_11dB`\n\nหลายคนเห็น `_11dB` แล้วอาจสงสัยว่า...\n\n> ทำไมต้องมีด้วย?\n> \n\nจริง ๆ แล้ว ADC ไม่สามารถวัดแรงดันได้ทุกช่วง\n\nAttenuation เปรียบเหมือนการขยายช่วงการวัดของ ADC\n\nยิ่งค่า dB มาก ก็ยิ่งวัดแรงดันได้กว้างขึ้น\n\nสำหรับบอร์ด ESP32-C3 ที่ใช้ไฟ 3.3V ค่า `_11dB` ถือว่าเป็นตัวเลือกที่พบได้บ่อยที่สุด\n\n---\n\n## สร้างตัวควบคุม ADC\n\n```rust\nlet mut adc = Adc::new(peripherals.ADC1, adc_config);\n```\n\nESP32-C3 มีวงจร ADC อยู่ภายในชิปอยู่แล้ว\n\nบรรทัดนี้คือการสร้างตัวควบคุม ADC1 พร้อมกับ Configuration ที่เราเตรียมไว้เมื่อสักครู่\n\n---\n\n## อ่านค่า Analog\n\n```rust\nlet value: u16 =\n    nb::block!(adc.read_oneshot(&mut pot)).unwrap();\n```\n\nดูเหมือนจะยาว แต่จริง ๆ มีอยู่แค่สามส่วน\n\n### อ่านค่าหนึ่งครั้ง\n\n```rust\nadc.read_oneshot(&mut pot)\n```\n\nสั่ง ADC ให้อ่านค่าจาก Potentiometer เพียงครั้งเดียว\n\n---\n\n### รอจนอ่านเสร็จ\n\n```rust\nnb::block!(...)\n```\n\nการแปลงค่า Analog เป็น Digital ใช้เวลาเล็กน้อย\n\nAPI จึงออกแบบเป็นแบบ **Non-blocking**\n\n`nb::block!` จะคอยรอจน ADC อ่านค่าเสร็จ แล้วจึงคืนค่ากลับมา\n\n---\n\n### นำค่าที่อ่านได้ออกมา\n\n```rust\n.unwrap()\n```\n\nถ้าอ่านสำเร็จก็จะได้ตัวเลขออกมา\n\nแต่ถ้าเกิดข้อผิดพลาด โปรแกรมจะ Panic ทันที\n\nสำหรับช่วงทดลองแบบนี้ ใช้ `unwrap()` ได้สบาย ๆ\n\n---\n![Electronic-Component-Potentiometer](https://drive.google.com/thumbnail?id=1MZv3349Lxv8LCDsOYRBg3TZl2Tj1jP1J&sz=w1000)\n\nเมื่อหมุน Potentiometer\n\n- หมุนไปทางซ้าย → ตัวเลขจะลดลง\n- หมุนไปทางขวา → ตัวเลขจะเพิ่มขึ้น\n\nจากเดิมที่เราเคยรู้จักโลกของ Digital ซึ่งมีเพียง **0** และ **1**\n\nตอนนี้เราได้เข้าสู่โลกของ **Analog** แล้ว\n\nโลกที่ไมโครคอนโทรลเลอร์สามารถรับรู้ \"ระดับ\" ของสัญญาณได้ ไม่ใช่แค่รู้ว่ามีหรือไม่มีเท่านั้น\n\nและในตอนต่อไป เราจะเริ่มใช้ค่าพวกนี้ควบคุมอุปกรณ์จริง ๆ กัน 🦀⚡\n","status":"published","created_at":"2026-07-05T10:55:20.795736+00:00","updated_at":"2026-07-05T10:55:20.795736+00:00","synced_at":"2026-07-05T10:55:20.795736+00:00"},{"id":29,"title":"Punch the button","slug":"punch-the-button","body":"---\ntags:\n  - eps-rs\n---\n## Punch the button\n\nเมื่อครั้งที่แล้วที่เราเจอกันเราได้ทำการทดลองการเปิดแสงสว่างภายในบอร์ดไปแล้ว\nโดยตัว  esp32 supermini c3 จะใช้ GPIO8 เป็น LED ในบอร์ด [board esp32 c3 สุดถูกๆๆๆ](https://s.shopee.co.th/3qKWysljao)\n\n![lilidm_esp_ep5](https://drive.google.com/thumbnail?id=1sn7ePk_H-lVQneXMcJ0_ZSztHHO2Hp8M&sz=w1000)\n\nแต่ก่อนจะไปถึงตรงนั้น ผมมีคำถามที่สงสัยมานาน...\n\n## PIN คืออะไรกันแน่?\n\nคำว่า **Pin** นี่เจอบ่อยมาก\n\n- Pin Project ใน ChatGPT\n- `Pin<Box<T>>` ใน Rust\n- BlackBerry PIN (เด็ก ๆ ทุกคนน่าจะรู้ XD)\n- GPIO Pin\n- หรือแม้แต่หมุดปักแผนที่ก็ยังเรียก Pin\n\nแต่ในโลกของ Embedded คำว่า **Pin** หมายถึง **ขา** ของชิปหรือบอร์ด เป็นจุดที่ใช้เชื่อมต่อกับโลกภายนอก จะรับข้อมูล ส่งข้อมูล รับแรงดันไฟ หรือส่งแรงดันไฟ ก็ผ่านขาเหล่านี้ทั้งนั้น\n\nดังนั้นเวลาเราพูดว่า **GPIO8** ก็หมายถึง **ขาหมายเลข 8** ที่สามารถใช้งานเป็น Input หรือ Output ได้\n\nช่างมันดีกว่ากลับมาการเดินทางของเรา \n\nGPIO (**General-Purpose Input/Output**.)  อย่างกับชื่อแป้งอเนกประสงค์\nเป็นการอ่านค่า**สัญญาณไฟฟ้า (High / Low)**  หรือ data การขาเหล่านี้ขึ้นอยู่กับจะใช้อะไร\nโดยในบทนี้เราจะใช้เพียง 2 โหมด \n\n1. input (read) \n2. output (write)\n\nเวลาที่เราใช้เราก้จะตั้งค่าว่า GPIO นั้นจะเป็นโหมดไหน มี setting อะไรบ้าง เช่น\n\nเราบอกว่าถ้า GPIO6 เป็น  low  ให้ print(”egg”) ถ้าไม่ให้ print(”break egg”)\nทีนี้ตอน board เรา loop run ก็จะ check GPIO6 ตลอดว่าเป็น  low ไหม\n\nที่นี้เราเบื่อ ก็เลยไปสั่งบรรทัดใหม่ว่า   \n       if button.is_low() {\n         led.set_high(); \n       } else {\n         led.set_low(); \n       };\nถ้าเราไปต่อ led ทีนี้เราก็ได้ไฟ ตืดๆ แบบ แมวหมุนๆ ทันดี \n\nตอนนี้เรารู้แล้วว่า GPIO ทำให้โปรแกรมสามารถรับและส่งสัญญาณไปยังโลกภายนอกได้\n\nแต่อย่าลืมว่าตอนบอร์ดมันทำงาน มันจะทำงานจากบนลงแล้ว  loop ไปเรื่อยๆ\nแปลว่าโปรแกรมไม่ได้หยุดรอปุ่ม แต่มันคอยแวะมาอ่านเป็นช่วง ๆ อาจจะกดไม่ติด\n\nและและ ที่สำคัญ ถ้าเราไม่ตั้ง pull ตอนใช้ GPIO อาจจะเกิดสัญญาณรบกวน (Floating) \nถึงกดถูกจังหวะโปรแกรมก็\nไม่รู้อยู่ดี ส่วนนี้ลองหาอ่านเพิ่มเติมได้ เพราะจะเกี่ยวกับไฟฟ้าแล้ว\n\nทีนี้เรามาต่อวงจรกันดีกว่า เราจะใช้ ปุ่มแบบแง่ง โดย เป็นสะพานไฟ จาก GPIO ที่เรา OUTPUT to Ground\nแต่ตอนไม่กดสะพานไฟจะขาด และกดมันก็จะต่อกัน\n\n[button](https://s.shopee.co.th/gO48kzN2n)\n\n![db63d550-733d-46f8-9212-0b0bf3815d69](https://drive.google.com/thumbnail?id=1LmqeVfhgf5Nz6gIT3TGoUENc8MXFv6ya&sz=w1000)\n\n┌─────┐\n1 ├─┐ ┌─┤ 3\n2 ├─┘ └─┤ 4\n└─────┘\n\n\nทีนี้เราก็จะมาตั้ง ค่า GPIO กัน\n\nlet button = Input::new(\n    peripherals.GPIO1,\n    InputConfig::default().with_pull(Pull::Up),\n);\n\nlet mut led = Output::new(peripherals.GPIO8, Level::High, OutputConfig::default());\n\nโดยเราจะอ่านค่าจาก button เผื่อไป set led  ของ GPIO8\n\nแต่สังเกตดีๆ เราจะเห็นว่า Pull เราตั้งเป็น Up เพราะว่า\nไม่กด = High เพราะ Pull-up ดึงขาไว้ที่ 3.3V\nกด = Low เพราะปุ่มเชื่อม GPIO เข้ากับ Ground\n\nส่วนที่เหลือรองจิตนาฝึกดูนะว่าจะเป็นอย่างไร\n\nวันนี้ขอลาไปก่อนบาย\n\n\n\n","status":"published","created_at":"2026-06-28T14:47:12.853466+00:00","updated_at":"2026-06-28T14:47:12.853466+00:00","synced_at":"2026-06-28T14:47:12.853466+00:00"},{"id":28,"title":"bright path","slug":"bright-path","body":"---\ntags:\n  - eps-rs\n---\nสวัสดีเหล่านักเดินทาง\nก่อนหน้านี้เราได้คุยกันเรื่องเตรียมอุปกรณ์กันหลาย ๆ อย่าง\nแต่มั่นใจได้เลยหลายคนน่าจะงงกันเป็นเรื่องปกติ\nถ้าเราไม่เคยใช้อุปกรณ์นั้นจริง ๆ ต่อให้เข้าใจอย่างถ่องแท้ก็อาจจำไม่ได้อยู่ดี\nเรามาดูกันดีกว่าว่า แสงปลายทางของเรานั้นหน้าตาเป็นอย่างไร\nที่งบอกแสงเราจะมาเช่น led บนบอร์ด esp32 c3 supermini กัน [board esp32 c3 สุดถูกๆๆๆ](https://s.shopee.co.th/3qKWysljao)\n\n\n![blink](https://drive.google.com/thumbnail?id=16esZ4WwDyazXbjq_lLEP090FFVY5_ld_&sz=w1000)\n\nก่อนอื่นต้องบอกก่อนเลยว่า esp32 c3 supermini นั้น จะมีGPIO8 เป็นตัวติดต่อกับ LED บนบอร์ดได้\nเราจะมาทำไฟวิบวับกัน แต่อย่าดูถูกการเริ่มต้นง่ายๆไปละ เพราะจริงๆแล้วสิ่งง่ายเหล่านี้รวมกันจะทำอะไรได้หลายอย่างเลย\nถ้าเราเข้าใจว่า แต่ละส่วนมีไว้ทำอะไร และทำไปทำไม เราก็จะคิดประยุกต์ไปทำเรื่องอื่นๆได้ (ถึงแม้ AI จะยึดครองโลกแล้วแต่เราก็ต้องเก่งขึ้นอยู่ดี)\n\nแต่บอกเลยว่าในยุค AI นี้เรื่องทำ hardware สนุกขึ้นเยอะมาก เพราะถ้าเราติดอะไร เช่น การควบคุม BIT หรือหาเอกสาร spec ไม่เจอสามารถไปต่อได้พร้อมกับ AI\n\nเรามาเข้าเรื่องเราดีกว่า นอกเรื่องมานาน ส่วนใครเพิ่งเริ่มเดินทาง สามารถไปดูการเตรียมตัวก่อนได้ที่  [คู่มือเริ่มต้น](https://www.lilidm.com/content?tag_id=26&content_id=25)\n\nก่อนอื่นเลย\nเราต้องเตรียมเข้าถึงอุปกรณ์ด้วย\n\n    let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());\n    let peripherals = esp_hal::init(config);\n\nคิดว่า peripherals เป็น item แบบหนึ่งที่ใช้เข้าถึงทุกอย่างของ ESP32. (แล้วถ้าเข้าเรื่อง owonership ก็สนุกดี เพราะมองเป็น item ที่ยืมกันไปยืมกันมาได้ด้วย)\n\nต่อมาก็ไปสร้างอุปกรณ์กันตอนนี้เราอยากได้ แสงบิ้บๆเราจึงต้องสร้าง\n\n    let mut led = Output::new(peripherals.GPIO8, Level::High, OutputConfig::default());\n\nเป็นการระบุว่า แสงบิ้บๆ คือ led ที่สามารถเปิดปิดไฟได้ (mut)\nเวลาที่เราเรียกใช้ก็\n    led.set_level(state);\n\n       let state = if led.is_set_low() {\n                Level::High\n            } else {\n                Level::Low\n            };\n\n\nทีนี้เราก็จะเปิดปิดไฟได้แล้วว\n\nลองเอาแนวคิดพวกนี้ไปลองเล่นกันดูว่า เราจะ บิ้บบี้บิบึบบิ้ กันอย่างไร บ๊าย\n\n","status":"published","created_at":"2026-06-21T15:26:03.197259+00:00","updated_at":"2026-06-21T15:26:03.197259+00:00","synced_at":"2026-06-21T15:26:03.197259+00:00"},{"id":26,"title":"there are tools","slug":"there_are_tools","body":"---\ntags:\n  - eps-rs\n---\nเอาล่ะ...\nหลังจากสร้างโปรเจกต์เสร็จ Flash ลงบอร์ดได้แล้ว\nก็ถึงเวลาที่เราจะเริ่มคุยกับ ESP32 กันจริง ๆ\nแต่เดี๋ยวก่อน...\n\n\n![LILIDM](https://drive.google.com/thumbnail?id=1FR3EXSaOVj1-az9jtgBYSI18VIV5mMG1&sz=w1000)\n\n\n[board esp32 c3 สุดถูกๆๆๆ](https://s.shopee.co.th/3qKWysljao)\n\nถึงจะมี GPIO, UART, SPI, Timer และของเล่นเต็มชิปไปหมด\nตอนนี้เรายังแตะอะไรไม่ได้สักอย่าง 🤣\nเหมือนเราเดินเข้าบ้านคนอื่นมาแล้วเห็นเครื่องมือเต็มโรงรถ\nแต่เจ้าของบ้านยังไม่ได้ให้กุญแจ\nดังนั้นสิ่งแรกที่เราต้องทำคือ \"เตรียมอุปกรณ์ภายใน\" ออกมาก่อน\nหรือในภาษา ESP-RS ก็คือการเรียก esp_hal::init()\nเพื่อบอกว่า\n\"พี่ครับ ขอเบิกของในคลังหน่อย\"\nจากนั้น ESP32 ก็จะส่ง GPIO, UART, SPI, Timer และอุปกรณ์ต่าง ๆ มาให้เราใช้งาน\nซึ่งทั้งหมดนี้จะถูกรวมอยู่ในตัวแปรที่ชื่อว่า peripherals\nและจากจุดนี้เอง เราถึงจะเริ่มสั่งงานฮาร์ดแวร์ได้จริง ๆ\n\nHAL ย่อมาจาก Hardware Abstraction Layer\n![code](https://drive.google.com/thumbnail?id=12vdcmCs4rFaBgIAGHABw43MBPi0B5YJg&sz=w1000)\n\n😆🦀📦⚡️\n\n\n\nแล้วค่อยอธิบายว่า Peripherals คือ \"ลังรวมอุปกรณ์ทั้งหมดที่เราเพิ่งเบิกออกมาจากคลัง\" ต่อได้เลยครับ \n\n\n\n\n\n","status":"published","created_at":"2026-06-14T17:49:07.952204+00:00","updated_at":"2026-06-14T17:49:07.952204+00:00","synced_at":"2026-06-14T17:49:07.952204+00:00"},{"id":25,"title":"prepare-for-new-world","slug":"prepare-for-new-world","body":"---\ntags:\n  - eps-rs\n  - esp_setup\n---\n\n[board esp32 c3 สุดถูกๆๆๆ](https://s.shopee.co.th/3qKWysljao)\n\n\nสวัสดีชาว rust bro ผมเชื่อใครอ่านถึงตรงนี้ต้องเข้าเส้นสักนิดนึงแล้วละ\nเอาละมาเตรียมสู้การเดินทางกัน ใครอยากเตรียมตัวแน่น เอามาดูคู่มือตรงนี้ก่อน [คู่มือ](https://docs.espressif.com/projects/rust/book/getting-started/tooling/esp-generate.html)\n\nคือการ ลงเครื่องมือสร้าง project คล้ายกับ cargo init นั้นเอง\n\n  cargo install esp-generate --locked <-- เวลาใช้ ก็ esp-generate ตามด้วยชื่อ project ใน terminal ได้เลย\n\n![HELLO](https://drive.google.com/thumbnail?id=19gP9Co9WUobbZJ1lgSu96RUNLcSrrRDs&sz=w1000)\n\nมาถึงตรงเราจะต้องเลือก board ที่เราต้องผมแนะนำ c3 มันถูกดี\n \"\" เห็นไหมทำไมไม่มี ตระกูล S จะเป็น chip อีกแบบที่ Rust ไม่ได้รองรับดีเท่าตระกูล C \"\"\n \"\" จริงก็ใช้ได้เหมือนกันแต่ต้องลงอีกแบบ \"\"\n\n![select](https://drive.google.com/thumbnail?id=1-FDW0wJv1jEhYMBypox34SVHtyRTzqyZ&sz=w1000)\n\nถึงตรงนี้เราก็เลือกแถวที่ highlight ตามภาพแล้วกดลูกศร  -> เพื่อเลือก\n\"\" ตัวนี้จะเป็น falsh แบบใหม่ที่ ไวกว่าและสามารถ log ได้ดีกว่า \"\"\n\n![model](https://drive.google.com/thumbnail?id=1Pl_hEKZAKDHp9R9emQebNuDZ_Rfp2aCu&sz=w1000)\n\nพอทำเสร็จแล้วเราจะเห็นหน้าตา Project เพื่อเข้าสู่ new world เป็นประมาณนี้\n\n![preview](https://drive.google.com/thumbnail?id=10ByBiPY7L2DMdtEGDScYn_gZlIOqn5ka&sz=w1000)\n\nพวกอันนี้จะเป็น Optional จะทำไม่ทำก็ได้ถ้าไครใช้ ide แล้วขึ้นสีแดงที่ no std เพราะไม่มี std พวก test ต่างจะแจ้ง error แบบนี้ละ\n\n![set](https://drive.google.com/thumbnail?id=1xF8ai_oQ96zt4U2jwRbYYmrEz7sdAs-A&sz=w1000)\n\nถึงตรงนี้ก็เสียบอุปกรณ์ ได้ตามสะดวก และ จากนั้น ก็ **\"\" cargo run \"\"** เราก็จะ flash เข้า board คือเป็นการเข้าสู่ new world เรียบร้อย\n\n![flash](https://drive.google.com/thumbnail?id=16skPPmD6yCiHSl8abgL0WhgicSaC26K1&sz=w1000)\n\nตอนต่อไปเราจะจุดประกายให้ board เรานั้นวิบวับกัน เจอกันได้เลย\n","status":"published","created_at":"2026-06-06T17:05:52.409492+00:00","updated_at":"2026-06-06T17:05:52.409492+00:00","synced_at":"2026-06-06T17:05:52.409492+00:00"},{"id":24,"title":"started-way-to-mc","slug":"started-way-to-mc","body":"---\ntags:\n  - eps-rs\n---\n![LILIDM](https://drive.google.com/thumbnail?id=1yesaMoOSRlAxALhcR5YCCzfl7Gds7ptZ&sz=w1000)\n\n\nสวัสดีทุกคน เรามาขึ้น ss ใหม่ คือ esp-rs แต่บอกเลยว่าอาจจะไม่ได้ลงทุกอาทิตย์\nเพราะส่วนตัว เรื่อง hardware กับ ไฟฟ้าของข้างน้อย แต่ที่สนใจทำอันนี้เพราะมีอยากไปประยุกต์กับสิ่งที่ใช้งานประจำ 5555\n\nต้องบอกก่อน ทำเลือก Rust. มาทำ เรื่อง Board esp32 จริงๆ มีตัวเลือก tiny python, c or c++ อยู่ที่ lib. เยอะและหลากลายกว่า แต่ Rust นั้นมีจุดน่าสนใจที่เลือกคือ ศึกษาภาษานี้ทำได้กว้าง และมีประสิทธิภาพทุกด้านเลย แม้จะ complie เป็นล้านปีก็ตาม 5555555\n\nแต่ที่มา ใช้ esp32 ใน Rust ก็ไม่ได้ยากขนาดนั้น นอกจากใช้ กระตูล (esp Sxx)  เรา Rust นั้นมี crate ชื่อ esp-rs อำนวยความความสะดวกต่างตั้งแต่   create dir จนถึง debug และ flash ลง Board  \n\nแต่ก่อนที่เราจะไปกันต่อ สิ่งที่ควรรู้คือ ชนิด Board เพราะไครกดมาอ่านต้องคันมือซื้อเล่นแน่ๆ (ไม่สนใจคงปัดทิ้งตั้งแต่ชื่อเรื่อง 555555)\n\n1.Xtensa \nจะเป็นตระกูล ESP32-s3 จะเป็นแบบ Closed-source\nจะเป็นพวกมี camera ให้ใช้งานอด้วย\nRust บน Xtensa ยังต้องพึ่งพา toolchain และ ecosystem เฉพาะทางมากกว่า RISC-V\n\n2.**RISC-V ← แนะนำ**\nจะเป็นตระกูล ESP32-c3, c6, h, etc. จะเป็นแบบ open source\nเป็นชิปรุ่นใหม่ที่ใช้สถาปัตยกรรม RISC-V และรองรับการพัฒนาด้วย Rust ได้เต็มที่\nสามารถใช้กับ rust ได้เต็มประสิทธิภาพ เพราะการช่วยกัน contribute แบบ เสรี\n\nใครอย่างลองเพิ่มเติมลองศึกษ ผ่านลิงค์นี้ได้เลย [esp-rs click](https://docs.espressif.com/projects/rust/book/getting-started/tooling/esp-generate.html)\n\n\n1. esp-generate ← เข้าใจว่าจะสร้างโปรเจคอย่างไร\n2. cargo run --release ← จะ flash ลงบอร์ด หรืออ่าน log อย่างไร\n\nในโพสต่อๆไปจะค่อยลงไปทีละส่วนตามที่ข้าพเจ้าได้ใจแล้ว โชคดี\n\n","status":"published","created_at":"2026-06-01T10:27:53.482579+00:00","updated_at":"2026-06-01T10:27:53.482579+00:00","synced_at":"2026-06-01T10:27:53.482579+00:00"},{"id":23,"title":"Primitive types","slug":"primitive-types","body":"---\ntags:\n  - 100excer\n---\n[100 exercises](https://rust-exercises.com/100-exercises/02_basic_calculator/01_integers.html?fbclid=IwY2xjawSH16hleHRuA2FlbQIxMABicmlkETFNYXlGYldMZDdGaGZ5bVpoc3J0YwZhcHBfaWQQMjIyMDM5MTc4ODIwMDg5MgABHuvG2jpp2kcvac8DPLFD9qqbZxU9OqIRbDT_UjfCqcAllEgbHSpkyRqDHyyL_aem_LIYom6Y8k8TP-aRlUcvnGA#primitive-types)\n\nต้องบอกว่าใน Rust เป็นภาษาที่ต้องข้างสติ๊กกับ ประเภทของข้อมูลเลยจะใช้อะไรต้องให้ complier เดาได้ว่าข้อมูลที่ใช้อยู่เป็นประเภทไหน\nและ integer ที่เราใช้กันในหลายๆ ภาษา จริงแล้วมันมีหลายประเภท แบ่งตามขนาดของตัวเลขนั้น\n\n| Bit Width | Signed | Unsigned |\n|-----------|--------|----------|\n| 8-bit     | i8     | u8       |\n| 16-bit    | i16    | u16      |\n| 32-bit    | i32    | u32      |\n| 64-bit    | i64    | u64      |\n| 128-bit   | i128   | u128     |\n\nแล้วหลักการในการแยกอันไหนเป็น primitive-type ง่ายๆคือ \nถ้าข้อมูลไหนสามารถกำหนดขนาดได้เลยแบบเปลี่ยนแปลงไม่ได้\n\n*อย่าไปสับสนกับ type ที่ประกาศแล้วบอกเปลี่ยนแปลงไม่ได้นะ ละไว้ฐานที่เข้าใจก่อน \n\n","status":"published","created_at":"2026-05-30T15:06:16.949291+00:00","updated_at":"2026-05-30T15:06:16.949291+00:00","synced_at":"2026-05-30T15:06:16.949291+00:00"},{"id":22,"title":"syntax","slug":"syntax","body":"---\ntags:\n  - 100excer\n---\nไหนมาเขียนเรื่องที่ลงใน facebook มาลงใหม่เริ่มจาก sytax เบื้องต้นของ Rust จำคร่าวๆเฉยๆก็ได้\n\n[100 exercises](https://rust-exercises.com/100-exercises/01_intro/01_syntax.html?fbclid=IwY2xjawSH18BleHRuA2FlbQIxMQBzcnRjBmFwcF9pZBAyMjIwMzkxNzg4MjAwODkyAAEe_pGXcPQYOkpEA19Es5NOHg3aVo5n-WHeFXpAPp7iOhCKorAlS7VCxwPAsg0_aem_nZmiJbFFbKBbTU_QqxgJkw)\n\n// comment\nfn hello(say:String) -> String {\n   format!(\"{say} world\")\n}\n\n1. comment\n2. Function\n3. Return type\n4. Return Value\n5. Input parameter\n\n\n","status":"published","created_at":"2026-05-30T14:52:02.926605+00:00","updated_at":"2026-05-30T14:52:02.926605+00:00","synced_at":"2026-05-30T14:52:02.926605+00:00"},{"id":21,"title":"Checkpoint_Start","slug":"Checkpoint_Start","body":"---\ntags:\n  - zero-to-hero\n---\n\n[ขนมระหว่างเรียน](https://s.shopee.co.th/60OIozMKYM)\n\nเริ่มต้นใสๆ ถ้าหลายๆคนมาจากภาษาอื่นเราจะมา โชว์ codeที่ทำให้หลายๆคนคุ้นเคยกันก่อน\nเพราะใน rust จะมี เรื่อง ownership และ borrow checker ที่ค่อนข้างปวดหัว\nแต่ถ้าเราไม่สนใจสนแค่ว่า เขียน โปรแกรมได้ ก็สามารถใช้ .clone() ในทุกอย่างได้เลย (ถ้า function นั้นไม่ได้บังคับ)\nมันจะไม่ต่างจาก javascript เลย เพราะ ตอนที่เรา pass arg เข้า function มันคือการ clone โต้งๆอยู่แล้ว\n\n```rust\nfn main() {\n    let x = 2; // ขยับแนวนอน\n    let y = 1; // ขนับแนวตั้ง\n\n    // สร้างบอร์ด mut บอกว่าตัวนี้เปลี่ยนแปลงได้\n    let mut board = vec![\n        vec![\".\".to_string(); 3],\n        vec![\".\".to_string(); 3],\n        vec![\".\".to_string(); 3],\n    ];\n        board[x][y] = \"X\".to_string();\n        print_board(board.clone());\n        println!(\" \");\n        println!(\"{board:?}\"); // เช็คว่าบอร์ดตายยัง\n\n}\n\n\nfn print_board(board: Vec<Vec<String>>) {\n    println!();\n    for row in board {\n        for cell in row {\n            print!(\"{} \", cell);\n        }\n\n        println!();\n    }\n}\n```\n\n","status":"published","created_at":"2026-05-09T16:35:00.042193+00:00","updated_at":"2026-05-09T16:35:00.042193+00:00","synced_at":"2026-05-09T16:35:00.042193+00:00"},{"id":20,"title":"Rust is hard","slug":"Rust_is_hard","body":"---\ntags:\n  - RUST\n---\n![LILIDM](https://drive.google.com/thumbnail?id=1mLSVvB7v65KFFpIeWZD2__SioQkuWqAN&sz=w1000)\n\n🦀 Rust: ภาษาที่คนชอบพูดว่า “ยาก” ทั้งที่ยังไม่เคยลอง 😏\nทุกคนบอกว่า Rust ปลอดภัยสุด\nแต่พอให้ลอง compile ก็หายไปเลย… 🫠\nอย่าเพิ่งหนีครับ\nถ้าอยากเข้าใจ Rust แบบไม่ปวดหัว — ตอนนี้มีทั้ง\n📘 เว็บสอนฟรี (อ่านเพลิน เหมือนดู meme)\n🎮 และคอร์สจริงจังใน Udemy สำหรับสาย backend\nเลือกได้เลยว่าจะเริ่มแบบขำ ๆ หรือจริงจัง\nแต่สุดท้าย… คุณจะตกหลุมรัก borrow checker เอง ❤️\nเดียวก่อน ก่อนจากไป ถ้าไครอยากจ้าง freelance เกี่ยวกับ rust ทักมาได้ครับ 😎\n\n🔗 [ฟรี! จาก katopz](https://gist.rs)\n🎓 [Dancing](https://www.udemy.com/course/rust-mastery-saga-backend-rust/)\n🔗 [Rustbook](https://doc.rust-lang.org/book/)\n🔗 [book](https://s.shopee.co.th/3LLaYFRWLM)","status":"published","created_at":"2026-04-15T09:48:02.025555+00:00","updated_at":"2026-04-15T09:48:02.025555+00:00","synced_at":"2026-04-15T09:48:02.025555+00:00"}],"tags":[{"id":22,"name":"zero-to-hero","slug":"zero-to-hero","parent_id":null,"created_at":"2026-05-02T15:11:00.408+00:00","updated_at":"2026-05-02T15:11:00.408+00:00","synced_at":null},{"id":24,"name":"RUST","slug":"rust","parent_id":null,"created_at":"2026-04-15T18:27:37.536+00:00","updated_at":"2026-04-15T18:27:37.536+00:00","synced_at":null},{"id":23,"name":"LIFE","slug":"life","parent_id":null,"created_at":"2026-05-02T15:10:35.059+00:00","updated_at":"2026-05-02T15:10:35.059+00:00","synced_at":null},{"id":25,"name":"100excer","slug":"100excer","parent_id":null,"created_at":"2026-05-30T14:33:18.068+00:00","updated_at":"2026-05-30T14:33:18.068+00:00","synced_at":null},{"id":26,"name":"eps-rs","slug":"eps-rs","parent_id":null,"created_at":"2026-06-01T10:17:01.951+00:00","updated_at":"2026-06-01T10:17:01.951+00:00","synced_at":null},{"id":27,"name":"spec_hardware","slug":"spec_hardware","parent_id":null,"created_at":"2026-07-25T15:44:26.204+00:00","updated_at":"2026-07-25T15:44:26.204+00:00","synced_at":null},{"id":28,"name":"esp_setup","slug":"esp_setup","parent_id":26,"created_at":"2026-08-09T14:36:39.543+00:00","updated_at":"2026-08-09T14:36:39.543+00:00","synced_at":null},{"id":29,"name":"database","slug":"database","parent_id":null,"created_at":"2026-09-06T14:00:42.144+00:00","updated_at":"2026-09-06T14:00:42.144+00:00","synced_at":null},{"id":31,"name":"observ","slug":"observ","parent_id":null,"created_at":"2026-10-18T06:01:07.370875+00:00","updated_at":"2026-10-18T06:01:07.370875+00:00","synced_at":"2026-10-18T06:01:07.370875+00:00"}],"content_tags":[{"id":13,"content_id":20,"tag_id":24,"created_at":"2026-04-15 18:27:49.816755+00"},{"id":14,"content_id":21,"tag_id":22,"created_at":"2026-05-09 16:35:00.61804+00"},{"id":15,"content_id":22,"tag_id":25,"created_at":"2026-05-30 14:52:03.45396+00"},{"id":16,"content_id":23,"tag_id":25,"created_at":"2026-05-30 15:06:17.537968+00"},{"id":17,"content_id":24,"tag_id":26,"created_at":"2026-06-01 10:28:28.218936+00"},{"id":18,"content_id":25,"tag_id":26,"created_at":"2026-06-06 17:05:52.920011+00"},{"id":19,"content_id":26,"tag_id":26,"created_at":"2026-06-14 17:52:02.099104+00"},{"id":20,"content_id":28,"tag_id":26,"created_at":"2026-06-21 15:42:10.531553+00"},{"id":21,"content_id":29,"tag_id":26,"created_at":"2026-06-28 14:47:13.499905+00"},{"id":22,"content_id":30,"tag_id":26,"created_at":"2026-07-05 11:02:20.105703+00"},{"id":23,"content_id":31,"tag_id":26,"created_at":"2026-07-12 10:06:56.216815+00"},{"id":24,"content_id":32,"tag_id":25,"created_at":"2026-07-14 06:53:36.403735+00"},{"id":25,"content_id":33,"tag_id":25,"created_at":"2026-07-14 07:09:07.709218+00"},{"id":26,"content_id":34,"tag_id":25,"created_at":"2026-07-14 07:11:20.659053+00"},{"id":27,"content_id":35,"tag_id":25,"created_at":"2026-07-14 07:13:19.761992+00"},{"id":28,"content_id":36,"tag_id":25,"created_at":"2026-07-14 07:16:20.542099+00"},{"id":29,"content_id":37,"tag_id":25,"created_at":"2026-07-14 07:22:06.405966+00"},{"id":30,"content_id":38,"tag_id":25,"created_at":"2026-07-14 07:24:10.237266+00"},{"id":31,"content_id":39,"tag_id":25,"created_at":"2026-07-14 07:34:12.010988+00"},{"id":32,"content_id":40,"tag_id":25,"created_at":"2026-07-14 07:35:35.874397+00"},{"id":33,"content_id":41,"tag_id":25,"created_at":"2026-07-14 07:37:20.195738+00"},{"id":34,"content_id":42,"tag_id":25,"created_at":"2026-07-14 07:40:16.433763+00"},{"id":35,"content_id":43,"tag_id":25,"created_at":"2026-07-14 07:41:28.69659+00"},{"id":36,"content_id":44,"tag_id":25,"created_at":"2026-07-14 07:44:39.328474+00"},{"id":37,"content_id":45,"tag_id":25,"created_at":"2026-07-14 07:45:54.526692+00"},{"id":38,"content_id":46,"tag_id":25,"created_at":"2026-07-14 07:47:09.6965+00"},{"id":39,"content_id":47,"tag_id":25,"created_at":"2026-07-14 07:48:40.72881+00"},{"id":40,"content_id":48,"tag_id":25,"created_at":"2026-07-14 07:50:00.241929+00"},{"id":41,"content_id":49,"tag_id":25,"created_at":"2026-07-14 07:51:35.718667+00"},{"id":42,"content_id":50,"tag_id":25,"created_at":"2026-07-14 07:53:13.00356+00"},{"id":43,"content_id":51,"tag_id":25,"created_at":"2026-07-14 07:54:36.979125+00"},{"id":44,"content_id":52,"tag_id":25,"created_at":"2026-07-14 07:56:19.985645+00"},{"id":45,"content_id":53,"tag_id":25,"created_at":"2026-07-14 07:57:48.972177+00"},{"id":46,"content_id":54,"tag_id":25,"created_at":"2026-07-14 07:59:12.548659+00"},{"id":47,"content_id":55,"tag_id":25,"created_at":"2026-07-14 08:00:20.18447+00"},{"id":48,"content_id":56,"tag_id":25,"created_at":"2026-07-14 08:01:41.105986+00"},{"id":49,"content_id":57,"tag_id":25,"created_at":"2026-07-14 08:02:54.038278+00"},{"id":50,"content_id":58,"tag_id":26,"created_at":"2026-07-18 12:53:03.952297+00"},{"id":51,"content_id":59,"tag_id":27,"created_at":"2026-07-25 16:06:01.642719+00"},{"id":52,"content_id":60,"tag_id":26,"created_at":"2026-07-26 05:01:20.014491+00"},{"id":53,"content_id":61,"tag_id":27,"created_at":"2026-07-26 13:31:11.647058+00"},{"id":54,"content_id":62,"tag_id":27,"created_at":"2026-07-26 14:00:42.767685+00"},{"id":55,"content_id":63,"tag_id":24,"created_at":"2026-08-01 07:46:52.405046+00"},{"id":56,"content_id":64,"tag_id":26,"created_at":"2026-08-01 08:10:46.498356+00"},{"id":57,"content_id":65,"tag_id":26,"created_at":"2026-08-09 14:23:55.503293+00"},{"id":58,"content_id":65,"tag_id":28,"created_at":"2026-08-09 14:37:02.32836+00"},{"id":59,"content_id":25,"tag_id":28,"created_at":"2026-08-10 06:11:25.191741+00"},{"id":60,"content_id":66,"tag_id":24,"created_at":"2026-08-15 13:29:23.834062+00"},{"id":61,"content_id":67,"tag_id":26,"created_at":"2026-08-22 15:11:16.585751+00"},{"id":62,"content_id":68,"tag_id":26,"created_at":"2026-08-30 09:30:08.76695+00"},{"id":63,"content_id":71,"tag_id":29,"created_at":"2026-09-06 14:04:36.371259+00"},{"id":64,"content_id":72,"tag_id":29,"created_at":"2026-09-13 13:56:31.375251+00"},{"id":65,"content_id":73,"tag_id":22,"created_at":"2026-09-19 15:50:08.340417+00"},{"id":66,"content_id":74,"tag_id":27,"created_at":"2026-09-25 04:35:38.1627+00"},{"id":67,"content_id":75,"tag_id":27,"created_at":"2026-09-25 04:38:53.067623+00"},{"id":68,"content_id":76,"tag_id":29,"created_at":"2026-09-27 14:20:22.873834+00"},{"id":69,"content_id":77,"tag_id":26,"created_at":"2026-10-03 11:31:27.214744+00"},{"id":70,"content_id":84,"tag_id":31,"created_at":"2026-10-18 06:28:04.117069+00"}]}