لنتعلم برمجة الأنظمة المدمجة

Embedded Systems Programming Bootcamp

كورس تعليمي يهدف الى اكسابك المعرفة اللازمة والمهارات التقنية المتعلقة ببرمجة وتطوير الأنظمة المدمجة وفهم خصائصها ومكوناتها

الصفحة الرئيسية
قائمة الدروس
خدمة RSS

للتواصل

13. بروتوكول الإتصال المتزامن I2C

2018-08-17






يعتبر الـ Inter-integrated Circuit - I2C نظام اتصال تسلسلي serial عالي السرعة وثنائي الاتجاه للربط مع الطرفيات كما هو الحال بالنسبة للـ SPI والـ UART. ويعتبر هذا البروتوكول أبطأ من SPI ولكنه يستخدم عدد أقل من الخطوط للإتصال حيث بالإمكان الإختيار بين أحد السرعات الأربع التالية:

  1. قياسي - 100 كيلوبت في الثانية
  2. سريع - 400 كيلوبت في الثانية
  3. سرعة مضاعفة - 1 ميجابت في الثانية
  4. سرعة عالية - 3.33 ميجابت في الثانية

الميزة الرئيسية التي تميز I2C عن UART و SPI هي أن ناقل I2C يمكنه دعم العديد من الأجهزة الرئيسية master والفرعية slave على نفس الناقل حيث تتيح لنا القدرة على دعم أجهزة متعددة في ناقل واحد تقليل عدد الأطراف pins الخارجية على المتحكم، مما يقلل من تكلفة وحجم الجهاز.

وبإمكان جميع الأجهزة المتصلة أن تعمل إما بالوضع الرئيسي master أو الفرعي slave. بالإضافة إلى أنه بإمكان هذه الأجهزة التبديل بين هذين الوضعين.

كما أن لكل جهاز متصل معرَف أو عنوان فريد unique address خاص به ومعيَن له مسبقاً، ولذلك يمكن للجهاز الرئيسي تحديد الجهاز الفرعي الذي سيقوم بالاتصال به.


الهاردوير

كل ما نحتاج اليه في هذا البروتوكول هما خطان:

1. SCL - Serial Clock

وهي تنقل إشارة الساعة والتي تنظم وتجدول نقل البيانات بين الأجهزة ويتم توليدها من الجهاز الرئيسي. وبما أن لدينا خط مستقل للساعة فذلك يجعل الـ I2C بروتوكول نقل متزامن synchronous.

2. SDA - Serial Data

هذا الخط مسؤول عن نقل البيانات.

في هذا البروتوكول يتم توصيل جميع الأجهزة الفرعية بشكل متوازي بالخطوط السابقة. فلو كان لديك أكثر من جهاز فرعي slave متصل بجهاز رئيسي واحد master فكل ما عليك فعله هو ربط جميع خطوط الـ SDA معاً وجميع خطوط الـ SCL معاً مما يجعلنا نحتاج الى إستخدام طرفين pins فقط لكل جهاز متصل، وهذه ميزة تحسب لصالح هذا البروتوكول.

الخطان من نوع “تصريف مفتوح” open drain مما يعني أنه يجب إيصالهما بمقاومات لأعلى pull up resistors بحيث تكون الخطوط مرتفعة high، وهي الحالة الغير نشطة idle للناقل bus بينما عندما تكون الأجهزة على هذا الناقل نشطة تكون الخطوط منخفضة low. وللإيضاح أكثر، فإن الخطوط من هذا النوع تكون الأطراف المتصله بها غير متصلة بشكل مباشر بمصدر الطاقة VCC وإنما تتصل مباشرة بـ GND.


البروتوكول

بالإمكان الحصول على التفاصيل الكاملة لبروتوكول الـ I2C من الرابط التالي:

https://www.nxp.com/docs/en/user-guide/UM10204.pdf

ونذكر هنا سريعاً بعض الخصائص المتعلقة بهذا البروتوكول:


هيكل البيانات المرسلة والمستقبلة

كنظرة عامة لتنسيق البيانات المرسله على الناقل فمن الممكن القول بأنها تأخذ الشكل التالي:

  1. بت للبدء Start
  2. عنوان الجهاز الفرعي الذي سيتم إرسال البيانات إليه Address
  3. بت التأكيد ACK
  4. بعد ذلك لدينا سلسلة من البيانات على حسب إحتياجنا الى إرسالها أو أستقبالها
  5. بعد كل سلسلة مكونة من 8 بت يأتي بت التأكيد ACK
  6. بت للتوقف Stop

