در قسمت اول آموزش سریال I2C، در مورد چگونگی عملکرد این پروتکل و برنامهنویسی رجیسترهای آن صحبت شد. اما درست است که مقداردهی و دسترسی مستقیم به رجیسترها باعث کنترل بیشتر برنامهنویس میشود؛ اما این کار پیچیدگیهای زیادی دارد. به خصوص زمانی که از واحد I2C استفاده میکنیم. در نرم افزار CodeVision توابعی برای کار با TWI یا I2C تعبیه شده است که کار کردن با این واحد را تا حد زیادی آسان میکند. در این قسمت توابع i2c را بررسی خواهیم پرداخت.
تذکر: قبل از خواندن این پست، حتما قسمت اول I2C را مطالعه کنید. دلیل این تاکید هم مفاهیم پایه پروتکل I2C بوده و تا حدودی دستورات توابع i2c به مباحث پست قبل وابسته است.
توابع I2C یا TWI
در کدویژن دو کتابخانه برای کار با توابع i2c منظور شده است:
- کتابخانه twi.h که برای راهاندازی I2C سخت افزاری است (به I2C سخت افزاری، TWI گویند).
- کتابخانه i2c.h که برای راه اندازی I2C به صورت نرم افزاری است.
کتابخانه twi.h از رجیسترهای میکروکنترلر استفاده میکند و سرعت بیشتری دارد. بر خلاف آن کتابخانه i2c.h به صورت 100 درصد نرمافزاری بوده و تمام سیگنالها توسط CPU تولید میشود. به همین خاطر سرعت کمتری دارد.
نکته: مزیت i2c.h این است که به دلخواه میتوانیم پایههای SDA و SCL را بر روی هرکدام از پینهای میکرو تنظیم کنیم.
کتابخانه twi.h
اگر بخواهیم از I2C، هم به صورت Master و هم به صورت Slave استفاده کنیم، باید از توابع i2c این کتابخانه استفاده کرد. 3 تابع در این کتابخانه وجود دارد:
- twi_master_init که برای پیکربندی رابط I2C به صورت Master به کار میرود.
- twi_master_trans که باعث تبادل داده با Slave میشود.
- twi_slave_init که برای پیکربندی I2C به صورت Slave استفاده میشود.
با استفاده از 3 تابع گفته شده، میتوان از I2C در هر چهار مد استفاده کرد (مدهای I2C در قسمت قبل به صورت مفصل گفته شدند).
نکته: در صورت استفاده از این کتابخانه باید بیت وقفه عمومی را 1 کرد.
تابع twi_master_init
برای اینکه I2C به صورت Master عمل کند، این تابع استفاده میشود. چون تولید کلاک بر روی SCL برعهده Master است، این تابع یک ورودی دریافت میکند که سرعت نرخ ارسال SCL را مشخص میکند و واحد آن KHz است. به عنوان مثال:
1 2 |
twi_master_init(100); |
دستور بالا، I2C را به صورت Master و با نرخ ارسال 100 کیلوهرتز تنظیم میکند. این دستور یکبار باید اجرا شود (در ابتدای main).
تابع twi_master_trans
پس از اینکه با تابع twi_master_init رابط I2C را آماده کردیم، هر کجا که نیاز به ارتباط با Slave داشته باشیم، از این تابع استفاده میکنیم. فرم کلی این تابع به شکل زیر است.
1 2 3 4 5 |
bool twi_master_trans( unsigned char slave_addr, unsigned char *tx_data, unsigned char tx_count, unsigned char *rx_data, unsigned char rx_count); |
- نوع دادهای که با کتابخانه stdbool.h در زبان C میتوان استفاده کرد، bool است که تنها دو مقدار میتواند داشته باشد: true یا false. اگر عملیات خواندن یا نوشتن موفقیت آمیز باشد، مقدار true به عنوان خروجی برگشت داده میشود.
- slave_addr آدرس Slave مورد نظر است که میخواهیم با آن تبادل کنیم. آدرس Slave به صورت 7 بیتی است.
- tx_data آرایهای است که میخواهیم به Slave بفرستیم.
- tx_count تعداد بایتی است که از آرایه tx_data به Slave فرستاده میشود.
- rx_data آرایهای است که اطلاعات دریافتی از Slave درون آن ریخته میشود.
- rx_count تعداد بایتی است که از Slave دریافت شده و در rx_data قرار میگیرد.
تابع فوق را به 3 صورت میتوان به کار برد.
حالت اول
در این حالت Master به صورت فرستنده عمل میکند (Master Transmitter).
1 2 3 |
unsigned char Send[5] = {'H','e','l','l','o'}; twi_master_trans(0x32,Send,5,0,0); |
در کد فوق، 5 بایت از آرایه Send به Slave با آدرس 0x32 ارسال میشود.
حالت دوم
در این حالت Master به صورت گیرنده عمل خواهد کرد (Master Receiver).
1 2 3 |
unsigned char Receive[16]; twi_master_trans(0x32,0,0,Receive,7); |
در کد بالا، 7 بایت از Slave به آدرس 0x32 دریافت شده و در آرایه Receive ذخیره میشود. باید گفت که طول آرایه تنها باید بزرگتر یا مساوی تعداد بایت دریافتی باشد؛ که در این مثال 16 بایت بوده و فقط 7 بایت اول آن پر میشود.
حالت سوم
در این حالت Master ابتدا به صورت فرستنده و پس از پایان ارسال، به صورت گیرنده عمل میکند.
1 2 3 4 |
unsigned char Send[5] = {'H','e','l','l','o'}; unsigned char Receive[16]; twi_master_trans(0x32,Send,5,Receive,7); |
در کد بالا، ابتدا Master به صورت فرستنده عمل کرده و 5 بایت از آرایه Send را به Slave میفرستد. سپس 7 بایت از Slave خوانده و در آرایه Receive ذخیره میکند.
تابع twi_slave_init
اگر بخواهیم I2C به صورت Slave عمل کند، از این تابع استفاده میکنیم. این تابع فرمی به شکل زیر دارد.
1 2 3 4 5 6 7 8 9 10 |
void twi_slave_init( bool match_any_addr, unsigned char addr, unsigned char *rx_buffer, unsigned char rx_buffer_size, unsigned char *tx_buffer, تابع دریافت, تابع ارسال ); |
- match_any_addr که اگر true باشد، قابلیت فراخوان عمومی Slave فعال میشود. فراخوان عمومی زمانی اتفاق میافتد که Master آدرس 0x00 را ارسال کند.
- addr که آدرس منحصر به فرد Slave است و باید 7 بیتی باشد. مثلا: 0x61.
- rx_buffer آرایهای است که اطلاعات فرستاده شده از طرف Master در آن ذخیره میشود.
- rx_buffer_size اندازه آرایه rx_buffer است.
- tx_buffer آرایهای است که باید به Master فرستاده شود.
تابع دریافت
تابعی در Slave است که فرم آن به شکل زیر میباشد.
1 2 3 4 5 6 7 8 9 10 11 12 |
bool twi_rx_handler(bool rx_complete){ if (twi_result==TWI_RES_OK){ //block 1 } else{ //block 2 return false; } if (rx_complete) return false; return (twi_rx_index<sizeof(twi_rx_buffer)); } |
این تابع هر زمان که 1 بایت دریافت شود، اجرا خواهد شد. اگر بایت دریافتی سالم باشد، block شماره 1 و در غیر این صورت block شماره 2 انجام میشود.
ورودی این تابع، rx_complete بوده که در صورتی که عملیات دریافت تمام شود، در آخرین اجرا true شده و میتوان با یک if کار مورد نظر را انجام داد.
نکته: این تابع توسط واحد twi صدا زده میشود و اجرای آن برعهده ما نیست.
تابع ارسال
این تابع در زمان ارسال اطلاعات از Slave به Master دو بار صدا زده میشود. یکبار در ابتدای انتقال و یکبار هم در انتهای انتقال. فرم آن هم به صورت زیر است.
1 2 3 4 5 6 7 |
unsigned char twi_tx_handler(bool tx_complete){ if (tx_complete==false){ return تعداد بایت ارسالی; } return 0; } |
این تابع یک ورودی به نام tx_complete دارد که در اولین اجرای تابع (ابتدای انتقال) برابر false است. به همین خاطر دستور if اجرا شده و باید تعداد بایتی را که میخواهیم ارسال شود، return کنیم.
در دومین اجرا مقدار tx_complete برابر true شده و 0 برگشت داده میشود.
نکته: این تابع هم توسط واحد twi صدا زده میشود و اجرای آن بر عهده ما نیست.
مثال 1 : ارسال اطلاعات از Master به Slave
با استفاده از توابع i2c کتابخانه twi.h، برنامهای مینویسیم که عبارت www.rasdino.ir را از Master به Slave انتقال دهد. پس Master در حالت فرستنده و Slave در حالت گیرنده قرار میگیرد.
برنامه Master
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <mega16.h> #include <delay.h> #include <twi.h> unsigned char Send[16] = "www.rasdino.ir"; void main(void){ twi_master_init(100); #asm("sei") while(1){ twi_master_trans(0x32,Send,14,0,0); delay_ms(1000); } } |
در برنامه فوق، ابتدا با تابع twi_master_init، رابط I2C را به صورت Master و با فرکانس کاری 100KHz پیکربندی کردیم. سپس بیت وقفه عمومی را 1 کرده تا واحد twi بتواند از وقفه استفاده کند.
در while هم با استفاده از تابع twi_master_trans، هر یک ثانیه تعداد 14 بایت از آرایه Send (که شامل عبارت www.rasdino.ir است) به Slave با آدرس 0x32 فرستاده میشود. چون قرار است اطلاعاتی دریافت نکنیم، دو پارامتر آخر این تابع را 0 قرار میدهیم.
برنامه Slave
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include <mega16.h> #include <delay.h> #include <alcd.h> #include <twi.h> unsigned char Receive[16]; bool receive_complete; bool twi_rx_handler(bool rx_complete){ if (rx_complete){ receive_complete = true; return false; } return (twi_rx_index<sizeof(Receive)); } void main(void){ lcd_init(16); lcd_clear(); //////////////////////////// twi_slave_init(false,0x32, Receive, sizeof(Receive), 0, twi_rx_handler, 0); #asm("sei") while(1){ if(receive_complete==true){ receive_complete = false; lcd_gotoxy(0,0); lcd_puts(Receive); delay_ms(500); lcd_clear(); } } } |
به عنوان یک توضیح مختصر، با استفاده از تابع twi_slave_init، رابط I2C به صورت Slave پیکربندی میشود. اولین پارامتر تابع را false میفرستیم که فراخوان عمومی I2C غیرفعال میشود. سپس آدرس منحصر به فرد Slave را 0x32 میفرستیم.
در پارامترهای بعدی به ترتیب Receive به عنوان آرایه دریافت اطلاعات، (sizeof(Receive به عنوان طول آرایه Receive، عدد 0 به عنوان آرایه ارسال که چون قصد ارسال نداریم 0 گذاشتیم، twi_rx_handler به عنوان تابع دریافت و در نهایت چون ارسال نداریم، نیازی به تابع ارسال نیست و به جای آن 0 قرار میدهیم.
در حلقه while متغیر receive_complete (که در اول برنامه تعریف کردیم) به طور مداوم چک شده و زمانی که برابر با true شود، مقدارش false شده و در سطر و ستون صفرم lcd، محتویات آرایه Receive چاپ خواهد شد.
تابع دریافت ما twi_rx_handler است که با هر بار دریافت یک بایت، یکبار اجرا میشود. در آخرین اجرا چون rx_complete برابر true است، متغیر receive_complete را true خواهیم کرد تا if داخل while اجرا شود.
مثال 2 : ارسال اطلاعات از Slave به Master
همان برنامه مثال 1 را طوری تغییر میدهیم که عبارت www.rasdino.ir اینبار از Slave به Master فرستاده شود.
برنامه Master
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <mega16.h> #include <delay.h> #include <alcd.h> #include <twi.h> unsigned char Receive[16]; void main(void){ lcd_init(16); lcd_clear(); ///////////////////////// twi_master_init(100); #asm("sei") while(1){ twi_master_trans(0x32,0,0,Receive,14); lcd_gotoxy(0,0); lcd_puts(Receive); delay_ms(500); lcd_clear(); delay_ms(500); } } |
تنها تفاوت این برنامه در چگونگی استفاده از تابع twi_master_trans است. در اولین پارامتر این تابع آدرس Slave که 0x32 است را وارد کردیم. چون میخواهیم عمل دریافت داده را انجام دهیم، پارامتر دوم و سوم که به ترتیب tx_data و tx_count بودند را 0 میکنیم. پارامتر چهارم هم آرایهای است که بایتهای دریافتی در آن ذخیره میشود و پارامتر پنجم تعداد بایتهای دریافتی است. چون رشته www.rasdino.ir شامل 14 کاراکتر است، تعداد بایت دریافتی را 14 قرار میدهیم.
در نهایت هم پس از دریافت، اطلاعات موجود در آرایه Receive بر روی lcd چاپ میشود.
برنامه Slave
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#include <mega16.h> #include <delay.h> #include <twi.h> unsigned char Send[15] = "www.rasdino.ir"; unsigned char twi_tx_handler(bool tx_complete) { if (tx_complete==false) return 14; return 0; } void main(void){ twi_slave_init(false,0x32, 0, 0, Send, 0, twi_tx_handler); #asm("sei") while(1); } |
در تابع main، رابط I2C را به صورت slave پیکربندی کردهایم؛ طوری که فراخوان عمومی غیرفعال (false)، آدرس منحصر به فرد Slave برابر 0x32، آرایه دریافت نداریم (0 گذاشتیم)، تعداد بایت دریافتی 0، آرایهای که باید فرستاده شود Send است، تابع دریافت نداریم (0 گذاشتیم و در نهایت چون میخواهیم عمل ارسال انجام دهیم، تابع twi_tx_handler را به عنوان تابع ارسال در نظر گرفتیم.
بیت وقفه عمومی را 1 کردیم و سپس CPU وارد یک while بدون دستور میشود. نکتهای که وجود دارد همین است. چون کتابخانه twi از وقفه I2C استفاده میکند، ارسال اطلاعات توسط وقفه صورت میگیرد و میتوان در داخل while کدهای دیگری از پروژه را گنجاند.
در تابع twi_tx_handler چون در بار اول اجرا شرط if به دلایل گفته شده صحیح است، باید تعداد بایت ارسالی را return کرد که در این مثال 14 است. در صورت اتمام ارسال اطلاعات، این تابع بار دیگر صدا زده میشود که در این حالت شرط برقرار نبوده و 0 را return میکنیم.
کتابخانه i2c.h
اگر بخواهیم i2c را بر روی پایهای دلخواه و به صورت نرم افزاری پیاده سازی کنیم، باید از توابع i2c این کتابخانه استفاده کرد. این کتابخانه دارای توابع i2c فقط برای مدهای Master هستند و قابلیت Slave بودن را ندارند.
برای استفاده از این کتابخانه باید پورتی که i2c بر روی آن است مشخص شود. همچنین شماره پین مربوط به SCL و SDA هم باید معلوم گردد.
1 2 3 4 5 6 |
#asm .equ __i2c_port=PORT_ADDRESS .equ __scl_bit=PIN_SCL .equ __sda_bit=PIN_SDA #endasm |
کد فوق باید بیرون از main نوشته شود و نکات زیر را هم باید در نظر گرفت:
- PORT_ADDRESS یکی از اعداد 0x15 ،0x18 ،0x21 یا 0x12 است که به ترتیب آدرس PORTC ،PORTB ،PORTA و PORTD هستند.
- PIN_SCL شماره پایهای است که پایه SCL روی آن تعریف میشود.
- PIN_SDA شماره پایهای است که پایه SDA روی آن تعریف خواهد شد.
به عنوان مثال کد زیر رابط i2c را بر روی پورت D تعریف میکند و شماره پین SCL آن 0 و شماره پین SDA آن 1 است.
1 2 3 4 5 6 |
#asm .equ __i2c_port=0x12 .equ __scl_bit=0 .equ __sda_bit=1 #endasm |
توابع کتابخانه i2c.h
5 تابع برای کار کردن با i2c وجود دارد و همانطور که گفتیم تنها برای حالت Master استفاده میشوند.
تابع i2c_init
این تابع تنظیمات اولیه I2C را انجام میدهد و به صورت پیشفرض، نرخ ارسال را بر روی بیشترین مقدار تنظیم میکند. اگر فرکانس کاری میکرو بیش از 6.4 مگاهرتز باشد، نرخ ارسال 400KHz است. در غیر این صورت بر روی 100KHz تنظیم خواهد شد. این تابع پارامتر ورودی و خروجی ندارد.
تابع i2c_start
این تابع یک شرط آغاز ایجاد میکند. طبق توضیحات قسمت قبلی آموزش، شرط آغاز برای شروع یک انتقال ایجاد میشود. این تابع هم هیچگونه پارامتر ورودی و خروجی ندارد.
تابع i2c_write
این تابع یک بایت را بر روی رابط I2C مینویسد. به همین خاطر یک پارامتر ورودی دارد که دادهای از نوع char دریافت میکند.
1 2 |
i2c_write(0x55); |
با دستور بالا، مقدار 0x55 بر روی I2C ارسال میشود.
تابع i2c_read
این تابع یک بایت را از رابط I2C میخواند و به عنوان خروجی برمیگرداند. همچنین یک ورودی هم دریافت میکند که وضعیت بیت ACK را معلوم میکند. اگر ورودی 1 باشد، پس از خواندن بایت، بیت ACK و اگر 0 باشد، بیت NACK به Slave فرستاده خواهد شد. در مورد بیت ACK یا تصدیق به طور مفصل در قسمت قبل بحث شده است.
1 2 |
data = i2c_read(0); |
خط فوق یک بایت داده را خوانده و در data ذخیره میکند و در پایان هم بیت NACK به نشانه آخرین بایت انتقال به Slave فرستاده میشود.
تابع i2c_stop
این تابع یک شرط پایان ایجاد میکند. شرط پایان نشانه اتمام تبادل داده با Slave است.
مثال 3 : راه اندازی I2C به صورت Master با i2c.h
برنامه Master در مثال 2 را با توابع i2c موجود در کتابخانه i2c.h بازنویسی میکنیم.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#include <mega16.h> #include <delay.h> #include <alcd.h> #include <i2c.h> unsigned char Receive[16]; unsigned char i; #asm .equ __i2c_port=0x12 .equ __scl_bit=0 .equ __sda_bit=1 #endasm void main(void){ lcd_init(16); lcd_clear(); ///////////////////////// i2c_init(); while(1){ ///////////////////////// read data from i2c i2c_start(); i2c_write((0x32<<1)|1); for(i=0;i<13;i++) Receive[i] = i2c_read(1); Receive[13] = i2c_read(0); i2c_stop(); ///////////////////////// show data on lcd lcd_gotoxy(0,0); lcd_puts(Receive); delay_ms(500); lcd_clear(); delay_ms(500); } } |
برنامه فوق 2 تفاوت مشهود با برنامه Master مثال 2 دارد: یکی بلاک asm# و یکی هم بلاک مربوط به خواندن اطلاعات از i2c.
بلاک asm#
با استفاده از این دستورات، پایههای SCL و SDA را به ترتیب بر روی PORTD.0 و PORTD.1 تعریف کردیم.
بلاک read data from i2c
ابتدا یک شرط آغاز با تابع i2c_start ایجاد میکنیم. سپس باید آدرس Slave را بفرستیم و از آنجایی که قصد خواندن از Slave را داریم باید بیت R/W را 1 کنیم. آدرس Slave همان 0x32 است. پس مقدار پارامتر ورودی i2c_write باید 01100101 باشد.
حال باید 14 بایت را بخوانیم. 13 بایت اول باید ACK فرستاده شود و پس از خواندن بایت آخر باید NACK بفرستیم. با استفاده از for (که 13 بار تکرار میشود) توسط تابع (1)i2c_read هر دفعه، یک بایت خوانده شده و ACK فرستاده میشود. پس از for هم یک بایت دیگر به صورت (0)i2c_read خوانده خواهد شد.
در نهایت هم با یک شرط پایان (i2c_stop)، انتقال داده تمام میشود.
هرگونه سوال یا مشکلی در کار با توابع i2c یا اجرای برنامهها وجود داشت، میتوانید زیر همین پست کامنت بگذارید.
برای آموزشهای بیشتر با رزدینو همراه باشید مبحث بعدی SPI میباشد.
13 پاسخ
با سلام من یک دستگاه xrf دستی دارم که خراب شده. وقتی نرم افزار دستگاه را اجرا میکنم همه چی OK هست بجز ولتاژ بایاس دتکتور که باید نرم افزار نشون بده 135 ولت ست شده و 135 ولت خوانده میشود. در حال حاضر هم ولتاژ ست و هم ولتاژ خوانده شده هر دو صفر است. بقیه پارامترها از قبیل دما و فشار هم مقدار ست شده و هم مقدار خوانده شده و باقی پارامترها همه درست هستند. نرم افزار منویی داره که میتوان دستورات I2C فرستاد ولی من وارد نیستم به I2C. آیا جنابعالی مایل به کمک به بنده هستید که با ارسال دستورات I2C بتونم قسمت خراب و دلیل صفر بودن ولتاژ بایاس را پیدا کنم؟ اگر تمایل دارید با بنده تماس حاصل فرمایید. با تشکر
با سلام میتوانید از طریق تلگرام در ارتباط باشید
بسیار عالی بود
بسیار عالی و کلی توضیح داده بودین
خیلی ممنون از شما.من مدت زیادی بود که از برنامه نویسی میکروها دور بودم و الان بعد از خوندن هر دو قسمت آموزش های شما کاملا تمامی مطالب برای من زنده شد.
امیدوارم موفق و سربلند باشید.
خوشحال میشم اگه به نشانی ایمیل من پیام بدین تا با هم در ارتباط باشیم.
ممنون
با درود فرادوان بسیار خرسندم که تنها اندکی توانسته ایم شمارا خوشحال کنیم و مفید بوده باشیم.
سپاس از شما که با نظراتتان به ما انرژی مثبت میدهید
خیلی مسلط و جامع و عالللی توضیح دادیین خدا خیرت بده .مرسی از زحماتتون.
سلام خیلی ممنون از توضیحات عالی .من مثال 1 رو انجام دادم.ولی تو slaveچیزی دریافت نمیشه همه چی هم درست هستش.؟
با سلام و خسته نباشید.
آیا همه این کدها را حفظ کرد و نوشت و یا اینکه با کدویزارد هم امکان ساده سازی وجود دارد ؟
سلام بنده دقیقا کد برنامه رو کپی کردم و اجرا کردم اما متاسفانه اجرا نشد
سلام وقت بخیر.
از طریق پشتیبانی به شماره 09909355855 به واتس آپ یا تلگرام پیام دهید تا کد سورس و فایل شبیه سازی برای شما ارسال گردد.
سلام وقت بخیر
ممنون خیلی عالی
متاسفانه کلاک میکرو به 16 مگا هرتز تغیر میکنه خواندن و نوشتن با کتابخانه i2c.h با مشکل مواجه میشه
ممنون میشم راهنماییی کنید
سلام. خواهش میکنم
بله متأسفانه در فرکانس بالا توابع با مشکل مواجه میشن که راهش اینه از رحیسترهای i2c به صورت مستقیم استفاده کنید.
در آینده کتابخانه اختصاصی خود سایت رو توی آموزش لینک دانلودش رو قرار میدم.