From 7d1cd9b7dd0db1ff1c2cec71ca07ad95d5f30253 Mon Sep 17 00:00:00 2001 From: Adam Greenwood-Byrne Date: Thu, 23 Jul 2020 20:37:48 +0100 Subject: [PATCH] Added part7-bluetooth working code --- part7-bluetooth/BCM4345C0.hcd | Bin 0 -> 56508 bytes part7-bluetooth/Makefile | 22 +++ part7-bluetooth/boot.S | 30 ++++ part7-bluetooth/bt.c | 65 +++++++++ part7-bluetooth/bt.h | 3 + part7-bluetooth/fb.c | 213 ++++++++++++++++++++++++++++ part7-bluetooth/fb.h | 9 ++ part7-bluetooth/io.c | 179 ++++++++++++++++++++++++ part7-bluetooth/io.h | 13 ++ part7-bluetooth/kernel.c | 122 ++++++++++++++++ part7-bluetooth/link.ld | 19 +++ part7-bluetooth/mb.c | 39 ++++++ part7-bluetooth/mb.h | 34 +++++ part7-bluetooth/terminal.h | 253 ++++++++++++++++++++++++++++++++++ 14 files changed, 1001 insertions(+) create mode 100644 part7-bluetooth/BCM4345C0.hcd create mode 100644 part7-bluetooth/Makefile create mode 100644 part7-bluetooth/boot.S create mode 100644 part7-bluetooth/bt.c create mode 100644 part7-bluetooth/bt.h create mode 100644 part7-bluetooth/fb.c create mode 100644 part7-bluetooth/fb.h create mode 100644 part7-bluetooth/io.c create mode 100644 part7-bluetooth/io.h create mode 100644 part7-bluetooth/kernel.c create mode 100644 part7-bluetooth/link.ld create mode 100644 part7-bluetooth/mb.c create mode 100644 part7-bluetooth/mb.h create mode 100644 part7-bluetooth/terminal.h diff --git a/part7-bluetooth/BCM4345C0.hcd b/part7-bluetooth/BCM4345C0.hcd new file mode 100644 index 0000000000000000000000000000000000000000..f4b2438a4d3cbd08d77bfea5d95247cd15ea2763 GIT binary patch literal 56508 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 Zn;1gI7bu?fa^rDK&!6Jo^3*hk-+u<|i_`!B literal 0 HcmV?d00001 diff --git a/part7-bluetooth/Makefile b/part7-bluetooth/Makefile new file mode 100644 index 0000000..3ecc081 --- /dev/null +++ b/part7-bluetooth/Makefile @@ -0,0 +1,22 @@ +CFILES = $(wildcard *.c) +OFILES = $(CFILES:.c=.o) +GCCFLAGS = -Wall -O2 -ffreestanding -nostdinc -nostdlib -nostartfiles +GCCPATH = ../../gcc-arm-9.2-2019.12-x86_64-aarch64-none-elf/bin + +all: clean kernel8.img + +boot.o: boot.S + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c boot.S -o boot.o + +BCM4345C0.o : BCM4345C0.hcd + $(GCCPATH)/aarch64-none-elf-objcopy -I binary -O elf64-littleaarch64 -B aarch64 $< $@ + +%.o: %.c + $(GCCPATH)/aarch64-none-elf-gcc $(GCCFLAGS) -c $< -o $@ + +kernel8.img: boot.o $(OFILES) BCM4345C0.o + $(GCCPATH)/aarch64-none-elf-ld -nostdlib -nostartfiles boot.o $(OFILES) BCM4345C0.o -T link.ld -o kernel8.elf + $(GCCPATH)/aarch64-none-elf-objcopy -O binary kernel8.elf kernel8.img + +clean: + /bin/rm kernel8.elf *.o *.img > /dev/null 2> /dev/null || true diff --git a/part7-bluetooth/boot.S b/part7-bluetooth/boot.S new file mode 100644 index 0000000..e09b7d4 --- /dev/null +++ b/part7-bluetooth/boot.S @@ -0,0 +1,30 @@ +.section ".text.boot" // Make sure the linker puts this at the start of the kernel image + +.global _start // Execution starts here + +_start: + // Check processor ID is zero (executing on main core), else hang + mrs x1, mpidr_el1 + and x1, x1, #3 + cbz x1, 2f + // We're not on the main core, so hang in an infinite wait loop +1: wfe + b 1b +2: // We're on the main core! + + // Set stack to start below our code + ldr x1, =_start + mov sp, x1 + + // Clean the BSS section + ldr x1, =__bss_start // Start address + ldr w2, =__bss_size // Size of the section +3: cbz w2, 4f // Quit loop if zero + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, 3b // Loop if non-zero + + // Jump to our main() routine in C (make sure it doesn't return) +4: bl main + // In case it does return, halt the master core too + b 1b diff --git a/part7-bluetooth/bt.c b/part7-bluetooth/bt.c new file mode 100644 index 0000000..eef6108 --- /dev/null +++ b/part7-bluetooth/bt.c @@ -0,0 +1,65 @@ +#include "io.h" +#include "fb.h" + +// UART0 + +enum { + ARM_UART0_BASE = PERIPHERAL_BASE + 0x201000, + ARM_UART0_DR = ARM_UART0_BASE + 0x00, + ARM_UART0_FR = ARM_UART0_BASE + 0x18, + ARM_UART0_IBRD = ARM_UART0_BASE + 0x24, + ARM_UART0_FBRD = ARM_UART0_BASE + 0x28, + ARM_UART0_LCRH = ARM_UART0_BASE + 0x2C, + ARM_UART0_CR = ARM_UART0_BASE + 0x30, + ARM_UART0_IFLS = ARM_UART0_BASE + 0x34, + ARM_UART0_IMSC = ARM_UART0_BASE + 0x38, + ARM_UART0_RIS = ARM_UART0_BASE + 0x3C, + ARM_UART0_MIS = ARM_UART0_BASE + 0x40, + ARM_UART0_ICR = ARM_UART0_BASE + 0x44 +}; + +unsigned int bt_isReadByteReady() { return (!(mmio_read(ARM_UART0_FR) & 0x10)); } + +unsigned char bt_readByte() +{ + unsigned char ch = mmio_read(ARM_UART0_DR) & 0xff; + return ch; +} + +unsigned char bt_waitReadByte() +{ + while (!bt_isReadByteReady()); + return bt_readByte(); +} + +void bt_writeByte(char byte) +{ + while ((mmio_read(ARM_UART0_FR) & 0x20) != 0); + mmio_write(ARM_UART0_DR, (unsigned int)byte); +} + +void bt_flushrx() +{ + while (bt_isReadByteReady()) bt_readByte(); +} + +void bt_init() +{ + gpio_useAsAlt3(30); + gpio_useAsAlt3(31); + gpio_useAsAlt3(32); + gpio_useAsAlt3(33); + + bt_flushrx(); + + mmio_write(ARM_UART0_IMSC, 0x00); + mmio_write(ARM_UART0_ICR, 0x7ff); + mmio_write(ARM_UART0_IBRD, 0x1a); + mmio_write(ARM_UART0_FBRD, 0x03); + mmio_write(ARM_UART0_IFLS, 0x08); + mmio_write(ARM_UART0_LCRH, 0x70); + mmio_write(ARM_UART0_CR, 0xB01); + mmio_write(ARM_UART0_IMSC, 0x430); + + wait_msec(0x100000); +} diff --git a/part7-bluetooth/bt.h b/part7-bluetooth/bt.h new file mode 100644 index 0000000..b63c07c --- /dev/null +++ b/part7-bluetooth/bt.h @@ -0,0 +1,3 @@ +unsigned char bt_waitReadByte(); +void bt_writeByte(unsigned char byte); +void bt_init(); diff --git a/part7-bluetooth/fb.c b/part7-bluetooth/fb.c new file mode 100644 index 0000000..ff52a43 --- /dev/null +++ b/part7-bluetooth/fb.c @@ -0,0 +1,213 @@ +#include "io.h" +#include "mb.h" +#include "terminal.h" + +unsigned int width, height, pitch, isrgb; +unsigned char *fb; + +void fb_init() +{ + mbox[0] = 35*4; // Length of message in bytes + mbox[1] = MBOX_REQUEST; + + mbox[2] = MBOX_TAG_SETPHYWH; // Tag identifier + mbox[3] = 8; // Value size in bytes + mbox[4] = 8; // Value size in bytes (again!) + mbox[5] = 1920; // Value(width) + mbox[6] = 1080; // Value(height) + + mbox[7] = MBOX_TAG_SETVIRTWH; + mbox[8] = 8; + mbox[9] = 8; + mbox[10] = 1920; + mbox[11] = 1080; + + mbox[12] = MBOX_TAG_SETVIRTOFF; + mbox[13] = 8; + mbox[14] = 8; + mbox[15] = 0; // Value(x) + mbox[16] = 0; // Value(y) + + mbox[17] = MBOX_TAG_SETDEPTH; + mbox[18] = 4; + mbox[19] = 4; + mbox[20] = 32; // Bits per pixel + + mbox[21] = MBOX_TAG_SETPXLORDR; + mbox[22] = 4; + mbox[23] = 4; + mbox[24] = 1; // RGB + + mbox[25] = MBOX_TAG_GETFB; + mbox[26] = 8; + mbox[27] = 8; + mbox[28] = 4096; // FrameBufferInfo.pointer + mbox[29] = 0; // FrameBufferInfo.size + + mbox[30] = MBOX_TAG_GETPITCH; + mbox[31] = 4; + mbox[32] = 4; + mbox[33] = 0; // Bytes per line + + mbox[34] = MBOX_TAG_LAST; + + // Check call is successful and we have a pointer with depth 32 + if (mbox_call(MBOX_CH_PROP) && mbox[20] == 32 && mbox[28] != 0) { + mbox[28] &= 0x3FFFFFFF; // Convert GPU address to ARM address + width = mbox[10]; // Actual physical width + height = mbox[11]; // Actual physical height + pitch = mbox[33]; // Number of bytes per line + isrgb = mbox[24]; // Pixel order + fb = (unsigned char *)((long)mbox[28]); + } +} + +void drawPixel(int x, int y, unsigned char attr) +{ + int offs = (y * pitch) + (x * 4); + *((unsigned int*)(fb + offs)) = vgapal[attr & 0x0f]; +} + +void drawRect(int x1, int y1, int x2, int y2, unsigned char attr, int fill) +{ + int y=y1; + + while (y <= y2) { + int x=x1; + while (x <= x2) { + if ((x == x1 || x == x2) || (y == y1 || y == y2)) drawPixel(x, y, attr); + else if (fill) drawPixel(x, y, (attr & 0xf0) >> 4); + x++; + } + y++; + } +} + +void drawLine(int x1, int y1, int x2, int y2, unsigned char attr) +{ + int dx, dy, p, x, y; + + dx = x2-x1; + dy = y2-y1; + x = x1; + y = y1; + p = 2*dy-dx; + + while (x= 0) { + drawPixel(x,y,attr); + y++; + p = p+2*dy-2*dx; + } else { + drawPixel(x,y,attr); + p = p+2*dy; + } + x++; + } +} + +void drawCircle(int x0, int y0, int radius, unsigned char attr, int fill) +{ + int x = radius; + int y = 0; + int err = 0; + + while (x >= y) { + if (fill) { + drawLine(x0 - y, y0 + x, x0 + y, y0 + x, (attr & 0xf0) >> 4); + drawLine(x0 - x, y0 + y, x0 + x, y0 + y, (attr & 0xf0) >> 4); + drawLine(x0 - x, y0 - y, x0 + x, y0 - y, (attr & 0xf0) >> 4); + drawLine(x0 - y, y0 - x, x0 + y, y0 - x, (attr & 0xf0) >> 4); + } + drawPixel(x0 - y, y0 + x, attr); + drawPixel(x0 + y, y0 + x, attr); + drawPixel(x0 - x, y0 + y, attr); + drawPixel(x0 + x, y0 + y, attr); + drawPixel(x0 - x, y0 - y, attr); + drawPixel(x0 + x, y0 - y, attr); + drawPixel(x0 - y, y0 - x, attr); + drawPixel(x0 + y, y0 - x, attr); + + if (err <= 0) { + y += 1; + err += 2*y + 1; + } + + if (err > 0) { + x -= 1; + err -= 2*x + 1; + } + } +} + +void drawChar(unsigned char ch, int x, int y, unsigned char attr, int zoom) +{ + unsigned char *glyph = (unsigned char *)&font + (ch < FONT_NUMGLYPHS ? ch : 0) * FONT_BPG; + + for (int i=1;i<=(FONT_HEIGHT*zoom);i++) { + for (int j=0;j<(FONT_WIDTH*zoom);j++) { + unsigned char mask = 1 << (j/zoom); + unsigned char col = (*glyph & mask) ? attr & 0x0f : (attr & 0xf0) >> 4; + + drawPixel(x+j, y+i, col); + } + glyph += (i%zoom) ? 0 : FONT_BPL; + } +} + +void drawString(int x, int y, char *s, unsigned char attr, int zoom) +{ + while (*s) { + if (*s == '\r') { + x = 0; + } else if(*s == '\n') { + x = 0; y += (FONT_HEIGHT*zoom); + } else { + drawChar(*s, x, y, attr, zoom); + x += (FONT_WIDTH*zoom); + } + s++; + } +} + +void moveRect(int oldx, int oldy, int width, int height, int shiftx, int shifty, unsigned char attr) +{ + unsigned int newx = oldx + shiftx, newy = oldy + shifty; + unsigned int xcount = 0, ycount = 0; + unsigned int bitmap[width][height]; // This is very unsafe if it's too big for the stack... + unsigned int offs; + + // Save the bitmap + while (xcount < width) { + while (ycount < height) { + offs = ((oldy + ycount) * pitch) + ((oldx + xcount) * 4); + + bitmap[xcount][ycount] = *((unsigned int*)(fb + offs)); + ycount++; + } + ycount=0; + xcount++; + } + // Wipe it out with background colour + drawRect(oldx, oldy, oldx + width, oldy + width, attr, 1); + // Draw it again + for (int i=newx;i field_max) return 0; + if (value > field_mask) return 0; + + unsigned int num_fields = 32 / field_size; + unsigned int reg = base + ((pin_number / num_fields) * 4); + unsigned int shift = (pin_number % num_fields) * field_size; + + unsigned int curval = mmio_read(reg); + curval &= ~(field_mask << shift); + curval |= value << shift; + mmio_write(reg, curval); + + return 1; +} + +unsigned int gpio_set (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPSET0, 1, GPIO_MAX_PIN); } +unsigned int gpio_clear (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPCLR0, 1, GPIO_MAX_PIN); } +unsigned int gpio_pull (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPPUPPDN0, 2, GPIO_MAX_PIN); } +unsigned int gpio_function(unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPFSEL0, 3, GPIO_MAX_PIN); } + +void gpio_useAsAlt3(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_ALT3); +} + +void gpio_useAsAlt5(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_ALT5); +} + +void gpio_initOutputPinWithPullNone(unsigned int pin_number) { + gpio_pull(pin_number, Pull_None); + gpio_function(pin_number, GPIO_FUNCTION_OUT); +} + +void gpio_setPinOutputBool(unsigned int pin_number, unsigned int onOrOff) { + if (onOrOff) { + gpio_set(pin_number, 1); + } else { + gpio_clear(pin_number, 1); + } +} + +// UART + +enum { + AUX_BASE = PERIPHERAL_BASE + 0x215000, + AUX_IRQ = AUX_BASE, + AUX_ENABLES = AUX_BASE + 4, + AUX_MU_IO_REG = AUX_BASE + 64, + AUX_MU_IER_REG = AUX_BASE + 68, + AUX_MU_IIR_REG = AUX_BASE + 72, + AUX_MU_LCR_REG = AUX_BASE + 76, + AUX_MU_MCR_REG = AUX_BASE + 80, + AUX_MU_LSR_REG = AUX_BASE + 84, + AUX_MU_MSR_REG = AUX_BASE + 88, + AUX_MU_SCRATCH = AUX_BASE + 92, + AUX_MU_CNTL_REG = AUX_BASE + 96, + AUX_MU_STAT_REG = AUX_BASE + 100, + AUX_MU_BAUD_REG = AUX_BASE + 104, + AUX_UART_CLOCK = 500000000, + UART_MAX_QUEUE = 16 * 1024 +}; + +#define AUX_MU_BAUD(baud) ((AUX_UART_CLOCK/(baud*8))-1) + +unsigned char uart_output_queue[UART_MAX_QUEUE]; +unsigned int uart_output_queue_write = 0; +unsigned int uart_output_queue_read = 0; + +void uart_init() { + mmio_write(AUX_ENABLES, 1); //enable UART1 + mmio_write(AUX_MU_IER_REG, 0); + mmio_write(AUX_MU_CNTL_REG, 0); + mmio_write(AUX_MU_LCR_REG, 3); //8 bits + mmio_write(AUX_MU_MCR_REG, 0); + mmio_write(AUX_MU_IER_REG, 0); + mmio_write(AUX_MU_IIR_REG, 0xC6); //disable interrupts + mmio_write(AUX_MU_BAUD_REG, AUX_MU_BAUD(115200)); + gpio_useAsAlt5(14); + gpio_useAsAlt5(15); + mmio_write(AUX_MU_CNTL_REG, 3); //enable RX/TX +} + +unsigned int uart_isOutputQueueEmpty() { + return uart_output_queue_read == uart_output_queue_write; +} + +unsigned int uart_isReadByteReady() { return mmio_read(AUX_MU_LSR_REG) & 0x01; } +unsigned int uart_isWriteByteReady() { return mmio_read(AUX_MU_LSR_REG) & 0x20; } + +unsigned char uart_readByte() { + while (!uart_isReadByteReady()); + return (unsigned char)mmio_read(AUX_MU_IO_REG); +} + +void uart_writeByteBlockingActual(unsigned char ch) { + while (!uart_isWriteByteReady()); + mmio_write(AUX_MU_IO_REG, (unsigned int)ch); +} + +void uart_loadOutputFifo() { + while (!uart_isOutputQueueEmpty() && uart_isWriteByteReady()) { + uart_writeByteBlockingActual(uart_output_queue[uart_output_queue_read]); + uart_output_queue_read = (uart_output_queue_read + 1) & (UART_MAX_QUEUE - 1); // Don't overrun + } +} + +void uart_writeByteBlocking(unsigned char ch) { + unsigned int next = (uart_output_queue_write + 1) & (UART_MAX_QUEUE - 1); // Don't overrun + + while (next == uart_output_queue_read) uart_loadOutputFifo(); + + uart_output_queue[uart_output_queue_write] = ch; + uart_output_queue_write = next; +} + +void uart_writeText(char *buffer) { + while (*buffer) { + if (*buffer == '\n') uart_writeByteBlocking('\r'); + uart_writeByteBlocking(*buffer++); + } +} + +void uart_drainOutputQueue() { + while (!uart_isOutputQueueEmpty()) uart_loadOutputFifo(); +} + +void uart_update() { + uart_loadOutputFifo(); + + if (uart_isReadByteReady()) { + unsigned char ch = uart_readByte(); + if (ch == '\r') uart_writeText("\n"); else uart_writeByteBlocking(ch); + } +} + +void uart_hex(unsigned int d) { + unsigned int n; + int c; + for(c=28;c>=0;c-=4) { + // get highest tetrad + n=(d>>c)&0xF; + // 0-9 => '0'-'9', 10-15 => 'A'-'F' + n+=n>9?0x37:0x30; + + uart_writeByteBlocking(n); + } +} diff --git a/part7-bluetooth/io.h b/part7-bluetooth/io.h new file mode 100644 index 0000000..0c61506 --- /dev/null +++ b/part7-bluetooth/io.h @@ -0,0 +1,13 @@ +#define PERIPHERAL_BASE 0xFE000000 + +void uart_init(); +void uart_writeText(char *buffer); +void uart_loadOutputFifo(); +unsigned char uart_readByte(); +unsigned int uart_isReadByteReady(); +void uart_writeByteBlocking(unsigned char ch); +void uart_update(); +void mmio_write(long reg, unsigned int val); +unsigned int mmio_read(long reg); +void gpio_useAsAlt3(unsigned int pin_number); +void uart_hex(unsigned int d); diff --git a/part7-bluetooth/kernel.c b/part7-bluetooth/kernel.c new file mode 100644 index 0000000..324d4af --- /dev/null +++ b/part7-bluetooth/kernel.c @@ -0,0 +1,122 @@ +#include "io.h" +#include "fb.h" +#include "bt.h" + +enum { + OGF_HOST_CONTROL = 0x03, + OGF_LE_CONTROL = 0x08, + OGF_VENDOR = 0x3f, + COMMAND_RESET_CHIP = 0x03, + VENDOR_LOAD_FIRMWARE = 0x2e, + HCI_COMMAND_PKT = 0x01, + HCI_EVENT_PKT = 0x04, + LL_SCAN_ACTIVE = 0x01, + EVENT_TYPE_COMMAND_STATUS = 0x0e +}; + +unsigned char lo(unsigned int val) { return (unsigned char)(val & 0xff); } +unsigned char hi(unsigned int val) { return (unsigned char)((val & 0xff00) >> 8); } + +unsigned char empty[] = {}; + +int hciCommandBytes(unsigned char *opcodebytes, unsigned char *data, unsigned char length) +{ + unsigned char c=0; + + bt_writeByte(HCI_COMMAND_PKT); + bt_writeByte(opcodebytes[0]); + bt_writeByte(opcodebytes[1]); + bt_writeByte(length); + + while (c++>3; diff --git a/part7-bluetooth/mb.c b/part7-bluetooth/mb.c new file mode 100644 index 0000000..4354247 --- /dev/null +++ b/part7-bluetooth/mb.c @@ -0,0 +1,39 @@ +#include "io.h" + +// The buffer must be 16-byte aligned as only the upper 28 bits of the address can be passed via the mailbox +volatile unsigned int __attribute__((aligned(16))) mbox[36]; + +enum { + VIDEOCORE_MBOX = (PERIPHERAL_BASE + 0x0000B880), + MBOX_READ = (VIDEOCORE_MBOX + 0x0), + MBOX_POLL = (VIDEOCORE_MBOX + 0x10), + MBOX_SENDER = (VIDEOCORE_MBOX + 0x14), + MBOX_STATUS = (VIDEOCORE_MBOX + 0x18), + MBOX_CONFIG = (VIDEOCORE_MBOX + 0x1C), + MBOX_WRITE = (VIDEOCORE_MBOX + 0x20), + MBOX_RESPONSE = 0x80000000, + MBOX_FULL = 0x80000000, + MBOX_EMPTY = 0x40000000 +}; + +unsigned int mbox_call(unsigned char ch) +{ + // 28-bit address (MSB) and 4-bit value (LSB) + unsigned int r = ((unsigned int)((long) &mbox) &~ 0xF) | (ch & 0xF); + + // Wait until we can write + while (mmio_read(MBOX_STATUS) & MBOX_FULL); + + // Write the address of our buffer to the mailbox with the channel appended + mmio_write(MBOX_WRITE, r); + + while (1) { + // Is there a reply? + while (mmio_read(MBOX_STATUS) & MBOX_EMPTY); + + // Is it a reply to our message? + if (r == mmio_read(MBOX_READ)) return mbox[1]==MBOX_RESPONSE; // Is it successful? + + } + return 0; +} diff --git a/part7-bluetooth/mb.h b/part7-bluetooth/mb.h new file mode 100644 index 0000000..0e1861b --- /dev/null +++ b/part7-bluetooth/mb.h @@ -0,0 +1,34 @@ +extern volatile unsigned int mbox[36]; + +enum { + MBOX_REQUEST = 0 +}; + +enum { + MBOX_CH_POWER = 0, + MBOX_CH_FB = 1, + MBOX_CH_VUART = 2, + MBOX_CH_VCHIQ = 3, + MBOX_CH_LEDS = 4, + MBOX_CH_BTNS = 5, + MBOX_CH_TOUCH = 6, + MBOX_CH_COUNT = 7, + MBOX_CH_PROP = 8 // Request from ARM for response by VideoCore +}; + +enum { + MBOX_TAG_SETPOWER = 0x28001, + MBOX_TAG_SETCLKRATE = 0x38002, + + MBOX_TAG_SETPHYWH = 0x48003, + MBOX_TAG_SETVIRTWH = 0x48004, + MBOX_TAG_SETVIRTOFF = 0x48009, + MBOX_TAG_SETDEPTH = 0x48005, + MBOX_TAG_SETPXLORDR = 0x48006, + MBOX_TAG_GETFB = 0x40001, + MBOX_TAG_GETPITCH = 0x40008, + + MBOX_TAG_LAST = 0 +}; + +unsigned int mbox_call(unsigned char ch); diff --git a/part7-bluetooth/terminal.h b/part7-bluetooth/terminal.h new file mode 100644 index 0000000..ef05b85 --- /dev/null +++ b/part7-bluetooth/terminal.h @@ -0,0 +1,253 @@ +unsigned int vgapal[] = { + 0x000000, + 0x0000AA, + 0x00AA00, + 0x00AAAA, + 0xAA0000, + 0xAA00AA, + 0xAA5500, + 0xAAAAAA, + 0x555555, + 0x5555FF, + 0x55FF55, + 0x55FFFF, + 0xFF5555, + 0xFF55FF, + 0xFFFF55, + 0xFFFFFF +}; + +enum { + FONT_WIDTH = 8, + FONT_HEIGHT = 8, + FONT_BPG = 8, // Bytes per glyph + FONT_BPL = 1, // Bytes per line + FONT_NUMGLYPHS = 224 +}; + +unsigned char font[FONT_NUMGLYPHS][FONT_BPG] = { + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0000 (nul) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0001 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0002 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0003 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0004 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0005 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0006 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0007 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0008 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0009 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000A + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000B + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000C + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000D + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000E + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+000F + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0010 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0011 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0012 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0013 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0014 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0015 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0016 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0017 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0018 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0019 + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001A + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001B + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001C + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001D + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001E + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+001F + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0020 (space) + { 0x18, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x00}, // U+0021 (!) + { 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0022 (") + { 0x36, 0x36, 0x7F, 0x36, 0x7F, 0x36, 0x36, 0x00}, // U+0023 (#) + { 0x0C, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x0C, 0x00}, // U+0024 ($) + { 0x00, 0x63, 0x33, 0x18, 0x0C, 0x66, 0x63, 0x00}, // U+0025 (%) + { 0x1C, 0x36, 0x1C, 0x6E, 0x3B, 0x33, 0x6E, 0x00}, // U+0026 (&) + { 0x06, 0x06, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0027 (') + { 0x18, 0x0C, 0x06, 0x06, 0x06, 0x0C, 0x18, 0x00}, // U+0028 (() + { 0x06, 0x0C, 0x18, 0x18, 0x18, 0x0C, 0x06, 0x00}, // U+0029 ()) + { 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00}, // U+002A (*) + { 0x00, 0x0C, 0x0C, 0x3F, 0x0C, 0x0C, 0x00, 0x00}, // U+002B (+) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+002C (,) + { 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x00}, // U+002D (-) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+002E (.) + { 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x00}, // U+002F (/) + { 0x3E, 0x63, 0x73, 0x7B, 0x6F, 0x67, 0x3E, 0x00}, // U+0030 (0) + { 0x0C, 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x3F, 0x00}, // U+0031 (1) + { 0x1E, 0x33, 0x30, 0x1C, 0x06, 0x33, 0x3F, 0x00}, // U+0032 (2) + { 0x1E, 0x33, 0x30, 0x1C, 0x30, 0x33, 0x1E, 0x00}, // U+0033 (3) + { 0x38, 0x3C, 0x36, 0x33, 0x7F, 0x30, 0x78, 0x00}, // U+0034 (4) + { 0x3F, 0x03, 0x1F, 0x30, 0x30, 0x33, 0x1E, 0x00}, // U+0035 (5) + { 0x1C, 0x06, 0x03, 0x1F, 0x33, 0x33, 0x1E, 0x00}, // U+0036 (6) + { 0x3F, 0x33, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x00}, // U+0037 (7) + { 0x1E, 0x33, 0x33, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+0038 (8) + { 0x1E, 0x33, 0x33, 0x3E, 0x30, 0x18, 0x0E, 0x00}, // U+0039 (9) + { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x00}, // U+003A (:) + { 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x0C, 0x0C, 0x06}, // U+003B (;) + { 0x18, 0x0C, 0x06, 0x03, 0x06, 0x0C, 0x18, 0x00}, // U+003C (<) + { 0x00, 0x00, 0x3F, 0x00, 0x00, 0x3F, 0x00, 0x00}, // U+003D (=) + { 0x06, 0x0C, 0x18, 0x30, 0x18, 0x0C, 0x06, 0x00}, // U+003E (>) + { 0x1E, 0x33, 0x30, 0x18, 0x0C, 0x00, 0x0C, 0x00}, // U+003F (?) + { 0x3E, 0x63, 0x7B, 0x7B, 0x7B, 0x03, 0x1E, 0x00}, // U+0040 (@) + { 0x0C, 0x1E, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x00}, // U+0041 (A) + { 0x3F, 0x66, 0x66, 0x3E, 0x66, 0x66, 0x3F, 0x00}, // U+0042 (B) + { 0x3C, 0x66, 0x03, 0x03, 0x03, 0x66, 0x3C, 0x00}, // U+0043 (C) + { 0x1F, 0x36, 0x66, 0x66, 0x66, 0x36, 0x1F, 0x00}, // U+0044 (D) + { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x46, 0x7F, 0x00}, // U+0045 (E) + { 0x7F, 0x46, 0x16, 0x1E, 0x16, 0x06, 0x0F, 0x00}, // U+0046 (F) + { 0x3C, 0x66, 0x03, 0x03, 0x73, 0x66, 0x7C, 0x00}, // U+0047 (G) + { 0x33, 0x33, 0x33, 0x3F, 0x33, 0x33, 0x33, 0x00}, // U+0048 (H) + { 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0049 (I) + { 0x78, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E, 0x00}, // U+004A (J) + { 0x67, 0x66, 0x36, 0x1E, 0x36, 0x66, 0x67, 0x00}, // U+004B (K) + { 0x0F, 0x06, 0x06, 0x06, 0x46, 0x66, 0x7F, 0x00}, // U+004C (L) + { 0x63, 0x77, 0x7F, 0x7F, 0x6B, 0x63, 0x63, 0x00}, // U+004D (M) + { 0x63, 0x67, 0x6F, 0x7B, 0x73, 0x63, 0x63, 0x00}, // U+004E (N) + { 0x1C, 0x36, 0x63, 0x63, 0x63, 0x36, 0x1C, 0x00}, // U+004F (O) + { 0x3F, 0x66, 0x66, 0x3E, 0x06, 0x06, 0x0F, 0x00}, // U+0050 (P) + { 0x1E, 0x33, 0x33, 0x33, 0x3B, 0x1E, 0x38, 0x00}, // U+0051 (Q) + { 0x3F, 0x66, 0x66, 0x3E, 0x36, 0x66, 0x67, 0x00}, // U+0052 (R) + { 0x1E, 0x33, 0x07, 0x0E, 0x38, 0x33, 0x1E, 0x00}, // U+0053 (S) + { 0x3F, 0x2D, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0054 (T) + { 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0x00}, // U+0055 (U) + { 0x33, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0056 (V) + { 0x63, 0x63, 0x63, 0x6B, 0x7F, 0x77, 0x63, 0x00}, // U+0057 (W) + { 0x63, 0x63, 0x36, 0x1C, 0x1C, 0x36, 0x63, 0x00}, // U+0058 (X) + { 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x0C, 0x1E, 0x00}, // U+0059 (Y) + { 0x7F, 0x63, 0x31, 0x18, 0x4C, 0x66, 0x7F, 0x00}, // U+005A (Z) + { 0x1E, 0x06, 0x06, 0x06, 0x06, 0x06, 0x1E, 0x00}, // U+005B ([) + { 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x40, 0x00}, // U+005C (\) + { 0x1E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1E, 0x00}, // U+005D (]) + { 0x08, 0x1C, 0x36, 0x63, 0x00, 0x00, 0x00, 0x00}, // U+005E (^) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF}, // U+005F (_) + { 0x0C, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+0060 (`) + { 0x00, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x6E, 0x00}, // U+0061 (a) + { 0x07, 0x06, 0x06, 0x3E, 0x66, 0x66, 0x3B, 0x00}, // U+0062 (b) + { 0x00, 0x00, 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x00}, // U+0063 (c) + { 0x38, 0x30, 0x30, 0x3e, 0x33, 0x33, 0x6E, 0x00}, // U+0064 (d) + { 0x00, 0x00, 0x1E, 0x33, 0x3f, 0x03, 0x1E, 0x00}, // U+0065 (e) + { 0x1C, 0x36, 0x06, 0x0f, 0x06, 0x06, 0x0F, 0x00}, // U+0066 (f) + { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0067 (g) + { 0x07, 0x06, 0x36, 0x6E, 0x66, 0x66, 0x67, 0x00}, // U+0068 (h) + { 0x0C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+0069 (i) + { 0x30, 0x00, 0x30, 0x30, 0x30, 0x33, 0x33, 0x1E}, // U+006A (j) + { 0x07, 0x06, 0x66, 0x36, 0x1E, 0x36, 0x67, 0x00}, // U+006B (k) + { 0x0E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+006C (l) + { 0x00, 0x00, 0x33, 0x7F, 0x7F, 0x6B, 0x63, 0x00}, // U+006D (m) + { 0x00, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x33, 0x00}, // U+006E (n) + { 0x00, 0x00, 0x1E, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+006F (o) + { 0x00, 0x00, 0x3B, 0x66, 0x66, 0x3E, 0x06, 0x0F}, // U+0070 (p) + { 0x00, 0x00, 0x6E, 0x33, 0x33, 0x3E, 0x30, 0x78}, // U+0071 (q) + { 0x00, 0x00, 0x3B, 0x6E, 0x66, 0x06, 0x0F, 0x00}, // U+0072 (r) + { 0x00, 0x00, 0x3E, 0x03, 0x1E, 0x30, 0x1F, 0x00}, // U+0073 (s) + { 0x08, 0x0C, 0x3E, 0x0C, 0x0C, 0x2C, 0x18, 0x00}, // U+0074 (t) + { 0x00, 0x00, 0x33, 0x33, 0x33, 0x33, 0x6E, 0x00}, // U+0075 (u) + { 0x00, 0x00, 0x33, 0x33, 0x33, 0x1E, 0x0C, 0x00}, // U+0076 (v) + { 0x00, 0x00, 0x63, 0x6B, 0x7F, 0x7F, 0x36, 0x00}, // U+0077 (w) + { 0x00, 0x00, 0x63, 0x36, 0x1C, 0x36, 0x63, 0x00}, // U+0078 (x) + { 0x00, 0x00, 0x33, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+0079 (y) + { 0x00, 0x00, 0x3F, 0x19, 0x0C, 0x26, 0x3F, 0x00}, // U+007A (z) + { 0x38, 0x0C, 0x0C, 0x07, 0x0C, 0x0C, 0x38, 0x00}, // U+007B ({) + { 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00}, // U+007C (|) + { 0x07, 0x0C, 0x0C, 0x38, 0x0C, 0x0C, 0x07, 0x00}, // U+007D (}) + { 0x6E, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007E (~) + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // U+007F + { 0x1E, 0x33, 0x03, 0x33, 0x1E, 0x18, 0x30, 0x1E}, // U+00C7 (C cedille) + { 0x00, 0x33, 0x00, 0x33, 0x33, 0x33, 0x7E, 0x00}, // U+00FC (u umlaut) + { 0x38, 0x00, 0x1E, 0x33, 0x3F, 0x03, 0x1E, 0x00}, // U+00E9 (e aigu) + { 0x7E, 0xC3, 0x3C, 0x60, 0x7C, 0x66, 0xFC, 0x00}, // U+00E2 (a circumflex) + { 0x33, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x7E, 0x00}, // U+00E4 (a umlaut) + { 0x07, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x7E, 0x00}, // U+00E0 (a grave) + { 0x0C, 0x0C, 0x1E, 0x30, 0x3E, 0x33, 0x7E, 0x00}, // U+00E5 (a ring) + { 0x00, 0x00, 0x1E, 0x03, 0x03, 0x1E, 0x30, 0x1C}, // U+00E7 (c cedille) + { 0x7E, 0xC3, 0x3C, 0x66, 0x7E, 0x06, 0x3C, 0x00}, // U+00EA (e circumflex) + { 0x33, 0x00, 0x1E, 0x33, 0x3F, 0x03, 0x1E, 0x00}, // U+00EB (e umlaut) + { 0x07, 0x00, 0x1E, 0x33, 0x3F, 0x03, 0x1E, 0x00}, // U+00E8 (e grave) + { 0x33, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+00EF (i umlaut) + { 0x3E, 0x63, 0x1C, 0x18, 0x18, 0x18, 0x3C, 0x00}, // U+00EE (i circumflex) + { 0x07, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+00EC (i grave) + { 0x63, 0x1C, 0x36, 0x63, 0x7F, 0x63, 0x63, 0x00}, // U+00C4 (A umlaut) + { 0x0C, 0x0C, 0x00, 0x1E, 0x33, 0x3F, 0x33, 0x00}, // U+00C5 (A ring) + { 0x07, 0x00, 0x3F, 0x06, 0x1E, 0x06, 0x3F, 0x00}, // U+00C8 (E grave) + { 0x00, 0x00, 0xFE, 0x30, 0xFE, 0x33, 0xFE, 0x00}, // U+00E6 (ae) + { 0x7C, 0x36, 0x33, 0x7F, 0x33, 0x33, 0x73, 0x00}, // U+00C6 (AE) + { 0x1E, 0x33, 0x00, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+00F4 (o circumflex) + { 0x00, 0x33, 0x00, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+00F6 (o umlaut) + { 0x00, 0x07, 0x00, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+00F2 (o grave) + { 0x1E, 0x33, 0x00, 0x33, 0x33, 0x33, 0x7E, 0x00}, // U+00FB (u circumflex) + { 0x00, 0x07, 0x00, 0x33, 0x33, 0x33, 0x7E, 0x00}, // U+00F9 (u grave) + { 0x00, 0x33, 0x00, 0x33, 0x33, 0x3E, 0x30, 0x1F}, // U+00FF (y umlaut) + { 0xC3, 0x18, 0x3C, 0x66, 0x66, 0x3C, 0x18, 0x00}, // U+00D6 (O umlaut) + { 0x33, 0x00, 0x33, 0x33, 0x33, 0x33, 0x1E, 0x00}, // U+00DC (U umlaut) + { 0x18, 0x18, 0x7E, 0x03, 0x03, 0x7E, 0x18, 0x18}, // U+00A2 (dollarcents) + { 0x1C, 0x36, 0x26, 0x0F, 0x06, 0x67, 0x3F, 0x00}, // U+00A3 (pound sterling) + { 0x33, 0x33, 0x1E, 0x3F, 0x0C, 0x3F, 0x0C, 0x0C}, // U+00A5 (yen) + { 0x7C, 0xC6, 0x1C, 0x36, 0x36, 0x1C, 0x33, 0x1E}, // U+00A7 (paragraph) + { 0x70, 0xD8, 0x18, 0x3C, 0x18, 0x18, 0x1B, 0x0E}, // U+0192 (dutch florijn) + { 0x38, 0x00, 0x1E, 0x30, 0x3E, 0x33, 0x7E, 0x00}, // U+00E1 (a aigu) + { 0x1C, 0x00, 0x0E, 0x0C, 0x0C, 0x0C, 0x1E, 0x00}, // U+00ED (i augu) + { 0x00, 0x38, 0x00, 0x1E, 0x33, 0x33, 0x1E, 0x00}, // U+00F3 (o aigu) + { 0x00, 0x38, 0x00, 0x33, 0x33, 0x33, 0x7E, 0x00}, // U+00FA (u aigu) + { 0x00, 0x1F, 0x00, 0x1F, 0x33, 0x33, 0x33, 0x00}, // U+00F1 (n ~) + { 0x3F, 0x00, 0x33, 0x37, 0x3F, 0x3B, 0x33, 0x00}, // U+00D1 (N ~) + { 0x3C, 0x36, 0x36, 0x7C, 0x00, 0x00, 0x00, 0x00}, // U+00AA (superscript a) + { 0x1C, 0x36, 0x36, 0x1C, 0x00, 0x00, 0x00, 0x00}, // U+00BA (superscript 0) + { 0x0C, 0x00, 0x0C, 0x06, 0x03, 0x33, 0x1E, 0x00}, // U+00BF (inverted ?) + { 0x00, 0x00, 0x00, 0x3F, 0x03, 0x03, 0x00, 0x00}, // U+2310 (gun pointing right) + { 0x00, 0x00, 0x00, 0x3F, 0x30, 0x30, 0x00, 0x00}, // U+00AC (gun pointing left) + { 0xC3, 0x63, 0x33, 0x7B, 0xCC, 0x66, 0x33, 0xF0}, // U+00BD (1/2) + { 0xC3, 0x63, 0x33, 0xBD, 0xEC, 0xF6, 0xF3, 0x03}, // U+00BC (1/4) + { 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00}, // U+00A1 (inverted !) + { 0x00, 0xCC, 0x66, 0x33, 0x66, 0xCC, 0x00, 0x00}, // U+00AB (<<) + { 0x00, 0x33, 0x66, 0xCC, 0x66, 0x33, 0x00, 0x00}, // U+00BB (>>) + { 0x55, 0x00, 0xAA, 0x00, 0x55, 0x00, 0xAA, 0x00}, // U+2591 (25% solid) + { 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA}, // U+2592 (50% solid) + { 0xFF, 0xAA, 0xFF, 0x55, 0xFF, 0xAA, 0xFF, 0x55}, // U+2593 (75% solid) + { 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}, // U+2502 (thin vertical) + { 0x08, 0x08, 0x08, 0x08, 0x0f, 0x08, 0x08, 0x08}, // U+2524 (down L, left L, up L) + { 0x08, 0x08, 0x08, 0x0F, 0x08, 0x0F, 0x08, 0x08}, // U+2561 (up L, down L, left D) + { 0x14, 0x14, 0x14, 0x14, 0x17, 0x14, 0x14, 0x14}, // U+2562 (up D, down D, left L) + { 0x00, 0x00, 0x00, 0x00, 0x1F, 0x14, 0x14, 0x14}, // U+2556 (down D, left L) + { 0x00, 0x00, 0x00, 0x0F, 0x08, 0x0F, 0x08, 0x08}, // U+2555 (down L, left D) + { 0x14, 0x14, 0x14, 0x17, 0x10, 0x17, 0x14, 0x14}, // U+2563 (up D, down D, left D) + { 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14}, // U+2551 (double vertical) + { 0x00, 0x00, 0x00, 0x1F, 0x10, 0x17, 0x14, 0x14}, // U+2557 (down D, left D) + { 0x14, 0x14, 0x14, 0x17, 0x10, 0x1F, 0x00, 0x00}, // U+255D (up D, left D) + { 0x14, 0x14, 0x14, 0x14, 0x1F, 0x00, 0x00, 0x00}, // U+255C (up D, left L) + { 0x08, 0x08, 0x08, 0x0F, 0x08, 0x0F, 0x00, 0x00}, // U+255B (up L, left D) + { 0x00, 0x00, 0x00, 0x00, 0x0f, 0x08, 0x08, 0x08}, // U+2510 (down L, left L) + { 0x08, 0x08, 0x08, 0x08, 0xf8, 0x00, 0x00, 0x00}, // U+2514 (up L, right L) + { 0x08, 0x08, 0x08, 0x08, 0xff, 0x00, 0x00, 0x00}, // U+2534 (up L, right L, left L) + { 0x00, 0x00, 0x00, 0x00, 0xff, 0x08, 0x08, 0x08}, // U+252C (down L, right L, left L) + { 0x08, 0x08, 0x08, 0x08, 0xf8, 0x08, 0x08, 0x08}, // U+251C (down L, right L, up L) + { 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00}, // U+2500 (thin horizontal) + { 0x08, 0x08, 0x08, 0x08, 0xff, 0x08, 0x08, 0x08}, // U+253C (up L, right L, left L, down L) + { 0x08, 0x08, 0x08, 0xF8, 0x08, 0xF8, 0x08, 0x08}, // U+255E (up L, down L, right D) + { 0x14, 0x14, 0x14, 0x14, 0xF4, 0x14, 0x14, 0x14}, // U+255F (up D, down D, right L) + { 0x14, 0x14, 0x14, 0xF4, 0x04, 0xFC, 0x00, 0x00}, // U+255A (up D, right D) + { 0x00, 0x00, 0x00, 0xFC, 0x04, 0xF4, 0x14, 0x14}, // U+2554 (down D, right D) + { 0x14, 0x14, 0x14, 0xF7, 0x00, 0xFF, 0x00, 0x00}, // U+2569 (left D, right D, up D) + { 0x00, 0x00, 0x00, 0xFF, 0x00, 0xF7, 0x14, 0x14}, // U+2566 (left D, right D, down D) + { 0x14, 0x14, 0x14, 0xF4, 0x04, 0xF4, 0x14, 0x14}, // U+2560 (up D, down D, right D) + { 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00}, // U+2550 (double horizontal) + { 0x14, 0x14, 0x14, 0xF7, 0x00, 0xF7, 0x14, 0x14}, // U+256C (left D, right D, down D, up D) + { 0x08, 0x08, 0x08, 0xFF, 0x00, 0xFF, 0x00, 0x00}, // U+2567 (left D, right D, up L) + { 0x14, 0x14, 0x14, 0x14, 0xFF, 0x00, 0x00, 0x00}, // U+2568 (left L, right L, up D) + { 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x08, 0x08}, // U+2564 (left D, right D, down L) + { 0x00, 0x00, 0x00, 0x00, 0xFF, 0x14, 0x14, 0x14}, // U+2565 (left L, right L, down D) + { 0x14, 0x14, 0x14, 0x14, 0xFC, 0x00, 0x00, 0x00}, // U+2559 (up D, right L) + { 0x08, 0x08, 0x08, 0xF8, 0x08, 0xF8, 0x00, 0x00}, // U+2558 (up L, right D) + { 0x00, 0x00, 0x00, 0xF8, 0x08, 0xF8, 0x08, 0x08}, // U+2552 (down L, right D) + { 0x00, 0x00, 0x00, 0x00, 0xFC, 0x14, 0x14, 0x14}, // U+2553 (down D, right L) + { 0x14, 0x14, 0x14, 0x14, 0xFF, 0x14, 0x14, 0x14}, // U+256B (left L, right L, down D, up D) + { 0x08, 0x08, 0x08, 0xFF, 0x08, 0xFF, 0x08, 0x08}, // U+256A (left D, right D, down L, up L) + { 0x08, 0x08, 0x08, 0x08, 0x0f, 0x00, 0x00, 0x00}, // U+2518 (up L, left L) + { 0x00, 0x00, 0x00, 0x00, 0xf8, 0x08, 0x08, 0x08}, // U+250C (down L, right L) + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, // U+2588 (solid) + { 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}, // U+2584 (bottom half) + { 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F}, // U+258C (left half) + { 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0}, // U+2590 (right half) + { 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00}, // U+2580 (top half) +};