AVR پروتکل i2c سال ۱۴۰۵

saalek110

Well-Known Member
در مورد ماژول سون سگمنت ، شبیه پروتکل i2c است ولی دقیقا همون i2c نیست ، سازنده اش شاید به دلیلی دوست داشته وارد پروتکل i2c بشه ، کدش را از اوش مصنوعی بگیرید...من هم اگر رفتم روی کامپیوتر کدش را می ذارم..


چون دقیقا i2c. نیست ، پس دو سیم دیگر از میکروکنترلر مشغول ماژول سون سگمنن میشه که البته خیلی بهتر از ۱۱ تا سیم سون سگمنت ساده است.
 

saalek110

Well-Known Member
در AVR‌ (و بنابراین در **CodeVisionAVR**) ماژول **TWI (I²C)** مجموعه‌ای از رجیسترهای مشخص دارد. برای کار با TWI مهم است دقیقاً بدانیم هر رجیستر چه وظیفه‌ای دارد، چون اکثر توابع کتابخانه در نهایت همین رجیسترها را مقداردهی می‌کنند.

در AVR فقط **۴ رجیستر اصلی TWI** وجود دارد:

--------------------------------
### 1. TWBR
**TWI Bit Rate Register**

وظیفه: تعیین سرعت SCL.

فرمول سرعت:
```
SCL Frequency = F_CPU / (16 + 2 * TWBR * Prescaler)
```

Prescaler همان TWPS در رجیستر TWSR است.

کاری که TWBR انجام می‌دهد:
- با مقداردهی آن می‌توان سرعت I2C را 100kHz / 400kHz / 1MHz تنظیم کرد.
- اگر مقدار را کوچک بگذاری سرعت زیاد می‌شود.

--------------------------------
### 2. TWSR
**TWI Status Register**

این رجیستر شامل دو بخش است:

**الف) TWS[7:3] — بیت‌های وضعیت (Status Bits)**
کدهای بازگشتی وضعیت TWI را ذخیره می‌کند مثل:
- 0x08 → Start condition sent
- 0x18 → SLA+W transmitted & ACK received
- 0x40 → SLA+R transmitted & ACK received

این‌ها برای کنترل درست پروتکل ضروری‌اند.

**ب) TWPS[1:0] — Prescaler Bits**
برای تنظیم prescaler نرخ SCL:
```
00 = prescaler 1
01 = prescaler 4
10 = prescaler 16
11 = prescaler 64
```

--------------------------------
### 3. TWDR
**TWI Data Register**

وظیفه:
- هنگام ارسال، داده‌ای که باید روی I2C بفرستی داخل این رجیستر قرار می‌گیرد.
- هنگام دریافت، داده ورودی از I2C در این رجیستر ذخیره می‌شود.
- مقداردهی آن انتقال را به طور خودکار آغاز نمی‌کند؛ باید بیت TWINT در TWCR را کلیر کنی تا عملیات شروع شود.

--------------------------------
### 4. TWCR
**TWI Control Register**

مهم‌ترین رجیستر TWI. کنترل کامل عملکرد را در اختیار می‌دهد.

بیت‌های مهم آن:

- **TWINT**
با نوشتن 1 در آن، عملیات TWI اجرا می‌شود و وقفه پایان عملیات نشان داده می‌شود.
- **TWSTA**
تولید Start Condition
- **TWSTO**
تولید Stop Condition
- **TWEA**
ارسال ACK بعد از دریافت داده
- **TWEN**
فعال‌کردن TWI
- **TWIE**
فعال‌کردن وقفه TWI

برای مثال:
```
TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
```
این دستور یک **Start** تولید می‌کند.

--------------------------------
### 5. TWAR
**TWI Address Register**

وظیفه:
- آدرس میکرو وقتی در حالت Slave هست.
- بیت TWGCE → فعال‌سازی پاسخ به General Call

اگر فقط Master هستی، معمولاً نیازی به دست‌کاری TWAR نداری.

--------------------------------
### 6. TWAMR
**TWI Address Mask Register**

