تصویر شاخص نوشته: I2C چیست و چرا با دو سیم می‌شود ده قطعه را وصل کرد

I2C چیست و چرا با دو سیم می‌شود ده قطعه را وصل کرد

۱۱ مرداد ۱۴۰۵ سنسورها و ورودی‌ها

پایه‌های ESP32 زودتر از چیزی که فکرش را می‌کنی تمام می‌شوند. برد DevKit سی‌پین روی کاغذ سخاوتمند است، ولی پایه‌های 34 تا 39 فقط ورودی‌اند و چند پایهٔ دیگر هنگام boot حساس‌اند. کافی است یک ربات کاملاً معمولی ببندی — سه LED، دو دکمه، یک Buzzer، یک پتانسیومتر، یک LDR، یک سنسور دما، یک Servo و یک سنسور فاصله (دو پایه) — تا دوازده پایه رفته باشد و هنوز حتی یک نمایشگر هم نداشته باشی.

و نمایشگر دقیقاً جایی است که این حساب‌وکتاب می‌شکند. یک OLED کوچک ۱۲۸ در ۶۴ نقطه دارد؛ ۸۱۹۲ پیکسل. اگر برای هر پیکسل یک سیم لازم بود، هیچ بردی در دنیا جوابگو نبود. ولی پشت آن ماژول فقط چهار پایه می‌بینی: VCC و GND برای برق، و دو سیم برای تمام داده‌ها. آن دو سیم I2C هستند — و همان دو سیم می‌توانند هم‌زمان به یک شتاب‌سنج، یک سنسور فشار و یک ساعت دقیق هم سرویس بدهند، بدون اینکه یک پایهٔ دیگر خرج شود.

و چون I2C وقتی خراب می‌شود معمولاً بی‌صدا خراب می‌شود، نیمهٔ دوم این نوشته عیب‌یابی است.

دو سیم، چند دستگاه

راهرویی را تصور کن که در چند اتاقش باز است. کسی ته راهرو بلند می‌گوید: «اتاق شمارهٔ ۶۰، جواب بده!» صدا به گوش همه می‌رسد، ولی فقط اتاق ۶۰ جواب می‌دهد و بقیه به کار خودشان می‌رسند. تا وقتی هیچ دو اتاقی شمارهٔ یکسان نداشته باشند، این راهرو بدون دعوا کار می‌کند.

I2C همین است. دو سیم دارد و هر دو بین همهٔ دستگاه‌ها مشترکاند:

  • SDA سیم داده است؛ حرف‌ها روی این یکی رد و بدل می‌شوند.
  • SCL سیم کلاک است؛ ضربان مشترکی که می‌گوید هر بیت دقیقاً کِی خوانده شود. بدون این ضربان، فرستنده و گیرنده راهی ندارند بفهمند یک بیت کجا تمام شد و بعدی کجا شروع شد.

و یک سیم سوم که سیم داده نیست ولی به همان اندازه اجباری است: GND مشترک. دلیلش فیزیکی است — «صفر» و «یک» روی این خط‌ها یعنی اختلاف ولتاژ نسبت به زمین؛ بدون زمین مشترک، «۰٫۲ ولت» نسبت به چه؟ فراموش‌کردن این سیم خرابیِ ناپایدار و گیج‌کننده می‌سازد، نه یک خطای تمیز.

روی این باس ESP32 نقش master را دارد: هر گفت‌وگو را او شروع می‌کند و هیچ دستگاهی خودسرانه حرف نمی‌زند، پس دو سنسور هیچ‌وقت وسط حرف هم نمی‌پرند. هر تبادل همان الگوی راهرو است: علامت شروع، بعد آدرس دستگاه، بعد یک بیت که می‌گوید می‌خواهم بخوانم یا بنویسم. دستگاهی که صدایش زده‌اند یک لحظه SDA را پایین می‌کشد، یعنی «بله، اینجام». نام این جواب کوتاه ACK است و کل ماجرای اسکن باس روی همین یک بیت بنا شده.

روی Breadboard هم SDA و SCL هرکدام باید در یک ردیف مشترک بنشینند تا ماژول‌ها واقعاً روی یک خط باشند؛ ساختار داخلی ردیف‌ها همین‌جا به کار می‌آید.

