В этой и следующих статьях приведу несколько примеров работы с популярным графическим дисплеем на контроллере SSD1306. Дисплей имеет такие интерфейсы подключения как 6800/8000, параллельный интерфейс, I2C и SPI. В моих примерах я буду использовать 7-ми выводной модуль дисплея разрешением 128x64, подключение будет по аппаратному SPI, также будет доступен исходник с интерфейсом I2C. Микроконтроллер буду использовать ATmega328, с достаточно большим объемом памяти, что пригодится например в примерах с анимацией.

В этой же статье напишем простую программу секундомера с точностью 0,1 секунда. Максимальное время отсчета 60 минут. Для управления используются две кнопки: "Старт/Стоп" для запуска и остановки секундомера, кнопка "Сброс" для обнуления времени, причем сброс можно сделать только тогда когда секундомер остановлен. ATmega328 тактируется от внешнего кварцевого резонатора частотой 8МГц. Биты конфигурации: HIGH: 0xD9, LOW: 0xEF.

Схема устройства

Подключение графического дисплея SSD1306 к микроконтроллерам AVR. Делаем простой секундомер.

Шестнадцатиричный таймер (Т1) настроен на прерывание 0,1 секунды, в обработчике прерывания инкрементируются значения десятых долей секунды, секунды и минуты.

Назначение выводов дисплея:

GND - общий вывод;
VDD - плюс питания(3,3 - 5V);
SCK(D0) - вход тактовой частоты;
SDA(D1) - вывод данных;
RES - вывод сброса. Когда установлен лог.0 выполняется полная инициализация драйвера дисплея. Для нормальной работы должен быть переведен лог.1;
DC - управляющий вход, данные или команда;
CS - выбор драйвера дисплея, активный уровень лог.0

Функция записи данных в дисплей в режиме SPI проста:

1 - Выбираем режим данные/команда. Чтобы отправить команду на выводе DC должен быть низкий лог. уровень, чтобы отправить данные на выводе DC должен быть высокий лог. уровень;
2 - Активируем драйвер дисплея, CS - низкий лог. уровень;
3 - Передаем данные в регистр SPDR;
4 - Ждем окончания передачи;
5 - Деактивируем драйвер дисплея, CS - высокий лог. уровень.

void disp_write(uint8_t mode, uint8_t data) // Режим: 1-данные, 0-команда
{
if(mode) DC_H; // Режим данных
else DC_L; // Режим команды
CS_L; // выбор драйвера дисплея, активен
SPDR = data; // Передаем данные в дисплей
while(!(SPSR & (1<<SPIF))); // Ждем окончания передачи
CS_H; // выбор драйвера дисплея, неактивен
}

Процедура инициализации дисплея:

1 - Выводы управления дисплеем конфигурируем на выход;
2 - Настраиваем интерфейс SPI, активируем, режим мастер, частота шины Fosc/64;
3 - садим линию синхронизации SCK на минус;
4 - Деактивируем драйвер дисплея;
5 - Производим сброс дисплея;
6 - Поочередно передаем команды конфигурации в дисплей.

void display_init(void)
{
SCK_DDR; // Выводы настроены на выход
DAT_DDR;
RST_DDR;
DC_DDR;
CS_DDR;   

SPCR |= (1 << SPE) // Разрешаем SPI
       |(1 << MSTR) // Режим "мастер"
	   |(1 << SPR1); // Скорость SPI fosc/64
SCK_L; // На линии SCK лог.ноль
CS_H; // выбор драйвера дисплея, неактивен
RST_L; // Сброс дисплея
_delay_us(3); // Небольшая задержка
RST_H; // Дисплей в нормальном состоянии
// Посылаем команды в дисплей из массива инициализации
for(uint8_t i = 0; i < sizeof init; i++) disp_write(0, pgm_read_byte(&init[i]));
}

Команды инициализации дисплея разрешением 128x64 будут такие:

0xAE, // Выключить дисплей

0xD5, // Настройка частоты обновления дисплея
0x80, 
///+----- делитель 0-F/ 0 - деление на 1
//+------ частота генератора. по умочанию 0x80
0xA8, // Установить multiplex ratio
0x3F, // 1/64 duty (значение по умолчанию), 0x1F - 128x32, 0x3F - 128x64

0xD3, // Смещение дисплея (offset)
0x00, // Нет смещения

0x40, // Начала строки начала разверки 0x40 с начала RAM

0x8D, // Управление внутреним преобразователем
0x14, // 0x10 - отключить (VCC подается извне) 0x14 - запустить внутрений DC/DC

0x20, // Режим автоматической адресации
0x00, // 0-по горизонтали с переходом на новую страницу (строку)
      // 1 - по вертикали с переходом на новую строку
      // 2 - только по выбранной странице без перехода 

0xA1, // Режим разверки по странице (по X)
      // A1 - нормальный режим (слева/направо) A0 - обратный (справа/налево)

