/*********************************************************************
This is a library for our Monochrome OLEDs based on SSD1306 drivers
Pick one up today in the adafruit shop!
------> http://www.adafruit.com/category/63_98
These displays use SPI to communicate, 4 or 5 pins are required to
interface
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
Written by Limor Fried/Ladyada for Adafruit Industries.
BSD license, check license.txt for more information
All text above, and the splash screen below must be included in any redistribution
This code is taken from the ADAfruit library - it is used for playing with an OLED screen
*********************************************************************/
#include <stdint.h>
#include <string.h>
#include "SSD1306.h"
#include "stm32l1xx_hal.h"
#define swap(x,y) { typeof(x)t = x; x=y; y=t; }
#define abs(x) ((x)>0?(x):-(x))
static uint8_t rotation = 0;
const uint16_t WIDTH = SSD1306_LCDWIDTH;
const uint16_t HEIGHT = SSD1306_LCDHEIGHT;
extern SPI_HandleTypeDef hspi1;
// the memory buffer for the LCD
// pointer to the current display - affects buffer used and also chipselect
static int cd = 0;
uint8_t display_buffer[MAX_PHYS_DISPLAYS][SSD1306_LCDHEIGHT * SSD1306_LCDWIDTH
/ 8];
inline uint8_t * display_address(void) {
return (uint8_t *) (&display_buffer[cd]);
}
inline uint8_t getRotation(void) {
return rotation;
}
inline int16_t width(void) {
switch (rotation) {
case 0:
return WIDTH;
break;
case 1:
return WIDTH;
break;
case 2:
return HEIGHT;
break;
case 3:
return -WIDTH;
break;
}
return 0;
}
inline int16_t height(void) {
switch (rotation) {
case 0:
return HEIGHT;
break;
case 1:
return HEIGHT;
break;
case 2:
return WIDTH;
break;
case 3:
return -HEIGHT;
break;
}
return 0;
}
inline void fastSPIwrite(uint8_t d) {
uint8_t buffer[1];
buffer[0] = d;
// todo chipselect based on 'cd' buffer choice
if(cd==0)
{
HAL_GPIO_WritePin(SPI_NSS1_GPIO_Port, SPI_NSS1_Pin, GPIO_PIN_RESET);
}
if(cd==1)
{
HAL_GPIO_WritePin(SPI_NSS2_GPIO_Port, SPI_NSS2_Pin, GPIO_PIN_RESET);
}
HAL_SPI_Transmit(&hspi1, buffer, 1, 2);
if(cd==0)
{
HAL_GPIO_WritePin(SPI_NSS1_GPIO_Port, SPI_NSS1_Pin, GPIO_PIN_SET);
}
if(cd==1)
{
HAL_GPIO_WritePin(SPI_NSS2_GPIO_Port, SPI_NSS2_Pin, GPIO_PIN_SET);
}
}
// the most basic function, set a single pixel
inline void drawPixel(int16_t x, int16_t y, uint16_t color) {
if ((x < 0) || (x >= width()) || (y < 0) || (y >= height()))
return;
// check rotation, move pixel around if necessary
switch (getRotation()) {
case 1:
swap(x, y)
;
x = WIDTH - x - 1;
break;
case 2:
x = WIDTH - x - 1;
y = HEIGHT - y - 1;
break;
case 3:
swap(x, y)
;
y = HEIGHT - y - 1;
break;
}
// x is which column
switch (color) {
case BLACK:
display_buffer[cd][x + (y / 8) * SSD1306_LCDWIDTH] &= ~(1 << (y & 7));
break;
default:
case WHITE:
display_buffer[cd][x + (y / 8) * SSD1306_LCDWIDTH] |= (1 << (y & 7));
break;
case INVERT:
display_buffer[cd][x + (y / 8) * SSD1306_LCDWIDTH] ^= (1 << (y & 7));
break;
}
}
void ssd1306_begin(uint8_t vccstate, uint8_t i2caddr) {
HAL_GPIO_WritePin(SPI_RESET_GPIO_Port, SPI_RESET_Pin, GPIO_PIN_SET);
// VDD (3.3V) goes high at start, lets just chill for a ms
HAL_Delay(1);
// bring reset low
HAL_GPIO_WritePin(SPI_RESET_GPIO_Port, SPI_RESET_Pin, GPIO_PIN_RESET);
// wait 10ms
HAL_Delay(10);
// bring out of reset
HAL_GPIO_WritePin(SPI_RESET_GPIO_Port, SPI_RESET_Pin, GPIO_PIN_SET);
// turn on VCC (9V?)
for (cd = 0; cd < 2; cd++) {
select_display(cd);
#if defined SSD1306_128_32
// Init sequence for 128x32 OLED module
ssd1306_command(SSD1306_DISPLAYOFF);// 0xAE
ssd1306_command(SSD1306_SETDISPLAYCLOCKDIV);// 0xD5
ssd1306_command(0x80);// the suggested ratio 0x80
ssd1306_command(SSD1306_SETMULTIPLEX);// 0xA8
ssd1306_command(0x1F);
ssd1306_command(SSD1306_SETDISPLAYOFFSET);// 0xD3
ssd1306_command(0x0);// no offset
ssd1306_command(SSD1306_SETSTARTLINE | 0x0);// line #0
ssd1306_command(SSD1306_CHARGEPUMP);// 0x8D
if (vccstate == SSD1306_EXTERNALVCC)
{ ssd1306_command(0x10);}
else
{ ssd1306_command(0x14);}
ssd1306_command(SSD1306_MEMORYMODE); // 0x20
ssd1306_command(0x00);// 0x0 act like ks0108
ssd1306_command(SSD1306_SEGREMAP | 0x1);
ssd1306_command(SSD1306_COMSCANDEC);
ssd1306_command(SSD1306_SETCOMPINS);// 0xDA
ssd1306_command(0x02);
ssd1306_command(SSD1306_SETCONTRAST);// 0x81
ssd1306_command(0x8F);
ssd1306_command(SSD1306_SETPRECHARGE);// 0xd9
if (vccstate == SSD1306_EXTERNALVCC)
{ ssd1306_command(0x22);}
else
{ ssd1306_command(0xF1);}
ssd1306_command(SSD1306_SETVCOMDETECT); // 0xDB
ssd1306_command(0x40);
ssd1306_command(SSD1306_DISPLAYALLON_RESUME);// 0xA4
ssd1306_command(SSD1306_NORMALDISPLAY);// 0xA6
#endif
#if defined SSD1306_128_64
// Init sequence for 128x64 OLED module
ssd1306_command(SSD1306_DISPLAYOFF); // 0xAE
ssd1306_command(SSD1306_SETDISPLAYCLOCKDIV); // 0xD5
ssd1306_command(0x80); // the suggested ratio 0x80
ssd1306_command(SSD1306_SETMULTIPLEX); // 0xA8
ssd1306_command(0x3F);
ssd1306_command(SSD1306_SETDISPLAYOFFSET); // 0xD3
ssd1306_command(0x0); // no offset
ssd1306_command(SSD1306_SETSTARTLINE | 0x0); // line #0
ssd1306_command(SSD1306_CHARGEPUMP); // 0x8D
if (vccstate == SSD1306_EXTERNALVCC) {
ssd1306_command(0x10);
} else {
ssd1306_command(0x14);
}
ssd1306_command(SSD1306_MEMORYMODE); // 0x20
ssd1306_command(0x00); // 0x0 act like ks0108
ssd1306_command(SSD1306_SEGREMAP | 0x1);
ssd1306_command(SSD1306_COMSCANDEC);
ssd1306_command(SSD1306_SETCOMPINS); // 0xDA
ssd1306_command(0x12);
ssd1306_command(SSD1306_SETCONTRAST); // 0x81
if (vccstate == SSD1306_EXTERNALVCC) {
ssd1306_command(0x9F);
} else {
ssd1306_command(0xCF);
}
ssd1306_command(SSD1306_SETPRECHARGE); // 0xd9
if (vccstate == SSD1306_EXTERNALVCC) {
ssd1306_command(0x22);
} else {
ssd1306_command(0xF1);
}
ssd1306_command(SSD1306_SETVCOMDETECT); // 0xDB
ssd1306_command(0x40);
ssd1306_command(SSD1306_DISPLAYALLON_RESUME); // 0xA4
ssd1306_command(SSD1306_NORMALDISPLAY); // 0xA6
#endif
ssd1306_command(SSD1306_DISPLAYON); //--turn on oled panel
}
select_display(0);
}
void invertDisplay(uint8_t i) {
if (i) {
ssd1306_command(SSD1306_INVERTDISPLAY);
} else {
ssd1306_command(SSD1306_NORMALDISPLAY);
}
}
void ssd1306_command(uint8_t c) {
HAL_GPIO_WritePin(SPI1CD_GPIO_Port, SPI1CD_Pin, GPIO_PIN_RESET);
fastSPIwrite(c);
}
// startscrollright
// Activate a right handed scroll for rows start through stop
// Hint, the display is 16 rows tall. To scroll the whole display, run:
// display.scrollright(0x00, 0x0F)
void startscrollright(uint8_t start, uint8_t stop) {
ssd1306_command(SSD1306_RIGHT_HORIZONTAL_SCROLL);
ssd1306_command(0X00);
ssd1306_command(start);
ssd1306_command(0X00);
ssd1306_command(stop);
ssd1306_command(0X00);
ssd1306_command(0XFF);
ssd1306_command(SSD1306_ACTIVATE_SCROLL);
}
// startscrollleft
// Activate a right handed scroll for rows start through stop
// Hint, the display is 16 rows tall. To scroll the whole display, run:
// display.scrollright(0x00, 0x0F)
void startscrollleft(uint8_t start, uint8_t stop) {
ssd1306_command(SSD1306_LEFT_HORIZONTAL_SCROLL);
ssd1306_command(0X00);
ssd1306_command(start);
ssd1306_command(0X00);
ssd1306_command(stop);
ssd1306_command(0X00);
ssd1306_command(0XFF);
ssd1306_command(SSD1306_ACTIVATE_SCROLL);
}
// startscrolldiagright
// Activate a diagonal scroll for rows start through stop
// Hint, the display is 16 rows tall. To scroll the whole display, run:
// display.scrollright(0x00, 0x0F)
void startscrolldiagright(uint8_t start, uint8_t stop) {
ssd1306_command(SSD1306_SET_VERTICAL_SCROLL_AREA);
ssd1306_command(0X00);
ssd1306_command(SSD1306_LCDHEIGHT);
ssd1306_command(SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL);
ssd1306_command(0X00);
ssd1306_command(start);
ssd1306_command(0X00);
ssd1306_command(stop);
ssd1306_command(0X01);
ssd1306_command(SSD1306_ACTIVATE_SCROLL);
}
// startscrolldiagleft
// Activate a diagonal scroll for rows start through stop
// Hint, the display is 16 rows tall. To scroll the whole display, run:
// display.scrollright(0x00, 0x0F)
void startscrolldiagleft(uint8_t start, uint8_t stop) {
ssd1306_command(SSD1306_SET_VERTICAL_SCROLL_AREA);
ssd1306_command(0X00);
ssd1306_command(SSD1306_LCDHEIGHT);
ssd1306_command(SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL);
ssd1306_command(0X00);
ssd1306_command(start);
ssd1306_command(0X00);
ssd1306_command(stop);
ssd1306_command(0X01);
ssd1306_command(SSD1306_ACTIVATE_SCROLL);
}
void stopscroll(void) {
ssd1306_command(SSD1306_DEACTIVATE_SCROLL);
}
// Dim the display
// dim = true: display is dimmed
// dim = false: display is normal
void dim(uint8_t dim) {
uint8_t contrast;
if (dim) {
contrast = 0; // Dimmed display
} else {
contrast = 0xCF;
}
// the range of contrast to too small to be really useful
// it is useful to dim the display
ssd1306_command(SSD1306_SETCONTRAST);
ssd1306_command(contrast);
}
void display(void) {
ssd1306_command(SSD1306_COLUMNADDR);
ssd1306_command(0); // Column start address (0 = reset)
ssd1306_command(131); // Column end address (127 = reset)
ssd1306_command(SSD1306_PAGEADDR);
ssd1306_command(0); // Page start address (0 = reset)
ssd1306_command((SSD1306_LCDHEIGHT == 64) ? 7 : 3); // Page end address
int row;
int col = 2;
for (row = 0; row < SSD1306_LCDHEIGHT / 8; row++) {
// set the cursor to
ssd1306_command(0xB0 + row); //set page address
ssd1306_command(col & 0xf); //set lower column address
ssd1306_command(0x10 | (col >> 4)); //set higher column address
HAL_GPIO_WritePin(SPI1CD_GPIO_Port, SPI1CD_Pin, GPIO_PIN_SET);
if(cd==0)
{
HAL_GPIO_WritePin(SPI_NSS1_GPIO_Port, SPI_NSS1_Pin, GPIO_PIN_RESET);
}
if(cd==1)
{
HAL_GPIO_WritePin(SPI_NSS2_GPIO_Port, SPI_NSS2_Pin, GPIO_PIN_RESET);
}
HAL_SPI_Transmit(&hspi1,
(uint8_t *) (&display_buffer[cd]) + row * SSD1306_LCDWIDTH,
SSD1306_LCDWIDTH, 100);
if(cd==0)
{
HAL_GPIO_WritePin(SPI_NSS1_GPIO_Port, SPI_NSS1_Pin, GPIO_PIN_SET);
}
if(cd==1)
{
HAL_GPIO_WritePin(SPI_NSS2_GPIO_Port, SPI_NSS2_Pin, GPIO_PIN_SET);
}
HAL_GPIO_WritePin(SPI1CD_GPIO_Port, SPI1CD_Pin, GPIO_PIN_RESET);
}
}
// clear everything
void clearDisplay(void) {
memset(&display_buffer
[cd
], 0, (SSD1306_LCDWIDTH
* SSD1306_LCDHEIGHT
/ 8));
}
void drawFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) {
boolean bSwap = false;
switch (rotation) {
case 0:
// 0 degree rotation, do nothing
break;
case 1:
// 90 degree rotation, swap x & y for rotation, then invert x
bSwap = true;
swap(x, y)
;
x = WIDTH - x - 1;
break;
case 2:
// 180 degree rotation, invert x and y - then shift y around for height.
x = WIDTH - x - 1;
y = HEIGHT - y - 1;
x -= (w - 1);
break;
case 3:
// 270 degree rotation, swap x & y for rotation, then invert y and adjust y for w (not to become h)
bSwap = true;
swap(x, y)
;
y = HEIGHT - y - 1;
y -= (w - 1);
break;
}
if (bSwap) {
drawFastVLineInternal(x, y, w, color);
} else {
drawFastHLineInternal(x, y, w, color);
}
}
void drawFastHLineInternal(int16_t x, int16_t y, int16_t w, uint16_t color) {
// Do bounds/limit checks
if (y < 0 || y >= HEIGHT) {
return;
}
// make sure we don't try to draw below 0
if (x < 0) {
w += x;
x = 0;
}
// make sure we don't go off the edge of the display
if ((x + w) > WIDTH) {
w = (HEIGHT - x);
}
// if our width is now negative, punt
if (w <= 0) {
return;
}
// set up the pointer for movement through the buffer
register uint8_t *pBuf = display_address();
// adjust the buffer pointer for the current row
pBuf += ((y / 8) * SSD1306_LCDWIDTH);
// and offset x columns in
pBuf += x;
register uint8_t mask = 1 << (y & 7);
if (color == WHITE) {
while (w--) {
*pBuf++ |= mask;
}
} else {
mask = ~mask;
while (w--) {
*pBuf++ &= mask;
}
}
}
void drawFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) {
boolean bSwap = false;
switch (rotation) {
case 0:
break;
case 1:
// 90 degree rotation, swap x & y for rotation, then invert x and adjust x for h (now to become w)
bSwap = true;
swap(x, y)
;
x = WIDTH - x - 1;
x -= (h - 1);
break;
case 2:
// 180 degree rotation, invert x and y - then shift y around for height.
x = WIDTH - x - 1;
y = HEIGHT - y - 1;
y -= (h - 1);
break;
case 3:
// 270 degree rotation, swap x & y for rotation, then invert y
bSwap = true;
swap(x, y)
;
y = HEIGHT - y - 1;
break;
}
if (bSwap) {
drawFastHLineInternal(x, y, h, color);
} else {
drawFastVLineInternal(x, y, h, color);
}
}
void drawFastVLineInternal(int16_t x, int16_t __y, int16_t __h, uint16_t color) {
// do nothing if we're off the left or right side of the screen
if (x < 0 || x >= WIDTH) {
return;
}
// make sure we don't try to draw below 0
if (__y < 0) {
// __y is negative, this will subtract enough from __h to account for __y being 0
__h += __y;
__y = 0;
}
// make sure we don't go past the height of the display
if ((__y + __h) > HEIGHT) {
__h = (HEIGHT - __y);
}
// if our height is now negative, punt
if (__h <= 0) {
return;
}
// this display doesn't need ints for coordinates, use local byte registers for faster juggling
register uint8_t y = __y;
register uint8_t h = __h;
// set up the pointer for fast movement through the buffer
register uint8_t *pBuf = display_address();
// adjust the buffer pointer for the current row
pBuf += ((y / 8) * SSD1306_LCDWIDTH);
// and offset x columns in
pBuf += x;
// do the first partial byte, if necessary - this requires some masking
register uint8_t mod = (y & 7);
if (mod) {
// mask off the high n bits we want to set
mod = 8 - mod;
// note - lookup table results in a nearly 10% performance improvement in fill* functions
// register uint8_t mask = ~(0xFF >> (mod));
static uint8_t premask[8] = { 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC,
0xFE };
register uint8_t mask = premask[mod];
// adjust the mask if we're not going to reach the end of this byte
if (h < mod) {
mask &= (0XFF >> (mod - h));
}
if (color == WHITE) {
*pBuf |= mask;
} else {
*pBuf &= ~mask;
}
// fast exit if we're done here!
if (h < mod) {
return;
}
h -= mod;
pBuf += SSD1306_LCDWIDTH;
}
// write solid bytes while we can - effectively doing 8 rows at a time
if (h >= 8) {
// store a local value to work with
register uint8_t val = (color == WHITE) ? 255 : 0;
do {
// write our value in
*pBuf = val;
// adjust the buffer forward 8 rows worth of data
pBuf += SSD1306_LCDWIDTH;
// adjust h & y (there's got to be a faster way for me to do this, but this should still help a fair bit for now)
h -= 8;
} while (h >= 8);
}
// now do the final partial byte, if necessary
if (h) {
mod = h & 7;
// this time we want to mask the low bits of the byte, vs the high bits we did above
// register uint8_t mask = (1 << mod) - 1;
// note - lookup table results in a nearly 10% performance improvement in fill* functions
static uint8_t postmask[8] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F,
0x7F };
register uint8_t mask = postmask[mod];
if (color == WHITE) {
*pBuf |= mask;
} else {
*pBuf &= ~mask;
}
}
}
/* using Bresenham draw algorithm */
void drawLine(int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint8_t color) {
int16_t x, y, dx, //deltas
dy, dx2, //scaled deltas
dy2, ix, //increase rate on the x axis
iy, //increase rate on the y axis
err; //the error term
uint16_t i; //looping variable
// identify the first pixel
x = x1;
y = y1;
// difference between starting and ending points
dx = x2 - x1;
dy = y2 - y1;
// calculate direction of the vector and store in ix and iy
if (dx >= 0)
ix = 1;
if (dx < 0) {
ix = -1;
}
if (dy >= 0)
iy = 1;
if (dy < 0) {
iy = -1;
}
// scale deltas and store in dx2 and dy2
dx2 = dx * 2;
dy2 = dy * 2;
// all variables are set and it's time to enter the main loop.
if (dx > dy) // dx is the major axis
{
// initialize the error term
err = dy2 - dx;
for (i = 0; i <= dx; i++) {
drawPixel(x, y, color);
if (err >= 0) {
err -= dx2;
y += iy;
}
err += dy2;
x += ix;
}
}
else // dy is the major axis
{
// initialize the error term
err = dx2 - dy;
for (i = 0; i <= dy; i++) {
drawPixel(x, y, color);
if (err >= 0) {
err -= dy2;
x += ix;
}
err += dx2;
y += iy;
}
}
}
void select_display(int i) {
if (i < MAX_PHYS_DISPLAYS) {
cd = i;
}
}