آدرس: چطور برد می‌فهمد با کدام قطعه حرف می‌زند

آدرس در I2C هفت بیت است، یعنی ۱۲۸ حالت ممکن؛ شانزده تا رزرو شده‌اند (0x00 تا 0x07 و 0x78 تا 0x7F) و ۱۱۲ آدرس واقعی می‌ماند. به این سقف در عمل نمی‌خوری؛ به سقف‌هایی می‌خوری که پایین‌تر می‌آیند. و مهم‌ترین نکته: آدرس را تو انتخاب نمی‌کنی، سازنده در خودِ تراشه گذاشته است.

قطعه آدرس پیش‌فرض آدرس دوم
SSD1306 OLED 0x3C 0x3D (با جابه‌جایی pad آدرس)
MPU6050 (برد GY-521) 0x68 0x69 (با وصل‌کردن AD0 به HIGH)
BME280 / BMP280 0x76 0x77

ستون سوم مهم‌ترین ستون جدول است: تقریباً هر قطعهٔ I2C یک یا دو بیت از آدرسش را قابل تغییر گذاشته — گاهی پایه‌ای جداگانه مثل AD0، گاهی یک pad پشت ماژول که با یک قطره لحیم جابه‌جا می‌شود. همان چیزی که به دردت می‌خورد وقتی خواستی دو تا از یک مدل را کنار هم بگذاری.

یک نکتهٔ ظاهراً کوچک که وقت زیادی می‌گیرد: 0x3C همان عدد ۶۰ است، فقط در مبنای شانزده. سخت‌افزاری‌ها آدرس را با 0x می‌نویسند، ولی scan() لیست عددهای معمولی برمی‌گرداند؛ پس دیدن [60] دقیقاً یعنی دیدن 0x3C.

و یک تلهٔ واقعی: در datasheet بعضی قطعه‌ها آدرس 0x78 نوشته شده، نه 0x3C. هیچ‌کدام غلط نیست؛ آن فرم هشت‌بیتی است که بیت خواندن/نوشتن به تهش چسبیده (0x3C یک بیت به چپ شیفت می‌شود). MicroPython همیشه فرم هفت‌بیتی می‌خواهد؛ اگر عددی از datasheet برداشتی و کار نکرد، نصفش کن.

اسکنِ باس: اولین کاری که قبل از هر کد دیگری می‌کنی

چون هر دستگاه به آدرس خودش ACK می‌دهد، می‌شود همهٔ آدرس‌های ممکن را به‌ترتیب صدا زد و دید کدام‌ها جواب می‌دهند. این همان اسکن باس است. برنامه را کامل کپی کن؛ روی هر مداری که SDA روی 21 و SCL روی 22 باشد بدون تغییر اجرا می‌شود:

# I2C bus scanner - ESP32 + MicroPython
from machine import Pin, I2C

# SDA=21, SCL=22
i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=400000)

KNOWN = {
    0x3C: "SSD1306 / SH1106 OLED",
    0x3D: "SSD1306 OLED (pad-e address jabeja shode)",
    0x68: "MPU6050 (AD0 = LOW)",
    0x69: "MPU6050 (AD0 = HIGH)",
    0x76: "BMP280 / BME280",
    0x27: "PCF8574 - LCD backpack",
}

found = i2c.scan()
print("tedad dastgah:", len(found))

for addr in found:
    name = KNOWN.get(addr, "nashenas")
    print("dec={:3d}  hex=0x{:02X}  ->  {}".format(addr, addr, name))

if len(found) == 0:
    print("hich chiz javab nadad -> SDA/SCL, VCC va GND ra check kon")

خروجی نمونه، با یک OLED و یک MPU6050 روی همان دو سیم:

tedad dastgah: 2
dec= 60  hex=0x3C  ->  SSD1306 / SH1106 OLED
dec=104  hex=0x68  ->  MPU6050 (AD0 = LOW)

