/* * Copyright (C) 2009 by Ulrich Prinz (uprinz2@netscape.net) * Copyright (C) 2009 by Rittal GmbH & Co. KG. All rights reserved. * * 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/ */ /*! * \file dev/spi_7seg.c * \brief Routines for 7 segment display. * * \verbatim * $Id: spi_7seg.c,$ * \endverbatim */ #include #include //#include #include #include #include #ifdef SPI2SEG_DEBUG #include #define NPRINTF(args,...) printf(args,##__VA_ARGS__) #else #define NPRINTF(args,...) #endif #include #include #include #include #include #include #include #include #include /* Number of used or connected digits */ #ifndef SEG7_DIGITS #define SEG7_DIGITS 4 #endif #define SEG7_REVERSE /* AS1106/7/8 Register Abbreviations */ #define SEGR_NOOP 0x00 #define SEGR_DIG0 0x01 #define SEGR_DIG1 0x02 #define SEGR_DIG2 0x03 #define SEGR_DIG3 0x04 #define SEGR_DIG4 0x05 #define SEGR_DIG5 0x06 #define SEGR_DIG6 0x07 #define SEGR_DIG7 0x08 #define SEGR_DEC_MODE 0x09 #define SEGR_INTENSITY 0x0a #define SEGR_SCAN_LIM 0x0b #define SEGR_SHUTDOWN 0x0c #define SEGR_FEATURE 0x0e #define SEGR_DSP_TEST 0x0f /* Shutdown Register (0x0c) */ #define SHUTDOWN_RESET 0x00 #define SHUTDOWN_SOFT 0x80 #define NORM_OP_RESET 0x01 #define NORM_OP_SOFT 0x81 /* Decode Mode Register (0x09) */ #define NO_DIG_DECODE 0x00 #define DIG_0_DECODE 0x01 #define DIG_0_3_DECODE 0x0f #define DIG_0_7_DECODE 0xff /* feature register (0x0e) */ #define SEGF_EXTCLK 0x01 #define SEGF_REGRES 0x02 #define SEGF_DECSEL 0x04 #define SEGF_SPIEN 0x08 #define SEGF_BLINK 0x10 #define SEGF_BLSLOW 0x20 #define SEGF_BLSYNC 0x40 #define SEGF_BLSTART 0x80 #define SEGF_BLMASK (SEGF_BLINK|SEGF_BLSLOW) /* Test Mode register (0x0f) */ #define TEST_MODE_OFF 0x00 #define TEST_MODE_ON 0x01 /* scan limit register (0x0b) */ #define DISPLAY_LIMIT 2 #ifndef SPI_RATE_DISP_7SEG #define SPI_RATE_DISP_7SEG 400000 #endif #ifndef SPI_MODE_DISP_7SEG #define SPI_MODE_DISP_7SEG SPI_MODE_3 #endif /* Bitmask for driver control */ #define ICMD_UPDATE 0x01 #define ICMD_INTENS 0x02 #define ICMD_ESCAPE 0x80 /*! * \brief Device Control Block for 7-Segment Display */ typedef struct { uint_fast8_t digit[SEG7_DIGITS]; /*!< Buffer for Display content */ uint_fast8_t dip; /*!< Actual cursor position */ uint_fast8_t freg; /*!< mirror of internal feature control register */ uint_fast8_t icmd; /*!< internal driver control register */ } DCB_7SEG; /********************************************************************************* ** ** 7-Segement Driver: Character Table ** **/ /* Display Interconnection * * --A-- * | | * F B Bit: 7 6 5 4 3 2 1 0 * +--G--+ Led: dp A B C D E F G * E C * | | * --D-- dp */ /* 7 Segment Display Character Table */ static const uint8_t Seg7CharTab[] = { /* ' ' */(0x00), /* '!' */(0x28), /* '"' */(0x22), /* '#' */(0x00), /* '$' */(0x5B), /* '%' */(0x00), /* '&' */(0x6F), /* '´' */(0x20), /* '(' */(0x4E), /* ')' */(0x78), /* '*' */(0x00), /* '+' */(0x31), /* ''' */(0x20), /* '-' */(0x01), /* '.' */(0x01), /* '/' */(0x15), /* '0' */(0x7E), /* '1' */(0x30), /* '2' */(0x6D), /* '3' */(0x79), /* '4' */(0x33), /* '5' */(0x5B), /* '6' */(0x5F), /* '7' */(0x70), /* '8' */(0x7F), /* '9' */(0x7B), /* ':' */(0x00), /* ';' */(0x00), /* '<' */(0x00), /* '=' */(0x09), /* '>' */(0x00), /* '?' */(0x65), /* '@' */(0x00), /* 'A' */(0x77), /* 'b' */(0x1F), /* 'c' */(0x0D), /* 'd' */(0x3D), /* 'E' */(0x4F), /* 'F' */(0x47), /* 'G' */(0x5F), /* 'H' */(0x37), /* 'i' */(0x10), /* 'J' */(0x3C), /* 'K' */(0x0F), /* 'L' */(0x0E), /* 'M' */(0x76), /* 'N' */(0x15), /* 'O' */(0x1D), /* 'P' */(0x67), /* 'Q' */(0x73), /* 'R' */(0x05), /* 'S' */(0x5B), /* 'T' */(0x0F), /* 'U' */(0x3E), /* 'V' */(0x1C), /* 'W' */(0x3F), /* 'X' */(0x37), /* 'Y' */(0x3B), /* 'Z' */(0x6D), /* '[' */(0x4E), /* '\' */(0x13), /* ']' */(0x78), /* '^' */(0x42), /* '_' */(0x01), }; /********************************************************************************* ** ** 7-Segement Driver: Low Level Communication ** **/ /*! * \brief Send 7seg command. * * \param node Specifies the SPI node. * \param addr Optional command parameter. * \param txbuf Pointer to the transmit data buffer, may be set to NULL. * \param rxbuf Pointer to the receive data buffer, may be set to NULL. * \param xlen Number of byte to receive and/or transmit. */ static int disp7segCommand(NUTSPINODE * node, uint8_t addr, const void *txbuf, void *rxbuf, int xlen) { int rc = -1; NUTSPIBUS *bus; uint8_t *tmp; uint8_t cmd[2]; NUTASSERT(node != NULL); bus = (NUTSPIBUS *) node->node_bus; NUTASSERT(bus != NULL); NUTASSERT(bus->bus_alloc != NULL); NUTASSERT(bus->bus_transfer != NULL); NUTASSERT(bus->bus_release != NULL); /* write address */ cmd[0] = addr; tmp = (uint8_t *)txbuf; /* write data */ cmd[1] = tmp[0]; //cmd[1] = (uint8_t )txbuf[0]; /* write data */ rc = (*bus->bus_alloc) (node, 1000); if (rc == 0) { rc = (*bus->bus_transfer) (node, cmd, NULL, 2); (*bus->bus_release) (node); } return rc; } /*! * \brief Push display buffer to display. * * \param dev Display device. */ void Spi7SegPush( NUTDEVICE * dev) { uint_fast8_t i; DCB_7SEG * dcb; NUTSPINODE *node; NUTASSERT(dev->dev_dcb != NULL); NUTASSERT(dev->dev_icb != NULL); dcb = dev->dev_dcb; node = dev->dev_icb; #ifdef SEG7_REVERSE NPRINTF("<"); for (i=0;idigit[i]), NULL, 1); NPRINTF(" %02x",dcb->digit[i]); } NPRINTF(">\n"); #else disp7segCommand(node, 0, dcb->digit, NULL, SEG7_DIGITS); #endif } /********************************************************************************* ** ** 7-Segement Driver: Character Driver ** **/ /*! * \brief Send single character to 7-Segment Display. * * A newline character will reset write pointer to digit 0. * Carriage return is ignored. * * \return Number of characters sent. */ int Spi7segPutc(NUTDEVICE * dev, char c) { DCB_7SEG * dcb; NUTSPINODE *node; NUTASSERT(dev->dev_dcb != NULL); NUTASSERT(dev->dev_icb != NULL); dcb = dev->dev_dcb; node = dev->dev_icb; NPRINTF("[%c]", c); if( dcb->icmd & ICMD_ESCAPE) { dcb->icmd &= ~ICMD_ESCAPE; /* Handle ESC sequences */ switch( c) { case 'b': /* blink slow */ dcb->freg |= (SEGF_BLINK | SEGF_BLSLOW); disp7segCommand( node, SEGR_FEATURE, &dcb->freg, NULL, 1); return 0; break; case 'f': /* blink fast */ dcb->freg &= ~SEGF_BLMASK; dcb->freg |= SEGF_BLINK ; disp7segCommand( node, SEGR_FEATURE, &dcb->freg, NULL, 1); return 0; break; case 'n': /* stop blinking */ dcb->freg &= ~SEGF_BLMASK; disp7segCommand( node, SEGR_FEATURE, &dcb->freg, NULL, 1); return 0; break; case 'h': /* home */ dcb->dip = 0; return 0; break; case 'c': /* clear */ memset( dcb->digit, 0, SEG7_DIGITS); dcb->icmd |= ICMD_UPDATE; break; case 't': /* test, all digits on */ memset( dcb->digit, 0xFF, SEG7_DIGITS); dcb->icmd |= ICMD_UPDATE; break; case 'i': /* intensity control */ dcb->icmd |= ICMD_INTENS; return 0; break; default: break; } } else { /* Non-ESC Character incoming */ /* Start ESC Sequence? */ if( c == 0x1b) { dcb->icmd |= ICMD_ESCAPE; return 0; } /* Complete Intensity command */ if( dcb->icmd & ICMD_INTENS) { dcb->icmd &= ~ICMD_INTENS; c = (c>'9')?(c-'A'+10):(c-'0'); disp7segCommand( node, SEGR_INTENSITY, &c, NULL, 1); return 0; } if( c == '\n' ) { /* Return to Digit 0 */ dcb->dip = 0; return 0; } /* Everything from here down, needs update of display */ if( c == '.') { /* Add decimal point to previous digit */ if( dcb->dip > 0) dcb->digit[dcb->dip-1] |= 0x80; dcb->icmd |= ICMD_UPDATE; } else if( (c >= ' ') && ( dcb->dip < SEG7_DIGITS)) { if( c > 0x5F) c -= 0x20; /* convert lower case to upper case */ /* Print incoming character */ dcb->digit[ dcb->dip++] = Seg7CharTab[(c&0xff)-' ']; dcb->icmd |= ICMD_UPDATE; } } if( dcb->dip > SEG7_DIGITS) dcb->dip = SEG7_DIGITS; if( dcb->icmd & ICMD_UPDATE) { dcb->icmd &= ~ICMD_UPDATE; Spi7SegPush( dev); } return 0; } /********************************************************************************* ** ** 7-Segement Driver: File Device Handling ** **/ /*! * \brief Handle I/O controls for 7-Segment Display. * * The 7-Seg Display is controlled by ESC-Sequences only. * * \return 0. */ static int Spi7SegIOCtl(NUTDEVICE * dev, int req, void *conf) { return 0; } /*! * \brief Send characters to 7-Segment Display. * * A newline character will reset write pointer to digit 0. * Carriage return is ignored. * * \return Number of characters sent. */ int Spi7segWrite(NUTFILE * fp, const void *buffer, int len) { int i=len; const char *cp = buffer; NUTASSERT(fp->nf_dev != NULL); while(i--) { Spi7segPutc( fp->nf_dev, *cp++); } return len; } /*! * \brief File Handle for 7-Sefgment Display. * * \param pos Selects the digit. * \param act Selects the action for the digits decimal dot. * */ void Spi7segDot(NUTDEVICE * dev, uint8_t pos, uint8_t act) { DCB_7SEG * dcb; NUTASSERT(dev->dev_dcb != NULL); dcb = dev->dev_dcb; if( pos < SEG7_DIGITS) { switch( act) { case DOT_7SEG_FLIP: dcb->digit[pos] ^= 0x80; break; case DOT_7SEG_SET: dcb->digit[pos] |= 0x80; break; case DOT_7SEG_CLR: dcb->digit[pos] &= ~0x80; break; } Spi7SegPush( dev); } } /*! * \brief Generate File Handle for 7-Sefgment Display. * * \param dev Specifies the 7seg device. * * \return 0 on success or -1 if no valid 7seg was found. */ NUTFILE *Spi7SegOpen(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_dev = dev; fp->nf_fcb = NULL; return fp; } /*! * \brief Close 7-Segment Device. * * \return 0 if closed and was opened before, else -1. */ static int Spi7SegClose(NUTFILE * fp) { if( fp != NULL) { free( fp); return 0; } return -1; } /********************************************************************************* ** ** 7-Segement Driver: Initialization & Device Description ** **/ /*! * \brief Initialize the 7seg device. * * This routine determines the 7seg type. It is internally called * by Nut/OS during device registration. * * The driver framework may call this function more than once. * * \param dev Specifies the 7seg device. * * \return 0 on success or -1 if no valid 7seg was found. */ int Spi7segInit(NUTDEVICE * dev) { uint8_t data; NUTSPINODE *node; DCB_7SEG * dcb; NUTASSERT(dev != NULL); NUTASSERT(dev->dev_icb != NULL); node = dev->dev_icb; /* Allocate device control block */ dcb = malloc( sizeof( DCB_7SEG)); if( dcb == NULL) return -1; memset( dcb, 0, sizeof( DCB_7SEG)); dev->dev_dcb = dcb; NPRINTF("INIT %d Digits...\n", SEG7_DIGITS); data = TEST_MODE_OFF; disp7segCommand(node, SEGR_DSP_TEST, &data, NULL, 1); disp7segCommand(node, SEGR_DSP_TEST, &data, NULL, 1); disp7segCommand(node, SEGR_DSP_TEST, &data, NULL, 1); data = NORM_OP_RESET; disp7segCommand(node, SEGR_SHUTDOWN, &data, NULL, 1); data = SEG7_DIGITS; disp7segCommand(node, SEGR_SCAN_LIM, &data, NULL, 1); dcb->freg = NO_DIG_DECODE; disp7segCommand(node, SEGR_DEC_MODE, &dcb->freg, NULL, 1); data = 0x0F; disp7segCommand(node, SEGR_INTENSITY, &data, NULL, 1); data = 0; disp7segCommand(node, SEGR_DIG0, &data, NULL, 1); disp7segCommand(node, SEGR_DIG1, &data, NULL, 1); disp7segCommand(node, SEGR_DIG2, &data, NULL, 1); return 0; } /*! * \brief 7seg SPI node implementation structure. */ NUTSPINODE nodeSpi7SEG = { NULL, /*!< \brief Pointer to the bus controller driver, node_bus. */ NULL, /*!< \brief Pointer to device driver specific settings, node_stat. */ SPI_RATE_DISP_7SEG, /*!< \brief Initial clock rate, node_rate. */ SPI_MODE_DISP_7SEG, /*!< \brief Initial mode, node_mode. */ 8, /*!< \brief Initial data bits, node_bits. */ 0 /*!< \brief Chip select, node_cs. */ }; /*! * \brief 7seg device implementation structure. */ NUTDEVICE devSpi7SEG = { NULL, /*!< \brief Pointer to next device, dev_next. */ {'7', 'S', 'E', 'G', 0, 0, 0}, /*!< \brief Unique device name, dev_name. */ IFTYP_CHAR, /*!< \brief Type of device, dev_type. */ 0, /*!< \brief Base address, dev_base (not used). */ 0, /*!< \brief First interrupt number, dev_irq (not used). */ &nodeSpi7SEG, /*!< \brief Interface control block, dev_icb. */ 0, /*!< \brief Driver control block, dev_dcb. */ Spi7segInit, /*!< \brief Driver initialization routine, dev_init. */ Spi7SegIOCtl, /*!< \brief Driver specific control function, dev_ioctl. */ 0, /*!< \brief Read from device, dev_read. */ Spi7segWrite, /*!< \brief Write to device, dev_write. */ #ifdef __HARVARD_ARCH__ 0, /*!< \brief Write data from program space to device, dev_write_P. */ #endif Spi7SegOpen, /*!< \brief Mount volume, dev_open. */ Spi7SegClose, /*!< \brief Unmount volume, dev_close. */ NULL, /*!< \brief Request file size, dev_size. */ NULL, /*!< \brief Select function, optional, not yet implemented */ };