דוא"ל:




קורס:"תכנות בזמן אמת בשפת ++C"

שיעור 3: דוגמא: מודם אלחוטי

[ <<< הקודם ] [ תוכן עניינים ]

      
תוכן עניינים

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

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

המודם שבתרשים (Comm. System) מקבל מידע לשידור ממכשיר מסוים להעברה ומשדר אותו למערכת אחרת דרך הלוויין. בערוץ הקליטה המודם מעביר את המידע הנקלט למכשיר המתאים.

מבנה לוגי של המודם

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

משדר - תת-מערכת הכוללת את המרכיבים הבסיסיים הבאים:

    מקור מידע (Information source) - המקור ממנו מגיע המידע לשידור
    קידוד מקור (source coding) - קידוד המידע ברמת המקור. מיועד להתגברות על שגיאות מידע בקלט. דוגמאות לשיטות קידוד: קידוד ויטרבי, קידוד ריד-סולומון.
    אפנון (Modulation) - קידוד המידע עפ"י שיטת השידור. שיטות אפנון נפוצות: אפנון תדר, אפנון פאזה, אפנון משרעת, CDMA, GSM.
    ממיר ספרתי-לאנלוגי (D2A) - המרת המידע מייצוג ספרתי לייצוג אנלוגי.
    אנטנת שידור- לשידור האות לאוויר.

מקלט - תת-מערכת הכוללת את המרכיבים ההפוכים למשדר:

    אנטנת קליטה - לקליטת האות שבאוויר.
    ממיר אנלוגי-לספרתי (A2D) - המרת האות הנקלט מייצוג אנלוגי לייצוג ספרתי.
    דה-אפנון (Demodulation) - קידוד הפוך (פענוח) לאפנון שבוצע בשידור.
    קידוד מקור הפוך (source decoder) - קידוד הפוך לקידוד המקור שבוצע בשידור.
    יעד הפלט (Information output) - היחידה אליה נשלח המידע הנקלט.

מבנה פיזי של המודם

התרשים הבא מתאר באופן סכמתי את המבנה הפיזי של המערכת:

קווי הבקרה והתזמון מיחידת הבקרה  לכל רכיבי המערכת הושמטו בכדי לפשט את השרטוט.

הזיכרון (memory) מכיל שני חלקים עיקריים: ה- ROM (Read Only Memory) הוא מקום האחסון של תוכנת המערכת, כלומר של קובץ הביצוע.

בהפעלת המכשיר, התוכנה מתחילה להתבצע מכתובת מסוימת ב- ROM. ה- RAM   (Raw Access Memory) הוא זיכרון המיועד לפעולות חישוב ולאחסון תוצאות הביניים.

המעבד (CPU) היא יחידת החישוב של המערכת. בכל רגע נתון מתבצעת במעבד הוראה אחת מתוך קוד התכנית לביצוע.

המעבד קורא את ההוראה הבאה מקטע הקוד (Code segment) ומבצע אותה.

יחידת הבקרה והתזמון (Timing & Control unit) היא מרכיב ראשי בתכנון המערכת: היא מספקת את אותות הבקרה והתזמון לרכיבי המערכת השונים.

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

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

שאלה: עפ"י התרשים, כיצד ממומש המאפנן (modulator) - בתוכנה או בחומרה? וכיצד ממומש מקודד המקור (source encoder) שבתרשים הלוגי?

חלוקת  משימות בין התוכנה והחומרה במערכת

משימות המערכת הנ"ל מבוצעות במתואם ע"י התוכנה והחומרה במערכת. ההחלטה מי מבצע מה תלויה במספר פרמטרים:

  • מהירות הביצוע (התלויה בקצב המידע): אם המהירות גבוהה מכדי לבצע את המשימה ע"י התוכנה נדרש רכיב חומרה ייעודי לביצוע המשימה. יש לשים לב שמהירות הביצוע ע"י התוכנה תלויה במעבד שבמערכת.
  • עלות רכיבי החומרה: רכיב חומרה יקר עלול לייקר את המערכת - מימוש המשימה בתוכנה (אם אפשרי) יחסוך את הצורך ברכיב.
  • זמן הפיתוח בתוכנה: פיתוח של מודול תוכנה המבצע משימה מורכבת עלול לצרוך זמן (וכסף) רב.
  • גמישות הפעלה ויכולת ביצוע שינויים: מודול תוכנה בדרך כלל גמיש יותר הן להפעלה במודים שונים והן לביצוע שינויים במודול.

הפעלת החומרה ע"י התוכנה

