תקשורת UART ב-CH32V003

לדיבוג, לתקשורת נתונים עם מחשב או לעבודה מול מודול חכם – UART הוא מרכיב בסיסי כמעט בכל פרויקט אמבדד. הגיע הזמן להפעיל אותו גם במיקרו-בקר הסיני החדש שלנו.

לוח עם מיקרו-בקר CH32V003, ברקע: שידור UART מלוח תאום שנקלט בסביבת הפיתוח
לוח עם מיקרו-בקר CH32V003, ברקע: שידור UART מלוח תאום שנקלט בסביבת הפיתוח

לפני שניגש לנושא העיקרי, רציתי לספר על תקלה שקרתה לי. במסגרת ניסוי קטן בקוד כתבתי בטעות פקודה שהפכה את פין PD1 לפין פלט. זה לא נשמע נורא, נכון? אבל PD1 הוא הפין שמשמש לצריבה (SWIO), ומרגע שהעליתי את הקוד השגוי, סביבת הפיתוח סירבה לתקשר עם המיקרו-בקר דרך הצורב ולהעלות קוד חדש. גם חיבור של קו האתחול (RST) מהצורב ללוח שלי – פעולה שאינה נדרשת בצריבה רגילה – לא עזר. קיבלתי רק הודעת שגיאה גנרית: "Failed to configure mcu. Please refer to "WCH-LinkUserManual.pdf" for more help." והמסמך שהוזכר בה לא תרם כלום.

מה שכן עזר היה היישום WCH-LinkUtility, תוכנת צריבה נפרדת מסביבת הפיתוח, שהעבירה כנראה את אות ה-RST הנכון בסופו של דבר והצליחה למחוק את כל זיכרונות המיקרו-בקר, מה שאיפשר צריבה-מחדש רגילה מסביבת הפיתוח. את התוכנה הזו צריך להוריד מהאתר של WCH, והיא לא מעניינת במיוחד – אני מזכיר אותה רק כרפרנס לעתיד, אם אני או מישהו אחר ייתקל בהודעת השגיאה בנסיבות דומות.

חזרה ל-UART

המודול הרלוונטי נקרא USART1 (האות S כי יש גם אופציה לתקשורת סינכרונית), וכמו כל מודול אחר ב-CH32V003, גם אותו צריך לחבר לשעון כדי שיקבל חשמל ויוכל לפעול. לשם כך נכתוב "1" לביט 14 ("USART1EN") ברגיסטר RCC->APB2PCENR .

עכשיו מגיעה נקודה חשובה שהתפספסה בפוסטים קודמים. המודול אולי מחובר לשעון, אבל זה לא בהכרח שעון המערכת הכללי, זה שספרנו לפיו את millis למשל. השעון של המודולים הפריפריאליים, וה-USART ביניהם, נקרא AHB וכברירת מחדל הוא שעון המערכת חלקי 3. זה מוגדר בביטים 4-7 ("HPRE") ברגיסטר RCC->CFGR0, וכדי לשנות זאת לשעון המערכת ללא חלוקה צריך לכתוב לארבעת הביטים האלה "0000". לא חובה לעשות זאת, אבל חשוב מאוד לזכור עם איזה שעון בדיוק עובדים כשיגיע השלב של…

הגדרת ה-Baudrate

הגדרת קצב התקשורת היא סיפור מצחיק בפני עצמו. במיקרו-בקרים רבים, כולל STM32 שעליו ה-CH32V מתבסס בנדיבות, אפשר להגדיר ל-UART "קצב דגימה" כפול (16 דגימות) או רגיל (8), מה שנותן לנו לבחור בין דיוק – מה שעדיף בדרך כלל – לבין מהירות אם נדרשים קצבי תקשורת גבוהים במיוחד. אבל ב-CH32V003 ספציפית קיים אך ורק קצב דגימה 16. זה הופך את החישובים להרבה יותר פשוטים ממה שמופיע ב-Reference Manual!

במסמך מצוין שקצב התקשורת הוא HCLK / (16*USARTDIV), כאשר HCLK הוא השעון הרלוונטי ואילו USARTDIV הוא ייצוג נדיר למדי של מספר הקסדצימלי עם ספרה אחת אחרי הנקודה. המספר הזה שוכן ברגיסטר USART1->BRR, שארבעת הביטים האחרונים (LSB) בו הם ה"אחרי הנקודה". זה תרגיל נחמד למי שרוצה להתאמן על בינארי והקס, אך מיותר מבחינה פרקטית. אם תעשו את החשבון תראו שבזכות קצב הדגימה הקבוע אפשר לצמצם את הנקודה העשרונית (הקסית?) ואת ההכפלה ב-16 בנוסחה, ולהגיע לחישוב הקליל:

USART1->BRR = HCLK / baudrate

לדוגמה, אם ה-HCLK שלי הוא AHB ללא חלוקה, ושעון המערכת הוא 24MHz, ואני רוצה קצב תקשורת 115,200 באוד, אכתוב ל-USART1->BRR את התוצאה המעוגלת של 24 מיליון חלקי 115,200, שהיא 208. זה הכול!

הגדרות פינים ושימוש פשוט

כברירת מחדל, הפין PD5 הוא TX והפין PD6 הוא RX (ניתן לבחור אופציות אחרות דרך AFIO). אנחנו צריכים שפין ה-RX יהיה מוגדר כפין קלט צף – קל מאוד, כי זו ברירת המחדל של כל הפינים באתחול המיקרו-בקר – ושפין ה-TX יהיה Multiplexed function push-pull (ערך "10" בביטים CNFy ברגיסטר GPIOx->CFGLR) עם Maximum speed 50MHz (ערך "11" בביטים MODEy שם). בקוד זה יכול להיראות ככה:

// TX @PD5 AF_PP 50MHz; RX @PD6 floating input GPIOD->CFGLR &= 0xFF0FFFFF; GPIOD->CFGLR |= 0x00B00000;

עלינו להפעיל גם את המודול עצמו, בנוסף על חיבור השעון שעשינו קודם. זה אומר לגשת לרגיסטר USART1->CTLR1 ולכתוב "1" לביט 13 ("UE"), לביט 3 ("TE") בשביל המשדר ולביט 2 ("RE") בשביל המקלט.

// UE, Transmitter enable, receiver enable USART1->CTLR1 |= 0x0000200C;

המידע שעובר דרך ה-UART, לשני הכיוונים, עובר דרך הרגיסטר USART1->DATAR . כדי לשלוח בייט יחיד פשוט נכתוב אותו לרגיסטר הזה, וכאשר מגיע בייט, נקרא אותו מהרגיסטר הזה. מאחורי הקלעים מדובר, כנראה, באחסון נפרד. אגב, כמו כל הרגיסטרים ב-CH32V, גם DATAR הוא בגודל 32 ביט, אבל לצורך ה-UART נעשה שימוש רק בתשעת הביטים הנמוכים. תשעה ולא שמונה, כי קיים גם מצב שידור אופציונלי של 9 ביטים בכל פריים.

ברגיסטר הסטטוס USART1->STATR, ביט מס' 7 ("TXE") יהפוך ל-1 כשאפשר לכתוב בבטחה בייט חדש לשידור. ביט מס' 6 ("TC") יהפוך ל-1 כשהשידור יושלם לגמרי, וביט מס' 5 ("RXNE") יהפוך ל-1 כשיגיע בייט חדש ויהיה מוכן לקריאה. כשנקרא את הרגיסטר USART1->DATAR, הביט RXNE יחזור להיות 0 אוטומטית.

כברירת מחדל, מודול ה-USART מכוון להגדרות הנפוצות ביותר (8 ביטים פר פריים, LSB-First, ללא Parity, ביט עצירה יחיד וכו'). אז יש לנו כעת מספיק מידע כדי לכתוב פונקציות קריאה ושידור בסיסיות חוסמות (blocking), ועלינו להבטיח שהבדיקה של RXNE בתוכנה תתבצע לעתים קרובות מספיק כדי שלא נפספס בייטים בשידורים מבחוץ. כדי לכתוב פונקציות אמינות יותר, עלינו לעבור ל…

UART מבוסס פסיקות

טבלת ווקטורי הפסיקות מראה שיש פסיקה אחת ויחידה ל-USART1 (פסיקה מס' 32), מה שאומר שכמו במיקרו-בקרי PIC הישנים, גם פעולת השידור וגם פעולת הקליטה יצטרכו להשתמש באותה פונקציית פסיקה, ושם בפנים נצטרך לכתוב בעצמנו את הקוד שמבדיל בין האירועים.

כדי להפעיל את הפסיקה הזו ברמה הגלובלית, נכתוב "1" לביט 0 (האחרון בקבוצה INTEN שאחראית לפסיקות 38-32) ברגיסטר PFIC-<IENR[1]. בדומה למה שראינו בפוסט על millis, זה הרפרנס בסביבת הפיתוח לרגיסטר PFIC_INER2 שבמפרטים. נפעיל את הפסיקות גם ברמה הלוקלית – ברגיסטר USART1->CTLR1 נכתוב "1" לביט 5 ("RXNEIE") בשביל לעורר פסיקה כשמגיע בייט מבחוץ. מה לגבי הפסיקה של "אפשר לשדר בייט"? נחכה איתה עד שיהיה לנו מה לשדר. בינתיים נזכור שהיא נשלטת על ידי ביט 7 ("TXEIE") באותו רגיסטר.

כמובן, אם צריך לשלוח רק בייט אחד כל פעם, במרווחי זמן גדולים מספיק, אין סיבה לעבוד עם פסיקות. פשוט נכתוב את הבייט ל-USART1->DATAR וזהו. הפסיקות עוזרות כשצריך לכתוב מערך או מחרוזת ברצף, עם מינימום עיכובים בשידור אבל גם עם הפרעה מינימלית ככל האפשר לשאר הקוד. אז הנה הגדרה פשוטה של משתנים ופונקציה לשידור מערך בייטים:

volatile u32 UARTBytesLeft = 0; volatile u8 *UARTSendData; void UARTSendNonBlocking(u8 *data, const u32 n) { UARTSendData = data; UARTBytesLeft = n; // Enable the transmit interrupt USART1->CTLR1 |= 0x00000080; }

הפונקציה מכינה את המצביע והמונה (שימו לב שהם גלובליים ומוגדרים כ-volatile), ומאפשרת לפסיקת "אפשר לשלוח בייט" לעורר את פונקציית הפסיקה שנראית בערך ככה:

פונקציית פסיקה ל-USART1
פונקציית פסיקה ל-USART1, בהתאם לקוד שלמעלה (לחצו לתמונה גדולה)

זהו, ה-UART מוכן. מי יהיה המודול הבא בתור? 🙂

להרשמה
הודע לי על
0 תגובות
Inline Feedbacks
הראה את כל התגובות