وفيما يلي سنقوم بذكر تفاصيل الأجزاء المختلفة لهذا البروتوكول:

بت البدء Start

في البداية يكون كل من خطي SCL و SDA في الوضع المرتفع high بسبب مقاومات السحب لأعلى pull-up resistors، وهذا يعني أنه لا يوجد هنالك أي نشاط في الناقل bus. ومن أجل الإشارة إلى بدء عملية نقل البيانات، سيقوم الجهاز الرئيسي بخفض خط SDA بينما يكون خط SCL عالي. وتعتبر هذه إشارة البدء START حيث يمكن إعتبارها تنبيهاً لجميع الأجهزة المتصلة بالناقل حيث أنها تصبح مستعدة للاستماع للجهاز الرئيسي.

العنوان Address

بعد بت البدء START، يقوم الجهاز الرئيسي بإرسال عنوان Address الجهاز الفرعي slave الذي يود التواصل معه وذلك عن طريق إرسال البت الأعلى most significant bit أولاً وإنتهاءاً بالبت الأدنى least significant bit. ويتكون العنوان من سبعة بتات لأن البت الثامنة هي بت التحكم بالإرسال والإستقبال R/S وتستخدم للإشارة الى ما اذا كان الجهاز الرئيسي سيقوم بالكتابة الى الجهاز الفرعي (0) أم القراءة منه (1) أي هل على الجهاز الفرعي الإستعداد لإستقبال بيانات أم هل هو طلب بيانات معينة من هذا الجهاز الفرعي .

بعد استلام العنوان، ستقوم جميع الأجهزة الفرعية بمقارنة العنوان المستلم من الجهاز الرئيسي بعنوانها الخاص فإذا تطابقت أستمرت في التواصل وإذا لم تتطابق فإنها تستمر في الانتظار حتى يصبح الناقل bus غير نشط وذلك بعد إرسال بت التوقف.

التأكيد ACK

سوف يستجيب الجهاز الفرعي الذي يتطابق معه العنوان بإرسال إشارة تأكيد ACK للإشارة الى أنه قد استلم بنجاح التسلسل السابق للبتات، وبذلك يقوم الجهاز الرئيسي بتحويل مسؤولية خط البيانات SDA الى الجهاز الفرعي.

وإذا ما كان الجهاز الفرعي قد تلقى التسلسل السابق بنجاح فإنه سيقوم بخفض خط SDL وهذا هو التأكيد ACK. أما اذا لم يقم الجهاز الفرعي بذلك فهذه الحالة الغير مؤكدة NACK وتعني بأن الجهاز الفرعي لم يستلم البتات السابقة بشكل صحيح لأي سبب كان.

إرسال/إستقبال البيانات

بعد تلقي إشارة ACK سيقوم الجهاز الرئيسي إما بنقل أو إستقبال البيانات حسب إشارة التحكم R/S المرسلة بعد عنوان الجهاز الفرعي. في حالة الإرسال، سيقوم الجهاز الرئيسي بإرسال البيانات بايت واحد في كل مرة ويستجيب الجهاز الفرعي بإرسال إشارة ACK بعد استلام كل بايت من البيانات. وعندما ينتهي الجهاز الرئيسي من إرسال جميع البيانات سيقوم بإرسال إشارة التوقف STOP.

أما في حالة أن الجهاز الرئيسي سيستقبل بيانات، فإن الجهاز الفرعي سيقوم بإرسال بايت البيانات الأول بعد إرسال إشارة ACK وسيستجيب الجهاز الرئيسي بإرسال إشارة ACK بعد استلام كل بايت من البيانات.

التوقف Stop

بمجرد أن يتلقى الجهاز الرئيسي جميع البيانات فإنه سيرسل إشارة NACK متبوعة بإشارة STOP لإخلاء الناقل لأي عملية نقل بيانات أخرى. وتحدث إشارة التوقف عندما ينتقل خط SDA من الوضع المنخفض 0 الى الوضع المرتفع 1 بينما يكون خط الـ SCL مرتفع .


تهيئة وضبط إعدادات الـ SPI

توضح صفحة 1015 في دليل البيانات طريقة تهيئة الـ I2C وكيف يمكننا تهيئتها للعمل ضمن حالات معينة.

سنبدأ أولاً بشرح عملية التهيئة، ثم سنشرح كيفية إعداد الـ I2C لإرسال وإستقبال بايت واحد من البيانات كجهاز رئيسي.

