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