0xC8, // Режим сканирования озу дисплея
      // для изменения системы координат
      // С0 - снизу/верх (начало нижний левый угол)
      // С8 - сверху/вниз (начало верний левый угол)
0xDA, // Аппаратная конфигурация COM
0x12, // 0x02 - 128x32, 0x12 - 128x64

0x81, // Установка яркости дисплея
0x8F, // 0x8F..0xCF

0xD9, // Настройка фаз DC/DC преоразователя
0xF1, // 0x22 - VCC подается извне / 0xF1 для внутренего

0xDB, // Установка уровня VcomH
0x40, // Влияет на яркость дисплея 0x00..0x70

0xA4, // Режим нормальный

0xA6, // 0xA6 - нет инверсии, 0xA7 - инверсия дисплея

0xAF  // Дисплей включен

Для отображения данных используем стандартный шрифт 5x8(русский или английский), для этого к проекту подключим файл font.h, там находится коды символов латиницы ASCII[0x20-0x7F], и символов кириллицы CP1251[0xC0-0xFF]

Функция вывода символа 5x8 такая:

void print_char_5x8(uint8_t column, uint8_t string, uint8_t sign) // 0..120 / 0..7 / Символ
{
if(column <= 120 && string < 8) // Проверяем установку столбца и строки 
  {
  if((sign >= 0x20) && (sign <= 0x7F))
  sign -= 32;// Смещение в таблице для символов ASCII[0x20-0x7F]
  else if (sign >= 0xC0)
  sign -= 96; // Смещение в таблице для символов CP1251[0xC0-0xFF]
  else  sign = 85; // Остальные игнорируем (их просто нет в таблице для экономии памяти)
  
  for(uint8_t i = 0; i < 5; i++)
	{
	scr_buffer[128*string + column + i] = pgm_read_byte(&font_5x8[5*sign + i]);
	}
  }
}

Код программы с подробными комментариями:

// Подключение графического дисплея SSD1306 к микроконтроллерам AVR. Секундомер.

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

#include "ssd1306.h"

volatile uint8_t m_sec, sec, min;
uint8_t lock, start_stop = 0;

// Прерывание по сравнению T1, отсчет 0,1с
ISR(TIMER1_COMPA_vect)
{   
m_sec++;  // Увеличиваем счётчик
if(m_sec >= 10)
{
sec++; // Увеличиваем секунды 
m_sec = 0;
}
if(sec >= 60)
{
min++; // Увеличиваем минуты
m_sec = 0; sec = 0;
}
if(min >= 60) // Если прошел час обнуляем все
{
m_sec = 0; sec = 0; min = 0; 
}
}

int main(void)
{
// Настройка портов ввода/вывода
DDRC =  0x00; // Входы кнопок
PORTC=  0xFF;

// Настройка таймера 1
OCR1A  = 3125; // Для отсчёта 0,1 сек в регистр сравнения записываем 3125
// 8000000Гц/256/3125 = прерывание 10 раз в секунду
TCCR1B |= (1 << WGM12)|(1 << CS12); // Режим СТС, делитель на 256
TIMSK1 |= (1 << OCIE1A); // Разрешение прерывания по сравнению T1 

display_init(); // Инициализация дисплея
screen_clear(); // Очистка экрана

sei(); // Глобально разрешаем прерывания

while(1)
{
if((PINC & (1 << PC0)) == 0 && lock) // Если нажата кнопка
{
start_stop ^= 1; // Переключаем состояние
lock = 0; // Блокировка
_delay_ms(10);
}	
if(PINC & (1 << PC0)) lock = 1; // Снимаем блокировку

if(start_stop) 
{
TCCR1B |= (1 << WGM12)|(1 << CS12); // Запускаем Т1
}
else 
{
TCCR1A = 0x00; // Останавливаем T1
TCCR1B = 0x00;
TCNT1 = 0x0000;
}

if((PINC & (1 << PC1)) == 0 && start_stop == 0) // Если нажата кнопка и секундомер остановлен
{
m_sec = 0; sec = 0; min = 0; // Обнуляем все 
_delay_ms(10);
while((PINC & (1 << PC1)) == 0){} // Ждем отпускания кнопки
}	

print_string_5x8(0,0,"СЕКУНДОМЕР"); // Выводим строку
print_char_5x8(0,2, (min/10)%10 + '0'); // Выводим символ
print_char_5x8(6,2, min%10 + '0');
print_char_5x8(12,2, ':');
print_char_5x8(18,2, (sec/10)%10 + '0');
print_char_5x8(24,2, sec%10+ '0');
print_char_5x8(30,2, ':');
print_char_5x8(36,2, m_sec%10+ '0');
screen_update(); // Обновляем экран
}

}


Архив для статьи " Подключение графического дисплея SSD1306 к микроконтроллерам AVR. Делаем простой секундомер"
Описание:

Проект AVRStudio4

Размер файла: 31.8 KB Количество загрузок: 1 489 Скачать