Вторник, 26.11.2024
Мой сайт
Меню сайта
Статистика

Онлайн всего: 47
Гостей: 47
Пользователей: 0
Главная » 2022 » Январь » 30 » Драйвер OLED-дисплея, директивы предкомпилятору и т.д
08:56
Драйвер OLED-дисплея, директивы предкомпилятору и т.д

Драйвер OLED-дисплея, директивы предкомпилятору и т.д

Есть у меня такой дисплей:
https://www.chipdip.ru/product/0.96inch-oled-a


… драйвер к которому можно взять тут (Adafruit_SSD1306-master):
https://www.chipdip.ru/product0/9000318627
Для работы ещё потребуется Adafruit-GFX-Library-master, потому что там наследуемый класс (сейчас не важно, что это).
На этот раз я не буду портить текст сокращениями, потому что такие программы как раз пишутся под возможно большее число вариантов использования. В тех файлах именно это во всей красе.
Но пока про дисплей. Смотрю файл Adafruit_SSD1306.h и вижу там три способа создания объекта для работы с дисплеем (три конструктора объекта):
class Adafruit_SSD1306 : public Adafruit_GFX {
 
public:
 
Adafruit_SSD1306(int8_t SID, int8_t SCLK, int8_t DC, int8_t RST, int8_t CS);
 
Adafruit_SSD1306(int8_t DC, int8_t RST, int8_t CS);
 
Adafruit_SSD1306(int8_t RST = -1);
Смотрю текст с часами и вижу там такое:
Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
.. т.е выбран первый вариант конструктора, в котором можно задать полный список PIN-ов для SPI подключения.
Пример с часами тоже в каком-то смысле - объект. У него много чего, чем можно управлять. Там и дисплей, и часы, и пищалка. Потому у него тоже есть файл с расширением *.H. Смотрю этот clock.h и вижу у нем самую стандартную директиву тому самому предкомпилятору define (чаще всего используемую):
#define       OLED_MOSI                      9
#define       OLED_CLK                       10
#define       OLED_DC                        11
#define       OLED_CS                        12
#define       OLED_RESET                     13
… которая даёт мне возможность писать в программе не ничего незначащие цифры – номера PIN-ов а их назначения большими буквами. Так проще и писать и читать текст программы. Вот только машине это все не надо. Даже помешало бы захламление памяти, если бы я решила завести переменные с такими названиями. Потому возник компромиссный механизм – предкомпиляция. До компиляции во всём тексте удобные для человека названия (определенные в define) будут заменены в данном случае на просто цифры – номера PIN-ов.
Вернусь к дисплею и попытаюсь понять, кто тут кто:

… потому что комбинация не совсем совпадает с тем, что должно быть для SPI:
https://akostina76.ucoz.ru/blog/2022-01-25-7444

.. а, с другой стороны, PIN D/C (данные или команда по описанию) как раз совпадает с протоколом передачи в LCD дисплея:
https://akostina76.ucoz.ru/blog/2022-01-29-7450

Ещё я вижу, что в сокращённый вариант (второй конструктор) передаются только DC, RST и CS. Видимо в этом случае MOSI и CLK должны быть подключены к PIN-ам по умолчанию (MOSI=11, CLK=SCK=clock = часы = 13).
Тексты всех трёх конструкторов, благо они короткие
Adafruit_SSD1306::Adafruit_SSD1306(int8_t SID, int8_t SCLK, int8_t DC, int8_t RST, int8_t CS) : Adafruit_GFX(SSD1306_LCDWIDTH, SSD1306_LCDHEIGHT) {
 
cs = CS;
  rst = RST;
  dc = DC;
  sclk = SCLK;
  sid = SID;
  hwSPI = false;
}

// constructor for hardware SPI - we indicate DataCommand, ChipSelect, Reset
Adafruit_SSD1306::Adafruit_SSD1306(int8_t DC, int8_t RST, int8_t CS) : Adafruit_GFX(SSD1306_LCDWIDTH, SSD1306_LCDHEIGHT) {
  dc = DC;
  rst = RST;
  cs = CS;
  hwSPI = true;
}

// initializer for I2C - we only indicate the reset pin!
Adafruit_SSD1306::Adafruit_SSD1306(int8_t reset) :
Adafruit_GFX(SSD1306_LCDWIDTH, SSD1306_LCDHEIGHT) {
  sclk = dc = cs = sid = -1;
  rst = reset;
}

… потому что в них не делается ничего кроме заполнения внутренних переменных объекта номерами PIN-ов. Точнее, почти ничего. Последний вариант записывает в номера PIN-ов для SPI значения -1. Дальше такие значения будут означать, что передача ведётся по I2C протоколу. А второй конструктор (в котором мало PIN-ов - параметров) присваивает значение «истина» внутренней переменной hwSPI. А это дальше будет означать, что для передачи будет использован стандартный SPI (со стандартными MOSI и CLK).
Стандартное использование дисплея такое:
Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
void setup()   {               
 
display.begin(SSD1306_SWITCHCAPVCC);
 
display.drawPixel(10, 10, WHITE);
 
display.display();
}

.. т.е после создания объекта полагается быть функции begin(). Вот её начало :
void Adafruit_SSD1306::begin(uint8_t vccstate, uint8_t i2caddr, bool reset) {
  _
vccstate = vccstate;
  _
i2caddr = i2caddr; // Адрес I2C устройства

  //
set pin directions
  if (sid != -1
  {   //  Не
I2C
    pinMode(dc, OUTPUT);
   
pinMode(cs, OUTPUT);
#
ifdef HAVE_PORTREG
    csport      = portOutputRegister(digitalPinToPort(cs));
   
cspinmask   = digitalPinToBitMask(cs);
   
dcport      = portOutputRegister(digitalPinToPort(dc));
   
dcpinmask   = digitalPinToBitMask(dc);
#
endif
/// Выше было для всех типов SPI передачи
   
if (!hwSPI){ // Переданы все SPI пины
      //
set pins for software-SPI
      pinMode(sid, OUTPUT);
     
pinMode(sclk, OUTPUT);
#
ifdef HAVE_PORTREG
      clkport     = portOutputRegister(digitalPinToPort(sclk));
     
clkpinmask  = digitalPinToBitMask(sclk);
     
mosiport    = portOutputRegister(digitalPinToPort(sid));
     
mosipinmask = digitalPinToBitMask(sid);
#
endif
      } // Конец «Переданы все SPI пины»
   
if (hwSPI){  // Передана часть SPI пинов
      
SPI.begin();
#
ifdef SPI_HAS_TRANSACTION
      SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
#
else
      SPI.setClockDivider (4);
#
endif
    } // Конец “// Передана часть SPI пинов»
  } // Конец “Не
I2C»
 
else
  {
    //
I2C Init
    Wire.begin();
#
ifdef __SAM3X8E__
    // Force 400 KHz I2C, rawr! (Uses pins 20, 21 for SDA, SCL)
    TWI1->TWI_CWGR = 0;

    TWI1->TWI_CWGR = ((VARIANT_MCK / (2 * 400000)) - 4) * 0x101;
#endif
  } // конец “I2C Init»
  if ((reset) && (rst >= 0)) {
    // Setup reset pin direction (used by both SPI and I2C)
    pinMode(rst, OUTPUT);
    digitalWrite(rst, HIGH);
    // VDD (3.3V) goes high at start, lets just chill for a ms
    delay(1);
    // bring reset low
    digitalWrite(rst, LOW);
    // wait 10ms
    delay(10);
    // bring out of reset
    digitalWrite(rst, HIGH);
    // turn on VCC (9V?)
  }

  // Команды дисплею:
  ssd1306_command(SSD1306_DISPLAYOFF);                    // 0xAE
  ssd1306_command(SSD1306_SETDISPLAYCLOCKDIV);            // 0xD5
  ssd1306_command(0x80);                                  // the suggested ratio 0x80


Первое, что я тут вижу - все три варианты (два SPI и I2C) засунуты в одну функцию. Разделены они стандартными if… else. Так обычно и делают.
Второе, что вижу - то, что меня интересовало. Похоже, что любой пин можно назначить портом для SPI. Для этого надо написать ему что-то типа
clkport     = portOutputRegister(digitalPinToPort(sclk));  
… И, наконец я вижу как работают стандартные инструменты передачи (на стандартных пинах). Здесь они инициализируются функциями SPI.begin(); Wire.begin();,  дальше передача байта потребует тоже по одной функции. Быстро, просто и коротка. Но на стандартных пинах.
А ещё я тут вижу #ifdef #endif. Это тоже директивы предкомпилятору. Если переменная условия не определена, текст перед компиляцией будет просто изъят. Появилось, скорее всего, тогда же когда и define, но в обычной жизни используется довольно редко. Язык Си был уже на больших машинах. И, конечно, на персональных компьютерах он тоже был. Только чуть разный. Отличие, правда, не в языке, а в функциях и библиотеках. Но какой-нибудь чисто расчётный текст вообще мог быть запущен где угодно.  А если не расчётный, а что-то более сложное хочется запускать в принципиально разных средах? Так и появились тексты из таких кусков для разных сред использования. Достаточно поменять что-то в начале текста и можно использовать в другой среде.
Пора вспомнить, что я читаю текст драйвера даже не дисплея, а его микросхемы – контроллера SSD1306. К этому SSD1306 могут быть подключены разные дисплеи. 128*64 точек или 128*32 точки, например. Программа требуется одна и та же. Только в одном тексте будет где-то 64. а в другом 32. С этим отличием успешно справится даже директива define. А ещё сам управляющий контроллер у меня может быть каким угодно. Они тоже чуть по-разному работают. Для учёта разной работы контроллеров тут обычно и используются эти #ifdef #endif., которые на больших компьютерах нужны для использования и компиляции в разных операционных системах.
Текст функции – command, передающей байт команды дисплею:
void Adafruit_SSD1306::ssd1306_command(uint8_t c) {
 
if (sid != -1)
  {
    //
SPI
#ifdef HAVE_PORTREG
    *csport |= cspinmask;
    *
dcport &= ~dcpinmask;
    *
csport &= ~cspinmask;
#
else
    digitalWrite(cs, HIGH);
   
digitalWrite(dc, LOW);
    digitalWrite(cs, LOW);
#
endif
    fastSPIwrite(c);
#ifdef HAVE_PORTREG
    *csport |= cspinmask;
#
else
    digitalWrite(cs, HIGH);
#
endif
  }
 
else
  {
    //
I2C
    uint8_t control = 0x00;   // Co = 0, D/C = 0
   
Wire.beginTransmission(_i2caddr);
   
Wire.write(control);
    Wire.write(c);
    Wire.endTransmission();
  }
}

В случае LCD дисплея была запись байта в пины и тик часов. Здесь вызовы функций fastSPIwrite(c) для SPI и  Wire.write(c) для I2C. Отмечу только, что пин DC (данные или команда) устанавливается для команд в 0.
Запись SPI:
inline void Adafruit_SSD1306::fastSPIwrite(uint8_t d) {
 
if(hwSPI) {
    (
void)SPI.transfer(d);
  } else {
   
for(uint8_t bit = 0x80; bit; bit >>= 1) {
#
ifdef HAVE_PORTREG
      *clkport &= ~clkpinmask;
     
if(d & bit) *mosiport |=  mosipinmask;
     
else        *mosiport &= ~mosipinmask;
      *
clkport |=  clkpinmask;
#
else
      digitalWrite(sclk, LOW);
     
if(d & bit) digitalWrite(sid, HIGH);
     
else        digitalWrite(sid, LOW);
     
digitalWrite(sclk, HIGH);
#endif
    }
  }
}

В случае стандартного SPI одна функция (SPI.transfer()). А в самопальном варианте SPI передачи обращает на себя внимание отсутствие задержек в микросекундах (выключаются часы, загружается бит из MOSI, включаются часы).
Про самопальную передачу I2C по любым пинам, похоже, тут:
https://www.instructables.com/id/Interfacing-LCD-With-Arduino-Using-Only-3-Pins/

Не очень хорошая штука у этого дисплея с передачей картинки. Если LCD дисплею я вначале командой передаю место в памяти, а потом букву, которую туда надо записать (после чего буква отображается на экране), то здесь я вначале «рисую» всё в специальном выделенном блоке памяти, а потом передаю микросхеме весь экран командой display(). Для маленького экрана это 128*64 бита. Для большого при такой организации работы:
https://akostina76.ucoz.ru/blog/2022-01-28-7448

… будет 1280 * 1024 бит. Понятно, что все сейчас быстро работает. Но, наверное, стоит учитывать и реальные потребности, особенно если будет использован такой метод передачи.

Просмотров: 118 | Добавил: akostina76 | Рейтинг: 0.0/0
Всего комментариев: 0
Имя *:
Email *:
Код *:
Форма входа
Поиск
Календарь
«  Январь 2022  »
ПнВтСрЧтПтСбВс
     12
3456789
10111213141516
17181920212223
24252627282930
31
Архив записей
Друзья сайта
  • Официальный блог
  • Сообщество uCoz
  • FAQ по системе
  • Инструкции для uCoz
  • Copyright MyCorp © 2024
    Бесплатный конструктор сайтов - uCoz