เจาะลึกการจำลอง UART ด้วย STM32 ตอนที่ 1: พื้นฐาน UART และการตั้งค่า Software
STM32 กับการสร้าง Software UART
ในบทความชุดนี้ เราจะพาคุณเรียนรู้การจำลองการทำงานของฮาร์ดแวร์ UART ด้วยการประยุกต์ใช้ Timer และ DMA ร่วมกันอย่างมีประสิทธิภาพ เพื่อสร้างระบบสื่อสารแบบ Serial ที่ทำงานได้โดยอัตโนมัติ โดยไม่ต้องอาศัยการประมวลผลจาก CPU ระหว่างการรับส่งข้อมูล ก่อนที่ข้อมูลไบต์แรกจะถูกส่งออกไป เราจะเริ่มต้นด้วยการกำหนดค่าภายใน STM32CubeMX เพื่อสร้าง Timing) และลอจิกการทำงานอัตโนมัติที่จำเป็น ซึ่งเป็นรากฐานสำคัญของระบบสื่อสารที่ทำงานอยู่เบื้องหลังอย่างแม่นยำและมีประสิทธิภาพ
ในบทความนี้ เราจะครอบคลุมหัวข้อต่อไปนี้
- บทนำ
- วิธีคำนวณค่า Baud Rate
- การตั้งค่า STM32CubeMX
- การนำโปรเจกต์เข้าสู่ STM32CubeIDE
1. บทนำ
ทำไมต้องจำลอง UART ด้วยซอฟต์แวร์ (Software UART)?
เมื่อโปรเจกต์มีขนาดใหญ่ขึ้น คุณมักจะพบว่าพอร์ต UART ที่มีอยู่บนไมโครคอนโทรลเลอร์เริ่มไม่เพียงพอต่อการใช้งาน อาจต้องการพอร์ตเพิ่มเติมสำหรับส่งข้อความ Debug เชื่อมต่อกับเซนเซอร์เฉพาะทาง หรือเพิ่มช่องทางการสื่อสาร Serial บนขาที่ไม่มีวงจร UART ในตัว
Software UART คือทางออกของปัญหานี้ เพราะช่วยให้คุณสามารถเปลี่ยน GPIO ทั่วไปให้กลายเป็นพอร์ต Serial ที่ใช้งานได้จริง เพิ่มความยืดหยุ่นในการออกแบบระบบ และขยายความสามารถด้านการสื่อสารของไมโครคอนโทรลเลอร์ให้ก้าวข้ามข้อจำกัดของฮาร์ดแวร์ที่มีอยู่
Software UART ทำงานอย่างไร?
ในการสื่อสารแบบ UART ข้อมูลจะถูกส่งในรูปแบบของ เฟรมข้อมูล (Standard UART Frame) ซึ่งประกอบด้วยลำดับของสัญญาณลอจิกระดับ High และ Low ที่ส่งตามช่วงเวลาที่กำหนด (Timing) อย่างเคร่งครัด โดยภายในหนึ่งเฟรมจะประกอบด้วยองค์ประกอบสำคัญดังต่อไปนี้

