/*
 * SSD1309.c
 *
 *  Purpose : OLED display driver for SSD1309-based display modules using the SPI bus interface
 *  Target : STM32
 *  Created on: Jan 11, 2018
 *  Author: Nefastor (nefastor.com)
 *
 *  	Note : this driver is also compatible with SSD1306-based displays
 *
 *      This is a DEBUG / R&D library with several hard-coded settings you may need to customize and/or apply :
 *
 *      *	Expects the SPI controller to be set at "Transmit Only Master" with the following settings :
 *      	- Motorola frame, 8-bit, MSB first
 *      	- CPOL "low", CPHA "1 Edge"
 *      	- Software NSS
 *      	- Baud rate : up to 9 Mbps for SSD1309 or up to 30 MBps for SSD1306
 *      *	Expects DMA to be setup to allow "Memory to SPI TX" transfers with the following settings :
 *      	- Normal mode
 *      	- Increment memory address only
 *      	- Data width : byte
 *      *	Expects a GPIO output pin named OLED_NSS (OLED Slave Select). Default value : high.
 *      *	Expects a GPIO output pin named OLED_RESET. Default value : low.
 *      *	Expects a GPIO output pin named OLED_DC (OLED Data / Command). Default value : low.
 *
 *		The oled_refresh() function must be called from your project's SysTick handler (Core\Src\stm32xxxx_it.c)
 *
 *      On the larger STM32 microcontrollers there may be multiple internal buses and multiple RAM blocks. For DMA
 *      to work, the frame buffer must be located in a RAM block that shares the same bus as the SPI controller
 *      you intend to use. It may be necessary to force the linker's hand to make that happen. For example, you
 *      can use the following code :
 *
 *      __attribute__ ((section (".sram4")))
 *
 *      It may be necessary to also edit your project's linker file to add the desired RAM block's definition.
 *
 *      This driver is easy to use :
 *
 *      *	First, call the oled_init() function and pass it the handle of your SPI controller as well as the
 *          desired frame rate. For example, if you're using SPI2 and 30 FPS, call oled_init(&hspi2, 30);
 *      *	The set_pixel() function lets you set individual pixels. Use if for drawing graphics.
 *      *	The print() function lets you print a character string.
 *
 *      Enjoy !
 *
 */

/************** Includes ***********************************************/

#include "SSD1309.h"
#include <string.h>

/************** Globals ************************************************/

// Handle of the SPI controller to which the display is connected
static SPI_HandleTypeDef *oled_spi = NULL;

// Frame buffer. Must be accessible to the same DMA controller as the SPI controller used to drive the OLED
static uint8_t frame_buffer[1024];

// Display refreshing interval in system ticks (milliseconds)
static int frame_interval;

// Constant : initialization parameters to be sent to the OLED only once at the start of operation
// Note : some are default value according to the datasheet but must be sent anyway or the display won't work
static const uint8_t oled_param[] = {
		0xAE,		// Display off during initialization
		0xD5,		// Display clock divider :
		0x80,		// the suggested ratio 0x80
		0xA8,		// MUX mode
		0x3F,
		0xD3,		// Display offset
		0x00,
		0x40,		// Start line
		0x8D,		// Charge pump settings
		0x14,
		0x20,		// Memory mode :
		0x00,		// Horizontal mode
		0x21,		// Column indices
		0x00,			// First column
		0x7F,		// Last column (127)
		0xC8,		// Vertical orientation : C0 or C8 to flip the screen vertically. Combine with A0/A1 respectively.
		0xA1,		// Horizontal orientation : A0 or A1 to flip the screen left-right.
		0xDA,		// Directions remapping
		0x12,
		0x81,		// Set brightness (0-255). Note : this works, but the effect is almost imperceptible.
		0x7F,		// 0x7F is the default value
		0xD9,		// Precharge settings : leave to default
	    0xF1,
		0xDB,		// "Set Vcomm Deselect Level" (don't touch that) :
		0x40,		// appears to correspond to a value close to VCC ??? (undocumented)
		0xA4,		// Resume internal RAM to OLED operation
		0xA6,		// Normal or video-inverse display (A6 or A7)
		0xAF		// Turn on OLED panel
};

// Standard ASCII 5x7 font. Stored in Flash to save RAM
// Defines pixel maps for ASCII characters 0x20 to 0x7F (32-127)
static const uint8_t Font5x7[] = {
	0x00, 0x00, 0x00, 0x00, 0x00,// (space)
	0x00, 0x00, 0x5F, 0x00, 0x00,// !
	0x00, 0x07, 0x00, 0x07, 0x00,// "
	0x14, 0x7F, 0x14, 0x7F, 0x14,// #
	0x24, 0x2A, 0x7F, 0x2A, 0x12,// $
	0x23, 0x13, 0x08, 0x64, 0x62,// %
	0x36, 0x49, 0x55, 0x22, 0x50,// &
	0x00, 0x05, 0x03, 0x00, 0x00,// '
	0x00, 0x1C, 0x22, 0x41, 0x00,// (
	0x00, 0x41, 0x22, 0x1C, 0x00,// )
	0x08, 0x2A, 0x1C, 0x2A, 0x08,// *
	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, 0x42, 0x7F, 0x40, 0x00,// 1
	0x42, 0x61, 0x51, 0x49, 0x46,// 2
	0x21, 0x41, 0x45, 0x4B, 0x31,// 3
	0x18, 0x14, 0x12, 0x7F, 0x10,// 4
	0x27, 0x45, 0x45, 0x45, 0x39,// 5
	0x3C, 0x4A, 0x49, 0x49, 0x30,// 6
	0x01, 0x71, 0x09, 0x05, 0x03,// 7
	0x36, 0x49, 0x49, 0x49, 0x36,// 8
	0x06, 0x49, 0x49, 0x29, 0x1E,// 9
	0x00, 0x36, 0x36, 0x00, 0x00,// :
	0x00, 0x56, 0x36, 0x00, 0x00,// ;
	0x00, 0x08, 0x14, 0x22, 0x41,// <
	0x14, 0x14, 0x14, 0x14, 0x14,// =
	0x41, 0x22, 0x14, 0x08, 0x00,// >
	0x02, 0x01, 0x51, 0x09, 0x06,// ?
	0x32, 0x49, 0x79, 0x41, 0x3E,// @
	0x7E, 0x11, 0x11, 0x11, 0x7E,// A
	0x7F, 0x49, 0x49, 0x49, 0x36,// B
	0x3E, 0x41, 0x41, 0x41, 0x22,// C
	0x7F, 0x41, 0x41, 0x22, 0x1C,// D
	0x7F, 0x49, 0x49, 0x49, 0x41,// E
	0x7F, 0x09, 0x09, 0x01, 0x01,// F
	0x3E, 0x41, 0x41, 0x51, 0x32,// G
	0x7F, 0x08, 0x08, 0x08, 0x7F,// H
	0x00, 0x41, 0x7F, 0x41, 0x00,// I
	0x20, 0x40, 0x41, 0x3F, 0x01,// J
	0x7F, 0x08, 0x14, 0x22, 0x41,// K
	0x7F, 0x40, 0x40, 0x40, 0x40,// L
	0x7F, 0x02, 0x04, 0x02, 0x7F,// M
	0x7F, 0x04, 0x08, 0x10, 0x7F,// N
	0x3E, 0x41, 0x41, 0x41, 0x3E,// O
	0x7F, 0x09, 0x09, 0x09, 0x06,// P
	0x3E, 0x41, 0x51, 0x21, 0x5E,// Q
	0x7F, 0x09, 0x19, 0x29, 0x46,// R
	0x46, 0x49, 0x49, 0x49, 0x31,// S
	0x01, 0x01, 0x7F, 0x01, 0x01,// T
	0x3F, 0x40, 0x40, 0x40, 0x3F,// U
	0x1F, 0x20, 0x40, 0x20, 0x1F,// V
	0x7F, 0x20, 0x18, 0x20, 0x7F,// W
	0x63, 0x14, 0x08, 0x14, 0x63,// X
	0x03, 0x04, 0x78, 0x04, 0x03,// Y
	0x61, 0x51, 0x49, 0x45, 0x43,// Z
	0x00, 0x00, 0x7F, 0x41, 0x41,// [
	0x02, 0x04, 0x08, 0x10, 0x20,// "\"
	0x41, 0x41, 0x7F, 0x00, 0x00,// ]
	0x04, 0x02, 0x01, 0x02, 0x04,// ^
	0x40, 0x40, 0x40, 0x40, 0x40,// _
	0x00, 0x01, 0x02, 0x04, 0x00,// `
	0x20, 0x54, 0x54, 0x54, 0x78,// a
	0x7F, 0x48, 0x44, 0x44, 0x38,// b
	0x38, 0x44, 0x44, 0x44, 0x20,// c
	0x38, 0x44, 0x44, 0x48, 0x7F,// d
	0x38, 0x54, 0x54, 0x54, 0x18,// e
	0x08, 0x7E, 0x09, 0x01, 0x02,// f
	0x08, 0x14, 0x54, 0x54, 0x3C,// g
	0x7F, 0x08, 0x04, 0x04, 0x78,// h
	0x00, 0x44, 0x7D, 0x40, 0x00,// i
	0x20, 0x40, 0x44, 0x3D, 0x00,// j
	0x00, 0x7F, 0x10, 0x28, 0x44,// k
	0x00, 0x41, 0x7F, 0x40, 0x00,// l
	0x7C, 0x04, 0x18, 0x04, 0x78,// m
	0x7C, 0x08, 0x04, 0x04, 0x78,// n
	0x38, 0x44, 0x44, 0x44, 0x38,// o
	0x7C, 0x14, 0x14, 0x14, 0x08,// p
	0x08, 0x14, 0x14, 0x18, 0x7C,// q
	0x7C, 0x08, 0x04, 0x04, 0x08,// r
	0x48, 0x54, 0x54, 0x54, 0x20,// s
	0x04, 0x3F, 0x44, 0x40, 0x20,// 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, 0x00,// {
	0x00, 0x00, 0x7F, 0x00, 0x00,// |
	0x00, 0x41, 0x36, 0x08, 0x00,// }
	0x08, 0x08, 0x2A, 0x1C, 0x08,// ->
	0x08, 0x1C, 0x2A, 0x08, 0x08 // <-
};

