OK, so continuing the series on PI 2 with windows 10 for IoT, I have now added to the previous app and created a demo app for a pair of Sharp 96*96 LCD displays
Again, there where no libraries for the boards. The LS013B4DN04 SPI display is the actual Sharp part and I wrote a library to drive as many of these as you have GPIOpins free on the PI, I then proceeded to put a cool UI ontop of it so I can play with the board under Windows 10.
Here is a video running through the demo app, I will follow up with a software overview and a technical look at the SPI buss as there are some interesting things I did to get things running fast.
This is a view of the boards I used, you could just as easily use the Adafruit version without changing the software
This is the strip board i made up for the demo, it has minimal wiring so no need to bother with a schematic
there are a total of 6 wires to the PI as shown below, Power control and LCD Enable both are tied to 3.3V, one display SPI CS goes to GPIO5, one to GPIO6
probably the easies setup for the hardware yet
So this is the UI I created in full technicolour
The video explains it all but in summary
The two displays have a textbox overlay so you can input your own text and send it to the displays, the long slider will allow you to change the font size. While the display library will support multiple fonts sizes on the screen at once along with graphics elements, the demo does not show this (Yet). clicking wither of the "Update" buttons will send the display content to the respective SPI SHARP96 Display, the left one is connected to CS GPIO5, the right to GPIO6 (The code could be changed easily to use and available GPIO pin).
The fill 1's and 0's will fill a 2D array in memory for each display with all 1's or all 0's depending on the button. All 1's is actually the light grey colour, the 0's gives you a mirror basically as the reflective index of these displays is so high.
The demo button runs through a set of patterns and showcases some of the libraries capabilities
- Write Pixel
- Line vertical
- Line Horizontal
- Line any direction
- Arcs
- Circles (Optimised to only calc 45deg of the circle and still plot all of it. also only integer math involved)
- Filled Rectangle
- Write a string or Character supporting the \n \r
all the text can be started at any x,y co-ordinate, not just on a fixed boundary
This is the Libray code, it will also be attached along with the rest of the solution
using System; using System.Threading.Tasks; using Windows.Devices.Enumeration; using Windows.Devices.Spi; using Windows.Devices.Gpio; namespace SHARP96Driver { public class sharp96Display { public string DefaultText ; public static UInt16 LCD_VERTICAL_MAX = 96; // Y public static UInt16 LCD_HORIZONTAL_MAX = 96; // X public static int SHARP_BLACK = 0x00; public static int SHARP_WHITE = 0xFF; public GpioPin PIN; public byte cursorX; public byte cursorY; public byte[,] DisplayBuffer; // A working pixel buffer for your code public sharp96Display(string defaultText, int ulValue, int pin) { //LCD_VERTICAL_MAX = 96; //LCD_HORIZONTAL_MAX = 96; //SHARP_BLACK = 0x00; //SHARP_WHITE = 0xFF; PIN = Sharp96.InitGPIO(pin, GpioPinDriveMode.Output, GpioPinValue.Low); cursorX = 0; cursorY = 0; DisplayBuffer = new byte[LCD_VERTICAL_MAX, LCD_HORIZONTAL_MAX / 8]; // A working pixel buffer for your code Sharp96.Sharp96x96_InitializeDisplayBuffer(this, (ulValue==0)? SHARP_BLACK: SHARP_WHITE) ; DefaultText = defaultText; Sharp96.write(this, defaultText.ToCharArray(), 1, (ulValue==0) ? SHARP_WHITE : SHARP_BLACK); if (PIN != null) Sharp96.Sharp96x96_Flush(this); } } public static class Sharp96 { //***************************************************************************** //1.3-inch screen has 96x96 resolution (9216 pixels stripe array) // LCD Screen Dimensions //***************************************************************************** public const UInt16 LCD_VERTICAL_MAX = 96; // Y public const UInt16 LCD_HORIZONTAL_MAX = 96; // X private static bool AutoWrap = true; //***************************************************************************** // for the Display Driver //***************************************************************************** public const int SHARP_BLACK = 0x00; public const int SHARP_WHITE = 0xFF; private const int SHARP_SEND_TOGGLE_VCOM_COMMAND = 0x01; private const int SHARP_SKIP_TOGGLE_VCOM_COMMAND = 0x00; private const int SHARP_LCD_TRAILER_BYTE = 0x00; private const int SHARP_VCOM_TOGGLE_BIT = 0x40; private const int SHARP_LCD_CMD_CHANGE_VCOM = 0x00; private const int SHARP_LCD_CMD_CLEAR_SCREEN = 0x20; private const int SHARP_LCD_CMD_WRITE_LINE = 0x80; public static byte[] reverse_data = { 0x00, 0x08, 0x04, 0x0C, 0x02, 0x0A, 0x06, 0x0E, 0x01, 0x09, 0x05, 0x0D, 0x03, 0x0B, 0x07, 0x0F }; private static int VCOMbit = 0x40; private static int flagSendToggleVCOMCommand = 0; private const int ClrBlack = SHARP_BLACK; public struct Graphics_Rectangle { public int XMin; public int XMax; public int YMin; public int YMax; } //***************************************************************************** //RaspBerry Pi2 Parameters //***************************************************************************** private const string SPI_CONTROLLER_NAME = "SPI0"; /* For Raspberry Pi 2, use SPI0 */ private const Int32 SPI_CHIP_SELECT_LINE = 0; /* Line 0 maps to physical pin number 24 on the Rpi2, line 1 to pin 26 */ private static byte[] writeBuffer2 = new byte[2];// for simple comands like CLS or VCON private static byte[] writeBuffer1346 = new byte[1346];// for writing a buffer to the screen private static byte[] readBuffer4 = new byte[4]; /*this is defined to hold the output data*/ private static byte[] writeBuffer4 = new byte[4];//register, then 16 bit value private static SpiDevice SpiGPIO; public static async Task InitSPI() { try { var settings = new SpiConnectionSettings(SPI_CHIP_SELECT_LINE); settings.ClockFrequency = 5000000;// 500kHz; settings.Mode = SpiMode.Mode0; //Mode0,1,2,3; MCP23S17 needs mode 0 string spiAqs = SpiDevice.GetDeviceSelector(SPI_CONTROLLER_NAME); var deviceInfo = await DeviceInformation.FindAllAsync(spiAqs); SpiGPIO = await SpiDevice.FromIdAsync(deviceInfo[0].Id, settings); } /* If initialization fails, display the exception and stop running */ catch (Exception ex) { //statusText.Text = "\nSPI Initialization Failed"; } } public static GpioPin InitGPIO(int GPIOpin, GpioPinDriveMode mode, GpioPinValue HiLow ) { var gpio = GpioController.GetDefault(); // Show an error if there is no GPIO controller if (gpio == null) { return null; } var pin = gpio.OpenPin(GPIOpin); if (pin == null) { return null; } pin.SetDriveMode(mode); pin.Write(HiLow); return pin; } //******************************************************************************* // //! Reverses the bit order.- Since the bit reversal function is called //! frequently by the several driver function this function is implemented //! to maximize code execution // // { 0x00,0x08,0x04,0x0C,0x02,0x0A,0x06,0x0E,0x01,0x09,0x05,0x0D,0x03,0x0B,0x07,0x0F } // //******************************************************************************* static byte Sharp96x96_reverse(byte x) { byte b = 0; b = (byte)(reverse_data[x & 0xF] << 4); b |= reverse_data[(x & 0xF0) >> 4]; return b; } //***************************************************************************** // //! Initialize DisplayBuffer. //! //! \param pvDisplayData is a pointer to the driver-specific data for this //! display driver. //! //! \param ulValue is the foreground color of the buffered data. //! //! This function initializes the display buffer and discards any cached data. //! //! \return None. // //***************************************************************************** public static void Sharp96x96_InitializeDisplayBuffer(sharp96Display display, int ulValue) { UInt16 i = 0, j = 0; for (i = 0; i < LCD_VERTICAL_MAX; i++) for (j = 0; j < (LCD_HORIZONTAL_MAX >> 3); j++) display.DisplayBuffer[i, j] = (byte)ulValue; } //***************************************************************************** // //! Draws a pixel on the screen. //! //! \param pvDisplayData is a pointer to the driver-specific data for this //! display driver. //! \param lX is the X coordinate of the pixel. //! \param lY is the Y coordinate of the pixel. //! \param ulValue is the color of the pixel. //! //! This function sets the given pixel to a particular color. The coordinates //! of the pixel are assumed to be within the extents of the display. //! //! \return None. // //***************************************************************************** public static void Sharp96x96_PixelDraw(sharp96Display display, UInt16 lX, UInt16 lY, int ulValue) { if ((lY >= LCD_VERTICAL_MAX) || (lX >= LCD_HORIZONTAL_MAX)) return; if (ClrBlack == ulValue) { display.DisplayBuffer[lY, lX >> 3] &= (byte)(~(0x80 >> (lX & 0x7))); } else { display.DisplayBuffer[lY, lX >> 3] |= (byte)((0x80 >> (lX & 0x7))); } } //***************************************************************************** // //! Draws a horizontal line. //! //! \param pvDisplayData is a pointer to the driver-specific data for this //! display driver. //! \param lX1 is the X coordinate of the start of the line. //! \param lX2 is the X coordinate of the end of the line. //! \param lY is the Y coordinate of the line. //! \param ulValue is the color of the line. //! //! This function draws a horizontal line on the display. The coordinates of //! the line are assumed to be within the extents of the display. //! //! \return None. // //***************************************************************************** public static void Sharp96x96_LineDrawH(sharp96Display display, int lX1, int lX2, int lY, int ulValue) { // sanity check if(lX1 > lX2) { int temp = lX2; lX2 = lX1; lX1 = temp; } int xi = 0; int x_index_min = lX1 >> 3; int x_index_max = lX2 >> 3; int ucfirst_x_byte, uclast_x_byte; //calculate first byte //mod by 8 and shift this # bits ucfirst_x_byte = (byte)(0xFF >> (lX1 & 0x7)); //calculate last byte //mod by 8 and shift this # bits uclast_x_byte = (byte)(0xFF << (7 - (lX2 & 0x7))); try { //check if more than one data byte if (x_index_min != x_index_max) { //black pixels (clear bits) if (ClrBlack == ulValue) { //write first byte display.DisplayBuffer[lY, x_index_min] &= (byte)(~ucfirst_x_byte); //write middle bytes for (xi = x_index_min; xi < x_index_max - 1; xi++) { display.DisplayBuffer[lY, xi + 1] = 0x00; } //write last byte display.DisplayBuffer[lY, xi + 1] &= (byte)(~uclast_x_byte); } //white pixels (set bits) else { //write first byte display.DisplayBuffer[lY, xi] |= (byte)(ucfirst_x_byte); //write middle bytes for (xi = x_index_min; xi < x_index_max - 1; xi++) { display.DisplayBuffer[lY, xi + 1] = 0xFF; } //write last byte display.DisplayBuffer[lY, xi + 1] |= (byte)(uclast_x_byte); } } //only one data byte else { //calculate value of single byte ucfirst_x_byte &= uclast_x_byte; //draw black pixels (clear bits) if (ClrBlack == ulValue) { display.DisplayBuffer[lY, xi] &= (byte)(~ucfirst_x_byte); } //white pixels (set bits) else { display.DisplayBuffer[lY, xi] |= (byte)(ucfirst_x_byte); } } } catch (Exception ex) { throw new Exception(ex.Message); } } //***************************************************************************** // //! Draws a vertical line. //! //! \param pvDisplayData is a pointer to the driver-specific data for this //! display driver. //! \param lX is the X coordinate of the line. //! \param lY1 is the Y coordinate of the start of the line. //! \param lY2 is the Y coordinate of the end of the line. //! \param ulValue is the color of the line. //! //! This function draws a vertical line on the display. The coordinates of the //! line are assumed to be within the extents of the display. //! //! \return None. // //***************************************************************************** public static void Sharp96x96_LineDrawV(sharp96Display display, int lX, int lY1, int lY2, int ulValue) { int yi = 0; int x_index = lX >> 3; int data_byte; //calculate data byte //mod by 8 and shift this # bits data_byte = (0x80 >> (lX & 0x7)); //write data to the display buffer for (yi = lY1; yi <= lY2; yi++) { //black pixels (clear bits) if (ClrBlack == ulValue) { display.DisplayBuffer[yi,x_index] &= (byte)(~data_byte); } //white pixels (set bits) else { display.DisplayBuffer[yi,x_index] |= (byte)(data_byte); } } } //***************************************************************************** // //! Draws a line. //! //! \param context is a pointer to the drawing context to use. //! \param x1 is the X coordinate of the start of the line. //! \param y1 is the Y coordinate of the start of the line. //! \param x2 is the X coordinate of the end of the line. //! \param y2 is the Y coordinate of the end of the line. //! //! This function draws a line, utilizing Sharp96x96_LineDrawH() and //! Sharp96x96_LineDrawV() to draw the line as efficiently as possible. //! it proceeds using Bresenham's line drawing algorithm. //! //! \return None. // //***************************************************************************** public static void Sharp96x96_drawLine(sharp96Display display, UInt16 x1, UInt16 y1, UInt16 x2, UInt16 y2, int ulValue) { UInt16 error, deltaX, deltaY; int yStep; bool SwapXY; // is this a vertical line as we can call an optimized routine for it if(x1 == x2) { Sharp96x96_LineDrawV(display, x1, y1, y2, ulValue); return; } // is this a horizontal line as we can call an optimized routine for it if (y1 == y2) { Sharp96x96_LineDrawH(display, x1, y1, y2, ulValue); return; } // Determine if the line is steep. A steep line has more motion in the Y // direction than the X direction. if(((y2 > y1) ? (y2 - y1) : (y1 - y2)) > ((x2 > x1) ? (x2 - x1) : (x1 - x2))) { SwapXY = true; } else { SwapXY = false; } // If the line is steep, then swap the X and Y coordinates. if(SwapXY) { error = x1; x1 = y1; y1 = error; error = x2; x2 = y2; y2 = error; } // // If the starting X coordinate is larger than the ending X coordinate, // then swap the start and end coordinates. // if(x1 > x2) { error = x1; x1 = x2; x2 = error; error = y1; y1 = y2; y2 = error; } // Compute the difference between the start and end coordinates in each axis. deltaX = (UInt16)(x2 - x1); deltaY = (UInt16)((y2 > y1) ? (y2 - y1) : (y1 - y2)); // Initialize the error term to negative half the X delta. error = (UInt16)(-deltaX / 2); // Determine the direction to step in the Y axis when required. if(y1<y2) yStep = 1; else yStep = -1; // Loop through all the points along the X axis of the line. for(; x1 <= x2; x1++) { // See if this is a steep line. if(SwapXY) { // Plot this point of the line, swapping the X and Y coordinates. Sharp96x96_PixelDraw(display, y1, x1, ulValue); } else { // Plot this point of the line, using the coordinates as is. Sharp96x96_PixelDraw(display, x1, y1, ulValue); } // Increment the error term by the Y delta. error += deltaY; // See if the error term is now greater than zero. if(error > 0) { // Take a step in the Y axis. y1 = (UInt16)(y1 + yStep); // this could be a - or a + step // Decrement the error term by the X delta. error -= deltaX; } } } //***************************************************************************** // //! Fills a rectangle. //! //! \param pvDisplayData is a pointer to the driver-specific data for this //! display driver. //! \param pRect is a pointer to the structure describing the rectangle. //! \param ulValue is the color of the rectangle. //! //! This function fills a rectangle on the display. The coordinates of the //! rectangle are assumed to be within the extents of the display, and the //! rectangle specification is fully inclusive (in other words, both sXMin and //! sXMax are drawn, along with sYMin and sYMax). //! //! \return None. // //***************************************************************************** public static void Sharp96x96_RectFill(sharp96Display display, Graphics_Rectangle pRect, int ulValue) { // Bounds Checking, if there all out, simply return, if any are in then do what can be done if ((pRect.XMin >= LCD_HORIZONTAL_MAX) && (pRect.YMin >= LCD_VERTICAL_MAX) && (pRect.XMax >= LCD_HORIZONTAL_MAX) && (pRect.YMax >= LCD_VERTICAL_MAX)) return; if (pRect.XMin >= LCD_HORIZONTAL_MAX) pRect.XMin = LCD_HORIZONTAL_MAX-1; if (pRect.YMin >= LCD_VERTICAL_MAX) pRect.YMin = LCD_VERTICAL_MAX-1; if (pRect.XMax >= LCD_HORIZONTAL_MAX) pRect.XMax = LCD_HORIZONTAL_MAX-1; if (pRect.YMax >= LCD_VERTICAL_MAX) pRect.YMax = LCD_VERTICAL_MAX-1; int xi = 0; int yi = 0; int x_index_min = pRect.XMin >> 3; int x_index_max = pRect.XMax >> 3; byte ucfirst_x_byte, uclast_x_byte; //calculate first byte //mod by 8 and shift this # bits ucfirst_x_byte = (byte)(0xFF >> (pRect.XMin & 0x7)); //calculate last byte //mod by 8 and shift this # bits uclast_x_byte = (byte)(0xFF << (7-(pRect.XMax & 0x7))); //check if more than one data byte if(x_index_min != x_index_max) { //write bytes for (yi = pRect.YMin; yi<= pRect.YMax; yi++) { //black pixels (clear bits) if(ClrBlack == ulValue) { //write first byte display.DisplayBuffer[yi, x_index_min] &= (byte)~ucfirst_x_byte; //write middle bytes for(xi = x_index_min+1; xi<x_index_max; xi++) { display.DisplayBuffer[yi, xi] = 0x00; } //write last byte display.DisplayBuffer[yi, x_index_max] &= (byte)~uclast_x_byte; } //white pixels (set bits) else { //write first byte display.DisplayBuffer[yi, x_index_min] |= (byte)ucfirst_x_byte; //write middle bytes for(xi = x_index_min+1; xi<x_index_max; xi++) { display.DisplayBuffer[yi, xi] = 0xFF; } //write last byte display.DisplayBuffer[yi, x_index_max] |= (byte)uclast_x_byte; } } } //only one data byte else { //calculate value of single byte ucfirst_x_byte &= uclast_x_byte; //black pixels (clear bits) if(ClrBlack == ulValue) { //write bytes for (yi = pRect.YMin; yi <= pRect.YMax; yi++) display.DisplayBuffer[yi, x_index_min] &= (byte)~ucfirst_x_byte; } //white pixels (set bits) else { for (yi = pRect.YMin; yi <= pRect.YMax; yi++) display.DisplayBuffer[yi, x_index_min] |= (byte)ucfirst_x_byte; } } } //***************************************************************************** // //! Flushes any cached drawing operations. //! //! \param pvDisplayData is a pointer to the driver-specific data for this //! display driver. //! //! //! This functions flushes any cached drawing operations to the display. This //! is useful when a local frame buffer is used for drawing operations, and the //! flush would copy the local frame buffer to the display. //! it builds a byte array for the call for a single refresh operation //! for more info see http://www.sharpmemorylcd.com/resources/programming_memory_lcd_app_note.pdf //! \return None. // //***************************************************************************** public static async Task Sharp96x96_Flush(sharp96Display display) { byte xi = 0; byte yi = 0; //image update mode(1X000000b) int bufferIndex = 0; int command = SHARP_LCD_CMD_WRITE_LINE; //COM inversion bit command = command ^ VCOMbit; writeBuffer1346[bufferIndex++] = (byte)command; flagSendToggleVCOMCommand = SHARP_SKIP_TOGGLE_VCOM_COMMAND; for (yi = 0; yi < LCD_VERTICAL_MAX; yi++) // Vertical (y) { writeBuffer1346[bufferIndex++] = Sharp96x96_reverse((byte)(yi+1)) ; // Write row Address for (xi = 0; xi < (LCD_HORIZONTAL_MAX >> 3); xi++) // Horizontal - should be 12 bytes for 96 bits { writeBuffer1346[bufferIndex++] = display.DisplayBuffer[yi, xi]; } writeBuffer1346[bufferIndex++] = SHARP_LCD_TRAILER_BYTE; } writeBuffer1346[bufferIndex] = SHARP_LCD_TRAILER_BYTE; // this is the last byte so it will overflow with a ++ HAL_LCD_writeCommandOrData(display, writeBuffer1346); } //***************************************************************************** // //! Send command to clear screen. //! //! \param pvDisplayData is a pointer to the driver-specific data for this //! display driver. //! \param ulValue is the background color of the buffered data. //! //! This function sets every pixel to the background color. //! //! \return None. // //***************************************************************************** public static void Sharp96x96_ClearScreen(sharp96Display display, int ulValue) { Sharp96x96_ClearScreen(display); if (ClrBlack == ulValue) Sharp96x96_InitializeDisplayBuffer(display, SHARP_BLACK); else Sharp96x96_InitializeDisplayBuffer(display, SHARP_WHITE); Sharp96x96_Flush(display); } public static void Sharp96x96_ClearScreen(sharp96Display display) { //clear screen mode(0X100000b) int command = SHARP_LCD_CMD_CLEAR_SCREEN; //COM inversion bit command = command ^ VCOMbit; writeBuffer2[0] = (byte)command; writeBuffer2[1] = SHARP_LCD_TRAILER_BYTE; HAL_LCD_writeCommandOrData(display, writeBuffer2); flagSendToggleVCOMCommand = SHARP_SKIP_TOGGLE_VCOM_COMMAND; } //***************************************************************************** // // Writes command or data to the LCD Driver // // \param ucCmdData is the 8 or 16 bit command to send to the LCD driver // Uses the SET_LCD_DATA macro // // \return None // //***************************************************************************** static void HAL_LCD_writeCommandOrData(sharp96Display display, byte[] command) { try {// ignoring real SS lines CS0 and CS1 as there wrong polarity display.PIN.Write(GpioPinValue.High); SpiGPIO.Write(command); display.PIN.Write(GpioPinValue.Low); } catch (Exception e) { throw new Exception(e.InnerException.Message); // Help Me !!!! } } //***************************************************************************** // //! Send toggle VCOM command. //! //! This function toggles the state of VCOM which prevents a DC bias from being //! built up within the panel. //! //! \return None. // //***************************************************************************** public static void Sharp96x96_SendToggleVCOMCommand(sharp96Display display) { VCOMbit ^= SHARP_VCOM_TOGGLE_BIT; if (SHARP_SEND_TOGGLE_VCOM_COMMAND == flagSendToggleVCOMCommand) { //clear screen mode(0X100000b) int command = SHARP_LCD_CMD_CHANGE_VCOM; //COM inversion bit command = command ^ VCOMbit; writeBuffer2[0] = (byte)command; writeBuffer2[1] = SHARP_LCD_TRAILER_BYTE; HAL_LCD_writeCommandOrData(display,writeBuffer2); } flagSendToggleVCOMCommand = SHARP_SEND_TOGGLE_VCOM_COMMAND; } public static void Arc(sharp96Display display, UInt16 lX, UInt16 lY, UInt16 Radius, UInt16 startAngle, UInt16 endAngle, int ulValue) { ArcEx(display, lX, lY, Radius, startAngle, endAngle, 0.1, ulValue); } public static void ArcEx(sharp96Display display, UInt16 lX, UInt16 lY, UInt16 Radius, UInt16 startAngle, UInt16 endAngle, double increment, int ulValue) { double startA = startAngle; double endA = endAngle; if (startA > 360) startA = 360; if (endA > 360) endA = 360; if (increment > 10) increment = 10.0; if (increment < 0.1) increment = 0.1; for (double i = startA; i < endA; i += increment) { double angle = i * System.Math.PI / 180; byte x = (byte)(lX + Radius * System.Math.Sin(angle)); byte y = (byte)(lY - Radius * System.Math.Cos(angle)); Sharp96x96_PixelDraw(display, x, y, ulValue); } } public static void DrawCircle(sharp96Display display, UInt16 x0, UInt16 y0, UInt16 radius, int ulValue) { // Based on https://en.wikipedia.org/wiki/Midpoint_circle_algorithm int x = radius; int y = 0; int decisionOver2 = 1 - x; // Decision criterion divided by 2 evaluated at x=r, y=0 while (x >= y) { Sharp96x96_PixelDraw(display, (UInt16)(x + x0), (UInt16)(y + y0), ulValue); Sharp96x96_PixelDraw(display, (UInt16)(y + x0), (UInt16)(x + y0), ulValue); Sharp96x96_PixelDraw(display, (UInt16)(-x + x0), (UInt16)(y + y0), ulValue); Sharp96x96_PixelDraw(display, (UInt16)(-y + x0), (UInt16)(x + y0), ulValue); Sharp96x96_PixelDraw(display, (UInt16)(-x + x0), (UInt16)(-y + y0), ulValue); Sharp96x96_PixelDraw(display, (UInt16)(-y + x0), (UInt16)(-x + y0), ulValue); Sharp96x96_PixelDraw(display, (UInt16)(x + x0), (UInt16)(-y + y0), ulValue); Sharp96x96_PixelDraw(display, (UInt16)(y + x0), (UInt16)(-x + y0), ulValue); y++; if (decisionOver2 <= 0) { decisionOver2 += 2 * y + 1; // Change in decision criterion for y -> y+1 } else { x--; decisionOver2 += 2 * (y - x) + 1; // Change for y -> y+1, x -> x-1 } } } public static void write(sharp96Display display, char[] c, byte textsize, int ulValue) { for (int len = 0; len < c.Length; len++) { write(display, c[len], textsize, ulValue); } } public static void write(sharp96Display display, char c, byte textsize, int ulValue) { // bounds check if (display.cursorY >= LCD_VERTICAL_MAX) return; // were off the screen if (c == '\n') // do we have a new line, if so simply adjust cursor position { display.cursorY += (byte)(textsize * 8); // next line based on font size display.cursorX = 0; // back to character 0 } else if (c == '\r') { display.cursorX = 0; // back to character 0 } else { drawChar(display, display.cursorX, display.cursorY, (byte)c, textsize, ulValue); display.cursorX += (byte)(textsize * 6); if (AutoWrap && (display.cursorX > (LCD_HORIZONTAL_MAX - textsize * 6))) { display.cursorY += (byte)(textsize * 8); // next line based on font size display.cursorX = 0; // back to character 0 } } } public static void drawChar(sharp96Display display, UInt16 x, UInt16 y, byte c, UInt16 size, int ulValue) { if ((x >= LCD_HORIZONTAL_MAX) || (y >= LCD_VERTICAL_MAX) || ((x + 6 * size - 1) < 0) || ((y + 8 * size - 1) < 0)) return; // bounds checks for (byte i = 0; i < 6; i++) // 5*7 font + space { UInt16 line; // index into character font array if (i == 5) line = 0x0; // space between characters else line = DisplayFont.font[((c * 5) + i)]; for (byte j = 0; j < 8; j++) // now process each bit of the character {// we have a bit and normal colour or no bit and inverted if ( (((line & 0x1)==1) && (ulValue != 0)) || (((line & 0x1) == 0) && (ulValue == 0)) ) // do we have a bit and black pixels (clear bits) { if (size == 1) // default size Sharp96x96_PixelDraw(display, (UInt16)(x + i),(UInt16)(y + j), 0xFF); else { // scaled up font Graphics_Rectangle pRect; pRect.XMin = x + (i * size); pRect.YMin = y + (j * size); pRect.XMax = pRect.XMin + size-1; pRect.YMax = pRect.YMin + size-1; Sharp96x96_RectFill(display, pRect, 0xFF); } } else // no bit so clear it, we need to handle reverse colour at some point { if (size == 1) // default size Sharp96x96_PixelDraw(display, (UInt16)(x + i),(UInt16)(y + j), 0x00); else { // big size Graphics_Rectangle pRect; pRect.XMin = x + (i * size); pRect.YMin = y + (j * size); pRect.XMax = pRect.XMin + size-1; pRect.YMax = pRect.YMin + size-1; Sharp96x96_RectFill(display, pRect, 0x00); } } line >>= 1; // next bit in the line } } } public static void setCursor(sharp96Display display, UInt16 x, UInt16 y) { // needs some bounds checking display.cursorX = (byte)x; display.cursorY = (byte)y; } } }
I will this time leave it to the video to describe it, please feel free to post questions though.
UPDATE: 13th August 2015:- Minor update to the VCOM routine to prevent screen BIAS effect, I noticed it was not working so I fixed it. the problem was it flipflops the bit but as I was doing it twice for each tick due to two displays, each display never got the alterate bit so it did not work. I moved the bit to the Sharp96Display class to it is unique to each display and adapted the routing. Now working well.
Here is what happens without it, the right display (On the first image) is supposed to be an even gray, as you can see, the old image remains
the other image was the result of being left on all night but with the bug i found unfixed and the third image shows what you see when you put another image on the display, as you can see, there is still text from the previous text
Now with the working code, the images will over a few minutes or more, fix themselves to no real harm done but somthing to pay attention to.
The attached code now contains the fix.
At this point I want to thank TI and Adafruit for their great graphics linraries that where the inspiration for this code. The above code is heavily modified and adapted from anything I saw at either site but their code helped immensly in figuring out the algorithms and base set of needed functions. The above code will also drive both thier products so win win all arround.
The TI PDF can be found here: http://www.ti.com/lit/ug/slau553/slau553.pdf
Adafruits link here: https://www.adafruit.com/products/1393
Sharps Datasheet here http://www.sharpmemorylcd.com/resources/ls013b4dn04_application_info.pdf
and a really useful APP note here: http://www.sharpmemorylcd.com/resources/programming_memory_lcd_app_note.pdf
The one thing not mentioned clearly anywhere is the need to swap the bits of the addressing around before sending the commands to the display, all other bytes go as normal but the address does not... wierd but its sorted in the code.