برای تطبیق آدرس چندگانه در حالت Slave استفاده می‌شود.
در حالت Master نیازی به آن نیست.
 

saalek110

Well-Known Member
در ادامه یک **مثال کاملاً عملی TWI در CodeVisionAVR** می‌گذارم که **از صفر** یک LCD کاراکتری I2C مبتنی بر PCF8574 را راه‌اندازی می‌کند.

کاملاً بدون کتابخانه
فقط با رجیسترهای **TWI**.

هدف:
ارسال “Hello” به LCD از طریق I2C در آدرس 0x27 (در صورت نیاز می‌تونی 0x20 هم بذاری).

--------------------------------
## پیکربندی TWI (I²C) در AVR

سرعت: 100kHz
با فرض F_CPU = 8MHz

```
TWBR = 32;
TWSR = 0x00; // prescaler = 1
```

--------------------------------
## توابع پایه TWI

```
#define F_CPU 8000000UL
#include <mega32.h>
#include <delay.h>

#define LCD_I2C_ADDR 0x27 // اگر 0x20 بود تغییر بده

//-------------------------------------
// Send START Condition
void TWI_Start()
{
TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);
while (!(TWCR & (1<<TWINT)));
}

//-------------------------------------
// Send STOP Condition
void TWI_Stop()
{
TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
delay_ms(1);
}

//-------------------------------------
// Write a Byte to I2C
void TWI_Write(unsigned char data)
{
TWDR = data;
TWCR = (1<<TWINT)|(1<<TWEN);
while (!(TWCR & (1<<TWINT)));
}
```

--------------------------------
## ارسال داده به ماژول PCF8574

PCF8574 الگوی زیر را به LCD منتقل می‌کند:
Bit0–Bit3 = پین‌های D4–D7
Bit4 = Backlight
Bit5 = Enable
Bit6 = Read/Write (همیشه صفر)
Bit7 = Register Select

```
unsigned char backlight = 0x08; // بک‌لایت روشن

void LCD_I2C_Write4Bits(unsigned char data)
{
unsigned char d = data | backlight;

// EN = 1
TWI_Write(d | 0x04);
delay_us(50);

// EN = 0
TWI_Write(d & ~0x04);
delay_us(50);
}

void LCD_I2C_Send(unsigned char value, unsigned char mode)
{
unsigned char high = (value & 0xF0) | mode;
unsigned char low = ((value<<4) & 0xF0) | mode;

TWI_Start();
TWI_Write(LCD_I2C_ADDR<<1); // SLA+W

LCD_I2C_Write4Bits(high);
LCD_I2C_Write4Bits(low);

TWI_Stop();
}

void LCD_Command(unsigned char cmd)
{
LCD_I2C_Send(cmd, 0x00);
}

void LCD_Data(unsigned char data)
{
LCD_I2C_Send(data, 0x01);
}
```

--------------------------------
## راه‌اندازی LCD

```
void LCD_Init()
{
delay_ms(50);

// Init sequence 4-bit
LCD_Command(0x33);
LCD_Command(0x32);
LCD_Command(0x28);
LCD_Command(0x0C);
LCD_Command(0x06);
LCD_Command(0x01);
delay_ms(2);
}
```

--------------------------------
## تابع چاپ رشته

```
void LCD_Print(char *str)
{
while(*str)
{
LCD_Data(*str++);
}
}
```

--------------------------------
## برنامه‌ی اصلی (main)

```
void main(void)
{
// TWI init
TWBR = 32; // 100kHz @8MHz
TWSR = 0x00;

LCD_Init();

LCD_Command(0x80); // Move to line 1
LCD_Print("Hello");

while(1);
}
```

--------------------------------
## اگر خواستی:
- نسخه با **interrupt TWI**
- نسخه مخصوص LCD با آدرس 0x20
- نسخه حرفه‌ای شامل Auto ACK checking
- یا کتابخانه‌ای تمیز و ساخت‌یافته

همه رو می‌تونم برات بنویسم.
 

saalek110

Well-Known Member
اتصال ماژول I2C (مبدل **PCF8574**) به LCD کاراکتری در **Proteus** دقیقاً همان الگوی ماژول‌های آماده I2C-LCD واقعی است.
Proteus معمولاً ماژول **I2C LCD** را دارد، ولی اگر از **IC جدا PCF8574** استفاده می‌کنی، باید **پایه‌به‌پایه** وصل کنی.

--------------------------------
## اتصال کامل PCF8574 به LCD 16x2

در تمام ماژول‌های I2C-LCD، سیم‌بندی به صورت زیر است:

### پایه‌های LCD
- **RS → P0**
- **RW → GND**
- **E → P2**
- **D4 → P4**
- **D5 → P5**
- **D6 → P6**
- **D7 → P7**
- **Backlight (LED+) → از طریق ترانزیستور یا مستقیم P3**
- **VSS → GND**
- **VDD → +5V**
- **Vo → پتانسیومتر (کنتراست)**

### پایه‌های PCF8574
- **P0 → RS**
- **P1 → RW** (در ماژول‌های آماده همیشه به GND وصل است)
- **P2 → E**
- **P3 → Backlight** (اگر خواستی ۰=خاموش، ۱=روشن)
- **P4 → D4**
- **P5 → D5**
- **P6 → D6**
- **P7 → D7**

### پایه‌های اصلی تغذیه و I2C
- **VCC → +5V**
- **GND → GND**
- **SCL → به SCL میکروکنترلر**
- **SDA → به SDA میکروکنترلر**
- **A0-A1-A2 → تنظیم آدرس (به GND یا +5 بستگی به آدرس)**

--------------------------------
## جمع‌بندی اتصالات (مهم‌ترین بخش)

PCF8574 → LCD

- P0 → RS
- P1 → RW (بهتر است RW را مستقیم GND کنی)
- P2 → E
- P3 → کنترل LED (BACKLIGHT)
- P4 → D4
- P5 → D5
- P6 → D6
- P7 → D7

LCD → تغذیه

- VDD → 5V
- VSS → GND
- Vo → پتانسیومتر 10k

PCF8574 → میکرو

- SDA → SDA
- SCL → SCL
- VCC → 5V
- GND → GND

--------------------------------
اگر بخواهی می‌توانم:
- **فایل Proteus آماده** شامل LCD + PCF8574 + AVR برایت درست کنم
- یا **شماتیک تصویری دقیق** برای اتصال
- و حتی **چک‌لیست عیب‌یابی** اگر کار نکند
 

saalek110

Well-Known Member
C:
    // مثال مفهومی با زبان C
    void Scan_I2C_Bus() {
        uint8_t address;
        uint8_t error;
        printf("Starting I2C Scan...\n");

        // آدرس‌های معتبر 7 بیتی از 0x08 تا 0x77 هستند
        for (address = 0x08; address < 0x78; address++) {
            // 1. ارسال سیگنال START
            I2C_Start();

            // 2. ارسال آدرس دستگاه (7 بیت آدرس + 1 بیت Read/Write)
            // برای اسکنر، فقط آدرس را ارسال می‌کنیم (یعنی بیت R/W برابر 0 است).
            // تابع I2C_SendByte باید علاوه بر ارسال بایت، پاسخ ACK را برگرداند.
            error = I2C_SendByte((address << 1) | 0); // آدرس 7 بیتی شیفت داده شده به چپ، با بیت R/W صفر (نوشتن)

            if (error == 0) { // اگر 0 باشد، یعنی ACK دریافت شده است
                printf("Device found at address 0x%02X\n", address);
                // اینجا می‌توانید آدرس پیدا شده را ذخیره کنید یا کاری انجام دهید.
            }
            // اگر خطا 0 نباشد (مثلاً NACK یا تایم‌اوت)، یعنی دستگاهی در این آدرس نیست.
            // نیازی به انجام کار خاصی نیست، حلقه به آدرس بعدی می‌رود.

            // 3. ارسال سیگنال STOP برای آزاد کردن باس
            I2C_Stop();

            // Optional: Small delay between scans if needed
            // delay_ms(1);
        }
        printf("I2C Scan finished.\n");
    }
 

