بررسی
امروزه حتی ساده ترین PCB علاوه بر میکروکنترلر اصلی شامل دو یا چند مدار مجتمع دیجیتالی (IC)، است که برای کارهای خاصی انتخاب شده اند. ADC و DAC، حافظههای EEPROM، سنسورها، پورتهای I/O ، کلاکهای RTC، مدارهای RF و کنترلکنندههای LCD ا، فقط فهرست کوچکی از آیسیهای ممکن برای انجام یک کار مشخص هستند. طراحی مدرن الکترونیک دیجیتال در مورد انتخاب (و برنامه نویسی) صحیح آی سی های قدرتمند، خاص و در بیشتر مواقع ارزان برای استفادهروی PCB نهایی است.
بر اساس ویژگی این آیسیها، اغلب برای تبادل پیام و داده با یک دستگاه قابل برنامهریزی (که معمولاً یک میکروکنترلر است، اما نه فقط آن) طبق یک پروتکل ارتباطی تعریف شده طراحی میشوند. دو مورد از پرکاربردترین پروتکلها برای ارتباطات درونبردی I²C و SPI می باشد که هر دو به اوایل دهه 80 بازمیگردند، اما هنوز در صنعت الکترونیک رایج هستند، بهخصوص زمانی که سرعت ارتباط اهمیت زیادی ندارد و ارتباطات محدود به مرزهای PCB است.
تقریباً همه میکروکنترلرهای STM32 سخت افزارهای جانبی خاصی را ارائه می دهند که می توانند با استفاده از پروتکل های I²C و SPI ارتباط برقرار کنند.در این فصل به طور خلاصه پروتکل I²C و APIهای CubeHAL مربوط به برنامه ریزی این ابزار جانبی را معرفی می کنیم. اگر علاقه مند به مطالعه عمیق تر پروتکل I²C هستید،فایل UM10204 توسط NXP² اطلاعات کامل و به روزتری را ارائه می دهد.
Introduction to the I²C specification
مدار Inter-Integrated Circuit (معروف به I²C – تلفظ I-squared-C یا به ندرت I-two-C) یک مشخصه سخت افزاری و پروتکلی است که توسط بخش نیمه هادی فیلیپس (در حال حاضر NXP Semiconductors³) در سال 1982 توسعه یافته است.این پروتکل – multi-slave ، half-duplex ، مشخصه گذرگاه سریال 8 بیتی single-ended و تنها از دو سیم برای اتصال تعداد معینی از دستگاه های slave به یک Master استفاده می کند. تا اکتبر 2006، توسعه دستگاههای مبتنی بر I²C منوط به پرداخت حق امتیاز به فیلیپس بود، اما این محدودیت در حال حاضر حذف شده است.
![](https://gebrabit.com/wp-content/themes/woodmart/images/lazy.png)
دو سیمی که گذرگاه I²C را تشکیل می دهند، خطوط open-drain دو طرفه هستند که به ترتیب خط داده سریال Serial Data Line (SDA) و خط کلاک سریال Clock Line (SCL) نامیده می شوند (شکل 1 را ببینید). پروتکل I²C مشخص می کند که این دو خط باید با مقاومت pull up شوند. اندازه این مقاومت ها به طور مستقیم با ظرفیت خازنی و سرعت انتقال ارتباط دارد. استفاده از مقاومت هایی با مقدار نزدیک به 4.7KΩ بسیار رایج است.
میکروکنترلرهای مدرن، مانند میکروکنترلرهای STM32، اجازه میدهند تا خطوط GPIO را بهعنوان pull-up open-drain پیکربندی کنیم و مقاومتهای pull-up داخلی را فعال کنیم. با جست و جو در وب متوجه می شوید که استفاده از مقاومتهای pull-up داخلی برای خطوط I²C بسیار رایج است. با این حال، در تمام میکروکنترلرهای STM32، مقاومتهای pull-up داخلی مقداری نزدیک به 20KΩ دارند تا از نشت ناخواسته ولتاژ جلوگیری شود. چنین مقداری زمان مورد نیاز باس برای رسیدن به حالت HIGH را افزایش و سرعت انتقال را کاهش می دهد. اگر سرعت در برنامه شما مهم نیست و اگر (بسیار مهم) از مسیرهای طولانی بین MCU و آی سی (کمتر از 2 سانتی متر) استفاده نمی کنید، استفاده از مقاومت های pull-up داخلی برای بسیاری از برنامه ها اشکالی ندارد. اما، اگر فضای کافی روی PCB برای قرار دادن چند مقاومت دارید، اکیداً پیشنهاد می شود از مقاومت های خارجی و اختصاصی استفاده کنید.
میکروکنترلرهای STM32F1 توانایی pull-up خطوط SDA و SCL را ندارند. GPIO آنها باید به صورت open-drain پیکربندی شوند، و دو مقاومت خارجی برای pull-up خطوط I²C مورد نیاز است.
برای پروتکلی که فقط با دو سیم کار میکند، باید راهی برای آدرس دهی به یک دستگاه slave منحصربه فرد در همان باس وجود داشته باشد. به همین دلیل ، هر دستگاه slave یک slave address منحصر به فرد در buS مربوطه ارائه می دهد. این آدرس ممکن است 7 یا 10 بیتی باشد (10 بیتی عادی نیست).
حتی اگر تراشههایی باشند که بتوانند با سرعتهای سفارشی (و اغلب مبهم) کار کنند سرعت گذرگاه I²C به خوبی توسط مشخصات پروتکل تعریف شده است. سرعت های رایج باس I²C کیلوهرتز 100 ( حالت استاندارد standard mode ) و 400 کیلوهرتز ( حالت سریع fast mode ) هستند.در ویرایشهای اخیر استاندارد ،پروتکل I²C میتواند با سرعتهای بالاتری اجرا شود (1 مگاهرتز، معروف به حالت fast mode plus ، و 3.4 مگاهرتز، معروف به حالت high speed mode ، و 5 مگاهرتز، معروف به حالت ultra fast mode).
رابط I²C یک پروتکل ساده است به طوری که اگر میکروکنترلر آن را نداشته باشد می توان یک واحد جانبی اختصاصی I²C را «شبیه سازی» کرد، این تکنیک bit-banging نامیده می شود و معمولاً در معماری های 8 بیتی ارزان که رابط I²C اختصاصی را برای کاهش تعداد پین و/یا هزینه IC ارائه نمی کنند ، استفاده می شود.
The I²C Protocol
در پروتکل I²C همه تراکنش ها همیشه توسط Master شروع و تکمیل می شوند. این یکی از معدود قوانین پروتکل ارتباطی I²C است که باید هنگام برنامهنویسی ( مخصوصاً اشکالزدایی debugging) دستگاههای I²C به خاطر داشت. همه پیامهای مبادله شده از طریق گذرگاه I²C به دو نوع فریم تقسیم میشوند: یک فریم آدرس address frame ، که در آن master نشان میدهد که پیام به کدام slave ارسال میشود، و یک یا چند فریم داده data frames ، که پیامهای داده ی 8 بیتی هستند که از master به slave یا برعکس ارسال میشوند. داده ها پس از low شدن SCL روی خط SDA قرار می گیرند و پس از high شدن خط SCL نمونه برداری می شوند. زمان بین لبههای کلاک و خواندن/نوشتن دادهها، توسط دستگاههای روی گذرگاه تعریف میشود و از تراشه به تراشه دیگر متفاوت است.
همانطور که قبلاً گفته شد، هم SDA و هم SCL خطوط دو طرفه هستند که از طریق یک منبع جریان یا مقاومت های pull-up به مثبت ولتاژ تغذیه متصل می شوند (شکل 1 را ببینید). وقتی گذرگاه آزاد است، هر دو خط HIGH هستند. بخش های خروجی دستگاه های متصل به باس باید open-drain یا open-collector برای انجام عمل wired-AND باشد. ظرفیت خازنی باس تعداد رابط های متصل به باس را محدود می کند. در کاربردهایی که فقط یک master وجود دارد، اگر دستگاهی که کلاک را کش دهد در باس وجود نداشته باشد خروجی SCL Master می تواند یک درایور push-pull باشد (در ادامه در مورد این موضوع بیشتر توضیح خواهیم داد).
اکنون ما قصد داریم مراحل اساسی ارتباط I²C را تجزیه و تحلیل کنیم.
![](https://gebrabit.com/wp-content/themes/woodmart/images/lazy.png)
START and STOP Condition
همه تراکنش ها با یک START شروع و با یک STOP خاتمه می یابند (شکل 2 را ببینید). یک انتقال HIGH به LOW در خط SDA در حالی که SCL HIGH است یک شرایط START را تعریف می کند. انتقال LOW به HIGH در خط SDA در حالی که SCL HIGH است یک شرایط STOP را تعریف می کند.
شرایط START و STOP همیشه توسط Master ایجاد می شود. گذرگاه پس از شرط START مشغول در نظر گرفته می شود. گذرگاه در زمان مشخصی پس از شرط STOP دوباره آزاد در نظر گرفته می شود. اگر یک START تکراری (که شرط RESTART نیز نامیده می شود) به جای شرط STOP ایجاد شود، گذرگاه مشغول می ماند (به زودی در این مورد بیشتر توضیح خواهیم داد). در مورد شرایط START و RESTART ،از نظر عملکردی یکسان هستند.
Byte Format
هر داده ای که در خط SDA ارسال می شود باید هشت بیت طول داشته باشد و این موضوع شامل فریم آدرس address frame نیز می شود که مدتی دیگر خواهیم دید. تعداد بایت های قابل انتقال نامحدود است. هر بایت باید توسط یک بیت تصدیق Acknowledge (ACK) دنبال شود. داده ها ابتدا با مهم ترین بیت Most Significant Bit (MSB) منتقل می شوند (شکل 2 را ببینید). اگر Slave به دلیل انجام تسک دیگری ، به عنوان مثال سرویس دهی به یک وقفه داخلی ، نتواند بایت کامل داده را دریافت یا ارسال کند، میتواند با LOW نگه داشتن خط کلاک SCL ، Master را در حالت انتظار قرار دهد. زمانی که Slave برای بایت دیگری از داده آماده شد ، خط کلاک SCL را آزاد می کند و انتقال داده ادامه می یابد.
Address Frame
فریم آدرس address frame همیشه در هر دنباله ارتباطی جدید، اول است. برای یک آدرس 7 بیتی، ابتدا مهم ترین بیت (MSB) آن و سپس بیت R/W که نشان می دهد آیا عملیات ، خواندن (1) یا نوشتن (0) است ، کلاک می شود (شکل 2 را ببینید).
![](https://gebrabit.com/wp-content/themes/woodmart/images/lazy.png)
در سیستم آدرس دهی 10 بیتی (شکل 3 را ببینید)، دو فریم برای انتقال آدرس Slave مورد نیاز است. فریم اول شامل کد 0XXD 1111 ، که در آن XX دو بیت MSB آدرس Slave 10 بیتی است و D بیت R/W ، می باشد (همانطور که در بالا می بینید). اولین بیت ACK فریم توسط همه Slave هایی که با دو بیت اول آدرس مطابقت دارند، مشخص می شود. همانند یک انتقال معمولی 7 بیتی، انتقال دیگر بلافاصله شروع می شود و این انتقال حاوی بیت های [7:0] آدرس است. در این مرحله، Slave آدرس شده باید با بیت ACK پاسخ دهد. اگر این کار را نکرد، حالت failure mode مانند سیستم 7 بیتی است.
توجه داشته باشید دستگاههای با آدرس ده بیت میتوانند با دستگاههایی که ۷ بیت آدرس دارند در کنار هم کار کنند، زیرا قسمت اصلی ۱۱۱۱۰ آدرس، بخشی از هیچ آدرس ۷ بیتی معتبری نیست.
Acknowledge (ACK) and Not Acknowledge (NACK)
ACK بعد از هر بایت انجام می شود. بیت ACK به گیرنده receiver این امکان را داده که به فرستنده سیگنال دهد که بایت با موفقیت دریافت شد و ممکن است بایت دیگری ارسال شود. Master تمام پالس های کلاک از جمله نهمین پالس کلاک ، ACK را روی خط SCL تولید می کند.
سیگنال ACK به صورت زیر تعریف می شود:
فرستنده خط SDA را در طول پالس کلاک acknowledge رها می کند تا گیرنده بتواند خط SDA را LOW کند و در طول دوره HIGH پالس کلاک، LOW ثابت می ماند. هنگامی که SDA در طول نهمین پالس کلاک HIGH باقی می ماند، به عنوان سیگنال Not Acknowledge (NACK) تعریف می شود. سپس Master می تواند شرایط STOP برای لغو انتقال یا شرایط RESTART را برای شروع انتقال جدید ایجاد کند. پنج شرط منجر به تولید NACK می شود :
- 1. هیچ گیرنده receiver ای در گذرگاه با آدرس ارسالی نیست، بنابراین دستگاهی برای پاسخگویی acknowledge وجود ندارد.
- 2. گیرنده receiver قادر به دریافت یا ارسال نیست زیرا در حال انجام برخی از کارهای بلادرنگ real-time است و آماده شروع ارتباط با Master نیست.
- 3. در حین انتقال، گیرنده receiver داده یا دستوراتی دریافت می کند که متوجه نمی شود.
- 4. در حین انتقال، گیرنده receiver نمی تواند بایت داده بیشتری دریافت کند.
- 5. یک master-receiver باید پایان انتقال را به فرستنده slave سیگنال دهد.
اثربخشی بیت ACK/NACK به دلیل ماهیت open-drain بودن پروتکل I²C است. Open-drain به این معنی است که هم master و هم slave در تراکنش می توانند خط سیگنال مربوطه را LOW نگه دارند، اما نمی توانند آن را HIGH کنند. اگر یکی از فرستنده و گیرنده خطی را آزاد کند، اگر دیگری آن را LOW نکند، به طور خودکار توسط مقاومت pull-up مربوطه HIGH می شود. همچنین ماهیت open-drain بودن پروتکل I²C تضمین می کند در جایی که یک دستگاه سعی به HIGH کردن خط سیگنال دارد در حالی که دستگاه دیگر می خواهد آن را همزمان LOW کند، احتمال آسیب به درایورها یا اتلاف بیش از حد توان در سیستم از بین می رود و اختلالی در باس بوجود نمی آید .
Data Frames
پس از ارسال فریم آدرس address frame ، داده ها می توانند شروع به انتقال کنند. Master به سادگی تولید پالس های کلاک خط SCL در یک بازه زمانی منظم را ادامه می دهد و بسته به اینکه بیت R/W عملیات خواندن یا نوشتن را نشان می دهد، داده ها توسط master یا slave بر روی SDA قرار می گیرد. معمولاً اولین یا دو بایت اول حاوی آدرس رجیستر slave برای نوشتن/خواندن است. برای مثال، برای I²C EEPROM، دو بایت اول بعد از فریم آدرس address frame ، نشان دهنده آدرس محل حافظه مورد نظر در تراکنش است.
بر اساس بیت R/W، بایت های متوالی توسط master (اگر بیت R/W روی 1 تنظیم شده باشد) یا Slave (اگر بیت R/W 0 باشد) پر می شود. تعداد فریم های داده دلخواه است و اکثر دستگاه های Slave رجیستر داخلی را به صورت خودکار افزایش می دهند. به این معنی که خواندن یا نوشتن بعدی از رجیستر بعدی پشت سرهم خواهد آمد. به این حالت حالت متوالی sequential یا انفجاری burst نیز می گویند (شکل 4 را ببینید) که راهی برای افزایش سرعت انتقال است.
![](https://gebrabit.com/wp-content/themes/woodmart/images/lazy.png)
Combined Transactions
پروتکل I²C اساسا یک الگوی ارتباطی ساده دارد:
- Master آدرس دستگاه Slave درگیر تراکنش را در گذرگاه ارسال می کند.
- بیت R/W که بیت LSB در بایت آدرس slave است، جهت جریان داده را تعیین می کند (from master to slave – W – or from slave to master – R)
- تعدادی بایت ارسال می شود که هر کدام با یک بیت ACK توسط یکی از دو همتا بر اساس جهت انتقال، تا زمانی که یک شرط STOP رخ دهد، همراه می شود.
این طرح ارتباط یک دام بزرگ دارد: اگر بخواهیم چیزی خاص در مورد دستگاه slave بپرسیم، باید از دو تراکنش مجزا استفاده کنیم. اجازه دهید این مثال را در نظر بگیریم. فرض کنید یک I²C EEPROM داریم. معمولاً این نوع دستگاه ها دارای تعدادی مکان حافظه قابل آدرس دهی هستند (یک EEPROM 64 کیلوبیتی در محدوده 0 – x1FFF0 آدرس پذیر است). برای بازیابی محتوای یک مکان حافظه، Master باید مراحل زیر را انجام دهد:
- تراکنش را با ارسال آدرس slave در گذرگاه I²C ، در حالت نوشتن ، شروع کند (آخرین بیت آدرس slave روی 0 تنظیم شود) به طوری که EEPROM شروع به نمونه برداری از پیام ها از طریق گذرگاه کند.
- ارسال دو بایت نشان دهنده مکانی از حافظه که می خواهیم بخوانیم.
- پایان دادن به تراکنش با ارسال شرایط STOP.
- یک تراکنش جدید با ارسال آدرس slave در گذرگاه I²C ، در حالت خواندن (آخرین بیت آدرس slave روی 1 تنظیم شود) شروع کند.
- خواندن n بایت (معمولاً یک بایت در صورت خواندن حافظه در حالت تصادفی، بیش از یک بایت در صورت خواندن حافظه در حالت متوالی) ارسال شده توسط دستگاه slave و سپس پایان دادن به تراکنش با شرط STOP.
![](https://gebrabit.com/wp-content/themes/woodmart/images/lazy.png)
برای پشتیبانی از این طرح ارتباطی رایج، پروتکل I²C تراکنش های ترکیبی combined transactions را تعریف می کند، جایی که جهت جریان داده پس از ارسال تعدادی بایت، معکوس می شود (معمولاً از Slave به Master یا بالعکس). شکل 5 این روش برای برقراری ارتباط با دستگاه های Slave را نشان می دهد. Master شروع به ارسال آدرس slave در حالت نوشتن می کند (به W با قرمز پررنگ در شکل 5 توجه کنید) و سپس آدرس رجیستر هایی را که می خواهیم بخوانیم ارسال می کند. در ادامه یک شرط START جدید بدون خاتمه تراکنش ارسال می شود: این شرط START اضافی نیز شرط شروع تکراری repeated START (یا شروع مجدد RESTART) نامیده می شود. Master دوباره آدرس Slave را ارسال می کند اما این بار تراکنش در حالت خواندن شروع می شود (به R با قرمز پررنگ در شکل 5 توجه کنید). Slave اکنون محتوای رجیسترهای مورد نظر را ارسال می کند و master هر بایت ارسالی را تأیید acknowledge می کند. Master تراکنش را با صدور یک NACK (این واقعا مهم است، همانطور که در ادامه خواهیم دید) و یک شرط STOP پایان می دهد.
Availability of I²C Peripherals in STM32 MCUs
بسته به نوع خانواده و پکیج مورد استفاده ، میکروکنترلرهای STM32 می توانند تا چهار واحد جانبی I²C مستقل ارائه دهند. جدول 1 در دسترس بودن تجهیزات جانبی I²C را در MCU های STM32 مورد استفاده در تمام شانزده برد Nucleo ، خلاصه می کند.
![](https://gebrabit.com/wp-content/themes/woodmart/images/lazy.png)
برای هر ابزار جانبی I²C و میکروکنترلر STM32 داده شده در جدول 1 ، پین های مربوط به خطوط SDA و SCL نشان داده شده است. علاوه بر این، ردیفهای تیرهتر، پینهای جایگزین مربوط به خطوط SDA و SCL را نشان میدهند که میتوان از آنها در طراحی بهینه PCB استفاده کرد. به عنوان مثال، با توجه به میکروکنترلر STM32F401RE ، میبینیم که I2C1 به PB7 و PB6 نگاشت شده است، اما پین های PB9 و PB8 نیز می توانند به عنوان پایه های جایگزین استفاده شوند. توجه داشته باشید که پین های I/O ابزار جانبی I2C1 در همه MCU های STM32 با پکیج LQFP-64 مشابه هستند. این امر یک نمونه مهم از سازگاری پین به پین pin-to-pin است که توسط میکروکنترلرهای STM32 ارائه شده است.
اکنون آمادهایم تا نحوه استفاده از CubeHAL API برای برنامهنویسی این ابزار جانبی را ببینیم.
HAL_I2C Module
برای برنامه نویسی ابزار جانبی I²C، CubeHAL ساختار I2C_HandleTypeDef را به صورت زیر تعریف می کند:
typedef struct {
I2C_TypeDef *Instance; /* I²C registers base address */
I2C_InitTypeDef Init; /* I²C communication parameters */
uint8_t *pBuffPtr; /* Pointer to I²C transfer buffer */
uint16_t XferSize; /* I²C transfer size */
__IO uint16_t XferCount; /* I²C transfer counter */
DMA_HandleTypeDef *hdmatx; /*
I²C Tx DMA handle parameters */
DMA_HandleTypeDef *hdmarx; /* I²C Rx DMA handle parameters */ HAL_LockTypeDef Lock; /* I²C locking object */
__IO HAL_I2C_StateTypeDef State; /* I²C communication state */
__IO HAL_I2C_ModeTypeDef Mode; /* I²C communication mode */
__IO uint32_t ErrorCode; /* I²C Error code */
} I2C_HandleTypeDef;
اجازه دهید مهم ترین بخش های این ساختار C را تحلیل کنیم.
- Instance: اشاره کننده به توصیفگر I²C که قرار است از آن استفاده کنیم. به عنوان مثال، I2C1 توصیف کننده اولین واحد جانبی I²C است.
- Init: نمونه ای از ساختار I2C_InitTypeDef است که برای پیکربندی واحد جانبی استفاده می شود. کمی جلوتر آن را عمیق تر بررسی خواهیم کرد.
- pBuffPtr: اشاره گر به بافر داخلی ای که برای ذخیره موقت داده های منتقل یا دریافت شده از واحد جانبی I²C استفاده می شود، است.این متغیر زمانی که I²C در حالت وقفه کار می کند استفاده می شود و نباید توسط کد کاربر تغییر داده شود.
- hdmatx، hdmarx: اشارهگر به نمونههایی از ساختار DMA_HandleTypeDef هست و زمانی که واحد جانبی I²C در حالت DMA کار میکند استفاده میشود.
راه اندازی واحد I²C با استفاده از Struct I2C_Inittypedef انجام می شود ، که به روش زیر تعریف شده است: