[{"content":"ผมใช้ Delta DVP14SS211R (ตระกูล SS2) เป็น Modbus RTU Master อ่านค่าจากโมดูลอนาล็อกและอุปกรณ์อื่นบนบัส RS-485 เดียวกัน กว่าจะเดินได้ลื่นก็ชนกำแพงหลายรอบ\nบทความนี้รวมทุกจุดที่ทำให้เสียเวลา เรียงตามลำดับที่มักจะเจอ ถ้าคุณกำลังนั่งงงว่าทำไมส่งคำสั่งออกไปแล้วเงียบ ลองไล่ตามนี้ครับ\nรู้จักพอร์ตก่อน DVP ตระกูล SS2 มีสองพอร์ตที่หน้าตาต่างกันชัดเจน:\nพอร์ต รูปร่าง ใช้ทำอะไร COM1 หัวกลม (RS-232) โหลดโปรแกรมจาก ISPSoft/WPLSoft COM2 ขั้วเขียว + / − (RS-485) Modbus RTU ถ้าต่อ Modbus ให้ใช้ COM2 เท่านั้น\nกับดักที่ 1 — พารามิเตอร์สื่อสารที่ลอกกันมาผิด ค้นในเน็ตจะเจอคนแนะนำให้ตั้ง D1120 เป็น H87 กันเยอะมาก ค่านั้นเป็นของอินเวอร์เตอร์ Delta VFD ซึ่งใช้ Even parity\nถ้าอุปกรณ์ปลายทางของคุณเป็น 8N1 (8 data bit, ไม่มี parity, 1 stop bit) ซึ่งเป็นค่ามาตรฐานของอุปกรณ์ Modbus ส่วนใหญ่ ต้องตั้งเป็น:\n1 MOV 16#81 D1120 // 9600, 8 data, None parity, 1 stop (RTU) parity สองฝั่งต้องตรงกันเป๊ะ ผิดตัวเดียวคือคุยกันไม่รู้เรื่องเลย ไม่มี error ให้เห็นด้วย\nชุดคำสั่งตั้งค่าเต็ม ๆ ที่ผมใช้ (วางใน network แรก ขับด้วย M1002 ซึ่งทำงานครั้งเดียวตอนเริ่ม):\n1 2 3 4 5 M1002 ─┬─ MOV 16#81 D1120 // พารามิเตอร์สื่อสาร ├─ SET M1143 // โหมด RTU (ไม่ใช่ ASCII) ├─ SET M1161 // โหมด 8-bit ├─ SET M1120 // คงค่า D1120 ไว้ └─ MOV 50 D1129 // timeout 50 ms แล้วเพิ่มการเคลียร์แฟล็กค้างไว้ด้วย ไม่งั้นพอเจอ error ครั้งเดียวจะค้างไปเลย:\n1 2 3 M1123 ─── RST M1123 // รับข้อมูลเสร็จ M1127 ─── RST M1127 // ส่ง/รับ สมบูรณ์ M1129 ─── RST M1129 // timeout 💡 ถ้าไม่อยากพิมพ์เอง ISPSoft มี Communication Wizard ให้เลือก \u0026ldquo;MODBUS MODRD/MODWR/MODRW\u0026rdquo; แล้วตั้ง Parity = None, RTU, 8-bit มันจะสร้างบล็อกนี้ให้อัตโนมัติ\nกับดักที่ 2 — ขาด SET M1122 แล้วเงียบสนิท ⭐ อันนี้กินเวลาผมไปทั้งวัน\nทุกคำสั่ง MODRW ต้องมี SET M1122 คู่กันเสมอ — M1122 คือแฟล็ก \u0026ldquo;ส่งคำขอออกไป\u0026rdquo; ถ้าไม่ได้เซ็ต PLC จะไม่ส่งอะไรออกสายเลย\nอาการที่เจอคือ ladder ทำงานปกติทุกอย่าง ไม่มี error ไม่มี timeout แต่อุปกรณ์ปลายทางเงียบ วัดสายก็ไม่มีสัญญาณ จนหลงคิดว่าพอร์ต COM2 เสียหรือสายขาด\n1 2 [= D500 K1] ─── MODRW K2 K4 H0 D100 K4 SET M1122 ← ขาดบรรทัดนี้ = ไม่มีอะไรเกิดขึ้น ถ้าอยากรู้ว่า PLC ส่งออกจริงไหม ให้ใช้ ESP32 ต่อโมดูล MAX485 ทำเป็นตัวดักฟังบัส:\n1 2 3 4 5 6 7 8 9 void setup() { Serial.begin(115200); Serial2.begin(9600, SERIAL_8N1, 16, 17); // RX=16, TX=17 } void loop() { while (Serial2.available()) { Serial.printf(\u0026#34;%02X \u0026#34;, Serial2.read()); // พิมพ์ทุกไบต์บนบัสเป็น hex } } เห็นไบต์วิ่งก็แปลว่า PLC ส่งจริง ปัญหาอยู่ปลายทาง — ไม่เห็นอะไรเลยก็กลับมาดู M1122\nกับดักที่ 3 — ค่าที่อ่านกลับมาไม่ได้อยู่ที่ D ตัวแรก เพราะเราตั้ง M1161 (โหมด 8-bit) ค่าที่ MODRW อ่านกลับมาจะเก็บเป็น เฟรมดิบแยกทีละไบต์ ไม่ใช่ค่าที่ใช้ได้ทันที\nถ้าปลายทางเป็น D100 หน้าตาจะเป็นแบบนี้:\nรีจิสเตอร์ เก็บอะไร D100 slave address D101 function code D102 byte count D103 data ไบต์แรก (high) D104 data ไบต์ที่สอง (low) \u0026hellip; ไล่ไปเรื่อย ๆ ค่าจริงแบบ 16 บิตต้องประกอบเอง:\n1 2 MUL D103 K256 D200 // high × 256 ADD D200 D104 D201 // + low → D201 คือค่าจริง ⚠️ ลำดับสำคัญ — บล็อก MUL ต้องอยู่เหนือ ADD ใน ladder ไม่งั้นจะได้ค่าของรอบก่อนหน้า\nกับดักซ้อนกับดัก: D100 เก็บ slave address ซึ่งเป็นค่าคงที่ ถ้าเขียนเงื่อนไขเทียบคำสั่งแบบ [= D100 K2] เพื่อดูว่า \u0026ldquo;slave 2 ตอบมาไหม\u0026rdquo; มันจะเป็นจริงตลอดเวลา เพราะ D100 เป็น 2 อยู่แล้วทุกรอบ ค่าที่อยากเช็คจริง ๆ อยู่ที่ไบต์ล่างของข้อมูล\nค่าที่ไม่เกิน 255 หยิบจากไบต์ล่างได้ตรง ๆ แต่ค่ามาก ๆ เช่นอนาล็อก 0–4095 ต้องประกอบสองไบต์เสมอ\nกับดักที่ 4 — SS2 รองรับ FC16 อยู่แล้ว หลายคนเข้าใจว่า Delta DVP เขียนได้ทีละรีจิสเตอร์ (FC06) เท่านั้น เลยไล่เขียนทีละตัว จริง ๆ SS2 รองรับ function code 01–06, 0F และ 10 (FC16)\n1 2 3 4 FC03 = อ่าน Holding Register FC04 = อ่าน Input Register (พวกอนาล็อกอินพุตมักอยู่ตรงนี้) FC06 = เขียนทีละตัว FC16 = เขียนหลายตัวรวดเดียว ← ใช้ได้ ไม่ต้องเขียนทีละตัว กับดักที่ 5 — อ่านหลาย slave บนบัสเดียวต้องเข้าคิว บัส RS-485 คุยได้ทีละคู่ ถ้ายิงคำสั่งซ้อนกันจะชนกันมั่ว วิธีที่ใช้ได้ดีคือทำตัวนับเป็น sequencer:\n1 2 3 4 5 [= D500 K0] AND [M1012↑] ─── MODRW ... (slave 1) + SET M1122 [= D500 K1] AND [M1012↑] ─── MODRW ... (slave 2) + SET M1122 [= D500 K2] AND [M1012↑] ─── MODRW ... (slave 3) + SET M1122 INC D500 [\u0026gt;= D500 K3] ─── MOV K0 D500 // วนกลับ สองจุดที่ต้องระวัง:\nวาง INC D500 ไว้เหนือเงื่อนไขวนกลับ ไม่งั้นตัวนับจะข้ามหรือค้าง ใช้ M1012 (พัลส์ 100 ms) อย่าใช้ M1013 (1 วินาที) — 100 ms ทำให้การอ่านลื่นและไม่ค้าง ส่วน 1 วินาทีจะหน่วงจนรู้สึกได้ถ้ามีหลาย slave กับดักที่ 6 — ISPSoft กับ WPLSoft พิมพ์ค่าคงที่ไม่เหมือนกัน ถ้าย้ายจาก WPLSoft มา ISPSoft แล้วโปรแกรมคอมไพล์ไม่ผ่านหรือค่าเพี้ยน อาจเป็นเพราะเรื่องนี้:\nพิมพ์ยังไง WPLSoft K100, H87 ได้ตรง ๆ ISPSoft พิมพ์ K หรือ H นำหน้า มันจะกลายเป็นชื่อตัวแปร → ต้องใช้ 16#81 สำหรับเลขฐานสิบหก และเลขล้วนสำหรับฐานสิบ กับดักที่ 7 — โมดูลอนาล็อกราคาประหยัดที่ใช้ได้ดี โมดูล MODBUS-4AI4AO (ยี่ห้อ AMSAMOTION) เป็นตัวที่ผมใช้อยู่ ค่าเริ่มต้นคือ:\nSlave ID = 1, 9600, 8N1 ตั้ง DIP switch เลือกโหมดกระแส 4–20 mA อ่านอนาล็อกอินพุต: FC04 แอดเดรส 0–3 เขียนอนาล็อกเอาต์พุต: FC06 หรือ FC16 แอดเดรส 0–3 สเกลที่ต้องจำ: ค่าดิบ 820 = 4 mA และ 4095 = 20 mA (ไม่ใช่ 0 = 4 mA อย่างที่หลายคนคิด) แปลงเป็นค่าจริงด้วย:\n1 ค่าจริง = (raw − 820) × ช่วงที่ต้องการ ÷ (4095 − 820) สรุปเป็นเช็กลิสต์ เวลาต่อ Modbus RTU กับ Delta DVP ไล่ตามนี้แล้วจะไม่หลงทาง:\nต่อที่ COM2 (ขั้วเขียว) ไม่ใช่ COM1 D1120 = 16#81 ถ้าปลายทางเป็น 8N1 — อย่าลอก H87 มาใช้ M1143 (RTU) + M1161 (8-bit) + M1120 (คงค่า) + D1129 (timeout) ทุก MODRW ต้องมี SET M1122 ค่าที่อ่านกลับอยู่ที่ D+3 เป็นต้นไป และต้องประกอบ high/low เอง หลาย slave ให้ทำ sequencer ด้วย M1012 ISPSoft ใช้ 16#81 ไม่ใช่ H87 ถ้ายังเงียบอยู่ ให้เอา ESP32 ไปดักฟังบัสดูว่ามีไบต์วิ่งจริงไหม จะตัดปัญหาได้ครึ่งหนึ่งทันทีว่าอยู่ฝั่งส่งหรือฝั่งรับ\nบทความนี้เขียนจากการลงมือทำจริง อุปกรณ์ที่ใช้คือ Delta DVP14SS211R กับโมดูล MODBUS-4AI4AO บนบัส RS-485 เดียวกัน หากมีจุดไหนที่คุณเจอต่างออกไป เล่าให้ฟังได้ครับ\n","permalink":"https://kc-iot.com/posts/delta-plc-modbus-rtu-master/","summary":"ตั้ง Delta DVP ให้อ่านค่าจากอุปกรณ์ Modbus RTU ผ่าน RS-485 — รวมกับดักที่คู่มือไม่ได้เขียนไว้ชัด ทั้งพารามิเตอร์ที่หลายคนตั้งผิด บรรทัดที่ขาดไปแล้วเงียบสนิท และการอ่านค่ากลับที่ชวนหลงทาง","title":"Delta PLC เป็น Modbus RTU Master: 7 กับดักที่ทำให้เสียเวลาทั้งวัน"},{"content":"วันหนึ่งเฟิร์มแวร์ที่เคยอัปเดตผ่าน OTA ได้ดีมาตลอด จู่ ๆ ก็ขึ้น fail begin ทุกครั้ง ทั้งที่ตอนคอมไพล์ Arduino IDE บอกชัดเจนว่า:\n1 2 Sketch uses 1368848 bytes (69%) of program storage space. Maximum is 1966080 bytes. ใช้ไปแค่ 69% เอง แล้วทำไมถึงเขียนไม่ลง?\nคำตอบ: ตัวเลขนั้นไม่ใช่ของบอร์ดคุณ เปอร์เซ็นต์ที่ IDE แสดงคือขนาดของตารางพาร์ทิชันที่คุณเลือกไว้ในเมนู Tools ตอนนั้น ไม่ใช่ตารางที่อยู่ในชิปจริง ๆ\nและนี่คือหัวใจของปัญหา — ตารางพาร์ทิชันเปลี่ยนผ่าน OTA ไม่ได้\nตารางพาร์ทิชันอยู่ที่แอดเดรส 0x8000 ในแฟลช ส่วน OTA เขียนได้เฉพาะช่อง app เท่านั้น เวลาอัปเดตผ่าน OTA ตารางจึงยังเป็นของเดิมที่ถูกเขียนไว้ตอน flash ผ่านสาย USB ครั้งแรก ตลอดไป\nเพราะฉะนั้นถ้าตอนแรกคุณ flash ด้วย USB โดยเลือก partition แบบ default แล้ววันหลังเปลี่ยนเมนูเป็น min_spiffs เพื่อให้โค้ดใหญ่ขึ้นได้ — บอร์ดจะยังใช้ตาราง default อยู่ IDE บอกว่าพอ แต่บอร์ดบอกว่าไม่พอ\nตัวเลขที่ต้องจำ Partition Scheme ช่อง app (OTA เขียนได้) Default 4MB with spiffs 1,310,720 ไบต์ Minimal SPIFFS (Large APPS) 1,966,080 ไบต์ ถ้าไฟล์เฟิร์มแวร์ของคุณอยู่ระหว่างสองค่านี้แล้ว OTA ล้ม แปลว่าบอร์ดถูก flash ครั้งแรกด้วย default เกือบแน่นอน\nเคสที่ผมเจอ: เฟิร์มแวร์เดิม 1,267,168 ไบต์ อัปเดต OTA ได้ปกติ (ต่ำกว่า 1,310,720) พอเพิ่มการเชื่อมต่อแบบเข้ารหัสเข้าไป โตเป็น 1,368,848 ไบต์ ซึ่งเกินเพดานเดิมไป 58 KB — จบเห่ทันที\nทางแก้ ต่อสาย USB flash หนึ่งครั้ง โดยเลือก partition scheme ที่ต้องการ การ flash ผ่าน USB จะเขียน bootloader + ตารางพาร์ทิชัน + แอปพร้อมกัน หลังจากนั้น OTA จะใช้พื้นที่ได้เต็มขนาดใหม่\nข่าวดีคือค่าที่ตั้งไว้ไม่หาย — ช่อง NVS อยู่ที่ 0x9000 และมีขนาดเท่ากันทั้งสอง scheme ค่า WiFi, รหัสอุปกรณ์, ค่าตั้งต่าง ๆ ที่เก็บด้วย Preferences.h ยังอยู่ครบหลัง flash\nทำให้ครั้งหน้าไม่ต้องเดา ปัญหาจริง ๆ ของเรื่องนี้คือคำว่า fail begin มันไม่ได้บอกอะไรเลย ให้แก้ตัวรับ OTA ฝั่งบอร์ดให้รายงานเหตุผลกลับมาด้วย:\n1 2 3 4 5 6 7 8 if (!Update.begin(sizeOfNewFirmware)) { // ส่งกลับไปให้เห็นว่าเกิดอะไรขึ้นจริง ๆ Serial.printf(\u0026#34;OTA เริ่มไม่ได้: %s | พื้นที่ที่เขียนได้จริง: %u ไบต์ | ไฟล์ใหม่: %u ไบต์\\n\u0026#34;, Update.errorString(), ESP.getFreeSketchSpace(), sizeOfNewFirmware); return; } ESP.getFreeSketchSpace() คือตัวเลขที่บอกความจริง — มันอ่านจากตารางพาร์ทิชันที่อยู่ในบอร์ดจริง ไม่ใช่จากเมนูตอนคอมไพล์ เอาสองตัวเลขนี้มาเทียบกันก็รู้ทันทีว่าปัญหาคืออะไร\nถ้าระบบ OTA ของคุณส่งผลกลับผ่าน MQTT หรือ HTTP อยู่แล้ว ส่งข้อความนี้กลับไปด้วยจะประหยัดเวลาไล่หาสาเหตุได้มาก\nเกร็ดเพิ่ม: ระบบกันอัปเดตถอยหลังที่อาจล็อกตัวเอง ถ้าระบบ OTA ของคุณมีกลไกกันอัปเดตย้อนเวอร์ชัน (เทียบ timestamp หรือเลขเวอร์ชันก่อนยอมเขียน) ให้ระวังกรณีนี้:\nพอ flash บอร์ดผ่าน USB ด้วยไฟล์ที่ไม่เคยอัปขึ้นเซิร์ฟเวอร์ บอร์ดจะรันเฟิร์มแวร์ที่เซิร์ฟเวอร์ไม่รู้จัก กลไกกันถอยหลังจะมองว่าทุกอย่างบนเซิร์ฟเวอร์ \u0026ldquo;เก่ากว่าตัวเอง\u0026rdquo; แล้ว ปฏิเสธ OTA ทุกเวอร์ชันถาวร — ปลดล็อกจากฝั่งเซิร์ฟเวอร์ไม่ได้เลย ต้องเดินไปต่อสาย USB อย่างเดียว\nวิธีกัน: อัปไฟล์ .bin ขึ้นเซิร์ฟเวอร์ก่อน แล้วค่อย flash USB ด้วยไฟล์เดียวกันนั้น พอบอร์ดบูตขึ้นมามันจะเห็นว่าตรงกับที่เซิร์ฟเวอร์มี แล้วรับค่าฐานเวลาไปใช้ต่อได้ตามปกติ\nทำเป็นนิสัยไว้เลยครับ: อัปขึ้นก่อน แล้วค่อย flash ไม่ว่าจะเร่งแค่ไหน\n","permalink":"https://kc-iot.com/posts/esp32-ota-fail-begin/","summary":"ตัวเลขเปอร์เซ็นต์ที่ Arduino IDE แสดงตอนคอมไพล์ คือขนาดของตารางพาร์ทิชันที่คุณเลือกในเมนู ไม่ใช่ตารางที่อยู่ในบอร์ดจริง สองอย่างนี้ต่างกันได้ และเป็นเหตุให้ OTA ล้มแบบงง ๆ","title":"ESP32 OTA ขึ้น fail begin ทั้งที่คอมไพล์บอกว่าใช้ไปแค่ 69%"},{"content":"ถ้าคุณต่อ ESP32 เข้ากับ RS-485 แล้วอ่านค่าไม่ได้ อ่านได้บ้างไม่ได้บ้าง หรือค่าเพี้ยนแบบไม่มีเหตุผล — ก่อนจะสั่งโมดูลใหม่ ลองเช็คเรื่องเดียวนี้ก่อนครับ\nกฎข้อเดียวที่สำคัญที่สุด เลี้ยงโมดูล RS-485 ด้วย 3.3V เสมอเมื่อใช้กับ ESP32 — ห้ามใช้ 5V แล้วต่อตัวแบ่งแรงดัน\nผมทดสอบซ้ำกับโมดูล 3 แบบ บนบอร์ด 3 รุ่น ได้ข้อสรุปตรงกันทุกครั้งว่า ตัวแปรที่ชี้เป็นชี้ตายคือแรงดันเลี้ยง ไม่ใช่ยี่ห้อหรือรุ่นของโมดูล\nวิธีต่อ ผลที่ได้ เลี้ยง 3.3V → ขา RO/RXD เข้า GPIO ตรง ✅ สัญญาณสวิงเต็ม 3.3V อ่านได้นิ่ง เลี้ยง 5V + ตัวแบ่งแรงดันลงมา 3.3V ❌ สัญญาณถูกกดลงมาใกล้เส้นตัดสิน บิตเพี้ยน เหตุผลคือตัวแบ่งแรงดันไม่ได้แค่ลดแรงดัน แต่ทำให้ขอบสัญญาณลาดลงด้วย พอความเร็ว 9600 bps ขึ้นไปและมีสายยาว ๆ ค่าที่ ESP32 อ่านได้จะไปวนอยู่ใกล้แรงดันเส้นตัดสินระหว่าง 0 กับ 1 — บางบิตผ่าน บางบิตไม่ผ่าน กลายเป็นอาการ \u0026ldquo;อ่านได้บ้างไม่ได้บ้าง\u0026rdquo; ที่หาสาเหตุยากที่สุด\nโดยเฉพาะ ESP32-C3 ที่มีเส้นตัดสินอยู่ราว 2.48V ยิ่งไวกับเรื่องนี้มาก\nบทเรียนที่ทำให้เกือบทิ้งของดี ผมมีโมดูล MAX485 สีน้ำเงินแบบ 4 ขา (VCC/TXD/RXD/GND ควบคุมทิศทางอัตโนมัติในตัว) เอามาทดสอบกับ Arduino UNO แล้วอ่านไม่ผ่านสักครั้ง ในขณะที่โมดูลตัวอื่นทดสอบบน UNO ตัวเดียวกันผ่านฉลุย\nผมสรุปตอนนั้นว่า \u0026ldquo;โมดูลตัวนี้เสีย\u0026rdquo; เพราะคุมตัวแปรครบแล้ว — บอร์ดเดียวกัน สายเดียวกัน โค้ดเดียวกัน เหลือแค่ตัวโมดูลที่ต่างกัน\nข้อสรุปนั้นผิดครับ\nพอเอามาเลี้ยง 3.3V ต่อกับ ESP32-C3 แล้วอ่านมิเตอร์จริง ผลคือ อ่านผ่าน 100% ครบทุกรีจิสเตอร์ ตั้งแต่ครั้งแรก\nสิ่งที่ผมพลาดคือ UNO ทำงานที่ 5V ล้วน ซึ่งไม่ใช่ปลายทางจริงของงาน การ \u0026ldquo;คุมตัวแปรครบ\u0026rdquo; บนแพลตฟอร์มที่ไม่ใช่ของจริง ไม่ได้แปลว่าข้อสรุปจะใช้ได้กับของจริง\nก่อนตัดสินว่าโมดูลเสีย ให้ลองเลี้ยง 3.3V บนบอร์ดที่จะใช้จริงก่อนเสมอ\nเลือกโมดูลให้ตรงงาน โมดูล RS-485 แบ่งเป็นสองกลุ่มใหญ่ตามวิธีสลับทิศทางรับ/ส่ง\nกลุ่มที่ 1 — ควบคุมทิศทางอัตโนมัติ มี 4 ขา: VCC, GND, TXD, RXD ไม่ต้องใช้ GPIO เพิ่ม เขียนโค้ดเหมือนใช้ Serial ธรรมดา\nเหมาะกับ: มิเตอร์ไฟฟ้า เซนเซอร์ โมดูล I/O — อุปกรณ์ที่ตอบกลับตามจังหวะปกติ\nบอร์ดสีดำ \u0026ldquo;RS485_TTL UART with Automatic Direction Control\u0026rdquo; — ตัวที่ผมใช้เป็นหลัก มี TVS diode กันไฟกระชากในตัว ลากสายได้ไกลเกือบกิโลเมตร โมดูลสีน้ำเงิน MAX485 + CD4069 (4 ขา) — ราคาถูกกว่ามาก และใช้ได้ดีจริง ถ้าเลี้ยง 3.3V กลุ่มที่ 2 — ต้องคุมทิศทางเอง (มีขา RE/DE) โมดูล MAX485 แบบ 6 ขา หรือชิปเปล่าอย่าง SN75176BP ต้องใช้ GPIO อีกขาสั่งสลับรับ/ส่งเอง\nเหมาะกับ: อินเวอร์เตอร์และ soft starter บางรุ่น (ABB เป็นตัวอย่างที่ชัด) ซึ่งจังหวะการตอบไม่เข้ากับวงจร auto-direction ทำให้ต้องคุมเองถึงจะคุยรู้เรื่อง\nการต่อ: RE ต้องชนกับ DE แล้วต่อเข้า GPIO ขาเดียว\n1 2 3 4 5 6 7 8 9 10 11 #define DE_PIN 4 void preTransmission() { digitalWrite(DE_PIN, HIGH); } // ส่ง void postTransmission() { digitalWrite(DE_PIN, LOW); } // กลับมารับ void setup() { pinMode(DE_PIN, OUTPUT); digitalWrite(DE_PIN, LOW); node.preTransmission(preTransmission); node.postTransmission(postTransmission); } ⚠️ ถ้าปล่อยขา RE ลอยไว้ ตัวรับจะปิดตลอดเวลา ผลคือส่งคำสั่งออกไปได้แต่ไม่เคยได้ยินอะไรกลับมาเลย — เป็นอีกอาการที่ทำให้คนเข้าใจผิดว่าอุปกรณ์ปลายทางไม่ตอบ\nระวังถ้าจะใช้ชิปเปล่า ผมลองเอา SN75176BP (ชิป DIP-8 เปล่า ๆ) ต่อตรงกับ ESP32-C3 โดยไม่ใส่ตัวต้านทานอะไรเลย ผลคือ:\nอ่านสำเร็จแค่ 42% แล้วพลาดยาวต่อเนื่อง หลายรีจิสเตอร์อ่านไม่ออกเลย ได้ค่าว่างกลับมา สาเหตุคือชิปเปล่าไม่มีอะไรอยู่บน PCB ช่วยเลย — ไม่มีวงจร fail-safe bias และตอนส่งทุกครั้งที่ DE ขึ้น HIGH ขา RE ก็ขึ้นตามด้วย ทำให้ขา RO กลายเป็นสถานะลอย (Hi-Z) ขา RX ของ ESP32 จึงรับสัญญาณรบกวนเข้าบัฟเฟอร์ปนกับคำตอบจริง\nถ้าจำเป็นต้องใช้ชิปเปล่าจริง ๆ ต้องเติมสามอย่างนี้:\nตำแหน่ง ค่า หน้าที่ RO → 3.3V 10 kΩ pull-up สำคัญที่สุด กันขาลอยตอนส่ง A → 3.3V 680 Ω fail-safe bias B → GND 680 Ω fail-safe bias (ถ้าใส่ตัวต้านทานปิดปลายสาย 120 Ω ด้วย ต้องลด bias ลงเหลือ 390 Ω)\nพูดตรง ๆ คือ ซื้อโมดูลสำเร็จรูปคุ้มกว่าเยอะ เพราะเขาใส่พวกนี้มาให้บนบอร์ดแล้ว\nเช็กลิสต์ก่อนโทษว่าของเสีย เลี้ยงโมดูลด้วย 3.3V หรือยัง (ไม่ใช่ 5V + ตัวแบ่งแรงดัน) ถ้าโมดูลมีขา RE/DE — ต่อ RE ชนกับ DE แล้วหรือยัง ต่อ GND ร่วม กับอุปกรณ์ปลายทางแล้วหรือยัง — สาย A/B สองเส้นอย่างเดียวไม่พอ ข้อนี้คนพลาดบ่อยมากเวลาอุปกรณ์อยู่คนละตู้ สลับสาย A กับ B ลองดู (ผิดขั้วไม่พังแต่ไม่ทำงาน) baud rate / parity / stop bit ตรงกับปลายทางไหม ทดสอบบนบอร์ดที่จะใช้จริง ไม่ใช่บอร์ด 5V ข้อ 1 กับข้อ 6 คือสองข้อที่ผมเสียเวลาไปมากที่สุด หวังว่าจะช่วยให้คุณข้ามไปได้เลยครับ\nถ้ามีอาการแปลก ๆ ที่ไม่ตรงกับข้างบน เล่าให้ฟังได้ครับ ผมอยากรู้ว่ามีเคสไหนที่ยังไม่เคยเจอบ้าง\n","permalink":"https://kc-iot.com/posts/esp32-rs485-module/","summary":"ผมเกือบทิ้งโมดูล RS-485 ที่ใช้งานได้ดีเพราะทดสอบผิดวิธี บทความนี้เล่าว่าตัวแปรที่ชี้เป็นชี้ตายไม่ใช่ยี่ห้อโมดูล แต่เป็นแรงดันที่เลี้ยงมัน พร้อมวิธีเลือกโมดูลให้ตรงกับงาน","title":"ESP32 อ่าน Modbus RS-485 ไม่ได้? ลองเลี้ยงโมดูลด้วย 3.3V ก่อนจะโทษว่าของเสีย"},{"content":"โค้ดต่อ WiFi ที่เราคุ้นกันดีมักหน้าตาประมาณนี้:\n1 2 3 4 5 6 7 8 9 10 11 12 13 void setupWiFi() { WiFi.begin(SSID, PASSWORD); int retry = 0; while (WiFi.status() != WL_CONNECTED \u0026amp;\u0026amp; retry \u0026lt; 30) { delay(500); Serial.print(\u0026#34;.\u0026#34;); retry++; } if (WiFi.status() != WL_CONNECTED) { Serial.println(\u0026#34;ต่อ WiFi ไม่ได้ รีบูต...\u0026#34;); ESP.restart(); // ← ตรงนี้แหละคือปัญหา } } บนโต๊ะทำงานมันดูสมเหตุสมผลมาก — ต่อไม่ได้ก็เริ่มใหม่ เดี๋ยวก็ติดเอง\nแต่พอเอาไปติดตั้งจริงในตู้ไฟฟ้าที่สัญญาณอ่อน มันจะกลายเป็นกับดักทันที\nเกิดอะไรขึ้นในสนามจริง ผมเจอกับบอร์ดที่ติดตั้งในตู้เหล็กใกล้หม้อแปลง สัญญาณ WiFi วัดได้ประมาณ −78 dBm ซึ่งอ่อนแต่ยังพอใช้ได้\nปัญหาคือที่ความแรงระดับนั้น การเชื่อมต่อบางครั้งใช้เวลา 20–40 วินาที กว่าจะสำเร็จ ซึ่งนานกว่าเวลารอในโค้ด ผลที่ตามมาคือ:\n1 2 3 บูต → รอ 15 วินาที → ยังไม่ติด → รีบูต → บูต → รอ 15 วินาที → ยังไม่ติด → รีบูต → วนแบบนี้ไปเรื่อย ๆ ไม่มีวันจบ และเพราะโค้ดค้างอยู่ใน setup() ไม่เคยเข้า loop() เลย ผลกระทบจึงลามไปทั้งหมด:\n❌ ไม่อ่านค่าจากเซนเซอร์/มิเตอร์ ❌ เว็บตั้งค่าบนบอร์ดเข้าไม่ได้ ❌ OTA ใช้ไม่ได้ — แก้ไขจากระยะไกลไม่ได้เลย ต้องเดินไปถอดบอร์ดมา ข้อสุดท้ายเจ็บที่สุด เพราะเป็นสถานการณ์ที่ต้องปีนขึ้นไปหน้างานพร้อมสายและโน้ตบุ๊ก\nหลักคิดที่ควรใช้แทน WiFi เป็นแค่ช่องทางส่งข้อมูล ไม่ใช่เงื่อนไขของการทำงาน\nเครื่องวัดที่ต่อเน็ตไม่ได้ ก็ยังควรวัดค่าต่อไปได้ ยังควรแสดงผลที่หน้าจอได้ และที่สำคัญที่สุดคือ ยังต้องเปิดรับการแก้ไขจากระยะไกลได้ทันทีที่เน็ตกลับมา\nเขียนแบบนี้แทน 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void setupWiFi() { WiFi.mode(WIFI_STA); WiFi.setAutoReconnect(true); // ให้ stack ต่อกลับเองเบื้องหลัง WiFi.setSleep(false); // ปิดโหมดประหยัดไฟ สัญญาณนิ่งกว่า WiFi.setTxPower(WIFI_POWER_19_5dBm); // ดันกำลังส่งสูงสุด ช่วยได้จริงตอนสัญญาณอ่อน WiFi.begin(SSID, PASSWORD); unsigned long t0 = millis(); while (WiFi.status() != WL_CONNECTED \u0026amp;\u0026amp; millis() - t0 \u0026lt; 15000) { delay(250); } if (WiFi.status() == WL_CONNECTED) { Serial.printf(\u0026#34;WiFi ต่อแล้ว: %s (%d dBm)\\n\u0026#34;, WiFi.localIP().toString().c_str(), WiFi.RSSI()); } else { Serial.println(\u0026#34;ยังต่อ WiFi ไม่ได้ — ทำงานต่อไปก่อน เดี๋ยวลองใหม่เอง\u0026#34;); // ไม่รีบูต ปล่อยให้ setup() จบแล้วเข้า loop() ตามปกติ } } แล้วใน loop() ทำตัวเฝ้าดูแบบไม่บล็อก:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 const unsigned long WIFI_CHECK_MS = 15000; // ตรวจทุก 15 วินาที const unsigned long WIFI_HARD_MS = 120000; // หลุดเกิน 2 นาที ค่อยรีเซ็ตสแตก unsigned long lastCheck = 0, downSince = 0; void manageWiFi() { if (millis() - lastCheck \u0026lt; WIFI_CHECK_MS) return; lastCheck = millis(); if (WiFi.status() == WL_CONNECTED) { downSince = 0; return; } if (downSince == 0) downSince = millis(); if (millis() - downSince \u0026gt; WIFI_HARD_MS) { // หลุดนานผิดปกติ = WiFi stack อาจค้าง เริ่มใหม่ทั้งชุด (แต่ไม่รีบูตบอร์ด) Serial.println(\u0026#34;WiFi หลุดนาน — เริ่ม stack ใหม่\u0026#34;); WiFi.disconnect(true); delay(100); WiFi.begin(SSID, PASSWORD); downSince = millis(); } else { WiFi.reconnect(); // ลองเบา ๆ ก่อน } } void loop() { manageWiFi(); // ไม่บล็อก คืนค่าทันทีถ้ายังไม่ถึงเวลา readSensors(); // ทำงานหลักต่อได้เสมอ handleWebServer(); handleOTA(); } จุดสำคัญคือ ทุกฟังก์ชันต้องไม่บล็อก — manageWiFi() เช็คเวลาแล้วคืนค่าทันทีถ้ายังไม่ถึงรอบ ทำให้งานหลักเดินได้เต็มความเร็ว\nทำไมต้องมีสองระดับ ในทางปฏิบัติผมเจอ WiFi หลุดสองแบบที่ต้องแก้ต่างกัน:\nอาการ สาเหตุ วิธีแก้ หลุดชั่วคราว AP รีบูต, สัญญาณตกชั่วขณะ, คนเดินบัง WiFi.reconnect() พอ หลุดยาวผิดปกติ WiFi stack ภายในค้าง ต้อง disconnect(true) แล้ว begin() ใหม่ แบบแรกเกิดบ่อยกว่ามากและหายเองใน 15–30 วินาที ถ้ารีเซ็ตสแตกทุกครั้งจะยิ่งช้ากว่าเดิม ส่วนแบบที่สองนาน ๆ เกิดที แต่ถ้าไม่มีตัวจัดการก็ค้างยาวจนกว่าจะมีคนไปกดรีเซ็ต\nแล้วถ้าอยากรู้ว่าบอร์ดหลุดตอนไหน ปัญหาของอุปกรณ์ที่เน็ตหลุดคือ มันบอกใครไม่ได้ว่าตัวเองหลุด วิธีที่ใช้ได้ผลคือให้ฝั่งเซิร์ฟเวอร์เป็นคนตัดสินแทน:\nใช้ MQTT Last Will (LWT) ให้ broker ประกาศ offline แทนบอร์ดเมื่อการเชื่อมต่อขาด หรือฝั่งเซิร์ฟเวอร์นับเวลาว่า \u0026ldquo;ถ้าไม่มีข้อมูลเข้ามาเกิน X นาที ให้ถือว่าออฟไลน์\u0026rdquo; ข้อควรรู้: MQTT ตั้ง LWT ได้แค่หนึ่ง topic ต่อหนึ่งการเชื่อมต่อ ถ้าบอร์ดหนึ่งส่งข้อมูลหลายหมวดและอยากรู้สถานะแยกกัน จะเจอปัญหาว่าอีกหมวดค้างสถานะ online ตลอด — ต้องใช้วิธีนับเวลาเงียบเสริมเข้าไป\nสรุป อย่ารีบูตเพราะ WiFi ต่อไม่ติด — ที่จุดสัญญาณอ่อนมันจะวนไม่จบและปิดทางแก้จากระยะไกลไปด้วย ให้ setup() ผ่านไปให้ได้เสมอ แล้วจัดการ WiFi แบบไม่บล็อกใน loop() แยกการแก้เป็นสองระดับ: reconnect เบา ๆ ก่อน แล้วค่อยรีเซ็ตสแตกถ้าหลุดยาว เปิด setAutoReconnect(true) + setSleep(false) + กำลังส่งสูงสุด ตั้งแต่แรก ถ้าจำได้ข้อเดียว ให้จำข้อนี้ครับ: บอร์ดที่ต่อเน็ตไม่ได้ยังมีประโยชน์ แต่บอร์ดที่วนรีบูตไม่มีประโยชน์อะไรเลย\n","permalink":"https://kc-iot.com/posts/esp32-wifi-no-restart/","summary":"โค้ดตัวอย่างที่หาเจอทั่วไปมักสั่งรีบูตเมื่อ WiFi ต่อไม่ติด ซึ่งใช้ได้ดีบนโต๊ะแต่กลายเป็นกับดักเมื่อเอาไปติดตั้งในตู้เหล็กที่สัญญาณอ่อน บทความนี้อธิบายว่าทำไม และควรเขียนแทนอย่างไร","title":"อย่าใส่ ESP.restart() ตอน WiFi ต่อไม่ติด — บอร์ดจะวนรีบูตไม่จบที่จุดสัญญาณอ่อน"},{"content":"ทำไมถึงมีเว็บนี้ ผมเป็นช่างไฟฟ้าที่ไปได้ไกลกว่าตู้คอนโทรล — เริ่มจากอยากรู้ว่าเครื่องจักรที่ดูแลอยู่ทำงานยังไงในแต่ละวัน เลยลองเอา ESP32 ไปต่ออ่านค่า แล้วก็ลามไปเรื่อยจนกลายเป็นระบบที่เฝ้าดูเครื่องจักรได้ทั้งโรงงาน\nระหว่างทางเจอปัญหาที่คู่มือไม่ได้เขียนไว้เยอะมาก บางเรื่องเสียเวลาไปทั้งวันกับบรรทัดเดียวที่ขาดไป บางเรื่องนึกว่าอุปกรณ์เสียแต่จริง ๆ แค่ต่อไฟผิดขา\nเว็บนี้คือที่เก็บเรื่องพวกนั้นครับ — เขียนไว้ให้ตัวเองในอนาคต และเผื่อมีคนกำลังติดปัญหาเดียวกันอยู่\nเนื้อหาแนวไหน ESP32 / Arduino กับงานอุตสาหกรรม — Modbus RTU, RS-485, การอ่านมิเตอร์และอินเวอร์เตอร์ PLC — Delta DVP, การทำ Modbus master, กับดักในการเขียน ladder LoRa / MQTT / Node-RED — ส่งข้อมูลจากหน้างานไปเก็บและแจ้งเตือน การเอาข้อมูลมาใช้จริง — ไม่ใช่แค่เก็บกราฟไว้ดูสวย ๆ แต่ใช้จับความผิดปกติก่อนเครื่องจะพัง ทุกบทความมาจากของที่ทำเองและเจอเอง ไม่ใช่แปลจากคู่มือ\nข้อจำกัดที่ควรรู้ เนื้อหาทั้งหมดเล่าเฉพาะ เทคนิคและวิธีแก้ปัญหา โดยไม่มีข้อมูลของสถานประกอบการใด ๆ ทั้งสิ้น — ไม่มีชื่อ ไม่มีที่อยู่เครือข่าย ไม่มีข้อมูลการผลิตหรือการใช้พลังงาน ตัวอย่างที่ยกมาเป็นค่าสมมติหรือค่าทั่วไปของอุปกรณ์นั้น ๆ\nและเนื่องจากผมเป็นช่างไม่ใช่วิศวกรที่มีใบอนุญาต สิ่งที่เขียนคือสิ่งที่ใช้ได้กับงานของผม โปรดตรวจสอบกับมาตรฐานและความปลอดภัยของงานคุณเองก่อนนำไปใช้ โดยเฉพาะงานที่เกี่ยวกับไฟฟ้าแรงดันสูงหรือระบบที่มีความเสี่ยง\nติดต่อ มีอะไรอยากถามหรือแลกเปลี่ยน ทักมาได้ครับ\n","permalink":"https://kc-iot.com/about/","summary":"เกี่ยวกับ KC IoT","title":"เกี่ยวกับเว็บนี้"}]