/* * Copyright (C) 2013 Ole Reinhardt * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * 3. Neither the name of the copyright holders nor the names of * 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. * * For additional information see http://www.ethernut.de/ * */ #include #include #include #include #include #include #include #include /*! * \file dev/lcd_st7565r.c * * Driver for ST7565R LCD controller which provides functionality to * initialize the display and write data and commands to the LCD controller. * */ #ifndef ST7565R_SPI_MODE #define ST7565R_SPI_MODE SPI_MODE_3 #endif #ifndef ST7565R_SPI_RATE #define ST7565R_SPI_RATE 20000000 #endif #define ST17565R_DEFAULT_WIDTH 128 #define ST17565R_DEFAULT_HEIGHT 32 #ifndef ST17565R_0_WIDTH #define ST17565R_0_WIDTH ST17565R_DEFAULT_WIDTH #endif #ifndef ST17565R_0_HEIGHT #define ST17565R_0_HEIGHT ST17565R_DEFAULT_HEIGHT #endif #ifndef ST17565R_1_WIDTH #define ST17565R_1_WIDTH ST17565R_DEFAULT_WIDTH #endif #ifndef ST17565R_1_HEIGHT #define ST17565R_1_HEIGHT ST17565R_DEFAULT_HEIGHT #endif #define min(a, b) ((a) < (b) ? (a) : (b)) /*! * \brief Lock LCD controller SPI node from concurrent access * * \param dev Specifies LCD device * */ int St7565rNodeLock(NUTDEVICE * dev) { NUTSPINODE *node; ST7565R_DCB *dcb; NUTASSERT (dev != NULL && dev->dev_icb != NULL); node = dev->dev_icb; NUTASSERT(node->node_dcb != NULL); dcb = (ST7565R_DCB *) node->node_dcb; return NutEventWait(&dcb->dcb_lock, NUT_WAIT_INFINITE); } /*! * \brief Unlock LCD controller SPI node from concurrent access * * \param dev Specifies LCD device * */ void St7565rNodeUnlock(NUTDEVICE * dev) { NUTSPINODE *node; ST7565R_DCB *dcb; NUTASSERT (dev != NULL && dev->dev_icb != NULL); node = dev->dev_icb; NUTASSERT(node->node_dcb != NULL); dcb = (ST7565R_DCB *) node->node_dcb; NutEventPost(&dcb->dcb_lock); } /*! * \brief Send a command to the display controller * * Pull A0 low before writing to the controller. * * \param node Specifies the SPI node. * \param cmd Command code. * * \return 0 on success, -1 on errors. */ static inline int St7565rWriteCmd(NUTSPINODE * node, uint8_t cmd) { ST7565R_DCB *dcb = (ST7565R_DCB *) node->node_dcb; NUTSPIBUS *bus = (NUTSPIBUS *) node->node_bus; int rc; GpioPinSetLow(dcb->a0_port, dcb->a0_pin); rc = (*bus->bus_alloc) (node, 0); if (rc == 0) { rc = (*bus->bus_transfer) (node, &cmd, NULL, 1); if (rc == 0) { (*bus->bus_wait) (node, NUT_WAIT_INFINITE); } (*bus->bus_release) (node); } return rc; } /*! * \brief Write data to the display controller * * Pull A0 high before writing to the controller. * * \param node Specifies the SPI node. * \param data data buffer * \param size Number of bytes to write * * \return 0 on success, -1 on errors. */ static inline int St7565rWriteData(NUTSPINODE * node, uint8_t *data, size_t size) { ST7565R_DCB *dcb = (ST7565R_DCB *) node->node_dcb; NUTSPIBUS *bus = (NUTSPIBUS *) node->node_bus; int rc; GpioPinSetHigh(dcb->a0_port, dcb->a0_pin); rc = (*bus->bus_alloc) (node, 0); if (rc == 0) { rc = (*bus->bus_transfer) (node,data, NULL, size); if (rc == 0) { (*bus->bus_wait) (node, NUT_WAIT_INFINITE); } (*bus->bus_release) (node); } GpioPinSetLow(dcb->a0_port, dcb->a0_pin); return rc; } /*! * \brief Reset the LCD controller. * * Hard- or soft reset can be selected: * hard: Hard reset the LCD controller by pulling the reset line low * soft: Send soft reset command to the LCD controller * * \param node Specifies the SPI node. * \param hard 1: hard reset, 0: soft reset */ static int St7565rReset(NUTSPINODE * node, int hard) { ST7565R_DCB *dcb = (ST7565R_DCB *) node->node_dcb; if (hard) { GpioPinSetHigh(dcb->reset_port, dcb->reset_pin); NutMicroDelay(10); GpioPinSetLow(dcb->reset_port, dcb->reset_pin); NutMicroDelay(10); GpioPinSetHigh(dcb->reset_port, dcb->reset_pin); NutMicroDelay(10); return 0; } else { return St7565rWriteCmd(node, ST7565R_CMD_RESET); } } /*! * \brief Enable / Disable the LCD sleep mode * * \param dev Specifies LCD device * \param sleep 1: enable sleep mode, 0: enter normal operation mode * * \return 0 on success, -1 on errors. */ int St7565rSetMode(NUTDEVICE * dev, int sleep) { NUTSPINODE *node; NUTASSERT (dev != NULL && dev->dev_icb != NULL); node = dev->dev_icb; return St7565rWriteCmd(node, sleep ? ST7565R_CMD_SLEEP_MODE : ST7565R_CMD_NORMAL_MODE); } /*! * \brief Set page in display RAM * * Next the column address should be set. * * \param dev Specifies LCD device * \param address page address (range: 0..8) * * \return 0 on success, -1 on errors. */ int St7565rSetPageAddress(NUTDEVICE * dev, uint8_t address) { NUTSPINODE *node; NUTASSERT (dev != NULL && dev->dev_icb != NULL); node = dev->dev_icb; return St7565rWriteCmd(node, ST7565R_CMD_PAGE_ADDRESS_SET(address & 0x0F)); } /*! * \brief Set column in display RAM * * \param dev Specifies LCD device * \param address column address (range: 0..131) * * \return 0 on success, -1 on errors. */ int St7565rSetColAddress(NUTDEVICE * dev, uint8_t address) { NUTSPINODE *node; int rc; NUTASSERT (dev != NULL && dev->dev_icb != NULL); node = dev->dev_icb; rc = St7565rWriteCmd(node, ST7565R_CMD_COLUMN_ADDRESS_SET_MSB(address >> 4)); rc |= St7565rWriteCmd(node, ST7565R_CMD_COLUMN_ADDRESS_SET_LSB(address & 0x0F)); return rc; } /*! * \brief Set display start draw line address * * Configure the line to start drawing on the LCD * * \param dev Specifies LCD device * \param address display start line address (range: 0..63) * * \return 0 on success, -1 on errors. */ int St7565rSetDisplayStartLineAddress(NUTDEVICE * dev, uint8_t address) { NUTSPINODE *node; NUTASSERT (dev != NULL && dev->dev_icb != NULL); node = dev->dev_icb; return St7565rWriteCmd(node, ST7565R_CMD_START_LINE_SET(address & 0x3F)); } /*! * \brief Turn on / off the display * * \param dev Specifies LCD device * \param enable 1: Turn on display, 0: turn off display * * \return 0 on success, -1 on errors. */ int St7565rDisplayEnable(NUTDEVICE * dev, int enable) { NUTSPINODE *node; NUTASSERT (dev != NULL && dev->dev_icb != NULL); node = dev->dev_icb; return St7565rWriteCmd(node, enable ? ST7565R_CMD_DISPLAY_ON : ST7565R_CMD_DISPLAY_OFF); } /*! * \brief Set LCD contrast * * The contrast value will be set by modifying the LCD voltage. Out of range * settings might damage the LCD, therefor the contrast value is clamped to the * configured contrast range. (\ref ST7565R_DISPLAY_CONTRAST_MAX ... \ref ST7565R_DISPLAY_CONTRAST_MIN.) * * \param dev Specifies LCD device * \param contrast a number between 0 and 63 where the max values is given by * the LCD. * * \return 0 on success, -1 on errors. */ int St7565rSetContrast(NUTDEVICE * dev, uint8_t contrast) { NUTSPINODE *node; int rc; NUTASSERT (dev != NULL && dev->dev_icb != NULL); node = dev->dev_icb; if (contrast < ST7565R_CONTRAST_MIN) { contrast = ST7565R_CONTRAST_MIN; } if (contrast > ST7565R_CONTRAST_MAX) { contrast = ST7565R_CONTRAST_MAX; } rc = St7565rWriteCmd(node, ST7565R_CMD_ELECTRONIC_VOLUME_MODE_SET); rc |= St7565rWriteCmd(node, ST7565R_CMD_ELECTRONIC_VOLUME(contrast)); return rc; } /*! * \brief Invert LCD * * \param dev Specifies LCD device * \param enable 1: invert, 0: normal mode * * \return 0 on success, -1 on errors. */ int St7565rDisplayInvert(NUTDEVICE * dev, int invert) { NUTSPINODE *node; NUTASSERT (dev != NULL && dev->dev_icb != NULL); node = dev->dev_icb; return St7565rWriteCmd(node, invert ? ST7565R_CMD_DISPLAY_REVERSE : ST7565R_CMD_DISPLAY_NORMAL); } /*! * \brief Set all pixels for debugging purposes * * Turn on all pixels for debugging purposes. This will not affect the LCD RAM * * \param dev Specifies LCD device * \param all_on 1: All pixels on, 0: normal mode, show content of the LCD RAM * * \return 0 on success, -1 on errors. */ int St7565rDebugPixelsAllOn(NUTDEVICE * dev, int all_on) { NUTSPINODE *node; NUTASSERT (dev != NULL && dev->dev_icb != NULL); node = dev->dev_icb; return St7565rWriteCmd(node, all_on ? ST7565R_CMD_DISPLAY_ALL_POINTS_ON : ST7565R_CMD_DISPLAY_ALL_POINTS_OFF); } /*! * \brief Copy RAM Framebuffer to LCD * * \param dev Specifies LCD device * * \return 0 on success, -1 on errors. */ int St7565rUpdateFb(NUTDEVICE * dev) { NUTSPINODE *node; FBINFO *dcb; size_t fb_size; size_t offset; int page; int rc = 0; NUTASSERT (dev != NULL && dev->dev_icb != NULL); node = dev->dev_icb; dcb = (FBINFO *) dev->dev_dcb; /* Calculate size of framebuffer in bytes. Each bit represents on pixel */ fb_size = dcb->width * dcb->height / 8; for (offset = 0, page = 0; offset < fb_size; offset += 128, page ++) { rc |= St7565rWriteCmd(node, ST7565R_CMD_PAGE_ADDRESS_SET(page)); rc |= St7565rWriteCmd(node, ST7565R_CMD_COLUMN_ADDRESS_SET_MSB(0)); rc |= St7565rWriteCmd(node, ST7565R_CMD_COLUMN_ADDRESS_SET_LSB(0)); rc |= St7565rWriteData(node, dcb->fb + offset, min(128, fb_size - offset)); } return rc; } /*! * \brief Handle I/O controls for St7565 Framebuffer. * * \return 0. */ static int St7565rIOCtl(NUTDEVICE * dev, int req, void *conf) { NUTASSERT (dev != NULL && dev->dev_icb != NULL); return 0; } /*! * \brief Write data to ST7565R framebuffer. * * Each write operation updates the LCD RAM with the shadow framebuffer * * \param fp Pointer to the NUTFILE struct * \param buffer Data to be written or NULL if the buffer shall be just * synced with the display. * \param len Number of bytes to write * * \return Number of bytes written. */ static int St7565rWrite(NUTFILE * fp, const void *buffer, int len) { FBINFO *dcb; NUTASSERT(fp != NULL && fp != NUTFILE_EOF && fp->nf_dev != NULL); NUTASSERT(fp->nf_dev->dev_icb != NULL && fp->nf_dev->dev_dcb != NULL); dcb = fp->nf_dev->dev_dcb; if (buffer == NULL) { /* Flush the LCD shadow buffer / sync with LCD */ St7565rUpdateFb(fp->nf_dev); return 0; } len = min(len, dcb->width * dcb->height / 8); if (dcb->fb != NULL) { int i, bcount; int bytes_per_line = dcb->width / 8; uint8_t *fb = dcb->fb; uint8_t *src = (uint8_t*) buffer; uint8_t y0, y1; /* The ST7565r LCD controller has a really brain dead memory layout. * Each byte in the framebuffer represents 8 vertical pixels. * The framebuffer is organized in several banks. On bank represents * width * 8 vertical pixels. * * The following loop re-orders a framebuffer content organized in the * "normal" layout (1 byte represents 8 horizontal pixels) to the LCD * controllers layout. */ y1 = 0x01; y0 = ~y1; bcount = bytes_per_line; for (i = 0; i < len; i++) { bcount --; if (*src & 0x01) (*(fb++)) |= y1; else (*(fb++)) &= y0; if (*src & 0x02) (*(fb++)) |= y1; else (*(fb++)) &= y0; if (*src & 0x04) (*(fb++)) |= y1; else (*(fb++)) &= y0; if (*src & 0x08) (*(fb++)) |= y1; else (*(fb++)) &= y0; if (*src & 0x10) (*(fb++)) |= y1; else (*(fb++)) &= y0; if (*src & 0x20) (*(fb++)) |= y1; else (*(fb++)) &= y0; if (*src & 0x40) (*(fb++)) |= y1; else (*(fb++)) &= y0; if (*src & 0x80) (*(fb++)) |= y1; else (*(fb++)) &= y0; if (bcount == 0) { bcount = bytes_per_line; y1 = y1 << 1; if (y1 == 0) { y1 = 0x01; } y0 = ~y1; fb = &dcb->fb[((i+1) / dcb->width) * dcb->width]; } src ++; } St7565rUpdateFb(fp->nf_dev); } else { return 0; } return len; } /*! * \brief Read data from ST7565R framebuffer. * * The Read operation does not sync the shadow framebuffer with the LCD RAM. * Read returns data from the shadow framebuffer. * * \param fp Pointer to the NUTFILE struct * \param buffer Pointer to the buffer that receives the data. * \param len Number of bytes to read * * \return Number of bytes read. */ static int St7565rRead(NUTFILE * fp, void *buffer, int len) { FBINFO *dcb; NUTASSERT(fp != NULL && fp != NUTFILE_EOF && fp->nf_dev != NULL); NUTASSERT(fp->nf_dev->dev_icb != NULL && fp->nf_dev->dev_dcb != NULL); dcb = fp->nf_dev->dev_dcb; if ((dcb->fb != NULL) && (buffer != NULL)) { int i, bcount; int bytes_per_line = dcb->width / 8; uint8_t *fb = dcb->fb; uint8_t *dest = (uint8_t*) buffer; uint8_t y1; /* The ST7565r LCD controller has a really brain dead memory layout. * Each byte in the framebuffer represents 8 vertical pixels. * The framebuffer is organized in several banks. On bank represents * width * 8 vertical pixels. * * The following loop re-orders the LCD framebuffer layout to the * "normal" layout (1 byte represents 8 horizontal pixels) to the LCD * controllers layout and fills the read buffer. */ len = min(len, dcb->width * dcb->height / 8); y1 = 0x01; bcount = bytes_per_line; for (i = 0; i < len; i++) { bcount --; *dest = 0; if ((*fb++) & y1) *dest |= 0x01; if ((*fb++) & y1) *dest |= 0x02; if ((*fb++) & y1) *dest |= 0x04; if ((*fb++) & y1) *dest |= 0x08; if ((*fb++) & y1) *dest |= 0x10; if ((*fb++) & y1) *dest |= 0x20; if ((*fb++) & y1) *dest |= 0x40; if ((*fb++) & y1) *dest |= 0x80; if (bcount == 0) { bcount = bytes_per_line; y1 = y1 << 1; if (y1 == 0) { y1 = 0x01; } fb = &dcb->fb[((i+1) / dcb->width) * dcb->width]; } dest ++; } } else { return 0; } return len; } /*! * \brief Generate File Handle for ST7565R framebuffer device. * * \param dev Specifies the ST7565R LCD controller device. * * \return 0 on success or -1 in case of an error. */ static NUTFILE *St7565rOpen(NUTDEVICE * dev, const char *name, int mode, int acc) { NUTFILE *fp; NUTASSERT( dev != NULL); if ((fp = malloc(sizeof(NUTFILE))) == 0) { return NUTFILE_EOF; } fp->nf_fcb = 0; fp->nf_dev = dev; return fp; } /*! * \brief Close ST7565R framebuffer device. * * \return 0 if closed and was opened before, else -1. */ static int St7565rClose(NUTFILE * fp) { if( fp != NULL) { free( fp); return 0; } return -1; } /*! * \brief Initialize the LCD controller device * * Call this function to initialize the hardware interface and the LCD * controller. When initialization is done the display is turned on and ready * to receive data. * * Compared to other SPI bus node devices (and other Nut/OS devices in general) * some settings have to be configured by the user before registering the * SPI node. These are: * - devSt7565rFb0.dev_dcb * - devSt7565rFb.dev_icb->node_dcb * * This routine is internally called by Nut/OS during device registration. * * The driver framework may call this function more than once. * * \param dev Specifies the ST7565R LCD controller device. * * \return 0 on success or -1 in case of an error */ static int St7565rInit(NUTDEVICE * dev) { NUTSPINODE *node; FBINFO *dcb; ST7565R_DCB *node_dcb; int rc = 0; NUTASSERT(dev != NULL); NUTASSERT(dev->dev_icb != NULL); NUTASSERT(dev->dev_dcb != NULL); node = dev->dev_icb; node_dcb = (ST7565R_DCB *) node->node_dcb; node_dcb->dcb_lock = SIGNALED; dcb = dev->dev_dcb; if (dcb->fb != NULL) { /* The device is just initialised */ return 0; } /* Allocate framebuffer memory, w*h / 8, as we have only 1 BPP */ dcb->bpp = 1; dcb->fb = malloc(dcb->width * dcb->height / 8); if (dcb->fb == NULL) { return -1; } memset(dcb->fb, 0, dcb->width * dcb->height / 8); GpioPinConfigSet(node_dcb->a0_port, node_dcb->a0_pin, GPIO_CFG_OUTPUT); /* Configure A0 pin to command mode */ GpioPinSetLow(node_dcb->a0_port, node_dcb->a0_pin); GpioPinConfigSet(node_dcb->reset_port, node_dcb->reset_pin, GPIO_CFG_OUTPUT); GpioPinSetHigh(node_dcb->reset_port, node_dcb->reset_pin); /* Apply hard reset of the LCD display controller */ rc |= St7565rReset(node, 1); /* Turn on the display */ rc |= St7565rDisplayEnable(dev, 0); /* Set the voltage bias ratio to 1/6 */ rc |= St7565rWriteCmd(node, ST7565R_CMD_LCD_BIAS_1_DIV_6_DUTY33); /* Set column address mode to increasing */ rc |= St7565rWriteCmd(node, ST7565R_CMD_ADC_NORMAL); /* Reverse the common mode scan direction COM31->COM0 */ rc |= St7565rWriteCmd(node, ST7565R_CMD_REVERSE_SCAN_DIRECTION); /* Set voltage resistor ratio to 2 */ rc |= St7565rWriteCmd(node, ST7565R_CMD_VOLTAGE_RESISTOR_RATIO_2); /* Set booster circuit, voltage regulator and voltage follower all to on */ rc |= St7565rWriteCmd(node, ST7565R_CMD_POWER_CTRL_ALL_ON); /* Set the booster ratio to 2X,3X,4X */ rc |= St7565rWriteCmd(node, ST7565R_CMD_BOOSTER_RATIO_SET); rc |= St7565rWriteCmd(node, ST7565R_CMD_BOOSTER_RATIO_2X_3X_4X); /* Set contrast to min value */ rc |= St7565rSetContrast(dev, ST7565R_CONTRAST_MAX); rc |= St7565rSetDisplayStartLineAddress(dev, 0); /* Non-inverted display */ rc |= St7565rDisplayInvert(dev, 0); /* Turn on the display */ rc |= St7565rDisplayEnable(dev, 1); if (rc != 0) { free(dcb->fb); return -1; } return 0; } /*! * \brief First ST7565R LCD Controller SPI node implementation structure. */ NUTSPINODE nodeSt7565r0 = { NULL, /*!< \brief Pointer to the bus controller driver, node_bus. */ NULL, /*!< \brief Pointer to the bus device driver specific settings, node_stat. */ ST7565R_SPI_RATE, /*!< \brief Initial clock rate, node_rate. */ ST7565R_SPI_MODE, /*!< \brief Initial mode, node_mode. */ 8, /*!< \brief Initial data bits, node_bits. */ 0, /*!< \brief Chip select, node_cs. */ NULL, /*!< \brief Pointer to our private device control block, node_dcb. */ }; /*! * \brief 7seg device implementation structure. */ NUTDEVICE devSt7565rFb0 = { NULL, /*!< \brief Pointer to next device, dev_next. */ {'F', 'B', '0', 0, 0, 0, 0, 0, 0}, /*!< \brief Unique device name, dev_name. */ IFTYP_FB, /*!< \brief Type of device, dev_type. */ 0, /*!< \brief Base address, dev_base (not used). */ 0, /*!< \brief First interrupt number, dev_irq (not used). */ &nodeSt7565r0, /*!< \brief Interface control block, dev_icb. */ 0, /*!< \brief Driver control block, dev_dcb. */ St7565rInit, /*!< \brief Driver initialization routine, dev_init. */ St7565rIOCtl, /*!< \brief Driver specific control function, dev_ioctl. */ St7565rRead, /*!< \brief Read from device, dev_read. */ St7565rWrite, /*!< \brief Write to device, dev_write. */ #ifdef __HARVARD_ARCH__ 0, /*!< \brief Write data from program space to device, dev_write_P. */ #endif St7565rOpen, /*!< \brief Mount volume, dev_open. */ St7565rClose, /*!< \brief Unmount volume, dev_close. */ 0, /*!< \brief Request file size, dev_size. */ 0, /*!< \brief Select, currently not implemented, dev_select. */ };