diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f00731 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +ssd1306_bin diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1db20aa --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +CC=gcc +CFLAGS=-I ./ +LDFLAGS=-static +OBJS=main.o ssd1306.o linux_i2c.o +BIN=ssd1306_bin + +%.o:%.c + $(CC) -c -o $@ $< $(CFLAGS) + +$(BIN):$(OBJS) + $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) + +clean: + rm -f ./*.o $(BIN) + diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..67dbe59 --- /dev/null +++ b/Readme.md @@ -0,0 +1,118 @@ +# OLED SSD1306 Linux driver +This is plain c code using linux I2C device node (/dev/i2c-X X for 0,1,2,3...). +Please make sure the linux has run "modprobe i2c-dev". +128x64 and 128x32 are supporting and using I2C interface ONLY +## Files +**linux_i2c.c** source code for i2c control in linux device node. +**linux_i2c.h** header file for i2c control in linux. +**ssd1306.c** i2c protocol functions to control oled SSD1306. +**ssd1306.h** header file for i2c protocol functions and defined commands in SSD1306. +**font.h** font header file define 5x7 small font and 8x8 normal font. ascii value from 0x20(SPACE) to 0x7e(~). +**main.c** main file to take params and control oled SSD1306. +**Makefile** plain Makefile to build the source code. It works in raspberry pi. +**Readme.md** this readme file. +## How to compile +Require make and gcc. If you use cross compile, please self define $(CC). +Type "make" to build binary "ssd1306_bin". +Type "make clean" to clean the project. +## How to use +- always init oled module ONCE when power up. +- set the device node address EVERYTIME if not using default value /dev/i2c-0 +- the init oled module always reset XY cursor to (0,0) +- all params can set together +- clear screen if new text need to write, otherwise wording overlapping happened +- resolution value stored in /tmp/.ssd1306_oled_type with format like "128x64" or "128x32" or "64x48" +- always do display rotation first and then filling text. otherwise the text cause mirror +- make sure the XY cursor setting in correct location before printing text +### Params +```sh +-I init oled (128x32 or 128x64 or 64x48) +-c clear (line number or all) +-d 0/display off 1/display on +-f 0/small font 5x7 1/normal font 8x8 (default small font) +-h help message +-i 0/normal oled 1/invert oled +-l put your line to display +-m put your strings to oled +-n I2C device node address (0,1,2..., default 0) +-r 0/normal 180/rotate +-x x position +-y y position +``` +## Example +### init the OLED once +- resolution 128x64 +```sh +$ ./ssd1306_bin -I 128x64 +``` +- resolution 128x32 +```sh +$ ./ssd1306_bin -I 128x32 +``` +- resolution 64x48 +```sh +$ ./ssd1306_bin -I 64x48 +``` +### clear display +- clear 1st line +```sh +./ssd1306_bin -c 0 +``` +- clear 2nd line +```sh +$ ./ssd1306_bin -c 1 +``` +- clear 4th line +```sh +$ ./ssd1306_bin -c 3 +``` +- clear whole screen +```sh +$ ./ssd1306_bin -c +``` +### display on/off +- turn off display +```sh +$ ./ssd1306_bin -d 0 +``` +- turn on display +```sh +$ ./ssd1306_bin -d 1 +``` +### inverting display +- normal oled (0 is off, 1 is on) +```sh +$ ./ssd1306_bin -i 0 +``` +- invert oled (0 is on, 1 is off) +```sh +$ ./ssd1306_bin -i 1 +``` +### print words +- write line "Hello World" +```sh +$ ./ssd1306_bin -l "Hello World" +``` +- write message "alpha\nbravo\ncharlie\ndelta" (please place \n for next line) +```sh +$ ./ssd1306_bin -m "alpha\nbravo\ncharlie\ndelta" +``` +### I2C device address (default is /dev/i2c-0) +- using /dev/i2c-1 +```sh +$ ./ssd1306_bin -n 1 +``` +### rotate display +- normal orientation +```sh +$ ./ssd1306_bin -r 0 +``` +- turn 180 orientation +```sh +$ ./ssd1306_bin -r 180 +``` +### set cursor location +- set XY cursor 8,1(x is column, 8 columns skipping, y is row, 2nd line) +```sh +$ ./ssd1306_bin -x 8 -y 1 +``` diff --git a/font.h b/font.h new file mode 100644 index 0000000..c1a272c --- /dev/null +++ b/font.h @@ -0,0 +1,213 @@ +#ifndef __FONT_H +#define __FONT_H + +#include +#include + +const uint8_t font5x7[] = { + 0x00, 0x00, 0x00, 0x00, 0x00, // SPACE + 0x00, 0x00, 0x5F, 0x00, 0x00, // ! + 0x00, 0x03, 0x00, 0x03, 0x00, // " + 0x14, 0x3E, 0x14, 0x3E, 0x14, // # + 0x24, 0x2A, 0x7F, 0x2A, 0x12, // $ + 0x43, 0x33, 0x08, 0x66, 0x61, // % + 0x36, 0x49, 0x55, 0x22, 0x50, // & + 0x00, 0x05, 0x03, 0x00, 0x00, // ' + 0x00, 0x1C, 0x22, 0x41, 0x00, // ( + 0x00, 0x41, 0x22, 0x1C, 0x00, // ) + 0x14, 0x08, 0x3E, 0x08, 0x14, // * + 0x08, 0x08, 0x3E, 0x08, 0x08, // + + 0x00, 0x50, 0x30, 0x00, 0x00, // , + 0x08, 0x08, 0x08, 0x08, 0x08, // - + 0x00, 0x60, 0x60, 0x00, 0x00, // . + 0x20, 0x10, 0x08, 0x04, 0x02, // / + + 0x3E, 0x51, 0x49, 0x45, 0x3E, // 0 + 0x00, 0x04, 0x02, 0x7F, 0x00, // 1 + 0x42, 0x61, 0x51, 0x49, 0x46, // 2 + 0x22, 0x41, 0x49, 0x49, 0x36, // 3 + 0x18, 0x14, 0x12, 0x7F, 0x10, // 4 + 0x27, 0x45, 0x45, 0x45, 0x39, // 5 + 0x3E, 0x49, 0x49, 0x49, 0x32, // 6 + 0x01, 0x01, 0x71, 0x09, 0x07, // 7 + 0x36, 0x49, 0x49, 0x49, 0x36, // 8 + 0x26, 0x49, 0x49, 0x49, 0x3E, // 9 + 0x00, 0x36, 0x36, 0x00, 0x00, // : + 0x00, 0x56, 0x36, 0x00, 0x00, // ; + 0x08, 0x14, 0x22, 0x41, 0x00, // < + 0x14, 0x14, 0x14, 0x14, 0x14, // = + 0x00, 0x41, 0x22, 0x14, 0x08, // > + 0x02, 0x01, 0x51, 0x09, 0x06, // ? + + 0x3E, 0x41, 0x59, 0x55, 0x5E, // @ + 0x7E, 0x09, 0x09, 0x09, 0x7E, // A + 0x7F, 0x49, 0x49, 0x49, 0x36, // B + 0x3E, 0x41, 0x41, 0x41, 0x22, // C + 0x7F, 0x41, 0x41, 0x41, 0x3E, // D + 0x7F, 0x49, 0x49, 0x49, 0x41, // E + 0x7F, 0x09, 0x09, 0x09, 0x01, // F + 0x3E, 0x41, 0x41, 0x49, 0x3A, // G + 0x7F, 0x08, 0x08, 0x08, 0x7F, // H + 0x00, 0x41, 0x7F, 0x41, 0x00, // I + 0x30, 0x40, 0x40, 0x40, 0x3F, // J + 0x7F, 0x08, 0x14, 0x22, 0x41, // K + 0x7F, 0x40, 0x40, 0x40, 0x40, // L + 0x7F, 0x02, 0x0C, 0x02, 0x7F, // M + 0x7F, 0x02, 0x04, 0x08, 0x7F, // N + 0x3E, 0x41, 0x41, 0x41, 0x3E, // O + + 0x7F, 0x09, 0x09, 0x09, 0x06, // P + 0x1E, 0x21, 0x21, 0x21, 0x5E, // Q + 0x7F, 0x09, 0x09, 0x09, 0x76, // R + 0x26, 0x49, 0x49, 0x49, 0x32, // S + 0x01, 0x01, 0x7F, 0x01, 0x01, // T + 0x3F, 0x40, 0x40, 0x40, 0x3F, // U + 0x1F, 0x20, 0x40, 0x20, 0x1F, // V + 0x7F, 0x20, 0x10, 0x20, 0x7F, // W + 0x41, 0x22, 0x1C, 0x22, 0x41, // X + 0x07, 0x08, 0x70, 0x08, 0x07, // Y + 0x61, 0x51, 0x49, 0x45, 0x43, // Z + 0x00, 0x7F, 0x41, 0x00, 0x00, // [ + 0x02, 0x04, 0x08, 0x10, 0x20, // backslash + 0x00, 0x00, 0x41, 0x7F, 0x00, // ] + 0x04, 0x02, 0x01, 0x02, 0x04, // ^ + 0x40, 0x40, 0x40, 0x40, 0x40, // _ + + 0x00, 0x01, 0x02, 0x04, 0x00, // ` + 0x20, 0x54, 0x54, 0x54, 0x78, // a + 0x7F, 0x44, 0x44, 0x44, 0x38, // b + 0x38, 0x44, 0x44, 0x44, 0x44, // c + 0x38, 0x44, 0x44, 0x44, 0x7F, // d + 0x38, 0x54, 0x54, 0x54, 0x18, // e + 0x04, 0x04, 0x7E, 0x05, 0x05, // f + 0x08, 0x54, 0x54, 0x54, 0x3C, // g + 0x7F, 0x08, 0x04, 0x04, 0x78, // h + 0x00, 0x44, 0x7D, 0x40, 0x00, // i + 0x20, 0x40, 0x44, 0x3D, 0x00, // j + 0x7F, 0x10, 0x28, 0x44, 0x00, // k + 0x00, 0x41, 0x7F, 0x40, 0x00, // l + 0x7C, 0x04, 0x78, 0x04, 0x78, // m + 0x7C, 0x08, 0x04, 0x04, 0x78, // n + 0x38, 0x44, 0x44, 0x44, 0x38, // o + + 0x7C, 0x14, 0x14, 0x14, 0x08, // p + 0x08, 0x14, 0x14, 0x14, 0x7C, // q + 0x00, 0x7C, 0x08, 0x04, 0x04, // r + 0x48, 0x54, 0x54, 0x54, 0x20, // s + 0x04, 0x04, 0x3F, 0x44, 0x44, // t + 0x3C, 0x40, 0x40, 0x20, 0x7C, // u + 0x1C, 0x20, 0x40, 0x20, 0x1C, // v + 0x3C, 0x40, 0x30, 0x40, 0x3C, // w + 0x44, 0x28, 0x10, 0x28, 0x44, // x + 0x0C, 0x50, 0x50, 0x50, 0x3C, // y + 0x44, 0x64, 0x54, 0x4C, 0x44, // z + 0x00, 0x08, 0x36, 0x41, 0x41, // { + 0x00, 0x00, 0x7F, 0x00, 0x00, // | + 0x41, 0x41, 0x36, 0x08, 0x00, // } + 0x02, 0x01, 0x02, 0x04, 0x02 // ~ +}; + +const uint8_t font8x8[] = { + 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // + 0x00,0x00,0x00,0x00,0x5F,0x00,0x00,0x00, // ! + 0x00,0x00,0x00,0x03,0x00,0x03,0x00,0x00, // " + 0x00,0x24,0x7E,0x24,0x24,0x7E,0x24,0x00, // # + 0x00,0x2E,0x2A,0x7F,0x2A,0x3A,0x00,0x00, // $ + 0x00,0x46,0x26,0x10,0x08,0x64,0x62,0x00, // % + 0x00,0x20,0x54,0x4A,0x54,0x20,0x50,0x00, // & + 0x00,0x00,0x00,0x04,0x02,0x00,0x00,0x00, // ' + 0x00,0x00,0x00,0x3C,0x42,0x00,0x00,0x00, // ( + 0x00,0x00,0x00,0x42,0x3C,0x00,0x00,0x00, // ) + 0x00,0x10,0x54,0x38,0x54,0x10,0x00,0x00, // * + 0x00,0x10,0x10,0x7C,0x10,0x10,0x00,0x00, // + + 0x00,0x00,0x00,0x80,0x60,0x00,0x00,0x00, // , + 0x00,0x10,0x10,0x10,0x10,0x10,0x00,0x00, // - + 0x00,0x00,0x00,0x60,0x60,0x00,0x00,0x00, // . + 0x00,0x40,0x20,0x10,0x08,0x04,0x00,0x00, // / + + 0x3C,0x62,0x52,0x4A,0x46,0x3C,0x00,0x00, // 0 + 0x44,0x42,0x7E,0x40,0x40,0x00,0x00,0x00, // 1 + 0x64,0x52,0x52,0x52,0x52,0x4C,0x00,0x00, // 2 + 0x24,0x42,0x42,0x4A,0x4A,0x34,0x00,0x00, // 3 + 0x30,0x28,0x24,0x7E,0x20,0x20,0x00,0x00, // 4 + 0x2E,0x4A,0x4A,0x4A,0x4A,0x32,0x00,0x00, // 5 + 0x3C,0x4A,0x4A,0x4A,0x4A,0x30,0x00,0x00, // 6 + 0x02,0x02,0x62,0x12,0x0A,0x06,0x00,0x00, // 7 + 0x34,0x4A,0x4A,0x4A,0x4A,0x34,0x00,0x00, // 8 + 0x0C,0x52,0x52,0x52,0x52,0x3C,0x00,0x00, // 9 + 0x00,0x00,0x00,0x48,0x00,0x00,0x00,0x00, // : + 0x00,0x00,0x80,0x64,0x00,0x00,0x00,0x00, // ; + 0x00,0x00,0x10,0x28,0x44,0x00,0x00,0x00, // < + 0x00,0x28,0x28,0x28,0x28,0x28,0x00,0x00, // = + 0x00,0x00,0x44,0x28,0x10,0x00,0x00,0x00, // > + 0x00,0x04,0x02,0x02,0x52,0x0A,0x04,0x00, // ? + + 0x00,0x3C,0x42,0x5A,0x56,0x5A,0x1C,0x00, // @ + 0x7C,0x12,0x12,0x12,0x12,0x7C,0x00,0x00, // A + 0x7E,0x4A,0x4A,0x4A,0x4A,0x34,0x00,0x00, // B + 0x3C,0x42,0x42,0x42,0x42,0x24,0x00,0x00, // C + 0x7E,0x42,0x42,0x42,0x24,0x18,0x00,0x00, // D + 0x7E,0x4A,0x4A,0x4A,0x4A,0x42,0x00,0x00, // E + 0x7E,0x0A,0x0A,0x0A,0x0A,0x02,0x00,0x00, // F + 0x3C,0x42,0x42,0x52,0x52,0x34,0x00,0x00, // G + 0x7E,0x08,0x08,0x08,0x08,0x7E,0x00,0x00, // H + 0x00,0x42,0x42,0x7E,0x42,0x42,0x00,0x00, // I + 0x30,0x40,0x40,0x40,0x40,0x3E,0x00,0x00, // J + 0x7E,0x08,0x08,0x14,0x22,0x40,0x00,0x00, // K + 0x7E,0x40,0x40,0x40,0x40,0x40,0x00,0x00, // L + 0x7E,0x04,0x08,0x08,0x04,0x7E,0x00,0x00, // M + 0x7E,0x04,0x08,0x10,0x20,0x7E,0x00,0x00, // N + 0x3C,0x42,0x42,0x42,0x42,0x3C,0x00,0x00, // O + + 0x7E,0x12,0x12,0x12,0x12,0x0C,0x00,0x00, // P + 0x3C,0x42,0x52,0x62,0x42,0x3C,0x00,0x00, // Q + 0x7E,0x12,0x12,0x12,0x32,0x4C,0x00,0x00, // R + 0x24,0x4A,0x4A,0x4A,0x4A,0x30,0x00,0x00, // S + 0x02,0x02,0x02,0x7E,0x02,0x02,0x02,0x00, // T + 0x3E,0x40,0x40,0x40,0x40,0x3E,0x00,0x00, // U + 0x1E,0x20,0x40,0x40,0x20,0x1E,0x00,0x00, // V + 0x3E,0x40,0x20,0x20,0x40,0x3E,0x00,0x00, // W + 0x42,0x24,0x18,0x18,0x24,0x42,0x00,0x00, // X + 0x02,0x04,0x08,0x70,0x08,0x04,0x02,0x00, // Y + 0x42,0x62,0x52,0x4A,0x46,0x42,0x00,0x00, // Z + 0x00,0x00,0x7E,0x42,0x42,0x00,0x00,0x00, // [ + 0x00,0x04,0x08,0x10,0x20,0x40,0x00,0x00, // + 0x00,0x00,0x42,0x42,0x7E,0x00,0x00,0x00, // ] + 0x00,0x08,0x04,0x7E,0x04,0x08,0x00,0x00, // ^ + 0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00, // _ + + 0x3C,0x42,0x99,0xA5,0xA5,0x81,0x42,0x3C, // ` + 0x00,0x20,0x54,0x54,0x54,0x78,0x00,0x00, // a + 0x00,0x7E,0x48,0x48,0x48,0x30,0x00,0x00, // b + 0x00,0x00,0x38,0x44,0x44,0x44,0x00,0x00, // c + 0x00,0x30,0x48,0x48,0x48,0x7E,0x00,0x00, // d + 0x00,0x38,0x54,0x54,0x54,0x48,0x00,0x00, // e + 0x00,0x00,0x00,0x7C,0x0A,0x02,0x00,0x00, // f + 0x00,0x18,0xA4,0xA4,0xA4,0xA4,0x7C,0x00, // g + 0x00,0x7E,0x08,0x08,0x08,0x70,0x00,0x00, // h + 0x00,0x00,0x00,0x48,0x7A,0x40,0x00,0x00, // i + 0x00,0x00,0x40,0x80,0x80,0x7A,0x00,0x00, // j + 0x00,0x7E,0x18,0x24,0x40,0x00,0x00,0x00, // k + 0x00,0x00,0x00,0x3E,0x40,0x40,0x00,0x00, // l + 0x00,0x7C,0x04,0x78,0x04,0x78,0x00,0x00, // m + 0x00,0x7C,0x04,0x04,0x04,0x78,0x00,0x00, // n + 0x00,0x38,0x44,0x44,0x44,0x38,0x00,0x00, // o + + 0x00,0xFC,0x24,0x24,0x24,0x18,0x00,0x00, // p + 0x00,0x18,0x24,0x24,0x24,0xFC,0x80,0x00, // q + 0x00,0x00,0x78,0x04,0x04,0x04,0x00,0x00, // r + 0x00,0x48,0x54,0x54,0x54,0x20,0x00,0x00, // s + 0x00,0x00,0x04,0x3E,0x44,0x40,0x00,0x00, // t + 0x00,0x3C,0x40,0x40,0x40,0x3C,0x00,0x00, // u + 0x00,0x0C,0x30,0x40,0x30,0x0C,0x00,0x00, // v + 0x00,0x3C,0x40,0x38,0x40,0x3C,0x00,0x00, // w + 0x00,0x44,0x28,0x10,0x28,0x44,0x00,0x00, // x + 0x00,0x1C,0xA0,0xA0,0xA0,0x7C,0x00,0x00, // y + 0x00,0x44,0x64,0x54,0x4C,0x44,0x00,0x00, // z + 0x00,0x08,0x08,0x76,0x42,0x42,0x00,0x00, // { + 0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00, // | + 0x00,0x42,0x42,0x76,0x08,0x08,0x00,0x00, // } + 0x00,0x00,0x04,0x02,0x04,0x02,0x00,0x00 // ~ +}; + +#endif diff --git a/linux_i2c.c b/linux_i2c.c new file mode 100644 index 0000000..fcfa337 --- /dev/null +++ b/linux_i2c.c @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int file_i2c = 0; + +uint8_t _i2c_init(int i2c, int dev_addr) +{ + if (file_i2c == 0) + { + char filename[32]; + sprintf(filename, "/dev/i2c-%d", i2c); // I2C bus number passed + file_i2c = open(filename, O_RDWR); + if (file_i2c < 0) + { + file_i2c = 0; + return 1; + } + if (ioctl(file_i2c, I2C_SLAVE, dev_addr) < 0) // set slave address + { + close(file_i2c); + file_i2c = 0; + return 1; + } + return 0; + } + + // assume done init already + return 0; +} + +uint8_t _i2c_close() +{ + if (file_i2c != 0) + { + close(file_i2c); + file_i2c = 0; + return 0; + } + + return 1; +} + +uint8_t _i2c_write(uint8_t* ptr, int16_t len) +{ + if (file_i2c == 0 || ptr == 0 || len <= 0) + return 1; + + int32_t rc; + rc = write(file_i2c, ptr, len); + + return 0; +} + +uint8_t _i2c_read(uint8_t *ptr, int16_t len) +{ + if (file_i2c == 0 || ptr == 0 || len <= 0) + return 1; + + int32_t rc; + rc = read(file_i2c, ptr, len); + + return 0; +} diff --git a/linux_i2c.h b/linux_i2c.h new file mode 100644 index 0000000..e77a5c6 --- /dev/null +++ b/linux_i2c.h @@ -0,0 +1,8 @@ +#ifndef __LINUX_I2C_H__ +#define __LINUX_I2C_H__ + +uint8_t _i2c_init(int i2c, int dev_addr); +uint8_t _i2c_close(); +uint8_t _i2c_write(uint8_t* ptr, int16_t len); +uint8_t _i2c_read(uint8_t *ptr, int16_t len); +#endif diff --git a/main.c b/main.c new file mode 100644 index 0000000..1b0ec69 --- /dev/null +++ b/main.c @@ -0,0 +1,224 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void print_help() +{ + printf("help message\n\n"); + printf("-I\t\tinit oled (128x32 or 128x64 or 64x48)\n"); + printf("-c\t\tclear (line number or all)\n"); + printf("-d\t\t0/display off 1/display on\n"); + printf("-f\t\t0/small font 5x7 1/normal font 8x8 (default normal font)\n"); + printf("-h\t\thelp message\n"); + printf("-i\t\t0/normal oled 1/invert oled\n"); + printf("-l\t\tput your line to display\n"); + printf("-m\t\tput your strings to oled\n"); + printf("-n\t\tI2C device node address (0,1,2..., default 0)\n"); + printf("-r\t\t0/normal 180/rotate\n"); + printf("-x\t\tx position\n"); + printf("-y\t\ty position\n"); +} + +int main(int argc, char **argv) +{ + uint8_t i2c_node_address = 0; + int x = -1; + int y = -1; + uint8_t line[25] = {0}; + uint8_t msg[200] = {0}; + uint8_t oled_type[10] = {0}; + int clear_line = -1; + int clear_all = -1; + int orientation = -1; + int inverted = -1; + int display = -1; + int font = 0; + + int cmd_opt = 0; + + while(cmd_opt != -1) + { + cmd_opt = getopt(argc, argv, "I:c::d:f:hi:l:m:n:r:x:y:"); + + /* Lets parse */ + switch (cmd_opt) { + case 'I': + strncpy(oled_type, optarg, sizeof(oled_type)); + break; + case 'c': + if (optarg) + { + clear_line = atoi(optarg); + } + else + { + clear_all = 1; + } + break; + case 'd': + display = atoi(optarg); + break; + case 'f': + font = atoi(optarg); + break; + case 'h': + print_help(); + return 0; + case 'i': + inverted = atoi(optarg); + break; + case 'l': + strncpy(line, optarg, sizeof(line)); + break; + case 'm': + strncpy(msg, optarg, sizeof(msg)); + break; + case 'n': + i2c_node_address = (uint8_t)atoi(optarg); + break; + case 'r': + orientation = atoi(optarg); + if (orientation != 0 && orientation != 180) + { + printf("orientation value must be 0 or 180\n"); + return 1; + } + break; + case 'x': + x = atoi(optarg); + break; + case 'y': + y = atoi(optarg); + break; + case -1: + // just ignore + break; + /* Error handle: Mainly missing arg or illegal option */ + case '?': + if (optopt == 'I') + { + printf("prams -%c missing oled type (128x64/128x32/64x48)\n", optopt); + return 1; + } + else if (optopt == 'd' || optopt == 'f' || optopt == 'i') + { + printf("prams -%c missing 0 or 1 fields\n", optopt); + return 1; + } + else if (optopt == 'l' || optopt == 'm') + { + printf("prams -%c missing string\n", optopt); + return 1; + } + else if (optopt == 'n') + { + printf("prams -%c missing 0,1,2... I2C device node number\n", optopt); + return 1; + } + else if (optopt == 'r') + { + printf("prams -%c missing 0 or 180 fields\n", optopt); + return 1; + } + else if (optopt == 'x' || optopt == 'y') + { + printf("prams -%c missing coordinate values\n", optopt); + return 1; + } + break; + default: + print_help(); + return 1; + } + } + + uint8_t rc = 0; + + // open the I2C device node + rc = ssd1306_init(i2c_node_address); + + if (rc != 0) + { + printf("no oled attached to /dev/i2c-%d\n", i2c_node_address); + return 1; + } + + // init oled module + if (oled_type[0] != 0) + { + if (strcmp(oled_type, "128x64") == 0) + rc += ssd1306_oled_default_config(64, 128); + else if (strcmp(oled_type, "128x32") == 0) + rc += ssd1306_oled_default_config(32, 128); + else if (strcmp(oled_type, "64x48") == 0) + rc += ssd1306_oled_default_config(48, 64); + } + else if (ssd1306_oled_load_resolution() != 0) + { + printf("please do init oled module with correction resolution first!\n"); + return 1; + } + + // clear display + if (clear_all > -1) + { + rc += ssd1306_oled_clear_screen(); + } + else if (clear_line > -1) + { + rc += ssd1306_oled_clear_line(clear_line); + } + + // set rotate orientation + if (orientation > -1) + { + rc += ssd1306_oled_set_rotate(orientation); + } + + // set oled inverted + if (inverted > -1) + { + rc += ssd1306_oled_display_flip(inverted); + } + + // set display on off + if (display > -1) + { + rc += ssd1306_oled_onoff(display); + } + + // set cursor XY + if (x > -1 && y > -1) + { + rc += ssd1306_oled_set_XY(x, y); + } + else if (x > -1) + { + rc += ssd1306_oled_set_X(x); + } + else if (y > -1) + { + rc += ssd1306_oled_set_Y(y); + } + + // print text + if (msg[0] != 0) + { + rc += ssd1306_oled_write_string(font, msg); + } + else if (line[0] != 0) + { + rc += ssd1306_oled_write_line(font, line); + } + + // close the I2C device node + ssd1306_end(); + + return rc; +} diff --git a/ssd1306.c b/ssd1306.c new file mode 100644 index 0000000..0e3f527 --- /dev/null +++ b/ssd1306.c @@ -0,0 +1,463 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const uint8_t init_oled_type_file[] = "/tmp/.ssd1306_oled_type"; + +static uint8_t data_buf[1024]; +static int16_t data_len = 0; +static uint8_t max_lines = 0; +static uint8_t max_columns = 0; +static uint8_t global_x = 0; +static uint8_t global_y = 0; + +uint8_t ssd1306_init(uint8_t i2c_dev) +{ + uint8_t rc; + rc = _i2c_init(i2c_dev, SSD1306_I2C_ADDR); + if (rc > 0) + return rc; + + // test i2c connection + uint8_t cmd = SSD1306_COMM_CONTROL_BYTE; + uint8_t result = 0; + _i2c_write(&cmd, 1); + _i2c_read(&result, 1); + if (result == 0) + return 1; + + return 0; +} + +uint8_t ssd1306_end() +{ + return _i2c_close(); +} + +uint8_t ssd1306_oled_onoff(uint8_t onoff) +{ + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + if (onoff == 0) + data_buf[1] = SSD1306_COMM_DISPLAY_OFF; + else + data_buf[1] = SSD1306_COMM_DISPLAY_ON; + + return _i2c_write(data_buf, 2); +} + +uint8_t ssd1306_oled_horizontal_flip(uint8_t flip) +{ + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + if (flip == 0) + data_buf[1] = SSD1306_COMM_HORIZ_NORM; + else + data_buf[1] = SSD1306_COMM_HORIZ_FLIP; + + return _i2c_write(data_buf, 2); +} + +uint8_t ssd1306_oled_display_flip(uint8_t flip) +{ + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + if (flip == 0) + data_buf[1] = SSD1306_COMM_DISP_NORM; + else + data_buf[1] = SSD1306_COMM_DISP_INVERSE; + + return _i2c_write(data_buf, 2); +} + +// 128x32 please use value 32 +// 128x64 please use value 64 +uint8_t ssd1306_oled_multiplex(uint8_t row) +{ + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + data_buf[1] = SSD1306_COMM_MULTIPLEX; + data_buf[2] = row - 1; + + return _i2c_write(data_buf, 3); +} + +// offset range 0x00~0x3f +uint8_t ssd1306_oled_vert_shift(uint8_t offset) +{ + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + data_buf[1] = SSD1306_COMM_VERT_OFFSET; + data_buf[2] = offset; + + return _i2c_write(data_buf, 3); +} + +// default value for clk is 0x80 +uint8_t ssd1306_oled_set_clock(uint8_t clk) +{ + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + data_buf[1] = SSD1306_COMM_CLK_SET; + data_buf[2] = clk; + + return _i2c_write(data_buf, 3); +} + +// default value for precharge is 0xf1 +uint8_t ssd1306_oled_set_precharge(uint8_t precharge) +{ + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + data_buf[1] = SSD1306_COMM_PRECHARGE; + data_buf[2] = precharge; + + return _i2c_write(data_buf, 3); +} + +// default value for deselect is 0x40 +uint8_t ssd1306_oled_set_deselect(uint8_t voltage) +{ + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + data_buf[1] = SSD1306_COMM_DESELECT_LV; + data_buf[2] = voltage; + + return _i2c_write(data_buf, 3); +} + +// default value for com pin is 0x02 +uint8_t ssd1306_oled_set_com_pin(uint8_t value) +{ + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + data_buf[1] = SSD1306_COMM_COM_PIN; + data_buf[2] = value; + + return _i2c_write(data_buf, 3); +} + +// default value use SSD1306_PAGE_MODE +uint8_t ssd1306_oled_set_mem_mode(uint8_t mode) +{ + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + data_buf[1] = SSD1306_COMM_MEMORY_MODE; + data_buf[2] = mode; + + return _i2c_write(data_buf, 3); +} + +uint8_t ssd1306_oled_set_col(uint8_t start, uint8_t end) +{ + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + data_buf[1] = SSD1306_COMM_SET_COL_ADDR; + data_buf[2] = start; + data_buf[3] = end; + + return _i2c_write(data_buf, 4); +} + +uint8_t ssd1306_oled_set_page(uint8_t start, uint8_t end) +{ + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + data_buf[1] = SSD1306_COMM_SET_PAGE_ADDR; + data_buf[2] = start; + data_buf[3] = end; + + return _i2c_write(data_buf, 4); +} + +// default contrast value is 0x7f +uint8_t ssd1306_oled_set_constrast(uint8_t value) +{ + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + data_buf[1] = SSD1306_COMM_CONTRAST; + data_buf[2] = value; + + return _i2c_write(data_buf, 3); +} + +uint8_t ssd1306_oled_scroll_onoff(uint8_t onoff) +{ + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + if (onoff == 0) + data_buf[1] = SSD1306_COMM_DISABLE_SCROLL; + else + data_buf[1] = SSD1306_COMM_ENABLE_SCROLL; + + return _i2c_write(data_buf, 2); +} + +uint8_t ssd1306_oled_set_X(uint8_t x) +{ + if (x >= max_columns) + return 1; + + global_x = x; + + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + data_buf[1] = SSD1306_COMM_LOW_COLUMN | (x & 0x0f); + data_buf[2] = SSD1306_COMM_HIGH_COLUMN | ((x >> 4) & 0x0f); + + return _i2c_write(data_buf, 3); +} + +uint8_t ssd1306_oled_set_Y(uint8_t y) +{ + if (y >= (max_lines / 8)) + return 1; + + global_y = y; + + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + data_buf[1] = SSD1306_COMM_PAGE_NUMBER | (y & 0x0f); + + return _i2c_write(data_buf, 2); +} + +uint8_t ssd1306_oled_set_XY(uint8_t x, uint8_t y) +{ + if (x >= max_columns || y >= (max_lines / 8)) + return 1; + + global_x = x; + global_y = y; + + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + data_buf[1] = SSD1306_COMM_PAGE_NUMBER | (y & 0x0f); + + data_buf[2] = SSD1306_COMM_LOW_COLUMN | (x & 0x0f); + + data_buf[3] = SSD1306_COMM_HIGH_COLUMN | ((x >> 4) & 0x0f); + + return _i2c_write(data_buf, 4); +} + +uint8_t ssd1306_oled_set_rotate(uint8_t degree) +{ + // only degree 0 and 180 + if (degree == 0) + { + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + data_buf[1] = SSD1306_COMM_HORIZ_FLIP; + data_buf[2] = SSD1306_COMM_SCAN_REVS; + + return _i2c_write(data_buf, 3); + } + else if (degree == 180) + { + data_buf[0] = SSD1306_COMM_CONTROL_BYTE; + data_buf[1] = SSD1306_COMM_HORIZ_NORM; + data_buf[2] = SSD1306_COMM_SCAN_NORM; + + return _i2c_write(data_buf, 3); + } + else + return 1; +} + +uint8_t ssd1306_oled_default_config(uint8_t oled_lines, uint8_t oled_columns) +{ + if (oled_lines != SSD1306_128_64_LINES && oled_lines != SSD1306_128_32_LINES && SSD1306_64_48_LINES) + oled_lines = SSD1306_128_64_LINES; + + if (oled_columns != SSD1306_128_64_COLUMNS && oled_lines != SSD1306_128_32_COLUMNS && SSD1306_64_48_COLUMNS) + oled_columns = SSD1306_128_64_COLUMNS; + + max_lines = oled_lines; + max_columns = oled_columns; + global_x = 0; + global_y = 0; + + if (ssd1306_oled_save_resolution(max_columns, max_lines) != 0) + return 1; + + uint16_t i = 0; + data_buf[i++] = SSD1306_COMM_CONTROL_BYTE; //command control byte + data_buf[i++] = SSD1306_COMM_DISPLAY_OFF; //display off + data_buf[i++] = SSD1306_COMM_DISP_NORM; //Set Normal Display (default) + data_buf[i++] = SSD1306_COMM_CLK_SET; //SETDISPLAYCLOCKDIV + data_buf[i++] = 0x80; // the suggested ratio 0x80 + data_buf[i++] = SSD1306_COMM_MULTIPLEX; //SSD1306_SETMULTIPLEX + data_buf[i++] = oled_lines - 1; // height is 32 or 64 (always -1) + data_buf[i++] = SSD1306_COMM_VERT_OFFSET; //SETDISPLAYOFFSET + data_buf[i++] = 0; //no offset + data_buf[i++] = SSD1306_COMM_START_LINE; //SETSTARTLINE + data_buf[i++] = SSD1306_COMM_CHARGE_PUMP; //CHARGEPUMP + data_buf[i++] = 0x14; //turn on charge pump + data_buf[i++] = SSD1306_COMM_MEMORY_MODE; //MEMORYMODE + data_buf[i++] = SSD1306_PAGE_MODE; // page mode + data_buf[i++] = SSD1306_COMM_HORIZ_NORM; //SEGREMAP Mirror screen horizontally (A0) + data_buf[i++] = SSD1306_COMM_SCAN_NORM; //COMSCANDEC Rotate screen vertically (C0) + data_buf[i++] = SSD1306_COMM_COM_PIN; //HARDWARE PIN + data_buf[i++] = 0x02; //normal + data_buf[i++] = SSD1306_COMM_CONTRAST; //SETCONTRAST + data_buf[i++] = 0x7f; // default contract value + data_buf[i++] = SSD1306_COMM_PRECHARGE; //SETPRECHARGE + data_buf[i++] = 0xf1; // default precharge value + data_buf[i++] = SSD1306_COMM_DESELECT_LV; //SETVCOMDETECT + data_buf[i++] = 0x40; // default deselect value + data_buf[i++] = SSD1306_COMM_RESUME_RAM; //DISPLAYALLON_RESUME + data_buf[i++] = SSD1306_COMM_DISP_NORM; //NORMALDISPLAY + data_buf[i++] = SSD1306_COMM_DISPLAY_ON; //DISPLAY ON + data_buf[i++] = SSD1306_COMM_DISABLE_SCROLL;//Stop scroll + + return _i2c_write(data_buf, i); +} + +uint8_t ssd1306_oled_write_line(uint8_t size, uint8_t* ptr) +{ + uint16_t i = 0; + uint16_t index = 0; + uint8_t* font_table = 0; + uint8_t font_table_width = 0; + + if (ptr == 0) + return 1; + + if (size == SSD1306_FONT_SMALL) // 5x7 + { + font_table = (uint8_t*)font5x7; + font_table_width = 5; + } + else if (size == SSD1306_FONT_NORMAL) // 8x8 + { + font_table = (uint8_t*)font8x8; + font_table_width = 8; + } + else + return 1; + + data_buf[i++] = SSD1306_DATA_CONTROL_BYTE; + + // font table range in ascii table is from 0x20(space) to 0x7e(~) + while (ptr[index] != 0 && i <= 1024) + { + if ((ptr[index] < ' ') || (ptr[index] > '~')) + return 1; + + uint8_t* font_ptr = &font_table[(ptr[index] - 0x20) * font_table_width]; + uint8_t j = 0; + for (j = 0; j < font_table_width; j++) + { + data_buf[i++] = font_ptr[j]; + if (i > 1024) + return 1; + } + // insert 1 col space for small font size) + if (size == SSD1306_FONT_SMALL) + data_buf[i++] = 0x00; + index++; + } + + return _i2c_write(data_buf, i); +} + +uint8_t ssd1306_oled_write_string(uint8_t size, uint8_t* ptr) +{ + uint8_t rc = 0; + + if (ptr == 0) + return 1; + + uint8_t* line = 0; + uint8_t* cr = 0; + uint8_t buf[20]; + + line = ptr; + do { + memset(buf, 0, 20); + cr = strstr(line, "\\n"); + if (cr != NULL) + { + strncpy(buf, line, cr - line); + } + else + { + strcpy(buf, line); + } + + // set cursor position + ssd1306_oled_set_XY(global_x, global_y); + rc += ssd1306_oled_write_line(size, buf); + + if (cr != NULL) + { + line = &cr[2]; + global_x = 0; + global_y++; + if (global_y >= (max_lines / 8)) + global_y = 0; + } + else + line = NULL; + + }while (line != NULL); + + return rc; +} + +uint8_t ssd1306_oled_clear_line(uint8_t row) +{ + uint8_t i; + if (row >= (max_lines / 8)) + return 1; + + ssd1306_oled_set_XY(0, row); + data_buf[0] = SSD1306_DATA_CONTROL_BYTE; + for (i = 0; i < max_columns; i++) + data_buf[i+1] = 0x00; + + return _i2c_write(data_buf, 1 + max_columns); +} + +uint8_t ssd1306_oled_clear_screen() +{ + uint8_t rc = 0; + uint8_t i; + + for (i = 0; i < (max_lines / 8); i++) + { + rc += ssd1306_oled_clear_line(i); + } + + return rc; +} + +uint8_t ssd1306_oled_save_resolution(uint8_t column, uint8_t row) +{ + FILE* fp; + + fp = fopen(init_oled_type_file, "w"); + + if (fp == NULL) + { + // file create failed + return 1; + } + + fprintf(fp, "%hhux%hhu", column, row); + fclose(fp); + + return 0; +} + +uint8_t ssd1306_oled_load_resolution() +{ + FILE* fp; + + fp = fopen(init_oled_type_file, "r"); + + if (fp == NULL) + { + // file not exists + return 1; + } + + // file exists + fscanf(fp, "%hhux%hhu", &max_columns, &max_lines); + fclose(fp); + + return 0; +} diff --git a/ssd1306.h b/ssd1306.h new file mode 100644 index 0000000..a85d333 --- /dev/null +++ b/ssd1306.h @@ -0,0 +1,85 @@ +#ifndef __SSD1306_H__ +#define __SSD1306_H__ + +#define SSD1306_I2C_ADDR 0x3c + +#define SSD1306_COMM_CONTROL_BYTE 0x00 +#define SSD1306_DATA_CONTROL_BYTE 0x40 + +#define SSD1306_COMM_DISPLAY_OFF 0xae +#define SSD1306_COMM_DISPLAY_ON 0xaf +#define SSD1306_COMM_HORIZ_NORM 0xa0 +#define SSD1306_COMM_HORIZ_FLIP 0xa1 +#define SSD1306_COMM_RESUME_RAM 0xa4 +#define SSD1306_COMM_IGNORE_RAM 0xa5 +#define SSD1306_COMM_DISP_NORM 0xa6 +#define SSD1306_COMM_DISP_INVERSE 0xa7 +#define SSD1306_COMM_MULTIPLEX 0xa8 +#define SSD1306_COMM_VERT_OFFSET 0xd3 +#define SSD1306_COMM_CLK_SET 0xd5 +#define SSD1306_COMM_PRECHARGE 0xd9 +#define SSD1306_COMM_COM_PIN 0xda +#define SSD1306_COMM_DESELECT_LV 0xdb +#define SSD1306_COMM_CONTRAST 0x81 +#define SSD1306_COMM_DISABLE_SCROLL 0x2e +#define SSD1306_COMM_ENABLE_SCROLL 0x2f +#define SSD1306_COMM_PAGE_NUMBER 0xb0 +#define SSD1306_COMM_LOW_COLUMN 0x00 +#define SSD1306_COMM_HIGH_COLUMN 0x10 + +#define SSD1306_COMM_START_LINE 0x40 + +#define SSD1306_COMM_CHARGE_PUMP 0x8d + +#define SSD1306_COMM_SCAN_NORM 0xc0 +#define SSD1306_COMM_SCAN_REVS 0xc8 + +#define SSD1306_COMM_MEMORY_MODE 0x20 +#define SSD1306_COMM_SET_COL_ADDR 0x21 +#define SSD1306_COMM_SET_PAGE_ADDR 0x22 + +#define SSD1306_HORI_MODE 0x00 +#define SSD1306_VERT_MODE 0x01 +#define SSD1306_PAGE_MODE 0x02 + +#define SSD1306_FONT_SMALL 0x00 +#define SSD1306_FONT_NORMAL 0x01 + +#define SSD1306_128_64_LINES 64 +#define SSD1306_128_32_LINES 32 +#define SSD1306_64_48_LINES 48 + +#define SSD1306_128_64_COLUMNS 128 +#define SSD1306_128_32_COLUMNS 128 +#define SSD1306_64_48_COLUMNS 64 + + +uint8_t ssd1306_init(uint8_t i2c_dev); +uint8_t ssd1306_end(); +uint8_t ssd1306_oled_onoff(uint8_t onoff); +uint8_t ssd1306_oled_horizontal_flip(uint8_t flip); +uint8_t ssd1306_oled_display_flip(uint8_t flip); +uint8_t ssd1306_oled_multiplex(uint8_t row); +uint8_t ssd1306_oled_vert_shift(uint8_t offset); +uint8_t ssd1306_oled_set_clock(uint8_t clk); +uint8_t ssd1306_oled_set_precharge(uint8_t precharge); +uint8_t ssd1306_oled_set_deselect(uint8_t voltage); +uint8_t ssd1306_oled_set_com_pin(uint8_t value); +uint8_t ssd1306_oled_set_mem_mode(uint8_t mode); +uint8_t ssd1306_oled_set_col(uint8_t start, uint8_t end); +uint8_t ssd1306_oled_set_page(uint8_t start, uint8_t end); +uint8_t ssd1306_oled_set_constrast(uint8_t value); +uint8_t ssd1306_oled_scroll_onoff(uint8_t onoff); +uint8_t ssd1306_oled_set_X(uint8_t x); +uint8_t ssd1306_oled_set_Y(uint8_t y); +uint8_t ssd1306_oled_set_XY(uint8_t x, uint8_t y); +uint8_t ssd1306_oled_set_rotate(uint8_t degree); +uint8_t ssd1306_oled_default_config(uint8_t oled_lines, uint8_t oled_columns); +uint8_t ssd1306_oled_write_line(uint8_t size, uint8_t* ptr); +uint8_t ssd1306_oled_write_string(uint8_t size, uint8_t* ptr); +uint8_t ssd1306_oled_clear_line(uint8_t row); +uint8_t ssd1306_oled_clear_screen(); +uint8_t ssd1306_oled_save_resolution(uint8_t column, uint8_t row); +uint8_t ssd1306_oled_load_resolution(); + +#endif