שְׁאֵלָה:
האם מישהו יכול להסביר את הקוד המוזר הזה, המשמש להגדרת טיימרים?
The Guy with The Hat
2014-02-24 08:06:06 UTC
view on stackexchange narkive permalink

בזמן שאני מסתכל על רישומים שאנשים אחרים כתבו, אני נתקל מדי פעם בקוד שנראה כך:

  TCCR1A = 0; TCCR1B = 0; TCNT1 = 34286; TCCR1B | = (1 << CS12); TIMSK1 | = (1 << TOIE1);  

כל מה שאני יודע זה שיש לזה משהו עם תזמון / טיימרים (אני חושב). איך אוכל לפענח - וליצור - קוד כזה? מה הם TCCR1A , TCCR1B , TCNT1 , CS12 , TIMSK1 , and TOIE1 ?

לא יודע מספיק לענות אבל: http://electronics.stackexchange.com/questions/92350/what-is-the-difference-between-tccr1a-and-tccr1b, http://forum.arduino.cc/index .php? topic = 134602.0, ו http://stackoverflow.com/questions/9475482/pulse-width-modulation-pwm-on-avr-studio. לא יודע אם כבר ראית את אלה.
הורד את גליון הנתונים "השלם" למכשירך מאתר [אתר Atmel] (http://atmel.com/) וקרא את הפרקים על טיימרים. גיליון הנתונים טוב להפליא לקרוא לדעתי.
שְׁלוֹשָׁה תשובות:
#1
+15
Connor Wolf
2014-02-24 09:54:06 UTC
view on stackexchange narkive permalink

זה לא נראה מוזר. כך נראה קוד MCU רגיל.

מה שיש לך כאן הוא דוגמה למושג ציוד היקפי ממופה זיכרון . בעיקרון, לחומרת MCU יש מיקומים מיוחדים במרחב הכתובות SRAM של ה- MCU שהוקצה לה. אם אתה כותב לכתובות אלה, החלקים של בתים שנכתבים לכתובת n שולטים על ההתנהגות של ה היקפי.

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

אם אתה מסתכל בכותרות של MCU , יש טבלאות גדולות ומפות של מילות מפתח<->. כך נפתרים דברים כמו TCCR1B וכו '... בזמן הידור.

מנגנון מיפוי זיכרון זה נמצא בשימוש נרחב ביותר ב- MCU. ה- ATmega MCU בארדואינו משתמש בו, וכך גם סדרות PIC, ARM, MSP430, STM32 ו- STM8, כמו גם המון MCU שאינני מכיר מיד.


ארדואינו קוד הוא החומר המוזר, עם פונקציות שניגשות לרשומות הבקרה של MCU בעקיפין. אמנם זה נראה "נחמד" במקצת, אבל הוא גם איטי בהרבה ומשתמש בהרבה יותר שטח תוכניות.

הקבועים המסתוריים מתוארים בפירוט רב ב גיליון הנתונים ATmega328P שעליך לקרוא אם אתה מעוניין לעשות משהו נוסף ואז להחליף סיכות בארדואינו.

בחר קטעים מגליון הנתונים המקושר לעיל:

enter image description here enter image description here enter image description here

אז, למשל, TIMSK1 | = (1 << TOIE1); מגדיר את הסיבית TOIE1 ב- TIMSK1 . זה מושג על ידי העברת בינארי 1 ( 0b00000001 ) שמאלה על ידי TOIE1 סיביות, כאשר TOIE1 מוגדר בקובץ כותרת כ- 0. זה לאחר מכן נקבע באופן קצת חכם לערך הנוכחי של TIMSK1 , אשר למעשה מגדיר סיבית זו גבוהה.

עיון בתיעוד עבור סיבית 0 של TIMSK1 , אנו יכולים לראות את זה מתואר כ

כאשר סיבית זו נכתבת לאחד, וסימן ה- I ברשימת הסטטוסים מוגדר (מופרע באופן גלובלי), הפסקת הצפה של טיימר / מונה 1 מופעלת . וקטור ההפרעה המתאים (ראה "הפרעות" בעמוד 57) מבוצע כאשר מוגדר דגל ה- TOV1 הממוקם ב- TIFR1.

יש לפרש את כל שאר השורות באותו אופן.


כמה הערות:

ייתכן שתראה דברים כמו TIMSK1 | = _BV (TOIE1); . _BV () הוא מאקרו הנפוץ במקור מיישום AVR libc. _BV (TOIE1) זהה מבחינה פונקציונלית ל- (1 << TOIE1) , עם יתרון של קריאה טובה יותר.

כמו כן, ייתכן שתראה גם קווים כאלה כמו: TIMSK1 & = ~ (1 << TOIE1); או TIMSK1 & = ~ _BV (TOIE1); . יש לזה את הפונקציה ההפוכה של TIMSK1 | = _BV (TOIE1); , בכך שהוא מבטל את ההגדרה הסיבית TOIE1 ב- TIMSK1 קוד>. זה מושג על ידי לקיחת מסיכת הסיביות המיוצרת על ידי _BV (TOIE1) , ביצוע פעולת NOT bitwise בה ( ~ ) ואז ANDing TIMSK1 קוד> לפי ערך זה שצויין (שהוא 0b11111110).

שים לב שבכל המקרים הללו, הערך של דברים כמו (1 << TOIE1) או _BV (TOIE1) נפתרים במלואם ב זמן הידור , ולכן הם מצמצמים באופן קבוע לקבוע פשוט, ולכן אין להם זמן ביצוע לחישוב בזמן הריצה.


קוד כתוב נכון בדרך כלל יכלול הערות עם קוד המפרט את מה שהרשמים מוקצים לעשות. הנה שגרת רך-SPI פשוטה למדי שכתבתי לאחרונה:

  uint8_t transactByteADC (uint8_t outByte) {// מעביר בייט אחד ל- ADC, ומקבל בייט אחד בו זמנית // עושה שום דבר עם שבב הבחירה // MSB ראשונה, נתונים שעונים בקצה עולה uint8_t loopCnt; uint8_t retDat = 0; עבור (loopCnt = 0; loopCnt < 8; loopCnt ++) {if (outByte & 0x80) // אם הסיבית הנוכחית גבוהה PORTC | = _BV (ADC_MOSI); // הגדר קו נתונים אחר PORTC & = ~ (_BV (ADC_MOSI)); // אחר בטל את הסדר אותו Byte << = 1; // ולהעביר את נתוני הפלט מעל לאיטרציה הבאה retDat << = 1; // מעבר על הנתונים שקוראים חזרה PORTC | = _BV (ADC_SCK); // הגדר את השעון גבוה אם (PINC & _BV (ADC_MISO)) // מדגם את שורת הקלט retDat | = 0x01; // והגדר את הסיבית בהחלפה אם הקלט גבוה PORTC & = ~ (_BV (ADC_SCK)); // הגדר שעון נמוך} החזר retDat;}  

PORTC הוא הרישום השולט בערך של סיכות פלט בתוך PORTC של ה- ATmega328P. PINC הוא המרשם בו ערכי ה קלט של PORTC זמינים. ביסודו, דברים כאלה הם מה שקורה באופן פנימי כשמשתמשים בפונקציות digitalWrite או digitalRead . עם זאת, יש פעולת בדיקה הממירה את "מספרי הסיכה" של הארדואינו למספרי סיכות חומרה בפועל, אשר לוקח איפשהו בתחום של 50 מחזורי שעון. כפי שאתה בוודאי יכול לנחש, אם אתה מנסה ללכת מהר, לבזבז 50 מחזורי שעון על פעולה שאמורה לדרוש רק 1 זה קצת מגוחך.

הפונקציה לעיל כנראה לוקחת איפשהו בתחום 100 -200 מחזורי שעון להעברת 8 ביטים. זה כרוך ב -24 סיכות בכתיבה, וב -8 קריאות. זה מהיר הרבה מאוד פעמים מאשר להשתמש בפונקציות digital {stuff} .

שים לב שקוד זה אמור לעבוד גם עם Atmega32u4 (משמש בליאונרדו) מכיוון שהוא מכיל יותר טיימרים מאשר ATmega328P.
+1 תשובה מעולה !! אתה יכול בבקשה לפרט קצת יותר על מה שאתה מתכוון ב'קוד ארדואינו הוא החומר המוזר '? מה בדיוק ייחודי לקוד Arduino, זה לא נמצא בדרך כלל בפלטפורמות אחרות?
@Ricardo - כנראה 90% + מהקוד המשובץ MCU קטן משתמש במניפולציה ישירה של רישום. ביצוע דברים עם פונקציות עקיף עקיפות הוא מאוד לא המצב הנפוץ של מניפולציה של IO / ציוד היקפי. יש כמה ערכות כלים להפשטת בקרת חומרה (Atmel ASF, למשל), אך בדרך כלל זה נכתב כדי להרכיב כמה שיותר כדי להפחית את תקופת זמן הריצה, וכמעט תמיד נדרש להבין את ציוד היקפי על ידי קריאת גליונות הנתונים.
ביסודו של דבר, דברים ארדואינו באמירה "הנה פונקציות שעושות X", מבלי לטרוח באמת להפנות את התיעוד בפועל או איך החומרה * עושה * את הדברים שהיא עושה, זה מאוד לא נורמלי. אני מבין שזה ערך ככלי היכרות, אבל למעט טיפוס מהיר, זה לא באמת נעשה בסביבות מקצועיות של ממש.
כדי להיות ברור, הדבר שהופך את קוד הארדואינו לבלתי רגיל עבור קושחת MCU מוטבע אינו * ייחודי * לקוד הארדואינו, זה פונקציה של הגישה הכוללת. ביסודו של דבר, ברגע שיש לך הבנה ראויה של ה- MCU * האמיתי *, לעשות דברים כמו שצריך (למשל להשתמש ישירות ברשומות חומרה) לוקח זמן רב עד אין זמן נוסף. ככזה, אם אתה רוצה ללמוד התפתחות MCU אמיתית, עדיף פשוט לשבת ולהבין מה ה- MCU שלך * בעצם * עושה, אלא להסתמך על ההפשטה של ​​מישהו * אחר, שנוטה להיות דולף.
ביסודו של דבר, כל מערכת הארדואינו מתוכננת פחות או יותר כך שתאפשר כניסה נמוכה של כניסה לתוכניות * שאינן *. זה עושה את זה טוב, אך יחד עם זאת זה לא עושה עבודה טובה לאלץ את משתמשי הקצה בפועל * להבין * את החומרה, פשוט כי זה מנוגד לחסמת כניסה נמוכה. אני מכיר בכך שזו יכולה להיות אבן דרך מדריכה רבת ערך לאנשים בדרך לתוכנת למידה / MCU dev, אך כל מי שמשתמש בה לא צריך להיות תחת אשליות שדרך הארדואינו היא בהכרח הטובה ביותר, או אפילו לפעמים טובה. דרך לעשות את מה שהיא עושה.
שים לב שאני אולי קצת ציני כאן, אבל הרבה ההתנהגויות שאני רואה בקהילת הארדואינו מתכנתות אנטי דפוסים. אני רואה הרבה תכנות "העתק-הדבק", התייחסות לספריות כקופסאות שחורות, וסתם שיטות עיצוב כלליות לקויות בקהילה כולה. כמובן, אני פעיל למדי ב- EE.stackexchange, כך שייתכן שיש לי מבט מעט מלוכסן, מכיוון שיש לי כמה כלי מנחה, וככאלה אני רואה הרבה מהשאלות הסגורות. בהחלט יש הטיה בשאלות הארדואינו שראיתי שם כלפי "תגיד לי מה ל- C&P לתקן", אלא "למה זה לא עובד".
מן הסתם כדאי גם לציין שעצם השימוש ב- C ++ הוא * מעט * יוצא דופן. רוב עבודות ה- MCU הקטנות נעשות ב- pure-C בלבד.
הבנת! לא היה לי ברור איך קראת לגישה עקיפה לרשמים בארדואינו, אבל עכשיו אני יודע שזה סוג ההפשטות של 'digitalWrite ()'. זה אמנם מצמצם את מחסום הכניסה, אך בהמשך הופך למחסום להמשך הדרך. אבל התשובה שלך היא צעד נהדר להסרת המכשול. לפחות למעני.
@Ricardo - כן. אחד הדברים שאני באמת * עושה * כמו בארדואינו ככלי לימוד הוא שהוא נותן לך סביבת עבודה למשחק עם החומרה באופן ישיר, מבלי שתצטרך להתמודד עם החלק הכי מרגיז לרוב: ההבאה הראשונית. עבדתי בפלטפורמות שכל מה שהיה לי היה ספק שירותי אינטרנט וללא דוגמאות עבודה. צריך לנסות לפתור מדוע ממשק ניפוי הבאגים הסדרתי שניסיתי לעבוד ללא ממשק ניפוי באגים אמיתי הוא מעורב. קיום פלטפורמה עם libs סדרתי קל לשימוש הוא נחמד מאוד.
#2
+3
TheDoctor
2014-02-24 09:07:30 UTC
view on stackexchange narkive permalink

TCCR1A הוא רושם בקרת טיימר / מונה 1

TCCR1B הוא רושם בקרת טיימר / מונה 1 B

TCNT1 הוא ערך המונה של טיימר / מונה 1

CS12 הוא הסיבית השלישית לבחירת שעון עבור טיימר / מונה 1

TIMSK1 הוא רישום מסיכת הפסיקה של הטיימר / מונה 1

TOIE1 הוא הפסקת ההפרעה של הטיימר / מונה 1

אז, הקוד מאפשר טיימר / מונה 1 ב 62.5 קילוהרץ ומכוון את הערך ל 34286. ואז הוא מאפשר את הפרעת ההצפה כך שכאשר הוא מגיע ל 65535, הוא יפעיל את פונקציית ההפרעה, ככל הנראה שכותרתו ISR (timer0_overflow_vect)

#3
+1
VENKATESAN
2020-01-18 22:44:35 UTC
view on stackexchange narkive permalink

ל- CS12 יש ערך 2 מכיוון שהוא מייצג את ביט 2 של רישום TCCR1B.

(1 << CS12) לוקח את הערך 1 (0b00000001) ומעביר אותו שמאלה פעמיים כדי לקבל (0b00000100). סדר הפעולות מכתיב שהדברים ב- () קורים קודם לכן אז זה נעשה לפני "| =" מוערך.

(1 << CS10) לוקח את הערך 1 (0b00000001) ומעביר אותו שמאלה 0 פעמים לקבל (0b00000001). סדר הפעולות מכתיב שדברים ב- () קורים קודם אז זה נעשה לפני שמעריכים את "| =".

אז עכשיו נקבל TCCR1B | = 0b00000101, שהוא זהה ל- TCCR1B = TCCR1B | 0b00000101.

מאז "|" הוא "OR", כל הביטים שאינם CS12 ב- TCCR1B אינם מושפעים.



שאלה ותשובה זו תורגמה אוטומטית מהשפה האנגלית.התוכן המקורי זמין ב- stackexchange, ואנו מודים לו על רישיון cc by-sa 3.0 עליו הוא מופץ.
Loading...