أود الإشارة الى أننا في هذه الأمثلة لن نكتب عناوين السجلات بأنفسنا ولكننا سنستخدم تعريفات السجلات الموجودة في ملف التعريفات كما يلي:

#include "C:\ti\TivaWare_C_Series-2.1.4.178\inc\tm4c123gh6pm.h"

وفيما يلي الخطوات المتبعة لتهيئة الـ I2C:

1. قم بتمكين ساعة الـ I2C باستخدام سجل RCGCI2C في وحدة التحكم في النظام (انظر صفحة 348).

سنستخدم في الأمثلة التالية I2C0 ولذلك سنقوم بكتابة 1 في الحقل R0 (بت 0)

SYSCTL_RCGCI2C_R = (1<<0); // activate I2C0

2. قم بتمكين الساعة في وحدة GPIO المناسبة عبر سجل RCGCGPIO في وحدة التحكم في النظام (انظر صفحة 340). لمعرفة أي منفذ GPIO لتمكينه، راجع الجدول 5-23 في الصفحة 1351.

بإمكاننا أن نرى في الجدول 5-23 بأن وحدة I2C0 موجودة في المنفذ B

ولذلك، لتفعيل المنفذ B نكتب 1 في حقل R1 (بت 1) في السجل RCGCGPIO:

SYSCTL_RCGCGPIO_R = (1<<1); // activate port B

وقبل أن نواصل، سنتأكد من ان المنفذ جاهز للعمل وذلك عن طريق قراءة القيمة الموجودة في الحقل R1 (بت 1) والتأكد من أنها تساوي 1.

while ((SYSCTL_PRGPIO_R & (1<<1)) == 0) {}; // is port B ready?

3. في وحدة GPIO ، قم بتمكين الأطراف المناسبة للوظيفة البديلة باستخدام سجل GPIOAFSEL (انظر صفحة 671). لتحديد أي GPIOs لتهيئتها، راجع جدول 4-23 في الصفحة 1344.

لقد ذكرنا سابقاً بأننا سنتعامل مع المنفذ B. وفي الجدول 5-23:

يتضح لنا بأننا سنستخدم الطرف PB2 لـ SCL والطرف PB3 لـ SDA. ولذلك يتوجب علينا تفعيل الوظائف البديلة لهذه الأطراف:

GPIO_PORTB_AFSEL_R = (1<<2)|(1<<3); // enable alt funct on PB2,3

4. تمكين طرف I2CSDA لتفعيل خاصية الصرف (الإستنزاف) المفتوح open-drain. انظر الصفحة 676.

يمكّن هذا السجل خاصية الـ open-drain للأطراف المستخدمة في بروتوكول I2C. وفي بورد TI Tiva الذي نستخدمه، يتم تعيين المنفذ I2CSDA فقط كـ open-drain. وسوف يصبح المنفذ I2CSCL كذلك open-drain مع I2CSDA. أما تعيين I2CSCL كـ open-drain مباشرة لن يعمل.

وعلى ذلك سيتم تمكين خاصية open-drain لـ SDA فقط وذلك للطرف PB3.

GPIO_PORTB_ODR_R = (1<<3); // enable open drain on PB3 only

عند تفعيل خاصية الـ open-drain فإنه يتوجب علينا كتابة 1 في البت المقابل للطرف المستخدم وذلك في السجل GPIODEN:

GPIO_PORTB_DEN_R = (1<<2)|(1<<3); // enable as digital signals

5. قم بإعداد حقول PMCn في سجل GPIOPCTL لتعيين إشارات I2C إلى الأطراف المناسبة. انظر الى الصفحة 688 والجدول 5-23 في الصفحة 1351.

بمجرد تمكيننا للوظائف البديلة، يتعين علينا عندئذ اختيار الوظيفة البديلة المحددة التي نريدها. في الجدول 5-23 في صفحة 1351 في دليل البيانات يمكننا رؤية أن I2C0 يقع في العمود 3:

في هذا السجل - GPIOPCTL - كل 4 بت تمثل طرف، فالحقل PMC0 (بت0-3) يمثل الطرف 0، والحقل PMC1 (بت4-7) يمثل الطرف 1، وهكذا.

وكما ذكرنا سابقاً، بما أن I2C0 تقع في العمود رقم 3 فإننا نضع القيمة 3 في الحقول التي تمثل الأطراف التى نود تحويلها الى I2C:

