Arduino_STM32/STM32F1/libraries/Lcd7920_STM/lcd7920_STM.cpp

566 lines
15 KiB
C++

// Driver for 128x634 graphical LCD with ST7920 controller
// D Crocker, Escher Technologies Ltd.
// adapted for STM32-Arduino by Matthias Diro
#include "lcd7920_STM.h"
#include <pins_arduino.h>
#include <avr/interrupt.h>
// LCD basic instructions. These all take 72us to execute except LcdDisplayClear, which takes 1.6ms
const uint8_t LcdDisplayClear = 0x01;
const uint8_t LcdHome = 0x02;
const uint8_t LcdEntryModeSet = 0x06; // move cursor right and indcement address when writing data
const uint8_t LcdDisplayOff = 0x08;
const uint8_t LcdDisplayOn = 0x0C; // add 0x02 for cursor on and/or 0x01 for cursor blink on
const uint8_t LcdFunctionSetBasicAlpha = 0x20;
const uint8_t LcdFunctionSetBasicGraphic = 0x22;
const uint8_t LcdFunctionSetExtendedAlpha = 0x24;
const uint8_t LcdFunctionSetExtendedGraphic = 0x26;
const uint8_t LcdSetDdramAddress = 0x80; // add the address we want to set
// LCD extended instructions
const uint8_t LcdSetGdramAddress = 0x80;
//const unsigned int LcdCommandDelayMicros = 72 - 24; // 72us required, less 24us time to send the command @ 1MHz
const unsigned int LcdCommandDelayMicros = 2;
const unsigned int LcdDataDelayMicros = 2;// 10; // Delay between sending data bytes
const unsigned int LcdDisplayClearDelayMillis = 2; // 1.6ms should be enough
const unsigned int numRows = 64;
const unsigned int numCols = 128;
Lcd7920::Lcd7920(uint8_t cPin, uint8_t dPin, bool spi) : clockPin(cPin), dataPin(dPin), useSpi(spi), currentFont(0), textInverted(false)
{
}
size_t Lcd7920::write(uint8_t ch)
{
if (gfxMode)
{
if (currentFont == 0)
{
return 0;
}
const uint8_t startChar = pgm_read_byte_near(&(currentFont->startCharacter));
const uint8_t endChar = pgm_read_byte_near(&(currentFont->endCharacter));
if (ch < startChar || ch > endChar)
{
return 0;
}
const uint8_t fontWidth = pgm_read_byte_near(&(currentFont->width));
const uint8_t fontHeight = pgm_read_byte_near(&(currentFont->height));
const uint8_t bytesPerColumn = (fontHeight + 7)/8;
const uint8_t bytesPerChar = (bytesPerColumn * fontWidth) + 1;
PROGMEM const uint8_t *fontPtr = (PROGMEM const uint8_t*)pgm_read_word_near(&(currentFont->ptr)) + (bytesPerChar * (ch - startChar));
uint16_t cmask = (1 << fontHeight) - 1;
uint8_t nCols = pgm_read_byte_near(fontPtr++);
// Update dirty rectangle coordinates, except for endCol which we do at the end
{
if (startRow > row) { startRow = row; }
if (startCol > column) { startCol = column; }
uint8_t nextRow = row + fontHeight;
if (nextRow > numRows) { nextRow = numRows; }
if (endRow < nextRow) { endRow = nextRow; }
}
// Decide whether to add a space column first (auto-kerning)
// We don't add a space column before a space character.
// We add a space column after a space character if we would have added one between the preceding and following characters.
if (column != numCols)
{
uint16_t thisCharColData = pgm_read_word_near(fontPtr) & cmask; // atmega328p is little-endian
if (thisCharColData == 0) // for characters with deliberate space row at the start, e.g. decimal point
{
thisCharColData = pgm_read_word_near(fontPtr + 2) & cmask;
}
bool wantSpace = ((thisCharColData | (thisCharColData << 1)) & (lastCharColData | (lastCharColData << 1))) != 0;
if (wantSpace)
{
// Add space after character
uint8_t mask = 0x80 >> (column & 7);
uint8_t *p = image + ((row * (numCols/8)) + (column/8));
for (uint8_t i = 0; i < fontHeight && p < (image + sizeof(image)); ++i)
{
if (textInverted)
{
*p |= mask;
}
else
{
*p &= ~mask;
}
p += (numCols/8);
}
++column;
}
}
while (nCols != 0 && column < numCols)
{
uint16_t colData = pgm_read_word_near(fontPtr);
fontPtr += bytesPerColumn;
if (colData != 0)
{
lastCharColData = colData & cmask;
}
uint8_t mask1 = 0x80 >> (column & 7);
uint8_t mask2 = ~mask1;
uint8_t *p = image + ((row * (numCols/8)) + (column/8));
const uint16_t setPixelVal = (textInverted) ? 0 : 1;
for (uint8_t i = 0; i < fontHeight && p < (image + sizeof(image)); ++i)
{
if ((colData & 1) == setPixelVal)
{
*p |= mask1; // set pixel
}
else
{
*p &= mask2; // clear pixel
}
colData >>= 1;
p += (numCols/8);
}
--nCols;
++column;
}
if (column > endCol) { endCol = column; }
}
else
{
// Alphanumeric mode
if (ch == '\r')
{
setCursor(0, column);
}
else if (ch == '\n')
{
setCursor(0, row + 1);
}
else
{
if (column == 16)
{
setCursor(row + 1, 0);
}
sendLcdData(ch);
++column;
commandDelay(); // so that we are ready to write another character immediately
}
}
return 1;
}
// Select normal or inverted text (only works in graphics mode)
void Lcd7920::textInvert(bool b)
{
if (b != textInverted)
{
textInverted = b;
lastCharColData = 0xFFFF; // always need space between inverted and non-inverted text
}
}
// NB - if using SPI then the SS pin must be set to be an output before calling this!
void Lcd7920::begin(bool gmode)
{
// Set up the SPI interface for talking to the LCD. We have to set MOSI, SCLK and SS to outputs, then enable SPI.
digitalWrite(clockPin, LOW);
digitalWrite(dataPin, LOW);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
if (useSpi)
{
delay(1); //dummy
// SPCR = (1 << SPE) | (1 << MSTR) | (1 << SPR0); // enable SPI, master mode, clock low when idle, data sampled on rising edge, clock = f/16 (= 1MHz), send MSB first
//SPSR = (1 << SPI2X); // double the speed to 2MHz (optional)
}
gfxMode = false;
sendLcdCommand(LcdFunctionSetBasicAlpha);
delay(1);
sendLcdCommand(LcdFunctionSetBasicAlpha);
commandDelay();
sendLcdCommand(LcdEntryModeSet);
commandDelay();
extendedMode = false;
clear(); // clear alpha ram
if (gmode)
{
gfxMode = true;
clear(); // clear gfx ram
}
setCursor(0, 0);
sendLcdCommand(LcdDisplayOn);
commandDelay();
currentFont = 0;
textInverted = false;
}
void Lcd7920::setFont(const PROGMEM LcdFont *newFont)
{
currentFont = newFont;
}
void Lcd7920::clear()
{
if (gfxMode)
{
memset(image, 0, sizeof(image));
// flag whole image as dirty and update
startRow = 0;
endRow = numRows;
startCol = 0;
endCol = numCols;
flush();
}
else
{
ensureBasicMode();
sendLcdCommand(LcdDisplayClear);
delay(LcdDisplayClearDelayMillis);
}
setCursor(0, 0);
textInverted = false;
}
// Draw a line using the Bresenham Algorithm (thanks Wikipedia)
void Lcd7920::line(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, PixelMode mode)
{
int dx = (x1 >= x0) ? x1 - x0 : x0 - x1;
int dy = (y1 >= y0) ? y1 - y0 : y0 - y1;
int sx = (x0 < x1) ? 1 : -1;
int sy = (y0 < y1) ? 1 : -1;
int err = dx - dy;
for (;;)
{
setPixel(x0, y0, mode);
if (x0 == x1 && y0 == y1) break;
int e2 = err + err;
if (e2 > -dy)
{
err -= dy;
x0 += sx;
}
if (e2 < dx)
{
err += dx;
y0 += sy;
}
}
}
void Lcd7920::fastHline(uint8_t x0, uint8_t y0, uint8_t x1, PixelMode mode)
{
for (byte xx=0;xx< x1;xx++)
{
setPixel(x0+xx, y0, mode);
}
}
void Lcd7920::fastVline(uint8_t x0, uint8_t y0, uint8_t y1, PixelMode mode)
{
for (byte yy=0;yy< y1;yy++)
{
setPixel(x0, y0+yy, mode);
}
}
// draw simple box - x0,y0=start, x1,y1-length in pixel
void Lcd7920::box(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, PixelMode mode)
{
//line( x0, y0, x0+x1, y0, mode);
fastHline(x0,y0,x1, mode);
fastHline(x0,y0+y1,x1, mode);
// line( x0, y0+y1, x0+x1, y0+y1, mode);
//line( x0, y0+1, x0, y0+y1-1, mode);
fastVline(x0,y0+1,y1-1, mode);
fastVline(x0+x1-1,y0+1,y1-1, mode);
//line( x0+x1, y0+1, x0+x1, y0+y1-1, mode);
//line( x0, y0, x0+x1, y0+y1, mode);
}
//draw full box
void Lcd7920::fillbox(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, PixelMode mode)
{
for (byte x=0;x<y1;x++)
{
//line( x0, y0+x, x0+x1, y0+x, mode);
fastHline(x0,y0+x,x1, mode);
}
}
// Draw a circle using the Bresenham Algorithm (thanks Wikipedia)
void Lcd7920::circle(uint8_t x0, uint8_t y0, uint8_t radius, PixelMode mode)
{
int f = 1 - (int)radius;
int ddF_x = 1;
int ddF_y = -2 * (int)radius;
int x = 0;
int y = radius;
setPixel(x0, y0 + radius, mode);
setPixel(x0, y0 - radius, mode);
setPixel(x0 + radius, y0, mode);
setPixel(x0 - radius, y0, mode);
while(x < y)
{
// keep ddF_x == 2 * x + 1;
// keep ddF_y == -2 * y;
// keep f == x*x + y*y - radius*radius + 2*x - y + 1;
if(f >= 0)
{
y--;
ddF_y += 2;
f += ddF_y;
}
x++;
ddF_x += 2;
f += ddF_x;
setPixel(x0 + x, y0 + y, mode);
setPixel(x0 - x, y0 + y, mode);
setPixel(x0 + x, y0 - y, mode);
setPixel(x0 - x, y0 - y, mode);
setPixel(x0 + y, y0 + x, mode);
setPixel(x0 - y, y0 + x, mode);
setPixel(x0 + y, y0 - x, mode);
setPixel(x0 - y, y0 - x, mode);
}
}
// Draw a bitmap
void Lcd7920::bitmap(uint8_t x0, uint8_t y0, uint8_t width, uint8_t height, PROGMEM const uint8_t data[])
{
for (uint8_t r = 0; r < height && r + y0 < numRows; ++r)
{
uint8_t *p = image + (((r + y0) * (numCols/8)) + (x0/8));
uint16_t bitMapOffset = r * (width/8);
for (uint8_t c = 0; c < (width/8) && c + (x0/8) < numCols/8; ++c)
{
*p++ = pgm_read_byte_near(bitMapOffset++);
}
}
if (x0 < startCol) startCol = x0;
if (x0 + width > endCol) endCol = x0 + width;
if (y0 < startRow) startRow = y0;
if (y0 + height > endRow) endRow = y0 + height;
}
// Flush the dirty part of the image to the lcd
void Lcd7920::flush()
{
if (gfxMode && endCol > startCol && endRow > startRow)
{
uint8_t startColNum = startCol/16;
uint8_t endColNum = (endCol + 15)/16;
for (uint8_t r = startRow; r < endRow; ++r)
{
setGraphicsAddress(r, startColNum);
uint8_t *ptr = image + ((16 * r) + (2 * startColNum));
for (uint8_t i = startColNum; i < endColNum; ++i)
{
sendLcdData(*ptr++);
//commandDelay(); // don't seem to need a delay here
sendLcdData(*ptr++);
//commandDelay(); // don't seem to need as long a delay as this
delayMicroseconds(LcdDataDelayMicros);
}
}
startRow = numRows;
startCol = numCols;
endCol = endRow = 0;
}
}
// Set the cursor position. We can only set alternate columns. The row addressing is rather odd.
void Lcd7920::setCursor(uint8_t r, uint8_t c)
{
if (gfxMode)
{
row = r % numRows;
column = c % numCols;
lastCharColData = 0u; // flag that we just set the cursor position, so no space before next character
}
else
{
row = r % 4;
column = c % 16;
ensureBasicMode();
sendLcdCommand(LcdSetDdramAddress + ((row & 1) * 0x10) + (column/2) + (row >> 1) * 8);
commandDelay();
}
}
void Lcd7920::setPixel(uint8_t x, uint8_t y, PixelMode mode)
{
if (y < numRows && x < numCols)
{
uint8_t *p = image + ((y * (numCols/8)) + (x/8));
uint8_t mask = 0x80u >> (x%8);
switch(mode)
{
case PixelClear:
*p &= ~mask;
break;
case PixelSet:
*p |= mask;
break;
case PixelFlip:
*p ^= mask;
break;
}
// Change the dirty rectangle to account for a pixel being dirty (we assume it was changed)
if (startRow > y) { startRow = y; }
if (endRow <= y) { endRow = y + 1; }
if (startCol > x) { startCol = x; }
if (endCol <= x) { endCol = x + 1; }
}
}
bool Lcd7920::readPixel(uint8_t x, uint8_t y)
{
if (y < numRows && x < numCols)
{
uint8_t *p = image + ((y * (numCols/8)) + (x/8));
return (*p & (0x80u >> (x%8))) != 0;
}
return false;
}
void Lcd7920::setGraphicsAddress(unsigned int r, unsigned int c)
{
if (gfxMode)
{
ensureExtendedMode();
sendLcdCommand(LcdSetGdramAddress | (r & 31));
//commandDelay(); // don't seem to need this one
sendLcdCommand(LcdSetGdramAddress | c | ((r & 32) >> 2));
commandDelay(); // we definitely need this one
}
}
void Lcd7920::commandDelay()
{
delayMicroseconds(LcdCommandDelayMicros);
}
// Send a command to the LCD
void Lcd7920::sendLcdCommand(uint8_t command)
{
sendLcd(0xF8, command);
}
// Send a data byte to the LCD
void Lcd7920::sendLcdData(uint8_t data)
{
sendLcd(0xFA, data);
}
// Send a command to the lcd. Data1 is sent as-is, data2 is split into 2 bytes, high nibble first.
void Lcd7920::sendLcd(uint8_t data1, uint8_t data2)
{
if (useSpi)
{
delay(1); // dummy
/*
SPDR = data1;
while ((SPSR & (1 << SPIF)) == 0) { }
SPDR = data2 & 0xF0;
while ((SPSR & (1 << SPIF)) == 0) { }
SPDR = data2 << 4;
while ((SPSR & (1 << SPIF)) == 0) { }
*/
}
else
{
sendLcdSlow(data1);
sendLcdSlow(data2 & 0xF0);
sendLcdSlow(data2 << 4);
}
}
void Lcd7920::sendLcdSlow(uint8_t data)
{
/*
#if 1
// Fast shiftOut function
volatile uint32_t *sclkPort = portOutputRegister(digitalPinToPort(clockPin));
volatile uint32_t *mosiPort = portOutputRegister(digitalPinToPort(dataPin));
uint8_t sclkMask = digitalPinToBitMask(clockPin);
uint8_t mosiMask = digitalPinToBitMask(dataPin);
uint8_t oldSREG = SREG;
cli();
for (uint8_t i = 0; i < 8; ++i)
{
if (data & 0x80)
{
*mosiPort |= mosiMask;
}
else
{
*mosiPort &= ~mosiMask;
}
*sclkPort |= sclkMask;
*sclkPort &= ~sclkMask;
data <<= 1;
}
SREG = oldSREG;
#else
*/
// really slow version, like Arduino shiftOut function
for (uint8_t i = 0; i < 8; ++i)
{
digitalWrite(dataPin, (data & 0x80) ? HIGH : LOW);
// mosifast((data & 0x80) ? HIGH : LOW); // faster
digitalWrite(clockPin, HIGH); // do not change to high speed!
digitalWrite(clockPin, LOW); // do not change to high speed pin change!
data <<= 1;
}
/*
for (uint8_t i = 0; i < 8; ++i)
{
mosifast((data & 0x80) ? HIGH : LOW);
delayMicroseconds(1);
sckfast(HIGH);
sckfast(LOW);
data <<= 1;
}
*/
//#endif
}
void Lcd7920::ensureBasicMode()
{
if (extendedMode)
{
sendLcdCommand(gfxMode ? LcdFunctionSetBasicGraphic : LcdFunctionSetBasicAlpha);
commandDelay();
extendedMode = false;
}
}
void Lcd7920::ensureExtendedMode()
{
if (!extendedMode)
{
sendLcdCommand(gfxMode ? LcdFunctionSetExtendedGraphic : LcdFunctionSetExtendedAlpha);
commandDelay();
extendedMode = true;
}
}