چطور بخوانی‌اش:

  • عدد اولِ هر خط همان است که scan() خام برمی‌گرداند و عدد دوم همان که روی ماژول نوشته شده. کد هر دو را چاپ می‌کند تا وسط عیب‌یابی مجبور نباشی مبنا عوض کنی.
  • tedad dastgah را با تعداد ماژول‌هایی که وصل کرده‌ای مقایسه کن. مهم‌ترین عدد خروجی همین است: اگر سه قطعه بسته‌ای و دو تا می‌بینی، مشکل داری — حتی اگر ظاهراً همه‌چیز کار کند.
  • لیست خالی یعنی مشکل از باس است، نه از کتابخانه. اول جابه‌جایی SDA و SCL را چک کن که رایج‌ترین خطای این باس است، بعد تغذیه و GND را.

یک محدودیت مهم هم دارد: اسکن فقط می‌پرسد «کسی در این آدرس هست؟»، نه «تو چه هستی؟». قطعهٔ اشتباه هم به همان اندازه مؤدبانه ACK می‌دهد. اسکنِ سالم یعنی «سیم‌کشی درست است»، نه «همه‌چیز درست است».

pull-up: چرا با یک قطعه کار می‌کرد و با سه تا نه

اینجا جایی است که بیشترین «ولی من که چیزی را عوض نکردم!» اتفاق می‌افتد.

روی I2C هیچ دستگاهی خط را به HIGH نمی‌بَرد؛ دستگاه‌ها فقط می‌توانند خط را پایین بکشند (به این می‌گویند open-drain) و بالابردن کار دو مقاومت است که خط‌ها را به تغذیه وصل می‌کنند: مقاومت‌های pull-up. یعنی حالت استراحتِ هر دو خط HIGH است و حرف‌زدن یعنی پایین‌کشیدنِ حساب‌شده. دلیلش اشتراکی‌بودن باس است: اگر دستگاه‌ها می‌توانستند فعالانه HIGH بدهند، کافی بود یکی HIGH بخواهد و دیگری LOW تا اتصال کوتاهی بین تغذیه و زمین بسازند.

و خودِ تله: تقریباً هر ماژول breakout ای که می‌خری، یک جفت pull-up روی خودش دارد — اغلب حدود 4.7kΩ، و روی برد GY-521 که MPU6050 رویش سوار است حدود 2.2kΩ. وقتی چند ماژول را به یک باس وصل می‌کنی این مقاومت‌ها موازی می‌شوند و مقاومت مؤثر پایین می‌آید:

  • یک ماژول 4.7kΩ → مؤثر 4.7kΩ. عالی.
  • دو ماژول 4.7kΩ → حدود 2.35kΩ. هنوز خوب.
  • یک GY-521 با 2.2kΩ هم اضافه کن → حدود 1.1kΩ. لبِ خطر.
  • ماژول چهارم → زیر 1kΩ. باس می‌میرد.

عدد «زیر یک کیلواهم» از هوا نیامده. استاندارد I2C می‌گوید دستگاه باید بتواند خط را زیر 0.4V پایین بکشد در حالی که حداکثر 3mA جریان می‌کشد. با تغذیهٔ 3.3V و مقاومت مؤثر 1.1kΩ جریان لازم حدود 2.6mA است — چسبیده به سقف. کمی پایین‌تر بروی، دستگاه دیگر زورش نمی‌رسد خط را آن‌قدر پایین بیاورد که طرف مقابل «صفر» بخواندش. نتیجه: باسی که گاهی جواب می‌دهد، گاهی نصفه، گاهی هیچ.

راه‌حل صریح است: فقط یک ماژول باید pull-up داشته باشد. روی بیشتر بردها این دو مقاومت یک pad قابل قطع‌شدن دارند؛ روی همه جز یکی بازشان کن. اگر ماژولی pad ندارد، تنها روی باس دوم برود.

خرابی معکوس هم هست: اگر هیچ pull-up ای روی باس نباشد، خط هیچ‌وقت به HIGH برنمی‌گردد و scan() خالی می‌دهد؛ دو مقاومت 4.7kΩ از SDA و SCL به 3V3 حلش می‌کند.

چهار خرابیِ رایج و نشانهٔ هرکدام