GPIO_PORTB_PCTL_R = 0x00003300; // configure PB2,3 as I2C

كما أننا سنقوم بتعطيل الوظائف التماثلية analog للأطراف PB2 و PB3:

GPIO_PORTB_AMSEL_R &= ~((1<<2)|(1<<3));  // disable analog functionality on PB2,3

6. قم بتهيئة الجهاز الرئيسي في I2C وذلك بكتابة القيمة 0x0000.0010 الى سجل I2CMCR.

نستخدم السجل I2CMCR لتحديد ما إذا كان الجهاز الذي نقوم ببرمجته هو الجهاز الرئيسي أم فرعي.

إسناد القيمة 1 الى الحقل MFE (بت 4) يجعل الجهاز الحالي هو الجهاز الرئيسي.

I2C0_MCR_R = (1<<4); // master function enable

7. حدد سرعة ساعة SCL المطلوبة بـ 100 كيلوبت في الثانية عن طريق كتابة القيمة المطلوبة الى سجل I2CMTPR. تمثل القيمة المكتوبة الى سجل I2CMTPR عدد فترات ساعة النظام في فترة ساعة SCL واحدة. يتم تحديد قيمة TPR بالمعادلة التالية:

TPR = (System Clock / (2 * (SCL_LP + SCL_HP) * SCL_CLK)) - 1؛

يمكن لوحدة الـ I2C العمل بأحد السرعات التالية:

يمكن تحديد السرعة المطلوبة بالكتابة الى الحقل TPR (بت 6:0) في السجل I2CMTPR

ويتم حساب قيمة TPR على النحو التالي:

TPR = (System Clock / (2 * (SCL_LP + SCL_HP) * SCL_CLK)) - 1

حيث أن:

وعلى ذلك نستطيع أن نستنتج قيمة TPR:

TPR = (16000000 / (2 * (6 + 4) * 100000)) - 1
TPR = (16000000 / (2 * 10 * 100000)) - 1
TPR = (16000000 / (2 * 10 * 100000)) - 1
TPR = (16000000 / 2000000) - 1
TPR = 8 - 1
TPR = 7
I2C0_MTPR_R = (7<<0); // configure for 100 kbps clock

وفيما يلي جميع الخطوات المتبعة لتهيئة وحدة الـ I2C:

void i20_init (void) {
  // 1. Enable the I2C clock using the RCGCI2C register in the System Control module (see page 348).
  SYSCTL_RCGCI2C_R = (1<<0); // activate I2C0
  
  // 2. Enable the clock to the appropriate GPIO module via the RCGCGPIO register in the System 
  //    Control module (see page 340). To find out which GPIO port to enable, refer to Table 23-5 on page 1351.
  SYSCTL_RCGCGPIO_R = (1<<1); // activate port B
  
  while ((SYSCTL_PRGPIO_R & (1<<1)) == 0) {}; // is port B ready?

  // 3. In the GPIO module, enable the appropriate pins for their alternate function using the 
  //    GPIOAFSEL register (see page 671). To determine which GPIOs to configure, 
  //    see Table 23-4 on page 1344.
  GPIO_PORTB_AFSEL_R = (1<<2)|(1<<3); // enable alt funct on PB2,3
  
  // 4. Enable the I2CSDA pin for open-drain operation. See page 676.
  GPIO_PORTB_ODR_R = (1<<3); // enable open drain on PB3 only
  
  GPIO_PORTB_DEN_R = (1<<2)|(1<<3); // enable digital I/O on PB2,3
  
  // 5. Configure the PMCn fields in the GPIOPCTL register to assign the I2C signals to the appropriate 
  //    pins. See page 688 and Table 23-5 on page 1351.                                        
  GPIO_PORTB_PCTL_R = 0x00003300; // configure PB2,3 as I2C
  
  GPIO_PORTB_AMSEL_R &= ~((1<<2)|(1<<3));  // disable analog functionality on PB2,3
  
  // 6. Initialize the I2C Master by writing the I2CMCR register with a value of 0x0000.0010.
  I2C0_MCR_R = (1<<4); // master function enable
  
  // 7. Set the desired SCL clock speed of 100 Kbps by writing the I2CMTPR register with the correct 
  //    value. The value written to the I2CMTPR register represents the number of system clock periods 
  //    in one SCL clock period. The TPR value is determined by the following equation:
  //
  //    TPR = (System Clock/(2*(SCL_LP + SCL_HP)*SCL_CLK))-1;
  //    TPR = (16MHz/(2*(6+4)*100000))-1;
  //    TPR = 7
  //
  //    Write the I2CMTPR register with the value of 0x0000.0007.
  I2C0_MTPR_R = (7<<0); // configure for 100 kbps clock
}

