Попытаюсь более-менее разъяснить, что к чему.
Изначально прошивка делалась на ATmega16 с 1кБ памяти. При этом поддерживались разные дисплеи - KS0108A/B (128x64), LS020 (176x132) и даже знакосинтезирующие. Естественно, что ввиду разного размера в пикселах любые экраны приходилось отрисовывать для каждого дисплея по-своему. Например, функция отображения информации от пульта (тестовый режим проверки пульта):
Спойлер
Код: Выделить всё
void showRC5Info(uint16_t rc5Buf)
{
#if defined(KS0108)
ks0108LoadFont(font_ks0066_ru_08, 1);
ks0108SetXY(0, 0);
ks0108WriteString((uint8_t*)"RC5:");
ks0108SetXY(5, 1);
ks0108WriteString((uint8_t*)"Raw = ");
ks0108WriteString(mkNumString(rc5Buf, 14, '0', 2));
ks0108SetXY(5, 2);
ks0108WriteString((uint8_t*)"Tog = ");
ks0108WriteString(mkNumString(((rc5Buf & 0x0800) > 0), 1, '0', 16));
ks0108SetXY(5, 3);
ks0108WriteString((uint8_t*)"Adr = ");
ks0108WriteString(mkNumString((rc5Buf & 0x07C0)>>6, 2, '0', 16));
ks0108SetXY(5, 4);
ks0108WriteString((uint8_t*)"Cmd = ");
ks0108WriteString(mkNumString(rc5Buf & 0x003F, 2, '0', 16));
ks0108SetXY(0, 6);
ks0108WriteString((uint8_t*)"Buttons:");
ks0108SetXY(5, 7);
ks0108WriteString(mkNumString(INPUT_PIN, 8, '0', 2));
#elif defined(KS0066) || defined(PCF8574)
ks0066SetXY(0, 0);
ks0066WriteString((uint8_t*)"R=");
ks0066WriteString(mkNumString(rc5Buf, 14, '0', 2));
ks0066SetXY(0, 1);
ks0066WriteString((uint8_t*)"TB=");
ks0066WriteString(mkNumString(((rc5Buf & 0x0800) > 0), 1, '0', 16));
ks0066WriteString((uint8_t*)",RC=");
ks0066WriteString(mkNumString((rc5Buf & 0x07C0)>>6, 2, '0', 16));
ks0066WriteString((uint8_t*)",CM=");
ks0066WriteString(mkNumString(rc5Buf & 0x003F, 2, '0', 16));
#elif defined(LS020)
ls020LoadFont(font_ks0066_ru_24, COLOR_CYAN, 1);
ls020SetXY(4, 0);
ls020WriteString(mkNumString(rc5Buf, 14, '0', 2));
ls020SetXY(5, 24);
ls020WriteString((uint8_t*)"Tog = ");
ls020WriteString(mkNumString(((rc5Buf & 0x0800) > 0), 1, '0', 16));
ls020SetXY(6, 48);
ls020WriteString((uint8_t*)"Adr = ");
ls020WriteString(mkNumString((rc5Buf & 0x07C0)>>6, 2, '0', 16));
ls020SetXY(4, 72);
ls020WriteString((uint8_t*)"Cmd = ");
ls020WriteString(mkNumString(rc5Buf & 0x003F, 2, '0', 16));
ls020SetXY(6, 104);
ls020WriteString((uint8_t*)"Btn = ");
ls020WriteString(mkNumString(INPUT_PIN, 8, '0', 2));
#elif defined(ST7920)
st7920LoadFont(font_ks0066_ru_08, 1);
st7920SetXY(0, 0);
st7920WriteString((uint8_t*)"RC5:");
st7920SetXY(5, 1);
st7920WriteString((uint8_t*)"Raw = ");
st7920WriteString(mkNumString(rc5Buf, 14, '0', 2));
st7920SetXY(5, 2);
st7920WriteString((uint8_t*)"Tog = ");
st7920WriteString(mkNumString(((rc5Buf & 0x0800) > 0), 1, '0', 16));
st7920SetXY(5, 3);
st7920WriteString((uint8_t*)"Adr = ");
st7920WriteString(mkNumString((rc5Buf & 0x07C0)>>6, 2, '0', 16));
st7920SetXY(5, 4);
st7920WriteString((uint8_t*)"Cmd = ");
st7920WriteString(mkNumString(rc5Buf & 0x003F, 2, '0', 16));
st7920SetXY(0, 6);
st7920WriteString((uint8_t*)"Buttons:");
st7920SetXY(5, 7);
st7920WriteString(mkNumString(INPUT_PIN, 8, '0', 2));
#endif
return;
}
И так, по большому счёту, написан
весь файл (и не только он) для вывода на экран. Сплошные #ifdef/#else и т.д. инструкции препроцессора. Это всё потому, что изначально планировался только один вариант железа, а уже потом пользователи стали просить добавить поддержку то того, то другого.
Это долго, сложно, громоздко. При добавлении новой функции, выводящей что-то на экран, приходилось реализовывать её для каждого дисплея по своему, учитывая его размеры и особенности работы.
После перехода проекта на ATmega32 решено было для ускорения вывода на экран использовать кадровый буфер и ограничиться размером экрана 128x64. Принцип простой - имеется массив 128x64 точек и мы рисуем картинку не в дисплей, а в этот массив. В частности, та же
функция для работы тестового режима начинает выглядеть уже так:
Спойлер
Код: Выделить всё
void showRC5Info(uint8_t **txtLabels)
{
uint16_t rc5Buf = getRC5Buf();
uint8_t btnBuf = getBtnBuf();
uint8_t encBuf = getEncBuf();
gdLoadFont(font_ks0066_ru_08, 1, FONT_DIR_0);
gdSetXY(0, 0);
writeStringEeprom(txtLabels[LABEL_IN_STATUS]);
gdSetXY(4, 9);
writeStringEeprom(txtLabels[LABEL_REMOTE]);
gdSetXY(45, 9);
gdWriteString(mkNumString(rc5Buf, 14, '0', 2));
gdSetXY(4, 18);
writeStringEeprom(txtLabels[LABEL_BUTTONS]);
gdSetXY(75, 18);
gdWriteString(mkNumString(btnBuf, 5, '0', 2));
gdSetXY(108, 18);
gdWriteString((uint8_t*)"/");
gdSetXY(117, 18);
gdWriteString(mkNumString(encBuf, 2, '0', 2));
gdSetXY(0, 30);
writeStringEeprom(txtLabels[LABEL_LEARN_MODE]);
gdSetXY(4, 39);
writeStringEeprom(txtLabels[LABEL_BUTTON]);
gdSetXY(48, 39);
writeStringEeprom(txtLabels[LABEL_RC5_STBY + rc5CmdInd]);
gdSetXY(8, 48);
writeStringEeprom(txtLabels[LABEL_ADDRESS]);
gdSetXY(64, 48);
rc5Addr = (rc5Buf & 0x07C0)>>6;
gdWriteString(mkNumString(rc5Addr, 2, '0', 16));
gdWriteString((uint8_t*)" => ");
gdWriteString(mkNumString(eeprom_read_byte(eepromRC5Addr), 2, '0', 16));
gdSetXY(8, 56);
writeStringEeprom(txtLabels[LABEL_COMMAND]);
gdSetXY(64, 56);
rc5Cmd = rc5Buf & 0x003F;
gdWriteString(mkNumString(rc5Cmd, 2, '0', 16));
gdWriteString((uint8_t*)" => ");
gdWriteString(mkNumString(eeprom_read_byte(eepromRC5Cmd + rc5CmdInd), 2, '0', 16));
return;
}
Гораздо короче, но, при этом, гораздо функциональнее.
Весь специфичный для дисплея код описан в соответствующих файлах -
ks0108.c и
st7920.c.
Всё, что нужно - это:
1. Реализовать кадровый буфер - массив fb[] - размером 1 кБайт (в случае ks0108 это массив 128x8, в случае st7920 это массив 32x32 - это с учётом организации пикселов в байты в этих дисплеях).
2. Написать функции для инициализации дисплея (init), очистки (clear) и для записи пиксела (drawPixel) в этот буфер.
3. За счёт таймера Timer0 организовать периодическую запись информации из буфера в дисплей.
Таймер настроен на вызов прерывания по переполнению 20000 в секунду (т.е., с периодом 50мкс). При каждом вызове он стартует АЦП, обеспечивая 10кГц анализ Фурье и пишет очередную пачку данных из кадрового буфера в дисплей. За 50 мкс дисплей вполне успевает обработать эту запись и быть готовым к следующей.
Итак, принцип такой:
а) любые функции пишут что-то в буфер (через функцию записи пиксела по нужным координатам), при этом не заморачиваясь по поводу экрана.
б) таймер, вызываясь 20000 раз в секунду, записывает очередной байт из буфера в экран. При размере буфера 1024 байта и небольших накладных расходах на позиционирование в начале строки весь экран обновляется около 18..19 раз в секунду.
Если нужно в эту систему добавить свой дисплей (свой файл myDisplay.c), нужно:
а) чтобы он имел те же размеры 128x64
б) завести 1кБ буфер для хранения картинки
в) написать обработчик прерывания таймера 0, записывающий очередной байт из буфера в дисплей и переходящий на следующий байт для будущей записи
В общем, архитектура проекта в плане работы с дисплеями, такова:
Код: Выделить всё
ks0108.h
/ ks0108.c
/
/
остальные ----- gdfb.c --- st7920.h
файлы gdfb.h st7920.c
проекта \
\
\ myDisplay.h
myDisplay.c
gdfb.c/gdfb.h - это своего рода абстрактный 128x64 дисплей, пробрасывающий графические функции дальше конкретному дисплею. И именно в него попиксельно рисуется картинка.
В принципе, подобным образом написаны и другие вещи (абстрактный тюнер + конкретные реализации, абстрактный аудиопроцессор + конкретные реализации), но там это не так сильно заметно, особенно по поводу аудиопроцессоров. Просто нет времени доделать это всё "красиво", да и, как говорится, работает - не трогай.