نشانه علت راه‌حل
دو ماژول وصل کرده‌ای ولی scan() فقط یک آدرس برمی‌گرداند آدرس تکراری — مثلاً دو OLED که هر دو 0x3C اند؛ باس دو دستگاه هم‌نام را از هم تشخیص نمی‌دهد و در ACK روی هم می‌افتند pad آدرس یکی را جابه‌جا کن یا AD0 را به HIGH ببر؛ اگر pad ندارد، دومی را روی باس دوم ببر
scan() خالی است یا هر بار عدد متفاوتی می‌دهد، در حالی که چراغ ماژول‌ها روشن است GND مشترک وصل نیست — معمولاً وقتی سنسور از منبع تغذیهٔ جدا برق می‌گیرد؛ سطح منطقی بدون مرجع مشترک بی‌معناست GND هر دستگاه را به GND برد وصل کن، حتی اگر برقش از جای دیگری می‌آید
scan() درست است و عدد ۶۰ را می‌بینی، ولی تصویر جابه‌جا، تکه‌تکه یا برفکی است درایور اشتباه — نمایشگر ۱٫۳ اینچ معمولاً SH1106 است نه SSD1306، و حافظهٔ تصویری‌اش ۱۳۲ ستون دارد نه ۱۲۸؛ کتابخانهٔ اشتباه تصویر را با آفست می‌نویسد کتابخانهٔ sh1106 را نصب کن. موقع خرید اسم چیپ را چک کن، نه اندازهٔ صفحه را
با یک قطعه کار می‌کرد؛ با سومی که اضافه شد باس مرد یا OSError های متناوب گرفتی pull-up های موازی — مقاومت مؤثر زیر 1kΩ رفته و دستگاه‌ها نمی‌توانند خط را به‌قدر کافی پایین بکشند pull-up همهٔ ماژول‌ها جز یکی را باز یا قطع کن

دو یادداشت. اول اینکه کتابخانه روی باسِ خراب معمولاً OSError: [Errno 19] ENODEV می‌دهد، یعنی «در این آدرس کسی جواب نداد» — پس همیشه اول scan() بزن. دوم اینکه ESP32 دو باس سخت‌افزاری I2C دارد و چون پایه‌ها از ماتریس GPIO عبور می‌کنند، باس دوم روی هر جفت پایهٔ آزاد می‌نشیند:

from machine import Pin, I2C

bus_a = I2C(0, scl=Pin(22), sda=Pin(21))   # bas-e hamishegi
bus_b = I2C(1, scl=Pin(19), sda=Pin(23))   # bas-e dovvom rooye pin-haye azad

print("bus A:", bus_a.scan(), "| bus B:", bus_b.scan())

دو پایه خرج می‌کند، ولی آدرس تکراری را قطعی حل می‌کند و بارِ pull-up را هم بین دو خط پخش می‌کند.

طول سیم و سرعت: یک حد واقعی، نه خرافه

«سیم‌هایت زیادی بلندند» شبیه خرافه به نظر می‌رسد، ولی یک عدد پشتش هست: استاندارد I2C سقف ظرفیت خازنی کل باس را 400pF گذاشته، و هر سانتی‌متر سیم، هر ردیف Breadboard و هر پایهٔ ورودی مقدار کوچکی به آن اضافه می‌کند.

چرا مهم است؟ چون خط با همان مقاومت pull-up شارژ می‌شود تا به HIGH برسد؛ این یک شیب است، نه یک پرش، و هرچه ظرفیت بیشتر باشد شیب کندتر است. از جایی به بعد گیرنده لحظه‌ای خط را نمونه‌برداری می‌کند که هنوز به آستانهٔ HIGH نرسیده و یک 1 را 0 می‌خواند. تله اینجاست که خطا تصادفی به نظر می‌رسد: برنامه ده بار درست کار می‌کند و بار یازدهم عدد پرت می‌دهد.

جامپرهای معمولی ۱۰ تا ۲۰ سانتی با دو سه ماژول بی‌دردسرند؛ نیم متر و بیشتر جایی است که خطاهای متناوب شروع می‌شوند. و I2C را از کنار سیم‌های موتور رد نکن — موتور نویز می‌سازد و این باس محافظت خاصی ندارد.

ارزان‌ترین درمان، که بهترین ابزار تشخیص هم هست، نصف‌کردن سرعت است:

from machine import Pin, I2C

# 100 kHz be jaye 400 kHz - kondtar vali moghavem-tar
i2c = I2C(0, scl=Pin(22), sda=Pin(21), freq=100000)
print(i2c.scan())