الإرسال

يوضح المثال التالي كيفية إرسال الجهاز الرئيسي لبايت واحد في الـ I2C.

سنستخدم الصورة التوضيحية 8-16 في صفحة 1008 من دليل البيانات لإرشادنا. علماً أننا لن نطبق التسلسل الموجود في المربع.

8. حدد عنوان الجهاز الفرعي وبأن العملية التالية هي إرسال عن طريق الكتابة الى سجل I2CMSA.

يجب علينا تحديد عنوان الجهاز الفرعي الذي نود الإرسال اليه عن طريق كتابة العنوان الى الحقل SA (بتات 7:1) في السجل I2CMSA.

I2C0_MSA_R = (slave << 1); // SA[7:1] is slave address

لاحظ أن العناوين في I2C مكونة من 7 بت فقط (بحد أقصى 127 جهازًا).

ويتم استخدام حقل R/S (بت 0) في نفس السجل للإشارة إلى ما إذا كنا سنرسل الى الجهاز الفرعي أم نستقبل منه.

وبما أننا سنرسل بيانات في هذا المثال فسنقوم بكتابة 0 الى الحقل R/S.

I2C0_MSA_R &= ~(1<<0); // R/S[0] is 0 for send

9. وضع البيانات (البايت الذي تود إرساله) في سجل البيانات I2CMDR.

هذا السجل مسؤول عن كتابة البيانات الى خط SDA أو قراءة البيانات منه.

وفيما يلي سنقوم بكتابة بيانات بحجم 1 بايت الى حقل DATA (بت 7:0) في السجل I2CMDR.

I2C0_MDR_R = data; // place byte for transmission

10. بدء إرسال بايت واحد من البيانات من الجهاز الرئيسي إلى الفرعي وذلك بكتابة القيمة 0x0000.0007 الى سجل I2CMCS.

لسجل I2CMCS حالتان، أحدهما عندما نكتب الى هذا السجل والأخرى حينما نقرأ منه. ولذلك له وظيفتان مزدوجه.

فعندما نكتب اليه، فإنه يعمل كسجل متحكم control register وعندما نقرأ منه فإنه يعمل كسجل للحاله status register.

سنقوم بكتابة القيمة 0x07 الى السجل I2CMCS وذلك لتبدأ وحدة الـ I2C بنقل بايت واحد من الجهاز الرئيسي الى الفرعي.

I2C0_MCS_R = 0x07;

والسبب في أننا نكتب هذه القيمة بالذات هو لأننا سنضع 1 في حقل RUN (بت 0) و 1 في حقل START (بت 1) و 1 في حقل STOP (بت 2) مما يعطينا الرقم الثنائي 1110 أي 0x07.

I2C0_MCS_R = ( (1<<2)     // generate stop
             | (1<<1)     // generate start/restart
             | (1<<0) );  // master enable

ولأننا جعلنا كل من START و RUN و STOP تساوي 1 فذلك سيجعل وحدة الـ I2C ترسل إشارة البدء Start ثم عنوان الجهاز الفرعي Slave Address ثم بت التحكم بالإرسال والإستقبال R/S ومن ثم ستتأكد من إستقبالها لبت التأكيد ACK ثم ستقوم بإرسال البيانات Data الموجوده في سجل البيانات وبعدها تتأكد مرة أخرى من بت التأكيد ACK وبعدها سترسل إشارة التوقف Stop لإنهاء عملية الإرسال.

11. انتظر حتى اكتمال عملية الإرسال عن طريق التأكد من أن بت BUSBSY في السجل I2CMS يساوي 0.

لمتابعة عملية الإرسال والتأكد من أنها تمت يجب علينا مراقبة الحقل BUSBSY (بت 6) في السجل I2CMS الى أن يساوي 0 مما يعني أن عملية الإرسال قد أنتهت.

while (I2C0_MCS_R & (1<<6)) {}; // wait for transmission done

12. تحقق من بت ERROR في سجل I2CMCS للتأكد من أنه تم الإرسال بشكل صحيح.

عند اكتمال الإرسال ، يجب أن يتحقق البرنامج من بت الخطأ ERROR (بت 1) في سجل I2CMCS للتأكد من عدم وجود خطأ في الإرسال.