saalek110

Well-Known Member
کد اسکن هنراه با کد سون سگمنت:

C:
#include <avr/io.h>
#include <util/delay.h>

// --- پیکربندی پایه‌ها ---
// پایه‌های سگمنت (مثلاً PORTD: D0-D6)
#define SEGMENT_PORT    PORTD
#define SEGMENT_DDR     DDRD
#define SEGMENT_MASK    0xFF // (1<<PD0) | (1<<PD1) | ... | (1<<PD6)

// پایه‌های انتخاب رقم (مثلاً PORTB: B0-B3)
#define DIGIT_PORT      PORTB
#define DIGIT_DDR       DDRB
// پایه‌های فعال‌کننده رقم (Common Cathode)
#define DIGIT_1_PIN     (1<<PB0)
#define DIGIT_2_PIN     (1<<PB1)
#define DIGIT_3_PIN     (1<<PB2)
#define DIGIT_4_PIN     (1<<PB3)

// --- نگاشت هگز به سگمنت (Common Cathode) ---
// الگوهای روشن برای 0-9 و A-F
// DP (نقطه اعشار) در اینجا لحاظ نشده برای سادگی
const uint8_t hex_to_segments[] = {
    // 0      1      2      3      4      5      6      7
    0x3F,  0x06,  0x5B,  0x4F,  0x66,  0x6D,  0x7D,  0x07,
    // 8      9      A      B      C      D      E      F
    0x7F,  0x6F,  0x77,  0x7C,  0x39,  0x5E,  0x79,  0x71
};

// --- متغیرهای سراسری ---
uint8_t display_buffer[4] = {0}; // بافر برای نگهداری 4 رقم هگز برای نمایش
volatile uint8_t current_digit = 0; // رقمی که الان باید نمایش داده شود (برای مالتی‌پلکسینگ)

// --- توابع کمکی ---

// فعال کردن یک رقم خاص و نمایش الگو
void set_digit_and_segments(uint8_t digit_index, uint8_t hex_value) {
    // خاموش کردن همه ارقام قبلی
    DIGIT_PORT |= (DIGIT_1_PIN | DIGIT_2_PIN | DIGIT_3_PIN | DIGIT_4_PIN); // همه را High می کنیم تا خاموش شوند (برای Common Cathode)

    // استخراج الگوی سگمنت برای مقدار هگز
    uint8_t segments = hex_to_segments[hex_value & 0x0F]; // فقط 4 بیت پایین استفاده می شود

    // پاک کردن بیت‌های سگمنت قبلی
    SEGMENT_PORT &= ~SEGMENT_MASK;
    // تنظیم بیت‌های سگمنت برای کاراکتر فعلی
    SEGMENT_PORT |= segments;

    // فعال کردن رقم مورد نظر
    switch (digit_index) {
        case 0: DIGIT_PORT &= ~DIGIT_1_PIN; break; // رقم اول (چپ ترین)
        case 1: DIGIT_PORT &= ~DIGIT_2_PIN; break; // رقم دوم
        case 2: DIGIT_PORT &= ~DIGIT_3_PIN; break; // رقم سوم
        case 3: DIGIT_PORT &= ~DIGIT_4_PIN; break; // رقم چهارم (راست ترین)
    }
}

// تابع اصلی آپدیت صفحه 7-segment (این تابع باید خیلی سریع تکرار شود)
void update_display() {
    // خاموش کردن رقم قبلی
    DIGIT_PORT |= (DIGIT_1_PIN | DIGIT_2_PIN | DIGIT_3_PIN | DIGIT_4_PIN);

    // نمایش رقم فعلی با مقدار از بافر
    set_digit_and_segments(current_digit, display_buffer[current_digit]);

    // رفتن به رقم بعدی برای نمایش بعدی
    current_digit++;
    if (current_digit >= 4) {
        current_digit = 0;
    }
}

