نمایش ولتاژ اندازه‌گیری شده از ADS1110 روی نمایشگر OLED1306 با استفاده از میکروکنترلر ATTiny13

برای نمایش ولتاژ اندازه‌گیری شده از ADS1110 روی نمایشگر OLED1306 با استفاده از میکروکنترلر ATTiny13، باید کد قبلی را توسعه دهیم تا بتواند با هر دو ماژول ارتباط برقرار کند. این کار چالش‌برانگیز است، زیرا هر دو ماژول از پروتکل I2C استفاده می‌کنند و ما باید کتابخانه I2C را به صورت نرم‌افزاری پیاده‌سازی کنیم.

شماتیک و اتصالات

برای این پروژه، اتصالات سخت‌افزاری زیر لازم است:

  • VCC از ATTiny13 به VCC هر دو ماژول ADS1110 و OLED1306
  • GND از ATTiny13 به GND هر دو ماژول
  • پایه PB0 از ATTiny13 به SDA هر دو ماژول
  • پایه PB1 از ATTiny13 به SCL هر دو ماژول
  • ولتاژ ورودی شما به پایه VIN+ ماژول ADS1110
  • پایه VIN- ماژول ADS1110 به GND متصل می‌شود.
  • دو مقاومت ۴.۷ کیلواهمی به عنوان مقاومت‌های پول‌آپ (Pull-up Resistors) روی خطوط SDA و SCL به VCC متصل می‌شوند.

توجه: به دلیل محدودیت‌های ATTiny13، این کد بهینه شده و فقط توابع ضروری را برای نمایش یک مقدار ساده روی OLED پیاده‌سازی می‌کند.


نمونه کد

این نمونه کد، عملکرد کد قبلی را با اضافه کردن توابع مربوط به نمایشگر OLED1306 تکمیل می‌کند.

C++

#include <avr/io.h>
#include <util/delay.h>
#include <stdio.h>

// I2C defines for ATTiny13
#define SDA_PORT PORTB
#define SDA_PIN PB0
#define SCL_PORT PORTB
#define SCL_PIN PB1
#define ADS1110_ADDR 0x48 // I2C address of ADS1110
#define OLED_ADDR 0x3C   // I2C address of OLED1306

// OLED commands
#define OLED_CMD 0x00
#define OLED_DATA 0x40

// Function prototypes
void i2c_init();
void i2c_start();
void i2c_stop();
void i2c_write(uint8_t data);
uint8_t i2c_read(uint8_t ack);
int16_t readADS1110();
void oled_init();
void oled_command(uint8_t command);
void oled_data(uint8_t data);
void oled_string(const char *str);
void oled_clear();
void oled_set_cursor(uint8_t col, uint8_t page);

void setup() {
  i2c_init();
  oled_init();
  oled_clear();
}

void loop() {
  int16_t adcValue = readADS1110();
  // Convert ADC value to voltage
  float voltage = (float)adcValue * (2.048 / 32768.0); // 2.048V is Vref, 32768 is half of 16bit range

  char voltageStr[10];
  sprintf(voltageStr, "%.2f V", voltage);

  oled_clear();
  oled_set_cursor(0, 2);
  oled_string("Voltage:");
  oled_set_cursor(0, 4);
  oled_string(voltageStr);

  _delay_ms(1000);
}

// I2C Software Implementation (Same as before)
void i2c_init() {
  DDRB &= ~((1 << SDA_PIN) | (1 << SCL_PIN));
  PORTB |= (1 << SDA_PIN) | (1 << SCL_PIN);
}

void i2c_start() {
  SDA_PORT |= (1 << SDA_PIN);
  SCL_PORT |= (1 << SCL_PIN);
  DDRB |= (1 << SDA_PIN); 
  _delay_us(5);
  DDRB &= ~(1 << SDA_PIN); 
  _delay_us(5);
}

void i2c_stop() {
  DDRB &= ~(1 << SCL_PIN);
  _delay_us(5);
  DDRB |= (1 << SDA_PIN);
  _delay_us(5);
  DDRB &= ~((1 << SDA_PIN) | (1 << SCL_PIN));
}

void i2c_write(uint8_t data) {
  for (uint8_t i = 0; i < 8; i++) {
    DDRB |= (1 << SCL_PIN);
    _delay_us(5);
    if (data & 0x80) {
      DDRB &= ~(1 << SDA_PIN);
    } else {
      DDRB |= (1 << SDA_PIN);
    }
    _delay_us(5);
    DDRB &= ~(1 << SCL_PIN);
    _delay_us(5);
    data <<= 1;
  }
  DDRB &= ~(1 << SDA_PIN);
  _delay_us(5);
  while (PINB & (1 << SDA_PIN));
}

uint8_t i2c_read(uint8_t ack) {
  uint8_t data = 0;
  DDRB &= ~(1 << SDA_PIN);
  for (uint8_t i = 0; i < 8; i++) {
    DDRB |= (1 << SCL_PIN);
    _delay_us(5);
    data <<= 1;
    if (PINB & (1 << SDA_PIN)) {
      data |= 1;
    }
    DDRB &= ~(1 << SCL_PIN);
    _delay_us(5);
  }
  if (ack) {
    DDRB |= (1 << SDA_PIN);
  } else {
    DDRB &= ~(1 << SDA_PIN);
  }
  return data;
}

// ADS1110 Reading Function (Same as before)
int16_t readADS1110() {
  int16_t value = 0;
  uint8_t highByte, lowByte;

  i2c_start();
  i2c_write(ADS1110_ADDR << 1); 
  i2c_write(0x8C);
  i2c_stop();

  _delay_ms(100);

  i2c_start();
  i2c_write((ADS1110_ADDR << 1) | 1);
  highByte = i2c_read(1);
  lowByte = i2c_read(0);
  i2c_stop();

  value = (highByte << 8) | lowByte;
  return value;
}

// OLED Functions
void oled_init() {
  oled_command(0xAE); // Display off
  oled_command(0x20); // Set Memory Addressing Mode
  oled_command(0x00); // Horizontal Addressing Mode
  oled_command(0xB0); // Set Page Start Address
  oled_command(0xC8); // Set COM Output Scan Direction
  oled_command(0x00); // Set low column address
  oled_command(0x10); // Set high column address
  oled_command(0x40); // Set start line address
  oled_command(0x81); // set contrast control register
  oled_command(0xFF);
  oled_command(0xA1); // set segment re-map 0 to 127
  oled_command(0xA6); // set normal display
  oled_command(0xA8); // set multiplex ratio(1 to 64)
  oled_command(0x3F);
  oled_command(0xA4); // 0xA4: output follows RAM content, 0xA5: output ignores RAM content
  oled_command(0xD3); // set display offset
  oled_command(0x00);
  oled_command(0xD5); // set display clock divide ratio/oscillator frequency
  oled_command(0xF0);
  oled_command(0xD9); // set pre-charge period
  oled_command(0x22);
  oled_command(0xDA); // set com pins hardware configuration
  oled_command(0x12);
  oled_command(0xDB); // set VCOMH
  oled_command(0x20);
  oled_command(0x8D); // set DC-DC enable
  oled_command(0x14);
  oled_command(0xAF); // turn on OLED
}

void oled_command(uint8_t command) {
  i2c_start();
  i2c_write(OLED_ADDR << 1);
  i2c_write(OLED_CMD);
  i2c_write(command);
  i2c_stop();
}

void oled_data(uint8_t data) {
  i2c_start();
  i2c_write(OLED_ADDR << 1);
  i2c_write(OLED_DATA);
  i2c_write(data);
  i2c_stop();
}

void oled_clear() {
  for (uint8_t page = 0; page < 8; page++) {
    oled_set_cursor(0, page);
    for (uint8_t col = 0; col < 128; col++) {
      oled_data(0x00);
    }
  }
}

void oled_set_cursor(uint8_t col, uint8_t page) {
  oled_command(0xB0 + page); // Set page start address
  oled_command(((col & 0xF0) >> 4) | 0x10); // Set high column address
  oled_command((col & 0x0F) | 0x00); // Set low column address
}

void oled_string(const char *str) {
  while (*str) {
    if (*str >= 32 && *str <= 127) {
      for (uint8_t i = 0; i < 5; i++) {
        oled_data(pgm_read_byte(&font_5x7[*str - 32][i]));
      }
      oled_data(0x00); // Spacing between characters
    }
    str++;
  }
}

توضیحات کد

  1. تابع readADS1110():این تابع همانند کد قبلی، مقدار خام ۱۶ بیتی را از ADS1110 می‌خواند.
  2. تبدیل مقدار ADC به ولتاژ:مقدار خوانده شده از ADS1110 یک عدد خام است که باید به ولتاژ تبدیل شود. ADS1110 دارای یک ولتاژ مرجع داخلی 2.048V است. با توجه به دقت ۱۶ بیتی، دامنه مقادیر آن از -32768 تا +32767 است.فرمول تبدیل به ولتاژ به صورت زیر است:$Voltage = (ADC\_Value) * (V_{ref} / (2^{15} – 1))$یا به صورت ساده‌تر:$Voltage = (ADC\_Value) * (2.048 / 32768.0)$در کد از 2.048 به جای 2.048 و از 32768.0 به جای 32767 استفاده شده تا دقت بیشتر و محاسبات ساده‌تری حاصل شود.
  3. توابع OLED (oled_…):این توابع به صورت نرم‌افزاری با نمایشگر OLED ارتباط برقرار می‌کنند.
  4. oled_init(): این تابع با ارسال دستورات I2C، نمایشگر OLED را راه‌اندازی و پیکربندی می‌کند.
  5. oled_command() و oled_data(): این توابع به ترتیب برای ارسال دستورات و داده‌ها به OLED استفاده می‌شوند.
  6. oled_clear(): کل صفحه نمایش را پاک می‌کند.
  7. oled_set_cursor(): موقعیت نمایش متن را تعیین می‌کند.
  8. oled_string(): این تابع یک رشته را روی OLED نمایش می‌دهد. توجه: برای این تابع، باید داده‌های فونت مناسب با سایز ۵x۷ را به صورت یک آرایه به کد اضافه کنید. این داده‌ها به دلیل حجم بالا در این مثال حذف شده‌اند.
  9. تابع loop():در این تابع، ابتدا مقدار ولتاژ از ADS1110 خوانده می‌شود. سپس مقدار ولتاژ با استفاده از sprintf به یک رشته تبدیل می‌شود تا بتوان آن را روی نمایشگر OLED چاپ کرد. در نهایت، با استفاده از توابع OLED، صفحه نمایش پاک شده و مقدار ولتاژ روی آن نمایش داده می‌شود.

چالش‌ها و نکات مهم

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

این کد یک نقطه شروع خوب برای پروژه‌تان است. اگر با خطای حافظه یا مشکلات مشابه مواجه شدید، می‌توانید با حذف توابع غیرضروری یا استفاده از یک میکروکنترلر با حافظه بیشتر (مانند ATTiny85 یا ATmega328) مشکل را حل کنید.

همچنین بررسی کنید

ساخت پروب اسیلوسکوپ برای اندازه گیری توان (قسمت دوم)

در قسمت یک، ما محدودیت های پهنای باند پروب های غیرفعال را پوشش دادیم و …

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *