В этой и следующих статьях приведу несколько примеров работы с популярным графическим дисплеем на контроллере SSD1306. Дисплей имеет такие интерфейсы подключения как 6800/8000, параллельный интерфейс, I2C и SPI. В моих примерах я буду использовать 7-ми выводной модуль дисплея разрешением 128x64, подключение будет по аппаратному SPI, также будет доступен исходник с интерфейсом I2C. Микроконтроллер буду использовать ATmega328, с достаточно большим объемом памяти, что пригодится например в примерах с анимацией.
В этой же статье напишем простую программу секундомера с точностью 0,1 секунда. Максимальное время отсчета 60 минут. Для управления используются две кнопки: "Старт/Стоп" для запуска и остановки секундомера, кнопка "Сброс" для обнуления времени, причем сброс можно сделать только тогда когда секундомер остановлен. ATmega328 тактируется от внешнего кварцевого резонатора частотой 8МГц. Биты конфигурации: HIGH: 0xD9, LOW: 0xEF.
Схема устройства
Шестнадцатиричный таймер (Т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 671 | Скачать |




Комментарии
Запускаю Вашу библиотеку по i2c. Столкнулся с такой проблемой показывает только пол экрана (ровно разделен пополам), на верхней половине отображается "секундомер" и циферблат, а на второй половине статические помехи (если передернуть питание рисунок помехи меняется).
Корректировку регистров (0x3F, // 1/64 duty (значение по умолчанию), 0x1F - 128x32, 0x3F - 128x64 и 0x12, // 0x02 - 128x32, 0x12 - 128x64) сделал.
Дисплей рабочий - проверял на другой библиотеке (но они слишком громоздкие).
Что я еще мог упустить?