return I2C0_MCS_R & (1<<1); //check for error

وفيما يلي الدالة الكاملة لإرسال الجهاز الرئيسي لبايت واحد:

uint32_t i2c0_send_1_byte(int8_t slave, uint8_t data){
  while (I2C0_MCS_R & (1<<0)) {}; // wait for I2C ready
  
  // 8. Specify the slave address of the master and that the next operation is a Transmit by writing the 
  //    I2CMSA register with a value of 0x0000.0076. This sets the slave address to 0x3B.  
  I2C0_MSA_R = (slave << 1); // SA[7:1] is slave address
  I2C0_MSA_R &= ~(1<<0); // R/S[0] is 0 for send
  
  // 9. Place data (byte) to be transmitted in the data register by writing the I2CMDR register with the 
  //    desired data.
  I2C0_MDR_R = data; // place byte for transmission
  
  // 10. Initiate a single byte transmit of the data from Master to Slave by writing the I2CMCS register 
  //     with a value of 0x0000.0007 (STOP, START, RUN).
  I2C0_MCS_R = ( (1<<2)     // generate stop
               | (1<<1)     // generate start/restart
               | (1<<0) );  // master enable
					   	
  // 11. Wait until the transmission completes by polling the I2CMCS register's BUSBSY bit until it has 
  //     been cleared.
  while (I2C0_MCS_R & (1<<6)) {}; // wait for transmission done
  
  return I2C0_MCS_R & (1<<1); //check for error
}

الإستقبال

يوضح المثال التالي كيفية تلقي الجهاز الرئيسي لبايت واحد في الـ I2C.

سنستخدم الصورة التوضيحية 9-16 في صفحة 1009 من دليل البيانات لإرشادنا. علماً أننا لن نطبق التسلسل الموجود في المربع.

الطريقة مشابهه الى حد كبير لما قمنا به عند إرسال بايت واحد. ولكننا سنمر عليها سريعاً هنا:

1. تكرار محاولة القراءة طالما وجد خطأ في إستقبال البيانات ولم نجتاز العدد المحدد من المحاولات.

  do{
          // code
  }                                         
  while(((I2C0_MCS_R & (1<<1)) != 0) && (retry_counter <= max_retries));

2. الإنتظار الى أن تصبح وحدة الـ I2C جاهزة.

while (I2C0_MCS_R & (1<<0)) {}; // wait for I2C ready

3. كتابة عنوان الجهاز الفرعي.

I2C0_MSA_R = (slave << 1); // SA[7:1] is slave address

4. الإشارة الى أننا نطلب إستقبال بيانات.

I2C0_MSA_R |= (1<<0);  // R/S[0] is 1 for receive

5. إرسال المعلومات السابقة.

I2C0_MCS_R = ( (1<<2)     // generate stop
             | (1<<1)     // generate start/restart
             | (1<<0) );  // master enable

6. التأكد من إكتمال عملية الإرسال.

while (I2C0_MCS_R & (1<<6)) {}; // wait for transmission done

7. قراءة البايت المستقبل.

return (I2C0_MDR_R & 0xFF);

وفيما يلي الدالة الكاملة لإستقبال الجهاز الرئيسي لبايت واحد:

uint8_t i2c0_recv_1_byte(int8_t slave, int max_retries){
  int retry_counter = 1;
  do{
    while (I2C0_MCS_R & (1<<0)) {}; // wait for I2C ready
    
	I2C0_MSA_R = (slave << 1); // SA[7:1] is slave address
    I2C0_MSA_R |= (1<<0);      // R/S[0] is 1 for receive
    
	I2C0_MCS_R = ( (1<<2)     // generate stop
                 | (1<<1)     // generate start/restart
                 | (1<<0) );  // master enable
    
	while (I2C0_MCS_R & (1<<6)) {}; // wait for transmission done
    
	++retry_counter;  // increment retry counter
  }  // repeat if error
  while(((I2C0_MCS_R & (1<<1)) != 0) && (retry_counter <= max_retries));
  
  return (I2C0_MDR_R & 0xFF);
}

ملاحظة: هذه المقالة تحت التحديث المستمر،، وملاحظاتكم وإقتراحاتكم ستساعد بإذن الله في إخراجها بالصورة التي كتبت من أجلها وهي تمهيد الطريق أمامكم لتعلم برمجة الأنظمة المدمجة