Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
hardware-bitcoin-wallet/pic32/ssd1306.c
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
588 lines (556 sloc)
24.8 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** \file ssd1306.c | |
| * | |
| * \brief Interfaces with SSD1306-based OLED display using PIC32's GPIO. | |
| * | |
| * The SSD1306 controller controls a 128x64 monochrome graphical OLED | |
| * display. The functions in this file deal with the software interfacing | |
| * requirements and also implement a column-based text renderer. | |
| * | |
| * For details on hardware interfacing requirements, see | |
| * configurePeripheralsForSSD1306(). For details on the renderer, see | |
| * renderDisplay(). To use the functions in this file, first call | |
| * initSSD1306() once. Then, clearDisplay(), nextLine(), | |
| * writeStringToDisplay(), writeStringToDisplayWordWrap() etc. may be used | |
| * to change the state of the display. Note that nothing will be displayed | |
| * until the display is turned on using displayOn(). | |
| * | |
| * A lot of the interface requirements were obtained from the SSD1306 | |
| * datasheet, obtained from http://www.adafruit.com/datasheets/SSD1306.pdf | |
| * on 30-Apr-2012. | |
| * | |
| * This file is licensed as described by the file LICENCE. | |
| */ | |
| #include <stdint.h> | |
| #include <stdbool.h> | |
| #include <p32xxxx.h> | |
| #include "pic32_system.h" | |
| /** Bit which specifies which pin (1 = RD0, 2 = RD1, 4 = RD2 etc.) on port D | |
| * the OLED controller's chip select line is connected to. */ | |
| #define OLED_CS (1 << 6) | |
| /** Bit which specifies which pin (1 = RD0, 2 = RD1, 4 = RD2 etc.) on port D | |
| * the OLED controller's reset line is connected to. */ | |
| #define OLED_RES (1 << 5) | |
| /** Bit which specifies which pin (1 = RD0, 2 = RD1, 4 = RD2 etc.) on port D | |
| * the OLED controller's serial data is connected to. */ | |
| #define OLED_SDIN (1 << 3) | |
| /** Bit which specifies which pin (1 = RD0, 2 = RD1, 4 = RD2 etc.) on port D | |
| * the OLED controller's serial clock line is connected to. */ | |
| #define OLED_SCLK (1 << 1) | |
| /** Width of the display, in number of pixels. */ | |
| #define DISPLAY_WIDTH 128 | |
| /** Height of the display, in number of pixels. | |
| * \warning This must be a multiple of 8. | |
| */ | |
| #define DISPLAY_HEIGHT 64 | |
| /** Width of a single character, in number of pixels. */ | |
| #define CHARACTER_WIDTH 8 | |
| /** Height of a single character, in number of pixels. This does not need to | |
| * be a multiple of 8. | |
| * \warning This must be >= 8. | |
| */ | |
| #define CHARACTER_HEIGHT 16 | |
| /** Maximum number of characters which can be on a line. */ | |
| #define CHARACTERS_PER_LINE 16 | |
| /** Maximum number of lines on screen. */ | |
| #define NUMBER_OF_LINES 4 | |
| /** Number of bits (or equivalently, total pixels) in #font_table that each | |
| * character occupies. */ | |
| #define CHARACTER_BITS (CHARACTER_WIDTH * CHARACTER_HEIGHT) | |
| /** The character encoding value that the font table begins at. Setting this | |
| * to a non-zero value saves space by not having to store the bitmaps for | |
| * unprintable characters. */ | |
| #define FONT_TABLE_START 32 | |
| /** The character encoding value for a blank (all-zero bitmap) character. */ | |
| #define FONT_BLANK 32 | |
| /** Packed, vertical, monochrome bitmaps for each character. | |
| * | |
| * The packed vertical bitmap can be interpreted as follows: imagine the font | |
| * table as a single large little-endian multi-precision integer. Start | |
| * with the least significant bit and move to more significant bits. The | |
| * least significant bit represents the top-left pixel of the first glyph. | |
| * For each increment in bit significance, move down to the next pixel. If | |
| * you get to the bottom, move to the top of the next (towards the right) | |
| * column. If you get to the bottom of the last column, move to the top-left | |
| * pixel of the next glyph. | |
| * | |
| * Because of the way lookupFontTable() works, this table should have at | |
| * least one extra 0 byte appended. | |
| * | |
| * Table generated from file "ter-u16b.bdf" using bdf_converter. | |
| * Font name: "-xos4-Terminus-Bold-R-Normal--16-160-72-72-C-80-ISO10646-1". | |
| */ | |
| const uint8_t font_table[] = { | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x0d, 0xfc, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x00, 0x00, | |
| 0x20, 0x01, 0xfc, 0x0f, 0xfc, 0x0f, 0x20, 0x01, 0xfc, 0x0f, 0xfc, 0x0f, 0x20, 0x01, 0x00, 0x00, | |
| 0x70, 0x04, 0xf8, 0x0c, 0x88, 0x08, 0xfe, 0x3f, 0x88, 0x08, 0x98, 0x0f, 0x10, 0x07, 0x00, 0x00, | |
| 0x08, 0x00, 0x1c, 0x0c, 0x14, 0x0f, 0xc8, 0x03, 0xf0, 0x04, 0x3c, 0x0a, 0x0c, 0x0e, 0x00, 0x04, | |
| 0x80, 0x07, 0xd8, 0x0f, 0x7c, 0x08, 0xe4, 0x0c, 0xbc, 0x07, 0xd8, 0x0f, 0x40, 0x08, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0xf0, 0x03, 0xf8, 0x07, 0x0c, 0x0c, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x0c, 0x0c, 0xf8, 0x07, 0xf0, 0x03, 0x00, 0x00, 0x00, 0x00, | |
| 0x80, 0x00, 0xa0, 0x02, 0xe0, 0x03, 0xc0, 0x01, 0xe0, 0x03, 0xa0, 0x02, 0x80, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0xe0, 0x03, 0xe0, 0x03, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x1c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0f, 0xc0, 0x03, 0xf0, 0x00, 0x3c, 0x00, 0x0c, 0x00, 0x00, 0x00, | |
| 0xf8, 0x07, 0xfc, 0x0f, 0x84, 0x09, 0xc4, 0x08, 0x64, 0x08, 0xfc, 0x0f, 0xf8, 0x07, 0x00, 0x00, | |
| 0x00, 0x00, 0x10, 0x08, 0x18, 0x08, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, | |
| 0x18, 0x0c, 0x1c, 0x0e, 0x04, 0x0b, 0x84, 0x09, 0xc4, 0x08, 0x7c, 0x08, 0x38, 0x08, 0x00, 0x00, | |
| 0x18, 0x06, 0x1c, 0x0e, 0x44, 0x08, 0x44, 0x08, 0x44, 0x08, 0xfc, 0x0f, 0xb8, 0x07, 0x00, 0x00, | |
| 0x80, 0x01, 0xc0, 0x01, 0x60, 0x01, 0x30, 0x01, 0x18, 0x01, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00, | |
| 0x7c, 0x04, 0x7c, 0x0c, 0x44, 0x08, 0x44, 0x08, 0x44, 0x08, 0xc4, 0x0f, 0x84, 0x07, 0x00, 0x00, | |
| 0xf0, 0x07, 0xf8, 0x0f, 0x4c, 0x08, 0x44, 0x08, 0x44, 0x08, 0xc4, 0x0f, 0x80, 0x07, 0x00, 0x00, | |
| 0x04, 0x00, 0x04, 0x00, 0x04, 0x0e, 0x84, 0x0f, 0xe4, 0x01, 0x7c, 0x00, 0x1c, 0x00, 0x00, 0x00, | |
| 0xb8, 0x07, 0xfc, 0x0f, 0x44, 0x08, 0x44, 0x08, 0x44, 0x08, 0xfc, 0x0f, 0xb8, 0x07, 0x00, 0x00, | |
| 0x78, 0x00, 0xfc, 0x08, 0x84, 0x08, 0x84, 0x08, 0x84, 0x0c, 0xfc, 0x07, 0xf8, 0x03, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x0c, 0x60, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x60, 0x1c, 0x60, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x80, 0x00, 0xc0, 0x01, 0x60, 0x03, 0x30, 0x06, 0x18, 0x0c, 0x08, 0x08, 0x00, 0x00, | |
| 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x20, 0x01, 0x00, 0x00, | |
| 0x00, 0x00, 0x08, 0x08, 0x18, 0x0c, 0x30, 0x06, 0x60, 0x03, 0xc0, 0x01, 0x80, 0x00, 0x00, 0x00, | |
| 0x38, 0x00, 0x3c, 0x00, 0x04, 0x00, 0x84, 0x0d, 0xc4, 0x0d, 0x7c, 0x00, 0x38, 0x00, 0x00, 0x00, | |
| 0xf8, 0x07, 0xfc, 0x0f, 0x04, 0x08, 0xe4, 0x09, 0x14, 0x0a, 0xfc, 0x0b, 0xf8, 0x0b, 0x00, 0x00, | |
| 0xf8, 0x0f, 0xfc, 0x0f, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0xfc, 0x0f, 0xf8, 0x0f, 0x00, 0x00, | |
| 0xfc, 0x0f, 0xfc, 0x0f, 0x44, 0x08, 0x44, 0x08, 0x44, 0x08, 0xfc, 0x0f, 0xb8, 0x07, 0x00, 0x00, | |
| 0xf8, 0x07, 0xfc, 0x0f, 0x04, 0x08, 0x04, 0x08, 0x04, 0x08, 0x1c, 0x0e, 0x18, 0x06, 0x00, 0x00, | |
| 0xfc, 0x0f, 0xfc, 0x0f, 0x04, 0x08, 0x04, 0x08, 0x0c, 0x0c, 0xf8, 0x07, 0xf0, 0x03, 0x00, 0x00, | |
| 0xfc, 0x0f, 0xfc, 0x0f, 0x44, 0x08, 0x44, 0x08, 0x44, 0x08, 0x04, 0x08, 0x04, 0x08, 0x00, 0x00, | |
| 0xfc, 0x0f, 0xfc, 0x0f, 0x44, 0x00, 0x44, 0x00, 0x44, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, | |
| 0xf8, 0x07, 0xfc, 0x0f, 0x04, 0x08, 0x84, 0x08, 0x84, 0x08, 0x9c, 0x0f, 0x98, 0x07, 0x00, 0x00, | |
| 0xfc, 0x0f, 0xfc, 0x0f, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0xfc, 0x0f, 0xfc, 0x0f, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x06, 0x00, 0x0e, 0x00, 0x08, 0x04, 0x08, 0xfc, 0x0f, 0xfc, 0x07, 0x04, 0x00, 0x00, 0x00, | |
| 0xfc, 0x0f, 0xfc, 0x0f, 0xc0, 0x00, 0xe0, 0x01, 0x30, 0x03, 0x1c, 0x0e, 0x0c, 0x0c, 0x00, 0x00, | |
| 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x00, | |
| 0xfc, 0x0f, 0xf8, 0x0f, 0x30, 0x00, 0x60, 0x00, 0x30, 0x00, 0xf8, 0x0f, 0xfc, 0x0f, 0x00, 0x00, | |
| 0xfc, 0x0f, 0xfc, 0x0f, 0x60, 0x00, 0xc0, 0x00, 0x80, 0x01, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00, | |
| 0xf8, 0x07, 0xfc, 0x0f, 0x04, 0x08, 0x04, 0x08, 0x04, 0x08, 0xfc, 0x0f, 0xf8, 0x07, 0x00, 0x00, | |
| 0xfc, 0x0f, 0xfc, 0x0f, 0x84, 0x00, 0x84, 0x00, 0x84, 0x00, 0xfc, 0x00, 0x78, 0x00, 0x00, 0x00, | |
| 0xf8, 0x07, 0xfc, 0x0f, 0x04, 0x08, 0x04, 0x0c, 0x04, 0x0c, 0xfc, 0x1f, 0xf8, 0x17, 0x00, 0x00, | |
| 0xfc, 0x0f, 0xfc, 0x0f, 0x84, 0x01, 0x84, 0x03, 0x84, 0x06, 0xfc, 0x0c, 0x78, 0x08, 0x00, 0x00, | |
| 0x38, 0x06, 0x7c, 0x0e, 0x44, 0x08, 0x44, 0x08, 0x44, 0x08, 0xcc, 0x0f, 0x88, 0x07, 0x00, 0x00, | |
| 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0x04, 0x00, 0x04, 0x00, 0x04, 0x00, | |
| 0xfc, 0x07, 0xfc, 0x0f, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0xfc, 0x0f, 0xfc, 0x07, 0x00, 0x00, | |
| 0x7c, 0x00, 0xfc, 0x03, 0x80, 0x0f, 0x00, 0x0c, 0x80, 0x0f, 0xfc, 0x03, 0x7c, 0x00, 0x00, 0x00, | |
| 0xfc, 0x0f, 0xfc, 0x07, 0x00, 0x03, 0x80, 0x01, 0x00, 0x03, 0xfc, 0x07, 0xfc, 0x0f, 0x00, 0x00, | |
| 0x0c, 0x0c, 0x3c, 0x0f, 0xf0, 0x03, 0xc0, 0x00, 0xf0, 0x03, 0x3c, 0x0f, 0x0c, 0x0c, 0x00, 0x00, | |
| 0x0c, 0x00, 0x3c, 0x00, 0x70, 0x00, 0xc0, 0x0f, 0xc0, 0x0f, 0x70, 0x00, 0x3c, 0x00, 0x0c, 0x00, | |
| 0x04, 0x0e, 0x04, 0x0f, 0x84, 0x09, 0xc4, 0x08, 0x64, 0x08, 0x3c, 0x08, 0x1c, 0x08, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0x04, 0x08, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x0c, 0x00, 0x3c, 0x00, 0xf0, 0x00, 0xc0, 0x03, 0x00, 0x0f, 0x00, 0x0c, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0x04, 0x08, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x08, 0x00, 0x0c, 0x00, 0x06, 0x00, 0x06, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x00, 0x00, | |
| 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x07, 0xa0, 0x0f, 0xa0, 0x08, 0xa0, 0x08, 0xa0, 0x08, 0xe0, 0x0f, 0xc0, 0x0f, 0x00, 0x00, | |
| 0xfc, 0x0f, 0xfc, 0x0f, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0xe0, 0x0f, 0xc0, 0x07, 0x00, 0x00, | |
| 0xc0, 0x07, 0xe0, 0x0f, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0x60, 0x0c, 0x40, 0x04, 0x00, 0x00, | |
| 0xc0, 0x07, 0xe0, 0x0f, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00, | |
| 0xc0, 0x07, 0xe0, 0x0f, 0x20, 0x09, 0x20, 0x09, 0x20, 0x09, 0xe0, 0x09, 0xc0, 0x01, 0x00, 0x00, | |
| 0x20, 0x00, 0x20, 0x00, 0xf8, 0x0f, 0xfc, 0x0f, 0x24, 0x00, 0x24, 0x00, 0x04, 0x00, 0x00, 0x00, | |
| 0xc0, 0x07, 0xe0, 0x4f, 0x20, 0x48, 0x20, 0x48, 0x20, 0x48, 0xe0, 0x7f, 0xe0, 0x3f, 0x00, 0x00, | |
| 0xfc, 0x0f, 0xfc, 0x0f, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xe0, 0x0f, 0xc0, 0x0f, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x20, 0x08, 0xec, 0x0f, 0xec, 0x0f, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x30, 0x00, 0x70, 0x00, 0x40, 0x20, 0x40, 0xec, 0x7f, 0xec, 0x3f, 0x00, 0x00, | |
| 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x01, 0x80, 0x03, 0xc0, 0x06, 0x60, 0x0c, 0x20, 0x08, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, | |
| 0xe0, 0x0f, 0xe0, 0x0f, 0x20, 0x00, 0xe0, 0x0f, 0x20, 0x00, 0xe0, 0x0f, 0xc0, 0x0f, 0x00, 0x00, | |
| 0xe0, 0x0f, 0xe0, 0x0f, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0xe0, 0x0f, 0xc0, 0x0f, 0x00, 0x00, | |
| 0xc0, 0x07, 0xe0, 0x0f, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0xe0, 0x0f, 0xc0, 0x07, 0x00, 0x00, | |
| 0xe0, 0x7f, 0xe0, 0x7f, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0xe0, 0x0f, 0xc0, 0x07, 0x00, 0x00, | |
| 0xc0, 0x07, 0xe0, 0x0f, 0x20, 0x08, 0x20, 0x08, 0x20, 0x08, 0xe0, 0x7f, 0xe0, 0x7f, 0x00, 0x00, | |
| 0xe0, 0x0f, 0xe0, 0x0f, 0xc0, 0x00, 0x60, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x00, 0x00, | |
| 0xc0, 0x08, 0xe0, 0x09, 0x20, 0x09, 0x20, 0x09, 0x20, 0x09, 0x20, 0x0f, 0x20, 0x06, 0x00, 0x00, | |
| 0x20, 0x00, 0x20, 0x00, 0xfc, 0x07, 0xfc, 0x0f, 0x20, 0x08, 0x20, 0x08, 0x00, 0x08, 0x00, 0x00, | |
| 0xe0, 0x07, 0xe0, 0x0f, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0xe0, 0x0f, 0xe0, 0x0f, 0x00, 0x00, | |
| 0xe0, 0x00, 0xe0, 0x03, 0x00, 0x0f, 0x00, 0x0c, 0x00, 0x0f, 0xe0, 0x03, 0xe0, 0x00, 0x00, 0x00, | |
| 0xe0, 0x07, 0xe0, 0x0f, 0x00, 0x08, 0x80, 0x0f, 0x00, 0x08, 0xe0, 0x0f, 0xe0, 0x07, 0x00, 0x00, | |
| 0x60, 0x0c, 0xe0, 0x0e, 0x80, 0x03, 0x00, 0x01, 0x80, 0x03, 0xe0, 0x0e, 0x60, 0x0c, 0x00, 0x00, | |
| 0xe0, 0x07, 0xe0, 0x4f, 0x00, 0x48, 0x00, 0x48, 0x00, 0x48, 0xe0, 0x7f, 0xe0, 0x3f, 0x00, 0x00, | |
| 0x20, 0x0c, 0x20, 0x0e, 0x20, 0x0b, 0xa0, 0x09, 0xe0, 0x08, 0x60, 0x08, 0x20, 0x08, 0x00, 0x00, | |
| 0x00, 0x00, 0x40, 0x00, 0xf8, 0x07, 0xbc, 0x0f, 0x04, 0x08, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x0f, 0xfc, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00, 0x00, 0x04, 0x08, 0x04, 0x08, 0xbc, 0x0f, 0xf8, 0x07, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x0c, 0x00, 0x0e, 0x00, 0x02, 0x00, 0x06, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x0e, 0x00, 0x06, 0x00, | |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
| 0x00}; | |
| /** Character buffer, in row-major order, starting from the top-left. | |
| * Characters should have their ASCII values written here. | |
| * renderDisplay() will render the contents of this buffer to the display. | |
| */ | |
| static uint8_t text_buffer[CHARACTERS_PER_LINE * NUMBER_OF_LINES]; | |
| /** The line where the (hidden) cursor is at. 0 = topmost. This is also the | |
| * line within #text_buffer that writeStringToDisplay() will write to next. | |
| */ | |
| static uint32_t cursor_line; | |
| /** The position on the current line where the (hidden) cursor is at. | |
| * 0 = leftmost. This is also the line offset within #text_buffer that | |
| * writeStringToDisplay() will write to next. | |
| */ | |
| static uint32_t cursor_pos; | |
| /** See ssd1306_bitbang.S. */ | |
| extern void ssd1306BitBangOneFrame(volatile uint32_t *port, uint32_t frame_data, uint32_t sclk_pin, uint32_t sdin_pin); | |
| /** Configures PIC32 ports to interface with SSD1306. The chip select (CS#) | |
| * line should be connected to the port specified by #OLED_CS. Likewise for | |
| * the reset (RES#) with #OLED_RES, serial data (SDIN) with #OLED_SDIN and | |
| * serial clock (SCLK) with #OLED_SCLK. | |
| * | |
| * The SSD1306 should have BS0 tied high. BS1 and BS2 should be tied low. | |
| * This selects "3-wire SPI" mode on the SSD1306. All other input pins should | |
| * be connected as directed on section 8.1 ("MCU Interface selection") of the | |
| * SSD1306 datasheet. | |
| */ | |
| static void configurePeripheralsForSSD1306(void) | |
| { | |
| // Set all OLED interface pins as normally high outputs. | |
| TRISDCLR = OLED_CS | OLED_RES | OLED_SDIN | OLED_SCLK; | |
| PORTDSET = OLED_CS | OLED_RES | OLED_SDIN | OLED_SCLK; | |
| } | |
| /** Write an 8 bit command or data to the SSD1306 by bit-banging GPIO. | |
| * The PIC32's SPI module isn't used because it can't send 9 bit wide frames. | |
| * \param is_data This should be true if value is data, false if value | |
| * is a command. | |
| * \param value The command or data to write. | |
| */ | |
| static void writeSPIByte(bool is_data, uint8_t value) | |
| { | |
| uint32_t frame; | |
| frame = value; | |
| if (is_data) | |
| { | |
| frame |= 0x100; | |
| } | |
| PORTDCLR = OLED_CS; | |
| ssd1306BitBangOneFrame(&PORTD, frame, OLED_SCLK, OLED_SDIN); | |
| PORTDSET = OLED_CS; | |
| } | |
| /** Turn display on. This must be called in order to have anything appear | |
| * on the screen. */ | |
| void displayOn(void) | |
| { | |
| writeSPIByte(false, 0xaf); // display on | |
| } | |
| /** Turn display off. This will cause the SSD1306 controller to enter a | |
| * low power state. */ | |
| void displayOff(void) | |
| { | |
| writeSPIByte(false, 0xae); // display off | |
| } | |
| /** Reset and initialise the SSD1306 display controller. This mostly follows | |
| * Figure 2 ("Software Initialization Flow Chart") on page 64 of the | |
| * SSD1306 datasheet. | |
| * This does not turn the display on (displayOn() does that). This is so | |
| * that display GDDRAM can be cleared. If the display is turned on | |
| * immediately, undefined junk will appear on the display. | |
| */ | |
| static void resetSSD1306(void) | |
| { | |
| // Section 8.9 ("Power ON and OFF sequence") on page 27 of the SSD1306 | |
| // datasheet covers the reset procedure. | |
| PORTDCLR = OLED_RES; | |
| // RES# needs to be low for at least 3 microseconds. | |
| delayCycles(50 * CYCLES_PER_MICROSECOND); // 50 microseconds just to be sure | |
| PORTDSET = OLED_RES; | |
| displayOff(); | |
| writeSPIByte(false, 0xa8); // set multiplex ratio | |
| writeSPIByte(false, 0x3f); // multiplex ratio = 64MUX | |
| writeSPIByte(false, 0xd3); // set display offset | |
| writeSPIByte(false, 0x00); // display offset = 0 (no vertical shift) | |
| writeSPIByte(false, 0x40); // set display start line to 0 | |
| writeSPIByte(false, 0xa1); // set segment re-map to invert horizontally | |
| writeSPIByte(false, 0xc8); // set COM scan direction to invert vertically | |
| writeSPIByte(false, 0xda); // set COM pins hardware configuration | |
| writeSPIByte(false, 0x12); // COM pins hardware configuration = alternative, no left/right remap | |
| writeSPIByte(false, 0x81); // set contrast | |
| writeSPIByte(false, 0xbf); // contrast = 75% | |
| writeSPIByte(false, 0xa4); // disable entire display on | |
| writeSPIByte(false, 0xa6); // set normal display | |
| writeSPIByte(false, 0xd5); // set oscillator frequency | |
| writeSPIByte(false, 0x80); // oscillator frequency = default | |
| writeSPIByte(false, 0x8d); // set charge pump | |
| writeSPIByte(false, 0x14); // charge pump = on | |
| writeSPIByte(false, 0x20); // set memory addressing mode | |
| writeSPIByte(false, 0x01); // memory addressing mode = vertical | |
| } | |
| /** Clear the display and all associated buffers. */ | |
| void clearDisplay(void) | |
| { | |
| uint32_t i; | |
| for (i = 0; i < 1024; i++) | |
| { | |
| writeSPIByte(true, 0); | |
| } | |
| cursor_line = 0; | |
| cursor_pos = 0; | |
| memset(text_buffer, FONT_BLANK, sizeof(text_buffer)); | |
| } | |
| /** Set up everything so that the display is ready to start having text | |
| * rendered on it. By default, this will not turn on the display; use | |
| * displayOn() to do that. */ | |
| void initSSD1306(void) | |
| { | |
| configurePeripheralsForSSD1306(); | |
| resetSSD1306(); | |
| clearDisplay(); | |
| } | |
| /** Font table byte lookup function which has bit granularity. Alternatively, | |
| * this can be thought of as treating the font table as a giant little-endian | |
| * multi-precision integer, shifting it to the right bit_offset times and | |
| * returning the least significant byte. | |
| * This does range checking of bit_offset. | |
| * \param bit_offset The font table offset, in bits, to grab a byte from. | |
| * \return A byte from within the font table. | |
| */ | |
| static uint8_t lookupFontTable(uint32_t bit_offset) | |
| { | |
| uint32_t index; | |
| uint32_t data; | |
| index = bit_offset >> 3; | |
| if (index >= (sizeof(font_table) - 1)) | |
| { | |
| return 0; // empty character | |
| } | |
| data = font_table[index] | (font_table[index + 1] << 8); | |
| return (uint8_t)(data >> (bit_offset & 7)); | |
| } | |
| /** Text buffer query function. As well as obtaining a character from | |
| * the text buffer, this also range checks its inputs and accounts | |
| * for the font table not starting at 0 (see #FONT_TABLE_START). | |
| * \param char_x x location (0 = leftmost) of character to fetch. | |
| * \param char_y y location (0 = topmost) of character to fetch. | |
| * \return The character at the specified location, with #FONT_TABLE_START | |
| * subtracted off the character's value. | |
| */ | |
| static uint8_t lookupTextBuffer(uint32_t char_x, uint32_t char_y) | |
| { | |
| uint8_t data; | |
| if ((char_x >= CHARACTERS_PER_LINE) || (char_y >= NUMBER_OF_LINES)) | |
| { | |
| // x or y out of range. | |
| return FONT_BLANK - FONT_TABLE_START; // blank character | |
| } | |
| data = text_buffer[CHARACTERS_PER_LINE * char_y + char_x]; | |
| if (data < FONT_TABLE_START) | |
| { | |
| // Text buffer has a character which isn't in font table. | |
| return FONT_BLANK - FONT_TABLE_START; // blank character | |
| } | |
| else | |
| { | |
| return data - FONT_TABLE_START; | |
| } | |
| } | |
| /** Using the contents of the text buffer (#text_buffer) and the font | |
| * in #font_table, this outputs a series of data bytes to the SSD1306 | |
| * display. | |
| * | |
| * Since the SSD1306 memory addressing mode is set to "vertical" by | |
| * resetSSD1306(), this function renders in columns, 8 pixels at a time. | |
| * The rendering loop starts at the top-left, moves downwards and when it | |
| * hits the bottom of the current column, moves right to start at the top | |
| * of the next column. Column-based rendering is done because the SSD1306's | |
| * GDDRAM is column-based (each byte of data corresponds to an 8 pixel high | |
| * column). | |
| * | |
| * In order for the renderer to work | |
| * correctly, #DISPLAY_WIDTH, #DISPLAY_HEIGHT, #CHARACTER_WIDTH | |
| * and #CHARACTER_HEIGHT must be set correctly. | |
| * The renderer only supports monochrome, fixed-width fonts. | |
| * However, the renderer can deal with fonts with a height which is not a | |
| * multiple of 8. | |
| * | |
| * This renderer isn't very fast. But it doesn't need to be. With the SPI bus | |
| * running at a bit rate of 1 Mhz, the renderer only needs to be able to | |
| * write one byte every 384 cycles, which is a long time. | |
| */ | |
| static void renderDisplay(void) | |
| { | |
| uint32_t x; // 0 = left edge, positive = right | |
| uint32_t y; // 0 = top edge, positive = down | |
| uint32_t char_x; // 0 = leftmost character, 1 = the one to the right of that etc. | |
| uint32_t char_y; // 0 = topmost character, 1 = the one below that etc. | |
| uint32_t char_x_offset; // x offset within character | |
| uint32_t char_y_offset; // y offset within character | |
| uint32_t amount; // number of bits to use | |
| uint8_t current_character; | |
| uint8_t next_character; | |
| uint8_t data; | |
| uint8_t temp_data; | |
| char_x = 0; | |
| char_x_offset = 0; | |
| for (x = 0; x < DISPLAY_WIDTH; x++) | |
| { | |
| // Render a column. | |
| char_y = 0; | |
| char_y_offset = 0; | |
| // The next (i.e. one below) character needs to be fetched in advance | |
| // because the next 8 pixels may go across the current's character | |
| // height boundary. | |
| current_character = lookupTextBuffer(char_x, char_y); | |
| next_character = lookupTextBuffer(char_x, char_y + 1); | |
| for (y = 0; y < DISPLAY_HEIGHT; y += 8) | |
| { | |
| if ((char_y_offset + 8) > CHARACTER_HEIGHT) | |
| { | |
| // Byte goes across character height boundary. | |
| amount = CHARACTER_HEIGHT - char_y_offset; | |
| } | |
| else | |
| { | |
| // Next byte resides entirely within current character. | |
| amount = 8; | |
| } | |
| data = lookupFontTable(current_character * CHARACTER_BITS + char_y_offset + char_x_offset * CHARACTER_HEIGHT); | |
| data &= (1 << amount) - 1; | |
| if ((char_y_offset + 8) > CHARACTER_HEIGHT) | |
| { | |
| // Need to fetch partial character column from next character. | |
| temp_data = lookupFontTable(next_character * CHARACTER_BITS + char_x_offset * CHARACTER_HEIGHT); | |
| data |= temp_data << amount; | |
| } | |
| writeSPIByte(true, data); | |
| // Prepare for next byte. | |
| char_y_offset += 8; | |
| if (char_y_offset >= CHARACTER_HEIGHT) | |
| { | |
| char_y_offset -= CHARACTER_HEIGHT; | |
| char_y++; | |
| current_character = next_character; | |
| next_character = lookupTextBuffer(char_x, char_y + 1); | |
| } | |
| } // end for (y = 0; y < DISPLAY_HEIGHT; y += 8) | |
| // Prepare for next column. | |
| char_x_offset++; | |
| if (char_x_offset >= CHARACTER_WIDTH) | |
| { | |
| char_x_offset = 0; | |
| char_x++; | |
| } | |
| } // end for (x = 0; x < DISPLAY_WIDTH; x++) | |
| } | |
| /** Move cursor to the start of the next line, but only if the cursor is not | |
| * already at the start of the current line. */ | |
| void nextLine(void) | |
| { | |
| if (cursor_pos != 0) | |
| { | |
| cursor_line++; | |
| cursor_pos = 0; | |
| } | |
| } | |
| /** Write one character to the text buffer (#text_buffer). | |
| * \param c The character to write. | |
| */ | |
| static void writeCharacterToTextBuffer(char c) | |
| { | |
| if (cursor_pos >= CHARACTERS_PER_LINE) | |
| { | |
| nextLine(); | |
| } | |
| if (cursor_line < NUMBER_OF_LINES) | |
| { | |
| text_buffer[cursor_line * CHARACTERS_PER_LINE + cursor_pos] = c; | |
| cursor_pos++; | |
| if (cursor_pos >= CHARACTERS_PER_LINE) | |
| { | |
| nextLine(); | |
| } | |
| } | |
| } | |
| /** Write a string to the display, beginning from the cursor's current | |
| * position. This does not do word wrapping. | |
| * \param str The string to write. | |
| */ | |
| void writeStringToDisplay(const char *str) | |
| { | |
| char current_char; | |
| do | |
| { | |
| current_char = *str; | |
| str++; | |
| if (current_char != '\0') | |
| { | |
| writeCharacterToTextBuffer(current_char); | |
| } | |
| } while (current_char != '\0'); | |
| renderDisplay(); | |
| } | |
| /** Get the length, in characters, of a word. | |
| * \param str The null-terminated string with the word in it. | |
| * \return The length of the word. | |
| */ | |
| static uint32_t getWordLength(const char *str) | |
| { | |
| uint32_t length; | |
| length = 0; | |
| while ((*str != '\0') && (*str != ' ')) | |
| { | |
| str++; | |
| length++; | |
| } | |
| return length; | |
| } | |
| /** Write a string to the display, beginning from the cursor's current | |
| * position. This does do word wrapping. | |
| * \param str The string to write. | |
| */ | |
| void writeStringToDisplayWordWrap(const char *str) | |
| { | |
| uint32_t length; | |
| uint32_t i; | |
| while (*str != '\0') | |
| { | |
| length = getWordLength(str); | |
| if ((cursor_pos + length) > CHARACTERS_PER_LINE) | |
| { | |
| // Need to word wrap. | |
| nextLine(); | |
| } | |
| for (i = 0; i < length; i++) | |
| { | |
| writeCharacterToTextBuffer(str[i]); | |
| } | |
| str += length; | |
| while (*str == ' ') | |
| { | |
| str++; | |
| if (cursor_pos != 0) | |
| { | |
| // A newline is equivalent to any number of spaces. | |
| writeCharacterToTextBuffer(' '); | |
| } | |
| } | |
| } | |
| renderDisplay(); | |
| } | |
| /** Queries whether the cursor is at (or past) the end of the display. | |
| * \return Whether the cursor is at (or past) the end. | |
| */ | |
| bool displayCursorAtEnd(void) | |
| { | |
| if (cursor_line >= NUMBER_OF_LINES) | |
| { | |
| return true; // cursor is past last line | |
| } | |
| else | |
| { | |
| if (cursor_line == (NUMBER_OF_LINES - 1)) | |
| { | |
| if (cursor_pos >= CHARACTERS_PER_LINE) | |
| { | |
| return true; // cursor is past end of last line | |
| } | |
| else | |
| { | |
| return false; | |
| } | |
| } | |
| else | |
| { | |
| return false; | |
| } | |
| } | |
| } |