- Semaphore คืออะไร?
- จะใช้ Semaphore ใน FreeRTOS ได้อย่างไร?
- คำอธิบายรหัสสัญญาณ
- แผนภูมิวงจรรวม
- Mutex คืออะไร?
- จะใช้ Mutex ใน FreeRTOS ได้อย่างไร?
- คำอธิบายรหัส Mutex
ในบทช่วยสอนก่อนหน้านี้เราได้กล่าวถึงพื้นฐานของ FreeRTOS กับ Arduino และวัตถุเคอร์เนลคิวใน FreeRTOS Arduino ตอนนี้ในบทช่วยสอน FreeRTOS ครั้งที่สามนี้เราจะเรียนรู้เพิ่มเติมเกี่ยวกับ FreeRTOS และ API ขั้นสูงซึ่งจะทำให้คุณเข้าใจแพลตฟอร์มมัลติทาสก์ได้อย่างลึกซึ้งยิ่งขึ้น
Semaphore และ Mutex (การยกเว้นร่วมกัน) เป็นวัตถุเคอร์เนลที่ใช้สำหรับการซิงโครไนซ์การจัดการทรัพยากรและการปกป้องทรัพยากรจากการทุจริต ในช่วงครึ่งแรกของบทช่วยสอนนี้เราจะเห็นแนวคิดเบื้องหลังSemaphoreว่าจะใช้อย่างไรและที่ไหน ในช่วงครึ่งหลังเราจะยังคงมีMutex
Semaphore คืออะไร?
ในแบบฝึกหัดก่อนหน้านี้เราได้พูดคุยเกี่ยวกับลำดับความสำคัญของงานและยังได้รับทราบว่างานที่มีลำดับความสำคัญสูงกว่าจะจองงานที่มีลำดับความสำคัญต่ำกว่าดังนั้นในขณะที่ดำเนินการงานที่มีลำดับความสำคัญสูงอาจมีความเป็นไปได้ที่ข้อมูลจะเสียหายในงานที่มีลำดับความสำคัญต่ำกว่าเนื่องจาก ยังไม่ได้ดำเนินการและข้อมูลจะมาอย่างต่อเนื่องในงานนี้จากเซ็นเซอร์ซึ่งทำให้ข้อมูลสูญหายและการทำงานผิดพลาดของแอปพลิเคชันทั้งหมด
ดังนั้นจึงมีความจำเป็นที่จะต้องปกป้องทรัพยากรจากการสูญหายของข้อมูลและที่นี่ Semaphore มีบทบาทสำคัญ
Semaphore เป็นกลไกการส่งสัญญาณที่งานในสถานะรอจะถูกส่งสัญญาณโดยงานอื่นเพื่อดำเนินการ กล่าวอีกนัยหนึ่งคือเมื่อ task1 เสร็จสิ้นการทำงานมันจะแสดงแฟล็กหรือเพิ่มแฟล็กทีละ 1 แล้วแฟล็กนี้ได้รับจากภารกิจอื่น (task2) ซึ่งแสดงว่าสามารถทำงานได้ในขณะนี้ เมื่อ task2 ทำงานเสร็จแล้วค่าสถานะจะลดลง 1
โดยพื้นฐานแล้วมันคือกลไก“ Give” และ“ Take” และ semaphore คือตัวแปรจำนวนเต็มที่ใช้เพื่อซิงโครไนซ์การเข้าถึงทรัพยากร
ประเภทของสัญญาณใน FreeRTOS:
สัญญาณมีสองประเภท
- เซมาฟอร์ไบนารี
- การนับสัญญาณ
1. Binary Semaphore:มีค่าจำนวนเต็ม 2 ค่า 0 และ 1 ซึ่งค่อนข้างคล้ายกับ Queue of length 1 ตัวอย่างเช่นเรามีสองงาน task1 และ task2 Task1 ส่งข้อมูลไปยัง task2 เพื่อให้ task2 ตรวจสอบรายการคิวอย่างต่อเนื่องหากมี 1 จากนั้นจึงสามารถอ่านข้อมูลอื่นได้ต้องรอจนกว่าจะกลายเป็น 1 หลังจากรับข้อมูลแล้ว task2 จะลดคิวและทำให้เป็น 0 นั่นหมายถึง task1 อีกครั้ง สามารถส่งข้อมูลไปยังงาน 2
จากตัวอย่างข้างต้นอาจกล่าวได้ว่า binary semaphore ใช้สำหรับการซิงโครไนซ์ระหว่างงานหรือระหว่างงานและขัดจังหวะ
2. การนับเซมาฟอร์:มีค่ามากกว่า 0 และสามารถคิดจากคิวที่มีความยาวมากกว่า 1 เซมาฟอร์นี้ใช้สำหรับการนับเหตุการณ์ ในสถานการณ์การใช้งานนี้ตัวจัดการเหตุการณ์จะ 'ให้' เซมาฟอร์ทุกครั้งที่มีเหตุการณ์เกิดขึ้น (เพิ่มค่าจำนวนเซมาฟอร์) และงานตัวจัดการจะ 'รับ' เซมาฟอร์ทุกครั้งที่ประมวลผลเหตุการณ์ (ลดค่าจำนวนเซมาฟอร์).
ดังนั้นค่าการนับจึงเป็นผลต่างระหว่างจำนวนเหตุการณ์ที่เกิดขึ้นและจำนวนที่ได้รับการประมวลผล
ตอนนี้เรามาดูวิธีใช้ Semaphore ในรหัส FreeRTOS ของเรา
จะใช้ Semaphore ใน FreeRTOS ได้อย่างไร?
FreeRTOS รองรับ API ที่แตกต่างกันสำหรับการสร้างเซมาฟอร์รับสัญญาณและให้สัญญาณ
ตอนนี้สามารถมี API สองประเภทสำหรับวัตถุเคอร์เนลเดียวกัน หากเราต้องให้สัญญาณจาก ISR จะไม่สามารถใช้ semaphore API ปกติได้ คุณควรใช้ API ที่ป้องกันการขัดจังหวะ
ในบทช่วยสอนนี้เราจะใช้ binary semaphoreเนื่องจากง่ายต่อการเข้าใจและนำไปใช้ เนื่องจากมีการใช้ฟังก์ชันการขัดจังหวะที่นี่คุณจึงต้องใช้ API ที่ป้องกันการขัดจังหวะในฟังก์ชัน ISR เมื่อเราพูดว่าการซิงโครไนซ์งานกับอินเทอร์รัปต์หมายถึงการทำให้งานอยู่ในสถานะกำลังรันทันทีหลังจาก ISR
การสร้าง Semaphore:
ในการใช้เคอร์เนลออบเจ็กต์ใด ๆ เราต้องสร้างมันก่อน สำหรับการสร้าง binary semaphore ให้ใช้vSemaphoreCreateBinary ()
API นี้ไม่ใช้พารามิเตอร์ใด ๆ และส่งกลับตัวแปรประเภท SemaphoreHandle_t ชื่อตัวแปรส่วนกลางsema_vถูกสร้างขึ้นเพื่อจัดเก็บเซมาฟอร์
SemaphoreHandle_t sema_v; sema_v = xSemaphoreCreateBinary ();
ให้สัญญาณ:
สำหรับการให้สัญญาณมีสองเวอร์ชัน - เวอร์ชันหนึ่งสำหรับการขัดจังหวะและอีกเวอร์ชันหนึ่งสำหรับงานปกติ
- xSemaphoreGive (): API นี้ใช้อาร์กิวเมนต์เดียวเท่านั้นซึ่งเป็นชื่อตัวแปรของสัญญาณเช่น sema_v ตามที่ระบุไว้ข้างต้นในขณะที่สร้างเซมาฟอร์ สามารถเรียกได้จากงานปกติที่คุณต้องการซิงโครไนซ์
- xSemaphoreGiveFromISR ():นี่คือเวอร์ชัน API ที่ป้องกันการขัดจังหวะของ xSemaphoreGive () เมื่อเราต้องการซิงโครไนซ์ ISR กับงานปกติควรใช้ xSemaphoreGiveFromISR () จากฟังก์ชัน ISR
รับสัญญาณ:
ที่จะใช้สัญญาณการใช้งานฟังก์ชั่น API xSemaphoreTake () API นี้ใช้สองพารามิเตอร์
xSemaphoreTake (SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait);
xSemaphore:ชื่อของสัญญาณที่จะใช้ในกรณีของเรา sema_v
xTicksToWait:นี่คือระยะเวลาสูงสุดที่งานจะรอในสถานะถูกปิดกั้นเพื่อให้สัญญาณพร้อมใช้งาน ในโปรเจ็กต์ของเราเราจะตั้งค่า xTicksToWait เป็น portMAX_DELAY เพื่อให้ task_1 รออย่างไม่มีกำหนดในสถานะถูกบล็อคจนกว่า sema_v จะพร้อมใช้งาน
ตอนนี้เรามาใช้ API เหล่านี้และเขียนโค้ดเพื่อทำงานบางอย่าง
ที่นี่ปุ่มกดหนึ่งปุ่มและไฟ LED สองดวงเชื่อมต่อกัน ปุ่มกดจะทำหน้าที่เป็นปุ่มขัดจังหวะซึ่งต่ออยู่กับพิน 2 ของ Arduino Uno เมื่อกดปุ่มนี้การขัดจังหวะจะถูกสร้างขึ้นและ LED ที่เชื่อมต่อกับพิน 8 จะเปิดขึ้นและเมื่อคุณกดอีกครั้งจะดับ
ดังนั้นเมื่อกดปุ่ม xSemaphoreGiveFromISR () จะถูกเรียกจากฟังก์ชัน ISR และฟังก์ชัน xSemaphoreTake () จะถูกเรียกจากฟังก์ชัน TaskLED
ในการทำให้ระบบมีลักษณะมัลติทาสก์ให้เชื่อมต่อ LED อื่น ๆ ด้วยพิน 7 ซึ่งจะอยู่ในสถานะกะพริบ
คำอธิบายรหัสสัญญาณ
เริ่มเขียนโค้ดโดยเปิด Arduino IDE
1. ขั้นแรกรวมไฟล์ส่วนหัว Arduino_FreeRTOS.h ตอนนี้ถ้าเคอร์เนลอ็อบเจ็กต์ใด ๆ ถูกใช้เช่นคิวเซมาฟอร์ไฟล์ส่วนหัวจะต้องรวมอยู่ด้วย
# รวม # รวม
2. ประกาศตัวแปรประเภทSemaphoreHandle_tเพื่อเก็บค่าของเซมาฟอร์
SemaphoreHandle_t ขัดจังหวะ Semaphore;
3. ในการตั้งค่าเป็นโมฆะ () ให้สร้างสองงาน (TaskLED และ TaskBlink) โดยใช้ xTaskCreate () API จากนั้นสร้างเซมาฟอร์โดยใช้ xSemaphoreCreateBinary () สร้างงานที่มีลำดับความสำคัญเท่ากันและหลังจากนั้นให้ลองเล่นกับหมายเลขนี้ กำหนดค่าพิน 2 เป็นอินพุตและเปิดใช้งานตัวต้านทานแบบดึงขึ้นภายในและแนบพินขัดจังหวะ สุดท้ายเริ่มตัวกำหนดตารางเวลาตามที่แสดงด้านล่าง
การตั้งค่าเป็นโมฆะ () { pinMode (2, INPUT_PULLUP); xTaskCreate (TaskLed, "Led", 128, NULL, 0, NULL); xTaskCreate (TaskBlink, "LedBlink", 128, NULL, 0, NULL); interruptSemaphore = xSemaphoreCreateBinary (); ถ้า (interruptSemaphore! = NULL) { attachInterrupt (digitalPinToInterrupt (2), debounceInterrupt, LOW); } }
4. ตอนนี้ใช้ฟังก์ชัน ISR ทำให้ฟังก์ชั่นและชื่อเดียวกันเป็นอาร์กิวเมนต์ที่สองของ attachInterrupt () ฟังก์ชั่น เพื่อให้การขัดจังหวะทำงานได้อย่างถูกต้องคุณต้องลบปัญหา debounce ของปุ่มกดโดยใช้ฟังก์ชัน millis หรือ micros และโดยการปรับเวลา debouncing จากฟังก์ชันนี้เรียกใช้ฟังก์ชัน interruptHandler () ดังที่แสดงด้านล่าง
debouncing_time ยาว = 150; ระเหยไม่ได้ลงนาม long last_micros; เป็นโมฆะ debounceInterrupt () { if ((long) (micros () - last_micros)> = debouncing_time * 1000) { interruptHandler (); last_micros = ไมโคร (); } }
ในฟังก์ชัน interruptHandler () ให้เรียกใช้ xSemaphoreGiveFromISR () API
เป็นโมฆะ interruptHandler () { xSemaphoreGiveFromISR (interruptSemaphore, NULL); }
ฟังก์ชั่นนี้จะส่งสัญญาณให้กับ TaskLed เพื่อเปิด LED
5. สร้าง TaskLed ฟังก์ชั่นและภายใน ขณะที่ ห่วงโทรxSemaphoreTake () API และตรวจสอบว่าสัญญาณจะมาประสบความสำเร็จหรือไม่ หากมีค่าเท่ากับ pdPASS (เช่น 1) ให้ทำการสลับ LED ตามที่แสดงด้านล่าง
โมฆะ TaskLed (โมฆะ * pvParameters) { (โมฆะ) pvParameters; PinMode (8, เอาท์พุท); ในขณะที่ (1) { if (xSemaphoreTake (interruptSemaphore, portMAX_DELAY) == pdPASS) { digitalWrite (8,! digitalRead (8)); } } }
6. สร้างฟังก์ชั่นเพื่อกะพริบ LED อื่น ๆ ที่เชื่อมต่อกับพิน 7
เป็นโมฆะ TaskLed1 (void * pvParameters) { (void) pvParameters; PinMode (7, เอาท์พุท); ในขณะที่ (1) { digitalWrite (7, HIGH); vTaskDelay (200 / portTICK_PERIOD_MS); digitalWrite (7, ต่ำ); vTaskDelay (200 / portTICK_PERIOD_MS); } }
7. ฟังก์ชัน void loop จะยังคงว่างเปล่า อย่าลืมมัน
ห่วงเป็นโมฆะ () {}
เพียงเท่านี้รหัสทั้งหมดสามารถพบได้ในตอนท้ายของบทช่วยสอนนี้ ตอนนี้อัปโหลดรหัสนี้และเชื่อมต่อ LED และปุ่มกดกับ Arduino UNO ตามแผนภาพวงจร
แผนภูมิวงจรรวม
หลังจากอัปโหลดโค้ดคุณจะเห็นไฟ LED กะพริบหลังจากผ่านไป 200 มิลลิวินาทีและเมื่อกดปุ่มแล้วไฟ LED ดวงที่สองจะติดสว่างทันทีดังที่แสดงในวิดีโอในตอนท้าย
ด้วยวิธีนี้สามารถใช้ semaphores ใน FreeRTOS กับ Arduinoซึ่งจำเป็นต้องส่งผ่านข้อมูลจากงานหนึ่งไปยังอีกงานหนึ่งโดยไม่สูญเสียใด ๆ
ตอนนี้เรามาดูกันว่า Mutex คืออะไรและจะใช้ FreeRTOS ได้อย่างไร
Mutex คืออะไร?
ดังที่อธิบายไว้ข้างต้นเซมาฟอร์เป็นกลไกการส่งสัญญาณในทำนองเดียวกัน Mutex เป็นกลไกการล็อกซึ่งแตกต่างจากเซมาฟอร์ที่มีฟังก์ชันแยกกันสำหรับการเพิ่มและลด แต่ใน Mutex ฟังก์ชันจะรับและให้ในตัวเอง เป็นเทคนิคในการหลีกเลี่ยงความเสียหายของทรัพยากรที่ใช้ร่วมกัน
เพื่อป้องกันทรัพยากรที่ใช้ร่วมกันหนึ่งกำหนดบัตรโทเค็น (mutex) ให้กับทรัพยากร ใครก็ตามที่มีการ์ดนี้สามารถเข้าถึงทรัพยากรอื่นได้ คนอื่นควรรอจนกว่าบัตรจะคืน ด้วยวิธีนี้ทรัพยากรเพียงหนึ่งเดียวที่สามารถเข้าถึงงานได้และคนอื่น ๆ รอคอยโอกาสของพวกเขา
มาทำความเข้าใจกับMutex ใน FreeRTOSด้วยตัวอย่าง
ที่นี่เรามีงานสามอย่างงานหนึ่งสำหรับการพิมพ์ข้อมูลบน LCD งานที่สองสำหรับการส่งข้อมูล LDR ไปยังงาน LCD และงานสุดท้ายสำหรับการส่งข้อมูลอุณหภูมิบน LCD ดังนั้นที่นี่สองงานคือการแบ่งปันทรัพยากรเดียวกันคือ LCD หากงาน LDR และงานอุณหภูมิส่งข้อมูลพร้อมกันข้อมูลใดข้อมูลหนึ่งอาจเสียหายหรือสูญหาย
ดังนั้นเพื่อป้องกันข้อมูลสูญหายเราจำเป็นต้องล็อคทรัพยากร LCD สำหรับ task1 จนกว่าจะเสร็จสิ้นการแสดงผล จากนั้นงาน LCD จะปลดล็อกจากนั้น task2 จึงสามารถทำงานได้
คุณสามารถสังเกตการทำงานของ Mutex และ semaphores ได้ในแผนภาพด้านล่าง
จะใช้ Mutex ใน FreeRTOS ได้อย่างไร?
นอกจากนี้ยังใช้ Mutexs ในลักษณะเดียวกับ semaphores ขั้นแรกให้สร้างจากนั้นให้และรับโดยใช้ API ที่เกี่ยวข้อง
การสร้าง Mutex:
เพื่อสร้าง Mutex ใช้ xSemaphoreCreateMutex () API ตามชื่อของมันบ่งบอกว่า Mutex เป็นเซมาฟอร์ไบนารีประเภทหนึ่ง ใช้ในบริบทและวัตถุประสงค์ที่แตกต่างกัน เซมาฟอร์ไบนารีใช้สำหรับการซิงโครไนซ์งานในขณะที่ Mutex ใช้สำหรับปกป้องทรัพยากรที่แชร์
API นี้จะใช้เวลาไม่โต้แย้งใด ๆ และผลตอบแทนของตัวแปรประเภทSemaphoreHandle_t ถ้าไม่สามารถสร้าง mutex ได้ xSemaphoreCreateMutex () จะ คืนค่า NULL
SemaphoreHandle_t mutex_v; mutex_v = xSemaphoreCreateMutex ();
การ Mutex:
เมื่องานต้องการเข้าถึงทรัพยากรจะใช้ Mutex โดยใช้ xSemaphoreTake () API มันเหมือนกับเซมาฟอร์ไบนารี นอกจากนี้ยังใช้สองพารามิเตอร์
xSemaphore:ชื่อของ Mutex จะต้องดำเนินการในกรณีของเราmutex_v
xTicksToWait:นี่คือระยะเวลาสูงสุดที่งานจะรอในสถานะถูกปิดกั้นเพื่อให้ Mutex พร้อมใช้งาน ในโปรเจ็กต์ของเราเราจะตั้งค่า xTicksToWait เป็น portMAX_DELAY เพื่อให้ task_1 รออย่างไม่มีกำหนดในสถานะถูกปิดกั้นจนกว่า mutex_v จะพร้อมใช้งาน
การให้ Mutex:
หลังจากเข้าถึงทรัพยากรที่ใช้ร่วมกันงานควรส่งคืน Mutex เพื่อให้งานอื่นสามารถเข้าถึงได้ xSemaphoreGive () API ใช้เพื่อคืนค่า Mutex
ฟังก์ชัน xSemaphoreGive () รับอาร์กิวเมนต์เดียวเท่านั้นซึ่งเป็น Mutex ที่จะให้ในกรณีของเรา mutex_v
โดยใช้ API ดังกล่าวข้างต้นขอใช้ Mutex ในรหัส FreeRTOS ใช้ Arduino IDE
คำอธิบายรหัส Mutex
เป้าหมายของส่วนนี้คือการใช้ Serial monitor เป็นทรัพยากรที่ใช้ร่วมกันและงานสองงานที่แตกต่างกันเพื่อเข้าถึงจอภาพอนุกรมเพื่อพิมพ์ข้อความ
1. ไฟล์ส่วนหัวจะยังคงเหมือนเซมาฟอร์
# รวม # รวม
2. ประกาศตัวแปรประเภท SemaphoreHandle_t เพื่อเก็บค่าของ Mutex
SemaphoreHandle_t mutex_v;
3. ใน การตั้งค่าเป็นโมฆะ () เริ่มต้นมอนิเตอร์แบบอนุกรมด้วยอัตรารับส่งข้อมูล 9600 และสร้างงานสองงาน (Task1 และ Task2) โดยใช้ xTaskCreate () API จากนั้นสร้าง Mutex โดยใช้ xSemaphoreCreateMutex () สร้างงานที่มีลำดับความสำคัญเท่ากันแล้วลองเล่นกับหมายเลขนี้ในภายหลัง
การตั้งค่าเป็นโมฆะ () { Serial.begin (9600); mutex_v = xSemaphoreCreateMutex (); ถ้า (mutex_v == NULL) { Serial.println ("ไม่สามารถสร้าง Mutex ได้"); } xTaskCreate (Task1, "งาน 1", 128, NULL, 1, NULL); xTaskCreate (Task2, "งาน 2", 128, NULL, 1, NULL); }
4. ตอนนี้สร้างฟังก์ชันงานสำหรับ Task1 และ Task2 ใน ขณะที่ทำงาน วนซ้ำของฟังก์ชันก่อนที่จะพิมพ์ข้อความบนมอนิเตอร์แบบอนุกรมเราต้องใช้ Mutex โดยใช้ xSemaphoreTake () จากนั้นพิมพ์ข้อความจากนั้นส่งคืน Mutex โดยใช้ xSemaphoreGive () จากนั้นให้ล่าช้า
โมฆะ Task1 (โมฆะ * pvParameters) { while (1) { xSemaphoreTake (mutex_v, portMAX_DELAY); Serial.println ("สวัสดีจาก Task1"); xSemaphoreGive (mutex_v); vTaskDelay (pdMS_TO_TICKS (1000)); } }
ในทำนองเดียวกันใช้ฟังก์ชัน Task2 ด้วยความล่าช้า 500 มิลลิวินาที
5. Void loop () จะยังคงว่างเปล่า
ตอนนี้อัปโหลดรหัสนี้บน Arduino UNO และเปิดจอภาพอนุกรม
คุณจะเห็นข้อความกำลังพิมพ์จาก task1 และ task2
หากต้องการทดสอบการทำงานของ Mutex เพียงแสดงความคิดเห็น xSemaphoreGive (mutex_v); จากงานใด ๆ คุณจะเห็นว่าแฮงค์โปรแกรมพิมพ์ข้อความล่าสุด
นี่คือวิธีการใช้งาน Semaphore และ Mutex ใน FreeRTOS ด้วย Arduino สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ Semaphore และ Mutex คุณสามารถเยี่ยมชมเอกสารอย่างเป็นทางการของ FreeRTOS
รหัสและวิดีโอที่สมบูรณ์สำหรับ Semaphore และ Mutes มีให้ด้านล่าง