- Start Bit
- Data 8 Bit
- Stop Bit
การพัฒนา Software UART แบบดั้งเดิมมักให้ CPU ควบคุมการเปลี่ยนสถานะของ GPIO ด้วยตนเอง พร้อมใช้ลูปหน่วงเวลา (Blocking Delay) เพื่อรักษาจังหวะของแต่ละบิต ซึ่งสิ้นเปลืองทรัพยากรของ CPU อย่างมาก
แนวทางที่เราจะใช้ในบทความนี้มีประสิทธิภาพกว่ามาก โดยอาศัย Hardware Timer ที่ทำงานในโหมด Output Compare Toggle แทน
หลักการคือ เราจะคำนวณล่วงหน้าว่าในช่วงเวลาใดสัญญาณจะต้องเปลี่ยนสถานะ จากนั้นส่งข้อมูลเวลา (Timestamp) เหล่านี้ไปยัง Timer ผ่าน DMA เมื่อ Timer ทำงาน ฮาร์ดแวร์จะสร้างสัญญาณ UART ที่ถูกต้องโดยอัตโนมัติ ทำให้ CPU ไม่ต้องเข้ามายุ่งเกี่ยวกับกระบวนการส่งข้อมูลเลย
ข้อดีและข้อจำกัด
ข้อดี
- CPU ทำงานได้อย่างเต็มประสิทธิภาพ เนื่องจาก DMA จะเป็นผู้รับภาระทั้งหมดในการส่งข้อมูล ทำให้ CPU สามารถประมวลผลงานอื่นที่สำคัญได้อย่างต่อเนื่อง ในขณะที่ข้อมูล Serial ถูกส่งออกไปเบื้องหลังโดยไม่สะดุด
- ใช้งานได้กับ GPIO ทุกขา
ข้อเสีย
- มีการใช้ module ภายใน MCU เพิ่มเติม เช่น Timer อย่างน้อยหนึ่งตัว และ DMA Stream อีกหนึ่งช่องซึ่งอาจเป็นทรัพยากรที่มีจำกัดในโปรเจกต์ที่ซับซ้อน
- อาจเกิด Timing Jitter ที่ Baud Rate สูง เพราะแม้ DMA จะมีความแม่นยำสูง แต่ก็ยังอาจได้รับผลกระทบจากการแย่งใช้ Bus ของระบบ หากมีการรับส่งข้อมูลผ่าน DMA อื่นที่มีลำดับความสำคัญสูงกว่า โดยเฉพาะเมื่อใช้ Baud Rate สูง เช่น 115200 หรือ 230400 bps ขึ้นไป อาจเริ่มเกิดความคลาดเคลื่อนของจังหวะสัญญาณ (Timing Jitter) ได้ ในขณะที่ Hardware UART โดยเฉพาะจะไม่ประสบปัญหานี้
- การรับข้อมูล (RX) มีความซับซ้อนกว่า
2. วิธีคำนวณค่า Baud Rate
ในหัวข้อนี้ เราจะอธิบายหลักการคำนวณค่าที่ใช้สำหรับตั้งค่า Timer เพื่อให้สามารถสร้างสัญญาณ UART ได้ตรงตาม Baud Rate ที่ต้องการ โดยจะอธิบายพื้นฐานทางคณิตศาสตร์ที่อยู่เบื้องหลังการตั้งค่า Timer ซึ่งสามารถวางไว้ต่อจากขั้นตอนการตั้งค่าใน STM32CubeMX หรือก่อนเข้าสู่ตัวอย่างโค้ดก็ได้
ความเข้าใจหลักการคำนวณค่า Timer เพื่อสร้างสัญญาณ UART ที่แม่นยำ
การกำหนดค่าพารามิเตอร์ของ Timer สำหรับจำลองการสื่อสารผ่านพอร์ต UART (Software UART) ให้ทำงานได้อย่างถูกต้องและมีเสถียรภาพ จำเป็นต้องอาศัย Timer ที่สามารถสร้างช่วงเวลาของแต่ละบิต (Bit Time) ได้อย่างแม่นยำ เนื่องจากการรับส่งข้อมูลแบบ UART อาศัยการกำหนดเวลาที่ตรงกันระหว่างอุปกรณ์ทั้งสองฝั่ง
หาก Timer สร้างช่วงเวลาคลาดเคลื่อนแม้เพียงเล็กน้อย ตำแหน่งการสุ่มอ่านข้อมูล (Sampling Point) ของฝั่งรับอาจเลื่อนออกจากกึ่งกลางของบิต ส่งผลให้ตีความค่าลอจิก 0 และ 1 ผิดพลาด และทำให้ข้อมูลที่ได้รับเกิดความเสียหายหรือไม่ถูกต้อง
ในหัวข้อนี้ เราจะอธิบายหลักการคำนวณค่าพารามิเตอร์ของ Timer เพื่อสร้าง Baud Rate ที่ 115,200 bps บนไมโครคอนโทรลเลอร์ STM32F411RE ซึ่งทำงานที่ความถี่สัญญาณนาฬิกา 100 MHz พร้อมอธิบายที่มาของค่าต่าง ๆ ที่ใช้ในการตั้งค่า เพื่อให้สามารถนำหลักการเดียวกันไปประยุกต์ใช้กับ Baud Rate หรือความถี่ระบบอื่นได้
1. การกำหนดความถี่ของ Timer Clock
สำหรับ STM32F411RE เราได้ตั้งค่าความถี่ระบบหลัก (SYSCLK) ไว้ที่ 100 MHz
เนื่องจากตัวหารความถี่ของบัส APB1 Prescaler ถูกกำหนดค่าเป็น 2 ทำให้ความถี่ของ APB1 Peripheral Clock ลดลงเหลือ 50 MHz อย่างไรก็ตาม ตามการออกแบบของฮาร์ดแวร์ STM32 หากค่า APB Prescaler ไม่ได้ตั้งไว้ที่ 1 ระบบจะทำการเพิ่มความถี่ของ Timer Clock เป็น 2 เท่าโดยอัตโนมัติ
ดังนั้น ความถี่ Clock ที่จ่ายให้กับ TIM3 จึงมีค่าเท่ากับ
100,000,000 Hz หรือ 100 MHz
ซึ่งเป็นความถี่ที่เราจะนำไปใช้ในการคำนวณค่าตั้งต้นของ Timer เพื่อสร้างสัญญาณ UART ต่อไป
2. การคำนวณจำนวน Tick ต่อหนึ่งบิต (Ticks Per Bit)
ค่า Baud Rate ที่ 115200 หมายความว่า มีการส่งข้อมูลจำนวน 115,200 บิตในทุก ๆ 1 วินาที ดังนั้น ระยะเวลาที่ใช้ในการส่งข้อมูลหนึ่งบิต คือ:
ขั้นตอนต่อไปคือการหาจำนวนรอบการทำงานของ Timer (Timer Tick) ที่สามารถเกิดขึ้นได้ภายในช่วงเวลา 8.68 µs โดยใช้สูตรการคำนวณดังต่อไปนี้:
การตั้งค่า Baud Rate
เนื่องจากรีจิสเตอร์ของ Timer สามารถเก็บค่าได้เฉพาะจำนวนเต็มเท่านั้น เราจึงจำเป็นต้องทำการปัดเศษค่า โดยอ้างอิงแนวทางจากเอกสาร AN4457 ของ ST ซึ่งแนะนำให้ใช้วิธีการปัดเศษทางคณิตศาสตร์ (Mathematical Rounding) ด้วยการเพิ่มค่าครึ่งหนึ่งของตัวหารก่อนทำการหารแบบจำนวนเต็ม เพื่อช่วยลดความคลาดเคลื่อนสะสม (Drift) ตลอดเฟรมข้อมูล UART ขนาด 10 บิต
3. ตรวจสอบค่าความคลาดเคลื่อนของ Baud Rate
เนื่องจากเราได้ทำการปัดค่า 868.055 ลงมาเป็น 868 ทำให้ค่า Baud Rate ที่ได้จริงจะเร็วกว่า 115,200 เล็กน้อย
ดังนั้น เราจึงต้องทำการคำนวณหาค่าความคลาดเคลื่อน (Error) ที่เกิดขึ้น:
1. คำนวณ Actual Baud Rate
1. คำนวณ Baud Rate Error
โดยทั่วไป การสื่อสารผ่าน UART สามารถยอมรับความคลาดเคลื่อนของ Baud Rate ได้สูงสุดประมาณ 2–3% ก่อนที่ข้อมูลบางบิตจะถูกตีความผิดพลาด สำหรับระบบของเรา ค่าความคลาดเคลื่อนอยู่ที่เพียง 0.006% เท่านั้น จึงถือว่าอยู่ในระดับที่แม่นยำอย่างมากและแทบไม่มีผลกระทบต่อการรับส่งข้อมูล
4. การเลือกค่า Prescaler (PSC) และ Auto-Reload (ARR)
Prescaler (PSC) เนื่องจากการนำความถี่ 100 MHz มาหารด้วย 115,200 จะได้ค่าเท่ากับประมาณ 868 ซึ่งเป็นค่าที่สามารถเก็บไว้ในรีจิสเตอร์ขนาด 16 บิตได้อย่างสบาย เราจึงไม่จำเป็นต้องลดความเร็วของ Timer ลง ดังนั้น เราจึงกำหนดค่า PSC = 0 ซึ่งหมายความว่า Timer จะทำงานด้วยความถี่เต็มที่ 100 MHz
Auto-Reload (ARR) เฟรมข้อมูล UART มาตรฐานมีความยาวทั้งหมด 10 บิต ประกอบด้วย
- 1 Start Bit
- 8 Data Bit
- 1 Stop Bit
ดังนั้น จำนวน Timer Tick ที่ต้องใช้สำหรับส่งข้อมูลครบหนึ่งเฟรมคือ
เราจึงกำหนดค่า ARR = 65535 ซึ่งเป็นค่าสูงสุดของรีจิสเตอร์ขนาด 16 บิต เพื่อป้องกันไม่ให้ตัวนับของ
Timer Counter รีเซ็ตระหว่างที่กำลังส่งข้อมูล
3. การตั้งค่าในโปรแกรม STM32CubeMX
เปิดโปรแกรม STM32CubeMX เลือก new project ตามรูปข้างล่าง