// تابعی برای تنظیم آدرس مورد نظر روی صفحه
void display_i2c_address(uint8_t address) {
    // تبدیل آدرس 8 بیتی به 4 رقم هگز
    display_buffer[0] = (address >> 4) & 0x0F; // رقم اول (بالاترین)
    display_buffer[1] = address & 0x0F;        // رقم دوم

    // برای سادگی، دو رقم آخر را در 4 رقم نمایش می‌دهیم
    // مثلاً 0x3C -> " 3C" یا "003C"
    // اگر می‌خواهید "003C" نمایش داده شود:
    display_buffer[0] = (address >> 4) & 0x0F; // رقم اول (مثلاً 3 از 0x3C)
    display_buffer[1] = address & 0x0F;        // رقم دوم (مثلاً C از 0x3C)
    display_buffer[2] = 0x00; // صفر (برای نمایش 003C)
    display_buffer[3] = 0x00; // صفر
}

// --- تابع اصلی (main) ---
int main(void) {
    // --- تنظیمات اولیه ---
    // فعال کردن DDR برای پایه‌های سگمنت و ارقام
    SEGMENT_DDR |= SEGMENT_MASK;
    DIGIT_DDR |= (DIGIT_1_PIN | DIGIT_2_PIN | DIGIT_3_PIN | DIGIT_4_PIN);

    // اطمینان از خاموش بودن همه ارقام در ابتدا
    DIGIT_PORT |= (DIGIT_1_PIN | DIGIT_2_PIN | DIGIT_3_PIN | DIGIT_4_PIN);

    // --- اینجا باید توابع راه‌اندازی I2C میکروکنترلر خود را فراخوانی کنید ---
    // i2c_init(); // تابع فرضی برای شروع I2C

    uint8_t found_address = 0; // آخرین آدرس پیدا شده

    // --- حلقه اصلی اسکن ---
    for (uint8_t address = 0x08; address < 0x78; address++) {
        // --- تلاش برای برقراری ارتباط با دستگاه ---
        // این قسمت کد I2C شما است.
        // فرض کنید تابعی دارید به نام `i2c_probe(address)` که 1 برمی‌گرداند اگر ACK بگیرد، 0 در غیر اینصورت.

        // bus_start();
        // uint8_t error = bus_send_byte((address << 1) | 0); // ارسال آدرس برای نوشتن
        // bus_stop();
        //
        // if (error == 0) { // یعنی ACK گرفته شده
        //    found_address = address; // آدرس را ذخیره کن
        // }

        // برای تست، فرض می‌کنیم آدرس 0x3C پیدا شده
        if (address == 0x3C) { // آدرس فرضی که پیدا شده
            found_address = address;
            // نمایش آدرس پیدا شده روی 7-segment
            display_i2c_address(found_address);
            // مکث 1.5 ثانیه‌ای
            _delay_ms(1500);
        }

        // --- آپدیت صفحه نمایش در هر تکرار حلقه (برای مالتی‌پلکسینگ) ---
        // این بخش باید خیلی سریع اتفاق بیفتد.
        // اگر از تایمر و ISR برای مالتی‌پلکسینگ استفاده کنید، بهتر است.
        // اگر نه، اینجا هم می‌شود فراخوانی کرد ولی با وقفه.
        update_display();
        _delay_ms(2); // تاخیر کوتاه بین تغییر ارقام (فرکانس آپدیت حدود 250Hz)
    }

    // اگر هیچ دستگاهی پیدا نشد، نمایش "----" یا "None"
    if (found_address == 0) {
        // display_buffer = {'-', '-', '-', '-'}; // نیاز به تابع نمایش کاراکتر خاص
        // نمایش "----" یا "NONE" رو بسته به فضای نمایش شما
    }


    // --- حلقه بینهایت ---
    while (1) {
        // در حالت عادی، صفحه نمایش باید توسط یک تایمر و ISR آپدیت شود
        // اما اگر این را در حلقه main گذاشتیم، حداقل باید آپدیت صفحه انجام شود
        update_display();
        _delay_ms(2); // تنظیم این تاخیر، سرعت مالتی‌پلکسینگ را تعیین می‌کند
    }

    return 0;
}
 
بالا