اگر روی 100000 کار کرد و روی 400000 نه، جوابت را گرفته‌ای: مشکل از کد نیست، از کیفیت سیگنال است — سیم بلند یا pull-up نامناسب. کندکردن باس فقط نرخ به‌روزرسانی را کم می‌کند، نه درستیِ داده را.

کِی I2C جواب نمی‌دهد

I2C را برای «چند قطعه، کم‌سیم» ساخته‌اند، نه برای «دادهٔ زیاد، سریع». در حالت رایج 400kHz هر بایت ۹ کلاک می‌خواهد (هشت بیت داده به‌علاوهٔ بیت ACK)، یعنی سقف نظری حدود ۴۴ کیلوبایت بر ثانیه — و در عمل کمتر، چون آدرس و شروع و پایان هر تبادل هم وقت می‌گیرد. این عدد را روی چیزهای واقعی امتحان کن:

  • یک فریم کامل OLED یعنی ۱۰۲۴ بایت؛ با 400kHz حدود ۲۳ میلی‌ثانیه، یعنی سقف نظری حدود ۴۰ فریم بر ثانیه. برای عدد و متن کافی است، و همین توضیح می‌دهد چرا انیمیشن نرم روی این نمایشگر سخت‌تر از انتظار است.
  • یک فریم دوربین حتی در 320×240 با دو بایت بر پیکسل، حدود ۱۵۳٬۶۰۰ بایت است؛ روی I2C هر فریم بیش از سه ثانیه طول می‌کشد، یعنی عملاً غیرممکن.

جالب اینکه ماژول‌های دوربین واقعاً I2C دارند، ولی فقط برای تنظیمات: روشنایی، رزولوشن، فرمت. خودِ پیکسل‌ها از یک باس جدا و پهن‌تر می‌آیند. همین تقسیم کار بهترین توصیف از جایگاه I2C است.

جایگزینِ سرعت‌بالا نامش SPI است و ده‌ها برابر سریع‌تر است — به قیمتی که عکسِ مسئلهٔ اول این نوشته است: SPI برای هر دستگاه یک سیم انتخاب اختصاصی می‌خواهد، پس هر قطعهٔ جدید باز یک پایه خرج می‌کند. پایه کم داری؟ I2C. سرعت کم داری؟ SPI.

و یک محدودیت آخر: همان 400pF می‌گوید این باس مالِ داخل یک دستگاه است، نه کابلی تا آن‌سر اتاق. ضمناً هر سنسوری I2C نیست — HC-SR04 پایه‌های اختصاصی خودش را می‌خواهد؛ بودجهٔ پایه‌ات را با این فرض ببند.

قدم بعدی

مؤثرترین کار این است که هر خرابی را یک بار عمداً بسازی، وقتی هنوز می‌دانی علتش چیست:

  1. فقط OLED را ببند، اسکنر را اجرا کن و عدد ۶۰ را ببین.
  2. قطعهٔ دوم را روی همان SDA و SCL اضافه کن و دوباره اسکن بگیر — باید دو آدرس ببینی. اگر یکی دیدی، با آدرس تکراری روبه‌رو شده‌ای.
  3. سیم GND یکی از دو ماژول را جدا کن و اسکن را تکرار کن؛ شکلِ خرابی را به چشم ببین.
  4. سیم را برگردان، سرعت را روی 100000 بگذار و مطمئن شو همه‌چیز سالم است.

بعد از این چهار قدم، «کار نمی‌کند» دیگر یک ابر مبهم نیست؛ می‌شود چند فرضیهٔ مشخص که به‌ترتیب ردشان می‌کنی. اگر قطعه‌ای به دستت نرسیده، همین اسکنر و نمایشگر را در آزمایشگاه اجرا کن.

برای دیدن آن‌طرفِ این دو سیم — اینکه SSD1306 چطور حافظهٔ تصویری‌اش را می‌چیند و چرا بدون show() صفحه سیاه می‌ماند — سراغ نوشتهٔ OLED و SSD1306 برو، و برای اینکه ببینی چرا اصلاً بردی مثل ESP32 انتخاب می‌شود، مقایسهٔ Arduino و ESP32.