ค้นหา STM32 MCU รุ่นที่ใช้งานอยู่ เลือกรุ่น MCU ดังกล่าว แล้วคลิกปุ่ม “New Project” ตามตัวอย่างด้านล่าง

จากนั้น ในส่วน System Core > RCC ให้เปิดใช้งาน External Oscillator

เนื่องจากในคู่มือนี้ใช้บอร์ด STM32F411RE Nucleo ซึ่งใช้สัญญาณนาฬิกาภายนอกความถี่ 8 MHz ที่สร้างจาก ST-Link บนบอร์ด
จากหน้า Clock Configuration ให้ตั้งค่าตามรายการต่อไปนี้:
- Input Frequency: 8 MHz
- PLL Source: HSE
- HCLK: 100 MHz แล้วกด Enter

การตั้งค่านี้จะกำหนดให้ MCU ทำงานที่ความถี่สูงสุด 100 MHz โปรดทราบว่า หาก MCU ของคุณรองรับการทำงานที่ความถี่สูงกว่า 100 MHz ให้ตั้งค่าความถี่เป็น 100 MHz เพื่อให้สามารถทำตามคู่มือนี้ได้อย่างถูกต้อง
จากนั้น ไปที่แท็บ Pinout & Configuration แล้วกำหนดค่าดังนี้:
ในหัวข้อ Timers เลือก TIM3
ตั้งค่า Clock Source เป็น Internal Clock
ตั้งค่า Channel 1 (CH1) เป็น Output Compare
ตั้งค่า Output Compare Mode เป็น Toggle on Match ตามตัวอย่างด้านล่าง

