diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | .vscode/c_cpp_properties.json | 19 | ||||
-rw-r--r-- | .vscode/launch.json | 26 | ||||
-rw-r--r-- | .vscode/settings.json | 22 | ||||
-rw-r--r-- | CMakeLists.txt | 49 | ||||
-rw-r--r-- | oled/BitBang_I2C.c | 539 | ||||
-rw-r--r-- | oled/BitBang_I2C.h | 109 | ||||
-rw-r--r-- | oled/CMakeLists.txt | 31 | ||||
-rw-r--r-- | oled/pico_sdk_import.cmake | 64 | ||||
-rw-r--r-- | oled/ss_oled.c | 2255 | ||||
-rw-r--r-- | oled/ss_oled.h | 211 | ||||
-rw-r--r-- | pico-watch.c | 40 | ||||
-rw-r--r-- | pico_sdk_import.cmake | 62 |
13 files changed, 3429 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9eacfc0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/** +.vscode/build/**
\ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..bec5635 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,19 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**", + "~/pico/pico-sdk/**", + "/home/pi/pico/pico-sdk/src/common/pico_stdlib/include" + ], + "defines": [], + "compilerPath": "/usr/bin/clang", + "cStandard": "c11", + "cppStandard": "c++14", + "intelliSenseMode": "clang-arm", + "configurationProvider": "ms-vscode.cmake-tools" + } + ], + "version": 4 +}
\ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..a30da91 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,26 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Pico Debug", + "cwd": "${workspaceRoot}", + "executable": "${command:cmake.launchTargetPath}", + "request": "launch", + "type": "cortex-debug", + "servertype": "openocd", + "gdbPath" : "gdb-multiarch", + "device": "RP2040", + "configFiles": [ + "interface/raspberrypi-swd.cfg", + "target/rp2040.cfg" + ], + "svdFile": "${env:PICO_SDK_PATH}/src/rp2040/hardware_regs/rp2040.svd", + "runToMain": true, + // Work around for stopping at main on restart + "postRestartCommands": [ + "break main", + "continue" + ] + } + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ff89d56 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,22 @@ +{ + // These settings tweaks to the cmake plugin will ensure + // that you debug using cortex-debug instead of trying to launch + // a Pico binary on the host + "cmake.statusbar.advanced": { + "debug": { + "visibility": "hidden" + }, + "launch": { + "visibility": "hidden" + }, + "build": { + "visibility": "hidden" + }, + "buildTarget": { + "visibility": "hidden" + } + }, + "cmake.buildBeforeRun": true, + // This may need to be arm-none-eabi-gdb depending on your system + "cortex-debug.gdbPath": "gdb-multiarch" +}
\ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..52b713f --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,49 @@ +# Generated Cmake Pico project file + +cmake_minimum_required(VERSION 3.12) + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +# initalize pico_sdk from installed location +# (note this can come from environment, CMake cache etc) +set(PICO_SDK_PATH "/home/pi/pico/pico-sdk") + +# Pull in Pico SDK (must be before project) +include(pico_sdk_import.cmake) + +project(pico-watch C CXX) + +# Initialise the Pico SDK +pico_sdk_init() + +# Add executable. Default name is the project name, version 0.1 + +#add_executable(pico-watch pico-watch.c ) +add_executable(pico-watch + pico-watch.c + BitBang_I2C.c + BitBang_I2C.h + ss_oled.c + ss_oled.h + ) + + +pico_set_program_name(pico-watch "pico-watch") +pico_set_program_version(pico-watch "0.1") + +pico_enable_stdio_uart(pico-watch 0) +pico_enable_stdio_usb(pico-watch 1) + +# Add the standard library to the build +target_link_libraries(pico-watch pico_stdlib) + +# Add any user requested libraries +target_link_libraries(pico-watch + hardware_i2c + hardware_timer + hardware_clocks + ) + +pico_add_extra_outputs(pico-watch) + diff --git a/oled/BitBang_I2C.c b/oled/BitBang_I2C.c new file mode 100644 index 0000000..e5c34da --- /dev/null +++ b/oled/BitBang_I2C.c @@ -0,0 +1,539 @@ +// +// Bit Bang I2C library +// Copyright (c) 2018 BitBank Software, Inc. +// Written by Larry Bank (bitbank@pobox.com) +// Project started 10/12/2018 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +#include <stdio.h> +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "pico/binary_info.h" +#include "hardware/i2c.h" + +#include "BitBang_I2C.h" +#define I2C_PORT i2c1 + +static uint8_t SDA_READ(uint8_t iSDA) +{ + return gpio_get(iSDA); +} +static void SCL_HIGH(uint8_t iSCL) +{ + gpio_init(iSCL); + gpio_set_dir(iSCL, GPIO_IN); +} + +static void SCL_LOW(uint8_t iSCL) +{ + gpio_set_dir(iSCL, GPIO_OUT); + gpio_put(iSCL, LOW); +} + +static void SDA_HIGH(uint8_t iSDA) +{ + gpio_init(iSDA); + gpio_set_dir(iSDA, GPIO_IN); +} + +static void SDA_LOW(uint8_t iSDA) +{ + gpio_set_dir(iSDA, GPIO_OUT); + gpio_put(iSDA, LOW); +} + +// +// Transmit a byte and read the ack bit +// if we get a NACK (negative acknowledge) return 0 +// otherwise return 1 for success +// + +static int i2cByteOut(BBI2C *pI2C, uint8_t b) +{ +uint8_t i, ack; +uint8_t iSDA = pI2C->iSDA; +uint8_t iSCL = pI2C->iSCL; // in case of bad C compiler +int iDelay = pI2C->iDelay; + + for (i=0; i<8; i++) + { + if (b & 0x80) + SDA_HIGH(iSDA); // set data line to 1 + else + SDA_LOW(iSDA); // set data line to 0 + SCL_HIGH(iSCL); // clock high (slave latches data) + sleep_us(iDelay); + SCL_LOW(iSCL); // clock low + b <<= 1; + sleep_us(iDelay); + } // for i +// read ack bit + SDA_HIGH(iSDA); // set data line for reading + SCL_HIGH(iSCL); // clock line high + sleep_us(iDelay); // DEBUG - delay/2 + ack = SDA_READ(iSDA); + SCL_LOW(iSCL); // clock low + sleep_us(iDelay); // DEBUG - delay/2 + SDA_LOW(iSDA); // data low + return (ack == 0) ? 1:0; // a low ACK bit means success +} /* i2cByteOut() */ + +static int i2cByteOutFast(BBI2C *pI2C, uint8_t b) +{ +uint8_t i, ack, iSDA, iSCL; +int iDelay; + + iSDA = pI2C->iSDA; + iSCL = pI2C->iSCL; + iDelay = pI2C->iDelay; + + if (b & 0x80) + SDA_HIGH(iSDA); // set data line to 1 + else + SDA_LOW(iSDA); // set data line to 0 + for (i=0; i<8; i++) + { + SCL_HIGH(iSCL); // clock high (slave latches data) + sleep_us(iDelay); + SCL_LOW(iSCL); // clock low + sleep_us(iDelay); + } // for i +// read ack bit + SDA_HIGH(iSDA); // set data line for reading + SCL_HIGH(iSCL); // clock line high + sleep_us(pI2C->iDelay); // DEBUG - delay/2 + ack = SDA_READ(iSDA); + SCL_LOW(iSCL); // clock low + sleep_us(pI2C->iDelay); // DEBUG - delay/2 + SDA_LOW(iSDA); // data low + return (ack == 0) ? 1:0; // a low ACK bit means success +} /* i2cByteOutFast() */ +// +// Receive a byte and read the ack bit +// if we get a NACK (negative acknowledge) return 0 +// otherwise return 1 for success +// +static uint8_t i2cByteIn(BBI2C *pI2C, uint8_t bLast) +{ +uint8_t i; +uint8_t b = 0; + + SDA_HIGH(pI2C->iSDA); // set data line as input + for (i=0; i<8; i++) + { + sleep_us(pI2C->iDelay); // wait for data to settle + SCL_HIGH(pI2C->iSCL); // clock high (slave latches data) + b <<= 1; + if (SDA_READ(pI2C->iSDA) != 0) // read the data bit + b |= 1; // set data bit + SCL_LOW(pI2C->iSCL); // cloc low + } // for i + if (bLast) + SDA_HIGH(pI2C->iSDA); // last byte sends a NACK + else + SDA_LOW(pI2C->iSDA); + SCL_HIGH(pI2C->iSCL); // clock high + sleep_us(pI2C->iDelay); + SCL_LOW(pI2C->iSCL); // clock low to send ack + sleep_us(pI2C->iDelay); + SDA_LOW(pI2C->iSDA); // data low + return b; +} /* i2cByteIn() */ + +// +// Send I2C STOP condition +// +static void i2cEnd(BBI2C *pI2C) +{ + SDA_LOW(pI2C->iSDA); // data line low + sleep_us(pI2C->iDelay); + SCL_HIGH(pI2C->iSCL); // clock high + sleep_us(pI2C->iDelay); + SDA_HIGH(pI2C->iSDA); // data high + sleep_us(pI2C->iDelay); +} /* i2cEnd() */ + + +static int i2cBegin(BBI2C *pI2C, uint8_t addr, uint8_t bRead) +{ + int rc; + SDA_LOW(pI2C->iSDA); // data line low first + sleep_us(pI2C->iDelay); + SCL_LOW(pI2C->iSCL); // then clock line low is a START signal + addr <<= 1; + if (bRead) + addr++; // set read bit + rc = i2cByteOut(pI2C, addr); // send the slave address and R/W bit + return rc; +} /* i2cBegin() */ + +static int i2cWrite(BBI2C *pI2C, uint8_t *pData, int iLen) +{ +uint8_t b; +int rc, iOldLen = iLen; + + rc = 1; + while (iLen && rc == 1) + { + b = *pData++; +// if (b == 0xff || b == 0) +// rc = i2cByteOutFast(pI2C, b); // speed it up a bit more if all bits are == +// else + rc = i2cByteOut(pI2C, b); + if (rc == 1) // success + { + iLen--; + } + } // for each byte + return (rc == 1) ? (iOldLen - iLen) : 0; // 0 indicates bad ack from sending a byte +} /* i2cWrite() */ + +static void i2cRead(BBI2C *pI2C, uint8_t *pData, int iLen) +{ + while (iLen--) + { + *pData++ = i2cByteIn(pI2C, iLen == 0); + } // for each byte +} /* i2cRead() */ +// +// Initialize the I2C BitBang library +// Pass the pin numbers used for SDA and SCL +// as well as the clock rate in Hz +// +void I2CInit(BBI2C *pI2C, uint32_t iClock) +{ + if (pI2C == NULL) return; + + if (pI2C->bWire) // use Wire library + { + i2c_init(I2C_PORT, iClock); + gpio_set_function(pI2C->iSDA, GPIO_FUNC_I2C); + gpio_set_function(pI2C->iSCL, GPIO_FUNC_I2C); + gpio_pull_up(pI2C->iSDA); + gpio_pull_up(pI2C->iSCL); + return; + } + if (pI2C->iSDA < 0xa0) + { + gpio_init(pI2C->iSDA); + gpio_init(pI2C->iSCL); +// gpio_set_dir(pI2C->iSDA, GPIO_OUT); +// gpio_set_dir(pI2C->iSCL, GPIO_OUT); +// gpio_put(pI2C->iSDA, LOW); // setting low = enabling as outputs +// gpio_put(pI2C->iSCL, LOW); + gpio_set_dir(pI2C->iSDA, GPIO_IN); // let the lines float (tri-state) + gpio_set_dir(pI2C->iSCL, GPIO_IN); + + } + // For now, we only support 100, 400 or 800K clock rates + // all other values default to 100K + if (iClock >= 1000000) + pI2C->iDelay = 0; // the code execution is enough delay + else if (iClock >= 800000) + pI2C->iDelay = 1; + else if (iClock >= 400000) + pI2C->iDelay = 2; + else if (iClock >= 100000) + pI2C->iDelay = 10; + else pI2C->iDelay = (uint16_t)(1000000 / iClock); +} /* i2cInit() */ +// +// Test a specific I2C address to see if a device responds +// returns 0 for no response, 1 for a response +// +uint8_t I2CTest(BBI2C *pI2C, uint8_t addr) +{ +uint8_t response = 0; + + if (pI2C->bWire) + { + int ret; + uint8_t rxdata; + ret = i2c_read_blocking(I2C_PORT, addr, &rxdata, 1, false); + return (ret >= 0); + } + if (i2cBegin(pI2C, addr, 0)) // try to write to the given address + { + response = 1; + } + i2cEnd(pI2C); + return response; +} /* I2CTest() */ +// +// Scans for I2C devices on the bus +// returns a bitmap of devices which are present (128 bits = 16 bytes, LSB first) +// A set bit indicates that a device responded at that address +// +void I2CScan(BBI2C *pI2C, uint8_t *pMap) +{ + int i; + for (i=0; i<16; i++) // clear the bitmap + pMap[i] = 0; + for (i=1; i<128; i++) // try every address + { + if (I2CTest(pI2C, i)) + { + pMap[i >> 3] |= (1 << (i & 7)); + } + } +} /* I2CScan() */ +// +// Write I2C data +// quits if a NACK is received and returns 0 +// otherwise returns the number of bytes written +// +int I2CWrite(BBI2C *pI2C, uint8_t iAddr, uint8_t *pData, int iLen) +{ + int rc = 0; + + if (pI2C->bWire) + { + rc = i2c_write_blocking(I2C_PORT, iAddr, pData, iLen, true); // true to keep master control of bus + return rc >= 0 ? iLen : 0; + } + rc = i2cBegin(pI2C, iAddr, 0); + if (rc == 1) // slave sent ACK for its address + { + rc = i2cWrite(pI2C, pData, iLen); + } + i2cEnd(pI2C); + return rc; // returns the number of bytes sent or 0 for error +} /* I2CWrite() */ +// +// Read N bytes starting at a specific I2C internal register +// +int I2CReadRegister(BBI2C *pI2C, uint8_t iAddr, uint8_t u8Register, uint8_t *pData, int iLen) +{ + int rc; + + if (pI2C->bWire) // use the wire library + { + rc = i2c_write_blocking(I2C_PORT, iAddr, &u8Register, 1, true); // true to keep master control of bus + if (rc >= 0) { + rc = i2c_read_blocking(I2C_PORT, iAddr, pData, iLen, false); + } + return (rc >= 0); + } + rc = i2cBegin(pI2C, iAddr, 0); // start a write operation + if (rc == 1) // slave sent ACK for its address + { + rc = i2cWrite(pI2C, &u8Register, 1); // write the register we want to read from + if (rc == 1) + { + i2cEnd(pI2C); + rc = i2cBegin(pI2C, iAddr, 1); // start a read operation + if (rc == 1) + { + i2cRead(pI2C, pData, iLen); + } + } + } + i2cEnd(pI2C); + return rc; // returns 1 for success, 0 for error +} /* I2CReadRegister() */ +// +// Read N bytes +// +int I2CRead(BBI2C *pI2C, uint8_t iAddr, uint8_t *pData, int iLen) +{ + int rc; + + if (pI2C->bWire) // use the wire library + { + rc = i2c_read_blocking(I2C_PORT, iAddr, pData, iLen, false); + return (rc >= 0); + } + rc = i2cBegin(pI2C, iAddr, 1); + if (rc == 1) // slave sent ACK for its address + { + i2cRead(pI2C, pData, iLen); + } + i2cEnd(pI2C); + return rc; // returns 1 for success, 0 for error +} /* I2CRead() */ +// +// Figure out what device is at that address +// returns the enumerated value +// +int I2CDiscoverDevice(BBI2C *pI2C, uint8_t i) +{ +uint8_t j, cTemp[8]; +int iDevice = DEVICE_UNKNOWN; + + if (i == 0x3c || i == 0x3d) // Probably an OLED display + { + I2CReadRegister(pI2C, i, 0x00, cTemp, 1); + cTemp[0] &= 0xbf; // mask off power on/off bit + if (cTemp[0] == 0x8) // SH1106 + iDevice = DEVICE_SH1106; + else if (cTemp[0] == 3 || cTemp[0] == 6) + iDevice = DEVICE_SSD1306; + return iDevice; + } + + if (i == 0x34 || i == 0x35) // Probably an AXP202/AXP192 PMU chip + { + I2CReadRegister(pI2C, i, 0x03, cTemp, 1); // chip ID + if (cTemp[0] == 0x41) + return DEVICE_AXP202; + else if (cTemp[0] == 0x03) + return DEVICE_AXP192; + } + + if (i >= 0x40 && i <= 0x4f) // check for TI INA219 power measurement sensor + { + I2CReadRegister(pI2C, i, 0x00, cTemp, 2); + if (cTemp[0] == 0x39 && cTemp[1] == 0x9f) + return DEVICE_INA219; + } + + // Check for Microchip 24AAXXXE64 family serial 2 Kbit EEPROM + if (i >= 0x50 && i <= 0x57) { + uint32_t u32Temp = 0; + I2CReadRegister(pI2C, i, 0xf8, (uint8_t *)&u32Temp, + 3); // check for Microchip's OUI + if (u32Temp == 0x000004a3 || u32Temp == 0x00001ec0 || + u32Temp == 0x00d88039 || u32Temp == 0x005410ec) + return DEVICE_24AAXXXE64; + } + +// else if (i == 0x5b) // MLX90615? +// { +// I2CReadRegister(pI2C, i, 0x10, cTemp, 3); +// for (j=0; j<3; j++) Serial.println(cTemp[j], HEX); +// } + // try to identify it from the known devices using register contents + { + // Check for TI HDC1080 + I2CReadRegister(pI2C, i, 0xff, cTemp, 2); + if (cTemp[0] == 0x10 && cTemp[1] == 0x50) + return DEVICE_HDC1080; + + // Check for BME680 + if (i == 0x76 || i == 0x77) + { + I2CReadRegister(pI2C, i, 0xd0, cTemp, 1); // chip ID + if (cTemp[0] == 0x61) // BME680 + return DEVICE_BME680; + } + // Check for VL53L0X + I2CReadRegister(pI2C, i, 0xc0, cTemp, 3); + if (cTemp[0] == 0xee && cTemp[1] == 0xaa && cTemp[2] == 0x10) + return DEVICE_VL53L0X; + + // Check for CCS811 + I2CReadRegister(pI2C, i, 0x20, cTemp, 1); + if (cTemp[0] == 0x81) // Device ID + return DEVICE_CCS811; + + // Check for LIS3DSH accelerometer from STMicro + I2CReadRegister(pI2C, i, 0x0f, cTemp, 1); + if (cTemp[0] == 0x3f) // WHO_AM_I + return DEVICE_LIS3DSH; + + // Check for LIS3DH accelerometer from STMicro + I2CReadRegister(pI2C, i, 0x0f, cTemp, 1); + if (cTemp[0] == 0x33) // WHO_AM_I + return DEVICE_LIS3DH; + + // Check for LSM9DS1 magnetometer/gyro/accel sensor from STMicro + I2CReadRegister(pI2C, i, 0x0f, cTemp, 1); + if (cTemp[0] == 0x68) // WHO_AM_I + return DEVICE_LSM9DS1; + + // Check for LPS25H pressure sensor from STMicro + I2CReadRegister(pI2C, i, 0x0f, cTemp, 1); + if (cTemp[0] == 0xbd) // WHO_AM_I + return DEVICE_LPS25H; + + // Check for HTS221 temp/humidity sensor from STMicro + I2CReadRegister(pI2C, i, 0x0f, cTemp, 1); + if (cTemp[0] == 0xbc) // WHO_AM_I + return DEVICE_HTS221; + + // Check for MAG3110 + I2CReadRegister(pI2C, i, 0x07, cTemp, 1); + if (cTemp[0] == 0xc4) // WHO_AM_I + return DEVICE_MAG3110; + + // Check for LM8330 keyboard controller + I2CReadRegister(pI2C, i, 0x80, cTemp, 2); + if (cTemp[0] == 0x0 && cTemp[1] == 0x84) // manufacturer code + software revision + return DEVICE_LM8330; + + // Check for MAX44009 + if (i == 0x4a || i == 0x4b) + { + for (j=0; j<8; j++) + I2CReadRegister(pI2C, i, j, &cTemp[j], 1); // check for power-up reset state of registers + if ((cTemp[2] == 3 || cTemp[2] == 2) && cTemp[6] == 0 && cTemp[7] == 0xff) + return DEVICE_MAX44009; + } + + // Check for ADS1115 + I2CReadRegister(pI2C, i, 0x02, cTemp, 2); // Lo_thresh defaults to 0x8000 + I2CReadRegister(pI2C, i, 0x03, &cTemp[2], 2); // Hi_thresh defaults to 0x7fff + if (cTemp[0] == 0x80 && cTemp[1] == 0x00 && cTemp[2] == 0x7f && cTemp[3] == 0xff) + return DEVICE_ADS1115; + + // Check for MCP9808 + I2CReadRegister(pI2C, i, 0x06, cTemp, 2); // manufacturer ID && get device ID/revision + I2CReadRegister(pI2C, i, 0x07, &cTemp[2], 2); // need to read them individually + if (cTemp[0] == 0 && cTemp[1] == 0x54 && cTemp[2] == 0x04 && cTemp[3] == 0x00) + return DEVICE_MCP9808; + + // Check for BMP280/BME280 + I2CReadRegister(pI2C, i, 0xd0, cTemp, 1); + if (cTemp[0] == 0x55) // BMP180 + return DEVICE_BMP180; + else if (cTemp[0] == 0x58) + return DEVICE_BMP280; + else if (cTemp[0] == 0x60) // BME280 + return DEVICE_BME280; + + // Check for LSM6DS3 + I2CReadRegister(pI2C, i, 0x0f, cTemp, 1); // WHO_AM_I + if (cTemp[0] == 0x69) + return DEVICE_LSM6DS3; + + // Check for ADXL345 + I2CReadRegister(pI2C, i, 0x00, cTemp, 1); // DEVID + if (cTemp[0] == 0xe5) + return DEVICE_ADXL345; + + // Check for MPU-60x0i, MPU-688x, and MPU-9250 + I2CReadRegister(pI2C, i, 0x75, cTemp, 1); + if (cTemp[0] == (i & 0xfe)) // Current I2C address (low bit set to 0) + return DEVICE_MPU6000; + else if (cTemp[0] == 0x71) + return DEVICE_MPU9250; + else if (cTemp[0] == 0x19) + return DEVICE_MPU6886; + + // Check for DS3231 RTC + I2CReadRegister(pI2C, i, 0x0e, cTemp, 1); // read the control register + if (i == 0x68 && + cTemp[0] == 0x1c) // fixed I2C address and power on reset value + return DEVICE_DS3231; + + // Check for DS1307 RTC + I2CReadRegister(pI2C, i, 0x07, cTemp, 1); // read the control register + if (i == 0x68 && + cTemp[0] == 0x03) // fixed I2C address and power on reset value + return DEVICE_DS1307; + + } + return iDevice; +} /* I2CDiscoverDevice() */ diff --git a/oled/BitBang_I2C.h b/oled/BitBang_I2C.h new file mode 100644 index 0000000..954c3f5 --- /dev/null +++ b/oled/BitBang_I2C.h @@ -0,0 +1,109 @@ +// +// Bit Bang I2C library +// Copyright (c) 2018 BitBank Software, Inc. +// Written by Larry Bank (bitbank@pobox.com) +// Project started 10/12/2018 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// +#ifndef __BITBANG_I2C__ +#define __BITBANG_I2C__ +// supported devices +enum { + DEVICE_UNKNOWN = 0, + DEVICE_SSD1306, + DEVICE_SH1106, + DEVICE_VL53L0X, + DEVICE_BMP180, + DEVICE_BMP280, + DEVICE_BME280, + DEVICE_MPU6000, + DEVICE_MPU9250, + DEVICE_MCP9808, + DEVICE_LSM6DS3, + DEVICE_ADXL345, + DEVICE_ADS1115, + DEVICE_MAX44009, + DEVICE_MAG3110, + DEVICE_CCS811, + DEVICE_HTS221, + DEVICE_LPS25H, + DEVICE_LSM9DS1, + DEVICE_LM8330, + DEVICE_DS3231, + DEVICE_LIS3DH, + DEVICE_LIS3DSH, + DEVICE_INA219, + DEVICE_SHT3X, + DEVICE_HDC1080, + DEVICE_MPU6886, + DEVICE_BME680, + DEVICE_AXP202, + DEVICE_AXP192, + DEVICE_24AAXXXE64, + DEVICE_DS1307 +}; + +#ifndef LOW +#define LOW 0 +#define HIGH 1 +#endif + +typedef struct mybbi2c +{ +uint8_t iSDA, iSCL; // pin numbers (0xff = disabled) +uint8_t bWire; // use the Wire library +uint8_t iSDABit, iSCLBit; // bit numbers of the ports +uint32_t iDelay; +} BBI2C; +// +// Read N bytes +// +int I2CRead(BBI2C *pI2C, uint8_t iAddr, uint8_t *pData, int iLen); +// +// Read N bytes starting at a specific I2C internal register +// +int I2CReadRegister(BBI2C *pI2C, uint8_t iAddr, uint8_t u8Register, uint8_t *pData, int iLen); +// +// Write I2C data +// quits if a NACK is received and returns 0 +// otherwise returns the number of bytes written +// +int I2CWrite(BBI2C *pI2C, uint8_t iAddr, uint8_t *pData, int iLen); +// +// Scans for I2C devices on the bus +// returns a bitmap of devices which are present (128 bits = 16 bytes, LSB first) +// +// Test if an address responds +// returns 0 if no response, 1 if it responds +// +uint8_t I2CTest(BBI2C *pI2C, uint8_t addr); + +// A set bit indicates that a device responded at that address +// +void I2CScan(BBI2C *pI2C, uint8_t *pMap); +// +// Initialize the I2C BitBang library +// Pass the pin numbers used for SDA and SCL +// as well as the clock rate in Hz +// +void I2CInit(BBI2C *pI2C, uint32_t iClock); +// +// Figure out what device is at that address +// returns the enumerated value +// +int I2CDiscoverDevice(BBI2C *pI2C, uint8_t i); + +#endif //__BITBANG_I2C__ + diff --git a/oled/CMakeLists.txt b/oled/CMakeLists.txt new file mode 100644 index 0000000..4850315 --- /dev/null +++ b/oled/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.12) + +include(pico_sdk_import.cmake) + +project(ss_oled_demo) +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +pico_sdk_init() + +add_executable(ss_oled_demo + ss_oled_demo.c + BitBang_I2C.c + BitBang_I2C.h + ss_oled.c + ss_oled.h + ) + +#target_sources(ss_oled_demo PRIVATE +# ss_oled_demo.c +# BitBang_I2C.c +# BitBang_I2C.h +# ss_oled.c +# ss_oled.h +# ) + +target_link_libraries(ss_oled_demo pico_stdlib hardware_i2c) +#pico_enable_stdio_usb(ss_oled_demo 1) + +pico_add_extra_outputs(ss_oled_demo) + diff --git a/oled/pico_sdk_import.cmake b/oled/pico_sdk_import.cmake new file mode 100644 index 0000000..f63ee3f --- /dev/null +++ b/oled/pico_sdk_import.cmake @@ -0,0 +1,64 @@ +# This is a copy of <PICO_SDK_PATH>/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() + +# todo document + +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 () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the PICO SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO 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") + +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 master + ) + if (NOT pico_sdk) + message("Downloading PICO SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "PICO 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 PICO SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the PICO SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) diff --git a/oled/ss_oled.c b/oled/ss_oled.c new file mode 100644 index 0000000..3cd9132 --- /dev/null +++ b/oled/ss_oled.c @@ -0,0 +1,2255 @@ +// +// ss_oled (Small, Simple OLED library) +// Copyright (c) 2017-2019 BitBank Software, Inc. +// Written by Larry Bank (bitbank@pobox.com) +// Project started 1/15/2017 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include "pico/stdlib.h" + +#include "ss_oled.h" + +const uint8_t ucFont[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x5f,0x5f,0x06,0x00, + 0x00,0x07,0x07,0x00,0x07,0x07,0x00,0x14,0x7f,0x7f,0x14,0x7f,0x7f,0x14, + 0x24,0x2e,0x2a,0x6b,0x6b,0x3a,0x12,0x46,0x66,0x30,0x18,0x0c,0x66,0x62, + 0x30,0x7a,0x4f,0x5d,0x37,0x7a,0x48,0x00,0x04,0x07,0x03,0x00,0x00,0x00, + 0x00,0x1c,0x3e,0x63,0x41,0x00,0x00,0x00,0x41,0x63,0x3e,0x1c,0x00,0x00, + 0x08,0x2a,0x3e,0x1c,0x3e,0x2a,0x08,0x00,0x08,0x08,0x3e,0x3e,0x08,0x08, + 0x00,0x00,0x80,0xe0,0x60,0x00,0x00,0x00,0x08,0x08,0x08,0x08,0x08,0x08, + 0x00,0x00,0x00,0x60,0x60,0x00,0x00,0x60,0x30,0x18,0x0c,0x06,0x03,0x01, + 0x3e,0x7f,0x59,0x4d,0x47,0x7f,0x3e,0x40,0x42,0x7f,0x7f,0x40,0x40,0x00, + 0x62,0x73,0x59,0x49,0x6f,0x66,0x00,0x22,0x63,0x49,0x49,0x7f,0x36,0x00, + 0x18,0x1c,0x16,0x53,0x7f,0x7f,0x50,0x27,0x67,0x45,0x45,0x7d,0x39,0x00, + 0x3c,0x7e,0x4b,0x49,0x79,0x30,0x00,0x03,0x03,0x71,0x79,0x0f,0x07,0x00, + 0x36,0x7f,0x49,0x49,0x7f,0x36,0x00,0x06,0x4f,0x49,0x69,0x3f,0x1e,0x00, + 0x00,0x00,0x00,0x66,0x66,0x00,0x00,0x00,0x00,0x80,0xe6,0x66,0x00,0x00, + 0x08,0x1c,0x36,0x63,0x41,0x00,0x00,0x00,0x14,0x14,0x14,0x14,0x14,0x14, + 0x00,0x41,0x63,0x36,0x1c,0x08,0x00,0x00,0x02,0x03,0x59,0x5d,0x07,0x02, + 0x3e,0x7f,0x41,0x5d,0x5d,0x5f,0x0e,0x7c,0x7e,0x13,0x13,0x7e,0x7c,0x00, + 0x41,0x7f,0x7f,0x49,0x49,0x7f,0x36,0x1c,0x3e,0x63,0x41,0x41,0x63,0x22, + 0x41,0x7f,0x7f,0x41,0x63,0x3e,0x1c,0x41,0x7f,0x7f,0x49,0x5d,0x41,0x63, + 0x41,0x7f,0x7f,0x49,0x1d,0x01,0x03,0x1c,0x3e,0x63,0x41,0x51,0x33,0x72, + 0x7f,0x7f,0x08,0x08,0x7f,0x7f,0x00,0x00,0x41,0x7f,0x7f,0x41,0x00,0x00, + 0x30,0x70,0x40,0x41,0x7f,0x3f,0x01,0x41,0x7f,0x7f,0x08,0x1c,0x77,0x63, + 0x41,0x7f,0x7f,0x41,0x40,0x60,0x70,0x7f,0x7f,0x0e,0x1c,0x0e,0x7f,0x7f, + 0x7f,0x7f,0x06,0x0c,0x18,0x7f,0x7f,0x1c,0x3e,0x63,0x41,0x63,0x3e,0x1c, + 0x41,0x7f,0x7f,0x49,0x09,0x0f,0x06,0x1e,0x3f,0x21,0x31,0x61,0x7f,0x5e, + 0x41,0x7f,0x7f,0x09,0x19,0x7f,0x66,0x26,0x6f,0x4d,0x49,0x59,0x73,0x32, + 0x03,0x41,0x7f,0x7f,0x41,0x03,0x00,0x7f,0x7f,0x40,0x40,0x7f,0x7f,0x00, + 0x1f,0x3f,0x60,0x60,0x3f,0x1f,0x00,0x3f,0x7f,0x60,0x30,0x60,0x7f,0x3f, + 0x63,0x77,0x1c,0x08,0x1c,0x77,0x63,0x07,0x4f,0x78,0x78,0x4f,0x07,0x00, + 0x47,0x63,0x71,0x59,0x4d,0x67,0x73,0x00,0x7f,0x7f,0x41,0x41,0x00,0x00, + 0x01,0x03,0x06,0x0c,0x18,0x30,0x60,0x00,0x41,0x41,0x7f,0x7f,0x00,0x00, + 0x08,0x0c,0x06,0x03,0x06,0x0c,0x08,0x80,0x80,0x80,0x80,0x80,0x80,0x80, + 0x00,0x00,0x03,0x07,0x04,0x00,0x00,0x20,0x74,0x54,0x54,0x3c,0x78,0x40, + 0x41,0x7f,0x3f,0x48,0x48,0x78,0x30,0x38,0x7c,0x44,0x44,0x6c,0x28,0x00, + 0x30,0x78,0x48,0x49,0x3f,0x7f,0x40,0x38,0x7c,0x54,0x54,0x5c,0x18,0x00, + 0x48,0x7e,0x7f,0x49,0x03,0x06,0x00,0x98,0xbc,0xa4,0xa4,0xf8,0x7c,0x04, + 0x41,0x7f,0x7f,0x08,0x04,0x7c,0x78,0x00,0x44,0x7d,0x7d,0x40,0x00,0x00, + 0x60,0xe0,0x80,0x84,0xfd,0x7d,0x00,0x41,0x7f,0x7f,0x10,0x38,0x6c,0x44, + 0x00,0x41,0x7f,0x7f,0x40,0x00,0x00,0x7c,0x7c,0x18,0x78,0x1c,0x7c,0x78, + 0x7c,0x78,0x04,0x04,0x7c,0x78,0x00,0x38,0x7c,0x44,0x44,0x7c,0x38,0x00, + 0x84,0xfc,0xf8,0xa4,0x24,0x3c,0x18,0x18,0x3c,0x24,0xa4,0xf8,0xfc,0x84, + 0x44,0x7c,0x78,0x4c,0x04,0x0c,0x18,0x48,0x5c,0x54,0x74,0x64,0x24,0x00, + 0x04,0x04,0x3e,0x7f,0x44,0x24,0x00,0x3c,0x7c,0x40,0x40,0x3c,0x7c,0x40, + 0x1c,0x3c,0x60,0x60,0x3c,0x1c,0x00,0x3c,0x7c,0x60,0x30,0x60,0x7c,0x3c, + 0x44,0x6c,0x38,0x10,0x38,0x6c,0x44,0x9c,0xbc,0xa0,0xa0,0xfc,0x7c,0x00, + 0x4c,0x64,0x74,0x5c,0x4c,0x64,0x00,0x08,0x08,0x3e,0x77,0x41,0x41,0x00, + 0x00,0x00,0x00,0x77,0x77,0x00,0x00,0x41,0x41,0x77,0x3e,0x08,0x08,0x00, + 0x02,0x03,0x01,0x03,0x02,0x03,0x01,0x70,0x78,0x4c,0x46,0x4c,0x78,0x70}; +const uint8_t ucBigFont[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xfc,0xfc,0xff,0xff,0xff,0xff,0xfc,0xfc,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0x3f,0x3f,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0f,0x0f,0x3f,0x3f,0x00,0x00,0x00,0x00,0x3f,0x3f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xfc,0xfc,0xfc,0xfc,0xc0,0xc0,0xfc,0xfc,0xfc,0xfc,0xc0,0xc0,0x00,0x00, + 0xc0,0xc0,0xff,0xff,0xff,0xff,0xc0,0xc0,0xff,0xff,0xff,0xff,0xc0,0xc0,0x00,0x00, + 0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0x0f,0x0f,0x3c,0x3c,0x00,0x00, + 0xf0,0xf0,0xc3,0xc3,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x00,0x00,0x03,0x03,0x03,0x03,0x3f,0x3f,0x3f,0x3f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x3c,0x3c,0xff,0xff,0xc3,0xc3,0xff,0xff,0x3c,0x3c,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x0f,0x0f,0xfc,0xfc,0xff,0xff,0x03,0x03,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x30,0x30,0x3f,0x3f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0xfc,0xfc,0xf0,0xf0,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0x0c,0x0c,0xcc,0xcc,0xff,0xff,0x3f,0x3f,0x3f,0x3f,0xff,0xff,0xcc,0xcc,0x0c,0x0c, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x0c,0x0c,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x30,0x30,0x3f,0x3f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0xc3,0xc3,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0xff,0xff,0xff,0xff,0x30,0x30,0x0f,0x0f,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x30,0x30,0x3c,0x3c,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x03,0x03,0xc3,0xc3,0xff,0xff,0x3c,0x3c,0x00,0x00, + 0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00, + 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x0f,0x0f,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x0c,0x0c,0x3f,0x3f,0xf3,0xf3,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0xc3,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x0c,0x0c,0x3c,0x3c,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xf3,0xf3,0x3f,0x3f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x3c,0x3c,0x3f,0x3f,0x03,0x03,0x03,0x03,0xc3,0xc3,0xff,0xff,0x3c,0x3c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x3f,0x3f,0x3f,0x3f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xf0,0xfc,0xfc,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0xfc,0xfc,0xf0,0xf0,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x3f,0x3f,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x3c,0x3c,0xf0,0xf0,0xc0,0xc0,0x00,0x00, + 0xff,0xff,0xff,0xff,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x03,0x03,0x0f,0x0f,0x3c,0x3c,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0x00,0x00, + 0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x0f,0x0f,0xfc,0xfc,0xf0,0xf0,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0xc3,0xc3,0x0f,0x0f,0x3f,0x3f,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x0f,0x0f,0x00,0x00,0xc0,0xc0,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0xc3,0xc3,0x0f,0x0f,0x3f,0x3f,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x03,0x03,0x0f,0x0f,0x3c,0x3c,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x0c,0x0c,0x0c,0x0c,0xfc,0xfc,0xfc,0xfc,0x00,0x00, + 0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x03,0x03,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00, + 0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00,0xf0,0xf0,0xff,0xff,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x0f,0x0f,0x3f,0x3f,0xf0,0xf0,0xc0,0xc0,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0xfc,0xfc,0xf0,0xf0,0xfc,0xfc,0xff,0xff,0xff,0xff,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x03,0x03,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0xfc,0xfc,0xf0,0xf0,0xc0,0xc0,0xff,0xff,0xff,0xff,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x03,0x03,0x0f,0x0f,0xff,0xff,0xff,0xff,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xf0,0xfc,0xfc,0x0f,0x0f,0x03,0x03,0x0f,0x0f,0xfc,0xfc,0xf0,0xf0,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0xc0,0xc0,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0f,0x0f,0xff,0xff,0xff,0xff,0xc3,0xc3,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x0f,0x0f,0xff,0xff,0xf0,0xf0,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x3c,0x3c,0xff,0xff,0xc3,0xc3,0x03,0x03,0x03,0x03,0x3f,0x3f,0x3c,0x3c,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0x03,0x03,0x03,0x03,0x0f,0x0f,0xfc,0xfc,0xf0,0xf0,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x3f,0x3f,0x0f,0x0f,0xff,0xff,0xff,0xff,0x0f,0x0f,0x3f,0x3f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x3f,0x3f,0xff,0xff,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xff,0xff,0x3f,0x3f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0xff,0xff,0xff,0xff,0xc0,0xc0,0xfc,0xfc,0xc0,0xc0,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0f,0x0f,0xff,0xff,0xf0,0xf0,0x00,0x00,0xf0,0xf0,0xff,0xff,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0xf0,0xf0,0xff,0xff,0x0f,0x0f,0xff,0xff,0xf0,0xf0,0x00,0x00,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x3f,0x3f,0x0f,0x0f,0x03,0x03,0x03,0x03,0xc3,0xc3,0xff,0xff,0x3f,0x3f,0x00,0x00, + 0xc0,0xc0,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x03,0x03,0x00,0x00,0xc0,0xc0,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x03,0x03,0x0f,0x0f,0x3f,0x3f,0xfc,0xfc,0xf0,0xf0,0xc0,0xc0,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0, + 0x00,0x00,0x00,0x00,0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0xf0,0xf0,0xfc,0xfc,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc0,0xc0,0xc3,0xc3,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xfc,0xfc,0xff,0xff,0x03,0x03,0x0f,0x0f,0x3c,0x3c,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0xc3,0xc3,0xcf,0xcf,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc0,0xc0,0xcf,0xcf,0xcf,0xcf,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xcf,0xcf,0xcf,0xcf,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0xf0,0xf0,0xf0,0xf0,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00, + 0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x3c,0x3c,0xff,0xff,0xc3,0xc3,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x03,0x03,0xff,0xff,0x03,0x03,0xff,0xff,0xff,0xff,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x0f,0x0f,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x03,0x03,0x00,0x00,0x03,0x03,0x0f,0x0f,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x3c,0x3c,0x30,0x30,0xf0,0xf0,0xc3,0xc3,0x03,0x03,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0xfc,0xfc,0xff,0xff,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0xf0,0xf0,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0f,0x0f,0x03,0x03,0x0f,0x0f,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0x00,0x00,0x03,0x03,0xff,0xff,0xfc,0xfc,0xff,0xff,0x03,0x03,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00, + 0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0xcc,0xcc,0xff,0xff,0x3f,0x3f,0x00,0x00, + 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0x00,0x00, + 0x03,0x03,0xc3,0xc3,0xf0,0xf0,0x3c,0x3c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00, + 0x0f,0x0f,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x0f,0x0f,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x00,0x00, + 0x00,0x00,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0c,0x0c,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xfc,0xfc,0xfc,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x0f,0x0f,0x0f,0x0f,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x03,0x03,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0xfc,0xfc,0xff,0xff,0x03,0x03,0x03,0x03,0x00,0x00, + 0x00,0x00,0x0c,0x0c,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x0f,0x0f,0x0c,0x0c,0x0f,0x0f,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, + 0x00,0x00,0x00,0x00,0xc0,0xc0,0xf0,0xf0,0xc0,0xc0,0x00,0x00,0x00,0x00,0x00,0x00, + 0xfc,0xfc,0xff,0xff,0x03,0x03,0x00,0x00,0x03,0x03,0xff,0xff,0xfc,0xfc,0x00,0x00, + 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00, + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + +// 5x7 font (in 6x8 cell) +const uint8_t ucSmallFont[] = { +0x00,0x00,0x00,0x00,0x00, +0x00,0x06,0x5f,0x06,0x00, +0x07,0x03,0x00,0x07,0x03, +0x24,0x7e,0x24,0x7e,0x24, +0x24,0x2b,0x6a,0x12,0x00, +0x63,0x13,0x08,0x64,0x63, +0x36,0x49,0x56,0x20,0x50, +0x00,0x07,0x03,0x00,0x00, +0x00,0x3e,0x41,0x00,0x00, +0x00,0x41,0x3e,0x00,0x00, +0x08,0x3e,0x1c,0x3e,0x08, +0x08,0x08,0x3e,0x08,0x08, +0x00,0xe0,0x60,0x00,0x00, +0x08,0x08,0x08,0x08,0x08, +0x00,0x60,0x60,0x00,0x00, +0x20,0x10,0x08,0x04,0x02, +0x3e,0x51,0x49,0x45,0x3e, +0x00,0x42,0x7f,0x40,0x00, +0x62,0x51,0x49,0x49,0x46, +0x22,0x49,0x49,0x49,0x36, +0x18,0x14,0x12,0x7f,0x10, +0x2f,0x49,0x49,0x49,0x31, +0x3c,0x4a,0x49,0x49,0x30, +0x01,0x71,0x09,0x05,0x03, +0x36,0x49,0x49,0x49,0x36, +0x06,0x49,0x49,0x29,0x1e, +0x00,0x6c,0x6c,0x00,0x00, +0x00,0xec,0x6c,0x00,0x00, +0x08,0x14,0x22,0x41,0x00, +0x24,0x24,0x24,0x24,0x24, +0x00,0x41,0x22,0x14,0x08, +0x02,0x01,0x59,0x09,0x06, +0x3e,0x41,0x5d,0x55,0x1e, +0x7e,0x11,0x11,0x11,0x7e, +0x7f,0x49,0x49,0x49,0x36, +0x3e,0x41,0x41,0x41,0x22, +0x7f,0x41,0x41,0x41,0x3e, +0x7f,0x49,0x49,0x49,0x41, +0x7f,0x09,0x09,0x09,0x01, +0x3e,0x41,0x49,0x49,0x7a, +0x7f,0x08,0x08,0x08,0x7f, +0x00,0x41,0x7f,0x41,0x00, +0x30,0x40,0x40,0x40,0x3f, +0x7f,0x08,0x14,0x22,0x41, +0x7f,0x40,0x40,0x40,0x40, +0x7f,0x02,0x04,0x02,0x7f, +0x7f,0x02,0x04,0x08,0x7f, +0x3e,0x41,0x41,0x41,0x3e, +0x7f,0x09,0x09,0x09,0x06, +0x3e,0x41,0x51,0x21,0x5e, +0x7f,0x09,0x09,0x19,0x66, +0x26,0x49,0x49,0x49,0x32, +0x01,0x01,0x7f,0x01,0x01, +0x3f,0x40,0x40,0x40,0x3f, +0x1f,0x20,0x40,0x20,0x1f, +0x3f,0x40,0x3c,0x40,0x3f, +0x63,0x14,0x08,0x14,0x63, +0x07,0x08,0x70,0x08,0x07, +0x71,0x49,0x45,0x43,0x00, +0x00,0x7f,0x41,0x41,0x00, +0x02,0x04,0x08,0x10,0x20, +0x00,0x41,0x41,0x7f,0x00, +0x04,0x02,0x01,0x02,0x04, +0x80,0x80,0x80,0x80,0x80, +0x00,0x03,0x07,0x00,0x00, +0x20,0x54,0x54,0x54,0x78, +0x7f,0x44,0x44,0x44,0x38, +0x38,0x44,0x44,0x44,0x28, +0x38,0x44,0x44,0x44,0x7f, +0x38,0x54,0x54,0x54,0x08, +0x08,0x7e,0x09,0x09,0x00, +0x18,0xa4,0xa4,0xa4,0x7c, +0x7f,0x04,0x04,0x78,0x00, +0x00,0x00,0x7d,0x40,0x00, +0x40,0x80,0x84,0x7d,0x00, +0x7f,0x10,0x28,0x44,0x00, +0x00,0x00,0x7f,0x40,0x00, +0x7c,0x04,0x18,0x04,0x78, +0x7c,0x04,0x04,0x78,0x00, +0x38,0x44,0x44,0x44,0x38, +0xfc,0x44,0x44,0x44,0x38, +0x38,0x44,0x44,0x44,0xfc, +0x44,0x78,0x44,0x04,0x08, +0x08,0x54,0x54,0x54,0x20, +0x04,0x3e,0x44,0x24,0x00, +0x3c,0x40,0x20,0x7c,0x00, +0x1c,0x20,0x40,0x20,0x1c, +0x3c,0x60,0x30,0x60,0x3c, +0x6c,0x10,0x10,0x6c,0x00, +0x9c,0xa0,0x60,0x3c,0x00, +0x64,0x54,0x54,0x4c,0x00, +0x08,0x3e,0x41,0x41,0x00, +0x00,0x00,0x77,0x00,0x00, +0x00,0x41,0x41,0x3e,0x08, +0x02,0x01,0x02,0x01,0x00, +0x3c,0x26,0x23,0x26,0x3c}; + +// Initialization sequences +const unsigned char oled128_initbuf[] = {0x00, 0xae,0xdc,0x00,0x81,0x40, + 0xa1,0xc8,0xa8,0x7f,0xd5,0x50,0xd9,0x22,0xdb,0x35,0xb0,0xda,0x12, + 0xa4,0xa6,0xaf}; + +const unsigned char oled64_initbuf[]={0x00,0xae,0xa8,0x3f,0xd3,0x00,0x40,0xa1,0xc8, + 0xda,0x12,0x81,0xff,0xa4,0xa6,0xd5,0x80,0x8d,0x14, + 0xaf,0x20,0x02}; + +const unsigned char oled32_initbuf[] = { +0x00,0xae,0xd5,0x80,0xa8,0x1f,0xd3,0x00,0x40,0x8d,0x14,0xa1,0xc8,0xda,0x02, +0x81,0x7f,0xd9,0xf1,0xdb,0x40,0xa4,0xa6,0xaf}; + +const unsigned char oled72_initbuf[]={0x00,0xae,0xa8,0x3f,0xd3,0x00,0x40,0xa1,0xc8, + 0xda,0x12,0x81,0xff,0xad,0x30,0xd9,0xf1,0xa4,0xa6,0xd5,0x80,0x8d,0x14, + 0xaf,0x20,0x02}; + +// some globals +static int iCSPin, iDCPin, iResetPin; +//static int iScreenOffset; // current write offset of screen data +//static uint8_t *ucScreen; // backbuffer provided by the user +//static int oled_wrap, oled_flip, oled_addr, oled_type; +//static int iCursorX, iCursorY; +//static uint8_t oled_x, oled_y; // width and height of the display +//static int iSDAPin, iSCLPin; +#define MAX_CACHE 32 +//static byte bCache[MAX_CACHE] = {0x40}; // for faster character drawing +//static byte bEnd = 1; +static void oledWriteCommand(SSOLED *pOLED, unsigned char c); +void InvertBytes(uint8_t *pData, uint8_t bLen); + +// wrapper/adapter functions to make the code work on Linux +static uint8_t pgm_read_byte(uint8_t *ptr) +{ + return *ptr; +} +static int16_t pgm_read_word(uint8_t *ptr) +{ + return ptr[0] + (ptr[1]<<8); +} + +static void _I2CWrite(SSOLED *pOLED, unsigned char *pData, int iLen) +{ + I2CWrite(&pOLED->bbi2c, pOLED->oled_addr, pData, iLen); +} /* _I2CWrite() */ + +#ifdef FUTURE +static void oledCachedFlush(void) +{ + _I2CWrite(bCache, bEnd); // write the old data +#ifdef USE_BACKBUFFER + memcpy(&ucScreen[iScreenOffset], &bCache[1], bEnd-1); + iScreenOffset += (bEnd - 1); +#endif + bEnd = 1; +} /* oledCachedFlush() */ + +static void oledCachedWrite(uint8_t *pData, uint8_t bLen) +{ + + if (bEnd + bLen > MAX_CACHE) // need to flush it + { + oledCachedFlush(); // write the old data + } + memcpy(&bCache[bEnd], pData, bLen); + bEnd += bLen; + +} /* oledCachedWrite() */ +#endif // FUTURE +#if !defined( __AVR_ATtiny85__ ) && !defined( _LINUX_ ) +// +// Initialize the OLED controller for SPI mode +// +#ifdef FUTURE +void oledSPIInit(int iType, int iDC, int iCS, int iReset, int bFlip, int bInvert, int32_t iSpeed) +{ +uint8_t uc[32], *s; +int iLen; + + ucScreen = NULL; // start with no backbuffer; user must provide one later + iDCPin = iDC; + iCSPin = iCS; + iResetPin = iReset; + oled_type = iType; + oled_flip = bFlip; + oled_wrap = 0; // default - disable text wrap + + pinMode(iDCPin, OUTPUT); + pinMode(iCSPin, OUTPUT); + digitalWrite(iCSPin, HIGH); + + // Reset it + if (iResetPin != -1) + { + pinMode(iResetPin, OUTPUT); + digitalWrite(iResetPin, HIGH); + delay(50); + digitalWrite(iResetPin, LOW); + delay(50); + digitalWrite(iResetPin, HIGH); + delay(10); + } +// Initialize SPI + SPI.begin(); + SPI.beginTransaction(SPISettings(iSpeed, MSBFIRST, SPI_MODE0)); +// SPI.setClockDivider(16); +// SPI.setBitOrder(MSBFIRST); +// SPI.setDataMode(SPI_MODE0); + + if (iType == OLED_128x32 || iType == OLED_96x16) + { + s = (uint8_t *)oled32_initbuf; + iLen = sizeof(oled32_initbuf); + } + else if (iType == OLED_128x128) + { + s = (uint8_t *)oled128_initbuf; + iLen = sizeof(oled128_initbuf); + } + else + { + s = (uint8_t *)oled64_initbuf; + iLen = sizeof(oled64_initbuf); + } + memcpy(uc, s, iLen); // do it from RAM + _I2CWrite(pOLED, s, iLen); + + if (bInvert) + { + uc[0] = 0; // command + uc[1] = 0xa7; // invert command + _I2CWrite(pOLED, uc, 2); + } + if (bFlip) // rotate display 180 + { + uc[0] = 0; // command + uc[1] = 0xa0; + _I2CWrite(pOLED, uc, 2); + uc[0] = 0; + uc[1] = 0xc0; + _I2CWrite(pOLED, uc, 2); + } + +} /* oledSPIInit() */ +#endif +#endif +// +// Initializes the OLED controller into "page mode" +// +int oledInit(SSOLED *pOLED, int iType, int iAddr, int bFlip, int bInvert, int bWire, int sda, int scl, int reset, int32_t iSpeed) +{ +unsigned char uc[4]; +int rc = OLED_NOT_FOUND; + + pOLED->ucScreen = NULL; // reset backbuffer; user must provide one later + pOLED->oled_type = iType; + pOLED->oled_flip = bFlip; + pOLED->oled_wrap = 0; // default - disable text wrap +#ifdef _LINUX_ + pOLED->bbi2c.iBus = sda; // bus number +#endif + pOLED->bbi2c.bWire = bWire; + pOLED->bbi2c.iSDA = sda; + pOLED->bbi2c.iSCL = scl; + iResetPin = reset; +// Disable SPI mode code + iCSPin = iDCPin = -1; + + I2CInit(&pOLED->bbi2c, iSpeed); // on Linux, SDA = bus number, SCL = device address + + // Reset it +#ifdef FUTURE + if (iResetPin != -1) + { + pinMode(iResetPin, OUTPUT); + digitalWrite(iResetPin, HIGH); + delay(50); + digitalWrite(iResetPin, LOW); + delay(50); + digitalWrite(iResetPin, HIGH); + delay(10); + } +#endif + + // find the device address if requested + if (iAddr == -1 || iAddr == 0 || iAddr == 0xff) // find it + { + I2CTest(&pOLED->bbi2c, 0x3c); + if (I2CTest(&pOLED->bbi2c, 0x3c)) + pOLED->oled_addr = 0x3c; + else if (I2CTest(&pOLED->bbi2c, 0x3d)) + pOLED->oled_addr = 0x3d; + else + return rc; // no display found! + } + else + { + pOLED->oled_addr = iAddr; + I2CTest(&pOLED->bbi2c, iAddr); + if (!I2CTest(&pOLED->bbi2c, iAddr)) + return rc; // no display found + } + // Detect the display controller (SSD1306, SH1107 or SH1106) + uint8_t u = 0; + I2CReadRegister(&pOLED->bbi2c, pOLED->oled_addr, 0x00, &u, 1); // read the status register + u &= 0x0f; // mask off power on/off bit + if (u == 0x7 || u == 0xf) // SH1107 + { + pOLED->oled_type = OLED_128x128; + rc = OLED_SH1107_3C; + bFlip = !bFlip; // SH1107 seems to have this reversed from the usual direction + } + else if (u == 0x8) // SH1106 + { + rc = OLED_SH1106_3C; + pOLED->oled_type = OLED_132x64; // needs to be treated a little differently + } + else if (u == 3 || u == 6) // 6=128x64 display, 3=smaller + { + rc = OLED_SSD1306_3C; + } + if (pOLED->oled_addr == 0x3d) + rc++; // return the '3D' version of the type + + if (iType == OLED_128x32 || iType == OLED_96x16) + _I2CWrite(pOLED,(unsigned char *)oled32_initbuf, sizeof(oled32_initbuf)); + else if (iType == OLED_128x128) + _I2CWrite(pOLED,(unsigned char *)oled128_initbuf, sizeof(oled128_initbuf)); + else if (iType == OLED_72x40) + _I2CWrite(pOLED,(unsigned char *)oled72_initbuf, sizeof(oled72_initbuf)); + else // 132x64, 128x64 and 64x32 + _I2CWrite(pOLED,(unsigned char *)oled64_initbuf, sizeof(oled64_initbuf)); + if (bInvert) + { + uc[0] = 0; // command + uc[1] = 0xa7; // invert command + _I2CWrite(pOLED,uc, 2); + } + if (bFlip) // rotate display 180 + { + uc[0] = 0; // command + uc[1] = 0xa0; + _I2CWrite(pOLED,uc, 2); + uc[1] = 0xc0; + _I2CWrite(pOLED,uc, 2); + } + pOLED->oled_x = 128; // assume 128x64 + pOLED->oled_y = 64; + if (iType == OLED_96x16) + { + pOLED->oled_x = 96; + pOLED->oled_y = 16; + } + else if (iType == OLED_128x32) + pOLED->oled_y = 32; + else if (iType == OLED_128x128) + pOLED->oled_y = 128; + else if (iType == OLED_64x32) + { + pOLED->oled_x = 64; + pOLED->oled_y = 32; + } + else if (iType == OLED_72x40) + { + pOLED->oled_x = 72; + pOLED->oled_y = 40; + } + return rc; +} /* oledInit() */ +// +// Sends a command to turn on or off the OLED display +// +void oledPower(SSOLED *pOLED, uint8_t bOn) +{ + if (bOn) + oledWriteCommand(pOLED, 0xaf); // turn on OLED + else + oledWriteCommand(pOLED, 0xae); // turn off OLED +} /* oledPower() */ + +// Send a single byte command to the OLED controller +static void oledWriteCommand(SSOLED *pOLED, unsigned char c) +{ +unsigned char buf[2]; + + buf[0] = 0x00; // command introducer + buf[1] = c; + _I2CWrite(pOLED, buf, 2); +} /* oledWriteCommand() */ + +static void oledWriteCommand2(SSOLED *pOLED, unsigned char c, unsigned char d) +{ +unsigned char buf[3]; + + buf[0] = 0x00; + buf[1] = c; + buf[2] = d; + _I2CWrite(pOLED, buf, 3); +} /* oledWriteCommand2() */ + +// +// Sets the brightness (0=off, 255=brightest) +// +void oledSetContrast(SSOLED *pOLED, unsigned char ucContrast) +{ + oledWriteCommand2(pOLED, 0x81, ucContrast); +} /* oledSetContrast() */ +// +// Scroll the internal buffer by 1 scanline (up/down) +// width is in pixels, lines is group of 8 rows +// +int oledScrollBuffer(SSOLED *pOLED, int iStartCol, int iEndCol, int iStartRow, int iEndRow, int bUp) +{ + uint8_t b, *s; + int col, row; + + if (iStartCol < 0 || iStartCol > 127 || iEndCol < 0 || iEndCol > 127 || iStartCol > iEndCol) // invalid + return -1; + if (iStartRow < 0 || iStartRow > 7 || iEndRow < 0 || iEndRow > 7 || iStartRow > iEndRow) + return -1; + + if (bUp) + { + for (row=iStartRow; row<=iEndRow; row++) + { + s = &pOLED->ucScreen[(row * 128) + iStartCol]; + for (col=iStartCol; col<=iEndCol; col++) + { + b = *s; + b >>= 1; // scroll pixels 'up' + if (row < iEndRow) + b |= (s[128] << 7); // capture pixel of row below, except for last row + *s++ = b; + } // for col + } // for row + } // up + else // down + { + for (row=iEndRow; row>=iStartRow; row--) + { + s = &pOLED->ucScreen[(row * 128)+iStartCol]; + for (col=iStartCol; col<=iEndCol; col++) + { + b = *s; + b <<= 1; // scroll down + if (row > iStartRow) + b |= (s[-128] >> 7); // capture pixel of row above + *s++ = b; + } // for col + } // for row + } + return 0; +} /* oledScrollBuffer() */ +// +// Send commands to position the "cursor" (aka memory write address) +// to the given row and column +// +static void oledSetPosition(SSOLED *pOLED, int x, int y, int bRender) +{ +unsigned char buf[4]; + + pOLED->iScreenOffset = (y*128)+x; + if (!bRender) + return; // don't send the commands to the OLED if we're not rendering the graphics now + if (pOLED->oled_type == OLED_64x32) // visible display starts at column 32, row 4 + { + x += 32; // display is centered in VRAM, so this is always true + if (pOLED->oled_flip == 0) // non-flipped display starts from line 4 + y += 4; + } + else if (pOLED->oled_type == OLED_132x64) // SH1106 has 128 pixels centered in 132 + { + x += 2; + } + else if (pOLED->oled_type == OLED_96x16) // visible display starts at line 2 + { // mapping is a bit strange on the 96x16 OLED + if (pOLED->oled_flip) + x += 32; + else + y += 2; + } + else if (pOLED->oled_type == OLED_72x40) // starts at x=28,y=3 + { + x += 28; + if (!pOLED->oled_flip) + { + y += 3; + } + } + buf[0] = 0x00; // command introducer + buf[1] = 0xb0 | y; // set page to Y + buf[2] = x & 0xf; // lower column address + buf[3] = 0x10 | (x >> 4); // upper column addr + _I2CWrite(pOLED, buf, 4); +} /* oledSetPosition() */ + +// +// Write a block of pixel data to the OLED +// Length can be anything from 1 to 1024 (whole display) +// +static void oledWriteDataBlock(SSOLED *pOLED, unsigned char *ucBuf, int iLen, int bRender) +{ +unsigned char ucTemp[129]; + + ucTemp[0] = 0x40; // data command +// Copying the data has the benefit in SPI mode of not letting +// the original data get overwritten by the SPI.transfer() function + if (bRender) + { + memcpy(&ucTemp[1], ucBuf, iLen); + _I2CWrite(pOLED, ucTemp, iLen+1); + } + // Keep a copy in local buffer + if (pOLED->ucScreen) + { + memcpy(&pOLED->ucScreen[pOLED->iScreenOffset], ucBuf, iLen); + pOLED->iScreenOffset += iLen; + pOLED->iScreenOffset &= 1023; // we use a fixed stride of 128 no matter what the display size + } +} +// +// Byte operands for compressing the data +// The first 2 bits are the type, followed by the counts +#define OP_MASK 0xc0 +#define OP_SKIPCOPY 0x00 +#define OP_COPYSKIP 0x40 +#define OP_REPEATSKIP 0x80 +#define OP_REPEAT 0xc0 +// +// Write a block of flash memory to the display +// +void oledWriteFlashBlock(SSOLED *pOLED, uint8_t *s, int iLen) +{ +int j; +int iWidthMask = pOLED->oled_x -1; +int iSizeMask = ((pOLED->oled_x * pOLED->oled_y)/8) - 1; +int iWidthShift = (pOLED->oled_x == 128) ? 7:6; // assume 128 or 64 wide +uint8_t ucTemp[128]; + + while (((pOLED->iScreenOffset & iWidthMask) + iLen) >= pOLED->oled_x) // if it will hit the page end + { + j = pOLED->oled_x - (pOLED->iScreenOffset & iWidthMask); // amount we can write in one shot + memcpy(ucTemp, s, j); + oledWriteDataBlock(pOLED, ucTemp, j, 1); + s += j; + iLen -= j; + pOLED->iScreenOffset = (pOLED->iScreenOffset + j) & iSizeMask; + oledSetPosition(pOLED, pOLED->iScreenOffset & iWidthMask, (pOLED->iScreenOffset >> iWidthShift), 1); + } // while it needs some help + memcpy(ucTemp, s, iLen); + oledWriteDataBlock(pOLED, ucTemp, iLen, 1); + pOLED->iScreenOffset = (pOLED->iScreenOffset + iLen) & iSizeMask; +} /* oledWriteFlashBlock() */ + +// +// Write a repeating byte to the display +// +void oledRepeatByte(SSOLED *pOLED, uint8_t b, int iLen) +{ +int j; +int iWidthMask = pOLED->oled_x -1; +int iWidthShift = (pOLED->oled_x == 128) ? 7:6; // assume 128 or 64 pixels wide +int iSizeMask = ((pOLED->oled_x * pOLED->oled_y)/8) -1; +uint8_t ucTemp[128]; + + memset(ucTemp, b, (iLen > 128) ? 128:iLen); + while (((pOLED->iScreenOffset & iWidthMask) + iLen) >= pOLED->oled_x) // if it will hit the page end + { + j = pOLED->oled_x - (pOLED->iScreenOffset & iWidthMask); // amount we can write in one shot + oledWriteDataBlock(pOLED, ucTemp, j, 1); + iLen -= j; + pOLED->iScreenOffset = (pOLED->iScreenOffset + j) & iSizeMask; + oledSetPosition(pOLED, pOLED->iScreenOffset & iWidthMask, (pOLED->iScreenOffset >> iWidthShift), 1); + } // while it needs some help + oledWriteDataBlock(pOLED, ucTemp, iLen, 1); + pOLED->iScreenOffset += iLen; +} /* oledRepeatByte() */ + +// +// Play a frame of animation data +// The animation data is assumed to be encoded for a full frame of the display +// Given the pointer to the start of the compressed data, +// it returns the pointer to the start of the next frame +// Frame rate control is up to the calling program to manage +// When it finishes the last frame, it will start again from the beginning +// +uint8_t * oledPlayAnimFrame(SSOLED *pOLED, uint8_t *pAnimation, uint8_t *pCurrent, int iLen) +{ +uint8_t *s; +int i, j; +unsigned char b, bCode; +int iBufferSize = (pOLED->oled_x * pOLED->oled_y)/8; // size in bytes of the display devce +int iWidthMask, iWidthShift; + + iWidthMask = pOLED->oled_x - 1; + iWidthShift = (pOLED->oled_x == 128) ? 7:6; // 128 or 64 pixels wide + if (pCurrent == NULL || pCurrent > pAnimation + iLen) + return NULL; // invalid starting point + + s = (uint8_t *)pCurrent; // start of animation data + i = 0; + oledSetPosition(pOLED, 0,0,1); + while (i < iBufferSize) // run one frame + { + bCode = pgm_read_byte(s++); + switch (bCode & OP_MASK) // different compression types + { + case OP_SKIPCOPY: // skip/copy + if (bCode == OP_SKIPCOPY) // big skip + { + b = pgm_read_byte(s++); + i += b + 1; + oledSetPosition(pOLED, i & iWidthMask, (i >> iWidthShift), 1); + } + else // skip/copy + { + if (bCode & 0x38) + { + i += ((bCode & 0x38) >> 3); // skip amount + oledSetPosition(pOLED, i & iWidthMask, (i >> iWidthShift), 1); + } + if (bCode & 7) + { + oledWriteFlashBlock(pOLED, s, bCode & 7); + s += (bCode & 7); + i += bCode & 7; + } + } + break; + case OP_COPYSKIP: // copy/skip + if (bCode == OP_COPYSKIP) // big copy + { + b = pgm_read_byte(s++); + j = b + 1; + oledWriteFlashBlock(pOLED, s, j); + s += j; + i += j; + } + else + { + j = ((bCode & 0x38) >> 3); + if (j) + { + oledWriteFlashBlock(pOLED, s, j); + s += j; + i += j; + } + if (bCode & 7) + { + i += (bCode & 7); // skip + oledSetPosition(pOLED, i & iWidthMask, (i >> iWidthShift), 1); + } + } + break; + case OP_REPEATSKIP: // repeat/skip + j = (bCode & 0x38) >> 3; // repeat count + b = pgm_read_byte(s++); + oledRepeatByte(pOLED, b, j); + i += j; + if (bCode & 7) + { + i += (bCode & 7); // skip amount + oledSetPosition(pOLED, i & iWidthMask, (i >> iWidthShift), 1); + } + break; + + case OP_REPEAT: + j = (bCode & 0x3f) + 1; + b = pgm_read_byte(s++); + oledRepeatByte(pOLED, b, j); + i += j; + break; + } // switch on code type + } // while rendering a frame + if (s >= pAnimation + iLen) // we've hit the end, restart from the beginning + s = pAnimation; + return s; // return pointer to start of next frame +} /* oledPlayAnimFrame() */ +// +// Draw a sprite of any size in any position +// If it goes beyond the left/right or top/bottom edges +// it's trimmed to show the valid parts +// This function requires a back buffer to be defined +// The priority color (0 or 1) determines which color is painted +// when a 1 is encountered in the source image. +// +void oledDrawSprite(SSOLED *pOLED, uint8_t *pSprite, int cx, int cy, int iPitch, int x, int y, uint8_t iPriority) +{ + int tx, ty, dx, dy, iStartX; + uint8_t *s, *d, uc, pix, ucSrcMask, ucDstMask; + + if (x+cx < 0 || y+cy < 0 || x >= pOLED->oled_x || y >= pOLED->oled_y || pOLED->ucScreen == NULL) + return; // no backbuffer or out of bounds + dy = y; // destination y + if (y < 0) // skip the invisible parts + { + cy += y; + y = -y; + pSprite += (y * iPitch); + dy = 0; + } + if (y + cy > pOLED->oled_y) + cy = pOLED->oled_y - y; + iStartX = 0; + dx = x; + if (x < 0) + { + cx += x; + x = -x; + iStartX = x; + dx = 0; + } + if (x + cx > pOLED->oled_x) + cx = pOLED->oled_x - x; + for (ty=0; ty<cy; ty++) + { + s = &pSprite[iStartX >> 3]; + d = &pOLED->ucScreen[(dy>>3) * pOLED->oled_x + dx]; + ucSrcMask = 0x80 >> (iStartX & 7); + pix = *s++; + ucDstMask = 1 << (dy & 7); + if (iPriority) // priority color is 1 + { + for (tx=0; tx<cx; tx++) + { + uc = d[0]; + if (pix & ucSrcMask) // set pixel in source, set it in dest + d[0] = (uc | ucDstMask); + d++; // next pixel column + ucSrcMask >>= 1; + if (ucSrcMask == 0) // read next byte + { + ucSrcMask = 0x80; + pix = *s++; + } + } // for tx + } // priorty color 1 + else + { + for (tx=0; tx<cx; tx++) + { + uc = d[0]; + if (pix & ucSrcMask) // clr pixel in source, clr it in dest + d[0] = (uc & ~ucDstMask); + d++; // next pixel column + ucSrcMask >>= 1; + if (ucSrcMask == 0) // read next byte + { + ucSrcMask = 0x80; + pix = *s++; + } + } // for tx + } // priority color 0 + dy++; + pSprite += iPitch; + } // for ty +} /* oledDrawSprite() */ +// +// Draw a 16x16 tile in any of 4 rotated positions +// Assumes input image is laid out like "normal" graphics with +// the MSB on the left and 2 bytes per line +// On AVR, the source image is assumed to be in FLASH memory +// The function can draw the tile on byte boundaries, so the x value +// can be from 0 to 112 and y can be from 0 to 6 +// +void oledDrawTile(SSOLED *pOLED, const uint8_t *pTile, int x, int y, int iRotation, int bInvert, int bRender) +{ + uint8_t ucTemp[32]; // prepare LCD data here + uint8_t i, j, k, iOffset, ucMask, uc, ucPixels; + uint8_t bFlipX=0, bFlipY=0; + + if (x < 0 || y < 0 || y > 6 || x > 112) + return; // out of bounds + if (pTile == NULL) return; // bad pointer; really? :( + if (iRotation == ANGLE_180 || iRotation == ANGLE_270 || iRotation == ANGLE_FLIPX) + bFlipX = 1; + if (iRotation == ANGLE_180 || iRotation == ANGLE_270 || iRotation == ANGLE_FLIPY) + bFlipY = 1; + + memset(ucTemp, 0, sizeof(ucTemp)); // we only set white pixels, so start from black + if (iRotation == ANGLE_0 || iRotation == ANGLE_180 || iRotation == ANGLE_FLIPX || iRotation == ANGLE_FLIPY) + { + for (j=0; j<16; j++) // y + { + for (i=0; i<16; i+=8) // x + { + ucPixels = pgm_read_byte((uint8_t*)pTile++); + ucMask = 0x80; // MSB is the first source pixel + for (k=0; k<8; k++) + { + if (ucPixels & ucMask) // translate the pixel + { + if (bFlipY) + uc = 0x80 >> (j & 7); + else + uc = 1 << (j & 7); + iOffset = i+k; + if (bFlipX) iOffset = 15-iOffset; + iOffset += (j & 8)<<1; // top/bottom half of output + if (bFlipY) + iOffset ^= 16; + ucTemp[iOffset] |= uc; + } + ucMask >>= 1; + } // for k + } // for i + } // for j + } + else // rotated 90/270 + { + for (j=0; j<16; j++) // y + { + for (i=0; i<16; i+=8) // x + { + ucPixels = pgm_read_byte((uint8_t*)pTile++); + ucMask = 0x80; // MSB is the first source pixel + for (k=0; k<8; k++) + { + if (ucPixels & ucMask) // translate the pixel + { + if (bFlipY) + uc = 0x80 >> k; + else + uc = 1 << k; + iOffset = 15-j; + if (bFlipX) iOffset = 15-iOffset; + iOffset += i<<1; // top/bottom half of output + if (bFlipY) + iOffset ^= 16; + ucTemp[iOffset] |= uc; + } + ucMask >>= 1; + } // for k + } // for i + } // for j + } + if (bInvert) InvertBytes(ucTemp, 32); + // Send the data to the display + oledSetPosition(pOLED, x, y, bRender); + oledWriteDataBlock(pOLED, ucTemp, 16, bRender); // top half + oledSetPosition(pOLED, x,y+1, bRender); + oledWriteDataBlock(pOLED, &ucTemp[16], 16, bRender); // bottom half +} /* oledDrawTile() */ + +// Set (or clear) an individual pixel +// The local copy of the frame buffer is used to avoid +// reading data from the display controller +int oledSetPixel(SSOLED *pOLED, int x, int y, unsigned char ucColor, int bRender) +{ +int i; +unsigned char uc, ucOld; + + i = ((y >> 3) * 128) + x; + if (i < 0 || i > 1023) // off the screen + return -1; + oledSetPosition(pOLED, x, y>>3, bRender); + + if (pOLED->ucScreen) + uc = ucOld = pOLED->ucScreen[i]; + else if (pOLED->oled_type == OLED_132x64 || pOLED->oled_type == OLED_128x128) // SH1106/SH1107 can read data + { + uint8_t ucTemp[3]; + ucTemp[0] = 0x80; // one command + ucTemp[1] = 0xE0; // read_modify_write + ucTemp[2] = 0xC0; // one data + _I2CWrite(pOLED, ucTemp, 3); + + // read a dummy byte followed by the data byte we want + I2CRead(&pOLED->bbi2c, pOLED->oled_addr, ucTemp, 2); + uc = ucOld = ucTemp[1]; // first byte is garbage + } + else + uc = ucOld = 0; + + uc &= ~(0x1 << (y & 7)); + if (ucColor) + { + uc |= (0x1 << (y & 7)); + } + if (uc != ucOld) // pixel changed + { +// oledSetPosition(x, y>>3); + if (pOLED->ucScreen) + { + oledWriteDataBlock(pOLED, &uc, 1, bRender); + pOLED->ucScreen[i] = uc; + } + else if (pOLED->oled_type == OLED_132x64 || pOLED->oled_type == OLED_128x128) // end the read_modify_write operation + { + uint8_t ucTemp[4]; + ucTemp[0] = 0xc0; // one data + ucTemp[1] = uc; // actual data + ucTemp[2] = 0x80; // one command + ucTemp[3] = 0xEE; // end read_modify_write operation + _I2CWrite(pOLED, ucTemp, 4); + } + } + return 0; +} /* oledSetPixel() */ + +// +// Invert font data +// +void InvertBytes(uint8_t *pData, uint8_t bLen) +{ +uint8_t i; + for (i=0; i<bLen; i++) + { + *pData = ~(*pData); + pData++; + } +} /* InvertBytes() */ + +// +// Load a 128x64 1-bpp Windows bitmap +// Pass the pointer to the beginning of the BMP file +// First pass version assumes a full screen bitmap +// +int oledLoadBMP(SSOLED *pOLED, uint8_t *pBMP, int bInvert, int bRender) +{ +int16_t i16; +int iOffBits, q, y, j; // offset to bitmap data +int iPitch; +uint8_t x, z, b, *s; +uint8_t dst_mask; +uint8_t ucTemp[16]; // process 16 bytes at a time +uint8_t bFlipped = false; + + i16 = pgm_read_word(pBMP); + if (i16 != 0x4d42) // must start with 'BM' + return -1; // not a BMP file + i16 = pgm_read_word(pBMP + 18); + if (i16 != 128) // must be 128 pixels wide + return -1; + i16 = pgm_read_word(pBMP + 22); + if (i16 != 64 && i16 != -64) // must be 64 pixels tall + return -1; + if (i16 == 64) // BMP is flipped vertically (typical) + bFlipped = true; + i16 = pgm_read_word(pBMP + 28); + if (i16 != 1) // must be 1 bit per pixel + return -1; + iOffBits = pgm_read_word(pBMP + 10); + iPitch = 16; + if (bFlipped) + { + iPitch = -16; + iOffBits += (63 * 16); // start from bottom + } + +// rotate the data and send it to the display + for (y=0; y<8; y++) // 8 lines of 8 pixels + { + oledSetPosition(pOLED, 0, y, bRender); + for (j=0; j<8; j++) // do 8 sections of 16 columns + { + s = &pBMP[iOffBits + (j*2) + (y * iPitch*8)]; // source line + memset(ucTemp, 0, 16); // start with all black + for (x=0; x<16; x+=8) // do each block of 16x8 pixels + { + dst_mask = 1; + for (q=0; q<8; q++) // gather 8 rows + { + b = pgm_read_byte(s + (q * iPitch)); + for (z=0; z<8; z++) // gather up the 8 bits of this column + { + if (b & 0x80) + ucTemp[x+z] |= dst_mask; + b <<= 1; + } // for z + dst_mask <<= 1; + } // for q + s++; // next source byte + } // for x + if (bInvert) InvertBytes(ucTemp, 16); + oledWriteDataBlock(pOLED, ucTemp, 16, bRender); + } // for j + } // for y + return 0; +} /* oledLoadBMP() */ +// +// Set the current cursor position +// The column represents the pixel column (0-127) +// The row represents the text row (0-7) +// +void oledSetCursor(SSOLED *pOLED, int x, int y) +{ + pOLED->iCursorX = x; + pOLED->iCursorY = y; +} /* oledSetCursor() */ +// +// Turn text wrap on or off for the oldWriteString() function +// +void oledSetTextWrap(SSOLED *pOLED, int bWrap) +{ + pOLED->oled_wrap = bWrap; +} /* oledSetTextWrap() */ +// +// Draw a string of normal (8x8), small (6x8) or large (16x32) characters +// At the given col+row +// +int oledWriteString(SSOLED *pOLED, int iScroll, int x, int y, char *szMsg, int iSize, int bInvert, int bRender) +{ +int i, iFontOff, iLen, iFontSkip; +unsigned char c, *s, ucTemp[40]; + + if (x == -1 || y == -1) // use the cursor position + { + x = pOLED->iCursorX; y = pOLED->iCursorY; + } + else + { + pOLED->iCursorX = x; pOLED->iCursorY = y; // set the new cursor position + } + if (pOLED->iCursorX >= pOLED->oled_x || pOLED->iCursorY >= pOLED->oled_y / 8) + return -1; // can't draw off the display + + oledSetPosition(pOLED, pOLED->iCursorX, pOLED->iCursorY, bRender); + if (iSize == FONT_8x8) // 8x8 font + { + i = 0; + iFontSkip = iScroll & 7; // number of columns to initially skip + while (pOLED->iCursorX < pOLED->oled_x && szMsg[i] != 0 && pOLED->iCursorY < pOLED->oled_y / 8) + { + if (iScroll < 8) // only display visible characters + { + c = (unsigned char)szMsg[i]; + iFontOff = (int)(c-32) * 7; + // we can't directly use the pointer to FLASH memory, so copy to a local buffer + ucTemp[0] = 0; + memcpy(&ucTemp[1], &ucFont[iFontOff], 7); + if (bInvert) InvertBytes(ucTemp, 8); + // oledCachedWrite(ucTemp, 8); + iLen = 8 - iFontSkip; + if (pOLED->iCursorX + iLen > pOLED->oled_x) // clip right edge + iLen = pOLED->oled_x - pOLED->iCursorX; + oledWriteDataBlock(pOLED, &ucTemp[iFontSkip], iLen, bRender); // write character pattern + pOLED->iCursorX += iLen; + if (pOLED->iCursorX >= pOLED->oled_x-7 && pOLED->oled_wrap) // word wrap enabled? + { + pOLED->iCursorX = 0; // start at the beginning of the next line + pOLED->iCursorY++; + oledSetPosition(pOLED, pOLED->iCursorX, pOLED->iCursorY, bRender); + } + iFontSkip = 0; + } + iScroll -= 8; + i++; + } // while +// oledCachedFlush(); // write any remaining data + return 0; + } // 8x8 +#ifndef __AVR__ + else if (iSize == FONT_16x32) // 16x32 font + { + i = 0; + iFontSkip = iScroll & 15; // number of columns to initially skip + while (pOLED->iCursorX < pOLED->oled_x && pOLED->iCursorY < (pOLED->oled_y / 8)-3 && szMsg[i] != 0) + { + if (iScroll < 16) // if characters are visible + { + s = (unsigned char *)&ucBigFont[(unsigned char)(szMsg[i]-32)*64]; + iLen = 16 - iFontSkip; + if (pOLED->iCursorX + iLen > pOLED->oled_x) // clip right edge + iLen = pOLED->oled_x - pOLED->iCursorX; + // we can't directly use the pointer to FLASH memory, so copy to a local buffer + oledSetPosition(pOLED, pOLED->iCursorX, pOLED->iCursorY, bRender); + memcpy(ucTemp, s, 16); + if (bInvert) InvertBytes(ucTemp, 16); + oledWriteDataBlock(pOLED, &ucTemp[iFontSkip], iLen, bRender); // write character pattern + oledSetPosition(pOLED, pOLED->iCursorX, pOLED->iCursorY+1, bRender); + memcpy(ucTemp, s+16, 16); + if (bInvert) InvertBytes(ucTemp, 16); + oledWriteDataBlock(pOLED, &ucTemp[iFontSkip], iLen, bRender); // write character pattern + if (pOLED->iCursorY <= 5) + { + oledSetPosition(pOLED, pOLED->iCursorX, pOLED->iCursorY+2, bRender); + memcpy(ucTemp, s+32, 16); + if (bInvert) InvertBytes(ucTemp, 16); + oledWriteDataBlock(pOLED, &ucTemp[iFontSkip], iLen, bRender); // write character pattern + } + if (pOLED->iCursorY <= 4) + { + oledSetPosition(pOLED, pOLED->iCursorX, pOLED->iCursorY+3, bRender); + memcpy(ucTemp, s+48, 16); + if (bInvert) InvertBytes(ucTemp, 16); + oledWriteDataBlock(pOLED, &ucTemp[iFontSkip], iLen, bRender); // write character pattern + } + pOLED->iCursorX += iLen; + if (pOLED->iCursorX >= pOLED->oled_x-15 && pOLED->oled_wrap) // word wrap enabled? + { + pOLED->iCursorX = 0; // start at the beginning of the next line + pOLED->iCursorY+=4; + } + iFontSkip = 0; + } // if character visible from scrolling + iScroll -= 16; + i++; + } // while + return 0; + } // 16x32 +#endif // !__AVR__ + else if (iSize == FONT_12x16) // 6x8 stretched to 12x16 + { + i = 0; + iFontSkip = iScroll % 12; // number of columns to initially skip + while (pOLED->iCursorX < pOLED->oled_x && pOLED->iCursorY < (pOLED->oled_y/8)-1 && szMsg[i] != 0) + { +// stretch the 'normal' font instead of using the big font + if (iScroll < 12) // if characters are visible + { + int tx, ty; + c = szMsg[i] - 32; + unsigned char uc1, uc2, ucMask, *pDest; + s = (unsigned char *)&ucSmallFont[(int)c*5]; + ucTemp[0] = 0; // first column is blank + memcpy(&ucTemp[1], s, 6); + if (bInvert) + InvertBytes(ucTemp, 6); + // Stretch the font to double width + double height + memset(&ucTemp[6], 0, 24); // write 24 new bytes + for (tx=0; tx<6; tx++) + { + ucMask = 3; + pDest = &ucTemp[6+tx*2]; + uc1 = uc2 = 0; + c = ucTemp[tx]; + for (ty=0; ty<4; ty++) + { + if (c & (1 << ty)) // a bit is set + uc1 |= ucMask; + if (c & (1 << (ty + 4))) + uc2 |= ucMask; + ucMask <<= 2; + } + pDest[0] = uc1; + pDest[1] = uc1; // double width + pDest[12] = uc2; + pDest[13] = uc2; + } + // smooth the diagonal lines + for (tx=0; tx<5; tx++) + { + uint8_t c0, c1, ucMask2; + c0 = ucTemp[tx]; + c1 = ucTemp[tx+1]; + pDest = &ucTemp[6+tx*2]; + ucMask = 1; + ucMask2 = 2; + for (ty=0; ty<7; ty++) + { + if (((c0 & ucMask) && !(c1 & ucMask) && !(c0 & ucMask2) && (c1 & ucMask2)) || (!(c0 & ucMask) && (c1 & ucMask) && (c0 & ucMask2) && !(c1 & ucMask2))) + { + if (ty < 3) // top half + { + pDest[1] |= (1 << ((ty * 2)+1)); + pDest[2] |= (1 << ((ty * 2)+1)); + pDest[1] |= (1 << ((ty+1) * 2)); + pDest[2] |= (1 << ((ty+1) * 2)); + } + else if (ty == 3) // on the border + { + pDest[1] |= 0x80; pDest[2] |= 0x80; + pDest[13] |= 1; pDest[14] |= 1; + } + else // bottom half + { + pDest[13] |= (1 << (2*(ty-4)+1)); + pDest[14] |= (1 << (2*(ty-4)+1)); + pDest[13] |= (1 << ((ty-3) * 2)); + pDest[14] |= (1 << ((ty-3) * 2)); + } + } + else if (!(c0 & ucMask) && (c1 & ucMask) && (c0 & ucMask2) && !(c1 & ucMask2)) + { + if (ty < 4) // top half + { + pDest[1] |= (1 << ((ty * 2)+1)); + pDest[2] |= (1 << ((ty+1) * 2)); + } + else + { + pDest[13] |= (1 << (2*(ty-4)+1)); + pDest[14] |= (1 << ((ty-3) * 2)); + } + } + ucMask <<= 1; ucMask2 <<= 1; + } + } + iLen = 12 - iFontSkip; + if (pOLED->iCursorX + iLen > pOLED->oled_x) // clip right edge + iLen = pOLED->oled_x - pOLED->iCursorX; + oledSetPosition(pOLED, pOLED->iCursorX, pOLED->iCursorY, bRender); + oledWriteDataBlock(pOLED, &ucTemp[6+iFontSkip], iLen, bRender); + oledSetPosition(pOLED, pOLED->iCursorX, pOLED->iCursorY+1, bRender); + oledWriteDataBlock(pOLED, &ucTemp[18+iFontSkip], iLen, bRender); + pOLED->iCursorX += iLen; + if (pOLED->iCursorX >= pOLED->oled_x-11 && pOLED->oled_wrap) // word wrap enabled? + { + pOLED->iCursorX = 0; // start at the beginning of the next line + pOLED->iCursorY += 2; + oledSetPosition(pOLED, pOLED->iCursorX, pOLED->iCursorY, bRender); + } + iFontSkip = 0; + } // if characters are visible + iScroll -= 12; + i++; + } // while + return 0; + } // 12x16 + else if (iSize == FONT_16x16) // 8x8 stretched to 16x16 + { + i = 0; + iFontSkip = iScroll & 15; // number of columns to initially skip + while (pOLED->iCursorX < pOLED->oled_x && pOLED->iCursorY < (pOLED->oled_y/8)-1 && szMsg[i] != 0) + { +// stretch the 'normal' font instead of using the big font + if (iScroll < 16) // if characters are visible + { + int tx, ty; + c = szMsg[i] - 32; + unsigned char uc1, uc2, ucMask, *pDest; + s = (unsigned char *)&ucFont[(int)c*7]; + ucTemp[0] = 0; + memcpy(&ucTemp[1], s, 7); + if (bInvert) + InvertBytes(ucTemp, 8); + // Stretch the font to double width + double height + memset(&ucTemp[8], 0, 32); // write 32 new bytes + for (tx=0; tx<8; tx++) + { + ucMask = 3; + pDest = &ucTemp[8+tx*2]; + uc1 = uc2 = 0; + c = ucTemp[tx]; + for (ty=0; ty<4; ty++) + { + if (c & (1 << ty)) // a bit is set + uc1 |= ucMask; + if (c & (1 << (ty + 4))) + uc2 |= ucMask; + ucMask <<= 2; + } + pDest[0] = uc1; + pDest[1] = uc1; // double width + pDest[16] = uc2; + pDest[17] = uc2; + } + iLen = 16 - iFontSkip; + if (pOLED->iCursorX + iLen > pOLED->oled_x) // clip right edge + iLen = pOLED->oled_x - pOLED->iCursorX; + oledSetPosition(pOLED, pOLED->iCursorX, pOLED->iCursorY, bRender); + oledWriteDataBlock(pOLED, &ucTemp[8+iFontSkip], iLen, bRender); + oledSetPosition(pOLED, pOLED->iCursorX, pOLED->iCursorY+1, bRender); + oledWriteDataBlock(pOLED, &ucTemp[24+iFontSkip], iLen, bRender); + pOLED->iCursorX += iLen; + if (pOLED->iCursorX >= pOLED->oled_x-15 && pOLED->oled_wrap) // word wrap enabled? + { + pOLED->iCursorX = 0; // start at the beginning of the next line + pOLED->iCursorY += 2; + oledSetPosition(pOLED, pOLED->iCursorX, pOLED->iCursorY, bRender); + } + iFontSkip = 0; + } // if characters are visible + iScroll -= 16; + i++; + } // while + return 0; + } // 16x16 + else if (iSize == FONT_6x8) // 6x8 font + { + i = 0; + iFontSkip = iScroll % 6; + while (pOLED->iCursorX < pOLED->oled_x && pOLED->iCursorY < (pOLED->oled_y/8) && szMsg[i] != 0) + { + if (iScroll < 6) // if characters are visible + { + c = szMsg[i] - 32; + // we can't directly use the pointer to FLASH memory, so copy to a local buffer + ucTemp[0] = 0; + memcpy(&ucTemp[1], &ucSmallFont[(int)c*5], 5); + if (bInvert) InvertBytes(ucTemp, 6); + iLen = 6 - iFontSkip; + if (pOLED->iCursorX + iLen > pOLED->oled_x) // clip right edge + iLen = pOLED->oled_x - pOLED->iCursorX; + oledWriteDataBlock(pOLED, &ucTemp[iFontSkip], iLen, bRender); // write character pattern + // oledCachedWrite(ucTemp, 6); + pOLED->iCursorX += iLen; + iFontSkip = 0; + if (pOLED->iCursorX >= pOLED->oled_x-5 && pOLED->oled_wrap) // word wrap enabled? + { + pOLED->iCursorX = 0; // start at the beginning of the next line + pOLED->iCursorY++; + oledSetPosition(pOLED, pOLED->iCursorX, pOLED->iCursorY, bRender); + } + } // if characters are visible + iScroll -= 6; + i++; + } +// oledCachedFlush(); // write any remaining data + return 0; + } // 6x8 + return -1; // invalid size +} /* oledWriteString() */ + +// +// Render a sprite/rectangle of pixels from a provided buffer to the display. +// The row values refer to byte rows, not pixel rows due to the memory +// layout of OLEDs. +// returns 0 for success, -1 for invalid parameter +// +int oledDrawGFX(SSOLED *pOLED, uint8_t *pBuffer, int iSrcCol, int iSrcRow, int iDestCol, int iDestRow, int iWidth, int iHeight, int iSrcPitch) +{ + int y; + + if (iSrcCol < 0 || iSrcCol > 127 || iSrcRow < 0 || iSrcRow > 7 || iDestCol < 0 || iDestCol >= pOLED->oled_x || iDestRow < 0 || iDestRow >= (pOLED->oled_y >> 3) || iSrcPitch <= 0) + return -1; // invalid + + for (y=iSrcRow; y<iSrcRow+iHeight; y++) + { + uint8_t *s = &pBuffer[(y * iSrcPitch)+iSrcCol]; + oledSetPosition(pOLED, iDestCol, iDestRow, 1); + oledWriteDataBlock(pOLED, s, iWidth, 1); + pBuffer += iSrcPitch; + iDestRow++; + } // for y + return 0; +} /* oledDrawGFX() */ +// +// Dump a screen's worth of data directly to the display +// Try to speed it up by comparing the new bytes with the existing buffer +// +void oledDumpBuffer(SSOLED *pOLED, uint8_t *pBuffer) +{ +int x, y; +int iLines, iCols; +uint8_t bNeedPos; +uint8_t *pSrc = pOLED->ucScreen; + + if (pBuffer == NULL) // dump the internal buffer if none is given + pBuffer = pOLED->ucScreen; + if (pBuffer == NULL) + return; // no backbuffer and no provided buffer + + iLines = pOLED->oled_y >> 3; + iCols = pOLED->oled_x >> 4; + for (y=0; y<iLines; y++) + { + bNeedPos = 1; // start of a new line means we need to set the position too + for (x=0; x<iCols; x++) // wiring library has a 32-byte buffer, so send 16 bytes so that the data prefix (0x40) can fit + { + if (pOLED->ucScreen == NULL || pBuffer == pSrc || memcmp(pSrc, pBuffer, 16) != 0) // doesn't match, need to send it + { + if (bNeedPos) // need to reposition output cursor? + { + bNeedPos = 0; + oledSetPosition(pOLED, x*16, y, 1); + } + oledWriteDataBlock(pOLED, pBuffer, 16, 1); + } + else + { + bNeedPos = 1; // we're skipping a block, so next time will need to set the new position + } + pSrc += 16; + pBuffer += 16; + } // for x + pSrc += (128 - pOLED->oled_x); // for narrow displays, skip to the next line + pBuffer += (128 - pOLED->oled_x); + } // for y +} /* oledDumpBuffer() */ +// +// Fill the frame buffer with a byte pattern +// e.g. all off (0x00) or all on (0xff) +// +void oledFill(SSOLED *pOLED, unsigned char ucData, int bRender) +{ +uint8_t x, y; +uint8_t iLines, iCols; +unsigned char temp[16]; + + iLines = pOLED->oled_y >> 3; + iCols = pOLED->oled_x >> 4; + memset(temp, ucData, 16); + pOLED->iCursorX = pOLED->iCursorY = 0; + + for (y=0; y<iLines; y++) + { + oledSetPosition(pOLED, 0,y, bRender); // set to (0,Y) + for (x=0; x<iCols; x++) // wiring library has a 32-byte buffer, so send 16 bytes so that the data prefix (0x40) can fit + { + oledWriteDataBlock(pOLED, temp, 16, bRender); + } // for x + // 72 isn't evenly divisible by 16, so fix it + if (pOLED->oled_type == OLED_72x40) + oledWriteDataBlock(pOLED, temp, 8, bRender); + } // for y + if (pOLED->ucScreen) + memset(pOLED->ucScreen, ucData, (pOLED->oled_x * pOLED->oled_y)/8); +} /* oledFill() */ + +// +// Provide or revoke a back buffer for your OLED graphics +// This allows you to manage the RAM used by ss_oled on tiny +// embedded platforms like the ATmega series +// Pass NULL to revoke the buffer. Make sure you provide a buffer +// large enough for your display (e.g. 128x64 needs 1K - 1024 bytes) +// +void oledSetBackBuffer(SSOLED *pOLED, uint8_t *pBuffer) +{ + pOLED->ucScreen = pBuffer; +} /* oledSetBackBuffer() */ + +void oledDrawLine(SSOLED *pOLED, int x1, int y1, int x2, int y2, int bRender) +{ + int temp; + int dx = x2 - x1; + int dy = y2 - y1; + int error; + uint8_t *p, *pStart, mask, bOld, bNew; + int xinc, yinc; + int y, x; + + if (x1 < 0 || x2 < 0 || y1 < 0 || y2 < 0 || x1 >= pOLED->oled_x || x2 >= pOLED->oled_x || y1 >= pOLED->oled_y || y2 >= pOLED->oled_y) + return; + + if(abs(dx) > abs(dy)) { + // X major case + if(x2 < x1) { + dx = -dx; + temp = x1; + x1 = x2; + x2 = temp; + temp = y1; + y1 = y2; + y2 = temp; + } + + y = y1; + dy = (y2 - y1); + error = dx >> 1; + yinc = 1; + if (dy < 0) + { + dy = -dy; + yinc = -1; + } + p = pStart = &pOLED->ucScreen[x1 + ((y >> 3) << 7)]; // point to current spot in back buffer + mask = 1 << (y & 7); // current bit offset + for(x=x1; x1 <= x2; x1++) { + *p++ |= mask; // set pixel and increment x pointer + error -= dy; + if (error < 0) + { + error += dx; + if (yinc > 0) + mask <<= 1; + else + mask >>= 1; + if (mask == 0) // we've moved outside the current row, write the data we changed + { + oledSetPosition(pOLED, x, y>>3, bRender); + oledWriteDataBlock(pOLED, pStart, (int)(p-pStart), bRender); // write the row we changed + x = x1+1; // we've already written the byte at x1 + y1 = y+yinc; + p += (yinc > 0) ? 128 : -128; + pStart = p; + mask = 1 << (y1 & 7); + } + y += yinc; + } + } // for x1 + if (p != pStart) // some data needs to be written + { + oledSetPosition(pOLED, x, y>>3, bRender); + oledWriteDataBlock(pOLED, pStart, (int)(p-pStart), bRender); + } + } + else { + // Y major case + if(y1 > y2) { + dy = -dy; + temp = x1; + x1 = x2; + x2 = temp; + temp = y1; + y1 = y2; + y2 = temp; + } + + p = &pOLED->ucScreen[x1 + ((y1 >> 3) * 128)]; // point to current spot in back buffer + bOld = bNew = p[0]; // current data at that address + mask = 1 << (y1 & 7); // current bit offset + dx = (x2 - x1); + error = dy >> 1; + xinc = 1; + if (dx < 0) + { + dx = -dx; + xinc = -1; + } + for(x = x1; y1 <= y2; y1++) { + bNew |= mask; // set the pixel + error -= dx; + mask <<= 1; // y1++ + if (mask == 0) // we're done with this byte, write it if necessary + { + if (bOld != bNew) + { + p[0] = bNew; // save to RAM + oledSetPosition(pOLED, x, y1>>3, bRender); + oledWriteDataBlock(pOLED, &bNew, 1, bRender); + } + p += 128; // next line + bOld = bNew = p[0]; + mask = 1; // start at LSB again + } + if (error < 0) + { + error += dy; + if (bOld != bNew) // write the last byte we modified if it changed + { + p[0] = bNew; // save to RAM + oledSetPosition(pOLED, x, y1>>3, bRender); + oledWriteDataBlock(pOLED, &bNew, 1, bRender); + } + p += xinc; + x += xinc; + bOld = bNew = p[0]; + } + } // for y + if (bOld != bNew) // write the last byte we modified if it changed + { + p[0] = bNew; // save to RAM + oledSetPosition(pOLED, x, y2>>3, bRender); + oledWriteDataBlock(pOLED, &bNew, 1, bRender); + } + } // y major case +} /* oledDrawLine() */ + +// +// For drawing ellipses, a circle is drawn and the x and y pixels are scaled by a 16-bit integer fraction +// This function draws a single pixel and scales its position based on the x/y fraction of the ellipse +// +static void DrawScaledPixel(SSOLED *pOLED, int iCX, int iCY, int x, int y, int32_t iXFrac, int32_t iYFrac, uint8_t ucColor) +{ + uint8_t *d, ucMask; + + if (iXFrac != 0x10000) x = ((x * iXFrac) >> 16); + if (iYFrac != 0x10000) y = ((y * iYFrac) >> 16); + x += iCX; y += iCY; + if (x < 0 || x >= pOLED->oled_x || y < 0 || y >= pOLED->oled_y) + return; // off the screen + d = &pOLED->ucScreen[((y >> 3)*128) + x]; + ucMask = 1 << (y & 7); + if (ucColor) + *d |= ucMask; + else + *d &= ~ucMask; +} /* DrawScaledPixel() */ +// +// For drawing filled ellipses +// +static void DrawScaledLine(SSOLED *pOLED, int iCX, int iCY, int x, int y, int32_t iXFrac, int32_t iYFrac, uint8_t ucColor) +{ + int iLen, x2; + uint8_t *d, ucMask; + if (iXFrac != 0x10000) x = ((x * iXFrac) >> 16); + if (iYFrac != 0x10000) y = ((y * iYFrac) >> 16); + iLen = x*2; + x = iCX - x; y += iCY; + x2 = x + iLen; + if (y < 0 || y >= pOLED->oled_y) + return; // completely off the screen + if (x < 0) x = 0; + if (x2 >= pOLED->oled_x) x2 = pOLED->oled_x-1; + iLen = x2 - x + 1; // new length + d = &pOLED->ucScreen[((y >> 3)*128) + x]; + ucMask = 1 << (y & 7); + if (ucColor) // white + { + for (; iLen > 0; iLen--) + *d++ |= ucMask; + } + else // black + { + for (; iLen > 0; iLen--) + *d++ &= ~ucMask; + } +} /* DrawScaledLine() */ +// +// Draw the 8 pixels around the Bresenham circle +// (scaled to make an ellipse) +// +static void BresenhamCircle(SSOLED *pOLED, int iCX, int iCY, int x, int y, int32_t iXFrac, int32_t iYFrac, uint8_t ucColor, uint8_t bFill) +{ + if (bFill) // draw a filled ellipse + { + // for a filled ellipse, draw 4 lines instead of 8 pixels + DrawScaledLine(pOLED, iCX, iCY, x, y, iXFrac, iYFrac, ucColor); + DrawScaledLine(pOLED, iCX, iCY, x, -y, iXFrac, iYFrac, ucColor); + DrawScaledLine(pOLED, iCX, iCY, y, x, iXFrac, iYFrac, ucColor); + DrawScaledLine(pOLED, iCX, iCY, y, -x, iXFrac, iYFrac, ucColor); + } + else // draw 8 pixels around the edges + { + DrawScaledPixel(pOLED, iCX, iCY, x, y, iXFrac, iYFrac, ucColor); + DrawScaledPixel(pOLED, iCX, iCY, -x, y, iXFrac, iYFrac, ucColor); + DrawScaledPixel(pOLED, iCX, iCY, x, -y, iXFrac, iYFrac, ucColor); + DrawScaledPixel(pOLED, iCX, iCY, -x, -y, iXFrac, iYFrac, ucColor); + DrawScaledPixel(pOLED, iCX, iCY, y, x, iXFrac, iYFrac, ucColor); + DrawScaledPixel(pOLED, iCX, iCY, -y, x, iXFrac, iYFrac, ucColor); + DrawScaledPixel(pOLED, iCX, iCY, y, -x, iXFrac, iYFrac, ucColor); + DrawScaledPixel(pOLED, iCX, iCY, -y, -x, iXFrac, iYFrac, ucColor); + } +} /* BresenhamCircle() */ + +// +// Draw an outline or filled ellipse +// +void oledEllipse(SSOLED *pOLED, int iCenterX, int iCenterY, int32_t iRadiusX, int32_t iRadiusY, uint8_t ucColor, uint8_t bFilled) +{ + int32_t iXFrac, iYFrac; + int iRadius, iDelta, x, y; + + if (pOLED == NULL || pOLED->ucScreen == NULL) + return; // must have back buffer defined + if (iRadiusX <= 0 || iRadiusY <= 0) return; // invalid radii + + if (iRadiusX > iRadiusY) // use X as the primary radius + { + iRadius = iRadiusX; + iXFrac = 65536; + iYFrac = (iRadiusY * 65536) / iRadiusX; + } + else + { + iRadius = iRadiusY; + iXFrac = (iRadiusX * 65536) / iRadiusY; + iYFrac = 65536; + } + iDelta = 3 - (2 * iRadius); + x = 0; y = iRadius; + while (x <= y) + { + BresenhamCircle(pOLED, iCenterX, iCenterY, x, y, iXFrac, iYFrac, ucColor, bFilled); + x++; + if (iDelta < 0) + { + iDelta += (4*x) + 6; + } + else + { + iDelta += 4 * (x-y) + 10; + y--; + } + } +} /* oledEllipse() */ +// +// Draw an outline or filled rectangle +// +void oledRectangle(SSOLED *pOLED, int x1, int y1, int x2, int y2, uint8_t ucColor, uint8_t bFilled) +{ + uint8_t *d, ucMask, ucMask2; + int tmp, iOff; + if (pOLED == NULL || pOLED->ucScreen == NULL) + return; // only works with a back buffer + if (x1 < 0 || y1 < 0 || x2 < 0 || y2 < 0 || + x1 >= pOLED->oled_x || y1 >= pOLED->oled_y || x2 >= pOLED->oled_x || y2 >= pOLED->oled_y) return; // invalid coordinates + // Make sure that X1/Y1 is above and to the left of X2/Y2 + // swap coordinates as needed to make this true + if (x2 < x1) + { + tmp = x1; + x1 = x2; + x2 = tmp; + } + if (y2 < y1) + { + tmp = y1; + y1 = y2; + y2 = tmp; + } + if (bFilled) + { + int x, y, iMiddle; + iMiddle = (y2 >> 3) - (y1 >> 3); + ucMask = 0xff << (y1 & 7); + if (iMiddle == 0) // top and bottom lines are in the same row + ucMask &= (0xff >> (7-(y2 & 7))); + d = &pOLED->ucScreen[(y1 >> 3)*128 + x1]; + // Draw top + for (x = x1; x <= x2; x++) + { + if (ucColor) + *d |= ucMask; + else + *d &= ~ucMask; + d++; + } + if (iMiddle > 1) // need to draw middle part + { + ucMask = (ucColor) ? 0xff : 0x00; + for (y=1; y<iMiddle; y++) + { + d = &pOLED->ucScreen[(y1 >> 3)*128 + x1 + (y*128)]; + for (x = x1; x <= x2; x++) + *d++ = ucMask; + } + } + if (iMiddle >= 1) // need to draw bottom part + { + ucMask = 0xff >> (7-(y2 & 7)); + d = &pOLED->ucScreen[(y2 >> 3)*128 + x1]; + for (x = x1; x <= x2; x++) + { + if (ucColor) + *d++ |= ucMask; + else + *d++ &= ~ucMask; + } + } + } + else // outline + { + // see if top and bottom lines are within the same byte rows + d = &pOLED->ucScreen[(y1 >> 3)*128 + x1]; + if ((y1 >> 3) == (y2 >> 3)) + { + ucMask2 = 0xff << (y1 & 7); // L/R end masks + ucMask = 1 << (y1 & 7); + ucMask |= 1 << (y2 & 7); + ucMask2 &= (0xff >> (7-(y2 & 7))); + if (ucColor) + { + *d++ |= ucMask2; // start + x1++; + for (; x1 < x2; x1++) + *d++ |= ucMask; + if (x1 <= x2) + *d++ |= ucMask2; // right edge + } + else + { + *d++ &= ~ucMask2; + x1++; + for (; x1 < x2; x1++) + *d++ &= ~ucMask; + if (x1 <= x2) + *d++ &= ~ucMask2; // right edge + } + } + else + { + int y; + // L/R sides + iOff = (x2 - x1); + ucMask = 1 << (y1 & 7); + for (y=y1; y <= y2; y++) + { + if (ucColor) { + *d |= ucMask; + d[iOff] |= ucMask; + } else { + *d &= ~ucMask; + d[iOff] &= ~ucMask; + } + ucMask <<= 1; + if (ucMask == 0) { + ucMask = 1; + d += 128; + } + } + // T/B sides + ucMask = 1 << (y1 & 7); + ucMask2 = 1 << (y2 & 7); + x1++; + d = &pOLED->ucScreen[(y1 >> 3)*128 + x1]; + iOff = (y2 >> 3) - (y1 >> 3); + iOff *= 128; + for (; x1 < x2; x1++) + { + if (ucColor) { + *d |= ucMask; + d[iOff] |= ucMask2; + } else { + *d &= ~ucMask; + d[iOff] &= ~ucMask2; + } + d++; + } + } + } // outline +} /* oledRectangle() */ diff --git a/oled/ss_oled.h b/oled/ss_oled.h new file mode 100644 index 0000000..f7e303c --- /dev/null +++ b/oled/ss_oled.h @@ -0,0 +1,211 @@ +#ifndef __SS_OLED_H__ +#define __SS_OLED_H__ + +#include "BitBang_I2C.h" + +typedef struct ssoleds +{ +uint8_t oled_addr; // requested address or 0xff for automatic detection +uint8_t oled_wrap, oled_flip, oled_type; +uint8_t *ucScreen; +uint8_t iCursorX, iCursorY; +uint8_t oled_x, oled_y; +int iScreenOffset; +BBI2C bbi2c; +} SSOLED; +// Make the Linux library interface C instead of C++ +#if defined(_LINUX_) && defined(__cplusplus) +extern "C" { +#endif + +// These are defined the same in my SPI_LCD library +#ifndef SPI_LCD_H + +// 4 possible font sizes: 8x8, 16x32, 6x8, 16x16 (stretched from 8x8) +enum { + FONT_6x8 = 0, + FONT_8x8, + FONT_12x16, + FONT_16x16, + FONT_16x32 +}; +#define FONT_NORMAL FONT_8x8 +#define FONT_SMALL FONT_6x8 +#define FONT_LARGE FONT_16x32 +#define FONT_STRETCHED FONT_16x16 +#endif + +// OLED type for init function +enum { + OLED_128x128 = 1, + OLED_128x32, + OLED_128x64, + OLED_132x64, + OLED_64x32, + OLED_96x16, + OLED_72x40 +}; + +// Rotation and flip angles to draw tiles +enum { + ANGLE_0=0, + ANGLE_90, + ANGLE_180, + ANGLE_270, + ANGLE_FLIPX, + ANGLE_FLIPY +}; + +// Return value from oledInit() +enum { + OLED_NOT_FOUND = -1, // no display found + OLED_SSD1306_3C, // SSD1306 found at 0x3C + OLED_SSD1306_3D, // SSD1306 found at 0x3D + OLED_SH1106_3C, // SH1106 found at 0x3C + OLED_SH1106_3D, // SH1106 found at 0x3D + OLED_SH1107_3C, // SH1107 + OLED_SH1107_3D +}; +// +// Initializes the OLED controller into "page mode" on I2C +// If SDAPin and SCLPin are not -1, then bit bang I2C on those pins +// Otherwise use the Wire library. +// If you don't need to use a separate reset pin, set it to -1 +// +int oledInit(SSOLED *pOLED, int iType, int iAddr, int bFlip, int bInvert, int bWire, int iSDAPin, int iSCLPin, int iResetPin, int32_t iSpeed); +// +// Initialize an SPI version of the display +// +void oledSPIInit(int iType, int iDC, int iCS, int iReset, int bFlip, int bInvert, int32_t iSpeed); + +// +// Provide or revoke a back buffer for your OLED graphics +// This allows you to manage the RAM used by ss_oled on tiny +// embedded platforms like the ATmega series +// Pass NULL to revoke the buffer. Make sure you provide a buffer +// large enough for your display (e.g. 128x64 needs 1K - 1024 bytes) +// +void oledSetBackBuffer(SSOLED *pOLED, uint8_t *pBuffer); +// +// Sets the brightness (0=off, 255=brightest) +// +void oledSetContrast(SSOLED *pOLED, unsigned char ucContrast); +// +// Load a 128x64 1-bpp Windows bitmap +// Pass the pointer to the beginning of the BMP file +// First pass version assumes a full screen bitmap +// +int oledLoadBMP(SSOLED *pOLED, uint8_t *pBMP, int bInvert, int bRender); +// +// Power up/down the display +// useful for low power situations +// +void oledPower(SSOLED *pOLED, uint8_t bOn); +// +// Set the current cursor position +// The column represents the pixel column (0-127) +// The row represents the text row (0-7) +// +void oledSetCursor(SSOLED *pOLED, int x, int y); + +// +// Turn text wrap on or off for the oldWriteString() function +// +void oledSetTextWrap(SSOLED *pOLED, int bWrap); +// +// Draw a string of normal (8x8), small (6x8) or large (16x32) characters +// At the given col+row with the given scroll offset. The scroll offset allows you to +// horizontally scroll text which does not fit on the width of the display. The offset +// represents the pixels to skip when drawing the text. An offset of 0 starts at the beginning +// of the text. +// The system remembers where the last text was written (the cursor position) +// To continue writing from the last position, set the x,y values to -1 +// The text can optionally wrap around to the next line by calling oledSetTextWrap(true); +// otherwise text which would go off the right edge will not be drawn and the cursor will +// be left "off screen" until set to a new position explicitly +// +// Returns 0 for success, -1 for invalid parameter +// +int oledWriteString(SSOLED *pOLED, int iScrollX, int x, int y, char *szMsg, int iSize, int bInvert, int bRender); +// +// Fill the frame buffer with a byte pattern +// e.g. all off (0x00) or all on (0xff) +// +void oledFill(SSOLED *pOLED, unsigned char ucData, int bRender); +// +// Set (or clear) an individual pixel +// The local copy of the frame buffer is used to avoid +// reading data from the display controller +// (which isn't possible in most configurations) +// This function needs the USE_BACKBUFFER macro to be defined +// otherwise, new pixels will erase old pixels within the same byte +// +int oledSetPixel(SSOLED *pOLED, int x, int y, unsigned char ucColor, int bRender); +// +// Dump an entire custom buffer to the display +// useful for custom animation effects +// +void oledDumpBuffer(SSOLED *pOLED, uint8_t *pBuffer); +// +// Render a window of pixels from a provided buffer or the library's internal buffer +// to the display. The row values refer to byte rows, not pixel rows due to the memory +// layout of OLEDs. Pass a src pointer of NULL to use the internal backing buffer +// returns 0 for success, -1 for invalid parameter +// +int oledDrawGFX(SSOLED *pOLED, uint8_t *pSrc, int iSrcCol, int iSrcRow, int iDestCol, int iDestRow, int iWidth, int iHeight, int iSrcPitch); + +// +// Draw a line between 2 points +// +void oledDrawLine(SSOLED *pOLED, int x1, int y1, int x2, int y2, int bRender); +// +// Play a frame of animation data +// The animation data is assumed to be encoded for a full frame of the display +// Given the pointer to the start of the compressed data, +// it returns the pointer to the start of the next frame +// Frame rate control is up to the calling program to manage +// When it finishes the last frame, it will start again from the beginning +// +uint8_t * oledPlayAnimFrame(SSOLED *pOLED, uint8_t *pAnimation, uint8_t *pCurrent, int iLen); + +// +// Scroll the internal buffer by 1 scanline (up/down) +// width is in pixels, lines is group of 8 rows +// Returns 0 for success, -1 for invalid parameter +// +int oledScrollBuffer(SSOLED *pOLED, int iStartCol, int iEndCol, int iStartRow, int iEndRow, int bUp); +// +// Draw a sprite of any size in any position +// If it goes beyond the left/right or top/bottom edges +// it's trimmed to show the valid parts +// This function requires a back buffer to be defined +// The priority color (0 or 1) determines which color is painted +// when a 1 is encountered in the source image. +// e.g. when 0, the input bitmap acts like a mask to clear +// the destination where bits are set. +// +void oledDrawSprite(SSOLED *pOLED, uint8_t *pSprite, int cx, int cy, int iPitch, int x, int y, uint8_t iPriority); +// +// Draw a 16x16 tile in any of 4 rotated positions +// Assumes input image is laid out like "normal" graphics with +// the MSB on the left and 2 bytes per line +// On AVR, the source image is assumed to be in FLASH memory +// The function can draw the tile on byte boundaries, so the x value +// can be from 0 to 112 and y can be from 0 to 6 +// +void oledDrawTile(SSOLED *pOLED, const uint8_t *pTile, int x, int y, int iRotation, int bInvert, int bRender); +// +// Draw an outline or filled ellipse +// +void oledEllipse(SSOLED *pOLED, int iCenterX, int iCenterY, int32_t iRadiusX, int32_t iRadiusY, uint8_t ucColor, uint8_t bFilled); +// +// Draw an outline or filled rectangle +// +void oledRectangle(SSOLED *pOLED, int x1, int y1, int x2, int y2, uint8_t ucColor, uint8_t bFilled); + +#if defined(_LINUX_) && defined(__cplusplus) +} +#endif // _LINUX_ + +#endif // __SS_OLED_H__ + diff --git a/pico-watch.c b/pico-watch.c new file mode 100644 index 0000000..3d39b9b --- /dev/null +++ b/pico-watch.c @@ -0,0 +1,40 @@ +#include <stdio.h> +#include "pico/stdlib.h" +#include "hardware/i2c.h" +#include "hardware/timer.h" +#include "hardware/clocks.h" + +// I2C defines +// This example will use I2C0 on GPIO8 (SDA) and GPIO9 (SCL) running at 400KHz. +// Pins can be changed, see the GPIO function select table in the datasheet for information on GPIO assignments +#define I2C_PORT i2c0 +#define I2C_SDA 8 +#define I2C_SCL 9 + +int64_t alarm_callback(alarm_id_t id, void *user_data) { + // Put your timeout handler code in here + return 0; +} + + + +int main() +{ + stdio_init_all(); + + // I2C Initialisation. Using it at 400Khz. + i2c_init(I2C_PORT, 400*1000); + + gpio_set_function(I2C_SDA, GPIO_FUNC_I2C); + gpio_set_function(I2C_SCL, GPIO_FUNC_I2C); + gpio_pull_up(I2C_SDA); + gpio_pull_up(I2C_SCL); + + // Timer example code - This example fires off the callback after 2000ms + add_alarm_in_ms(2000, alarm_callback, NULL, false); + + + puts("Hello, world!"); + + return 0; +} diff --git a/pico_sdk_import.cmake b/pico_sdk_import.cmake new file mode 100644 index 0000000..e02a33e --- /dev/null +++ b/pico_sdk_import.cmake @@ -0,0 +1,62 @@ +# This is a copy of <PICO_SDK_PATH>/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() + +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 () + +set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the PICO SDK") +set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of PICO 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") + +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 master + ) + if (NOT pico_sdk) + message("Downloading PICO SDK") + FetchContent_Populate(pico_sdk) + set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) + endif () + set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) + else () + message(FATAL_ERROR + "PICO 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 PICO SDK") +endif () + +set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the PICO SDK" FORCE) + +include(${PICO_SDK_INIT_CMAKE_FILE}) |