אנחנו ממשיכים ללמוד איך להפעיל את המיקרו-בקר הסיני הזול עם ליבת RISC-V, והפעם: קריאת קלט דיגיטלי, ועבודה בסיסית עם המרת ADC (קלט אנלוגי)
קלט דיגיטלי
כפי שראינו בפוסט עם פרויקט הבלינק, ההגדרה של פיני קלט/פלט דיגיטליים "פשוטים" נעשית באמצעות הרגיסטר GPIOx_CFGLR, שמכיל 4 ביטים לכל פין – שניים שנקראים CNFy ומיד אחריהם שניים שנקראים MODEy. ברירת המחדל היא "01" ל-CNF, שזה "פין קלט צף", ו-"00" ל-MODE שזה "פין קלט". כלומר, בעצם אנחנו לא צריכים לגעת בכלום – ברירת המחדל של כל ה-GPIO היא קלט דיגיטלי. אבל יש גם את העניין של נגדי pull-up ו-pull-down פנימיים. אם נרצה להשתמש באחד מהם, צריך לשנות את CNFy ל-"10". לאחר מכן, הערך של הביט ODRy ברגיסטר הפלט GPIOx_OUTDR יקבע אם יופעל pull-down (ערך "0", ברירת המחדל) או pull-up (ערך "1").
לסיום, כדי לקרוא את הערך הדיגיטלי מהפין, נסתכל על הביט IDRy המתאים ברגיסטר GPIOx_INDR. כל זה די פשוט ומוכר לכל מי שעבד עם רגיסטרים, לא משנה אפילו באיזה מיקרו-בקר.
בלוח הפיתוח שתכננתי יש לחצן לשימוש חופשי, שמתחבר מצד אחד לפין PC3 ומצד שני (דרך נגד עם ערך נמוך) לאדמה. ההגדרה המתאימה, שהופכת את PC3 לפין קלט עם נגד pull-up פנימי, נראית כך:
GPIOC->CFGLR = 0x44448444;
GPIOC->OUTDR |= 0x08; // Set ODR3 to Pull-up
קלט אנלוגי
מודול ה-ADC של ה-CH32V003 מאתגר. יש בו הרבה מנגנונים ואפשרויות, ולרוע המזל התיעוד שלו לא תמיד ברור ואפילו לא עבר הגהה. אז גם אם מה שאציג פה נראה קצר ופשוט יחסית, בפועל נדרש לי הרבה זמן לזהות את החלקים הרלוונטיים לביצוע המרת ADC בסיסית, ויש המון פרמטרים שהשארתי במצב ברירת המחדל ולא התייחסתי אליהם כאן בכלל. למי שרוצה לחקור את ה-ADC הזה יותר לעומק, הנה טיפ שימושי: במקרה או שלא במקרה, הוא בנוי ועובד בצורה מאוד דומה ל-ADC של STM32F103, המיקרו-בקר שבלוחות ה-"Blue pill" שהיו נפוצים וזולים פעם.
בכל אופן, קצת רקע: ה-ADC של ה-CH32V003 הוא בעל רזולוציה צנועה של 10 ביטים, יש לו פונקציית "Analog watchdog" אופציונלית (פסיקה שמופעלת אוטומטית כאשר הערך הנקלט חוצה סף שאנחנו מגדירים), והוא עובד לפי שתי רשימות: רשימת ערוצים רגילה, ורשימת "Injection" לבדיקות דחופות. במקום לומר לו ישירות איזה ערוץ לקרוא (כגון פין GPIO מסוים), עלינו לכתוב מראש לרגיסטרי-רשימה מיוחדים את כל הערוצים שמעניינים אותנו – אפילו אם יש רק אחד כזה – וה-ADC יקרא את הרשימה ויפעל לפיה. זה נחמד אם היישום דורש בדיקה רציפה של מספר ערוצים שונים, למשל במערכת עם פאנל ממשק שכולל מספר פוטנציומטרים. קריאה פשוטה של ערוץ יחיד, לעומת זאת, טיפה יותר מסורבלת מאשר במיקרו-בקרים כמו ATmega.
אז אנחנו מתחילים, כמו שעשינו עם ה-GPIO, בחיבור שעון למודול ה-ADC כדי שיקבל חשמל ויוכל לעבוד. לשם כך נכתוב "1" לביט 9 ("ADC1EN") ברגיסטר RCC->APB2PCENR . אם נרצה לשנות את תדר שעון ה-ADC (פעולה חשובה בתנאים מסוימים), אחת האפשרויות היא לשנות את ה-Prescaler שלו באמצעות הביטים ADCPRE (ביטים 11-15) ברגיסטר RCC-<CFGR0 .
רשימת הערוצים הרגילה נמצאת ברגיסטרים ADC1->RSQR1/2/3 . קבוצת הביטים "L" (אינדקסים 20-23) שב-RSQR1 היא ה-Length, כלומר אורך הרשימה. שימו לב שאורך הרשימה המינימלי הוא 1, אבל זה מיוצג על ידי המספר "0000" דווקא. בשביל שני ערוצים צריך לכתוב שם "0001" וכן הלאה. כל חמישיית ביטים אחרי L (או מביט 29 עד 0, ברגיסטרים 2 ו-3) מכילה אינדקס של ערוץ שאותו רוצים לקרוא, בין 0 ל-9. החמישייה הקרובה ביותר ל-L היא מקום 16 ברשימה, אחריה מקום 15, עד החמישייה שבסוף RSQR3 (הביטים הנמוכים ביותר) שהיא מקום 1. למה צריך 5 ביטים בשביל הטווח 0-9? כנראה עניין של תאימות עם דגמים מתקדמים יותר. בשיטה הזו אנחנו יכולים בעיקרון להגדיר רשימה עם ערוץ אחד עד שישה-עשר ערוצים, באיזה סדר שבא לנו.
רגע, מי הם בכלל הערוצים שממוספרים 0-9? מאיפה כל אחד מגיע? את המידע הלא-טריוויאלי הזה צריך לאסוף ממסמכי המפרטים השונים. במקרה של ה-CH32V003F4P6, במארז TSSOP20:
- A0 = PA2
- A1 = PA1
- A2 = PC4
- A3 = PD2
- A4 = PD3
- A5 = PD5
- A6 = PD6
- A7 = PD4
- A8 = Vref (ערך רפרנס פנימי)
- A9 = Vcal (ערך כוונון פנימי)
בפוסט הנוכחי, המטרה היא לקרוא ערך מערוץ אחד בלבד. אז נכתוב את האינדקס שלו במקום הראשון ברשימה – כלומר, בסוף ADC1->RSQR3 – ונשאיר את כל שאר הפרמטרים והרגיסטרים האחרים בברירת המחדל. נתניע את מודול ה-ADC באמצעות כתיבת "1" לביט 0 ("ADON") ברגיסטר ADC1-<CTLR2. מעכשיו, כל עוד הביט הזה הוא "1", כתיבה נוספת של "1" אליו (ורק אליו, ברגיסטר הזה) תגרום להתחלה של המרת ADC. המודול ייגש לרשימה הרגילה, יראה שיש שם רק ערוץ אחד לקרוא, יקרא אותו ובסיום יכתוב "1" לביט מס' 1 ("EOC") ברגיסטר הסטטוס ADC1->STATR . אנחנו יכולים לדגום את הביט הזה בתוכנה כדי לברר מתי ההמרה הסתיימה, או להפעיל פסיקה שמסתמכת עליו.
הערך שנקרא מהערוץ יישמר ברגיסטר ADC1->RDATAR . ברגע שנקרא את הערך שמופיע בו, הביט EOC ינוקה אוטומטית ויחזור להיות "0".
קוד לדוגמה
כרגיל, כל המלל הזה מתבטא בסופו של דבר במספר שורות קוד פשוטות. אם הגדרנו את ה-ADC להמרה בודדת כפי שהסברתי למעלה, הפונקציה לקריאה בסיסית נראית ככה:
// Assumes ADC is configured correctly
u32 getSimpleAnalogReading(void) {
ADC1->CTLR2 |= 1; // "ADON"
// Wait for results (EOC)...
while (0 == ADC1->STATR & 2) {}
return ADC1->RDATAR;
}
בתוכנית בדיקה שכתבתי, השתמשתי בפין PC4 בתור ערוץ הקלט האנלוגי. בטבלה למעלה אפשר לראות שזהו ערוץ אנלוגי מס' 2. לכן, קוד האתחול של מודול ה-ADC לקריאה של הערוץ הזה בלבד נראה ככה:
// Using PC4 for analog input (A2)
// Enable clock for ADC
RCC->APB2PCENR |= 0x00000200; // ADC1EN
// ADC clock prescaler default is /2
// Select channel (5 bits in sequence register)
ADC1->RSQR3 = 0x00000002;
// Initial "1" write to ADON to wake module up.
ADC1->CTLR2 |= 1;
// ADC result default is right-aligned.