เนื้อหาในบทความนี้ต่อเนื่องจากเรื่อง
“พัฒนาระบบควบคุมบน ESP32 โดยไลบรารี FreeRTOS” โดยประเด็นสำคัญคือการแก้ปัญหาที่ทิ้งท้ายไว้ในบทความนั้นเกี่ยวกับค่าสัมประสิทธิ์ของตัวควบคุมที่เป็นค่าคงที่ในโปรแกรม เมื่อผู้ใช้เปลี่ยนค่าคาบเวลาการสุ่มของอัลกอริทึมควบคุมไปจากเดิมจะทำให้ตัวควบคุมทำงานไม่ถูกต้อง วิธีการง่ายสุดคือใช้ซอฟต์แวร์ Scilab คำนวณค่าสัมประสิทธิ์ตามคาบเวลาใหม่และอัพเดทให้กับตัวควบคุม ซึ่งสามารถส่งผ่านพอร์ตอนุกรมหรือจากหน้าของ NETPIE Freeboard ก็ได้ อย่างไรก็ตามการใช้ slider widget มีความไม่เหมาะสมบางประการ และ NETPIE ยังไม่มี widget สำเร็จรูปสำหรับการส่งคำสั่งในรูปสตริงใดๆ ให้กับอุปกรณ์ ดังนั้นเราจะศึกษาการใช้ widget ประเภท html ทำหน้าที่ดังกล่าว โดยต้องมีการใส่โค้ด JavaScript เพื่อ chat ไปหา microgear ทางด้าน ESP32
ย้อนกลับไปในหัวข้อ 3.5 บทที่ 3 ของหนังสือ “ตัวควบคุมป้อนกลับบนอินเทอร์เน็ตโดย ESP8266 และ ESP32” เราได้ออกแบบตัวควบคุมเชิงเส้นสำหรับพลานต์ถังน้ำ 3 ระดับ ที่มีฟังก์ชันถ่ายโอน
ตัวควบคุมในบทที่ 3 ใช้คาบเวลาการสุ่ม T = 0.08 วินาที เมื่อใช้ Scilab แปลงเป็นระบบเวลาวิยุต ได้สัมประสิทธิ์ของตัวควบคุมเท่ากับ
float a[3] = {1, -0.048780487804877870, -0.951219512195121910};
float b[3] = { 1.492682926829265800, 0.058536585365856639,
-1.434146341463415200 };
ซึ่งได้ทดสอบแล้วว่าให้ผลตอบสนองที่ใกล้เคียงกับการจำลอง (รูปที่ 3.11) และได้กำหนดเป็นตัวแปรคงที่ในโปรแกรมนับแต่นั้นมา
จนกระทั่งเมื่อตัวควบคุมเปลี่ยนมาใช้โครงสร้างของ FreeRTOS ที่ผู้ใช้สามารถเปลี่ยนคาบเวลาได้ในขณะทำงาน ค่าโดยปริยายของ T ถูกเปลี่ยนเป็น 0.01 วินาที สำหรับตัวควบคุม PID ไม่มีปัญหาเนื่องจากสัมประสิทธิ์จะติดเป็นสมการที่อัพเดทใหม่ทุกครั้งที่ค่า T เปลี่ยน แต่ตัวควบคุมเชิงเส้นที่สัมประสิทธิ์เป็นค่าคงที่จะทำงานไม่ถูกต้อง ทดลองได้โดยเปลี่ยนตัวควบคุมด้วยคำสั่ง controller=cc และเปรียบเทียบผลตอบสนองขั้นบันไดกับข้อมูลจากการจำลองได้ดังรูปที่ 1 จะเห็นได้ว่าแตกต่างกันอย่างชัดเจน ผลตอบสนองนี้แสดงถึงลูปป้อนกลับที่ไม่เสถียร
รูปที่ 1 ผลตอบสนองขึ้นบันไดจากตัวควบคุมเชิงเส้นที่ค่าสัมประสิทธิ์ไม่ถูกต้อง
โดยใช้คำสั่งการแปลงบน Scilab ตามที่สาธิตในบทที่ 3 ค่าสัมประสิทธิ์ที่ถูกต้องเมื่อเปลี่ยนค่าคาบเวลาการสุ่ม T = 0.01 วินาที จะเป็นดังนี้
float a[3] = {1, -0.333333333333333040, -0.666666666666666630};
float b[3] = { 1.253124999999999600, 0.006249999999995537,
-1.246874999999998400};
การแก้ปัญหาทำได้ 2 วิธีคือ
สร้างสมการของสัมประสิทธิ์ที่เป็นฟังก์ชันของคาบเวลา T
สร้างคำสั่งสำหรับโหลดสัมประสิทธิ์ใหม่เมื่อต้องการเปลี่ยนคาบเวลา
วิธีการที่ 1 มีข้อได้เปรียบคือโปรแกรมสามารถอัพเดทสัมประสิทธิ์ได้อัตโนมัติทุกครั้งที่ผู้ใช้เปลี่ยนคาบเวลา แต่การหาความสัมพันธ์สำหรับตัวควบคุมเชิงเส้นอันดับทั่วไปจะซับซ้อนมาก ในภาคผนวก C ของหนังสือได้นำเสนอวิธีการนี้สำหรับฟังก์ชันถ่ายโอนอันดับสอง ซึ่งสามารถประยุกต์ใช้กับตัวควบคุมเชิงเส้นทั่วไปได้หลังจากแปลงเป็นตัวควบคุมอันดับสองต่ออนุกรมกัน ซึ่งเป็นวิธีที่นิยมใช้สำหรับระบบฝังตัว
ในบทความนี้จะใช้วิธีการที่ 2 โดยสร้างคำสั่งใหม่ในฟังก์ชัน cmdInt() ตามฟอร์แมตดังนี้
cccoef = n, a[0], a[1],... ,a[n-1],b[0],b[1], ..., b[n-1]
ซึ่งสามารถส่งผ่านพอร์ตอนุกรมหรือ NETPIE ได้ รายละเอียดในการสร้าง widget บน NETPIE Freeboard จะได้กล่าวถึงต่อไป เพื่อความง่ายต่อการทำความเข้าใจ จะจำกัดจำนวนสัมประสิทธิ์ของพหุนามเศษและส่วนไว้เท่ากับ 3
ในฟังก์ชัน cmdInt() เพิ่มเงื่อนไขสำหรับคำสั่งใหม่เข้าไปดังนี้
// receive custom control coefficients from user
else if (cmdstring.equalsIgnoreCase("cccoeff")) {
int parmIndex = parmstring.indexOf(',');
String numcoeffstr = parmstring.substring(0, parmIndex);
numcoeffstr.trim();
parmstring = parmstring.substring(parmIndex+1);
int numcoeff = numcoeffstr.toInt();
int parmcount = 0;
while (parmcount < numcoeff) { // a[i] coefficient
parmIndex = parmstring.indexOf(',');
coeffstr_a[parmcount] = parmstring.substring(0, parmIndex);
parmstring = parmstring.substring(parmIndex+1);
coeffstr_a[parmcount++].trim();
}
parmcount = 0;
while (parmcount < numcoeff) { // b[i] coefficient
parmIndex = parmstring.indexOf(',');
coeffstr_b[parmcount] = parmstring.substring(0, parmIndex);
parmstring = parmstring.substring(parmIndex+1);
coeffstr_b[parmcount++].trim();
}
// update control coefficients
taskENTER_CRITICAL(&myMutex);
parmcount = 0;
while (parmcount < numcoeff) {
a[parmcount] = coeffstr_a[parmcount].toFloat();
b[parmcount] = coeffstr_b[parmcount].toFloat();
x[parmcount] = 0; // reset controller state
parmcount++;
}
taskEXIT_CRITICAL(&myMutex);
Serial.println("Custom control coefficients updated");
// for debug purposes
Serial.println("==========================================");
Serial.print("numcoeff = ");
Serial.println(numcoeff);
parmcount = 0;
while (parmcount < numcoeff) {
Serial.print("a[");
Serial.print(parmcount);
Serial.print("]=");
Serial.print(a[parmcount],12);
Serial.print(", b[");
Serial.print(parmcount);
Serial.print("]=");
Serial.println(b[parmcount],12);
parmcount++;
}
}
โค้ดในส่วนนี้เป็นเพียงการแยกสตริงของสัมประสิทธิ์และแปลงเป็นตัวเลขเพื่ออัพเดทให้กับตัวแปรสัมประสิทธิ์เดิม นอกจากนั้นยังให้แสดงค่าใน Serial Monitor เพื่อตรวจสอบว่ารับค่าได้ถูกต้องหรือไม่
ส่วนสำหรับทางด้านผู้ใช้ เพื่อความสะดวกจะเขียนเป็นฟังก์ชัน Scilab ดังนี้
function cmdstr=gencoeff(T)
s=poly(0,'s');
C=syslin('c',1500*(s+0.5)/(s*(s+1000)));
Css = tf2ss(C);
Cdss = cls2dls(Css,T);
Cd = ss2tf(Cdss);
a = coeff(Cd.den);
a = a(:,$:-1:1);
b = coeff(Cd.num);
b = b(:,$:-1:1);
cmdstr="cccoeff=3,"+msprintf("%d, %22.18f, %22.18f,",a)
+msprintf("%22.18f, %22.18f, %22.18f",b);
endfunction
โดยเมื่อโหลดเข้าในหน่วยความจำของ Scilab แล้วสามารถเรียกใช้ได้โดยพิมพ์ชื่อฟังก์ชันและใส่ค่าคาบเวลาเป็นอาร์กิวเมนต์ ตัวอย่างเช่น
--> gencoeff(0.01)
ans =
cccoeff=3,1, -0.333333333333333040, -0.666666666666666630,
1.253124999999999600, 0.006249999999995537, -1.246874999999998400
คัดลอกคำสั่งนี้ไปใส่ในช่องส่งคำสั่งของ Serial Monitor และคลิก [Send] จะเห็นว่าค่าสัมประสิทธิ์ถูกอัพเดทเป็นค่าใหม่ ตรวจสอบจากเอาต์พุตที่แสดงใน Serial Monitor ในรูปที่ 2 เพื่อความแน่ใจว่าไม่มีความผิดพลาดเกิดขึ้น
รูปที่ 2 ส่งคำสั่งที่กำเนิดจากฟังก์ชัน gencoeff() ให้กับตัวควบคุม
เมื่ออัพเดทสัมประสิทธิ์ที่ตรงกับคาบเวลาของตัวควบคุม สังเกตที่จอ OLED จะเห็นว่าค่าของเอาต์พุตหยุดแกว่งและเข้าสู่ค่าคำสั่ง แสดงการทำงานที่ถูกต้อง ทดลองเปรียบเทียบผลตอบสนองขั้นบันไดกับที่ได้จากการจำลองได้ดังรูปที่ 3
รูปที่ 3 ผลตอบสนองจากตัวควบคุมเทียบกับการจำลองหลังการปรับค่าสัมประสิทธิ์ใหม่
การอัพเดทสัมประสิทธิ์ผ่าน NETPIE
หากมั่นใจว่าระบบมีการป้องกันความผิดพลาดดีเพียงพอ ผู้ปฏิบัติงานสามารถเปลี่ยนค่าสัมประสิทธิ์ตัวควบคุมจากระยะไกลจากหน้า Freeboard ของ NETPIE ผ่านอินเทอร์เน็ตได้ อย่างเช่นกรณีตัวควบคุม PID เราได้สาธิตการใช้สไลเดอร์ปรับค่าของพารามิเตอร์ควบคุม อย่างไรก็ตามการปรับค่าสัมประสิทธิ์ตัวควบคุมเชิงเส้นโดยสไลเดอร์มีความไม่เหมาะสมในทางปฏิบัติ กล่าวคือ
ค่าสัมประสิทธิ์เป็นตัวเลขที่ต้องการความแม่นยำสูงถึงจุดทศนิยมหลายตำแหน่ง ขณะที่การกำเนิดค่าโดยสไลเดอร์ถูกจำกัดโดยขั้นของการเลื่อนซึ่งไม่สามารถตั้งความละเอียดมากๆ ได้
ค่าสัมประสิทธิ์ถูกคำนวณโดยคอมพิวเตอร์ตามคาบเวลาที่กำหนดอย่างถูกต้อง ไม่ต้องมีการปรับแต่งเพิ่มเติมโดยผู้ใช้อีก การใช้สไลเดอร์จะไม่สื่อความหมาย แต่เพิ่มโอกาสที่จะเกิดความผิดพลาดจากผู้ใช้งาน
เราต้องการส่งค่าสัมประสิทธิ์ทั้งชุดเพื่ออัพเดทในเวลาเดียวกัน
ดังนั้นการอัพเดทที่เหมาะสมคือส่งสตริงคำสั่งเช่นเดียวกับที่ใช้ใน Serial Monitor ซึ่งเราได้สร้างความสัมพันธ์ระหว่างชุดคำสั่งของตัวควบคุมกับ NETPIE ไว้แล้ว เพียงแต่ต้องการ widget ที่สามารถส่งสตริงใดๆ ที่ผู้ใช้ป้อนให้มายังอุปกรณ์เท่านั้นเอง ซึ่ง widget ดังกล่าวยังไม่มีให้ใช้โดยตรง แต่ซ่อนอยู่ในประเภทที่เรียกว่า html โดยต้องเขียนโค้ด JavaScript เพิ่มเติม
หมายเหตุ : ข้อมูลในส่วนนี้เก็บตกมาจาก
NETPIE Facebook Group โดยดัดแปลงโค้ดตัวอย่างของคุณ Sirawit Moonrinta
ล็อกอิน NETPIE เข้าไปที่ Freeboard เดิมที่ใช้อยู่ เราจะเพิ่ม widget ตัวใหม่โดยคลิกที่ + ADD PANE เลือก Type เป็นแบบ html ดังแสดงในรูปที่ 4
รูปที่ 4 สร้าง widget ใหม่ เลือกเป็นแบบ HTML
จะได้ widget ที่มีฟิลด์อินพุตดังในรุปที่ 5
รูปที่ 5 อินพุตฟิลด์ของ HTML Widget
สิ่งเดียวที่ต้องทำคือใส่โค้ด JavaScript ลงในช่อง HTML โดยเขียนโค้ดดังนี้
หากต้องการคัดลอกตัวอย่างโค้ดนี้
ไปที่ลิงก์นี้ใน NETPIE group จะอยู่ในคอมเม้นต์ที่เขียนโดยคุณ Sirawit Moonrinta
บรรทัดที่ผู้อ่านต้องเปลี่ยนให้ตรงกับชื่อ dataset และ alias ของท่านคือ
microgear[“xxxxxxx”].chat(“xxxxxx”,val.value);
สำหรับที่ผู้เขียนใช้อยู่คือ dataset ชื่อ myIoFCboard2 และ alias ชื่อ myIoFCdevice สำหรับฟิลด์ HEIGHT BLOCKS ตั้งค่า 2 ก็เพียงพอ คลิกที่เครื่องหมายประแจด้านบนเพื่อใส่ชื่อของ widget ผลคือ widget ที่มีช่องรับคำสั่งและปุ่ม [SEND] ดังแสดงในรูปที่ 6
รูปที่ 6 widget ใหม่สำหรับส่งคำสั่งให้กับตัวควบคุม
หากผู้อ่านสงสัยว่า สตริงที่ใส่ไปในช่องนี้จะไปสั่งงานตัวควบคุมได้อย่างไร ให้ย้อนไปหาพื้นฐานของ NETPIE คือข้อความใดๆ ที่ chat ไปยัง microgear จะรับโดยฟังก์ชัน onMsghandler() ซึ่งในโปรแกรมเราเขียนไว้ดังนี้
void onMsghandler(char *topic, uint8_t* msg, unsigned int msglen) {
char *m = (char *)msg;
m[msglen] = '\0';
rcvdstring = m;
Serial.println(rcvdstring);
cmdInt();
}
โดยจะเห็นว่าที่บรรทัดสุดท้าย ฟังก์ชัน cmdInt() ถูกเรียกให้ประมวลผลข้อความที่ส่งมาและเก็บไว้ใน rcvdstring ดังนั้นคำสั่งใดๆ ที่พิมพ์ใน widget นี้จะถูกเรียกทำงานเหมือนกับคำสั่งที่พิมพ์ใน Serial Monitor ทุกประการ หากไม่เชื่อลองพิมพ์คำสั่งตรวจสอบสถานะใน widget ที่สร้างใหม่ เช่น controller, netpie, netpiefeed จะเห็นการตอบสนองบน Serial Monitor ดังในรูปที่ 7
รูปที่ 7 การพิมพ์คำสั่งลงใน command box
ขณะนี้สัมประสิทธิ์ตัวควบคุมยังเป็นค่าที่ไม่ถูกต้อง ดังนั้นจากรูปที่ 7จะเห็นเอาต์พุตของพลานต์แตกต่างจากค่าคำสั่งค่อนข้างมาก และเอาต์พุตจะแกว่งอยู่ตลอดเวลาเนื่องจากระบบป้อนกลับไม่เสถียร ซึ่งเราจะแก้ไขโดยใส่สตริงคำสั่งสำหรับอัพเดทสัมประสิทธิ์ตัวควบคุมลงในช่องของ COMMAND BOX และคลิก [SEND] ผลที่ได้เป็นดังรูปที่ 8 (ขยายคอลัมน์ให้กว้างขึ้นเป็น 3 เพื่อให้เห็นคำสั่งที่คัดลอกใส่ลงไป) ค่าสัมประสิทธิ์ตัวควบคุมถูกอัพเดทผ่านอินเทอร์เน็ตอย่างถูกต้อง เอาต์พุตของพลานต์จะหยุดแกว่งและเข้าสู่ค่าที่ต้องการ คือ 1
รูปที่ 8 ใช้คำสั่ง cccoeff เพื่ออัพเดทสัมประสิทธิ์ตัวควบคุมผ่านอินเทอร์เน็ต
สรุป
เนื้อหาในบทความนี้กล่าวถึงการอัพเดทสัมประสิทธิ์ตัวควบคุมผ่านคำสั่ง เพื่อให้ถูกต้องตามค่าคาบเวลาที่ผู้ใช้เลือก ค่าสัมประสิทธิ์ต้องถูกเปลี่ยนทุกครั้งที่มีการปรับค่าคาบเวลาเพื่อให้ตัวควบคุมทำงานได้อย่างถูกต้อง การส่งสตริงคำสั่งสามารถกระทำผ่าน Serial Monitor กรณีอยู่หน้างาน หรืออัพเดทผ่านอินเทอร์เน็ตโดยอาศัย widget ประเภท HTML ที่มีให้ใช้บน NETPIE Freeboard
ในการนำไปใช้งานจริง สิ่งที่ต้องปรับปรุงคือการตรวจสอบหากผู้ใช้ใส่ข้อมูลผิดพลาดและยกเลิกการเปลี่ยนค่าสัมประสิทธิ์ในกรณีที่ไม่เป็นค่าตัวเลข หรือมีจำนวนสัมประสิทธิ์ไม่ถูกต้อง การกำเนิดคำสั่งโดย Scilab ช่วยลดโอกาสที่จะเกิดความผิดพลาดได้ในระดับหนึ่ง
ลงทะเบียนฝึกอบรม IoT สำหรับงานควบคุมอุตสาหกรรมโดย ESP32 และ NETPIE (2561/2 : อาทิตย์ 22 เมษายน 2561)
รวมไฟล์โปรแกรมที่ใช้
htmlwidget.zip
คำแนะนำเพิ่มเติม
- 3 ธันวาคม 2562 : คุณ Sayori Mori ได้แนะนำในกรุพ NETPIE "โค้ดที่แนะนำมานั้น ผิด นะคะ
< form onclick="function()">
การใส่อีเว้น onclick ไว้ที่ form ทำให้โค้ดทำงานทุกครั้งที่คลิกที่ตัวformนั้นก็คือหน้าทั้งหมด คลิกโดนตรงไหนก็ทำงานหมด เอาปุ่ม button ออกมันก็ยังทำงานเองได้
ที่ถูกต้องควรใส่ onclick ไว้ที่ตัวปุ่มเองค่ะดังนี้
< form> < input type="button" onclick="function()"> </form >
แบบนี้โค้ดจะทำงานเฉพาะเมื่อเราคลิกที่ปุ่มเป้าหมายเท่านั้นค่ะ"
No comments:
Post a Comment