מערכת משובצת מחשב מופעלת במשולב ע"י התוכנה והחומרה. התוכנה היא השולטת ומפעילה את כלל המערכת. פעולות עיקריות שמבצעת התוכנה במסגרת זו:

  - אתחול החומרה וביצוע בדיקות עצמיות (BIT=Built In Test)
  - הפעלת ממשק המשתמש
  - תזמון מודולי התוכנה והחומרה לעבודה בזמנים מתאימים
  - קבלת מידע מהמשתמש והעברתו לחומרה לשידור
  - קבלת המידע הנקלט מהחומרה והעברתו למשתמש
  - בדיקת סטטוס החומרה לזיהוי תקלות ולטיפול בהן

כיצד התוכנה והחומרה "מדברות" ביניהן? קיימים מספר רכיבים המשמשים בהעברת מידע ובקרה בין התוכנה והחומרה:

    רגיסטרים - יחידות זיכרון המשמשות להעברת הוראות מהתוכנה לחומרה ולקבלת סטטוסים חוזרים מהחומרה. גדלים טיפוסיים: 8, 16 ו- 32 סיביות.
    רכיבי FIFO - יחידות שניתן לכתוב אליהן ולקרוא מהן בו זמנית, כאשר המידע המוכנס ראשון בכתיבה יוצא ראשון בקריאה. הן שימושיות בדרך כלל בהעברת המידע לשידור מהתוכנה לחומרה ( FIFO שידור) והעברת המידע הנקלט מהחומרה לתוכנה ( FIFO קליטה).
    שעונים - יחידות המתזמנות את המודולים השונים במערכת. התוכנה מאתחלת את שעוני המערכת לקבלת פסיקות בזמנים או במרווחי זמן מסוימים, ובקבלת הפסיקות התוכנה מתזמנת את המודולים המתאימים לביצוע.
    
    התרשים להלן ממחיש את אופן הפעלת החומרה ע"י התוכנה:

הערה : Tx ו- Rx הם קיצורים מתאימים ל- transmit ו- receive  (שידור וקליטה).

תוכנת המודם

נראה כעת את התוכנה המפעילה את המערכת. תרשים המחלקות:



הסבר: תרשים זה כולל מספר מצומצם וחיוני של מחלקות בתוכנת המודם.

  • Modem היא המחלקה הראשית המפעילה את המודם. היא מוגדרת כ- Singleton, ולמעשה היא גם משמשת כ- Facade עבור כלל מערכת המודם (ראה/י פרק 16).
  • Register היא מחלקת הבסיס לרגיסטרי הבקרה (ControlReg) והסטטוס (StatusReg), המספקת הגדרת Mode. הירושה כאן אינה כוללת פונקציה וירטואלית כלשהי, וזאת בכדי שעצם מהרגיסטר לא יתפוס יותר מבית בודד בזיכרון (תוספת פונקציה וירטואלית אחת תגרום להוספת המצביע vptr לעצם).
  • FifoReg הוא רגיסטר הממשק ל- Fifo: המחלקה הראשית, Modem, מחזיקה שני מופעים שלו - אחד עבור הכתיבה בשידור, והאחר עבור הקריאה בקליטה.
  • ModemApp היא מחלקת היישום המשתמשת בתוכנת המודם. גם היא מוגדרת כ- Singleton. היא כוללת את הפונקציה main() הכוללת את הלולאה הראשית (אינסופית) של היישום, וכן תהליכים לקריאת מידע לשידור מהקלט ולמשלוח המידע הנקלט לפלט.

קוד המחלקות

קוד המחלקה Register:

 
class Register
{
public:
          enum Mode {               // mode of work
                   NORMAL=0,     // normal work: transmit and receive
                   DEBUG=1,        // debug mode
                   LOOP_BACK=2,          // loop-back mode
                   BIT=3                 // Built-In Test mode
          };
};
 

קוד המחלקות ControlReg ו- StatusReg:

 
class ControlReg : public Register
{
public:
          void  setInit()              { m_init = true; }
          void  setMode(Mode mode) { m_mode = mode; }
private:
          unsigned char  m_mode:2;
          unsigned char  m_init:1;      // hardware initialize, atuo-reset 
};
 
class StatusReg : public Register
{
public:
          enum BITResult {     // Built-In Test (BIT) result
                   BIT_OK=0,        // BIT result OK
                   RADIO_ERROR=1,     // radio unit test error
                   MEM_ERROR=2,        // memory unit test error
                   FIFO_ERROR=3         // FIFO unit test error
          };
          int    getMode() const { return m_mode; }
          int    getBITResult() const { return m_BITResult; }
          bool  isOverflow() const { return m_fifoOverflow; }
          bool  isUnderflow() const { return m_fifoUnderflow; }
private:
          volatile unsigned char          m_mode:2;
          volatile unsigned char          m_BITResult:3;
          volatile bool                 m_fifoOverflow:1;
          volatile bool                 m_fifoUnderflow:1;
};
 

קוד המחלקה FifoReg:

 
class FifoReg
{
          volatile unsigned char          m_data:8;
public:
          void  setData(int data) { m_data = data; }
          int    getData() const { return m_data; }
};
 

וקוד המחלקה הראשית, Modem:

 
class Modem
{
 
  - הגדרת מצביעים לרגיסטרים:
 
          StatusReg         *m_pStatus;
          ControlReg       *m_pControl;
          FifoReg     *m_pTxFifo;
          FifoReg     *m_pRxFifo;
 
  - איתחול ב-‑constructor : כתובות הרגיסטרים נקבעות באמצעות הטכניקה שלעיל, "placement new":
 
          Modem()
          {
                   // set addresses of registers
                   m_pStatus        = reinterpret_cast<StatusReg*> (0xff210980);
                   m_pControl       = reinterpret_cast<ControlReg*> (0xff210984);            
                   m_pTxFifo         = reinterpret_cast<FifoReg*> (0xff210988);
                   m_pRxFifo        = reinterpret_cast<FifoReg*> (0xff21098c);
 
                   // allocate with placement new
                   m_pStatus = new (m_pStatus) StatusReg;
                   m_pControl = new (m_pControl) ControlReg;
                   m_pTxFifo = new (m_pTxFifo) FifoReg;
                   m_pRxFifo = new (m_pRxFifo) FifoReg;
 
                   m_pControl->setInit();                        // init hardware, auto-reset
          }
          
public:
 
  - פונקצית הגישה ל- Singleton:
 
          static Modem& instance() // singleton access method
                   { static Modem theModem; return theModem; }
 
  - פונקציה לביצוע הבדיקה הפנימית של החומרה (BIT) :
 
        int  do_BIT() // Built In Test 
          {
                   m_pControl->setMode(ControlReg::BIT);   // start Built In Test 
                   
                   // wait for BIT end (Polling)
                   while(m_pStatus->getMode() == StatusReg::BIT)
                                      ;
 
                   // return BIT result
                   return m_pStatus->getBITResult();
          }
 
    לאחר קביעת המצב ברגיסטר הבקרה ל- BIT, הפונקציה בודקת בלולאה (Polling) האם החומרה סיימה את תהליך ה- BIT. בסיום, מוחזרת תוצאת ה- BIT.
    
  - פונקציה לביצוע קידוד מקור למידע לפני השידור:
 
          void encode(const char *msg) const {/*...*/}
 
  - פונקציה לביצוע דה-קידוד (פענוח) למידע המקודד שנקלט:
 
          void decode(const char *msg) const {/*...*/}
 
  - פונקציה לכתיבת חוצץ בעל אורך נתון ל- FIFO השידור:
 
          void write_to_FIFO(const char *msg, int length)
          {
                   encode(msg);
                   for(int i=0; i<length; i++)  // write to Tx FIFO register
                            m_pTxFifo->setData(msg[i]);
                   
                   if(m_pStatus->isOverflow())  // check for overflow 
                            cerr << "Error: Overflow in Tx FIFO" << endl;
          }
         
 
  - פונקציה לקריאת חוצץ בעל אורך נתון מ- FIFO הקליטה:
 
        void read_from_FIFO(const char *msg, int length) const
          {
                   for(int i=0; i<length; i++)  // read from Rx FIFO register 
                            msg[i] = (char) m_pRxFifo->getData();
                   
                   if(m_pStatus->isUnderflow()) // check for underflow 
                            cerr << "Error: Underflow in Rx FIFO" << endl;
                   else
                            decode(msg);
          }
};
 

מחלקת היישום, ModemApp:

 
class ModemApp
{
          enum { MSG_SIZE=64, MSGS_NUM=4};
          unsigned char  m_txBuffer[MSGS_NUM][MSG_SIZE]; 
          unsigned char  m_rxBuffer[MSGS_NUM][MSG_SIZE];
          ModemApp() {}
public:
          static ModemApp& instance() 
          { static ModemApp theModemApp;  return theModemApp; }
          void start_input() { /*...*/ }
          void start_output() { /*...*/ }
          
          // main() - demo uses of the Modem application
          int main()
          {
                   // do Built In Test 
                   if(Modem::instance().do_BIT() != StatusReg::BIT_OK)
                   {
                            cerr << "Error: Hardware BIT error" << endl;
                            return -1;
                   }
                   
                   start_input(); // this process writes the tx buffer
                   start_output(); // this process reads the rx buffer
                   
                   // main loop 
                   for(int i=0 ;  ; i++)         // forever 
                   {
                            int index = i % MSGS_NUM;
                            
                            // write tx buffer to tx FIFO 
                            Modem::instance().write_to_FIFO(m_txBuffer[index], MSG_SIZE);
                            
                            // read rx buffer from rx FIFO 
                            Modem::instance().read_from_FIFO(m_rxBuffer[index], MSG_SIZE);
                   }
          }
};
 

והפונקציה הראשית, main(), פשוט קוראת ל- ModemApp::main():

 
int main()
{
          return ModemApp::instance().main();
}
 



[ <<< הקודם ] [ תוכן עניינים ]