pr1v1t 0 Posted April 7, 2014 Share Posted April 7, 2014 Hey guys! I'm attempting to develop some drivers for an ILI9341-based 320x240, full color LCD display in parallel 8-bit mode (this screen does not have SPI connections). I am using TI's graphics library and template drivers as a starting point. I thought I had what would be a working driver, but after initializing the LCD (it turns off then back on) during debug, all I get is a white background when attempting to run TI's GRLIB example file for the MSP430F5529 (replacing their LCD drivers with my own). I've used about five different versions of the LCD initialization code, none of which want to work. Any ideas as to where the problem lies in these drivers? Both the driver header file and c file are included: I have also included TI's example file main.c at the bottom. Thanks in advance. I'm running out of ideas here. Could it possibly be a problem with defining my pins from the LCD as GPIO_PORTx_BASE instead of defining an exact port (1,2,3,etc)? TI's drivers seem to handle this just fine with a similar pinout, but I figured I should ask to make sure. Template_Driver.c /* --COPYRIGHT--,BSD * Copyright (c) 2013, Texas Instruments Incorporated * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Texas Instruments Incorporated nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * --/COPYRIGHT--*/ //***************************************************************************** // // Template_Driver.c - Display driver for any LCD Controller. This file serves as // a template for creating new LCD driver files // //***************************************************************************** // //! \addtogroup display_api //! @{ // //***************************************************************************** //***************************************************************************** // // READ ME // // This template driver is intended to be modified for creating new LCD drivers // It is setup so that only Template_DriverPixelDraw() and DPYCOLORTRANSLATE() // and some LCD size configuration settings in the header file Template_Driver.h // are REQUIRED to be written. These functions are marked with the string // "TemplateDisplayFix" in the comments so that a search through Template_Driver.c and // Template_Driver.h can quickly identify the necessary areas of change. // // Template_DriverPixelDraw() is the base function to write to the LCD // display. Functions like WriteData(), WriteCommand(), and SetAddress() // are suggested to be used to help implement the Template_DriverPixelDraw() // function, but are not required. SetAddress() should be used by other pixel // level functions to help optimize them. // // This is not an optimized driver however and will significantly impact // performance. It is highly recommended to first get the prototypes working // with the single pixel writes, and then go back and optimize the driver. // Please see application note www.ti.com/lit/pdf/slaa548 for more information // on how to fully optimize LCD driver files. In int16_t, driver optimizations // should take advantage of the auto-incrementing of the LCD controller. // This should be utilized so that a loop of WriteData() can be used instead // of a loop of Template_DriverPixelDraw(). The pixel draw loop contains both a // SetAddress() + WriteData() compared to WriteData() alone. This is a big time // saver especially for the line draws and Template_DriverPixelDrawMultiple. // More optimization can be done by reducing function calls by writing macros, // eliminating unnecessary instructions, and of course taking advantage of other // features offered by the LCD controller. With so many pixels on an LCD screen // each instruction can have a large impact on total drawing time. // //***************************************************************************** //***************************************************************************** // // Include Files // //***************************************************************************** #include <msp430.h> #include "grlib.h" #include "Template_Driver.h" #define MCLK_FREQUENCY 25 #define MCLKFREQUENCY 25 //***************************************************************************** // // Global Variables // //***************************************************************************** /* Global buffer for the display. This is especially useful on 1BPP, 2BPP, and 4BPP displays. This allows you to update pixels while reading the neighboring pixels from the buffer instead of a read from the LCD controller. A buffer is not required as a read followed by a write can be used instead.*/ uint8_t Template_Memory[(LCD_X_SIZE * LCD_Y_SIZE * BPP + 7) / 8]; //***************************************************************************** // // Suggested functions to help facilitate writing the required functions below // //***************************************************************************** // Writes data to the LCD controller static void WriteData(unsigned char usData) { /* Write data to the LCD controller. For instance this can be bit banged with 6800 or 8080 protocol or this could be the SPI routine for a SPI LCD */ P6DIR = BIT1; P6OUT |= BIT1; //RD HIGH //COMMAND_MODE; P7DIR = BIT4; P7OUT &= BIT4;//CS LOW; P6DIR = BIT0; P6OUT &= ~BIT0; //WR LOW _delay_cycles(MCLKFREQUENCY * 1000); P3DIR = BIT0 + BIT1 + BIT2 + BIT3 +BIT4 +BIT5 + BIT6 + BIT7; P3OUT |= usData; //LSB PORT = 3.0 _delay_cycles(MCLKFREQUENCY * 1000); P6DIR = BIT0; P6OUT = BIT0; //WR HIGH } // Writes a command to the LCD controller static void WriteCommand(unsigned char ucCommand) { /* This function is typically very similar (sometimes the same) as WriteData() The difference is that this is for the LCD to interpret commands instead of pixel data. For instance in 8080 protocol, this means pulling the DC line low.*/ P6DIR = BIT1; P6OUT |= BIT1; //RD HIGH //COMMAND_MODE; P7DIR = BIT4; P7OUT &= BIT4;//CS LOW; P6DIR = BIT0; P6OUT &= ~BIT0; //WR LOW _delay_cycles(MCLKFREQUENCY * 1000); P3DIR = BIT0 + BIT1 + BIT2 + BIT3 + BIT4 + BIT5 + BIT6 + BIT7; P3OUT |= ucCommand; _delay_cycles(MCLKFREQUENCY * 1000); P6DIR = BIT0; P6OUT |= BIT0; //WR HIGH } // Sets the pixel address of the LCD driver void SetAddress(int16_t lX, int16_t lY, int16_t mX, int16_t mY) { /* This function typically writes commands (using WriteCommand()) to the LCD to tell it where to move the cursor for the next pixel to be drawn. */ WriteCommand(0x2A); WriteData((lX >> 8) & 0xFF); WriteData(lX & 0xFF); WriteData((lY >> 8) & 0xFF); WriteData(lY & 0xFF); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0x2B); WriteData((mX >> 8) & 0xFF); WriteData(mX & 0xFF); WriteData((mY >> 8) & 0xFF); WriteData(mY & 0xFF); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0x2C); P7DIR = BIT4; P7OUT |= BIT4; } // Initializes the pins required for the GPIO-based LCD interface. // This function configures the GPIO pins used to control the LCD display // when the basic GPIO interface is in use. On exit, the LCD controller // has been reset and is ready to receive command and data writes. static void InitGPIOLCDInterface(void) { /* Initialize the hardware to configure the ports properly for communication */ volatile unsigned int i; // // Configure the pins that connect to the LCD as GPIO outputs. // P1DIR = 0xFF; // Set Port 1 as outputs P1OUT = 0x00; // Set Port 1 outputs low P2DIR = 0xFF; // Set Port 2 as outputs P2OUT = 0x00; // Set Port 2 outputs low P3DIR = 0xFF; // Set Port 3 as outputs P3OUT = 0x00; // Set Port 3 outputs low P4DIR = 0xFF; // Set Port 4 as outputs P4OUT = 0x00; // Set Port 4 outputs low //P5 used with XT2, User buttons, User LEDs, and LCD Backlight P6DIR = 0xFF; // Set Port 6 as outputs P6OUT = 0x00; // Set Port 6 outputs low //P7 is touch screen (P7.0-P7.3) and RGB control (P7.4-P7.7) // // Set the LCD Backlight on. P5.6 set high to enable // TPS61165 Boost converter supports using P5.6 as PWM to dim backlight // P4DIR = BIT1; P4OUT |= BIT1; // // Set the LCD control pins to their default values. This also asserts the // LCD reset signal. // P6DIR = BIT1 + BIT0; P6OUT |= BIT1 + BIT0; // Set WRn and RDn high (P3.0/P3.1) // // Delay for 1ms. // __delay_cycles(MCLKFREQUENCY * 1000); // // Deassert the LCD reset signal. // P6DIR = BIT2; P6OUT |= BIT2; // // Delay for 1ms while the LCD comes out of reset. // __delay_cycles(MCLK_FREQUENCY * 1000); } // Initialize DisplayBuffer. // This function initializes the display buffer and discards any cached data. static void InitLCDDisplayBuffer(void *pvDisplayData, uint16_t ulValue) { uint16_t i=0,j=0; for(i =0; i< LCD_Y_SIZE; i++) for(j =0; j< (LCD_X_SIZE * BPP + 7) / 8; j++) Template_Memory[i * LCD_Y_SIZE + j] = ulValue; } // Initializes the display driver. // This function initializes the LCD controller // // TemplateDisplayFix void Template_DriverInit(void) { /*Initialize the LCD controller. This typically looks like: InitGPIOLCDInterface(); InitLCDDisplayBuffer(); // Init LCD controller parameters // Enable LCD // Init Backlight // Clear Screen */ InitGPIOLCDInterface(); InitLCDDisplayBuffer(&g_sTemplate_Driver, 0xFF); P6DIR = BIT3; P6OUT |= BIT3; _delay_cycles(1000); P6DIR = BIT3; P6OUT &= ~BIT3; _delay_cycles(1000); P6DIR = BIT3; P6OUT |= BIT3; _delay_cycles(1000); WriteCommand(0x01); P7DIR = BIT4; P7OUT |= BIT4; __delay_cycles(MCLK_FREQUENCY * 1000); WriteCommand(0x28); WriteCommand(0xCF); WriteData(0x00); WriteData(0x81);//was 83 WriteData(0x30); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0xED); WriteData(0x64); WriteData(0x03); WriteData(0x12); WriteData(0x81); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0xCB); WriteData(0x39); WriteData(0x2C); WriteData(0x00); WriteData(0x34); WriteData(0x02); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0xCF); WriteData(0x00); WriteData(0xC1); WriteData(0x30); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0xE8); WriteData(0x85); WriteData(0x00); WriteData(0x78); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0xEA); WriteData(0x00); WriteData(0x00); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0xED); WriteData(0x64); WriteData(0x03); WriteData(0x12); WriteData(0x81); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0xF7); WriteData(0x20); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0xC0); WriteData(0x23); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0xC1); WriteData(0x10); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0xC5); WriteData(0x2B); WriteData(0x2B); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0x36); WriteData(0x48); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0xB1); WriteData(0x00); WriteData(0x1B); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0xB6); WriteData(0x0A); WriteData(0x02); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0xF2); WriteData(0x02); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0x3A); WriteData(0x05); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0x26); WriteData(0x01); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0xE0); WriteData(0x0f); WriteData(0x31); WriteData(0x2B); WriteData(0x0C); WriteData(0x0E); WriteData(0x08); WriteData(0x4E); WriteData(0xF1); WriteData(0x37); WriteData(0x07); WriteData(0x10); WriteData(0x03); WriteData(0x0E); WriteData(0x09); WriteData(0x00); P7DIR = BIT4; P7OUT |= BIT4; WriteCommand(0xE1); WriteData(0x00); WriteData(0x0E); WriteData(0x14); WriteData(0x03); WriteData(0x11); WriteData(0x07); WriteData(0x31); WriteData(0xC1); WriteData(0x48); WriteData(0x08); WriteData(0x0F); WriteData(0x0C); WriteData(0x31); WriteData(0x36); WriteData(0x0F); P7DIR = BIT4; P7OUT |= BIT4; /* WriteCommand(0x2a); WriteData(0x00); WriteData(0x00); WriteData(0x00); WriteData(0xEF); WriteCommand(0x2b); WriteData(0x00); WriteData(0x00); WriteData(0x01); WriteData(0x3F); WriteCommand(0xb7); WriteData(0x07); WriteCommand(0xb6); WriteData(0x0a); WriteData(0x82); WriteData(0x27); WriteData(0x00); */ WriteCommand(0x11); P7DIR = BIT4; P7OUT |= BIT4; __delay_cycles(MCLK_FREQUENCY * 120); WriteCommand(0x29); P7DIR = BIT4; P7OUT |= BIT4; } //***************************************************************************** // // All the following functions (below) for the LCD driver are required by grlib // //***************************************************************************** //***************************************************************************** // //! 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. // //***************************************************************************** // TemplateDisplayFix static void Template_DriverPixelDraw(void *pvDisplayData, int16_t lX, int16_t lY, uint16_t ulValue) { /* This function already has checked that the pixel is within the extents of the LCD screen and the color ulValue has already been translated to the LCD. This function typically looks like: // Interpret pixel data (if needed) // Update buffer (if applicable) // Template_Memory[lY * LCD_Y_SIZE + (lX * BPP / 8)] = , |= , &= ... // Template memory must be modified at the bit level for 1/2/4BPP displays // SetAddress(MAPPED_X(lX, lY), MAPPED_Y(lX, lY)); // WriteData(ulValue); */ SetAddress(lX, lX, lY,lY); WriteData((unsigned char )((ulValue>>8)&0xff)); WriteData((unsigned char )(ulValue&0xff)); P6DIR = BIT0; P6OUT |= BIT0; } //***************************************************************************** // //! Draws a horizontal sequence of pixels 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 first pixel. //! \param lY is the Y coordinate of the first pixel. //! \param lX0 is sub-pixel offset within the pixel data, which is valid for 1 //! or 4 bit per pixel formats. //! \param lCount is the number of pixels to draw. //! \param lBPP is the number of bits per pixel; must be 1, 4, or 8. //! \param pucData is a pointer to the pixel data. For 1 and 4 bit per pixel //! formats, the most significant bit(s) represent the left-most pixel. //! \param pucPalette is a pointer to the palette used to draw the pixels. //! //! This function draws a horizontal sequence of pixels on the screen, using //! the supplied palette. For 1 bit per pixel format, the palette contains //! pre-translated colors; for 4 and 8 bit per pixel formats, the palette //! contains 24-bit RGB values that must be translated before being written to //! the display. //! //! \return None. // //***************************************************************************** static void Template_DriverPixelDrawMultiple(void *pvDisplayData, int16_t lX, int16_t lY, int16_t lX0, int16_t lCount, int16_t lBPP, const uint8_t *pucData, const uint16_t *pucPalette) { uint16_t ulByte; // // Determine how to interpret the pixel data based on the number of bits // per pixel. // switch(lBPP) { // The pixel data is in 1 bit per pixel format case 1: { // Loop while there are more pixels to draw while(lCount > 0) { // Get the next byte of image data ulByte = *pucData++; // Loop through the pixels in this byte of image data for(; (lX0 < 8) && lCount; lX0++, lCount--) { // Draw this pixel in the appropriate color Template_DriverPixelDraw(pvDisplayData, lX++, lY, ((uint16_t *)pucPalette)[(ulByte >> (7 - lX0)) & 1]); } // Start at the beginning of the next byte of image data lX0 = 0; } // The image data has been drawn break; } // The pixel data is in 2 bit per pixel format case 2: { // Loop while there are more pixels to draw while(lCount > 0) { // Get the next byte of image data ulByte = *pucData++; // Loop through the pixels in this byte of image data for(; (lX0 < 4) && lCount; lX0++, lCount--) { // Draw this pixel in the appropriate color Template_DriverPixelDraw(pvDisplayData, lX++, lY, ((uint16_t *)pucPalette)[(ulByte >> (6 - (lX0 << 1))) & 3]); } // Start at the beginning of the next byte of image data lX0 = 0; } // The image data has been drawn break; } // The pixel data is in 4 bit per pixel format case 4: { // Loop while there are more pixels to draw. "Duff's device" is // used to jump into the middle of the loop if the first nibble of // the pixel data should not be used. Duff's device makes use of // the fact that a case statement is legal anywhere within a // sub-block of a switch statement. See // http://en.wikipedia.org/wiki/Duff's_device for detailed // information about Duff's device. switch(lX0 & 1) { case 0: while(lCount) { // Get the upper nibble of the next byte of pixel data // and extract the corresponding entry from the palette ulByte = (*pucData >> 4); ulByte = (*(uint16_t *)(pucPalette + ulByte)); // Write to LCD screen Template_DriverPixelDraw(pvDisplayData, lX++, lY, ulByte); // Decrement the count of pixels to draw lCount--; // See if there is another pixel to draw if(lCount) { case 1: // Get the lower nibble of the next byte of pixel // data and extract the corresponding entry from // the palette ulByte = (*pucData++ & 15); ulByte = (*(uint16_t *)(pucPalette + ulByte)); // Write to LCD screen Template_DriverPixelDraw(pvDisplayData, lX++, lY, ulByte); // Decrement the count of pixels to draw lCount--; } } } // The image data has been drawn. break; } // The pixel data is in 8 bit per pixel format case 8: { // Loop while there are more pixels to draw while(lCount--) { // Get the next byte of pixel data and extract the // corresponding entry from the palette ulByte = *pucData++; ulByte = (*(uint16_t *)(pucPalette + ulByte)); // Write to LCD screen Template_DriverPixelDraw(pvDisplayData, lX++, lY, ulByte); } // The image data has been drawn break; } } } //***************************************************************************** // //! 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. // //***************************************************************************** static void Template_DriverLineDrawH(void *pvDisplayData, int16_t lX1, int16_t lX2, int16_t lY, uint16_t ulValue) { /* Ideally this function shouldn't call pixel draw. It should have it's own definition using the built in auto-incrementing of the LCD controller and its own calls to SetAddress() and WriteData(). Better yet, SetAddress() and WriteData() can be made into macros as well to eliminate function call overhead. */ do { Template_DriverPixelDraw(pvDisplayData, lX1, lY, ulValue); } while(lX1++ < lX2); } //***************************************************************************** // //! 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. // //***************************************************************************** static void Template_DriverLineDrawV(void *pvDisplayData, int16_t lX, int16_t lY1, int16_t lY2, uint16_t ulValue) { do { Template_DriverPixelDraw(pvDisplayData, lX, lY1, ulValue); } while(lY1++ < lY2); } //***************************************************************************** // //! 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. // //***************************************************************************** static void Template_DriverRectFill(void *pvDisplayData, const tRectangle *pRect, uint16_t ulValue) { int16_t x0 = pRect->sXMin; int16_t x1 = pRect->sXMax; int16_t y0 = pRect->sYMin; int16_t y1 = pRect->sYMax; while(y0++ <= y1) { Template_DriverLineDrawH(pvDisplayData, x0, x1, y0, ulValue); } } //***************************************************************************** // //! Translates a 24-bit RGB color to a display driver-specific color. //! //! \param pvDisplayData is a pointer to the driver-specific data for this //! display driver. //! \param ulValue is the 24-bit RGB color. The least-significant byte is the //! blue channel, the next byte is the green channel, and the third byte is the //! red channel. //! //! This function translates a 24-bit RGB color into a value that can be //! written into the display's frame buffer in order to reproduce that color, //! or the closest possible approximation of that color. //! //! \return Returns the display-driver specific color. // //***************************************************************************** static uint16_t Template_DriverColorTranslate(void *pvDisplayData, uint32_t ulValue) { /* The DPYCOLORTRANSLATE macro should be defined in TemplateDriver.h */ // // Translate from a 24-bit RGB color to a color accepted by the LCD. // return(DPYCOLORTRANSLATE(ulValue)); } //***************************************************************************** // //! 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. //! //! \return None. // //***************************************************************************** static void Template_DriverFlush(void *pvDisplayData) { // Flush Buffer here. This function is not needed if a buffer is not used, // or if the buffer is always updated with the screen writes. int16_t i=0,j=0; for(i =0; i< LCD_Y_SIZE; i++) for(j =0; j< (LCD_X_SIZE * BPP + 7) / 8; j++) Template_DriverPixelDraw(pvDisplayData, j, i, Template_Memory[i * LCD_Y_SIZE + j]); } //***************************************************************************** // //! Send command to clear screen. //! //! \param pvDisplayData is a pointer to the driver-specific data for this //! display driver. //! //! This function does a clear screen and the Display Buffer contents //! are initialized to the current background color. //! //! \return None. // //***************************************************************************** static void Template_DriverClearScreen (void *pvDisplayData, uint16_t ulValue) { // This fills the entire display to clear it // Some LCD drivers support a simple command to clear the display int16_t y0 = 0; while(y0++ <= (LCD_Y_SIZE - 1)) { Template_DriverLineDrawH(pvDisplayData, 0, LCD_X_SIZE - 1, y0, ulValue); } } //***************************************************************************** // //! The display structure that describes the driver for the blank template. // //***************************************************************************** const tDisplay g_sTemplate_Driver = { sizeof(tDisplay), Template_Memory, #if defined(PORTRAIT) || defined(PORTRAIT_FLIP) LCD_Y_SIZE, LCD_X_SIZE, #else LCD_X_SIZE, LCD_Y_SIZE, #endif Template_DriverPixelDraw, Template_DriverPixelDrawMultiple, Template_DriverLineDrawH, Template_DriverLineDrawV, Template_DriverRectFill, Template_DriverColorTranslate, Template_DriverFlush, Template_DriverClearScreen }; //***************************************************************************** // // Close the Doxygen group. //! @} // //***************************************************************************** Template_Header.h /* --COPYRIGHT--,BSD * Copyright (c) 2013, Texas Instruments Incorporated * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Texas Instruments Incorporated nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * --/COPYRIGHT--*/ //***************************************************************************** // // Template_Driver.h - Prototypes for the Hitachi138x110 LCD // display driver with a HD66753 // controller. // //***************************************************************************** #ifndef __Template_Driver_H__ #define __Template_Driver_H__ //***************************************************************************** // // Include Files // //***************************************************************************** /*Put any necessary include files here*/ //***************************************************************************** // // User Configuration for the LCD Driver // //***************************************************************************** // Number of pixels on LCD X-axis #define LCD_X_SIZE 320 // Number of pixels on LCD Y-axis #define LCD_Y_SIZE 240 // Number of bits required to draw one pixel on the LCD screen #define BPP 8 #define LCD_BUS_WIDTH 8 // Define LCD Screen Orientation Here #define LANDSCAPE //#define LANDSCAPE_FLIP //#define PORTRAIT //#define PORTRAIT_FLIP #define LCD_DATAH_PINS 0xFF #define LCD_DATAH_BASE GPIO_PORT3_BASE // Data register addresses of Ports on MSP430 #define GPIO_PORTA_BASE 0x0200 // GPIO Port A #define GPIO_PORTB_BASE 0x0220 // GPIO Port B #define GPIO_PORTC_BASE 0x0240 // GPIO Port C #define GPIO_PORTD_BASE 0x0260 // GPIO Port D // Data register offsets into Ports on MSP430 #define GPIO_P1_DATA 0x0002 // GPIO Data #define GPIO_P2_DATA 0x0003 // GPIO Data #define GPIO_P3_DATA 0x0002 // GPIO Data #define GPIO_P4_DATA 0x0003 // GPIO Data #define GPIO_P5_DATA 0x0002 // GPIO Data #define GPIO_P6_DATA 0x0003 // GPIO Data #define GPIO_P7_DATA 0x0002 // GPIO Data #define GPIO_P8_DATA 0x0003 // GPIO Data // Control Pins #define LCD_RST_BASE GPIO_PORT6_BASE #define LCD_RST_PIN GPIO_PIN_3 #define LCD_RD_BASE GPIO_PORT6_BASE #define LCD_RD_PIN GPIO_PIN_1 #define LCD_WR_BASE GPIO_PORT6_BASE #define LCD_WR_PIN GPIO_PIN_0 #define LCD_DC_BASE GPIO_PORT6_BASE #define LCD_DC_PIN GPIO_PIN_2 // Backlight Pins #define LCD_BL_BASE GPIO_PORT6_BASE #define LCD_BL_PIN GPIO_PIN_6 // GPIO Pin values #define GPIO_PIN_0 0x0001 // GPIO pin 0 #define GPIO_PIN_1 0x0002 // GPIO pin 1 #define GPIO_PIN_2 0x0004 // GPIO pin 2 #define GPIO_PIN_3 0x0008 // GPIO pin 3 #define GPIO_PIN_4 0x0010 // GPIO pin 4 #define GPIO_PIN_5 0x0020 // GPIO pin 5 #define GPIO_PIN_6 0x0040 // GPIO pin 6 #define GPIO_PIN_7 0x0080 // GPIO pin 7 //***************************************************************************** // // Defines for the pins that are used to communicate with the LCD Driver // //***************************************************************************** /*Define Pin Names here i.e. #define LCD_RESET_OUT P6OUT*/ //***************************************************************************** // // Defines for LCD driver configuration // //***************************************************************************** /* Defines for pixels, colors, masks, etc. Anything Template_Driver.c needs*/ //***************************************************************************** // // This driver operates in four different screen orientations. They are: // // * Portrait - The screen is taller than it is wide. This is selected by defining // PORTRAIT. // // * Landscape - The screen is wider than it is tall. This is selected by defining // LANDSCAPE. // // * Portrait flip - The screen is taller than it is wide. This is // selected by defining PORTRAIT_FLIP. // // * Landscape flip - The screen is wider than it is tall. This is // selected by defining LANDSCAPE_FLIP. // // These can also be imagined in terms of screen rotation; if landscape mode is // 0 degrees of screen rotation, portrait flip is 90 degrees of clockwise // rotation, landscape flip is 180 degrees of rotation, and portrait is // 270 degress of clockwise rotation. // // If no screen orientation is selected, "landscape" mode will be used. // //***************************************************************************** #if ! defined(PORTRAIT) && ! defined(PORTRAIT_FLIP) && \ ! defined(LANDSCAPE) && ! defined(LANDSCAPE_FLIP) #define LANDSCAPE #endif //***************************************************************************** // // Various definitions controlling coordinate space mapping and drawing // direction in the four supported orientations. // //***************************************************************************** #ifdef PORTRAIT #define MAPPED_X(x, y) (LCD_X_SIZE - (y) - 1) #define MAPPED_Y(x, y) (x) #endif #ifdef LANDSCAPE #define MAPPED_X(x, y) (x) #define MAPPED_Y(x, y) (y) #endif #ifdef PORTRAIT_FLIP #define MAPPED_X(x, y) (y) #define MAPPED_Y(x, y) (LCD_Y_SIZE - (x) - 1) #endif #ifdef LANDSCAPE_FLIP #define MAPPED_X(x, y) (LCD_X_SIZE - (x) - 1) #define MAPPED_Y(x, y) (LCD_Y_SIZE - (y) - 1) #endif //***************************************************************************** // // Various LCD Controller command name labels and associated control bits // //***************************************************************************** //***************************************************************************** // // Macros for the Display Driver // //***************************************************************************** /* All macros can go here. This is typically the color translation function (example below) and could also include Set_Address(), Write_Data(), etc. */ // // Translates a 24-bit RGB color to a display driver-specific color. // // \param c is the 24-bit RGB color. The least-significant byte is the blue // channel, the next byte is the green channel, and the third byte is the red // channel. // // This macro translates a 24-bit RGB color into a value that can be written // into the display's frame buffer in order to reproduce that color, or the // closest possible approximation of that color. This particular example // requires the 8-8-8 24 bit RGB color to convert into 5-6-5 16 bit RGB Color // Your conversion should be made to fit your LCD settings. // // \return Returns the display-driver specific color #define DPYCOLORTRANSLATE(c) ((((c) & 0x00f80000) >> 8) | \ (((c) & 0x0000fc00) >> 5) | \ (((c) & 0x000000f8) >> 3)) //***************************************************************************** // // Prototypes for the globals exported by this driver. // //***************************************************************************** extern void Template_DriverInit(void); extern const tDisplay g_sTemplate_Driver; extern uint8_t Template_Memory[]; #define HWREG(x) \ (*((volatile unsigned int *)(x))) #define HWREGH(x) \ (*((volatile unsigned short *)(x))) #define HWREGB(x) \ (*((volatile unsigned char *)(x))) #endif // __Template_Driver_H__ main.c /* --COPYRIGHT--,BSD * Copyright (c) 2013, Texas Instruments Incorporated * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Texas Instruments Incorporated nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * --/COPYRIGHT--*/ #include <stdint.h> #include "hw_memmap.h" #include "driverlibHeaders.h" #include "grlib.h" #include "LcdDriver/Template_Driver.h" #include "Images/images.h" #define MCLKFREQUENCY 25 uint16_t timeoutCounter; tContext g_sContext; tRectangle g_sRect; int32_t stringWidth = 0; void Board_init(void); void Clock_init(void); void Delay(void); void main(void) { tRectangle myRectangle1 = { 5, 10, 60, 50}; tRectangle myRectangle2 = { 30, 20, 100, 60}; tRectangle myRectangle3 = { 0, 0, 101, 63}; // Stop WDT WDT_A_hold(WDT_A_BASE); // Basic GPIO initialization Board_init(); Clock_init(); // Set up LCD Template_DriverInit(); GrContextInit(&g_sContext, &g_sTemplate_Driver); GrContextForegroundSet(&g_sContext, ClrBlack); GrContextBackgroundSet(&g_sContext, ClrWhite); GrContextFontSet(&g_sContext, &g_sFontFixed6x8); GrClearDisplay(&g_sContext); // Intro Screen GrStringDrawCentered(&g_sContext, "How to use MSP430", AUTO_STRING_LENGTH, 51, 16, TRANSPARENT_TEXT); GrStringDrawCentered(&g_sContext, "Graphics Library", AUTO_STRING_LENGTH, 51, 32, TRANSPARENT_TEXT); GrStringDrawCentered(&g_sContext, "Primitives", AUTO_STRING_LENGTH, 51, 48, TRANSPARENT_TEXT); Delay(); GrClearDisplay(&g_sContext); // Draw pixels and lines on the display GrStringDraw(&g_sContext, "Draw Pixels", AUTO_STRING_LENGTH, 20, 0, TRANSPARENT_TEXT); GrStringDraw(&g_sContext, "& Lines", AUTO_STRING_LENGTH, 30, 10, TRANSPARENT_TEXT); GrPixelDraw(&g_sContext, 10, 10); GrPixelDraw(&g_sContext, 10, 12); GrPixelDraw(&g_sContext, 12, 12); GrPixelDraw(&g_sContext, 12, 10); GrLineDraw(&g_sContext, 15, 15, 60, 60); GrLineDraw(&g_sContext, 10, 50, 90, 10); GrLineDraw(&g_sContext, 0, GrContextDpyHeightGet(&g_sContext) - 1, GrContextDpyWidthGet(&g_sContext) - 1, GrContextDpyHeightGet(&g_sContext) - 1); Delay(); GrClearDisplay(&g_sContext); // Draw circles on the display GrStringDrawCentered(&g_sContext, "Draw Circles", AUTO_STRING_LENGTH, 51, 5, TRANSPARENT_TEXT); GrCircleDraw(&g_sContext, 30, 50, 10); GrCircleFill(&g_sContext, 65, 37, 23); Delay(); GrClearDisplay(&g_sContext); // Draw rectangles on the display GrStringDrawCentered(&g_sContext, "Draw Rectangles", AUTO_STRING_LENGTH, 51, 5, TRANSPARENT_TEXT); GrRectDraw(&g_sContext, &myRectangle1); GrRectFill(&g_sContext, &myRectangle2); // Text won't be visible on screen due to transparency GrStringDrawCentered(&g_sContext, "Normal Text", AUTO_STRING_LENGTH, 65, 30, TRANSPARENT_TEXT); // Text draws foreground and background for opacity GrStringDrawCentered(&g_sContext, "Opaque Text", AUTO_STRING_LENGTH, 65, 40, OPAQUE_TEXT); GrContextForegroundSet(&g_sContext, ClrWhite); GrContextBackgroundSet(&g_sContext, ClrBlack); // Text draws with inverted color to become visible GrStringDrawCentered(&g_sContext, "Invert Text", AUTO_STRING_LENGTH, 65, 50, TRANSPARENT_TEXT); Delay(); GrClearDisplay(&g_sContext); // Invert the foreground and background colors GrContextForegroundSet(&g_sContext, ClrBlack); GrContextBackgroundSet(&g_sContext, ClrWhite); GrRectFill(&g_sContext, &myRectangle3); GrContextForegroundSet(&g_sContext, ClrWhite); GrContextBackgroundSet(&g_sContext, ClrBlack); GrStringDrawCentered(&g_sContext, "Invert Colors", AUTO_STRING_LENGTH, 51, 5, TRANSPARENT_TEXT); GrRectDraw(&g_sContext, &myRectangle1); GrRectFill(&g_sContext, &myRectangle2); // Text won't be visible on screen due to transparency GrStringDrawCentered(&g_sContext, "Normal Text", AUTO_STRING_LENGTH, 65, 30, TRANSPARENT_TEXT); // Text draws foreground and background for opacity GrStringDrawCentered(&g_sContext, "Opaque Text", AUTO_STRING_LENGTH, 65, 40, OPAQUE_TEXT); GrContextForegroundSet(&g_sContext, ClrBlack); GrContextBackgroundSet(&g_sContext, ClrWhite); // Text draws with inverted color to become visible GrStringDrawCentered(&g_sContext, "Invert Text", AUTO_STRING_LENGTH, 65, 50, TRANSPARENT_TEXT); Delay(); GrContextForegroundSet(&g_sContext, ClrBlack); GrContextBackgroundSet(&g_sContext, ClrWhite); GrClearDisplay(&g_sContext); // Draw Images on the display GrImageDraw(&g_sContext, &LPRocket_96x37_1BPP_UNCOMP, 3, 13); Delay(); GrClearDisplay(&g_sContext); GrImageDraw(&g_sContext, &TI_Logo_69x64_1BPP_UNCOMP, 16, 0); while(1) { } } void Board_init(void) { // Setup XT1 and XT2 GPIO_setAsPeripheralModuleFunctionInputPin( GPIO_PORT_P5, GPIO_PIN2 + GPIO_PIN3 + GPIO_PIN4 + GPIO_PIN5 ); GPIO_setOutputLowOnPin( GPIO_PORT_P1, GPIO_PIN0 + GPIO_PIN1 + GPIO_PIN2 + GPIO_PIN3 + GPIO_PIN4 + GPIO_PIN5 ); // Configure LED ports GPIO_setAsOutputPin( GPIO_PORT_P1, GPIO_PIN0 + GPIO_PIN1 + GPIO_PIN2 + GPIO_PIN3 + GPIO_PIN4 + GPIO_PIN5 ); GPIO_setOutputLowOnPin( GPIO_PORT_P8, GPIO_PIN1 + GPIO_PIN2 ); GPIO_setAsOutputPin( GPIO_PORT_P8, GPIO_PIN1 + GPIO_PIN2 ); // Configure button ports // Buttons on P1.7/P2.2 are inputs /* GPIO_setAsInputPin( GPIO_PORT_PA, GPIO_PIN7 + GPIO_PIN10 );*/ // Configure CapTouch ports /*GPIO_setOutputLowOnPin( GPIO_PORT_P1, GPIO_PIN1 + GPIO_PIN2 + GPIO_PIN3 + GPIO_PIN4 + GPIO_PIN5 + GPIO_PIN6 ); GPIO_setAsOutputPin( GPIO_PORT_P1, GPIO_PIN1 + GPIO_PIN2 + GPIO_PIN3 + GPIO_PIN4 + GPIO_PIN5 + GPIO_PIN6 ); GPIO_setOutputLowOnPin( GPIO_PORT_P6, GPIO_PIN0 + GPIO_PIN1 + GPIO_PIN2 + GPIO_PIN3 + GPIO_PIN4 + GPIO_PIN5 + GPIO_PIN6 + GPIO_PIN7 ); GPIO_setAsInputPin( GPIO_PORT_P6, GPIO_PIN0 + GPIO_PIN1 + GPIO_PIN2 + GPIO_PIN3 + GPIO_PIN4 + GPIO_PIN5 + GPIO_PIN6 + GPIO_PIN7 ); */ // Configure Cma3000 ports // ACCEL_INT pin is input /* GPIO_setAsInputPin( GPIO_PORT_P2, GPIO_PIN5 ); // SCK GPIO_setOutputLowOnPin( GPIO_PORT_P2, GPIO_PIN7 ); GPIO_setAsOutputPin( GPIO_PORT_P2, GPIO_PIN7 ); // ACCEL_SIMO, ACCEL_CS, ACCEL_PWR to low GPIO_setOutputLowOnPin( GPIO_PORT_P3, GPIO_PIN3 + GPIO_PIN5 + GPIO_PIN6 ); // ACCEL_SOMI pin is input GPIO_setAsInputPin( GPIO_PORT_P3, GPIO_PIN4 ); // ACCEL_SIMO, ACCEL_CS, ACCEL_PWR as outp GPIO_setAsOutputPin( GPIO_PORT_P3, GPIO_PIN3 + GPIO_PIN5 + GPIO_PIN6 ); */ // Configure Dogs102x6 ports // LCD_C/D, LCD_RST GPIO_setOutputLowOnPin( GPIO_PORT_P6, GPIO_PIN1 + GPIO_PIN3 ); GPIO_setAsOutputPin( GPIO_PORT_P6, GPIO_PIN1 + GPIO_PIN3 ); // LCD_CS, LCD_BL_EN GPIO_setOutputLowOnPin( GPIO_PORT_P6, GPIO_PIN0 + GPIO_PIN6 ); GPIO_setAsOutputPin( GPIO_PORT_P6, GPIO_PIN0 + GPIO_PIN6 ); // SIMO, SCK GPIO_setOutputLowOnPin( GPIO_PORT_P2, GPIO_PIN4 + GPIO_PIN5 ); // SOMI pin is input GPIO_setAsInputPin( GPIO_PORT_P4, GPIO_PIN2 ); GPIO_setAsOutputPin( GPIO_PORT_P4, GPIO_PIN1 + GPIO_PIN3 ); // Configure SDCard ports // SD_CS to high GPIO_setOutputHighOnPin( GPIO_PORT_P3, GPIO_PIN7 ); GPIO_setAsOutputPin( GPIO_PORT_P3, GPIO_PIN7 ); // Configure Wheel ports // A5 ADC input GPIO_setAsInputPin( GPIO_PORT_P6, GPIO_PIN5 ); // POT_PWR GPIO_setOutputLowOnPin( GPIO_PORT_P8, GPIO_PIN0 ); GPIO_setAsOutputPin( GPIO_PORT_P8, GPIO_PIN0 ); // Configure unused ports for low power GPIO_setOutputLowOnPin( GPIO_PORT_P2, GPIO_PIN0 + GPIO_PIN1 + GPIO_PIN2 + GPIO_PIN3 + GPIO_PIN6 + GPIO_PIN7 ); GPIO_setAsOutputPin( GPIO_PORT_P2, GPIO_PIN0 + GPIO_PIN1 + GPIO_PIN2 + GPIO_PIN3 + GPIO_PIN6 +GPIO_PIN7 ); GPIO_setOutputLowOnPin( GPIO_PORT_P1, GPIO_PIN0 + GPIO_PIN1 + GPIO_PIN2 + GPIO_PIN3 + GPIO_PIN4 + GPIO_PIN5 + GPIO_PIN6 ); GPIO_setAsOutputPin( GPIO_PORT_P1, GPIO_PIN0 + GPIO_PIN1 + GPIO_PIN2 + GPIO_PIN3 + GPIO_PIN4 + GPIO_PIN5 + GPIO_PIN6 ); GPIO_setOutputLowOnPin( GPIO_PORT_P4, GPIO_PIN0 + GPIO_PIN3 ); GPIO_setAsOutputPin( GPIO_PORT_P4, GPIO_PIN0 + GPIO_PIN3 ); GPIO_setOutputLowOnPin( GPIO_PORT_P6, GPIO_PIN4 + GPIO_PIN5 ); GPIO_setAsOutputPin( GPIO_PORT_P6, GPIO_PIN4 + GPIO_PIN5 ); GPIO_setOutputLowOnPin( GPIO_PORT_P7, GPIO_PIN0 + GPIO_PIN1 + GPIO_PIN2 + GPIO_PIN3 + GPIO_PIN5+ GPIO_PIN7 ); GPIO_setAsOutputPin( GPIO_PORT_P7, GPIO_PIN0 + GPIO_PIN1 + GPIO_PIN2 + GPIO_PIN3 + GPIO_PIN5+ GPIO_PIN7 ); } void Clock_init(void) { UCS_setExternalClockSource(UCS_BASE, 32768, 0); // Set Vcore to accomodate for max. allowed system speed PMM_setVCore(PMM_BASE, PMM_CORE_LEVEL_3 ); // Use 32.768kHz XTAL as reference UCS_LFXT1Start( UCS_BASE, UCS_XT1_DRIVE0, UCS_XCAP_3 ); // Set system clock to max (25MHz) UCS_initFLLSettle( UCS_BASE, 25000, 762 ); SFR_enableInterrupt(SFR_BASE, SFR_OSCILLATOR_FAULT_INTERRUPT ); // Globally enable interrupts __enable_interrupt(); } void Delay(void) { __delay_cycles(MCLKFREQUENCY * 4); } Quote Link to post Share on other sites
pr1v1t 0 Posted April 7, 2014 Author Share Posted April 7, 2014 I went ahead and changed the port definitions in the header file from GPIO_PORT(LETTER)_BASE to the exact port, i.e: GPIO_PORT3_BASE. I also noticed some pins listed in the template's main.c and changed those to the exact port/pin combinations I am using, adjusting the unused port settings accordingly. Still, nothing happens when i build and debug (besides the screen seeming to reboot by turning display off then on). The updated code has been put in the original post. Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.