aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--.vscode/c_cpp_properties.json19
-rw-r--r--.vscode/launch.json26
-rw-r--r--.vscode/settings.json22
-rw-r--r--CMakeLists.txt49
-rw-r--r--oled/BitBang_I2C.c539
-rw-r--r--oled/BitBang_I2C.h109
-rw-r--r--oled/CMakeLists.txt31
-rw-r--r--oled/pico_sdk_import.cmake64
-rw-r--r--oled/ss_oled.c2255
-rw-r--r--oled/ss_oled.h211
-rw-r--r--pico-watch.c40
-rw-r--r--pico_sdk_import.cmake62
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})