NOTE : Pin PA6 ถูกกำหนดเรียบร้อยแล้ว โดยในคู่มือนี้จะใช้เป็นขา TX สำหรับการส่งข้อมูล
จากนั้น ไปที่แท็บ DMA Settings แล้วเพิ่มช่องสัญญาณ DMA โดยกำหนด Direction เป็น Memory to Peripheral ตามตัวอย่างด้านล่าง

จากนั้น ไปที่ Project Manager > Code Generation แล้วเปิดใช้งานตัวเลือก “Generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral”

สุดท้าย ไปที่แท็บ Project จากนั้นกำหนดชื่อโปรเจกต์ ตั้งค่า Toolchain / IDE เป็น STM32CubeIDE แล้วคลิกปุ่ม “Generate Code” ตามตัวอย่างด้านล่าง

4. การเปิดโปรเจกต์ใน STM32CubeIDE
เปิดโปรแกรม STM32CubeIDE เลือก Workspace ที่ต้องการ จากนั้นคลิกปุ่ม “Launch”
เมื่อเข้าสู่ STM32CubeIDE แล้ว ให้คลิกเมนู File และเลือก “STM32 Project Create/Import” ตามตัวอย่างด้านล่าง

จากนั้น ในหน้าต่าง Import STM32 Project ให้เลือก “STM32CubeMX/STM32CubeIDE Project” แล้วคลิกปุ่ม “Next” ตามตัวอย่างด้านล่าง

จากนั้น เลือกโฟลเดอร์ที่มีไฟล์ .ioc อยู่ แล้วคลิกปุ่ม “Finish” ตามตัวอย่างด้านล่าง

หมายเหตุ: ชื่อโปรเจกต์ใช้สำหรับการอ้างอิงเท่านั้น
เพียงเท่านี้ การตั้งค่าโปรเจกต์สำหรับ Software UART ก็พร้อมสำหรับการพัฒนาในขั้นตอนถัดไปแล้ว
😊 ขอบคุณที่ติดตามอ่าน หากบทความนี้เป็นประโยชน์ อย่าลืมแชร์ให้เพื่อน ๆ ที่สนใจ Embedded Systems ด้วยนะครับ
Source : Emulating UART Part 1: Introduction and environment Setup
Translated and edited by Bunpajarn
STM32| UART |TIMER | DMA |