/*************** Private Functions ***************************************************/

// Print a single ASCII character (7 x 5 pixel font) :
// The location parameters are in character units, not pixels : "col" goes from 0 to 20, "line" goes from 0 to 7.
// Very fast : exploits the layout of the frame buffer, where each byte represents a column of 8 pixels.
// The font used in this library also represents characters using columns of 8 pixels. Couldn't be simpler.
void print_character (char c, uint8_t col, uint8_t line)
{
	// Convert the ASCII code to an index in the font array:
	c -= 32;				// The font starts at ASCII code 32
	uint16_t f = c * 5;		// There are five bytes for each character (note : Cortex-M3 has single-cycle multiply : it's OK to use it)

	// convert col and line to a frame buffer index
	uint16_t i = (line * 128) + (col * 6) + 1;

	// write the character (structures and pointers might make it faster and nicer to read):
	frame_buffer[i++] = Font5x7[f++];
	frame_buffer[i++] = Font5x7[f++];
	frame_buffer[i++] = Font5x7[f++];
	frame_buffer[i++] = Font5x7[f++];
	frame_buffer[i++] = Font5x7[f++];
	frame_buffer[i] = 0;	// empty column to the right
}

/***************** API Functions ***************************************************/

// Initialization function. Must be called first and only once
// Note : frame rate is in frames per second, maximum is 1000
void oled_init (SPI_HandleTypeDef *hspi, int frame_rate)
{
	// Leave reset state
	OLED_RES_1;		// Release the display's reset pin
	OLED_SS_0;		// Select the OLED

	// Transfer the display's settings using a blocking HAL function instead of a DMA transfer
	// The point is to wait until this transfer has completed and then switch the display to data mode
	HAL_SPI_Transmit (hspi, (uint8_t*) oled_param, sizeof(oled_param), HAL_MAX_DELAY);
	OLED_DC_1;		// From now on, all transfers will be data (pixel rasters, a.k.a. frame buffers)

	// Compute frame refresh interval in milliseconds based on frame rate in Hz :
	frame_interval = 1000 / frame_rate;

	// Save the SPI controller handler
	oled_spi = hspi;
}

// Heavily-optimized function to set a single pixel on a 128 x 64 display.
// It exploits the layout of the frame buffer, where each byte represents a column of 8 pixels.
void set_pixel (uint16_t x, uint16_t y, uint8_t c)	// X, Y coordinates and color (1 or 0)
{
	uint16_t i = ((y & 0x38) << 4) | x;	// offset by X to get the address of the byte of interest
	uint8_t m = 0x1 << (y & 0x7);	// 8 bits in a byte represent 8 lines, so this is obvious

	if (c == 1)
		frame_buffer[i] |= m;	// sets one bit in the byte
	else
		frame_buffer[i] &= ~m;	// clears. Note : XOR would toggle a bit
}

// Function to print a C string. It just loops through the string and prints each character.
void print (char* s, uint8_t col, uint8_t line)
{
	int k = 0;
	int len = strlen (s);

	while (k < len)
	{
		print_character (s[k], col + k, line);
		k++;
	}
}

// Function to refresh the display. It triggers a DMA transfer of the frame buffer to the SPI bus
// Call this function from the SysTick handler in your project's Core\Src\stm32xxxx_it.c source file.
// The default System Tick frequency is 1 KHz, so this function will get called once ever millisecond.
void oled_refresh ()
{
	static int system_ticks = 0;	// Keep track of system time to regulate frame rate

	if (system_ticks++ == frame_interval)
	{
		system_ticks = 0;
		HAL_SPI_Transmit_DMA (oled_spi, frame_buffer, 1024);
	}
}


