commit 758503a739b360aa8469bb324b1f679cb85d1d63 Author: Ondřej Hruška Date: Mon Jun 12 23:00:08 2023 +0200 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3da6947 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ +cmake-build* + + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..b289fa4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.25) +project(hd44780utf C) + +set(CMAKE_C_STANDARD 99) + +add_executable(hd44780utf + src/utf8.c + src/main.c + src/cgrom.c + src/cgram.c + src/lcdbuf.h + src/lcdbuf.c) diff --git a/src/cgram.c b/src/cgram.c new file mode 100644 index 0000000..da6c22e --- /dev/null +++ b/src/cgram.c @@ -0,0 +1,477 @@ +/** + * TODO file description + */ + +#include "cgram.h" + +const struct cgram_pattern CGRAM_CZ[] = { + { + .symbol = "ě", + .fallback = 'e', + .data = { + 0b01010, + 0b00100, + 0b01110, + 0b10001, + 0b11111, + 0b10000, + 0b01110 + }, + }, + { + .symbol = "š", + .fallback = 's', + .data = { + 0b01010, + 0b00100, + 0b01110, + 0b10000, + 0b01110, + 0b00001, + 0b11110 + }, + }, + { + .symbol = "č", + .fallback = 'c', + .data = { + 0b01010, + 0b00100, + 0b01110, + 0b10000, + 0b10000, + 0b10001, + 0b01110 + }, + }, + { + .symbol = "ř", + .fallback = 'r', + .data = { + 0b01010, + 0b00100, + 0b10110, + 0b11001, + 0b10000, + 0b10000, + 0b10000 + }, + }, + { + .symbol = "ž", + .fallback = 'z', + .data = { + 0b01010, + 0b00100, + 0b11111, + 0b00010, + 0b00100, + 0b01000, + 0b11111 + }, + }, + { + .symbol = "ý", + .fallback = 'y', + .data = { + 0b00010, + 0b00100, + 0b10001, + 0b10001, + 0b01111, + 0b00001, + 0b01110 + }, + }, + { + .symbol = "á", + .fallback = 'a', + .data = { + 0b00010, + 0b00100, + 0b01110, + 0b00001, + 0b01111, + 0b10001, + 0b01111 + }, + }, + { + .symbol = "í", + .fallback = 'i', + .data = { + 0b00110, + 0b00000, + 0b01100, + 0b00100, + 0b00100, + 0b00100, + 0b01110 + }, + }, + { + .symbol = "é", + .fallback = 'e', + .data = { + 0b00010, + 0b00100, + 0b01110, + 0b10001, + 0b11111, + 0b10000, + 0b01110 + }, + }, + { + .symbol = "ú", + .fallback = 'u', + .data = { + 0b00010, + 0b00100, + 0b10001, + 0b10001, + 0b10001, + 0b10011, + 0b01101 + }, + }, + { + .symbol = "ů", + .fallback = 'u', + .data = { + 0b00100, + 0b01010, + 0b10101, + 0b10001, + 0b10001, + 0b10011, + 0b01101 + }, + }, + { + .symbol = "ď", + .fallback = 'd', + .data = { + 0b01101, + 0b00001, + 0b01101, + 0b10011, + 0b10001, + 0b10001, + 0b01111 + }, + }, + { + .symbol = "ť", + .fallback = 't', + .data = { + 0b01010, + 0b01001, + 0b11100, + 0b01000, + 0b01000, + 0b01001, + 0b00110 + }, + }, + { + .symbol = "ň", + .fallback = 'n', + .data = { + 0b01010, + 0b00100, + 0b10110, + 0b11001, + 0b10001, + 0b10001, + 0b10001 + }, + }, + + // UPPERCASE + { + .symbol = "Ě", + .fallback = 'E', + .data = { + 0b01010, + 0b00100, + 0b11111, + 0b10000, + 0b11100, + 0b10000, + 0b11111 + }, + }, + { + .symbol = "Š", + .fallback = 'S', + .data = { + 0b01010, + 0b00100, + 0b01111, + 0b10000, + 0b01110, + 0b00001, + 0b11110 + }, + }, + { + .symbol = "Č", + .fallback = 'C', + .data = { + 0b01010, + 0b00100, + 0b01110, + 0b10001, + 0b10000, + 0b10001, + 0b01110 + }, + }, + { + .symbol = "Ř", + .fallback = 'R', + .data = { + 0b01010, + 0b00100, + 0b11110, + 0b10001, + 0b11110, + 0b10010, + 0b10001 + }, + }, + { + .symbol = "Ž", + .fallback = 'Z', + .data = { + 0b01010, + 0b00100, + 0b11111, + 0b00001, + 0b01110, + 0b10000, + 0b11111 + }, + }, + { + .symbol = "Ý", + .fallback = 'Y', + .data = { + 0b00010, + 0b00100, + 0b10001, + 0b10001, + 0b01010, + 0b00100, + 0b00100 + }, + }, + { + .symbol = "Á", + .fallback = 'A', + .data = { + 0b00010, + 0b00100, + 0b01110, + 0b10001, + 0b11111, + 0b10001, + 0b10001 + }, + }, + { + .symbol = "Í", + .fallback = 'I', + .data = { + 0b00010, + 0b00100, + 0b01110, + 0b00100, + 0b00100, + 0b00100, + 0b01110 + }, + }, + { + .symbol = "É", + .fallback = 'E', + .data = { + 0b00010, + 0b00100, + 0b11111, + 0b10000, + 0b11100, + 0b10000, + 0b11111 + }, + }, + { + .symbol = "Ú", + .fallback = 'U', + .data = { + 0b00010, + 0b00100, + 0b10001, + 0b10001, + 0b10001, + 0b10001, + 0b01110 + }, + }, + { + .symbol = "Ů", + .fallback = 'U', + .data = { + 0b00100, + 0b01010, + 0b10101, + 0b10001, + 0b10001, + 0b10001, + 0b01110 + }, + }, + { + .symbol = "Ď", + .fallback = 'D', + .data = { + 0b01010, + 0b00100, + 0b11100, + 0b10010, + 0b10001, + 0b10010, + 0b11100 + }, + }, + { + .symbol = "Ť", + .fallback = 'T', + .data = { + 0b01010, + 0b00100, + 0b11111, + 0b00100, + 0b00100, + 0b00100, + 0b00100 + }, + }, + { + .symbol = "Ň", + .fallback = 'N', + .data = { + 0b01010, + 0b00100, + 0b10001, + 0b11001, + 0b10101, + 0b10011, + 0b10001 + }, + }, + + {}, /* end mark */ +}; + + +/* + // this should be more or less the default font + + {{0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000}}, // + {{0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00000, 0b00100}}, // ! + {{0b01010, 0b01010, 0b01010, 0b00000, 0b00000, 0b00000, 0b00000}}, // " + {{0b01010, 0b01010, 0b11111, 0b01010, 0b11111, 0b01010, 0b01010}}, // # + {{0b00100, 0b01111, 0b10100, 0b01110, 0b00101, 0b11110, 0b00100}}, // $ + {{0b11000, 0b11001, 0b00010, 0b00100, 0b01000, 0b10011, 0b00011}}, // % + {{0b01100, 0b10010, 0b10100, 0b01000, 0b10101, 0b10010, 0b01101}}, // & + {{0b01100, 0b00100, 0b01000, 0b00000, 0b00000, 0b00000, 0b00000}}, // ' + {{0b00010, 0b00100, 0b01000, 0b01000, 0b01000, 0b00100, 0b00010}}, // ( + {{0b01000, 0b00100, 0b00010, 0b00010, 0b00010, 0b00100, 0b01000}}, // ) + {{0b00000, 0b00100, 0b10101, 0b01110, 0b10101, 0b00100, 0b00000}}, // * + {{0b00000, 0b00100, 0b00100, 0b11111, 0b00100, 0b00100, 0b00000}}, // + + {{0b00000, 0b00000, 0b00000, 0b00000, 0b01100, 0b00100, 0b01000}}, // , + {{0b00000, 0b00000, 0b00000, 0b11111, 0b00000, 0b00000, 0b00000}}, // - + {{0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b01100, 0b01100}}, // . + {{0b00000, 0b00001, 0b00010, 0b00100, 0b01000, 0b10000, 0b00000}}, // / + {{0b01110, 0b10001, 0b10011, 0b10101, 0b11001, 0b10001, 0b01110}}, // 0 + {{0b00100, 0b01100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110}}, // 1 + {{0b01110, 0b10001, 0b00001, 0b00010, 0b00100, 0b01000, 0b11111}}, // 2 + {{0b11111, 0b00010, 0b00100, 0b00010, 0b00001, 0b10001, 0b01110}}, // 3 + {{0b00010, 0b00110, 0b01010, 0b10010, 0b11111, 0b00010, 0b00010}}, // 4 + {{0b11111, 0b10000, 0b11110, 0b00001, 0b00001, 0b10001, 0b01110}}, // 5 + {{0b00110, 0b01000, 0b10000, 0b11110, 0b10001, 0b10001, 0b01110}}, // 6 + {{0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b01000, 0b01000}}, // 7 + {{0b01110, 0b10001, 0b10001, 0b01110, 0b10001, 0b10001, 0b01110}}, // 8 + {{0b01110, 0b10001, 0b10001, 0b01111, 0b00001, 0b00010, 0b01100}}, // 9 + {{0b00000, 0b01100, 0b01100, 0b00000, 0b01100, 0b01100, 0b00000}}, // : + {{0b00000, 0b01100, 0b01100, 0b00000, 0b01100, 0b00100, 0b01000}}, // ; + {{0b00010, 0b00100, 0b01000, 0b10000, 0b01000, 0b00100, 0b00010}}, // < + {{0b00000, 0b00000, 0b11111, 0b00000, 0b11111, 0b00000, 0b00000}}, // = + {{0b01000, 0b00100, 0b00010, 0b00001, 0b00010, 0b00100, 0b01000}}, // > + {{0b01110, 0b10001, 0b00001, 0b00010, 0b00100, 0b00000, 0b00100}}, // ? + {{0b01110, 0b10001, 0b00001, 0b01101, 0b10101, 0b10101, 0b01110}}, // @ + {{0b01110, 0b10001, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001}}, // A + {{0b11110, 0b10001, 0b10001, 0b11110, 0b10001, 0b10001, 0b11110}}, // B + {{0b01110, 0b10001, 0b10000, 0b10000, 0b10000, 0b10001, 0b01110}}, // C + {{0b11100, 0b10010, 0b10001, 0b10001, 0b10001, 0b10010, 0b11100}}, // D + {{0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b11111}}, // E + {{0b11111, 0b10000, 0b10000, 0b11110, 0b10000, 0b10000, 0b10000}}, // F + {{0b01110, 0b10001, 0b10000, 0b10111, 0b10001, 0b10001, 0b01111}}, // G + {{0b10001, 0b10001, 0b10001, 0b11111, 0b10001, 0b10001, 0b10001}}, // H + {{0b01110, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110}}, // I + {{0b00111, 0b00010, 0b00010, 0b00010, 0b00010, 0b10010, 0b01100}}, // J + {{0b10001, 0b10010, 0b10100, 0b11000, 0b10100, 0b10010, 0b10001}}, // K + {{0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b10000, 0b11111}}, // L + {{0b10001, 0b11011, 0b10101, 0b10101, 0b10001, 0b10001, 0b10001}}, // M + {{0b10001, 0b10001, 0b11001, 0b10101, 0b10011, 0b10001, 0b10001}}, // N + {{0b01110, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110}}, // O + {{0b11110, 0b10001, 0b10001, 0b11110, 0b10000, 0b10000, 0b10000}}, // P + {{0b01110, 0b10001, 0b10001, 0b10001, 0b10101, 0b10010, 0b01101}}, // Q + {{0b11110, 0b10001, 0b10001, 0b11110, 0b10100, 0b10010, 0b10001}}, // R + {{0b01111, 0b10000, 0b10000, 0b01110, 0b00001, 0b00001, 0b11110}}, // S + {{0b11111, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100}}, // T + {{0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01110}}, // U + {{0b10001, 0b10001, 0b10001, 0b10001, 0b10001, 0b01010, 0b00100}}, // V + {{0b10001, 0b10001, 0b10001, 0b10101, 0b10101, 0b10101, 0b01010}}, // W + {{0b10001, 0b10001, 0b01010, 0b00100, 0b01010, 0b10001, 0b10001}}, // X + {{0b10001, 0b10001, 0b10001, 0b01010, 0b00100, 0b00100, 0b00100}}, // Y + {{0b11111, 0b00001, 0b00010, 0b00100, 0b01000, 0b10000, 0b11111}}, // Z + {{0b01110, 0b01000, 0b01000, 0b01000, 0b01000, 0b01000, 0b01110}}, // [ + {{0b00000, 0b10000, 0b01000, 0b00100, 0b00010, 0b00001, 0b00000}}, // \ + {{0b01110, 0b00010, 0b00010, 0b00010, 0b00010, 0b00010, 0b01110}}, // ] + {{0b00100, 0b01010, 0b10001, 0b00000, 0b00000, 0b00000, 0b00000}}, // ^ + {{0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111}}, // _ + {{0b01000, 0b00100, 0b00010, 0b00000, 0b00000, 0b00000, 0b00000}}, // ` + {{0b00000, 0b00000, 0b01110, 0b00001, 0b01111, 0b10001, 0b01111}}, // a + {{0b10000, 0b10000, 0b10110, 0b11001, 0b10001, 0b10001, 0b11110}}, // b + {{0b00000, 0b00000, 0b01110, 0b10000, 0b10000, 0b10001, 0b01110}}, // c + {{0b00001, 0b00001, 0b01101, 0b10011, 0b10001, 0b10001, 0b01111}}, // d + {{0b00000, 0b00000, 0b01110, 0b10001, 0b11111, 0b10000, 0b01110}}, // e + {{0b00110, 0b01001, 0b01000, 0b11100, 0b01000, 0b01000, 0b01000}}, // f + {{0b00000, 0b01111, 0b10001, 0b10001, 0b01111, 0b00001, 0b01110}}, // g + {{0b10000, 0b10000, 0b10110, 0b11001, 0b10001, 0b10001, 0b10001}}, // h + {{0b00100, 0b00000, 0b01100, 0b00100, 0b00100, 0b00100, 0b01110}}, // i + {{0b00010, 0b00000, 0b00110, 0b00010, 0b00010, 0b10010, 0b01100}}, // j + {{0b10000, 0b10000, 0b10010, 0b10100, 0b11000, 0b10100, 0b10010}}, // k + {{0b01100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b01110}}, // l + {{0b00000, 0b00000, 0b11010, 0b10101, 0b10101, 0b10001, 0b10001}}, // m + {{0b00000, 0b00000, 0b10110, 0b11001, 0b10001, 0b10001, 0b10001}}, // n + {{0b00000, 0b00000, 0b01110, 0b10001, 0b10001, 0b10001, 0b01110}}, // o + {{0b00000, 0b00000, 0b11110, 0b10001, 0b11110, 0b10000, 0b10000}}, // p + {{0b00000, 0b00000, 0b01101, 0b10011, 0b01111, 0b00001, 0b00001}}, // q + {{0b00000, 0b00000, 0b10110, 0b11001, 0b10000, 0b10000, 0b10000}}, // r + {{0b00000, 0b00000, 0b01110, 0b10000, 0b01110, 0b00001, 0b11110}}, // s + {{0b01000, 0b01000, 0b11100, 0b01000, 0b01000, 0b01001, 0b00110}}, // t + {{0b00000, 0b00000, 0b10001, 0b10001, 0b10001, 0b10011, 0b01101}}, // u + {{0b00000, 0b00000, 0b10001, 0b10001, 0b10001, 0b01010, 0b00100}}, // v + {{0b00000, 0b00000, 0b10001, 0b10001, 0b10101, 0b10101, 0b01010}}, // w + {{0b00000, 0b00000, 0b10001, 0b01010, 0b00100, 0b01010, 0b10001}}, // x + {{0b00000, 0b00000, 0b10001, 0b10001, 0b01111, 0b00001, 0b01110}}, // y + {{0b00000, 0b00000, 0b11111, 0b00010, 0b00100, 0b01000, 0b11111}}, // z + {{0b00010, 0b00100, 0b00100, 0b01000, 0b00100, 0b00100, 0b00010}}, // { + {{0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100, 0b00100}}, // | + {{0b01000, 0b00100, 0b00100, 0b00010, 0b00100, 0b00100, 0b01000}}, // } + {{0b00000, 0b00000, 0b00000, 0b01101, 0b10010, 0b00000, 0b00000}}, // ~ +*/ diff --git a/src/cgram.h b/src/cgram.h new file mode 100644 index 0000000..da3c1f6 --- /dev/null +++ b/src/cgram.h @@ -0,0 +1,25 @@ +/** + * TODO file description + */ + +#ifndef HD44780UTF_CGRAM_H +#define HD44780UTF_CGRAM_H + +#include "utf8.h" +#include + +/** Pattern for CGRAM */ +struct cgram_pattern { + /** The symbol displayed */ + struct Utf8Char symbol; + + /** ASCII char shown if there is no space for the CGRAM pattern */ + char fallback; + + /** Graphic data (rows) - the 8th row is typically blank and can be left out from the definition */ + uint8_t data[8]; +}; + +extern const struct cgram_pattern CGRAM_CZ[]; + +#endif //HD44780UTF_CGRAM_H diff --git a/src/cgrom.c b/src/cgrom.c new file mode 100644 index 0000000..03b78a4 --- /dev/null +++ b/src/cgrom.c @@ -0,0 +1,136 @@ +#include +#include "utf8.h" +#include "cgrom.h" + +const struct cgrom_entry CGROM_A00[] = { + {.address = 32, .symbol = " "}, + {.address = 33, .symbol = "!"}, + {.address = 34, .symbol = "\""}, + {.address = 35, .symbol = "#"}, + {.address = 36, .symbol = "$"}, + {.address = 37, .symbol = "%"}, + {.address = 38, .symbol = "&"}, + {.address = 39, .symbol = "'"}, + {.address = 40, .symbol = "("}, + {.address = 41, .symbol = ")"}, + {.address = 42, .symbol = "*"}, + {.address = 43, .symbol = "+"}, + {.address = 44, .symbol = ","}, + {.address = 45, .symbol = "-"}, + {.address = 46, .symbol = "."}, + {.address = 47, .symbol = "/"}, + {.address = 48, .symbol = "0"}, + {.address = 49, .symbol = "1"}, + {.address = 50, .symbol = "2"}, + {.address = 51, .symbol = "3"}, + {.address = 52, .symbol = "4"}, + {.address = 53, .symbol = "5"}, + {.address = 54, .symbol = "6"}, + {.address = 55, .symbol = "7"}, + {.address = 56, .symbol = "8"}, + {.address = 57, .symbol = "9"}, + {.address = 58, .symbol = ":"}, + {.address = 59, .symbol = ";"}, + {.address = 60, .symbol = "<"}, + {.address = 61, .symbol = "="}, + {.address = 62, .symbol = ">"}, + {.address = 63, .symbol = "?"}, + {.address = 64, .symbol = "@"}, + {.address = 65, .symbol = "A"}, + {.address = 66, .symbol = "B"}, + {.address = 67, .symbol = "C"}, + {.address = 68, .symbol = "D"}, + {.address = 69, .symbol = "E"}, + {.address = 70, .symbol = "F"}, + {.address = 71, .symbol = "G"}, + {.address = 72, .symbol = "H"}, + {.address = 73, .symbol = "I"}, + {.address = 74, .symbol = "J"}, + {.address = 75, .symbol = "K"}, + {.address = 76, .symbol = "L"}, + {.address = 77, .symbol = "M"}, + {.address = 78, .symbol = "N"}, + {.address = 79, .symbol = "O"}, + {.address = 80, .symbol = "P"}, + {.address = 81, .symbol = "Q"}, + {.address = 82, .symbol = "R"}, + {.address = 83, .symbol = "S"}, + {.address = 84, .symbol = "T"}, + {.address = 85, .symbol = "U"}, + {.address = 86, .symbol = "V"}, + {.address = 87, .symbol = "W"}, + {.address = 88, .symbol = "X"}, + {.address = 89, .symbol = "Y"}, + {.address = 90, .symbol = "Z"}, + {.address = 91, .symbol = "["}, + {.address = 92, .symbol = "¥"}, // yen + {.address = 93, .symbol = "]"}, + {.address = 94, .symbol = "^"}, + {.address = 95, .symbol = "_"}, + {.address = 96, .symbol = "`"}, + {.address = 97, .symbol = "a"}, + {.address = 98, .symbol = "b"}, + {.address = 99, .symbol = "c"}, + {.address = 100, .symbol = "d"}, + {.address = 101, .symbol = "e"}, + {.address = 102, .symbol = "f"}, + {.address = 103, .symbol = "g"}, + {.address = 104, .symbol = "h"}, + {.address = 105, .symbol = "i"}, + {.address = 106, .symbol = "j"}, + {.address = 107, .symbol = "k"}, + {.address = 108, .symbol = "l"}, + {.address = 109, .symbol = "m"}, + {.address = 110, .symbol = "n"}, + {.address = 111, .symbol = "o"}, + {.address = 112, .symbol = "p"}, + {.address = 113, .symbol = "q"}, + {.address = 114, .symbol = "r"}, + {.address = 115, .symbol = "s"}, + {.address = 116, .symbol = "t"}, + {.address = 117, .symbol = "u"}, + {.address = 118, .symbol = "v"}, + {.address = 119, .symbol = "w"}, + {.address = 120, .symbol = "x"}, + {.address = 121, .symbol = "y"}, + {.address = 122, .symbol = "z"}, + {.address = 123, .symbol = "{"}, + {.address = 124, .symbol = "|"}, + {.address = 125, .symbol = "}"}, + {.address = 126, .symbol = "←"}, + {.address = 127, .symbol = "→"}, + + // lots of japanese symbols - add them yourself if you need them + {.address = 0xA2, .symbol = "「"}, + {.address = 0xA3, .symbol = "」"}, + {.address = 0xA5, .symbol = "·"}, + {.address = 0xDF, .symbol = "°"}, + + // there is also some greek and obscure diacritics + {.address = 0xE0, .symbol = "α"}, + {.address = 0xE1, .symbol = "ä"}, + {.address = 0xE2, .symbol = "β"}, + {.address = 0xE3, .symbol = "ϵ"}, + {.address = 0xE4, .symbol = "μ"}, + {.address = 0xE5, .symbol = "σ"}, + {.address = 0xE6, .symbol = "ρ"}, + {.address = 0xE8, .symbol = "√"}, + // E9 is nice superscript minus one, but it's not in Unicode :( + + {.address = 0xEC, .symbol = "¢"}, + {.address = 0xED, .symbol = "£"}, + {.address = 0xEE, .symbol = "ñ"}, + {.address = 0xEF, .symbol = "ö"}, + + {.address = 0xF2, .symbol = "Θ"}, + {.address = 0xF3, .symbol = "∞"}, + {.address = 0xF4, .symbol = "Ω"}, + {.address = 0xF5, .symbol = "Ü"}, + {.address = 0xF6, .symbol = "Σ"}, + {.address = 0xF7, .symbol = "π"}, + + {.address = 0xFC, .symbol = "円"}, + {.address = 0xFD, .symbol = "÷"}, + {.address = 0xFF, .symbol = "█"}, + {}, /* end mark */ +}; diff --git a/src/cgrom.h b/src/cgrom.h new file mode 100644 index 0000000..c9f091d --- /dev/null +++ b/src/cgrom.h @@ -0,0 +1,24 @@ +/** + * A definition of the actual CGROM + */ + +#ifndef HD44780UTF_CGROM_H +#define HD44780UTF_CGROM_H + +#include +#include "utf8.h" + +/** CGROM look-up table entry */ +struct cgrom_entry { + /** Address in the CGROM */ + uint8_t address; + + /** Corresponding symbol */ + struct Utf8Char symbol; +}; + + +/** The standard japanese lookup table, terminated by an empty entry */ +extern const struct cgrom_entry CGROM_A00[]; + +#endif //HD44780UTF_CGROM_H diff --git a/src/lcdbuf.c b/src/lcdbuf.c new file mode 100644 index 0000000..451a01d --- /dev/null +++ b/src/lcdbuf.c @@ -0,0 +1,256 @@ +/** + * TODO file description + */ + +#include +#include +#include "lcdbuf.h" + + +/** Initialize the struct */ +void LcdBuffer_Init(struct LcdBuffer *self, const struct cgrom_entry *cgrom, const struct cgram_pattern *custom_symbols) +{ + assert(self); + assert(cgrom); + assert(custom_symbols); + + LcdBuffer_Clear(self); + self->cgrom = cgrom; + self->custom_symbols = custom_symbols; +} + +/** Clear the screen */ +void LcdBuffer_Clear(struct LcdBuffer *self) +{ + assert(self); + + memset(self->cgram, 0, sizeof(self->cgram)); + memset(self->screen, 32, sizeof(self->screen)); + memset(self->dirty_extents, 0, sizeof(self->dirty_extents)); + self->full_repaint_required = false; +} + +/** Write what needs to be written to the HW, clear all dirty marks */ +void LcdBuffer_Flush(struct LcdBuffer *self) +{ + for (int i = 0; i < 8; i++) { + if (self->cgram[i].refcount > 0 && self->cgram[i].dirty) { + LcdBuffer_IO_WriteCGRAM(i, self->custom_symbols[self->cgram[i].symbol_index].data); + self->cgram[i].dirty = false; + } + } + + if (self->full_repaint_required) { + for (int r = 0; r < LINE_NUM; r++) { + LcdBuffer_IO_WriteAt(r, 0, self->screen[r], LINE_LEN); + } + memset(self->dirty_extents, 0, sizeof(self->dirty_extents)); + } else { + for (int e = 0; e < BUFLEN_DIRTY_LIST; e++) { + struct DirtyExtent *ext = &self->dirty_extents[e]; + if (!ext->count) { + continue; + } + LcdBuffer_IO_WriteAt(ext->row, ext->col, &self->screen[ext->row][ext->col], ext->count); + ext->count = 0; // mark the slot as free + } + } +} + +/** Fully write everything to the display */ +void LcdBuffer_FlushAll(struct LcdBuffer *self) +{ + for (int i = 0; i < 8; i++) { + if (self->cgram[i].refcount > 0) { + LcdBuffer_IO_WriteCGRAM(i, self->custom_symbols[self->cgram[i].symbol_index].data); + } + self->cgram[i].dirty = false; + } + + for (int r = 0; r < LINE_NUM; r++) { + LcdBuffer_IO_WriteAt(r, 0, self->screen[r], LINE_LEN); + } + + memset(self->dirty_extents, 0, sizeof(self->dirty_extents)); + self->full_repaint_required = false; +} + +static void mark_dirty(struct LcdBuffer *self, uint8_t row, uint8_t col) +{ + int first_empty_extent_slot = -1; + for (int i = 0; i < BUFLEN_DIRTY_LIST; i++) { + struct DirtyExtent *ext = &self->dirty_extents[i]; + if (ext->count == 0) { + // unused + if (first_empty_extent_slot == -1) { + first_empty_extent_slot = i; + } + continue; + } + if (ext->row != row) { + // not this row + continue; + } + + // this is a filled extent + + if (ext->col < col && ext->col + ext->count > col) { + // already in this extent + return; + } + + if (col < ext->col && (ext->col - col) <= 5) { + ext->count += (ext->col - col); + ext->col = col; + return; + } + + if (col >= ext->col + ext->count && (col - ext->col + ext->count) <= 5) { + ext->count += (col - ext->col + ext->count); + return; + } + } + + if (first_empty_extent_slot == -1) { + self->full_repaint_required = true; + } else { + self->dirty_extents[first_empty_extent_slot].col = col; + self->dirty_extents[first_empty_extent_slot].row = row; + self->dirty_extents[first_empty_extent_slot].count = 1; + } +} + +/** Set one utf8 character at a position */ +void LcdBuffer_Set(struct LcdBuffer *self, uint8_t row, uint8_t col, struct Utf8Char ch) +{ + assert(self); + assert(row < LINE_NUM); + assert(col < LINE_LEN); + + uint8_t oldchar = self->screen[row][col]; + + if (oldchar >= 8 && oldchar == ch.uint) { + // No change + return; + } + + // Fast path for standard ASCII + if (ch.uint >= 32 && ch.uint < 126 && ch.uint != '\\') { // A00 has YEN in place of BACKSLASH + // normal ASCII + if (oldchar < 8) { + // release refcount on the CGRAM cell + self->cgram[oldchar].refcount -= 1; + } + + self->screen[row][col] = ch.uint; + goto done_dirty; + } + + // Find if it's in CGROM + const struct cgrom_entry *rom = self->cgrom; + for (;;) { + if (rom->symbol.uint == 0) { + // End of the lookup table + break; + } + + if (rom->symbol.uint == ch.uint) { + // found it! + if (oldchar < 8) { + // release refcount on the CGRAM cell + self->cgram[oldchar].refcount -= 1; + } + + self->screen[row][col] = rom->address; + goto done_dirty; + } + + rom++; + } + + // Check if the same custom char is already used - if so, increment refcount and reuse it + int first_empty_custom_slot = -1; + for (int i = 0; i < 8; i++) { + if (self->cgram[i].refcount > 0) { + if (self->cgram[i].uint == ch.uint) { + if (oldchar == i) { + // No change, was already the same custom + return; + } + + if (oldchar < 8) { + // release refcount on the CGRAM cell + self->cgram[oldchar].refcount -= 1; + } + + self->cgram[i].refcount += 1; + self->screen[row][col] = i; + goto done_dirty; + } + } else if (first_empty_custom_slot == -1) { + first_empty_custom_slot = i; + } + } + + // New custom pattern is needed + + if (oldchar < 8) { + // release refcount on the CGRAM cell + self->cgram[oldchar].refcount -= 1; + } + + uint32_t index = 0; + const struct cgram_pattern *pattern = self->custom_symbols; + for (;;) { + if (pattern->symbol.uint == 0) { + // End of the lookup table + break; + } + + if (pattern->symbol.uint == ch.uint) { + // found it! + + if (first_empty_custom_slot == -1) { + // Whoops, out of slots. Show a fallback glyph + if (oldchar != pattern->fallback) { + self->screen[row][col] = pattern->fallback; + goto done_dirty; + } + return; + } + + // Allocate a new slot in the CGRAM + self->cgram[first_empty_custom_slot].refcount = 1; + self->cgram[first_empty_custom_slot].uint = ch.uint; + self->cgram[first_empty_custom_slot].dirty = true; // it should be flushed! + self->cgram[first_empty_custom_slot].symbol_index = index; + + self->screen[row][col] = first_empty_custom_slot; + goto done_dirty; + } + + index++; + pattern++; + } + + // Fallback, no way to show this glyph + self->screen[row][col] = '?'; + + done_dirty: + mark_dirty(self, row, col); +} + +/** Write a UTF8 string at a position */ +void LcdBuffer_Write(struct LcdBuffer *self, uint8_t row, uint8_t col, char *utf_string) +{ + struct Utf8Iterator iter; + Utf8Iterator_Init(&iter, utf_string); + struct Utf8Char uchar; + while ((uchar = Utf8Iterator_Next(&iter)).uint) { + if (col >= LINE_LEN) { + break; + } + LcdBuffer_Set(self, row, col, uchar); + col++; + } +} diff --git a/src/lcdbuf.h b/src/lcdbuf.h new file mode 100644 index 0000000..53f8022 --- /dev/null +++ b/src/lcdbuf.h @@ -0,0 +1,82 @@ +/** + * TODO file description + */ + +#ifndef HD44780UTF_LCDBUF_H +#define HD44780UTF_LCDBUF_H + +#include +#include "utf8.h" +#include "cgram.h" +#include "cgrom.h" + +#define LINE_NUM 4 +#define LINE_LEN 20 + +#define BUFLEN_DIRTY_LIST 8 + +_Static_assert(LINE_NUM * LINE_LEN < 256, "LINE_NUM * LINE_LEN must fit in u8"); + +/** Indicates a range of screen cells that were changed and must be written to HW */ +struct DirtyExtent { + uint8_t row; + uint8_t col; + uint8_t count; +}; + +/** Struct for one CGRAM slot */ +struct CgramState { + /** UTF8 uint shown in this slot */ + uint32_t uint; + /** Array index in the custom symbols table, use for look-up when writing the font data to HW */ + uint32_t symbol_index; + /** Number of occurrences of this symbol in the screen array */ + uint8_t refcount; + /** This CGRAM slot needs to be written to HW */ + bool dirty; +}; + +struct LcdBuffer { + /** The raw screen buffer. Custom symbols are 0x00-0x07 */ + uint8_t screen[LINE_NUM][LINE_LEN]; + /** CGRAM state array */ + struct CgramState cgram[8]; + /** Hardware CGROM lookup table, used to map UTF8 to existing ROM symbols */ + const struct cgrom_entry *cgrom; + /** Defined custom display pattern of utf8 symbols */ + const struct cgram_pattern *custom_symbols; + + /** Array of dirty extents - ranges in the display that need to be flushed to HW */ + struct DirtyExtent dirty_extents[BUFLEN_DIRTY_LIST]; + /** If the dirty extents array was not sufficient to hold all changes, this flag is set, + * indicating the dirty_extents array should be disregarded. */ + bool full_repaint_required; +}; + +/** Initialize the struct */ +void LcdBuffer_Init(struct LcdBuffer *self, const struct cgrom_entry *cgrom, const struct cgram_pattern *custom_symbols); + +/** Clear the screen */ +void LcdBuffer_Clear(struct LcdBuffer *self); + +/** Write what needs to be written to the HW, clear all dirty marks */ +void LcdBuffer_Flush(struct LcdBuffer *self); + +/** Fully write everything to the display */ +void LcdBuffer_FlushAll(struct LcdBuffer *self); + +/** Set one utf8 character at a position */ +void LcdBuffer_Set(struct LcdBuffer *self, uint8_t row, uint8_t col, struct Utf8Char ch); + +/** Write a UTF8 string at a position */ +void LcdBuffer_Write(struct LcdBuffer *self, uint8_t row, uint8_t col, char *utf_string); + +/* Callbacks - need to be implemented by the application! */ + +/** Write character data at position */ +void LcdBuffer_IO_WriteAt(uint8_t row, uint8_t col, const uint8_t *buf, uint8_t len); + +/** Write CGRAM data. Data is always 8 bytes long. */ +void LcdBuffer_IO_WriteCGRAM(uint8_t position, const uint8_t* data); + +#endif //HD44780UTF_LCDBUF_H diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..212de0d --- /dev/null +++ b/src/main.c @@ -0,0 +1,28 @@ +#include +#include +#include "lcdbuf.h" + +int main() +{ + struct LcdBuffer buf; + LcdBuffer_Init(&buf, CGROM_A00, CGRAM_CZ); + + LcdBuffer_Write(&buf, 0, 0, "Ahoj"); + + LcdBuffer_Flush(&buf); + + return 0; +} + +/** Write character data at position */ +void LcdBuffer_IO_WriteAt(uint8_t row, uint8_t col, const uint8_t *buf, uint8_t len) +{ + printf("W@%d,%d: \"%.*s\" (len %d)\n", row, col, len, buf, len); +} + +/** Write CGRAM data. Data is always 8 bytes long. */ +void LcdBuffer_IO_WriteCGRAM(uint8_t position, const uint8_t *data) +{ + printf("G@%d: %02x %02x %02x %02x %02x %02x %02x %02x\n", + position, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7]); +} diff --git a/src/utf8.c b/src/utf8.c new file mode 100644 index 0000000..fb28f02 --- /dev/null +++ b/src/utf8.c @@ -0,0 +1,136 @@ +#include +#include "utf8.h" + +// +// Created by MightyPork on 2017/08/20. +// +// UTF-8 parser - collects bytes of a code point before writing them +// into a screen cell. +// + +const struct Utf8Char EMPTY_CHAR = (struct Utf8Char) {.uint = 0}; + + +// Code Points First Byte Second Byte Third Byte Fourth Byte +// U+0000 - U+007F 00 - 7F +// U+0080 - U+07FF C2 - DF 80 - BF +// U+0800 - U+0FFF E0 *A0 - BF 80 - BF +// U+1000 - U+CFFF E1 - EC 80 - BF 80 - BF +// U+D000 - U+D7FF ED 80 - *9F 80 - BF +// U+E000 - U+FFFF EE - EF 80 - BF 80 - BF +// U+10000 - U+3FFFF F0 *90 - BF 80 - BF 80 - BF +// U+40000 - U+FFFFF F1 - F3 80 - BF 80 - BF 80 - BF +// U+100000 - U+10FFFF F4 80 - *8F 80 - BF 80 - BF + +void Utf8Parser_Clear(struct Utf8Parser *self) +{ + self->buffer.uint = 0; + self->utf_j = 0; + self->utf_len = 0; +} + +/** + * Handle a received character + */ +struct Utf8Char Utf8Parser_Handle(struct Utf8Parser *self, char c) +{ + uint8_t *bytes = self->buffer.bytes; + + uint8_t uc = (uint8_t) c; + // collecting unicode glyphs... + if (uc & 0x80) { + if (self->utf_len == 0) { + bytes[0] = uc; + self->utf_j = 1; + + // start + if (uc == 0xC0 || uc == 0xC1 || uc > 0xF4) { + // forbidden start codes + goto fail; + } + + if ((uc & 0xE0) == 0xC0) { + self->utf_len = 2; + } else if ((uc & 0xF0) == 0xE0) { + self->utf_len = 3; + } else if ((uc & 0xF8) == 0xF0) { + self->utf_len = 4; + } else { + // chars over 127 that don't start unicode sequences + goto fail; + } + } else { + if ((uc & 0xC0) != 0x80) { + bytes[self->utf_j++] = uc; + goto fail; + } else { + bytes[self->utf_j++] = uc; + if (self->utf_j >= self->utf_len) { + // check for bad sequences - overlong or some other problem + if (bytes[0] == 0xF4 && bytes[1] > 0x8F) { goto fail; } + if (bytes[0] == 0xF0 && bytes[1] < 0x90) { goto fail; } + if (bytes[0] == 0xED && bytes[1] > 0x9F) { goto fail; } + if (bytes[0] == 0xE0 && bytes[1] < 0xA0) { goto fail; } + + // trap for surrogates - those break javascript + if (bytes[0] == 0xED && bytes[1] >= 0xA0 && bytes[1] <= 0xBF) { goto fail; } + + goto success; + } + } + } + } else { + bytes[0] = uc; + goto success; + } + + return EMPTY_CHAR; + + success:; + struct Utf8Char result = self->buffer; + self->buffer.uint = 0; // erase the buffer + self->utf_len = 0; + return result; + + fail: + self->buffer.uint = 0; // erase the buffer + self->utf_len = 0; + return EMPTY_CHAR; +} + + +void Utf8Iterator_Init(struct Utf8Iterator *self, const char *source) +{ + Utf8Parser_Clear(&self->parser); + self->source = source; +} + + +struct Utf8Char Utf8Iterator_Next(struct Utf8Iterator *self) +{ + char c; + struct Utf8Char uchar; + while (1) { + c = *self->source++; + if (!c) { break; } + + uchar = Utf8Parser_Handle(&self->parser, c); + if (uchar.uint) { + return uchar; + } + } + return EMPTY_CHAR; +} + + +size_t utf8_strlen(const char *text) +{ + // TODO optimize + struct Utf8Iterator iter; + Utf8Iterator_Init(&iter, text); + size_t num = 0; + while ((Utf8Iterator_Next(&iter)).uint) { + num++; + } + return num; +} diff --git a/src/utf8.h b/src/utf8.h new file mode 100644 index 0000000..e1d85a4 --- /dev/null +++ b/src/utf8.h @@ -0,0 +1,115 @@ +/** + * UTF-8 string parsing and character iteration + * + * Created on 2020/01/04. + */ + +#ifndef LIQUIDTYPE_UTF8_H +#define LIQUIDTYPE_UTF8_H + +#include +#include +#include + +/** Character containing all zeros */ +extern const struct Utf8Char EMPTY_CHAR; + +/** + * UTF-8 encoded character. + * + * It's convenient to use the uint values internally to represent this symbol. + * Since UTF8 can't contain zero bytes, just strip the trailing zeros when composing a string. + */ +struct Utf8Char { + union { + /** + * character bytes; padded by zero bytes if shorter than 4. + * + * Can be initialized by a string literal in lookup tables + */ + uint8_t bytes[4]; + + /** u32 view of the bytes */ + uint32_t uint; + }; +}; + +/** UTF8 string parser internal state */ +struct Utf8Parser { + /** UTF-8 bytes buffer */ + struct Utf8Char buffer; + /** Currently collected UTF-8 character length */ + uint8_t utf_len; + /** Position in the current character */ + uint8_t utf_j; +}; + +/** + * Clear the parser internal state + * + * @param self + */ +void Utf8Parser_Clear(struct Utf8Parser *self); + +/** + * Initialize the parser struct before starting to parse + */ +static void Utf8Parser_Init(struct Utf8Parser *self) { + Utf8Parser_Clear(self); +} + +/** + * Parse a character. + * + * The returned struct contains NIL (uint == 0) if no character is yet available. + * + * ASCII is passed through, utf-8 is collected and returned in one piece. + */ +struct Utf8Char Utf8Parser_Handle(struct Utf8Parser *self, char c); + +/** + * Utf8 character iterator. + * + * Usage: + * struct Utf8Iterator iter; + * Utf8Iterator_Init(&iter, myString); + * + * struct Utf8Char uchar; + * while ((uchar = Utf8Iterator_Next(&iter)).uint) { + * // do something with the char + * } + */ +struct Utf8Iterator { + /* Characters to parse. The pointer is advanced as the iterator progresses. */ + const char *source; + struct Utf8Parser parser; +}; + +/** + * Initialize the iterator struct + * + * @param self + * @param source - string to iterate; It can be in RO memory, it's only read. + */ +void Utf8Iterator_Init(struct Utf8Iterator *self, const char *source); + +/** + * Get the next character from the iterator; + * + * Returns empty character if there are no more characters to parse (the .uint field is zero) + * + * Invalid characters are skipped. + */ +struct Utf8Char Utf8Iterator_Next(struct Utf8Iterator *self); + +/** + * Get utf8 string length, counting codepoints. + * + * @attention This function is rather expensive, cache the result if reused + * + * @param text - utf8 string, zero terminated + * @return number of codepoints + */ +size_t utf8_strlen(const char *text); + +#endif //LIQUIDTYPE_UTF8_H