commit f2007b7e9b622a5087584e51f9e1638dc0573364 Author: Slavasil Date: Thu Sep 10 14:35:16 2026 +0300 initialize repository diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..12f7a37 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.27) +set(PICO_SDK_PATH /home/sl/pico-sdk) +set(PICO_PLATFORM rp2350) +include(pico_sdk_import.cmake) + +project(GPIOTest) + +set(PICO_PSRAM_CS_PIN 47 CACHE STRING "GPIO used as Chip Select for PSRAM") +set(PICO_PSRAM_SIZE_BYTES 8388608 CACHE STRING "PSRAM size in bytes") +pico_sdk_init() + +add_executable(program main.c st7789.c graphics.c utf8.c ds1302.c beep.c) +target_link_libraries(program pico_stdlib pico_bootrom pico_rand hardware_gpio hardware_pwm hardware_spi hardware_regs hardware_psram pico_stdio_usb pico_time pico_sync) +#target_compile_definitions(program PRIVATE PICO_PSRAM_CS_PIN=${PICO_PSRAM_CS_PIN} PICO_PSRAM_SIZE_BYTES=${PICO_PSRAM_CS_PIN}) +target_compile_definitions(program PRIVATE PICO_PSRAM_CS_PIN=0 PICO_PSRAM_SIZE_BYTES=8388608) + +pico_enable_stdio_usb(program 1) + +pico_embed_pt_in_binary(program "${CMAKE_CURRENT_LIST_DIR}/partition_table.json") + +pico_add_extra_outputs(program) diff --git a/ds1302.c b/ds1302.c new file mode 100644 index 0000000..d13f2be --- /dev/null +++ b/ds1302.c @@ -0,0 +1,162 @@ +#include "ds1302.h" + +void _prepare_read(uint8_t address); +void _prepare_write(uint8_t address); +uint8_t _read_byte(); +void _write_byte(uint8_t value); +void _make_clock_cycle(); + +void _end_transfer(); + +#define REG_SECONDS 0x80 // SEC +#define REG_MINUTES 0x82 // MIN +#define REG_HOUR 0x84 // HR +#define REG_DATE 0x86 // DATE +#define REG_MONTH 0x88 // MONTH +#define REG_DAY 0x8A // DAY +#define REG_YEAR 0x8C // YEAR +#define REG_WP 0x8E // CONTROL +// TRICKLE CHARGER 1001000_ 0x90 +#define REG_BURST 0xBE // CLOCK BURST + +#define REG_RAM00 0xC0 +#define REG_RAM_BURST 0xFE + +void ds1302_init() { + gpio_init(PIN_RTC_RESET); + gpio_set_dir(PIN_RTC_RESET, GPIO_OUT); + gpio_put(PIN_RTC_RESET, LOW); + + gpio_init(PIN_RTC_CLOCK); + gpio_set_dir(PIN_RTC_CLOCK, GPIO_OUT); + gpio_put(PIN_RTC_CLOCK, LOW); + + gpio_init(PIN_RTC_IO); + gpio_set_dir(PIN_RTC_IO, GPIO_IN); +} + +int ds1302_is_clock_halted() { + _prepare_read(REG_SECONDS); + uint8_t seconds = _read_byte(); + _end_transfer(); + return (seconds & 0b10000000); +} + +void ds1302_get_datetime(DateTime *dt) { + _prepare_read(REG_BURST); + dt->second = ds1302_decode_bcd(_read_byte() & 0b01111111); + dt->minute = ds1302_decode_bcd(_read_byte() & 0b01111111); + dt->hour = ds1302_decode_bcd(_read_byte() & 0b00111111); + dt->day = ds1302_decode_bcd(_read_byte() & 0b00111111); + dt->month = ds1302_decode_bcd(_read_byte() & 0b00011111); + dt->dow = ds1302_decode_bcd(_read_byte() & 0b00000111); + dt->year = ds1302_decode_bcd(_read_byte() & 0b01111111); + _end_transfer(); +} + +void ds1302_set_datetime(DateTime *dt) { + // disable write protection + _prepare_write(REG_WP); + _write_byte(0b00000000); // 0, false + _end_transfer(); + // OR call writeProtect(0) + + _prepare_write(REG_BURST); + _write_byte(ds1302_encode_bcd(dt->second % 60)); + _write_byte(ds1302_encode_bcd(dt->minute % 60)); + _write_byte(ds1302_encode_bcd(dt->hour % 24)); + _write_byte(ds1302_encode_bcd(dt->day % 32)); + _write_byte(ds1302_encode_bcd(dt->month % 13)); + _write_byte(ds1302_encode_bcd(dt->dow % 8)); + _write_byte(ds1302_encode_bcd(dt->year % 100)); + _write_byte(0b10000000); + _end_transfer(); +} + +void ds1302_halt() { + _prepare_write(REG_SECONDS); + _write_byte(0b10000000); + _end_transfer(); +} + +void _prepare_read(uint8_t address) { + gpio_set_dir(PIN_RTC_IO, GPIO_OUT); + gpio_put(PIN_RTC_RESET, HIGH); + uint8_t command = 0b10000001 | address; + _write_byte(command); + gpio_set_dir(PIN_RTC_IO, GPIO_IN); +} + +void _prepare_write(uint8_t address) { + gpio_set_dir(PIN_RTC_IO, GPIO_OUT); + gpio_put(PIN_RTC_RESET, HIGH); + uint8_t command = 0b10000000 | address; + _write_byte(command); +} + +void _end_transfer() { gpio_put(PIN_RTC_RESET, LOW); } + +uint8_t _read_byte() { + uint8_t byte = 0; + for (uint8_t b = 0; b < 8; b++) { + if (gpio_get(PIN_RTC_IO) == HIGH) + byte |= 0x01 << b; + _make_clock_cycle(); + } + return byte; +} + +void _write_byte(uint8_t value) { + for (uint8_t b = 0; b < 8; b++) { + gpio_put(PIN_RTC_IO, (value & 0x01) ? HIGH : LOW); + _make_clock_cycle(); + value >>= 1; + } +} + +void _make_clock_cycle() { + gpio_put(PIN_RTC_CLOCK, HIGH); + sleep_us(1); + gpio_put(PIN_RTC_CLOCK, LOW); + sleep_us(1); +} + +void ds1302_get_ram_byte(uint8_t addr, uint8_t *data) { + _prepare_read(addr); + *data = _read_byte(); + _end_transfer(); +} + +void ds1302_set_ram_byte(uint8_t addr, uint8_t *data) { + _prepare_write(addr); + _write_byte(*data); + _end_transfer(); +} + +void ds1302_set_write_protection(int enable) { + _prepare_write(REG_WP); + _write_byte(enable); + _end_transfer(); +} + +void ds1302_write_ram_bulk(const uint8_t *data, int len) { + if (len <= 0 || len > 31) { + return; + } + _prepare_write(REG_RAM_BURST); + for (int i = 0; i < len; i++) { + _write_byte(data[i]); + } + _end_transfer(); +} + +void ds1302_read_ram_bulk(uint8_t *data, int len) { + if (len <= 0 || len > 31) { + return; + } + _prepare_read(REG_RAM_BURST); + for (int i = 0; i < len; i++) { + data[i] = _read_byte(); + } + _end_transfer(); +} diff --git a/ds1302.h b/ds1302.h new file mode 100644 index 0000000..c4c5b72 --- /dev/null +++ b/ds1302.h @@ -0,0 +1,84 @@ +/** + * Ds1302.h + * + * @version 1.0.3 + * @author Rafa Couto + * @license GNU Affero General Public License v3.0 + * @see https://github.com/Treboada/Ds1302 + * + * adapted for Pico by Ken Webb, 27 Oct 2022 + * + * 31 Oct 2022 + * I am using additional ideas and code on ds1302 Ram from : + * https://github.com/msparks/arduino-ds1302 Matt Sparks no license? + * "Arduino library for the DS1302 Real Time Clock chip" + * includes "Setting and accessing the 31 bytes of static RAM. Single-byte and multi-byte (burst) modes are supported." + * + */ + +#ifndef _DS_1302_H +#define _DS_1302_H + +#include "pico/stdlib.h" +#include "pins.h" +#include +#include +#include + +// the following defines are from Arduino.h +// these are needed in Ds1302.cpp +#define HIGH 0x1 +#define LOW 0x0 + +typedef struct { + uint8_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t minute; + uint8_t second; + uint8_t dow; +} DateTime; + +/** + * Months of year + */ +enum MONTH { + MONTH_JAN = 1, + MONTH_FEB = 2, + MONTH_MAR = 3, + MONTH_APR = 4, + MONTH_MAY = 5, + MONTH_JUN = 6, + MONTH_JUL = 7, + MONTH_AUG = 8, + MONTH_SET = 9, + MONTH_OCT = 10, + MONTH_NOV = 11, + MONTH_DEC = 12 +}; + +/** + * Days of week + */ +enum DOW { DOW_MON = 1, DOW_TUE = 2, DOW_WED = 3, DOW_THU = 4, DOW_FRI = 5, DOW_SAT = 6, DOW_SUN = 7 }; + +void ds1302_init(); +int ds1302_is_clock_halted(); +void ds1302_halt(); +void ds1302_get_datetime(DateTime *dt); +void ds1302_set_datetime(DateTime *dt); + +void ds1302_get_ram_byte(uint8_t addr, uint8_t *data); +void ds1302_set_ram_byte(uint8_t addr, uint8_t *data); + +void ds1302_set_write_protection(int enable); + +void ds1302_write_ram_bulk(const uint8_t *data, int len); +void ds1302_read_ram_bulk(uint8_t *data, int len); + +inline uint8_t ds1302_encode_bcd(uint8_t dec) { return ((dec / 10 * 16) + (dec % 10)); } + +inline uint8_t ds1302_decode_bcd(uint8_t bcd) { return ((bcd / 16 * 10) + (bcd % 16)); } + +#endif // _DS_1302_H diff --git a/font_ubuntu.h b/font_ubuntu.h new file mode 100644 index 0000000..2dd871c --- /dev/null +++ b/font_ubuntu.h @@ -0,0 +1,521 @@ +// Header File for SSD1306 characters +// Generated with TTF2BMH, edited manually +// Font Ubuntu Regular +// Font Size: 24 +const char GLYPH_MISSING[] = {254, 2, 194, 226, 98, 98, 226, 194, 130, 2, 254, 0, 255, 0, 0, 0, 192, 248, + 62, 15, 3, 0, 255, 0, 127, 64, 64, 64, 76, 76, 64, 64, 64, 64, 127, 0}; +const char GLYPH_EXCLAMATION_MARK[] = {254, 254, 63, 63, 7, 7}; +const char GLYPH_QUOTATION_MARK[] = {63, 63, 0, 0, 0, 63, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const char GLYPH_NUMBER_SIGN[] = {192, 192, 192, 240, 254, 206, 192, 192, 192, 192, 192, 240, 254, 206, 192, + 48, 48, 252, 255, 51, 48, 48, 48, 48, 48, 252, 255, 51, 48, 48, + 0, 7, 7, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0}; +const char GLYPH_DOLLAR_SIGN[] = {240, 248, 152, 12, 12, 15, 15, 12, 12, 24, 0, 0, 1, 3, 3, 7, 6, + 12, 12, 24, 240, 224, 3, 7, 6, 6, 6, 62, 62, 6, 7, 3, 1}; +const char GLYPH_PERCENT_SIGN[] = {248, 252, 14, 6, 6, 14, 252, 248, 0, 0, 128, 224, 120, 60, 14, 2, 0, + 1, 3, 7, 6, 6, 7, 195, 225, 120, 30, 7, 1, 248, 252, 14, 6, 6, + 0, 0, 0, 0, 4, 7, 3, 1, 0, 0, 0, 0, 1, 3, 7, 6, 6}; +const char GLYPH_AMPERSAND[] = {0, 0, 120, 252, 142, 6, 6, 134, 206, 252, 120, 0, 0, 0, 0, + 240, 248, 12, 7, 7, 7, 15, 29, 57, 112, 192, 192, 112, 60, 12, + 0, 3, 3, 7, 6, 6, 6, 6, 6, 3, 3, 1, 3, 6, 4}; +const char GLYPH_APOSTROPHE[] = {63, 63, 0, 0, 0, 0}; +const char GLYPH_LEFT_PARENTHESIS[] = {128, 224, 120, 14, 3, 1, 255, 255, 0, 0, 0, 0, 0, 3, 15, 56, 96, 192}; +const char GLYPH_RIGHT_PARENTHESIS[] = {1, 3, 14, 120, 224, 128, 0, 0, 0, 0, 255, 255, 192, 96, 56, 15, 3, 0}; +const char GLYPH_ASTERISK[] = {16, 56, 48, 176, 238, 126, 238, 176, 48, 56, 16, 0, 0, 2, 7, 3, 0, + 1, 3, 7, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const char GLYPH_PLUS_SIGN[] = {0, 0, 0, 0, 0, 224, 224, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 255, + 255, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0}; +const char GLYPH_COMMA[] = {0, 0, 0, 0, 0, 0, 96, 127, 15}; +const char GLYPH_HYPHEN_MINUS[] = {0, 0, 0, 0, 0, 0, 12, 12, 12, 12, 12, 12, 0, 0, 0, 0, 0, 0}; +const char GLYPH_FULL_STOP[] = {0, 0, 0, 0, 0, 0, 7, 7, 7}; +const char GLYPH_SOLIDUS[] = {0, 0, 0, 0, 0, 0, 224, 248, 31, 7, 0, 0, 128, 224, 124, + 31, 3, 0, 0, 0, 240, 124, 15, 3, 0, 0, 0, 0, 0, 0}; +const char GLYPH_DIGIT_ZERO[] = {192, 240, 60, 12, 6, 6, 6, 6, 6, 12, 60, 240, 192, 63, 255, 192, 0, 0, 0, 0, + 0, 0, 0, 192, 255, 63, 0, 0, 3, 3, 6, 6, 6, 6, 7, 3, 1, 0, 0}; +const char GLYPH_DIGIT_ONE[] = {32, 112, 48, 56, 28, 254, 254, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 7, 7}; +const char GLYPH_DIGIT_TWO[] = {8, 12, 14, 6, 6, 6, 6, 142, 252, 248, 0, 0, 192, 224, 112, 56, 12, + 6, 3, 1, 0, 0, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6}; +const char GLYPH_DIGIT_THREE[] = {4, 12, 6, 6, 6, 6, 6, 134, 140, 252, 120, 0, 0, 0, 0, 3, 3, 3, + 3, 7, 7, 140, 252, 240, 3, 7, 6, 6, 6, 6, 6, 6, 3, 3, 1, 0}; +const char GLYPH_DIGIT_FOUR[] = {0, 0, 0, 128, 192, 224, 56, 60, 30, 254, 254, 0, 0, 56, 60, 55, 51, 49, 48, 48, + 48, 48, 255, 255, 48, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0}; +const char GLYPH_DIGIT_FIVE[] = {0, 0, 254, 254, 6, 6, 6, 6, 6, 6, 0, 0, 3, 3, 3, 3, 3, + 7, 6, 14, 252, 248, 3, 7, 6, 6, 6, 6, 6, 7, 3, 1, 0}; +const char GLYPH_DIGIT_SIX[] = {0, 192, 240, 56, 24, 12, 12, 6, 6, 6, 6, 0, 127, 255, 134, 3, 3, 3, + 3, 3, 7, 142, 252, 248, 0, 1, 3, 7, 6, 6, 6, 6, 7, 3, 1, 0}; +const char GLYPH_DIGIT_SEVEN[] = {6, 6, 6, 6, 6, 6, 134, 230, 118, 30, 14, 0, 0, 0, 0, 240, 126, + 15, 1, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0}; +const char GLYPH_DIGIT_EIGHT[] = {112, 248, 140, 142, 6, 6, 6, 6, 6, 14, 140, 252, 112, 240, 249, 141, 7, 7, 3, 2, + 6, 7, 15, 157, 252, 240, 0, 1, 3, 7, 6, 6, 6, 6, 6, 7, 3, 1, 0}; +const char GLYPH_DIGIT_NINE[] = {240, 248, 28, 14, 6, 6, 6, 6, 12, 28, 248, 224, 1, 7, 7, 14, 12, 12, + 12, 140, 204, 246, 127, 15, 0, 6, 6, 6, 6, 3, 3, 3, 1, 0, 0, 0}; +const char GLYPH_COLON[] = {192, 192, 192, 1, 1, 1, 7, 7, 7}; +const char GLYPH_SEMICOLON[] = {192, 192, 192, 1, 1, 1, 96, 127, 15}; +const char GLYPH_LESS_THAN_SIGN[] = {0, 0, 0, 0, 0, 0, 0, 0, 128, 128, 128, 192, 128, + 28, 28, 28, 54, 54, 119, 99, 99, 193, 193, 193, 128, 128, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0}; +const char GLYPH_EQUALS_SIGN[] = {128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 49, 49, 49, 49, 49, 49, + 49, 49, 49, 49, 49, 49, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const char GLYPH_GREATER_THAN_SIGN[] = {128, 192, 128, 128, 128, 0, 0, 0, 0, 0, 0, 0, 0, + 128, 128, 193, 193, 193, 99, 99, 119, 54, 54, 28, 28, 28, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const char GLYPH_QUESTION_MARK[] = {4, 6, 6, 6, 6, 6, 142, 252, 120, 0, 0, 0, 56, 62, + 7, 3, 1, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0}; +const char GLYPH_COMMERCIAL_AT[] = {0, 192, 240, 120, 24, 12, 12, 142, 198, 198, 198, 198, 198, 198, 204, 12, + 255, 255, 129, 0, 0, 126, 255, 195, 129, 0, 0, 0, 0, 255, 255, 0, + 0, 3, 15, 30, 24, 56, 48, 113, 99, 99, 99, 99, 99, 97, 99, 3}; +const char GLYPH_LATIN_CAPITAL_LETTER_A[] = {0, 0, 0, 0, 0, 192, 240, 60, 14, 60, 240, 192, 0, 0, 0, 0, 0, + 0, 0, 224, 248, 62, 55, 49, 48, 48, 48, 49, 55, 62, 248, 224, 0, 0, + 4, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 7, 4}; +const char GLYPH_LATIN_CAPITAL_LETTER_B[] = {254, 254, 6, 6, 6, 6, 6, 6, 6, 12, 156, 248, 240, 0, + 255, 255, 6, 6, 6, 6, 6, 6, 6, 7, 15, 141, 248, 240, + 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 3, 3, 1, 0}; +const char GLYPH_LATIN_CAPITAL_LETTER_C[] = {128, 240, 120, 28, 12, 14, 6, 6, 6, 6, 6, 12, 12, + 63, 255, 224, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 1, 3, 3, 6, 6, 6, 6, 6, 6, 7, 3}; +const char GLYPH_LATIN_CAPITAL_LETTER_D[] = {254, 254, 6, 6, 6, 6, 6, 6, 14, 12, 12, 24, 120, 224, 128, + 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 224, 127, 31, + 7, 7, 6, 6, 6, 6, 6, 6, 6, 3, 3, 1, 0, 0, 0}; +const char GLYPH_LATIN_CAPITAL_LETTER_E[] = {254, 254, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 255, 255, 6, 6, 6, 6, + 6, 6, 6, 6, 0, 0, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6}; +const char GLYPH_LATIN_CAPITAL_LETTER_F[] = {254, 254, 6, 6, 6, 6, 6, 6, 6, 6, 6, 255, 255, 6, 6, 6, 6, + 6, 6, 6, 6, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const char GLYPH_LATIN_CAPITAL_LETTER_G[] = {128, 240, 120, 28, 12, 6, 6, 6, 6, 6, 6, 12, 4, + 63, 255, 224, 128, 0, 0, 0, 0, 0, 0, 0, 252, 252, + 0, 0, 1, 3, 3, 6, 6, 6, 6, 6, 6, 7, 3}; +const char GLYPH_LATIN_CAPITAL_LETTER_H[] = {254, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254, 254, + 255, 255, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 255, 255, + 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7}; +const char GLYPH_LATIN_CAPITAL_LETTER_I[] = {254, 254, 255, 255, 7, 7}; +const char GLYPH_LATIN_CAPITAL_LETTER_J[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 254, 254, 0, 0, 0, 0, 0, 0, + 0, 0, 128, 255, 255, 2, 3, 6, 6, 6, 6, 6, 7, 3, 1, 0}; +const char GLYPH_LATIN_CAPITAL_LETTER_K[] = {254, 254, 0, 0, 128, 192, 96, 48, 24, 12, 6, 2, 0, 0, + 255, 255, 6, 15, 29, 24, 56, 112, 224, 192, 128, 0, 0, 0, + 7, 7, 0, 0, 0, 0, 0, 0, 0, 1, 3, 7, 6, 4}; +const char GLYPH_LATIN_CAPITAL_LETTER_L[] = {254, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6}; +const char GLYPH_LATIN_CAPITAL_LETTER_M[] = {0, 254, 126, 60, 240, 192, 0, 0, 0, 0, 0, 0, 192, 240, 60, 126, + 255, 255, 0, 0, 0, 3, 15, 124, 240, 240, 124, 15, 3, 0, 0, 0, + 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const char GLYPH_LATIN_CAPITAL_LETTER_N[] = {254, 252, 28, 56, 112, 192, 128, 0, 0, 0, 0, 0, 0, 254, 254, + 255, 255, 0, 0, 0, 0, 1, 3, 14, 28, 56, 112, 192, 255, 255, + 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 7}; +const char GLYPH_LATIN_CAPITAL_LETTER_O[] = {192, 240, 120, 28, 12, 14, 6, 6, 6, 6, 14, 12, 28, 120, 240, 192, + 63, 255, 224, 128, 0, 0, 0, 0, 0, 0, 0, 0, 128, 224, 255, 31, + 0, 0, 1, 3, 3, 6, 6, 6, 6, 6, 6, 3, 3, 1, 0, 0}; +const char GLYPH_LATIN_CAPITAL_LETTER_P[] = {254, 254, 6, 6, 6, 6, 6, 6, 6, 12, 28, 248, 240, + 255, 255, 12, 12, 12, 12, 12, 12, 12, 6, 7, 3, 1, + 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const char GLYPH_LATIN_CAPITAL_LETTER_Q[] = {192, 240, 120, 28, 12, 14, 6, 6, 6, 6, 14, 12, 28, 120, 240, 192, + 63, 255, 224, 128, 0, 0, 0, 0, 0, 0, 0, 0, 128, 224, 127, 31, + 0, 0, 1, 3, 3, 6, 6, 30, 62, 118, 98, 99, 193, 193, 0, 0}; +const char GLYPH_LATIN_CAPITAL_LETTER_R[] = {254, 254, 6, 6, 6, 6, 6, 6, 6, 12, 28, 248, 240, 0, + 255, 255, 12, 12, 12, 12, 12, 12, 60, 118, 231, 195, 1, 0, + 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 7, 6}; +const char GLYPH_LATIN_CAPITAL_LETTER_S[] = {248, 252, 140, 6, 6, 6, 6, 6, 6, 14, 4, 0, 0, 1, 1, 3, 3, 6, + 6, 12, 12, 24, 248, 224, 3, 3, 6, 6, 6, 6, 6, 6, 6, 3, 3, 1}; +const char GLYPH_LATIN_CAPITAL_LETTER_T[] = {6, 6, 6, 6, 6, 6, 254, 254, 6, 6, 6, 6, 6, 6, 0, 0, 0, 0, 0, 0, 255, + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0}; +const char GLYPH_LATIN_CAPITAL_LETTER_U[] = {254, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254, 254, + 127, 255, 128, 0, 0, 0, 0, 0, 0, 0, 0, 128, 255, 127, + 0, 1, 3, 3, 6, 6, 6, 6, 6, 6, 3, 3, 1, 0}; +const char GLYPH_LATIN_CAPITAL_LETTER_V[] = {6, 30, 252, 224, 0, 0, 0, 0, 0, 0, 0, 0, 224, 252, 30, 2, + 0, 0, 0, 7, 31, 252, 224, 128, 128, 224, 252, 31, 7, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 3, 7, 7, 3, 0, 0, 0, 0, 0, 0}; +const char GLYPH_LATIN_CAPITAL_LETTER_W[] = { + 14, 254, 248, 0, 0, 0, 0, 0, 128, 248, 120, 224, 0, 0, 0, 0, 0, 0, 1, 31, 255, 240, 0, 224, 252, 31, + 3, 0, 3, 31, 252, 224, 0, 240, 0, 0, 0, 1, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 7, 7}; +const char GLYPH_LATIN_CAPITAL_LETTER_X[] = {2, 6, 28, 56, 224, 192, 0, 0, 192, 224, 56, 28, 6, 2, + 0, 0, 128, 224, 112, 57, 15, 15, 57, 112, 224, 128, 0, 0, + 4, 7, 3, 1, 0, 0, 0, 0, 0, 0, 1, 3, 7, 4}; +const char GLYPH_LATIN_CAPITAL_LETTER_Y[] = {2, 14, 60, 240, 192, 0, 0, 0, 0, 192, 240, 60, 14, 2, + 0, 0, 0, 0, 1, 7, 254, 254, 7, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0}; +const char GLYPH_LATIN_CAPITAL_LETTER_Z[] = {6, 6, 6, 6, 6, 6, 6, 134, 198, 102, 54, 30, 14, + 0, 128, 224, 112, 24, 12, 7, 1, 0, 0, 0, 0, 0, + 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6}; +const char GLYPH_LEFT_SQUARE_BRACKET[] = {255, 255, 1, 1, 1, 1, 255, 255, 0, 0, 0, 0, 255, 255, 192, 192, 192, 192}; +const char GLYPH_REVERSE_SOLIDUS[] = {7, 31, 248, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 31, 124, + 224, 128, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 15, 124, 240, 128}; +const char GLYPH_RIGHT_SQUARE_BRACKET[] = {1, 1, 1, 1, 255, 255, 0, 0, 0, 0, 255, 255, 192, 192, 192, 192, 255, 255}; +const char GLYPH_CIRCUMFLEX_ACCENT[] = {0, 128, 224, 240, 124, 62, 14, 62, 124, 240, 224, 128, 0, 2, 7, 3, 1, 0, 0, 0, + 0, 0, 1, 3, 7, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const char GLYPH_LOW_LINE[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192, 192}; +const char GLYPH_GRAVE_ACCENT[] = {1, 3, 7, 14, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const char GLYPH_LATIN_SMALL_LETTER_A[] = {0, 96, 96, 96, 96, 96, 96, 96, 192, 192, 128, 224, 240, 48, 24, 24, 24, + 24, 24, 24, 255, 255, 1, 3, 7, 6, 6, 6, 6, 6, 6, 7, 7}; +const char GLYPH_LATIN_SMALL_LETTER_B[] = {255, 255, 192, 96, 96, 96, 96, 96, 192, 192, 128, 0, + 255, 255, 0, 0, 0, 0, 0, 0, 0, 193, 255, 126, + 3, 7, 6, 6, 6, 6, 6, 6, 3, 3, 1, 0}; +const char GLYPH_LATIN_SMALL_LETTER_C[] = {0, 128, 192, 192, 96, 96, 96, 96, 96, 96, 126, 255, 129, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 3, 3, 6, 6, 6, 6, 6, 7}; +const char GLYPH_LATIN_SMALL_LETTER_D[] = {0, 128, 192, 192, 96, 96, 96, 96, 96, 192, 255, 255, + 126, 255, 193, 0, 0, 0, 0, 0, 0, 0, 255, 255, + 0, 1, 3, 3, 7, 6, 6, 6, 6, 6, 7, 3}; +const char GLYPH_LATIN_SMALL_LETTER_E[] = {0, 128, 192, 192, 96, 96, 96, 96, 96, 224, 192, 128, 0, + 126, 255, 153, 24, 24, 24, 24, 24, 24, 24, 25, 31, 30, + 0, 1, 3, 3, 6, 6, 6, 6, 6, 6, 6, 3, 0}; +const char GLYPH_LATIN_SMALL_LETTER_F[] = {252, 255, 99, 97, 97, 97, 97, 1, 255, 255, 0, 0, + 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0}; +const char GLYPH_LATIN_SMALL_LETTER_G[] = {0, 128, 192, 192, 96, 96, 96, 96, 96, 96, 224, 192, + 62, 255, 193, 128, 0, 0, 0, 0, 0, 128, 255, 255, + 0, 96, 193, 193, 195, 195, 195, 195, 227, 113, 63, 31}; +const char GLYPH_LATIN_SMALL_LETTER_H[] = {255, 255, 96, 96, 96, 96, 96, 224, 192, 192, 0, 255, 255, 0, 0, 0, 0, + 0, 0, 1, 255, 255, 7, 7, 0, 0, 0, 0, 0, 0, 0, 7, 7}; +const char GLYPH_LATIN_SMALL_LETTER_I[] = {227, 227, 255, 255, 7, 7}; +const char GLYPH_LATIN_SMALL_LETTER_J[] = {0, 0, 227, 227, 0, 0, 255, 255, 192, 224, 127, 63}; +const char GLYPH_LATIN_SMALL_LETTER_K[] = {255, 255, 0, 0, 0, 0, 128, 192, 96, 32, 32, 0, 255, 255, 24, 60, 54, 115, + 225, 192, 128, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 1, 3, 7, 6, 4}; +const char GLYPH_LATIN_SMALL_LETTER_L[] = {255, 255, 0, 0, 255, 255, 0, 0, 3, 7, 6, 6}; +const char GLYPH_LATIN_SMALL_LETTER_M[] = {192, 224, 96, 96, 96, 96, 96, 224, 192, 192, 64, 96, 96, 96, 96, 224, + 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 0, + 7, 7, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0}; +const char GLYPH_LATIN_SMALL_LETTER_N[] = {192, 224, 96, 96, 96, 96, 96, 224, 192, 192, 0, 255, 255, 0, 0, 0, 0, + 0, 0, 1, 255, 255, 7, 7, 0, 0, 0, 0, 0, 0, 0, 7, 7}; +const char GLYPH_LATIN_SMALL_LETTER_O[] = {0, 128, 192, 192, 96, 96, 96, 96, 96, 192, 192, 128, 0, + 126, 255, 129, 0, 0, 0, 0, 0, 0, 0, 131, 255, 126, + 0, 1, 3, 3, 6, 6, 6, 6, 6, 3, 3, 1, 0}; +const char GLYPH_LATIN_SMALL_LETTER_P[] = {192, 224, 96, 96, 96, 96, 96, 224, 192, 192, 128, 0, + 255, 255, 0, 0, 0, 0, 0, 0, 0, 131, 255, 126, + 255, 255, 3, 6, 6, 6, 6, 6, 3, 3, 1, 0}; +const char GLYPH_LATIN_SMALL_LETTER_Q[] = {0, 128, 192, 192, 96, 96, 96, 96, 96, 96, 224, 192, + 126, 255, 129, 0, 0, 0, 0, 0, 0, 0, 255, 255, + 0, 1, 3, 3, 6, 6, 6, 6, 6, 3, 255, 255}; +const char GLYPH_LATIN_SMALL_LETTER_R[] = {192, 224, 96, 96, 96, 96, 96, 96, 255, 255, 0, 0, + 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0}; +const char GLYPH_LATIN_SMALL_LETTER_S[] = {128, 192, 224, 96, 96, 96, 96, 96, 224, 0, 7, 15, 12, 28, 24, + 24, 56, 48, 240, 224, 3, 6, 6, 6, 6, 6, 6, 7, 3, 1}; +const char GLYPH_LATIN_SMALL_LETTER_T[] = {254, 254, 96, 96, 96, 96, 96, 0, 255, 255, 0, 0, + 0, 0, 0, 0, 1, 3, 7, 6, 6, 6, 6, 3}; +const char GLYPH_LATIN_SMALL_LETTER_U[] = {224, 224, 0, 0, 0, 0, 0, 0, 0, 224, 224, 255, 255, 128, 0, 0, 0, + 0, 0, 0, 255, 255, 0, 3, 3, 7, 6, 6, 6, 6, 6, 7, 3}; +const char GLYPH_LATIN_SMALL_LETTER_V[] = {96, 224, 192, 0, 0, 0, 0, 0, 0, 192, 224, 96, 0, 3, 31, 252, 240, 128, + 128, 240, 252, 31, 3, 0, 0, 0, 0, 0, 3, 7, 7, 3, 0, 0, 0, 0}; +const char GLYPH_LATIN_SMALL_LETTER_W[] = { + 96, 224, 128, 0, 0, 0, 0, 0, 224, 224, 128, 0, 0, 0, 0, 0, 128, 224, 0, 7, 63, 252, 224, 0, 224, 254, 31, + 1, 31, 252, 224, 0, 224, 252, 63, 7, 0, 0, 0, 1, 7, 7, 7, 1, 0, 0, 0, 0, 7, 7, 7, 1, 0, 0}; +const char GLYPH_LATIN_SMALL_LETTER_X[] = {32, 96, 224, 128, 0, 0, 0, 0, 0, 128, 224, 96, 32, + 0, 0, 128, 195, 231, 126, 24, 126, 231, 195, 128, 0, 0, + 4, 6, 7, 1, 0, 0, 0, 0, 0, 1, 7, 6, 4}; +const char GLYPH_LATIN_SMALL_LETTER_Y[] = {32, 224, 192, 0, 0, 0, 0, 0, 0, 192, 224, 32, + 0, 1, 15, 62, 248, 192, 0, 224, 252, 31, 3, 0, + 192, 192, 192, 192, 97, 127, 31, 7, 0, 0, 0, 0}; +const char GLYPH_LATIN_SMALL_LETTER_Z[] = {96, 96, 96, 96, 96, 96, 96, 224, 224, 224, 0, 128, 224, 112, 56, + 12, 7, 3, 1, 0, 7, 7, 7, 6, 6, 6, 6, 6, 6, 6}; +const char GLYPH_LEFT_CURLY_BRACKET[] = {0, 0, 254, 255, 3, 1, 1, 24, 60, 247, 227, + 0, 0, 0, 0, 0, 63, 127, 224, 192, 192}; +const char GLYPH_VERTICAL_LINE[] = {255, 255, 255, 255, 255, 255}; +const char GLYPH_RIGHT_CURLY_BRACKET[] = {1, 1, 3, 255, 254, 0, 0, 0, 0, 0, 227, + 247, 60, 24, 192, 192, 224, 127, 63, 0, 0}; +const char GLYPH_NUMERO_SIGN[] = {254, 254, 28, 56, 224, 192, 0, 0, 0, 0, 0, 0, 254, 254, 255, 255, 0, 0, 0, 1, 7, + 14, 56, 112, 192, 128, 255, 255, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 7, 7}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_BE[] = {254, 254, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 255, 255, 3, 3, 3, 3, + 3, 3, 6, 142, 252, 248, 7, 7, 6, 6, 6, 6, 6, 6, 3, 3, 1, 0}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_GHE[] = {254, 254, 6, 6, 6, 6, 6, 6, 6, 6, 6, 255, 255, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_DE[] = { + 0, 0, 0, 0, 0, 0, 254, 254, 6, 6, 6, 6, 6, 6, 254, 254, 0, 0, 0, 0, 0, 0, 192, 252, 63, 3, 0, + 0, 0, 0, 0, 0, 255, 255, 0, 0, 254, 254, 6, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 254, 254}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_IO[] = {254, 254, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 255, 255, 6, 6, 6, 6, + 6, 6, 6, 6, 0, 0, 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_ZHE[] = {2, 6, 12, 24, 48, 96, 192, 128, 0, 0, 254, 254, 0, 0, 128, 192, 96, + 0, 0, 128, 192, 96, 56, 28, 7, 3, 2, 255, 255, 2, 3, 7, 28, 56, + 4, 7, 3, 1, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_ZE[] = {0, 4, 14, 6, 6, 6, 6, 6, 14, 140, 252, 240, 0, + 0, 0, 0, 6, 6, 6, 6, 6, 7, 15, 29, 248, 240, + 3, 3, 7, 6, 6, 6, 6, 6, 6, 3, 3, 3, 0}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_I[] = {254, 254, 0, 0, 0, 0, 0, 0, 128, 192, 224, 112, 24, 252, 254, + 255, 255, 192, 96, 56, 28, 14, 7, 3, 1, 0, 0, 0, 255, 255, + 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_SHORT_I[] = { + 254, 254, 0, 0, 0, 0, 0, 0, 128, 192, 224, 112, 24, 252, 254, 255, 255, 192, 96, 56, 28, 14, 7, + 3, 1, 0, 0, 0, 255, 255, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_KA[] = {254, 254, 0, 0, 128, 192, 96, 48, 24, 12, 6, 2, 0, 0, + 255, 255, 6, 15, 29, 24, 56, 112, 224, 192, 128, 0, 0, 0, + 7, 7, 0, 0, 0, 0, 0, 0, 0, 1, 3, 7, 6, 4}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_EL[] = {0, 0, 0, 0, 0, 0, 254, 254, 6, 6, 6, 6, 6, 6, 254, 254, + 0, 0, 0, 128, 192, 252, 63, 3, 0, 0, 0, 0, 0, 0, 255, 255, + 6, 6, 7, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_PE[] = {254, 254, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 254, 254, + 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, + 7, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_U[] = {2, 14, 60, 240, 192, 0, 0, 0, 0, 0, 224, 252, 30, 2, + 0, 0, 0, 0, 3, 15, 188, 240, 124, 31, 3, 0, 0, 0, + 0, 6, 6, 6, 6, 7, 3, 1, 0, 0, 0, 0, 0, 0}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_EF[] = {128, 224, 112, 56, 24, 24, 12, 12, 12, 255, 255, 12, 12, + 12, 24, 24, 56, 15, 63, 112, 224, 192, 192, 128, 128, 128, + 255, 255, 128, 128, 128, 192, 192, 224, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 7, 7, 1, 1, 1, 0, 0, 0}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_TSE[] = {254, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254, 254, 0, 0, + 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, + 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 254, 254}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_CHE[] = {254, 254, 0, 0, 0, 0, 0, 0, 0, 0, 254, 254, + 3, 15, 14, 28, 24, 24, 24, 24, 24, 24, 255, 255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_SHA[] = {254, 254, 0, 0, 0, 0, 0, 0, 0, 254, 254, 0, 0, 0, 0, 0, + 255, 255, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, + 7, 7, 6, 6, 6, 6, 6, 6, 6, 7, 7, 6, 6, 6, 6, 6}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_SHCHA[] = {254, 254, 0, 0, 0, 0, 0, 0, 0, 0, 254, 254, 0, 0, 0, 0, + 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, + 7, 7, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 6, 6, 6, 6}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_HARD_SIGN[] = {6, 6, 6, 6, 6, 254, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 255, 255, 3, 3, 3, 3, 3, 3, 6, 142, 252, 248, + 0, 0, 0, 0, 0, 7, 7, 6, 6, 6, 6, 6, 6, 3, 3, 1, 0}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_YERU[] = {254, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 254, + 255, 255, 3, 3, 3, 3, 3, 3, 6, 142, 252, 248, 0, 0, 0, 255, + 7, 7, 6, 6, 6, 6, 6, 6, 3, 3, 1, 0, 0, 0, 0, 7}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_SOFT_SIGN[] = {254, 254, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255, 255, 3, 3, 3, 3, 3, 3, 6, 142, 252, 248, + 7, 7, 6, 6, 6, 6, 6, 6, 3, 3, 1, 0}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_E[] = {12, 12, 6, 6, 6, 6, 6, 6, 12, 28, 120, 240, 192, + 0, 0, 0, 6, 6, 6, 6, 6, 6, 134, 230, 255, 63, + 3, 7, 6, 6, 6, 6, 6, 6, 3, 3, 1, 0, 0}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_YU[] = {254, 254, 0, 0, 0, 0, 128, 224, 120, 24, 12, 14, 6, 6, 6, 6, + 255, 255, 6, 6, 6, 6, 63, 255, 224, 128, 0, 0, 0, 0, 0, 0, + 7, 7, 0, 0, 0, 0, 0, 0, 1, 3, 3, 7, 6, 6, 6, 6}; +const char GLYPH_CYRILLIC_CAPITAL_LETTER_YA[] = {0, 240, 248, 28, 12, 6, 6, 6, 6, 6, 6, 6, 254, 254, + 0, 1, 131, 231, 126, 28, 12, 12, 12, 12, 12, 12, 255, 255, + 4, 7, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7}; +const char GLYPH_CYRILLIC_SMALL_LETTER_BE[] = {192, 248, 220, 198, 102, 99, 99, 99, 97, 193, 193, 129, 0, + 127, 255, 192, 0, 0, 0, 0, 0, 0, 0, 129, 255, 126, + 0, 1, 3, 3, 6, 6, 6, 6, 6, 3, 3, 1, 0}; +const char GLYPH_CYRILLIC_SMALL_LETTER_VE[] = {224, 224, 96, 96, 96, 96, 224, 192, 128, 0, 255, 255, 24, 24, 24, + 24, 28, 63, 247, 224, 7, 7, 6, 6, 6, 6, 6, 7, 3, 1}; +const char GLYPH_CYRILLIC_SMALL_LETTER_GHE[] = {224, 224, 96, 96, 96, 96, 96, 96, 96, 255, 255, 0, 0, 0, + 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 0, 0}; +const char GLYPH_CYRILLIC_SMALL_LETTER_DE[] = {0, 0, 0, 0, 0, 224, 224, 96, 96, 96, 96, 224, 224, 0, 0, + 0, 0, 0, 128, 240, 255, 15, 0, 0, 0, 0, 255, 255, 0, 0, + 126, 126, 6, 7, 7, 6, 6, 6, 6, 6, 6, 7, 7, 126, 126}; +const char GLYPH_CYRILLIC_SMALL_LETTER_IO[] = {0, 128, 192, 199, 103, 103, 96, 96, 103, 231, 199, 128, 0, + 126, 255, 153, 24, 24, 24, 24, 24, 24, 24, 25, 31, 30, + 0, 1, 3, 3, 6, 6, 6, 6, 6, 6, 6, 3, 0}; +const char GLYPH_CYRILLIC_SMALL_LETTER_ZHE[] = { + 0, 32, 96, 224, 192, 0, 0, 0, 0, 224, 224, 0, 0, 0, 0, 192, 224, 96, 0, 0, 0, 128, 193, 99, 54, 28, 8, + 255, 255, 8, 28, 54, 99, 193, 128, 0, 4, 6, 7, 1, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0, 0, 1, 7}; +const char GLYPH_CYRILLIC_SMALL_LETTER_ZE[] = {192, 96, 96, 96, 96, 96, 96, 96, 192, 192, 128, 0, 0, 0, 24, 24, 24, + 24, 24, 60, 255, 227, 3, 6, 6, 6, 6, 6, 6, 6, 7, 3, 1}; +const char GLYPH_CYRILLIC_SMALL_LETTER_I[] = {224, 224, 0, 0, 0, 0, 0, 0, 128, 192, 224, 224, + 255, 255, 192, 224, 112, 60, 30, 7, 3, 1, 255, 255, + 7, 7, 3, 1, 0, 0, 0, 0, 0, 0, 7, 7}; +const char GLYPH_CYRILLIC_SMALL_LETTER_SHORT_I[] = {224, 224, 1, 3, 7, 6, 6, 7, 131, 193, 224, 224, + 255, 255, 192, 224, 112, 60, 30, 7, 3, 1, 255, 255, + 7, 7, 3, 1, 0, 0, 0, 0, 0, 0, 7, 7}; +const char GLYPH_CYRILLIC_SMALL_LETTER_KA[] = {224, 224, 0, 0, 0, 0, 128, 192, 96, 32, 0, 0, + 255, 255, 24, 60, 54, 115, 225, 192, 128, 0, 0, 0, + 7, 7, 0, 0, 0, 0, 0, 1, 3, 7, 6, 4}; +const char GLYPH_CYRILLIC_SMALL_LETTER_EL[] = {0, 0, 0, 0, 224, 224, 96, 96, 96, 96, 96, 96, 224, 224, + 0, 0, 128, 224, 255, 31, 0, 0, 0, 0, 0, 0, 255, 255, + 6, 7, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7}; +const char GLYPH_CYRILLIC_SMALL_LETTER_EM[] = {192, 224, 96, 192, 0, 0, 0, 0, 0, 0, 0, 192, 96, 224, 224, + 255, 255, 0, 1, 15, 56, 224, 128, 224, 56, 15, 1, 0, 255, 255, + 7, 7, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 7, 7}; +const char GLYPH_CYRILLIC_SMALL_LETTER_EN[] = {224, 224, 0, 0, 0, 0, 0, 0, 0, 224, 224, 255, 255, 24, 24, 24, 24, + 24, 24, 24, 255, 255, 7, 7, 0, 0, 0, 0, 0, 0, 0, 7, 7}; +const char GLYPH_CYRILLIC_SMALL_LETTER_PE[] = {224, 224, 96, 96, 96, 96, 96, 96, 96, 224, 224, 255, 255, 0, 0, 0, 0, + 0, 0, 0, 255, 255, 7, 7, 0, 0, 0, 0, 0, 0, 0, 7, 7}; +const char GLYPH_CYRILLIC_SMALL_LETTER_TE[] = {96, 96, 96, 96, 224, 224, 96, 96, 96, 96, 0, 0, 0, 0, 255, + 255, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0, 0}; +const char GLYPH_CYRILLIC_SMALL_LETTER_EF[] = { + 0, 128, 128, 192, 224, 96, 96, 255, 255, 96, 96, 224, 192, 128, 128, 0, 126, 255, 195, 0, 0, 0, 0, 255, + 255, 0, 0, 0, 0, 195, 255, 126, 0, 1, 3, 3, 7, 6, 6, 255, 255, 6, 6, 7, 3, 1, 0, 0}; +const char GLYPH_CYRILLIC_SMALL_LETTER_TSE[] = {224, 224, 0, 0, 0, 0, 0, 0, 224, 224, 0, 0, + 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, + 7, 7, 6, 6, 6, 6, 6, 6, 7, 7, 126, 126}; +const char GLYPH_CYRILLIC_SMALL_LETTER_CHE[] = {224, 224, 0, 0, 0, 0, 0, 0, 224, 224, 15, 31, 56, 48, 48, + 48, 48, 48, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 7, 7}; +const char GLYPH_CYRILLIC_SMALL_LETTER_SHA[] = {224, 224, 0, 0, 0, 0, 0, 0, 224, 224, 0, 0, 0, 0, 0, 224, + 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, + 7, 7, 6, 6, 6, 6, 6, 6, 7, 7, 6, 6, 6, 6, 6, 7}; +const char GLYPH_CYRILLIC_SMALL_LETTER_SHCHA[] = {224, 224, 0, 0, 0, 0, 0, 0, 224, 224, 0, 0, 0, 0, 0, 224, + 255, 255, 0, 0, 0, 0, 0, 0, 255, 255, 0, 0, 0, 0, 0, 255, + 7, 7, 6, 6, 6, 6, 6, 6, 7, 7, 6, 6, 6, 6, 6, 7}; +const char GLYPH_CYRILLIC_SMALL_LETTER_HARD_SIGN[] = {96, 96, 96, 224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 255, 255, 12, 12, 12, 12, 12, 12, 28, 24, 248, 240, + 0, 0, 0, 7, 7, 6, 6, 6, 6, 6, 6, 6, 3, 3, 1}; +const char GLYPH_CYRILLIC_SMALL_LETTER_YERU[] = {224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 224, 224, + 255, 255, 12, 12, 12, 12, 12, 12, 24, 248, 240, 0, 0, 0, 255, 255, + 7, 7, 6, 6, 6, 6, 6, 6, 3, 3, 1, 0, 0, 0, 7, 7}; +const char GLYPH_CYRILLIC_SMALL_LETTER_SOFT_SIGN[] = {224, 224, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 12, 12, 12, + 12, 12, 28, 248, 240, 7, 7, 6, 6, 6, 6, 6, 7, 3, 1}; +const char GLYPH_CYRILLIC_SMALL_LETTER_E[] = {64, 96, 96, 96, 96, 96, 192, 192, 128, 0, 0, 0, 0, 24, 24, + 24, 24, 153, 255, 126, 7, 6, 6, 6, 6, 6, 7, 3, 1, 0}; +const char GLYPH_CYRILLIC_SMALL_LETTER_YU[] = {224, 224, 0, 0, 0, 0, 128, 192, 192, 96, 96, 96, 96, 96, 192, 192, + 255, 255, 24, 24, 24, 126, 255, 129, 0, 0, 0, 0, 0, 0, 0, 131, + 7, 7, 0, 0, 0, 0, 1, 3, 3, 6, 6, 6, 6, 6, 3, 3}; +const char GLYPH_CYRILLIC_SMALL_LETTER_YA[] = {0, 128, 192, 192, 96, 96, 96, 96, 96, 224, 224, + 0, 15, 159, 248, 112, 48, 48, 48, 48, 255, 255, + 4, 7, 3, 0, 0, 0, 0, 0, 0, 7, 7}; + +const char GLYPH_WIDTH[143] = { + 2, 7, 15, 11, 17, 15, 2, 6, 6, 11, 12, 3, 6, 3, 10, 13, 7, 11, 12, 13, 11, 12, 11, 13, 12, 3, 3, 13, 12, + 13, 9, 16, 17, 14, 13, 15, 12, 11, 13, 14, 2, 11, 14, 12, 16, 15, 16, 13, 16, 14, 12, 14, 14, 16, 17, 14, 14, 13, + 6, 11, 6, 13, 13, 5, 11, 12, 10, 12, 13, 8, 12, 11, 2, 4, 12, 4, 16, 11, 13, 12, 12, 8, 10, 8, 11, 12, 18, + 13, 12, 10, 7, 2, 7, 14, 12, 11, 18, 12, 17, 13, 15, 15, 14, 16, 14, 14, 17, 16, 12, 16, 16, 17, 16, 12, 13, 16, + 14, 13, 10, 9, 15, 13, 18, 11, 12, 12, 12, 14, 15, 11, 11, 10, 16, 12, 10, 16, 16, 15, 16, 10, 10, 16, 11}; + +const char *GLYPH_ADDR[143] = { + GLYPH_EXCLAMATION_MARK, + GLYPH_QUOTATION_MARK, + GLYPH_NUMBER_SIGN, + GLYPH_DOLLAR_SIGN, + GLYPH_PERCENT_SIGN, + GLYPH_AMPERSAND, + GLYPH_APOSTROPHE, + GLYPH_LEFT_PARENTHESIS, + GLYPH_RIGHT_PARENTHESIS, + GLYPH_ASTERISK, + GLYPH_PLUS_SIGN, + GLYPH_COMMA, + GLYPH_HYPHEN_MINUS, + GLYPH_FULL_STOP, + GLYPH_SOLIDUS, + GLYPH_DIGIT_ZERO, + GLYPH_DIGIT_ONE, + GLYPH_DIGIT_TWO, + GLYPH_DIGIT_THREE, + GLYPH_DIGIT_FOUR, + GLYPH_DIGIT_FIVE, + GLYPH_DIGIT_SIX, + GLYPH_DIGIT_SEVEN, + GLYPH_DIGIT_EIGHT, + GLYPH_DIGIT_NINE, + GLYPH_COLON, + GLYPH_SEMICOLON, + GLYPH_LESS_THAN_SIGN, + GLYPH_EQUALS_SIGN, + GLYPH_GREATER_THAN_SIGN, + GLYPH_QUESTION_MARK, + GLYPH_COMMERCIAL_AT, + GLYPH_LATIN_CAPITAL_LETTER_A, + GLYPH_LATIN_CAPITAL_LETTER_B, + GLYPH_LATIN_CAPITAL_LETTER_C, + GLYPH_LATIN_CAPITAL_LETTER_D, + GLYPH_LATIN_CAPITAL_LETTER_E, + GLYPH_LATIN_CAPITAL_LETTER_F, + GLYPH_LATIN_CAPITAL_LETTER_G, + GLYPH_LATIN_CAPITAL_LETTER_H, + GLYPH_LATIN_CAPITAL_LETTER_I, + GLYPH_LATIN_CAPITAL_LETTER_J, + GLYPH_LATIN_CAPITAL_LETTER_K, + GLYPH_LATIN_CAPITAL_LETTER_L, + GLYPH_LATIN_CAPITAL_LETTER_M, + GLYPH_LATIN_CAPITAL_LETTER_N, + GLYPH_LATIN_CAPITAL_LETTER_O, + GLYPH_LATIN_CAPITAL_LETTER_P, + GLYPH_LATIN_CAPITAL_LETTER_Q, + GLYPH_LATIN_CAPITAL_LETTER_R, + GLYPH_LATIN_CAPITAL_LETTER_S, + GLYPH_LATIN_CAPITAL_LETTER_T, + GLYPH_LATIN_CAPITAL_LETTER_U, + GLYPH_LATIN_CAPITAL_LETTER_V, + GLYPH_LATIN_CAPITAL_LETTER_W, + GLYPH_LATIN_CAPITAL_LETTER_X, + GLYPH_LATIN_CAPITAL_LETTER_Y, + GLYPH_LATIN_CAPITAL_LETTER_Z, + GLYPH_LEFT_SQUARE_BRACKET, + GLYPH_REVERSE_SOLIDUS, + GLYPH_RIGHT_SQUARE_BRACKET, + GLYPH_CIRCUMFLEX_ACCENT, + GLYPH_LOW_LINE, + GLYPH_GRAVE_ACCENT, + GLYPH_LATIN_SMALL_LETTER_A, + GLYPH_LATIN_SMALL_LETTER_B, + GLYPH_LATIN_SMALL_LETTER_C, + GLYPH_LATIN_SMALL_LETTER_D, + GLYPH_LATIN_SMALL_LETTER_E, + GLYPH_LATIN_SMALL_LETTER_F, + GLYPH_LATIN_SMALL_LETTER_G, + GLYPH_LATIN_SMALL_LETTER_H, + GLYPH_LATIN_SMALL_LETTER_I, + GLYPH_LATIN_SMALL_LETTER_J, + GLYPH_LATIN_SMALL_LETTER_K, + GLYPH_LATIN_SMALL_LETTER_L, + GLYPH_LATIN_SMALL_LETTER_M, + GLYPH_LATIN_SMALL_LETTER_N, + GLYPH_LATIN_SMALL_LETTER_O, + GLYPH_LATIN_SMALL_LETTER_P, + GLYPH_LATIN_SMALL_LETTER_Q, + GLYPH_LATIN_SMALL_LETTER_R, + GLYPH_LATIN_SMALL_LETTER_S, + GLYPH_LATIN_SMALL_LETTER_T, + GLYPH_LATIN_SMALL_LETTER_U, + GLYPH_LATIN_SMALL_LETTER_V, + GLYPH_LATIN_SMALL_LETTER_W, + GLYPH_LATIN_SMALL_LETTER_X, + GLYPH_LATIN_SMALL_LETTER_Y, + GLYPH_LATIN_SMALL_LETTER_Z, + GLYPH_LEFT_CURLY_BRACKET, + GLYPH_VERTICAL_LINE, + GLYPH_RIGHT_CURLY_BRACKET, + GLYPH_NUMERO_SIGN, + GLYPH_CYRILLIC_CAPITAL_LETTER_BE, + GLYPH_CYRILLIC_CAPITAL_LETTER_GHE, + GLYPH_CYRILLIC_CAPITAL_LETTER_DE, + GLYPH_CYRILLIC_CAPITAL_LETTER_IO, + GLYPH_CYRILLIC_CAPITAL_LETTER_ZHE, + GLYPH_CYRILLIC_CAPITAL_LETTER_ZE, + GLYPH_CYRILLIC_CAPITAL_LETTER_I, + GLYPH_CYRILLIC_CAPITAL_LETTER_SHORT_I, + GLYPH_CYRILLIC_CAPITAL_LETTER_KA, + GLYPH_CYRILLIC_CAPITAL_LETTER_EL, + GLYPH_CYRILLIC_CAPITAL_LETTER_PE, + GLYPH_CYRILLIC_CAPITAL_LETTER_U, + GLYPH_CYRILLIC_CAPITAL_LETTER_EF, + GLYPH_CYRILLIC_CAPITAL_LETTER_TSE, + GLYPH_CYRILLIC_CAPITAL_LETTER_CHE, + GLYPH_CYRILLIC_CAPITAL_LETTER_SHA, + GLYPH_CYRILLIC_CAPITAL_LETTER_SHCHA, + GLYPH_CYRILLIC_CAPITAL_LETTER_HARD_SIGN, + GLYPH_CYRILLIC_CAPITAL_LETTER_YERU, + GLYPH_CYRILLIC_CAPITAL_LETTER_SOFT_SIGN, + GLYPH_CYRILLIC_CAPITAL_LETTER_E, + GLYPH_CYRILLIC_CAPITAL_LETTER_YU, + GLYPH_CYRILLIC_CAPITAL_LETTER_YA, + GLYPH_CYRILLIC_SMALL_LETTER_BE, + GLYPH_CYRILLIC_SMALL_LETTER_VE, + GLYPH_CYRILLIC_SMALL_LETTER_GHE, + GLYPH_CYRILLIC_SMALL_LETTER_DE, + GLYPH_CYRILLIC_SMALL_LETTER_IO, + GLYPH_CYRILLIC_SMALL_LETTER_ZHE, + GLYPH_CYRILLIC_SMALL_LETTER_ZE, + GLYPH_CYRILLIC_SMALL_LETTER_I, + GLYPH_CYRILLIC_SMALL_LETTER_SHORT_I, + GLYPH_CYRILLIC_SMALL_LETTER_KA, + GLYPH_CYRILLIC_SMALL_LETTER_EL, + GLYPH_CYRILLIC_SMALL_LETTER_EM, + GLYPH_CYRILLIC_SMALL_LETTER_EN, + GLYPH_CYRILLIC_SMALL_LETTER_PE, + GLYPH_CYRILLIC_SMALL_LETTER_TE, + GLYPH_CYRILLIC_SMALL_LETTER_EF, + GLYPH_CYRILLIC_SMALL_LETTER_TSE, + GLYPH_CYRILLIC_SMALL_LETTER_CHE, + GLYPH_CYRILLIC_SMALL_LETTER_SHA, + GLYPH_CYRILLIC_SMALL_LETTER_SHCHA, + GLYPH_CYRILLIC_SMALL_LETTER_HARD_SIGN, + GLYPH_CYRILLIC_SMALL_LETTER_YERU, + GLYPH_CYRILLIC_SMALL_LETTER_SOFT_SIGN, + GLYPH_CYRILLIC_SMALL_LETTER_E, + GLYPH_CYRILLIC_SMALL_LETTER_YU, + GLYPH_CYRILLIC_SMALL_LETTER_YA, +}; + +inline int glyph_index_for_code_point(int codePoint) { + const int cyrillicTable[] = {32, 94, 33, 95, 96, 36, 98, 99, 100, 101, 102, 103, 44, 39, 46, 104, + 47, 34, 51, 105, 106, 55, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, + 64, 117, 118, 119, 120, 68, 122, 123, 124, 125, 126, 127, 128, 129, 78, 130, + 79, 66, 131, 88, 132, 87, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142}; + if (codePoint < 32) + return -1; + else if (codePoint < 126) + return codePoint - 33; + else if (codePoint < 0x401) + return -1; + else if (codePoint == 0x401) + return 97; + else if (codePoint < 0x410) + return -1; + else if (codePoint < 0x450) + return cyrillicTable[codePoint - 0x410]; + else + return -1; +} diff --git a/graphics.c b/graphics.c new file mode 100644 index 0000000..b5e2d4c --- /dev/null +++ b/graphics.c @@ -0,0 +1,225 @@ +#include "graphics.h" +#include "font_ubuntu.h" +#include "utf8.h" +#include + +void lcd_draw_horizontal_digit_segment(uint16_t x, uint16_t y, uint16_t color) { + x += LCD_OFFSET_X; + y += LCD_OFFSET_Y; + uint16_t y1 = y + 8; + uint16_t x0 = x + 7; + uint16_t w = 32; + for (int i = y; i < y1; ++i, w += 2, --x0) { + _draw_h_strip(x0, i, w, color); + } + x0 += 2; + w -= 4; + y = y1; + y1 = y + 7; + for (int i = y; i < y1; ++i, w -= 2, ++x0) { + _draw_h_strip(x0, i, w, color); + } +} + +void lcd_draw_vertical_digit_segment(uint16_t x, uint16_t y, uint16_t color) { + x += LCD_OFFSET_X; + y += LCD_OFFSET_Y; + uint16_t x1 = x + 8; + uint16_t y0 = y + 7; + uint16_t h = 32; + for (int i = x; i < x1; ++i, h += 2, --y0) { + _draw_v_strip(i, y0, h, color); + } + y0 += 2; + h -= 4; + x = x1; + x1 = x + 7; + for (int i = x; i < x1; ++i, h -= 2, ++y0) { + _draw_v_strip(i, y0, h, color); + } +} + +void lcd_draw_horizontal_small_digit_segment(uint16_t x, uint16_t y, uint16_t color) { + x += LCD_OFFSET_X; + y += LCD_OFFSET_Y; + uint16_t y1 = y + 6; + uint16_t x0 = x + 5; + uint16_t w = 24; + for (int i = y; i < y1; ++i, w += 2, --x0) { + _draw_h_strip(x0, i, w, color); + } + x0 += 2; + w -= 4; + y = y1; + y1 = y + 5; + for (int i = y; i < y1; ++i, w -= 2, ++x0) { + _draw_h_strip(x0, i, w, color); + } +} + +void lcd_draw_vertical_small_digit_segment(uint16_t x, uint16_t y, uint16_t color) { + x += LCD_OFFSET_X; + y += LCD_OFFSET_Y; + uint16_t x1 = x + 6; + uint16_t y0 = y + 5; + uint16_t h = 24; + for (int i = x; i < x1; ++i, h += 2, --y0) { + _draw_v_strip(i, y0, h, color); + } + y0 += 2; + h -= 4; + x = x1; + x1 = x + 5; + for (int i = x; i < x1; ++i, h -= 2, ++y0) { + _draw_v_strip(i, y0, h, color); + } +} + +void lcd_draw_horizontal_xsmall_digit_segment(uint16_t x, uint16_t y, uint16_t color) { + x += LCD_OFFSET_X; + y += LCD_OFFSET_Y; + uint16_t y1 = y + 4; + uint16_t x0 = x + 3; + uint16_t w = 16; + for (int i = y; i < y1; ++i, w += 2, --x0) { + _draw_h_strip(x0, i, w, color); + } + x0 += 2; + w -= 4; + y = y1; + y1 = y + 3; + for (int i = y; i < y1; ++i, w -= 2, ++x0) { + _draw_h_strip(x0, i, w, color); + } +} + +void lcd_draw_vertical_xsmall_digit_segment(uint16_t x, uint16_t y, uint16_t color) { + x += LCD_OFFSET_X; + y += LCD_OFFSET_Y; + uint16_t x1 = x + 4; + uint16_t y0 = y + 3; + uint16_t h = 16; + for (int i = x; i < x1; ++i, h += 2, --y0) { + _draw_v_strip(i, y0, h, color); + } + y0 += 2; + h -= 4; + x = x1; + x1 = x + 3; + for (int i = x; i < x1; ++i, h -= 2, ++y0) { + _draw_v_strip(i, y0, h, color); + } +} + +const uint8_t DIGIT_MASKS[10] = {0b1110111, 0b0100100, 0b1011101, 0b1101101, 0b0101110, + 0b1101011, 0b1111011, 0b0100101, 0b1111111, 0b1101111}; + +void lcd_draw_digit(uint8_t digit, uint16_t x, uint16_t y, uint16_t color, uint16_t bgColor) { + const uint16_t SEGMENT_OFFSETS[7][2] = {{9, 0}, {0, 9}, {49, 9}, {9, 49}, {0, 58}, {49, 58}, {9, 99}}; + for (int seg = 0; seg < 7; ++seg) { + uint16_t segColor = ((DIGIT_MASKS[digit] >> seg) & 1) ? color : bgColor; + if (seg == 0 || seg == 3 || seg == 6) + lcd_draw_horizontal_digit_segment(x + SEGMENT_OFFSETS[seg][0], y + SEGMENT_OFFSETS[seg][1], segColor); + else + lcd_draw_vertical_digit_segment(x + SEGMENT_OFFSETS[seg][0], y + SEGMENT_OFFSETS[seg][1], segColor); + } +} + +void lcd_draw_small_digit(uint8_t digit, uint16_t x, uint16_t y, uint16_t color, uint16_t bgColor) { + const uint16_t SEGMENT_OFFSETS[7][2] = {{7, 0}, {0, 7}, {37, 7}, {7, 37}, {0, 44}, {37, 44}, {7, 74}}; + for (int seg = 0; seg < 7; ++seg) { + uint16_t segColor = ((DIGIT_MASKS[digit] >> seg) & 1) ? color : bgColor; + if (seg == 0 || seg == 3 || seg == 6) + lcd_draw_horizontal_small_digit_segment(x + SEGMENT_OFFSETS[seg][0], y + SEGMENT_OFFSETS[seg][1], segColor); + else + lcd_draw_vertical_small_digit_segment(x + SEGMENT_OFFSETS[seg][0], y + SEGMENT_OFFSETS[seg][1], segColor); + } +} + +void lcd_draw_xsmall_digit(uint8_t digit, uint16_t x, uint16_t y, uint16_t color, uint16_t bgColor) { + const uint16_t SEGMENT_OFFSETS[7][2] = {{4, 0}, {0, 4}, {23, 4}, {4, 23}, {0, 27}, {23, 27}, {4, 46}}; + for (int seg = 0; seg < 7; ++seg) { + uint16_t segColor = ((DIGIT_MASKS[digit] >> seg) & 1) ? color : bgColor; + if (seg == 0 || seg == 3 || seg == 6) + lcd_draw_horizontal_xsmall_digit_segment(x + SEGMENT_OFFSETS[seg][0], y + SEGMENT_OFFSETS[seg][1], segColor); + else + lcd_draw_vertical_xsmall_digit_segment(x + SEGMENT_OFFSETS[seg][0], y + SEGMENT_OFFSETS[seg][1], segColor); + } +} + +void lcd_draw_decimal_point(uint16_t x, uint16_t y, uint16_t color) { + x += LCD_OFFSET_X; + y += LCD_OFFSET_Y; + for (int i = 0; i < 5; ++i) { + _draw_h_strip(x + 4 - i, y + i, 8 + i, color); + } + for (int i = 5; i < 9; ++i) { + _draw_h_strip(x, y + i, 12, color); + } + for (int i = 9; i < 13; ++i) { + _draw_h_strip(x, y + i, 20 - i, color); + } +} + +void lcd_write_string(const char *string, uint16_t x, uint16_t y, uint16_t color, uint16_t bgColor) { + x += LCD_OFFSET_X; + y += LCD_OFFSET_Y; + static uint16_t glyphPixels[24 * 24]; + while (*string) { + int character = decode_code_point(&string); + if (character != 32) { + int glyphIndex = glyph_index_for_code_point(character); + const uint8_t *glyph; + int8_t glyphWidth; + if (glyphIndex != -1) { + glyph = GLYPH_ADDR[glyphIndex]; + glyphWidth = GLYPH_WIDTH[glyphIndex]; + } else { + glyph = GLYPH_MISSING; + glyphWidth = 12; + } + int row = 0, col = 0; + for (int byte = 0; byte < glyphWidth * 3; ++byte) { + int rem = glyph[byte]; + for (int bit = 0; bit < 8; ++bit) { + glyphPixels[(row * 8 + bit) * (glyphWidth + LETTER_SPACING) + col] = (rem & 1) ? color : bgColor; + rem >>= 1; + } + if (++col == glyphWidth) { + col = 0; + ++row; + } + } + for (int j = 0; j < 24; ++j) { + for (int i = 0; i < LETTER_SPACING; ++i) + glyphPixels[j * (glyphWidth + LETTER_SPACING) + glyphWidth + i] = 0; + } + st7789_caset(x, x + glyphWidth + LETTER_SPACING - 1); + st7789_raset(y, y + 24 - 1); + st7789_write(glyphPixels, 24 * (glyphWidth + LETTER_SPACING) * sizeof(uint16_t)); + x += glyphWidth + LETTER_SPACING; + } else { + lcd_fill_rectangle(x, y, 12, 24, bgColor); + x += 12; + } + } +} + +int lcd_string_width(const char *string) { + int width = 0; + while (*string) { + int character = decode_code_point(&string); + if (character == 32) { + width += 12; + continue; + } else { + int glyphIndex = glyph_index_for_code_point(character); + if (glyphIndex != -1) { + width += GLYPH_WIDTH[glyphIndex] + 1; + } else { + width += 12; + } + } + } + return width; +} diff --git a/graphics.h b/graphics.h new file mode 100644 index 0000000..aed81bd --- /dev/null +++ b/graphics.h @@ -0,0 +1,62 @@ +#ifndef VELOMETER_GRAPHICS_H +#define VELOMETER_GRAPHICS_H +#include "st7789.h" + +#define LCD_OFFSET_X 2 +#define LCD_OFFSET_Y 34 +#define LCD_WIDTH 320 +#define LCD_HEIGHT 172 +#define LETTER_SPACING 2 + +static inline void _set_pixel(uint16_t x, uint16_t y, uint16_t color) { + st7789_set_cursor(x, y); + st7789_put(color); +} + +static inline void _draw_h_strip(uint16_t x, uint16_t y, uint16_t len, uint16_t color) { + st7789_caset(x, x + len - 1); + st7789_raset(y, y); + while (len--) { + st7789_put(color); + } +} + +static inline void _draw_v_strip(uint16_t x, uint16_t y, uint16_t len, uint16_t color) { + st7789_caset(x, x); + st7789_raset(y, y + len - 1); + while (len--) { + st7789_put(color); + } +} + +inline void lcd_set_pixel(uint16_t x, uint16_t y, uint16_t color) { + x += LCD_OFFSET_X; + y += LCD_OFFSET_Y; + st7789_set_cursor(x, y); + st7789_put(color); +} + +inline void lcd_fill_rectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) { + x += LCD_OFFSET_X; + y += LCD_OFFSET_Y; + st7789_caset(x, x + w - 1); + st7789_raset(y, y + h - 1); + for (uint16_t i = 0; i < w * h; ++i) { + st7789_put(color); + } +} + +void lcd_draw_horizontal_digit_segment(uint16_t x, uint16_t y, uint16_t color); +void lcd_draw_vertical_digit_segment(uint16_t x, uint16_t y, uint16_t color); +void lcd_draw_horizontal_small_digit_segment(uint16_t x, uint16_t y, uint16_t color); +void lcd_draw_vertical_small_digit_segment(uint16_t x, uint16_t y, uint16_t color); +void lcd_draw_horizontal_xsmall_digit_segment(uint16_t x, uint16_t y, uint16_t color); +void lcd_draw_vertical_xsmall_digit_segment(uint16_t x, uint16_t y, uint16_t color); +void lcd_draw_digit(uint8_t digit, uint16_t x, uint16_t y, uint16_t color, uint16_t bgColor); +void lcd_draw_small_digit(uint8_t digit, uint16_t x, uint16_t y, uint16_t color, uint16_t bgColor); +void lcd_draw_xsmall_digit(uint8_t digit, uint16_t x, uint16_t y, uint16_t color, uint16_t bgColor); +void lcd_draw_decimal_point(uint16_t x, uint16_t y, uint16_t color); +void lcd_write_string(const char *string, uint16_t x, uint16_t y, uint16_t color, uint16_t bgColor); +int lcd_string_width(const char *string); + +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..ac5e4b5 --- /dev/null +++ b/main.c @@ -0,0 +1,589 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "beep.h" +#include "ds1302.h" +#include "graphics.h" +#include "pins.h" +#include "st7789.h" + +#define LOGGING + +#define HALL_SENSOR_DEBOUNCE_DELAY_us 10000 +#define BUTTON_DEBOUNCE_DELAY_us 3000 +#define BUTTON_HOLD_TIME_ms 1000 +#define TICKS_PER_REVOLUTION 6 +#define SPEED_MEASUREMENT_INTERVAL_ms 100 +#define SPEED_MEASUREMENT_WINDOW_SIZE 16 + +#define SPEED_MEASUREMENT_WINDOW_INTERVAL (SPEED_MEASUREMENT_INTERVAL_ms * SPEED_MEASUREMENT_WINDOW_SIZE) + +enum GUIScreen { + MAIN_SCREEN, + SETTINGS_SCREEN, +}; + +enum DisplayMode { + DISPLAY_MODE_SPEED, + DISPLAY_MODE_DISTANCE, +}; + +struct GUIState { + uint screen; + union { + struct { + uint displayMode; + bool displayModeChanged; + } mainScreen; + struct { + //... + } settingsScreen; + } screenState; +}; + +void on_gpio_interrupt(uint pin, uint32_t eventMask); +int64_t hall_sensor_debounce_callback(alarm_id_t alarmId, void *userData); +int64_t button_debounce_callback(alarm_id_t alarmId, void *userData); +int64_t button_hold_callback(alarm_id_t alarmId, void *userData); +bool speed_measure_timer_callback(repeating_timer_t *rt); +void on_button_short_press(uint button); +void on_button_long_press(uint button); + +void update_lcd(); +void gui_init(struct GUIState *state); +void init_button_states(); +void save_to_rtc(); +bool load_from_rtc(); +void reset(); + +const uint8_t FRONT_BUTTON_PINS[4] = {PIN_BUTTON_0, PIN_BUTTON_1, PIN_BUTTON_2, PIN_BUTTON_3}; + +const uint16_t BRIGHTNESS_CONVERT_TABLE_X[] = {0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100}; +const uint16_t BRIGHTNESS_CONVERT_TABLE_Y[] = {0, 7, 12, 21, 37, 64, 111, 194, 338, 588, 1024}; +const size_t BRIGHTNESS_CONVERT_LABLE_LEN = 11; + +const uint millimetersPerRevolution = 1540; +const uint cmphPerTpw = 360000 * millimetersPerRevolution / SPEED_MEASUREMENT_WINDOW_INTERVAL / + TICKS_PER_REVOLUTION; // cm/hour per Ticks/window +const uint cmPerTick = millimetersPerRevolution / TICKS_PER_REVOLUTION / 10; // cm per tick + +int16_t convert_brightness(int16_t input) { + for (int i = 0; i < BRIGHTNESS_CONVERT_LABLE_LEN - 1; ++i) { + if (input < BRIGHTNESS_CONVERT_TABLE_X[i + 1]) { + int16_t x0 = BRIGHTNESS_CONVERT_TABLE_X[i]; + int16_t x1 = BRIGHTNESS_CONVERT_TABLE_X[i + 1]; + int16_t y0 = BRIGHTNESS_CONVERT_TABLE_Y[i]; + int16_t y1 = BRIGHTNESS_CONVERT_TABLE_Y[i + 1]; + return y0 + (y1 - y0) * (input - x0) / (x1 - x0); + } + } + return -1; +} + +// for debouncing +alarm_id_t hallSensorDebounceAlarm = 0; +bool hallSensorLastState = 0; +bool hallSensorPrevState = 0; + +struct { + alarm_id_t debounceAlarm, holdAlarm; + bool currentState, prevState, isHeld; +} buttonStates[4]; + +// for debug (and for the future) +absolute_time_t lastWheelTickTime = 0; + +// for speed measurement +uint8_t intervalWheelTicks[SPEED_MEASUREMENT_WINDOW_SIZE]; +uint8_t currentInterval = 0; +uint16_t ticksPerWindow = 0; +// critical_section_t intervalWheelTicksCritSec; +repeating_timer_t speedMeasureTimer; + +uint totalTicks = 0; + +DateTime currentDatetime; + +// unused currently +// repeating_timer_t audioPlaybackTimer; + +struct GUIState guiState; + +const uint16_t *find_splash_image_offset(const uint16_t *start) { + const uint16_t marker[3] = {0x6f66, 0x626f, 0x7261}; + for (int offs = 0; offs < 2 * 1024 * 1024; ++offs) { + if (start[offs + 0] == marker[0] && start[offs + 1] == marker[1] && start[offs + 2] == marker[2]) { + return start + offs; + } + } + return NULL; +} + +int main() { +#ifdef LOGGING + stdio_usb_init(); +#endif + + // display init + const struct st7789_config displayConfig = { + .spi = PICO_DEFAULT_SPI_INSTANCE(), + .gpio_din = PIN_LCD_SDA, + .gpio_clk = PIN_LCD_SCL, + .gpio_cs = PIN_LCD_CS, + .gpio_dc = PIN_LCD_DATA_COMMAND, + .gpio_rst = PIN_LCD_RESET, + .gpio_bl = PIN_LCD_BACKLIGHT, + }; + st7789_init(&displayConfig, LCD_WIDTH + LCD_OFFSET_X, LCD_HEIGHT + LCD_OFFSET_Y); + + // splash image (just for fun) + const uint16_t *splashImage = (const uint16_t *)(XIP_NOCACHE_NOALLOC_NOTRANSLATE_BASE + 1581056); + if (splashImage) { + st7789_caset(LCD_OFFSET_X, LCD_OFFSET_X + 317); + st7789_raset(LCD_OFFSET_Y, LCD_OFFSET_Y + 171); + st7789_write(splashImage, 318 * 172 * sizeof(uint16_t)); + } else { + rom_reset_usb_boot(0, 0); + } + sleep_ms(1000); + +#ifdef LOGGING + if (psram_is_available()) { + printf("PSRAM available. detecting size... "); + size_t psramSize = psram_detect_size(); + printf("%u\n", psramSize); + } +#endif + + // critical_section_init(&intervalWheelTicksCritSec); + add_repeating_timer_ms(SPEED_MEASUREMENT_INTERVAL_ms, speed_measure_timer_callback, NULL, &speedMeasureTimer); + + gpio_set_irq_callback(on_gpio_interrupt); + + gpio_init(PIN_BEEPER); + gpio_set_dir(PIN_BEEPER, GPIO_OUT); + + // hall sensor + gpio_init(PIN_MAIN_HALL_SENSOR); + gpio_pull_up(PIN_MAIN_HALL_SENSOR); + gpio_set_irq_enabled(PIN_MAIN_HALL_SENSOR, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true); + + // on-board LED + gpio_init(25); + gpio_set_dir(25, true); + + // front buttons + init_button_states(); + gpio_init_mask(0b1111 << 15); + for (int pin = 15; pin <= 18; ++pin) { + gpio_pull_up(pin); + gpio_set_irq_enabled(pin, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, true); + } + + irq_set_enabled(IO_IRQ_BANK0, 1); + + ds1302_init(); + + DateTime tmp = {26, 9, 9, 15, 10, 0, DOW_WED}; + // ds1302_set_datetime(&tmp); + + load_from_rtc(); + + gpio_set_function(PIN_LCD_BACKLIGHT, GPIO_FUNC_PWM); + pwm_set_wrap(BACKLIGHT_PWM_SLICE, 1024); + pwm_set_chan_level(BACKLIGHT_PWM_SLICE, BACKLIGHT_PWM_CHANNEL, 1024); + pwm_set_enabled(BACKLIGHT_PWM_SLICE, true); + + gpio_set_function(7, GPIO_FUNC_PWM); + pwm_set_wrap(3, 256); + pwm_set_chan_level(3, PWM_CHAN_B, 0); + pwm_set_enabled(3, true); + + gui_init(&guiState); + + absolute_time_t nextLcdUpdateTime = delayed_by_ms(get_absolute_time(), 50); + absolute_time_t nextClockUpdateTime = get_absolute_time(); + absolute_time_t nextSaveToRtcTime = delayed_by_ms(get_absolute_time(), 10000); + + while (true) { + absolute_time_t t = get_absolute_time(); + if (t >= nextClockUpdateTime) { + nextClockUpdateTime = delayed_by_ms(t, 1000); + ds1302_get_datetime(¤tDatetime); + printf("time is %i %i %i %i %i %i %i\n", currentDatetime.year, currentDatetime.month, currentDatetime.day, + currentDatetime.dow, currentDatetime.hour, currentDatetime.minute, currentDatetime.second); + } + if (t >= nextLcdUpdateTime) { + nextLcdUpdateTime = delayed_by_ms(t, 50); + update_lcd(); + if (gpio_get(PIN_BUTTON_0) == 0 && gpio_get(PIN_BUTTON_3) == 0) { + rom_reset_usb_boot(0, 0); + } + } + if (t >= nextSaveToRtcTime) { + nextSaveToRtcTime = delayed_by_ms(t, 10000); + save_to_rtc(); + } + sleep_until(nextLcdUpdateTime); + } + + return 0; +} + +void on_gpio_interrupt(uint pin, uint32_t eventMask) { + if (pin == PIN_MAIN_HALL_SENSOR) { + if (eventMask & GPIO_IRQ_EDGE_RISE) + hallSensorLastState = 0; + if (eventMask & GPIO_IRQ_EDGE_FALL) + hallSensorLastState = 1; + if (hallSensorDebounceAlarm) + cancel_alarm(hallSensorDebounceAlarm); + hallSensorDebounceAlarm = + add_alarm_in_us(HALL_SENSOR_DEBOUNCE_DELAY_us, hall_sensor_debounce_callback, NULL, false); + gpio_acknowledge_irq(pin, eventMask); + } else if (pin == PIN_BUTTON_0 || pin == PIN_BUTTON_1 || pin == PIN_BUTTON_2 || pin == PIN_BUTTON_3) { + uint buttonIndex; + switch (pin) { + case PIN_BUTTON_0: + buttonIndex = 0; + break; + case PIN_BUTTON_1: + buttonIndex = 1; + break; + case PIN_BUTTON_2: + buttonIndex = 2; + break; + case PIN_BUTTON_3: + buttonIndex = 3; + } + auto buttonState = &buttonStates[buttonIndex]; + // if (eventMask & GPIO_IRQ_EDGE_RISE) + // buttonState->currentState = 0; + // if (eventMask & GPIO_IRQ_EDGE_FALL) + // buttonState->currentState = 1; + // buttonState->currentState = !gpio_get(pin); + if (buttonState->debounceAlarm != -1) + cancel_alarm(buttonState->debounceAlarm); + buttonState->debounceAlarm = + add_alarm_in_us(BUTTON_DEBOUNCE_DELAY_us, button_debounce_callback, (void *)buttonIndex, false); + gpio_acknowledge_irq(pin, eventMask); + } +} + +int64_t hall_sensor_debounce_callback(alarm_id_t alarmId, void *userData) { + (void)alarmId; + (void)userData; + if (hallSensorLastState != hallSensorPrevState) { + absolute_time_t now = get_absolute_time(); + lastWheelTickTime = now; + ++intervalWheelTicks[currentInterval]; + ++totalTicks; + + gpio_put(25, !gpio_get(25)); + hallSensorPrevState = hallSensorLastState; + } + return 0; +} + +int64_t button_debounce_callback(alarm_id_t alarmId, void *userData) { + (void)alarmId; + uint buttonIndex = (uint)userData; + auto buttonState = &buttonStates[buttonIndex]; + bool currentState = !gpio_get(FRONT_BUTTON_PINS[buttonIndex]); + if (currentState != buttonState->prevState) { + buttonState->prevState = currentState; + if (buttonState->holdAlarm != -1) { + cancel_alarm(buttonState->holdAlarm); + } + if (currentState == 1) { + buttonState->holdAlarm = add_alarm_in_ms(BUTTON_HOLD_TIME_ms, button_hold_callback, (void *)buttonIndex, false); + } else { + buttonState->holdAlarm = -1; + if (!buttonState->isHeld) + on_button_short_press(buttonIndex); + else + buttonState->isHeld = false; + } + } + return 0; +} + +int64_t button_hold_callback(alarm_id_t alarmId, void *userData) { + (void)alarmId; + uint buttonIndex = (uint)userData; + auto buttonState = &buttonStates[buttonIndex]; + buttonState->holdAlarm = -1; + buttonState->isHeld = true; + on_button_long_press(buttonIndex); + return 0; +} + +void on_button_short_press(uint button) { + // beep(60, 900); +#ifdef LOGGING + printf("button %i short press\n", button); +#endif + if (button == 3 && guiState.screen == MAIN_SCREEN) { + uint oldDispMode = guiState.screenState.mainScreen.displayMode; + if (oldDispMode == DISPLAY_MODE_SPEED) + guiState.screenState.mainScreen.displayMode = DISPLAY_MODE_DISTANCE; + else + guiState.screenState.mainScreen.displayMode = DISPLAY_MODE_SPEED; + guiState.screenState.mainScreen.displayModeChanged = true; + } +} + +void on_button_long_press(uint button) { + // beep(60, 1273); +#ifdef LOGGING + printf("button %i long press\n"); +#endif + if (button == 2 && buttonStates[3].isHeld || + button == 3 && buttonStates[2].isHeld) { // if buttons 2 & 3 pressed and held simultaneously... + reset(); + } +} + +bool speed_measure_timer_callback(repeating_timer_t *rt) { + (void)rt; + + uint16_t sum = 0; + /* + printf("window: "); + for (int i = 0; i < 16; ++i) { + sum += intervalWheelTicks[i]; + if (currentInterval == i) + printf("[%i] ", intervalWheelTicks[i]); + else + printf("%i ", intervalWheelTicks[i]); + } + printf("\n"); + */ + for (int i = 0; i < 16; ++i) { + sum += intervalWheelTicks[i]; + } + currentInterval = (currentInterval + 1) % 16; + intervalWheelTicks[currentInterval] = 0; + ticksPerWindow = sum; + // printf("Total %i \r", sum); + return true; +} + +// speed is in "2.1" decimal fixed-point, in km/h +void draw_speed(uint value) { +#define DIGITS_START_X 20 +#define DIGITS_START_Y 37 +#define DIGIT_WIDTH 64 +#define DIGIT_SPACING 8 + + if (value > 999) + value = 999; + + uint16_t color; + if (value >= 300) + color = 0xfd04; + else if (value >= 200) + color = 0xfe84; + else if (value >= 150) + color = 0xdfe4; + else if (value >= 100) + color = 0x6fe4; + else if (value >= 50) + color = 0x27fb; + else + color = 0x04ff; + + lcd_draw_digit(value / 100, DIGITS_START_X, DIGITS_START_Y, color, 0x0000); + lcd_draw_digit((value / 10) % 10, DIGITS_START_X + DIGIT_WIDTH + DIGIT_SPACING, DIGITS_START_Y, color, 0x0000); + lcd_draw_decimal_point(DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING, DIGITS_START_Y + 101, color); + lcd_draw_digit(value % 10, DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING * 2 + 5, DIGITS_START_Y, color, 0x0000); + +#undef DIGITS_START_X +#undef DIGITS_START_Y +#undef DIGIT_WIDTH +#undef DIGIT_SPACING +} + +// distance is in "2.3" decimal fixed-point, in kilometers +void draw_distance(uint value) { +#define DIGITS_START_X 20 +#define DIGITS_START_Y 37 +#define SMALL_DIGITS_Y_OFFSET 28 +#define XSMALL_DIGITS_Y_OFFSET 28 +#define DIGIT_WIDTH 64 +#define SMALL_DIGIT_WIDTH 48 +#define XSMALL_DIGIT_WIDTH 30 +#define DIGIT_SPACING 8 +#define SMALL_DIGIT_SPACING 6 +#define XSMALL_DIGIT_SPACING 4 + + if (value > 99999) + value = 99999; + + lcd_draw_digit(value / 10000, DIGITS_START_X, DIGITS_START_Y, 0xffff, 0x0000); + lcd_draw_digit((value / 1000) % 10, DIGITS_START_X + DIGIT_WIDTH + DIGIT_SPACING, DIGITS_START_Y, 0xffff, 0x0000); + lcd_draw_decimal_point(DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING, DIGITS_START_Y + 101, 0xffff); + lcd_draw_small_digit((value / 100) % 10, DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING * 2 + 10, + DIGITS_START_Y + SMALL_DIGITS_Y_OFFSET, 0xffff, 0x0000); + lcd_draw_small_digit((value / 10) % 10, + DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING * 2 + SMALL_DIGIT_WIDTH + SMALL_DIGIT_SPACING + + 10, + DIGITS_START_Y + SMALL_DIGITS_Y_OFFSET, 0xffff, 0x0000); + lcd_draw_xsmall_digit(value % 10, + DIGITS_START_X + DIGIT_WIDTH * 2 + DIGIT_SPACING * 2 + SMALL_DIGIT_WIDTH * 2 + + SMALL_DIGIT_SPACING + XSMALL_DIGIT_SPACING + 10, + DIGITS_START_Y + XSMALL_DIGITS_Y_OFFSET, 0xffff, 0x0000); + +#undef DIGITS_START_X +#undef DIGITS_START_Y +#undef DIGIT_WIDTH +#undef DIGIT_SPACING +} + +void update_lcd() { + if (guiState.screen == MAIN_SCREEN) { + if (guiState.screenState.mainScreen.displayModeChanged) { + // lcd_fill_rectangle(49, 27, 213, 113, 0x0000); + st7789_fill(0x0000); + guiState.screenState.mainScreen.displayModeChanged = false; + } + + if (guiState.screenState.mainScreen.displayMode == DISPLAY_MODE_SPEED) { + uint convertedSpeed = ticksPerWindow * cmphPerTpw / 10000; + draw_speed(convertedSpeed); + lcd_write_string("Скорость", 20, 0, 0xffff, 0x0000); + lcd_write_string("км/ч", 241, 126, 0xffff, 0x0000); + } else if (guiState.screenState.mainScreen.displayMode == DISPLAY_MODE_DISTANCE) { + uint convertedDistance = totalTicks * cmPerTick / 100; + draw_distance(convertedDistance); + lcd_write_string("Расстояние", 20, 0, 0xffff, 0x0000); + lcd_write_string("км", 280, 126, 0xffff, 0x0000); + } + + char timeDisplay[6]; + memset(timeDisplay, ' ', sizeof(timeDisplay)); + timeDisplay[5] = 0; + sprintf(timeDisplay, "%i:%02i", currentDatetime.hour, currentDatetime.minute); + lcd_write_string(timeDisplay, 320 - 20 - lcd_string_width(timeDisplay), 0, 0xffff, 0x0000); + lcd_fill_rectangle(320 - 160, 0, 140 - lcd_string_width(timeDisplay), 24, 0x0000); + } else if (guiState.screen == SETTINGS_SCREEN) { + } +} + +void gui_init(struct GUIState *state) { + state->screen = 0; + state->screenState.mainScreen.displayMode = DISPLAY_MODE_DISTANCE; + state->screenState.mainScreen.displayModeChanged = true; +} + +void init_button_states() { + for (int i = 0; i < 4; ++i) { + buttonStates[i].debounceAlarm = -100; + buttonStates[i].holdAlarm = -1; + buttonStates[i].currentState = 0; + buttonStates[i].prevState = 0; + buttonStates[i].isHeld = 0; + } +} + +void reset() { + pwm_set_chan_level(BACKLIGHT_PWM_SLICE, BACKLIGHT_PWM_CHANNEL, 0); // turn off the screen + (*((volatile uint32_t *)(PPB_BASE + 0x0ED0C))) = 0x5FA0004; // weird trick to actually reset the controller +} + +void save_to_rtc() { + printf("saving distance to RTC... "); + static bool savedBefore = false; + static uint savedDistance; + uint8_t buffer[31]; + memset(buffer, 0, 31); + uint newDistance = totalTicks * cmPerTick / 10; + if (savedBefore && newDistance == savedDistance) { + printf("skipped saving\n"); + return; + } + + *(uint *)(buffer + 0) = newDistance; + + uint16_t checksum = 0; + for (int i = 0; i < 29; ++i) { + checksum += buffer[i]; + } + *(uint16_t *)(buffer + 29) = checksum; + + uint8_t checkBuffer[31]; + int limit = 10; + do { + printf("\nattempting to write...\n"); + ds1302_set_write_protection(0); + // for (int i = 0; i < 31; ++i) { + // ds1302_set_ram_byte(0xC0 + i * 2, buffer + i); + // sleep_us(2); + // } + ds1302_write_ram_bulk(buffer, 31); + for (int i = 0; i < 30; ++i) + printf("%02x ", buffer[i]); + printf("%02x\n", buffer[30]); + ds1302_read_ram_bulk(checkBuffer, 31); + printf("attempting to read back...\n"); + for (int i = 0; i < 30; ++i) + printf("%02x ", checkBuffer[i]); + printf("%02x\n", checkBuffer[30]); + if (limit-- == 0) + break; + } while (memcmp(buffer, checkBuffer, 31) != 0); + +#ifdef LOGGING + for (int i = 0; i < 30; ++i) + printf("%02x ", buffer[i]); + printf("%02x\n", buffer[30]); +#endif + + savedDistance = newDistance; + savedBefore = true; +#ifdef LOGGING + printf("saved\n"); +#endif +} + +bool load_from_rtc() { + uint8_t buffer[31]; + ds1302_read_ram_bulk(buffer, 31); + +#ifdef LOGGING + for (int i = 0; i < 30; ++i) + printf("%02x ", buffer[i]); + printf("%02x\n", buffer[30]); +#endif + + uint16_t checksum = *(uint16_t *)(buffer + 29); + uint16_t actualChecksum = 0; + for (int i = 0; i < 29; ++i) { + actualChecksum += buffer[i]; + } + if (actualChecksum != checksum) { +#ifdef LOGGING + printf("discarding invalid data in RTC memory\n"); +#endif + return false; + } + + uint savedDistance = *(uint *)(buffer + 0); + totalTicks = savedDistance * 10 / cmPerTick; +#ifdef LOGGING + printf("successfully loaded data from RTC memory\n"); +#endif + return true; +} diff --git a/partition_table.json b/partition_table.json new file mode 100644 index 0000000..04dc218 --- /dev/null +++ b/partition_table.json @@ -0,0 +1,15 @@ +{ + "version": [1, 0], + "unpartitioned": { + "families": ["absolute"], + "permissions": { + "secure": "rw", + "nonsecure": "rw", + "bootloader": "rw" + } + }, + "partitions": [ + {"name": "app", "id": 0, "size": "1536K", "families": ["rp2350-arm-s"], "permissions": {"secure": "rw", "nonsecure": "rw", "bootloader": "rw"}}, + {"name": "splash", "id": 1, "size": "112K", "families": ["data"], "permissions": {"secure": "rw", "nonsecure": "rw", "bootloader": "rw"}} + ] +} diff --git a/pico_sdk_import.cmake b/pico_sdk_import.cmake new file mode 100644 index 0000000..d493cc2 --- /dev/null +++ b/pico_sdk_import.cmake @@ -0,0 +1,121 @@ +# This is a copy of /external/pico_sdk_import.cmake + +# This can be dropped into an external project to help locate this SDK +# It should be include()ed prior to project() + +# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. +# +# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +# following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +# disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) + set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) + message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) + set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) + message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) + set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) + message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") +endif () + +if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG)) + set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG}) + message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')") +endif () + +if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG) + set(PICO_SDK_FETCH_FROM_GIT_TAG "master") + message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG") +endif() + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") +set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") +set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK") + +if (NOT PICO_SDK_PATH) + if (PICO_SDK_FETCH_FROM_GIT) + include(FetchContent) + set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) + if (PICO_SDK_FETCH_FROM_GIT_PATH) + get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") + endif () + FetchContent_Declare( + pico_sdk + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + ) + + if (NOT pico_sdk) + message("Downloading Raspberry Pi Pico SDK") + # GIT_SUBMODULES_RECURSE was added in 3.17 + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + GIT_SUBMODULES_RECURSE FALSE + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + else () + FetchContent_Populate( + pico_sdk + QUIET + GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk + GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG} + + SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src + BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build + SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild + ) + endif () + + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." + ) + endif () +endif () + +get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") +if (NOT EXISTS ${PICO_SDK_PATH}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") +endif () + +set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) +if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) + message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/pins.h b/pins.h new file mode 100644 index 0000000..075818c --- /dev/null +++ b/pins.h @@ -0,0 +1,28 @@ +#ifndef VELOMETER_PINS_H +#define VELOMETER_PINS_H + +#define PIN_LCD_CS 1 +#define PIN_LCD_SCL 2 +#define PIN_LCD_SDA 3 +#define PIN_LCD_DATA_COMMAND 4 +#define PIN_LCD_RESET 5 + +#define PIN_LCD_BACKLIGHT 6 +#define BACKLIGHT_PWM_SLICE 3 +#define BACKLIGHT_PWM_CHANNEL 0 + +#define PIN_RTC_RESET 9 +#define PIN_RTC_CLOCK 10 +#define PIN_RTC_IO 11 + +#define PIN_BEEPER 12 + +#define PIN_BUTTON_0 15 +#define PIN_BUTTON_1 16 +#define PIN_BUTTON_2 17 +#define PIN_BUTTON_3 18 + +#define PIN_MAIN_HALL_SENSOR 23 +#define PIN_AUX_HALL_SENSOR 24 + +#endif diff --git a/splash.png b/splash.png new file mode 100644 index 0000000..8b86c29 Binary files /dev/null and b/splash.png differ diff --git a/st7789.c b/st7789.c new file mode 100644 index 0000000..91bf4af --- /dev/null +++ b/st7789.c @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#include + +#include "hardware/gpio.h" +#include "pico/stdlib.h" + +#include "st7789.h" + +static struct st7789_config st7789_cfg; +static uint16_t st7789_width; +static uint16_t st7789_height; +static bool st7789_data_mode = false; + +void st7789_cmd(uint8_t cmd, const uint8_t *data, size_t len) { + if (st7789_cfg.gpio_cs > -1) { + spi_set_format(st7789_cfg.spi, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); + } else { + spi_set_format(st7789_cfg.spi, 8, SPI_CPOL_1, SPI_CPHA_1, SPI_MSB_FIRST); + } + st7789_data_mode = false; + + sleep_us(1); + if (st7789_cfg.gpio_cs > -1) { + gpio_put(st7789_cfg.gpio_cs, 0); + } + gpio_put(st7789_cfg.gpio_dc, 0); + sleep_us(1); + + spi_write_blocking(st7789_cfg.spi, &cmd, sizeof(cmd)); + + if (len) { + sleep_us(1); + gpio_put(st7789_cfg.gpio_dc, 1); + sleep_us(1); + + spi_write_blocking(st7789_cfg.spi, data, len); + } + + sleep_us(1); + if (st7789_cfg.gpio_cs > -1) { + gpio_put(st7789_cfg.gpio_cs, 1); + } + gpio_put(st7789_cfg.gpio_dc, 1); + sleep_us(1); +} + +void st7789_caset(uint16_t xs, uint16_t xe) { + uint8_t data[] = { + xs >> 8, + xs & 0xff, + xe >> 8, + xe & 0xff, + }; + + // CASET (2Ah): Column Address Set + st7789_cmd(0x2a, data, sizeof(data)); +} + +void st7789_raset(uint16_t ys, uint16_t ye) { + uint8_t data[] = { + ys >> 8, + ys & 0xff, + ye >> 8, + ye & 0xff, + }; + + // RASET (2Bh): Row Address Set + st7789_cmd(0x2b, data, sizeof(data)); +} + +void st7789_init(const struct st7789_config *config, uint16_t width, uint16_t height) { + memcpy(&st7789_cfg, config, sizeof(st7789_cfg)); + st7789_width = width; + st7789_height = height; + + spi_init(st7789_cfg.spi, 125 * 1000 * 1000); + if (st7789_cfg.gpio_cs > -1) { + spi_set_format(st7789_cfg.spi, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); + } else { + spi_set_format(st7789_cfg.spi, 8, SPI_CPOL_1, SPI_CPHA_1, SPI_MSB_FIRST); + } + + gpio_set_function(st7789_cfg.gpio_din, GPIO_FUNC_SPI); + gpio_set_function(st7789_cfg.gpio_clk, GPIO_FUNC_SPI); + + if (st7789_cfg.gpio_cs > -1) { + gpio_init(st7789_cfg.gpio_cs); + } + gpio_init(st7789_cfg.gpio_dc); + gpio_init(st7789_cfg.gpio_rst); + gpio_init(st7789_cfg.gpio_bl); + + if (st7789_cfg.gpio_cs > -1) { + gpio_set_dir(st7789_cfg.gpio_cs, GPIO_OUT); + } + gpio_set_dir(st7789_cfg.gpio_dc, GPIO_OUT); + gpio_set_dir(st7789_cfg.gpio_rst, GPIO_OUT); + gpio_set_dir(st7789_cfg.gpio_bl, GPIO_OUT); + + if (st7789_cfg.gpio_cs > -1) { + gpio_put(st7789_cfg.gpio_cs, 1); + } + gpio_put(st7789_cfg.gpio_dc, 1); + gpio_put(st7789_cfg.gpio_rst, 1); + sleep_ms(100); + + // SWRESET (01h): Software Reset + st7789_cmd(0x01, NULL, 0); + sleep_ms(150); + + // SLPOUT (11h): Sleep Out + st7789_cmd(0x11, NULL, 0); + sleep_ms(50); + + // COLMOD (3Ah): Interface Pixel Format + // - RGB interface color format = 65K of RGB interface + // - Control interface color format = 16bit/pixel + st7789_cmd(0x3a, (uint8_t[]){0x55}, 1); + sleep_ms(10); + + // MADCTL (36h): Memory Data Access Control + // - Page Address Order = Top to Bottom + // - Column Address Order = Left to Right + // - Page/Column Order = Normal Mode + // - Line Address Order = LCD Refresh Top to Bottom + // - RGB/BGR Order = RGB + // - Display Data Latch Data Order = LCD Refresh Left to Right + st7789_cmd(0x36, (uint8_t[]){0xA0}, 1); + + st7789_caset(0, width); + st7789_raset(0, height); + + // INVON (21h): Display Inversion On + st7789_cmd(0x21, NULL, 0); + sleep_ms(10); + + // NORON (13h): Normal Display Mode On + st7789_cmd(0x13, NULL, 0); + sleep_ms(10); + + // DISPON (29h): Display On + st7789_cmd(0x29, NULL, 0); + sleep_ms(10); + + st7789_fill(0x0000); + sleep_ms(50); + gpio_put(st7789_cfg.gpio_bl, 1); +} + +void st7789_ramwr() { + sleep_us(1); + if (st7789_cfg.gpio_cs > -1) { + gpio_put(st7789_cfg.gpio_cs, 0); + } + gpio_put(st7789_cfg.gpio_dc, 0); + sleep_us(1); + + // RAMWR (2Ch): Memory Write + uint8_t cmd = 0x2c; + spi_write_blocking(st7789_cfg.spi, &cmd, sizeof(cmd)); + + sleep_us(1); + if (st7789_cfg.gpio_cs > -1) { + gpio_put(st7789_cfg.gpio_cs, 0); + } + gpio_put(st7789_cfg.gpio_dc, 1); + sleep_us(1); +} + +void st7789_write(const void *data, size_t len) { + if (!st7789_data_mode) { + st7789_ramwr(); + + if (st7789_cfg.gpio_cs > -1) { + spi_set_format(st7789_cfg.spi, 16, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); + } else { + spi_set_format(st7789_cfg.spi, 16, SPI_CPOL_1, SPI_CPHA_1, SPI_MSB_FIRST); + } + + st7789_data_mode = true; + } + + spi_write16_blocking(st7789_cfg.spi, data, len / 2); +} + +void st7789_put(uint16_t pixel) { st7789_write(&pixel, sizeof(pixel)); } + +void st7789_fill(uint16_t pixel) { + int num_pixels = st7789_width * st7789_height; + + st7789_set_cursor(0, 0); + + for (int i = 0; i < num_pixels; i++) { + st7789_put(pixel); + } +} + +void st7789_set_cursor(uint16_t x, uint16_t y) { + st7789_caset(x, st7789_width); + st7789_raset(y, st7789_height); +} + +void st7789_vertical_scroll(uint16_t row) { + uint8_t data[] = {(row >> 8) & 0xff, row & 0x00ff}; + + // VSCSAD (37h): Vertical Scroll Start Address of RAM + st7789_cmd(0x37, data, sizeof(data)); +} diff --git a/st7789.h b/st7789.h new file mode 100644 index 0000000..06ffad0 --- /dev/null +++ b/st7789.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved. + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#ifndef _PICO_ST7789_H_ +#define _PICO_ST7789_H_ + +#include "hardware/spi.h" + +struct st7789_config { + spi_inst_t *spi; + uint gpio_din; + uint gpio_clk; + int gpio_cs; + uint gpio_dc; + uint gpio_rst; + uint gpio_bl; +}; + +void st7789_cmd(uint8_t cmd, const uint8_t *data, size_t len); +void st7789_init(const struct st7789_config *config, uint16_t width, uint16_t height); +void st7789_write(const void *data, size_t len); +void st7789_put(uint16_t pixel); +void st7789_fill(uint16_t pixel); +void st7789_caset(uint16_t xs, uint16_t xe); +void st7789_raset(uint16_t ys, uint16_t ye); +void st7789_set_cursor(uint16_t x, uint16_t y); +void st7789_vertical_scroll(uint16_t row); + +#endif diff --git a/utf8.c b/utf8.c new file mode 100644 index 0000000..255ed6d --- /dev/null +++ b/utf8.c @@ -0,0 +1,59 @@ +#include "utf8.h" + +// This macro tests if a char is a continuation byte in utf8. +#define IS_CONT(x) (((x) & 0xc0) == 0x80) + +// This returns the code point encoded at **s and advances *s to point to the +// next character. Thus it can easily be used in a loop. +int decode_code_point(const char **s) { + int k = **s ? __builtin_clz(~(**s << 24)) : 0; // Count # of leading 1 bits. + int mask = (1 << (8 - k)) - 1; // All 1s with k leading 0s. + int value = **s & mask; + // k = 0 for one-byte code points; otherwise, k = #total bytes. + for (++(*s), --k; k > 0 && IS_CONT(**s); --k, ++(*s)) { + value <<= 6; + value += (**s & 0x3F); + } + return value; +} + +// This assumes that `code` is <= 0x10FFFF and ensures that nothing will be +// written at or beyond `end`. It advances *s so it's easy to use in a loop. +void encode_code_point(char **s, char *end, int code) { + char val[4]; + int lead_byte_max = 0x7F; + int val_index = 0; + while (code > lead_byte_max) { + val[val_index++] = (code & 0x3F) | 0x80; + code >>= 6; + lead_byte_max >>= (val_index == 1 ? 2 : 1); + } + val[val_index++] = (code & lead_byte_max) | (~lead_byte_max << 1); + while (val_index-- && *s < end) { + **s = val[val_index]; + (*s)++; + } +} + +// This returns 0 if no split was needed. +int split_into_surrogates(int code, int *surr1, int *surr2) { + if (code <= 0xFFFF) + return 0; + *surr2 = 0xDC00 | (code & 0x3FF); // Save the low 10 bits. + code >>= 10; // Drop the low 10 bits. + // If `code` now has low bits "uuu uuxx xxxx", then the bits of *surr are + // "1101 10ww wwxx xxxx" where wwww = (uuuuu - 1). + *surr1 = 0xD800 | ((code & 0x7FF) - 0x40); + return 1; +} + +// This expects to be used in a loop and see all code points in *code. Start +// *old at 0; this function updates *old for you - don't change it after +// initialization. This returns 0 when *code is the 1st of a surrogate pair; +// otherwise use *code as the final code point. +int join_from_surrogates(int *old, int *code) { + if (*old) + *code = (((*old & 0x3FF) + 0x40) << 10) + (*code & 0x3FF); + *old = ((*code & 0xD800) == 0xD800 ? *code : 0); + return !(*old); +} diff --git a/utf8.h b/utf8.h new file mode 100644 index 0000000..2958666 --- /dev/null +++ b/utf8.h @@ -0,0 +1,9 @@ +#ifndef VELOMETER_UTF8_H +#define VELOMETER_UTF8_H + +int decode_code_point(const char **s); +void encode_code_point(char **s, char *end, int code); +int split_into_surrogates(int code, int *surr1, int *surr2); +int join_from_surrogates(int *old, int *code); + +#endif