From 51504a032e0f4cf5a238fe77f12c11a4b4191059 Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Wed, 25 May 2016 08:47:21 -0600 Subject: [PATCH 1/4] Oops... forgot to add PCF8574 LCD Backpack files before doing the commit. --- drivers/lcd/pcf8574_lcd_backpack.c | 1403 +++++++++++++++++++ drivers/lcd/pcf8574_lcd_backpack_readme.txt | 245 ++++ include/nuttx/lcd/pcf8574_lcd_backpack.h | 176 +++ 3 files changed, 1824 insertions(+) create mode 100644 drivers/lcd/pcf8574_lcd_backpack.c create mode 100644 drivers/lcd/pcf8574_lcd_backpack_readme.txt create mode 100644 include/nuttx/lcd/pcf8574_lcd_backpack.h diff --git a/drivers/lcd/pcf8574_lcd_backpack.c b/drivers/lcd/pcf8574_lcd_backpack.c new file mode 100644 index 0000000000..22fd29bb96 --- /dev/null +++ b/drivers/lcd/pcf8574_lcd_backpack.c @@ -0,0 +1,1403 @@ +/**************************************************************************** + * drivers/lcd/pcf8574_lcd_backpack.c + * + * Copyright (C) 2016 Gregory Nutt. All rights reserved. + * Author: dev@ziggurat29.com + * + * 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 NuttX 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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef CONFIG_LIB_SLCDCODEC +# error please also select Library Routines, Segment LCD CODEC +#endif + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* The PCF8574 is a 100 KHz device */ + +#define I2C_FREQ 100000 + +/* timing characteristics of the LCD interface */ + +#define DELAY_US_NYBBLE0 200 +#define DELAY_US_NYBBLE1 100 +#define DELAY_US_WRITE 35 +#define DELAY_US_HOMECLEAR 1500 + +/* HD44780 commands */ + +#define CMD_CLEAR 0x01 +#define CMD_HOME 0x02 +#define CMD_CURSOR_ON_SOLID 0x0e +#define CMD_CURSOR_OFF 0x0c +#define CMD_CURSOR_ON_BLINK 0x0f +#define CMD_SET_CGADDR 0x40 +#define CMD_SET_DDADDR 0x80 + +#ifdef CONFIG_DEBUG_LCD +# define lcddbg dbg +# define lcdvdbg vdbg +#else +# define lcddbg(x...) +# define lcdvdbg(x...) +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct pcf8574_lcd_dev_s +{ + FAR struct i2c_master_s *i2c; /* I2C interface */ + struct pcf8574_lcd_backpack_config_s cfg; /* gpio configuration */ + uint8_t bl_bit; /* current backlight bit */ + sem_t sem_excl; /* mutex */ +}; + +struct lcd_instream_s +{ + struct lib_instream_s stream; + FAR const char *buffer; + ssize_t nbytes; +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Character driver methods */ + +static int pcf8574_lcd_open(FAR struct file *filep); +static int pcf8574_lcd_close(FAR struct file *filep); +static ssize_t pcf8574_lcd_read(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static ssize_t pcf8574_lcd_write(FAR struct file *filep, + FAR const char *buffer, size_t buflen); +static int pcf8574_lcd_ioctl(FAR struct file *filep, int cmd, + unsigned long arg); +#ifndef CONFIG_DISABLE_POLL +static int pcf8574lcd_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations g_pcf8574_lcd_fops = +{ + pcf8574_lcd_open, /* open */ + pcf8574_lcd_close, /* close */ + pcf8574_lcd_read, /* read */ + pcf8574_lcd_write, /* write */ + 0, /* seek */ + pcf8574_lcd_ioctl, /* ioctl */ +#ifndef CONFIG_DISABLE_POLL + pcf8574lcd_poll, /* poll */ +#endif + 0 /* unlink */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pca8574_write + * + * Description: + * primitive I2C write operation for the PCA8574, which is the IO expander + * device used on the board. The board essentially byte-bangs the + * parallel interface in nybble mode much as one might with a conventional + * GPIO based interface. The I2C interface simply sets the state of the + * 8 IO lines to control the 4 data, 3 control, and one for backlight, + * signals. + * + ****************************************************************************/ + +static void pca8574_write(FAR struct pcf8574_lcd_dev_s *priv, uint8_t data) +{ + struct i2c_config_s config; + int ret; + + /* Set up the I2C configuration */ + + config.frequency = I2C_FREQ; + config.address = priv->cfg.addr; + config.addrlen = 7; + + /* Write the value */ + + ret = i2c_write(priv->i2c, &config, &data, 1); + if (ret < 0) + { + lcdvdbg("pca8574_write() failed: %d\n", ret); + return; + } + + return; +} + +/**************************************************************************** + * Name: pca8574_read + * + * Description: + * primitive I2C read operation for the PCA8574, which is the IO expander + * device used on the board. The PCF8574 is 'interesting' in that it doesn't + * really have a data direction register, but instead the outputs are current- + * limited when high, so by setting an IO line high, you are also making it + * an input. Consequently, before using this method, you'll need to perform a + * pca8574_write() setting the bits you are interested in reading to 1's, + * then call this method. + * + ****************************************************************************/ + +static int pca8574_read(FAR struct pcf8574_lcd_dev_s *priv, uint8_t* data) +{ + struct i2c_config_s config; + int ret; + + /* Set up the I2C configuration */ + + config.frequency = I2C_FREQ; + config.address = priv->cfg.addr; + config.addrlen = 7; + + /* Read the value */ + + ret = i2c_read(priv->i2c, &config, data, 1); + if (ret < 0) + { + lcdvdbg("pca8574_read() failed: %d\n", ret); + } + + return ret; +} + +/**************************************************************************** + * Name: lcd_backlight + * + * Description: + * turn on, or off, the LCD backlight + * + ****************************************************************************/ + +static void lcd_backlight(FAR struct pcf8574_lcd_dev_s *priv, bool blOn) +{ + uint8_t data; + + data = ((blOn && priv->cfg.bl_active_high) || + (!blOn && !priv->cfg.bl_active_high)) ? (1 << priv->cfg.bl) : 0; + pca8574_write(priv, data); + priv->bl_bit = data; +} + +/**************************************************************************** + * Name: rc2addr + * + * Description: + * This converts a row/column pair to a screen memory address. + * + ****************************************************************************/ + +static inline uint8_t rc2addr(FAR struct pcf8574_lcd_dev_s *priv, + uint8_t row, uint8_t col) +{ + if (row < 2) + { + /* 1 and 2 line displays are simple; line0 @ 0x00, line1 @ 0x40 */ + + return row * 0x40 + col; + } + else + { + /* 4 line displays are intersting; third line really is a continuation + * of first line, and fourth line is a continuation of second. + */ + + return (row - 2) * 0x40 + (col - priv->cfg.cols); + } +} + +/**************************************************************************** + * Name: addr2rc + * + * Description: + * This converts a screen memory address to a row/column pair. + * + ****************************************************************************/ + +static inline void addr2rc(FAR struct pcf8574_lcd_dev_s *priv, + uint8_t addr, uint8_t* row, uint8_t* col) +{ + + *row = addr / 0x40; + *col = addr % 0x40; + + if (*col >= priv->cfg.cols) + { + /* 4 line displays have third and fourth lines really as continuation + * of first and second. + */ + + *row += 2; + *col -= priv->cfg.cols; + } +} + +/**************************************************************************** + * Name: prepare_nybble + * + * Description: + * This is a bit tedious, but scramble the bits of the nybble into position + * as per this board's particular wiring. Most boards are either on the + * top four bits, or bottom four, so a shift would do typically in those + * cases, but this gives us ultimate flexibility. + * + ****************************************************************************/ + +uint8_t prepare_nybble(FAR struct pcf8574_lcd_dev_s *priv, uint8_t nybble) +{ + uint8_t lcddata = 0; + + if (nybble & 0x08) + { + lcddata |= (1 << priv->cfg.d7); + } + + if (nybble & 0x04) + { + lcddata |= (1 << priv->cfg.d6); + } + + if (nybble & 0x02) + { + lcddata |= (1 << priv->cfg.d5); + } + + if (nybble & 0x01) + { + lcddata |= (1 << priv->cfg.d4); + } + + return lcddata; +} + +/**************************************************************************** + * Name: unprepare_nybble + * + * Description: + * This is the opposite of prepare_nybble(), and is used to unscramble bits + * when reading data from the display, as per board wiring. + * + ****************************************************************************/ + +uint8_t unprepare_nybble(FAR struct pcf8574_lcd_dev_s *priv, uint8_t lcddata) +{ + uint8_t data = 0; + + if (lcddata & (1 << priv->cfg.d7)) + { + data |= 0x08; + } + + if (lcddata & (1 << priv->cfg.d6)) + { + data |= 0x04; + } + + if (lcddata & (1 << priv->cfg.d5)) + { + data |= 0x02; + } + + if (lcddata & (1 << priv->cfg.d4)) + { + data |= 0x01; + } + + return data; +} + +/**************************************************************************** + * Name: latch_nybble + * + * Description: + * Latch a nybble on the LCD bus. This is done for each of two halves of a + * write operation in 4-bit mode. The 'rs' param is false for command + * transfers, and true for data transfers. + * + ****************************************************************************/ + +static void latch_nybble(FAR struct pcf8574_lcd_dev_s *priv, uint8_t nybble, + bool rs) +{ + uint8_t lcddata; + uint8_t en_bit; + uint8_t rs_bit; + + en_bit = 1 << priv->cfg.en; + rs_bit = rs ? (1 << priv->cfg.rs) : 0; + + /* Put the nybble, preserving backlight, reset R/~W and set EN and maybe RS */ + + lcddata = prepare_nybble(priv, nybble) | priv->bl_bit | en_bit | rs_bit; + pca8574_write(priv, lcddata); + up_udelay(DELAY_US_NYBBLE0); /* setup */ + + /* Latch on EN falling edge */ + + lcddata &= ~en_bit; + pca8574_write(priv, lcddata); + up_udelay(DELAY_US_NYBBLE1); /* hold */ +} + +/**************************************************************************** + * Name: load_nybble + * + * Description: + * Load a nybble from the LCD bus. This is done for each of two halves of a + * read operation in 4-bit mode. The 'rs' param is false for command + * transfers (the only one is to read status and the address register), and + * true for data transfers. + * + ****************************************************************************/ + +static uint8_t load_nybble(FAR struct pcf8574_lcd_dev_s *priv, bool rs) +{ + uint8_t lcddata; + uint8_t en_bit; + uint8_t rs_bit; + uint8_t rw_bit; + uint8_t data; + + en_bit = 1 << priv->cfg.en; + rs_bit = rs ? (1 << priv->cfg.rs) : 0; + rw_bit = 1 << priv->cfg.rw; + + /* Put highs on the data lines, preserve, set R/~W and set EN and maybe RS */ + + lcddata = prepare_nybble(priv, 0x0f) | priv->bl_bit | en_bit | rw_bit | rs_bit; + pca8574_write(priv, lcddata); + up_udelay(DELAY_US_NYBBLE0); /* setup */ + + /* Now read the data */ + + pca8574_read(priv, &data); + data = unprepare_nybble(priv, data); + + /* Transaction completed on EN falling edge */ + + lcddata &= ~en_bit; + pca8574_write(priv, lcddata); + up_udelay(DELAY_US_NYBBLE1); /* hold */ + + return data; +} + +/**************************************************************************** + * Name: lcd_putcmd + * + * Description: + * Write a command to the LCD. Most of the time this is done in nybble + * mode in two phases, but in special cases (like initialization) we do not + * do two phases. + * + ****************************************************************************/ + +static void lcd_putcmd(FAR struct pcf8574_lcd_dev_s *priv, uint8_t data) +{ + latch_nybble(priv, data >> 4, false); + latch_nybble(priv, data, false); + up_udelay(DELAY_US_WRITE); +} + +/**************************************************************************** + * Name: lcd_putdata + * + * Description: + * Write a byte to the LCD. This is used both for screen data and for + * character generator data, depending on a previous command that selected + * which ever is the destination. + * + ****************************************************************************/ + +static inline void lcd_putdata(FAR struct pcf8574_lcd_dev_s *priv, + uint8_t data) +{ + latch_nybble(priv, data >> 4, true); + latch_nybble(priv, data, true); + up_udelay(DELAY_US_WRITE); +} + +/**************************************************************************** + * Name: lcd_getdata + * + * Description: + * Read a data byte from the LCD. + * + ****************************************************************************/ + +static inline uint8_t lcd_getdata(FAR struct pcf8574_lcd_dev_s *priv) +{ + uint8_t data; + data = (load_nybble(priv, true) << 4) | load_nybble(priv, true); + return data; +} + +/**************************************************************************** + * Name: lcd_getcmd + * + * Description: + * Read a command byte from the LCD. There really is only one such read: + * get 'busy' status, and current address value. + * + ****************************************************************************/ + +static inline uint8_t lcd_getcmd(FAR struct pcf8574_lcd_dev_s *priv) +{ + uint8_t data; + data = (load_nybble(priv, false) << 4) | load_nybble(priv, false); + return data; +} + +/**************************************************************************** + * Name: lcd_read_busy_addr + * + * Description: + * Read the busy flag, and, optionally, the current value of the address + * register (data or character generator dependent on a previous command). + * + ****************************************************************************/ + +static bool lcd_read_busy_addr(FAR struct pcf8574_lcd_dev_s *priv, uint8_t* addr) +{ + uint8_t data = lcd_getcmd(priv); + + if (NULL != addr) + { + *addr = data & 0x7f; + } + + return (data & 0x80) ? true : false; +} + +/**************************************************************************** + * Name: lcd_init + * + * Description: + * perform the initialization sequence to get the LCD into a known state. + * + ****************************************************************************/ + +static void lcd_init(FAR struct pcf8574_lcd_dev_s *priv) +{ + /* Wait for more than 15 ms after Vcc for the LCD to stabilize */ + + usleep(20000); + + /* Perform the init sequence. This sequence of commands is constructed so + * that it will get the device into nybble mode irrespective of what state + * the device is currently in (could be 8 bit, 4 bit nyb 0, 4 bit nyb 1). + * By sending the 'set 8-bit mode' three times, we will definitely end up + * in 8 bit mode, and then we can reliably transition to 4 bit mode for + * the remainder of operations. + */ + + /* Send Command 0x30, set 8-bit mode, and wait > 4.1 ms*/ + + latch_nybble(priv, 0x30>>4, false); + usleep(5000); + + /* Send Command 0x30, set 8-bit mode, and wait > 100 us */ + + latch_nybble(priv, 0x30>>4, false); + usleep(200); + + /* Send Command 0x30, set 8-bit mode */ + + latch_nybble(priv, 0x30>>4, false); + + /* now Function set: Set interface to be 4 bits long (only 1 cycle write for the first time). */ + + latch_nybble(priv, 0x20>>4, false); + + /* Function set: DL=0;Interface is 4 bits, N=1 (2 Lines), F=0 (5x8 dots font) */ + + lcd_putcmd(priv, 0x28); + + /* Display Off: D=0 (Display off), C=0 (Cursor Off), B=0 (Blinking Off) */ + + lcd_putcmd(priv, 0x08); + + /* Display Clear */ + + lcd_putcmd(priv, CMD_CLEAR); + up_udelay(DELAY_US_HOMECLEAR); /* clear needs extra time */ + + /* Entry Mode Set: I/D=1 (Increment), S=0 (No shift) */ + + lcd_putcmd(priv, 0x06); + + /* Display On, Cursor Off */ + + lcd_putcmd(priv, 0x0C); +} + +/**************************************************************************** + * Name: lcd_create_char + * + * Description: + * This creates a custom character pattern. There can be 8 5x8 patterns. + * The bitmap proceeds top to bottom, msb-lsb, and is right justified (i.e. + * only bits 4-0 are used). By convention, you are meant to always leave the + * last line (byte) zero so that the cursor can use this line, but this is + * not strictly required. + * + * Parameters: + * priv - device instance + * idxchar - which character is being imaged; 0 - 7 + * chardata - the character image bitmap; must be 8 bytes always + * + ****************************************************************************/ + +static void lcd_create_char(FAR struct pcf8574_lcd_dev_s *priv, + uint8_t idxchar, const uint8_t *chardata) +{ + int nIdx; + uint8_t addr; + + (void)lcd_read_busy_addr(priv, &addr); + lcd_putcmd(priv, CMD_SET_CGADDR | (idxchar << 3)); /* set CGRAM address */ + + for (nIdx = 0; nIdx < 8; ++nIdx) + { + lcd_putdata(priv, chardata[nIdx]); + } + + lcd_putcmd(priv, CMD_SET_DDADDR | addr); /* restore DDRAM address */ +} + +/**************************************************************************** + * Name: lcd_set_curpos + * + * Description: + * This sets the cursor position based on row, column addressing. + * + * Parameters: + * priv - device instance + * row - row position + * col - column position + * + ****************************************************************************/ + +static void lcd_set_curpos(FAR struct pcf8574_lcd_dev_s *priv, + uint8_t row, uint8_t col) +{ + uint8_t addr; + addr = rc2addr(priv, row, col); + lcd_putcmd(priv, CMD_SET_DDADDR | addr); /* set DDRAM address */ +} + +/**************************************************************************** + * Name: lcd_get_curpos + * + * Description: + * This gets the cursor position based on row, column addressing. + * + * Parameters: + * priv - device instance + * row - row position + * col - column position + * + ****************************************************************************/ + +static void lcd_get_curpos(FAR struct pcf8574_lcd_dev_s *priv, + uint8_t *row, uint8_t *col) +{ + uint8_t addr; + + (void)lcd_read_busy_addr(priv, &addr); + addr2rc(priv, addr, row, col); +} + +/**************************************************************************** + * Name: lcd_scroll_up + * + * Description: + * Scroll the display up, and clear the new (last) line. + * + ****************************************************************************/ + +static void lcd_scroll_up(FAR struct pcf8574_lcd_dev_s *priv) +{ + uint8_t *data; + int nRow; + int nCol; + + data = (uint8_t *)malloc(priv->cfg.cols); + if (NULL == data) + { + lcdvdbg("Failed to allocate buffer in lcd_scroll_up()\n"); + return; + } + + for (nRow = 1; nRow < priv->cfg.rows; ++nRow) + { + lcd_set_curpos(priv, nRow, 0); + for (nCol = 0; nCol < priv->cfg.cols; ++nCol) + { + data[nCol] = lcd_getdata(priv); + } + + lcd_set_curpos(priv, nRow - 1, 0); + for (nCol = 0; nCol < priv->cfg.cols; ++nCol) + { + lcd_putdata(priv, data[nCol]); + } + } + + lcd_set_curpos(priv, priv->cfg.rows - 1, 0); + for (nCol = 0; nCol < priv->cfg.cols; ++nCol) + { + lcd_putdata(priv, ' '); + } + + lcd_set_curpos(priv, priv->cfg.rows - 1, 0); + + free(data); + return; +} + +/**************************************************************************** + * Name: lcd_codec_action + * + * Description: + * Perform an 'action' as per the Segment LCD codec. + * + * Parameters: + * priv - device instance + * code - SLCD code action code + * count - count param for those actions that take it + * + ****************************************************************************/ + +static void lcd_codec_action(FAR struct pcf8574_lcd_dev_s *priv, + enum slcdcode_e code, uint8_t count) +{ + switch (code) + { + /* Erasure */ + + case SLCDCODE_BACKDEL: /* Backspace (backward delete) N characters */ + { + if (count <= 0) /* silly case */ + break; + + else + { + uint8_t row; + uint8_t col; + + lcd_get_curpos(priv, &row, &col); + if (count > col) /* saturate to preceding columns available */ + { + count = col; + } + + lcd_set_curpos(priv, row, col-count); + } + + /* ... and conscientiously fall through to next case ... */ + } + + case SLCDCODE_FWDDEL: /* Delete (forward delete) N characters, moving text */ + { + if (count <= 0) /* silly case */ + { + break; + } + + else + { + uint8_t row; + uint8_t col; + uint8_t start; + uint8_t end; + uint8_t nIdx; + uint8_t data; + + lcd_get_curpos(priv, &row, &col); + start = col + count; + + if (start >= priv->cfg.cols) /* silly case of nothing left */ + { + break; + } + + end = start + count; + if (end > priv->cfg.cols) /* saturate */ + { + end = priv->cfg.cols; + } + + for(nIdx = col; nIdx < end; ++start, ++nIdx) /* much like memmove */ + { + lcd_set_curpos(priv, row, start); + data = lcd_getdata(priv); + lcd_set_curpos(priv, row, nIdx); + lcd_putdata(priv, data); + } + + for(;nIdx < priv->cfg.cols; ++nIdx) /* much like memset */ + { + lcd_putdata(priv, ' '); + } + + lcd_set_curpos(priv, row, col); + } + } + break; + + case SLCDCODE_ERASE: /* Erase N characters from the cursor position */ + if (count > 0) + { + uint8_t row; + uint8_t col; + uint8_t end; + uint8_t nIdx; + + lcd_get_curpos(priv, &row, &col); + end = col + count; + if (end > priv->cfg.cols) + { + end = priv->cfg.cols; + } + + for (nIdx = col; nIdx < end; ++nIdx) + { + lcd_putdata(priv, ' '); + } + + lcd_set_curpos(priv, row, col); + } + break; + + case SLCDCODE_CLEAR: /* Home the cursor and erase the entire display */ + { + lcd_putcmd(priv, CMD_CLEAR); + up_udelay(DELAY_US_HOMECLEAR); /* clear needs extra time */ + } + break; + + case SLCDCODE_ERASEEOL: /* Erase from the cursor position to the end of line */ + { + uint8_t row; + uint8_t col; + uint8_t nIdx; + + lcd_get_curpos(priv, &row, &col); + + for (nIdx = col; nIdx < priv->cfg.cols; ++nIdx) + { + lcd_putdata(priv, ' '); + } + + lcd_set_curpos(priv, row, col); + } + break; + + /* Cursor movement */ + + case SLCDCODE_LEFT: /* Cursor left by N characters */ + { + uint8_t row; + uint8_t col; + + lcd_get_curpos(priv, &row, &col); + if (count > col) + { + col = 0; + } + else + { + col -= count; + } + + lcd_set_curpos(priv, row, col); + } + break; + + case SLCDCODE_RIGHT: /* Cursor right by N characters */ + { + uint8_t row; + uint8_t col; + + lcd_get_curpos(priv, &row, &col); + col += count; + if (col >= priv->cfg.cols) + { + col = priv->cfg.cols-1; + } + + lcd_set_curpos(priv, row, col); + } + break; + + case SLCDCODE_UP: /* Cursor up by N lines */ + { + uint8_t row; + uint8_t col; + + lcd_get_curpos(priv, &row, &col); + if (count > row) + { + row = 0; + } + else + { + row -= count; + } + + lcd_set_curpos(priv, row, col); + } + break; + + case SLCDCODE_DOWN: /* Cursor down by N lines */ + { + uint8_t row; + uint8_t col; + + lcd_get_curpos(priv, &row, &col); + row += count; + if (row >= priv->cfg.rows) + { + row = priv->cfg.rows - 1; + } + + lcd_set_curpos(priv, row, col); + } + break; + + case SLCDCODE_HOME: /* Cursor home */ + { + uint8_t row; + uint8_t col; + + lcd_get_curpos(priv, &row, &col); + lcd_set_curpos(priv, row, 0); + } + break; + + case SLCDCODE_END: /* Cursor end */ + { + uint8_t row; + uint8_t col; + + lcd_get_curpos(priv, &row, &col); + lcd_set_curpos(priv, row, priv->cfg.cols - 1); + } + break; + + case SLCDCODE_PAGEUP: /* Cursor up by N pages */ + case SLCDCODE_PAGEDOWN: /* Cursor down by N pages */ + break; /* Not supportable on this SLCD */ + + /* Blinking */ + + case SLCDCODE_BLINKSTART: /* Start blinking with current cursor position */ + lcd_putcmd(priv, CMD_CURSOR_ON_BLINK); + break; + + case SLCDCODE_BLINKEND: /* End blinking after the current cursor position */ + case SLCDCODE_BLINKOFF: /* Turn blinking off */ + lcd_putcmd(priv, CMD_CURSOR_OFF); + break; /* Not implemented */ + + /* These are actually unreportable errors */ + + default: + case SLCDCODE_NORMAL: /* Not a special keycode */ + break; + } +} + +/**************************************************************************** + * Name: lcd_getstream + * + * Description: + * Get one character from the LCD codec stream. + * + ****************************************************************************/ + +static int lcd_getstream(FAR struct lib_instream_s *instream) +{ + FAR struct lcd_instream_s *lcdstream = (FAR struct lcd_instream_s *)instream; + + if (lcdstream->nbytes > 0) + { + lcdstream->nbytes--; + lcdstream->stream.nget++; + return (int)*lcdstream->buffer++; + } + + return EOF; +} + +/**************************************************************************** + * Name: pcf8574_lcd_open + * + * Description: + * requisite device 'open' method; we don't do anything special + * + ****************************************************************************/ + +static int pcf8574_lcd_open(FAR struct file *filep) +{ + return OK; +} + +/**************************************************************************** + * Name: pcf8574_lcd_close + * + * Description: + * requisite device 'close' method; we don't do anything special + * + ****************************************************************************/ + +static int pcf8574_lcd_close(FAR struct file *filep) +{ + return OK; +} + +/**************************************************************************** + * Name: pcf8574_lcd_read + * + * Description: + * This simply reads as much of the display memory as possible. This is + * probably not very interesting. + * + ****************************************************************************/ + +static ssize_t pcf8574_lcd_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct pcf8574_lcd_dev_s *priv = (FAR struct pcf8574_lcd_dev_s *)inode->i_private; + int nIdx; + uint8_t addr; + uint8_t row; + uint8_t col; + + sem_wait(&priv->sem_excl); + + /* Get current cursor position so we can restore it */ + + (void)lcd_read_busy_addr(priv, &addr); + addr2rc(priv, addr, &row, &col); + + /* Just read the entire display into the given buffer, as much as possible */ + + nIdx = 0; + row = 0; + col = 0; + + while (nIdx < buflen && row < priv->cfg.rows) + { + if (0 == col) + { + lcd_set_curpos(priv, row, 0); + } + + buffer[nIdx] = lcd_getdata(priv); + + ++nIdx; + ++col; + if (priv->cfg.cols == col) + { + ++row; + col = 0; + } + } + + lcd_putcmd(priv, CMD_SET_DDADDR | addr); /* Restore DDRAM address */ + + sem_post(&priv->sem_excl); + return nIdx; +} + +/**************************************************************************** + * Name: pcf8574_lcd_write + * + * Description: + * Output a sequence of characters to the device. + * + ****************************************************************************/ + +static ssize_t pcf8574_lcd_write(FAR struct file *filep, + FAR const char *buffer, + size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct pcf8574_lcd_dev_s *priv = (FAR struct pcf8574_lcd_dev_s *)inode->i_private; + struct lcd_instream_s instream; + uint8_t row; + uint8_t col; + struct slcdstate_s state; + enum slcdret_e result; + uint8_t ch; + uint8_t count; + + sem_wait(&priv->sem_excl); + + /* Initialize the stream for use with the SLCD CODEC */ + + instream.stream.get = lcd_getstream; + instream.stream.nget = 0; + instream.buffer = buffer; + instream.nbytes = buflen; + + /* Get the current cursor position now; we'll keep track of it as we go */ + + lcd_get_curpos(priv, &row, &col); + + /* Now decode and process every byte in the input buffer */ + + memset(&state, 0, sizeof(struct slcdstate_s)); + while ((result = slcd_decode(&instream.stream, &state, &ch, &count)) != SLCDRET_EOF) + { + if (result == SLCDRET_CHAR) /* A normal character was returned */ + { + /* Check for ASCII control characters */ + + if (ch == ASCII_TAB) + { + lcd_putcmd(priv, CMD_CURSOR_ON_BLINK); + } + else if (ch == ASCII_VT) + { + /* Turn the backlight on */ + + lcd_backlight(priv, true); + } + else if (ch == ASCII_FF) + { + /* Turn the backlight off */ + + lcd_backlight(priv, false); + } + else if (ch == ASCII_CR) + { + /* Perform a Home */ + + lcd_putcmd(priv, CMD_HOME); + up_udelay(DELAY_US_HOMECLEAR); /* home needs extra time */ + row = 0; + col = 0; + } + else if (ch == ASCII_SO) + { + lcd_putcmd(priv, CMD_CURSOR_OFF); + } + else if (ch == ASCII_SI) + { + /* Perform the re-initialize */ + + lcd_init(priv); + row = 0; + col = 0; + } + else if (ch == ASCII_LF) + { + /* unixian line term; go to start of next line */ + + row += 1; + if (row >= priv->cfg.rows) + { + lcd_scroll_up(priv); + row = priv->cfg.rows - 1; + } + + col = 0; + lcd_set_curpos(priv, row, col); + } + else if (ch == ASCII_BS) + { + /* Perform the backward deletion */ + + lcd_codec_action(priv, SLCDCODE_BACKDEL, 1); + + lcd_get_curpos(priv, &row, &col); + } + else if (ch == ASCII_DEL) + { + /* Perform the forward deletion */ + + lcd_codec_action(priv, SLCDCODE_FWDDEL, 1); + + lcd_get_curpos(priv, &row, &col); + } + else + { + /* All others are fair game. See if we need to wrap line. */ + + if (col >= priv->cfg.cols) + { + row += 1; + if (row >= priv->cfg.rows) + { + lcd_scroll_up(priv); + row = priv->cfg.rows - 1; + } + + col = 0; + lcd_set_curpos(priv, row, col); + } + + lcd_putdata(priv, ch); + ++col; + } + } + else /* (result == SLCDRET_SPEC) */ /* A special SLCD action was returned */ + { + lcd_codec_action(priv, (enum slcdcode_e)ch, count); + + /* we can't know what happened, so it's easier just to re-inquire + * as to where we are. + */ + + lcd_get_curpos(priv, &row, &col); + } + } + + sem_post(&priv->sem_excl); + return buflen; +} + +/**************************************************************************** + * Name: pcf8574_lcd_ioctl + * + * Description: + * Perform device operations that are outside the standard I/O model. + * + ****************************************************************************/ + +static int pcf8574_lcd_ioctl(FAR struct file *filep, int cmd, + unsigned long arg) +{ + switch (cmd) + { + case SLCDIOC_GETATTRIBUTES: /* SLCDIOC_GETATTRIBUTES: Get the attributes of the SLCD */ + { + FAR struct inode *inode = filep->f_inode; + FAR struct pcf8574_lcd_dev_s *priv = (FAR struct pcf8574_lcd_dev_s *)inode->i_private; + FAR struct slcd_attributes_s *attr = (FAR struct slcd_attributes_s *)((uintptr_t)arg); + + lcdvdbg("SLCDIOC_GETATTRIBUTES:\n"); + + if (!attr) + { + return -EINVAL; + } + + attr->nrows = priv->cfg.rows; + attr->ncolumns = priv->cfg.cols; + attr->nbars = 0; + attr->maxcontrast = 0; + attr->maxbrightness = 1; /* 'brightness' for us is the backlight */ + } + break; + + case SLCDIOC_CURPOS: /* SLCDIOC_CURPOS: Get the SLCD cursor position */ + { + FAR struct inode *inode = filep->f_inode; + FAR struct pcf8574_lcd_dev_s *priv = (FAR struct pcf8574_lcd_dev_s *)inode->i_private; + FAR struct slcd_curpos_s *attr = (FAR struct slcd_curpos_s *)((uintptr_t)arg); + uint8_t row; + uint8_t col; + + sem_wait(&priv->sem_excl); + + lcd_get_curpos(priv, &row, &col); + attr->row = row; + attr->column = col; + + sem_post(&priv->sem_excl); + } + break; + + case SLCDIOC_GETBRIGHTNESS: /* Get the current brightness setting */ + { + FAR struct inode *inode = filep->f_inode; + FAR struct pcf8574_lcd_dev_s *priv = (FAR struct pcf8574_lcd_dev_s *)inode->i_private; + bool bOn; + + bOn = (priv->bl_bit && priv->cfg.bl_active_high) || (!priv->bl_bit && !priv->cfg.bl_active_high); + *(int*)((uintptr_t)arg) = bOn ? 1 : 0; + } + break; + + case SLCDIOC_SETBRIGHTNESS: /* Set the brightness to a new value */ + { + FAR struct inode *inode = filep->f_inode; + FAR struct pcf8574_lcd_dev_s *priv = (FAR struct pcf8574_lcd_dev_s *)inode->i_private; + + sem_wait(&priv->sem_excl); + lcd_backlight(priv, arg ? true : false); + sem_post(&priv->sem_excl); + } + break; + + case SLCDIOC_CREATECHAR: /* Create a custom character pattern */ + { + FAR struct inode *inode = filep->f_inode; + FAR struct pcf8574_lcd_dev_s *priv = (FAR struct pcf8574_lcd_dev_s *)inode->i_private; + FAR struct slcd_createchar_s *attr = (FAR struct slcd_createchar_s *)((uintptr_t)arg); + + sem_wait(&priv->sem_excl); + lcd_create_char(priv, attr->idx, attr->bmp); + sem_post(&priv->sem_excl); + } + break; + + case SLCDIOC_SETBAR: /* SLCDIOC_SETBAR: Set bars on a bar display */ + case SLCDIOC_GETCONTRAST: /* SLCDIOC_GETCONTRAST: Get the current contrast setting */ + case SLCDIOC_SETCONTRAST: /* SLCDIOC_SETCONTRAST: Set the contrast to a new value */ + default: + return -ENOTTY; + } + + return OK; +} + +/**************************************************************************** + * Name: pcf8574lcd_poll + ****************************************************************************/ + +#ifndef CONFIG_DISABLE_POLL +static int pcf8574lcd_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup) +{ + if (setup) + { + /* Data is always available to be read */ + + fds->revents |= (fds->events & (POLLIN|POLLOUT)); + if (fds->revents != 0) + { + sem_post(fds->sem); + } + } + + return OK; +} +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pcf8574_lcd_backpack_register + * + * Description: + * Register a character driver that is an I2C LCD 'backpack' for the + * ever-popular HD44780 based 16x2 LCD via pcf8574 I2C IO expander. + * + ****************************************************************************/ + +int pcf8574_lcd_backpack_register(FAR const char *devpath, + FAR struct i2c_master_s *i2c, + FAR struct pcf8574_lcd_backpack_config_s *cfg) +{ + FAR struct pcf8574_lcd_dev_s *priv; + int ret; + + /* Sanity check on geometry */ + + if (cfg->rows < 1 || cfg->rows > 4) + { + lcdvdbg("Display rows must be 1-4\n"); + return -EINVAL; + } + + if ((cfg->cols < 1 || cfg->cols > 64) || (cfg->rows == 4 && cfg->cols > 32)) + { + lcdvdbg("Display cols must be 1-64, and may not be part of a 4x40 configuration\n"); + return -EINVAL; + } + + /* Initialize the device structure */ + + priv = (FAR struct pcf8574_lcd_dev_s *)kmm_malloc(sizeof(struct pcf8574_lcd_dev_s)); + if (!priv) + { + lcdvdbg("Failed to allocate instance\n"); + return -ENOMEM; + } + + priv->i2c = i2c; + priv->cfg = *cfg; + priv->bl_bit = priv->cfg.bl_active_high ? 0 : (1 << priv->cfg.bl); + sem_init(&priv->sem_excl, 0, 1); + + /* Initialize */ + + lcd_init(priv); + + /* Register the character driver */ + + ret = register_driver(devpath, &g_pcf8574_lcd_fops, 0666, priv); + if (ret < 0) + { + lcdvdbg("Failed to register driver: %d\n", ret); + kmm_free(priv); + } + + lcdvdbg("pcf8574_lcd_backpack driver loaded successfully!\n"); + return ret; +} diff --git a/drivers/lcd/pcf8574_lcd_backpack_readme.txt b/drivers/lcd/pcf8574_lcd_backpack_readme.txt new file mode 100644 index 0000000000..c7d2775d11 --- /dev/null +++ b/drivers/lcd/pcf8574_lcd_backpack_readme.txt @@ -0,0 +1,245 @@ +pcf8574 lcd backpack - readme.txt +20160524a, ziggurat29 + +Abstract +======== + +This describes the use of the pcf8574_lcd_backpack.h, .c driver module for NuttX. + +Contents +======== + + o Summary for Those Who Don't Like to Read + o Introduction + o Usage + - Specifying the I2C Address + - Specifying the LCD Display Format + - Specifying Unknown/New Backpacks + o Special Features + - Codec + - Ioctl + o Troubleshooting + +Summary for Those Who Don't Like to Read +======================================== + +To use, in your board_app_initialize(), + +1) instantiate an I2C bus: + + FAR struct i2c_master_s* i2c = stm32l4_i2cbus_initialize(1); + +2) set the configuration for the particular make of board, and LCD format: + + struct pcf8574_lcd_backpack_config_s cfg = LCD_I2C_BACKPACK_CFG_MJKDZ; + cfg.rows = 2; + cfg.cols = 16; + +3) instantiate the device on the I2C bus previously created: + + ret = pcf8574_lcd_backpack_register("/dev/slcd0", i2c, &cfg); + +Introduction +============ + +The character LCD modules based on the HD44780 (and compatible ST7706U, KS0066U, +SED1278, etc.) drivers have been around for many decades and are quite popular. +One challenge is that they require a large number of GPIO (11 in 8-bit mode, 7 +in 4-bit mode, and an additional line if you control the backlight). + +To address this, several folks have created daughter boards for the LCD module +which present a two-wire I2C interface. Generally, folks call these interface +boards an 'lcd backpack'. A large class of them (and in particular, the very +inexpensive ones found on ebay, q.v. google "ebay i2c lcd backpack"; they're +usually about $USD 1), use the same design: a PCF8574 I2C IO expander. +Variations occur in mapping GPIO line to LCD pins, but otherwise the +expectation is that you control the LCD at a low-level tweaking the lines +("byte-banging"?) + +My original motivation for producing this was to simply serve as a test device +for some I2C driver work I was doing, but it occurred to me that it may be +useful to others, given the popularity of the 'lcd backpack', so I cleaned up +the code and made it general to support all the variations on the market, and +also to adopt the NuttX notion of a 'segment lcd codec', which is used to +transport escape sequences (for doing things like clearing the display, turning +on/off the cursor, etc), and also the standard ioctls. + +I believe it should support all "lcd backpack"s on the market (because you can +specify the particular wiring), and all HD44780-based LCD modules in 1-line, +2-line, and 4-line configurations (except 4x40 -- this is not supported by +the hardware). + +This module should be cpu-architecture-neutral, and work with any standard I2C +bus object. At the time of this writing it has been tested only with the +STM32L4 chip and with the 'MJKDZ' backpack board with a 16x2 lcd module. + +Usage +===== + +The driver is contained in the files pcf8574_lcd_backpack.h and +pcf8574_lcd_backpack.c; you can include these in your build in whatever manner +you choose (e.g. copy them into your board's src directory, and reference them +in the Makefile). + +As with other I2C devices, you first instantiate the I2C bus, and then +instantiate the driver on that bus. When instantiating the driver, you also +provide a configuration 'descriptor' that specified board wiring and LCD +format parameters. You can explicitly specify any wiring configuration, and +some known popular boards are already #defined for your convenience. + +E.g.: + + #include + #include "pcf8574_lcd_backpack.h" + + #define MJKDZ_I2C_PORTNO 1 + #define MJKDZ_DEVICE_NAME "/dev/lcd0" + + FAR struct i2c_master_s* g_i2cMJKDZ = NULL; + + .... + + g_i2cMJKDZ = stm32l4_i2cbus_initialize(MJKDZ_I2C_PORTNO); + + .... + + struct pcf8574_lcd_backpack_config_s cfg = LCD_I2C_BACKPACK_CFG_MJKDZ; + cfg.rows = 2; + cfg.cols = 16; + + ret = pcf8574_lcd_backpack_register(MJKDZ_DEVICE_NAME, g_i2cMJKDZ, &cfg); + +If all the above executes successfully, you should wind up with a character +device node "/dev/lcd0". Applications can open that node and write() to it, +and the shell can emit data to it (e.g. 'echo Hi, there! > /dev/lcd0'). + +That is the basic configuration. Some additional configuration points are +worth noting. + +Specifying the I2C Address +-------------------------- + +The 'struct pcf8574_lcd_backpack_config_s' shown above is initialized using +the convenience macro LCD_I2C_BACKPACK_CFG_MJKDZ. Those convenience macros +use the default I2C address for the board, however many of the boards allow +altering the address (by jumpers, or removing pullups). You need to specify +the correct address for your board's physical configuration. You can do that +via + + cfg.addr = 0x23; + +Specifying the LCD Display Format +--------------------------------- + +The LCD modules cannot 'self-describe' their physical format, so it must be +explicitly provided to the driver. The correct format is important for +computing screen coordinate addresses and for scrolling and line wrap. + +In the example above, the screen format is specifying by setting the +fields in the configuration descriptor: + + cfg.rows = 2; + cfg.cols = 16; + +The lcd backpack can accomodate all known 1-line and 2-line displays, and +4-line displays up to 4 x 32. Explicitly, the 4 x 40 /cannot/ be supported +because it has an important hardware difference (it is actually two 4x20 +controllers, and the LCD backpack does not have the wiring for the +second controller's 'E' line). This is a hardware limitation of the +lcd backpack, rather than the driver. + +Specifying Unknown/New Backpacks +-------------------------------- + +The descriptor initializer macros in the form LCD_I2C_BACKPACK_CFG_xxx +located near the top of pcf8574_lcd_backpack.h are provided for convenience. +However, their use is not required, and it can be useful to initialize the +descriptor with explicit values, say, for custom or unknown boards. + +The format of this descriptor is conscientiously chosen to be semantically +similar to an equivalent initialization mechanism popular in the Arduino +community used in their LCD support libraries. It specifies: + + * I2C address + * pin mapping for data lines + * pin mapping for control lines + * pin mapping for backlight control line + * polarity sense of backlight control line + +and we add to that + + * (row, column) size of display + +(the Arduino libraries specify display size at a different point in code) +You should be able to readily port a functional Arduino project by cutting- +and-pasting the sequence of numbers that are the pin defs for the lcd +backpack you are using. + +Special Features +================ + +Codec +----- + +The driver supports the NuttX 'segment lcd codec', which facilitates the +encoding of control functions into the write() stream. These can be used +to clear the display, move the cursor, etc. For details, q.v. + + nuttx/lcd/slcd_codec.h + +Ioctl +----- + +The driver supports the NuttX ioctl definitions for segment lcd. Q.v. + + nuttx/lcd/slcd_ioctl.h + +Additionally, the ioctl SLCDIOC_CREATECHAR is provided to allow the +creation of custom characters. + +The HD44780 devices generally support the creation of 8 custom +characters, which map to code points 0-7. The characters are 5x8 +pixels (with the expectation that the last row is left blank, to +accommodate the underscore cursor, though this is not strictly a +requirement). + +The SLCDIOC_CREATECHAR ioctl takes a parameter, which is a struct +consisting of the character index being programmed (0-7) and the +8-byte bitmap of the character image. The bitmap is constructed +with each byte representing a row, from top row to bottom row. +Each row is imaged left to right, MSB to LSB, right-justified (i.e., +bit 4 is leftmost, bit 0 is rightmost, and bits 7-5 are unused). + +You may reference these characters simply by including them in +the data you write() to the device, e.g. + + write(fd, "\x01,\x02Hi, there!\n", 13); + +Example of programming a character image: + + static const struct slcd_createchar_s custom_char = + { 4, { 0x04, 0x0e, 0x15, 0x04, 0x04, 0x04, 0x04, 0x00 } }; /* up arrow */ + + ret = ioctl(fd, SLCDIOC_CREATECHAR, (unsigned long)custom_char); + +Now character '\x04' will display as an 'up arrow'. + +Note, you might consider avoiding the use of code point 0x00 unless +absolutely needed, because the embedded nul character can cause +problems. The driver, and write() apis are binary, and unaffected, +but things like printf() and puts() assume C-style strings, and are +affected. + +Troubleshooting +=============== + +* Check your I2C address. turn on debugging output so you can see + bus timeouts that suggest a non-responsive slave. +* Check your board wiring and configuration specification. Buzz + out the lines if you have to. +* Con't forget to check the 'contrast' potentiometer. The voltage + at the central wiper should be approximately 0.29 V. The useful + range of voltages at this pin is very narrow, and outside that + range there will be nothing visible on the display, so most of the + turn range of the pot is non-useful. Much of human life has been + wasted in the rediscovery of this farcically idiotic diff --git a/include/nuttx/lcd/pcf8574_lcd_backpack.h b/include/nuttx/lcd/pcf8574_lcd_backpack.h new file mode 100644 index 0000000000..437d71d64f --- /dev/null +++ b/include/nuttx/lcd/pcf8574_lcd_backpack.h @@ -0,0 +1,176 @@ +/**************************************************************************** + * include/nuttx/lcd/pcf8574_lcd_backpack.h + * + * Copyright (C) 2016 Gregory Nutt. All rights reserved. + * Author: dev@ziggurat29.com + * + * 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 NuttX 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. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_LCD_PCF8574_LCD_BACKPACK_H +#define __INCLUDE_NUTTX_LCD_PCF8574_LCD_BACKPACK_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configurations of some well-known boards. You may still have to modify the + * address if was changed from the default. You will also need to specify the + * geometry of your attached LCD display. You can support: + * 1x8, 1x12, 1x16, 2x8, 2x12, 2x16, 2x20, 2x24, 2x40, 4x16, 4x20 + * Pretty much anything on the market except 4x40, which really consists of two + * separate 2x40 controllers, and the I2C backpack doesn't support those due + * to the second 'E' line being needed. + * Consider these 'informative'. + * XXX Note, actual testing has been done on LCD_I2C_BACKPACK_CFG_MJKDZ only, + * the others come from online research. + */ + +/* board marked 'mjkdz' */ + +#define LCD_I2C_BACKPACK_CFG_MJKDZ {0x20,4,5,6,0,1,2,3,7,false,0,0} + +/* YwRobot/DFRobot/SainSmart */ + +#define LCD_I2C_BACKPACK_CFG_SAINSMART {0x27,2,1,0,4,5,6,7,3,true,0,0} + +/* Robot Arduino LCM1602/2004 */ + +#define LCD_I2C_BACKPACK_CFG_ROBOT {0x27,2,1,0,4,5,6,7,3,false,0,0} + +/* I2CLCDextraIO board modded for backlight (pnp transistor) */ + +#define LCD_I2C_BACKPACK_CFG_I2CIO_PNP {0x38,6,5,4,0,1,2,3,7,false,0,0} + +/* I2CLCDextraIO board modded for backlight (npn transistor) */ + +#define LCD_I2C_BACKPACK_CFG_I2CIO_NPN {0x38,6,5,4,0,1,2,3,7,true,0,0} + +/* SLCDIOC_CREATECHAR: Create a custom character pattern + * + * argument: pointer to slcd_createchar_s structure (defined below) + */ + +#define SLCDIOC_CREATECHAR _SLCDIOC(0x80) + + /**************************************************************************** + * Public Types + ****************************************************************************/ + +/* Used to specify the pin wiring for this particular module */ + +struct pcf8574_lcd_backpack_config_s +{ + uint8_t addr; /* I2C address; 'unshifted' (i.e. disregarding the LSB R/W bit) + * these can vary widely depending on board pullups, whether it + * uses a PCF8574-T or -AT, etc. Many default to either 0x20 + * or 0x27, and some default to 0x38 or 0x3f. Check with seller. + */ + uint8_t en; /* gpio bit for LCD EN */ + uint8_t rw; /* gpio bit for LCD RW */ + uint8_t rs; /* gpio bit for LCD RS */ + uint8_t d4; /* gpio bit for LCD D4 */ + uint8_t d5; /* gpio bit for LCD D5 */ + uint8_t d6; /* gpio bit for LCD D6 */ + uint8_t d7; /* gpio bit for LCD D7 */ + uint8_t bl; /* gpio bit for backlight control */ + bool bl_active_high; /* is the backlight control active high? */ + uint8_t rows; /* screen geometry, rows, 1, 2 or 4 */ + uint8_t cols; /* screen geometry, cols, 8, 12, 16, 20, 24, 40 */ +}; + +/* Used with the SLCDIOC_CREATECHAR ioctl call */ + +struct slcd_createchar_s +{ + uint8_t idx; /* Custom character index; 0-7. Note; you'll probably + * want to avoid code point 0 unless you really need it, + * because embedded nul in a C string can cause surprises. + */ + uint8_t bmp[8]; /* Custom character bitmap. The bitmap is structured as + * a 5x8 bitmap. '1' = pixel on, msb-lsb left-to-right, + * and right-justified (i.e. only bits 4-0 are used). + * Each byte represents 1 row. By convention, you are + * expected to leave the last row all 0's, because it is + * used by the cursor, but this is not strictly required. + */ +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: pcf8574_lcd_backpack_register + * + * Description: + * Register a character driver that is a I2C LCD 'backpack' based on the + * PCF8574 I2C IO expander. It allows operation of the ever-popular HD44780 + * based LCDs via I2C instead of parallel (saving a bunch of gpio lines). + * + * There are a multitude of these available from various sources (e.g. ebay). + * They typically vary by gpio-to-lcd pin mapping, and I2C addresss, but + * otherwise are functionally identical. + * + * The characters presented for codes depend on the masked rom of the + * particular LCD device attached, but the most common are: + * 'A00', which present 0x20-0x7f, and 0xa0-0xff', similar to JIS X 0201 + * 'A02', which present 0x10-0xff, and include various european symbols + * In both cases, codes 0x00-0x07 map to the CGRAM characters, which can be + * loaded via ioctl SLCDIOC_CREATECHAR (q.v.). + * + * This driver supports the SLCD codec for various escape sequences; q.v. + * nuttx/lcd/slcd_codec.h for details. This driver supports the SLCD ioctl + * interface for various extended commands; q.v. nuttx/lcd/slcd_ioctl.h for + * details. This driver supports an additional ioctl for defining custom + * characters; see above for details. + * + * Parameters: + * devpath - path to device node; arbitrary, but typically '/dev/lcd0' or such + * i2c - the low-level i2c bus onto which to bind + * cfg - the board-specific configuration + * + ****************************************************************************/ + +int pcf8574_lcd_backpack_register(FAR const char *devpath, + FAR struct i2c_master_s *i2c, + FAR struct pcf8574_lcd_backpack_config_s *cfg); + +#endif /* __INCLUDE_NUTTX_LCD_PCF8574_LCD_BACKPACK_H */ From d58e4acf178f338a69ce26f216d8ba3d97c40a97 Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Wed, 25 May 2016 08:48:32 -0600 Subject: [PATCH 2/4] Remove whitespace from the end of lines --- drivers/lcd/pcf8574_lcd_backpack.c | 26 ++++++++++----------- drivers/lcd/pcf8574_lcd_backpack_readme.txt | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/lcd/pcf8574_lcd_backpack.c b/drivers/lcd/pcf8574_lcd_backpack.c index 22fd29bb96..09ccdfcd2b 100644 --- a/drivers/lcd/pcf8574_lcd_backpack.c +++ b/drivers/lcd/pcf8574_lcd_backpack.c @@ -567,24 +567,24 @@ static void lcd_init(FAR struct pcf8574_lcd_dev_s *priv) latch_nybble(priv, 0x20>>4, false); /* Function set: DL=0;Interface is 4 bits, N=1 (2 Lines), F=0 (5x8 dots font) */ - + lcd_putcmd(priv, 0x28); /* Display Off: D=0 (Display off), C=0 (Cursor Off), B=0 (Blinking Off) */ - + lcd_putcmd(priv, 0x08); /* Display Clear */ - + lcd_putcmd(priv, CMD_CLEAR); up_udelay(DELAY_US_HOMECLEAR); /* clear needs extra time */ /* Entry Mode Set: I/D=1 (Increment), S=0 (No shift) */ - + lcd_putcmd(priv, 0x06); /* Display On, Cursor Off */ - + lcd_putcmd(priv, 0x0C); } @@ -610,7 +610,7 @@ static void lcd_create_char(FAR struct pcf8574_lcd_dev_s *priv, { int nIdx; uint8_t addr; - + (void)lcd_read_busy_addr(priv, &addr); lcd_putcmd(priv, CMD_SET_CGADDR | (idxchar << 3)); /* set CGRAM address */ @@ -737,7 +737,7 @@ static void lcd_codec_action(FAR struct pcf8574_lcd_dev_s *priv, { if (count <= 0) /* silly case */ break; - + else { uint8_t row; @@ -817,12 +817,12 @@ static void lcd_codec_action(FAR struct pcf8574_lcd_dev_s *priv, { end = priv->cfg.cols; } - + for (nIdx = col; nIdx < end; ++nIdx) { lcd_putdata(priv, ' '); } - + lcd_set_curpos(priv, row, col); } break; @@ -841,12 +841,12 @@ static void lcd_codec_action(FAR struct pcf8574_lcd_dev_s *priv, uint8_t nIdx; lcd_get_curpos(priv, &row, &col); - + for (nIdx = col; nIdx < priv->cfg.cols; ++nIdx) { lcd_putdata(priv, ' '); } - + lcd_set_curpos(priv, row, col); } break; @@ -932,7 +932,7 @@ static void lcd_codec_action(FAR struct pcf8574_lcd_dev_s *priv, lcd_set_curpos(priv, row, 0); } break; - + case SLCDCODE_END: /* Cursor end */ { uint8_t row; @@ -1356,7 +1356,7 @@ int pcf8574_lcd_backpack_register(FAR const char *devpath, { FAR struct pcf8574_lcd_dev_s *priv; int ret; - + /* Sanity check on geometry */ if (cfg->rows < 1 || cfg->rows > 4) diff --git a/drivers/lcd/pcf8574_lcd_backpack_readme.txt b/drivers/lcd/pcf8574_lcd_backpack_readme.txt index c7d2775d11..d5573ea377 100644 --- a/drivers/lcd/pcf8574_lcd_backpack_readme.txt +++ b/drivers/lcd/pcf8574_lcd_backpack_readme.txt @@ -91,7 +91,7 @@ E.g.: #include #include "pcf8574_lcd_backpack.h" - + #define MJKDZ_I2C_PORTNO 1 #define MJKDZ_DEVICE_NAME "/dev/lcd0" @@ -242,4 +242,4 @@ Troubleshooting range of voltages at this pin is very narrow, and outside that range there will be nothing visible on the display, so most of the turn range of the pot is non-useful. Much of human life has been - wasted in the rediscovery of this farcically idiotic + wasted in the rediscovery of this farcically idiotic From 9a2002a30227271829589c0f3a090f6cf281c389 Mon Sep 17 00:00:00 2001 From: Aleksandr Vyhovanec Date: Wed, 25 May 2016 08:59:47 -0600 Subject: [PATCH 3/4] 1-wire driver based on U[S]ART in single-wire, half-duplex mode. --- arch/arm/src/stm32/stm32_1wire.c | 1333 ++++++++++++++++++++++++++++++ arch/arm/src/stm32/stm32_1wire.h | 113 +++ include/nuttx/1wire.h | 215 +++++ 3 files changed, 1661 insertions(+) create mode 100644 arch/arm/src/stm32/stm32_1wire.c create mode 100644 arch/arm/src/stm32/stm32_1wire.h create mode 100644 include/nuttx/1wire.h diff --git a/arch/arm/src/stm32/stm32_1wire.c b/arch/arm/src/stm32/stm32_1wire.c new file mode 100644 index 0000000000..d81feb7999 --- /dev/null +++ b/arch/arm/src/stm32/stm32_1wire.c @@ -0,0 +1,1333 @@ +/**************************************************************************** + * arch/arm/src/stm32/stm32_1wire.c + * + * Copyright (C) 2016 Aleksandr Vyhovanec. All rights reserved. + * Author: Aleksandr Vyhovanec + * + * 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 NuttX 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. + * + ****************************************************************************/ + +/* Links: + * https://www.maximintegrated.com/en/app-notes/index.mvp/id/214 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "up_arch.h" + +#include "stm32_rcc.h" +#include "stm32_1wire.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define BUS_TIMEOUT 5 /* tv_sec */ + +#define RESET_BAUD 9600 +#define RESET_TX 0xF0 +#define TIMESLOT_BAUD 115200 +#define READ_TX 0xFF +#define READ_RX1 0xFF +#define WRITE_TX0 0x00 +#define WRITE_TX1 0xFF + +#define PIN_OPENDRAIN(GPIO) ((GPIO) | GPIO_OPENDRAIN) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* 1-Wire bus task */ + +enum stm32_1wire_msg_e +{ + ONEWIRETASK_NONE = 0, + ONEWIRETASK_RESET, + ONEWIRETASK_WRITE, + ONEWIRETASK_READ +}; + +struct stm32_1wire_msg_s +{ + enum stm32_1wire_msg_e task; /* Task */ + uint8_t *buffer; /* Task buffer */ + int buflen; /* Buffer length */ +}; + +/* 1-Wire device hardware configuration */ + +struct stm32_1wire_config_s +{ + const uint32_t usartbase; /* Base address of USART registers */ + const uint32_t apbclock; /* PCLK 1 or 2 frequency */ + const uint32_t data_pin; /* GPIO configuration for DATA */ + const uint8_t irq; /* IRQ associated with this USART */ + int (*const vector)(int irq, void *context); /* Interrupt handler */ +}; + +/* 1-Wire device Private Data */ + +struct stm32_1wire_priv_s +{ + const struct stm32_1wire_config_s *config; /* Port configuration */ + volatile int refs; /* Referernce count */ + sem_t sem_excl; /* Mutual exclusion semaphore */ + sem_t sem_isr; /* Interrupt wait semaphore */ + int baud; /* Baud rate */ + const struct stm32_1wire_msg_s *msgs; /* Messages data */ + uint8_t *byte; /* Current byte */ + uint8_t bit; /* Current bit */ + volatile int result; /* Exchange result */ +}; + +/* 1-Wire device, Instance */ + +struct stm32_1wire_inst_s +{ + const struct onewire_ops_s *ops; /* Standard 1-Wire operations */ + struct stm32_1wire_priv_s *priv; /* Common driver private data structure */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static inline uint32_t stm32_1wire_in(struct stm32_1wire_priv_s *priv, int offset); +static inline void stm32_1wire_out(struct stm32_1wire_priv_s *priv, int offset, uint32_t value); +static int stm32_1wire_recv(struct stm32_1wire_priv_s *priv); +static void stm32_1wire_send(struct stm32_1wire_priv_s *priv, int ch); +static void stm32_1wire_set_baud(struct stm32_1wire_priv_s *priv); +static void stm32_1wire_set_apb_clock(struct stm32_1wire_priv_s *priv, bool on); +static int stm32_1wire_init(FAR struct stm32_1wire_priv_s *priv); +static int stm32_1wire_deinit(FAR struct stm32_1wire_priv_s *priv); +static inline void stm32_1wire_sem_init(FAR struct stm32_1wire_priv_s *priv); +static inline void stm32_1wire_sem_destroy(FAR struct stm32_1wire_priv_s *priv); +static inline void stm32_1wire_sem_wait(FAR struct stm32_1wire_priv_s *priv); +static inline void stm32_1wire_sem_post(FAR struct stm32_1wire_priv_s *priv); +static int stm32_1wire_process(struct stm32_1wire_priv_s *priv, + FAR const struct stm32_1wire_msg_s *msgs, int count); +static int stm32_1wire_isr(struct stm32_1wire_priv_s *priv); + +#ifdef CONFIG_STM32_1WIRE1 +static int up_interrupt_1wire1(int irq, void *context); +#endif +#ifdef CONFIG_STM32_1WIRE2 +static int up_interrupt_1wire2(int irq, void *context); +#endif +#ifdef CONFIG_STM32_1WIRE3 +static int up_interrupt_1wire3(int irq, void *context); +#endif +#ifdef CONFIG_STM32_1WIRE4 +static int up_interrupt_1wire4(int irq, void *context); +#endif +#ifdef CONFIG_STM32_1WIRE5 +static int up_interrupt_1wire5(int irq, void *context); +#endif +#ifdef CONFIG_STM32_1WIRE6 +static int up_interrupt_1wire6(int irq, void *context); +#endif +#ifdef CONFIG_STM32_1WIRE7 +static int up_interrupt_1wire7(int irq, void *context); +#endif +#ifdef CONFIG_STM32_1WIRE8 +static int up_interrupt_1wire8(int irq, void *context); +#endif + +static int stm32_1wire_reset(FAR struct onewire_dev_s *dev); +static int stm32_1wire_write(FAR struct onewire_dev_s *dev, + const uint8_t *buffer, int buflen); +static int stm32_1wire_read(FAR struct onewire_dev_s *dev, uint8_t *buffer, + int buflen); +static int stm32_1wire_exchange(FAR struct onewire_dev_s *dev, bool reset, + const uint8_t *txbuffer, int txbuflen, + uint8_t *rxbuffer, int rxbuflen); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* 1-Wire device structures */ + +#ifdef CONFIG_STM32_1WIRE1 + +static const struct stm32_1wire_config_s stm32_1wire1_config = +{ + .usartbase = STM32_USART1_BASE, + .apbclock = STM32_PCLK2_FREQUENCY, + .data_pin = PIN_OPENDRAIN(GPIO_USART1_TX), + .irq = STM32_IRQ_USART1, + .vector = up_interrupt_1wire1, +}; + +static struct stm32_1wire_priv_s stm32_1wire1_priv = +{ + .config = &stm32_1wire1_config, + .refs = 0, + .msgs = NULL +}; + +#endif + +#ifdef CONFIG_STM32_1WIRE2 + +static const struct stm32_1wire_config_s stm32_1wire2_config = +{ + .usartbase = STM32_USART2_BASE, + .apbclock = STM32_PCLK1_FREQUENCY, + .data_pin = PIN_OPENDRAIN(GPIO_USART2_TX), + .irq = STM32_IRQ_USART2, + .vector = up_interrupt_1wire2, +}; + +static struct stm32_1wire_priv_s stm32_1wire2_priv = +{ + .config = &stm32_1wire2_config, + .refs = 0, + .msgs = NULL +}; + +#endif + +#ifdef CONFIG_STM32_1WIRE3 + +static const struct stm32_1wire_config_s stm32_1wire3_config = +{ + .usartbase = STM32_USART3_BASE, + .apbclock = STM32_PCLK1_FREQUENCY, + .data_pin = PIN_OPENDRAIN(GPIO_USART3_TX), + .irq = STM32_IRQ_USART3, + .vector = up_interrupt_1wire3, +}; + +static struct stm32_1wire_priv_s stm32_1wire3_priv = +{ + .config = &stm32_1wire3_config, + .refs = 0, + .msgs = NULL +}; + +#endif + +#ifdef CONFIG_STM32_1WIRE4 + +static const struct stm32_1wire_config_s stm32_1wire4_config = +{ + .usartbase = STM32_USART4_BASE, + .apbclock = STM32_PCLK1_FREQUENCY, + .data_pin = PIN_OPENDRAIN(GPIO_USART4_TX), + .irq = STM32_IRQ_USART4, + .vector = up_interrupt_1wire4, +}; + +static struct stm32_1wire_priv_s stm32_1wire4_priv = +{ + .config = &stm32_1wire4_config, + .refs = 0, + .msgs = NULL +}; + +#endif + +#ifdef CONFIG_STM32_1WIRE5 + +static const struct stm32_1wire_config_s stm32_1wire5_config = +{ + .usartbase = STM32_USART5_BASE, + .apbclock = STM32_PCLK1_FREQUENCY, + .data_pin = PIN_OPENDRAIN(GPIO_USART5_TX), + .irq = STM32_IRQ_USART5, + .vector = up_interrupt_1wire5, +}; + +static struct stm32_1wire_priv_s stm32_1wire5_priv = +{ + .config = &stm32_1wire5_config, + .refs = 0, + .msgs = NULL +}; + +#endif + +#ifdef CONFIG_STM32_1WIRE6 + +static const struct stm32_1wire_config_s stm32_1wire6_config = +{ + .usartbase = STM32_USART6_BASE, + .apbclock = STM32_PCLK2_FREQUENCY, + .data_pin = PIN_OPENDRAIN(GPIO_USART6_TX), + .irq = STM32_IRQ_USART6, + .vector = up_interrupt_1wire6, +}; + +static struct stm32_1wire_priv_s stm32_1wire6_priv = +{ + .config = &stm32_1wire6_config, + .refs = 0, + .msgs = NULL +}; + +#endif + +#ifdef CONFIG_STM32_1WIRE7 + +static const struct stm32_1wire_config_s stm32_1wire7_config = +{ + .usartbase = STM32_USART7_BASE, + .apbclock = STM32_PCLK1_FREQUENCY, + .data_pin = PIN_OPENDRAIN(GPIO_USART7_TX), + .irq = STM32_IRQ_USART7, + .vector = up_interrupt_1wire7, +}; + +static struct stm32_1wire_priv_s stm32_1wire7_priv = +{ + .config = &stm32_1wire7_config, + .refs = 0, + .msgs = NULL +}; + +#endif + +#ifdef CONFIG_STM32_1WIRE8 + +static const struct stm32_1wire_config_s stm32_1wire8_config = +{ + .usartbase = STM32_USART8_BASE, + .apbclock = STM32_PCLK1_FREQUENCY, + .data_pin = PIN_OPENDRAIN(GPIO_USART8_TX), + .irq = STM32_IRQ_USART8, + .vector = up_interrupt_1wire8, +}; + +static struct stm32_1wire_priv_s stm32_1wire8_priv = +{ + .config = &stm32_1wire8_config, + .refs = 0, + .msgs = NULL +}; + +#endif + +/* Device Structures, Instantiation */ + +static const struct onewire_ops_s stm32_1wire_ops = +{ + .reset = stm32_1wire_reset, + .write = stm32_1wire_write, + .read = stm32_1wire_read, + .exchange = stm32_1wire_exchange +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: stm32_1wire_in + ****************************************************************************/ + +static inline uint32_t stm32_1wire_in(struct stm32_1wire_priv_s *priv, + int offset) +{ + return getreg32(priv->config->usartbase + offset); +} + +/**************************************************************************** + * Name: stm32_1wire_out + ****************************************************************************/ + +static inline void stm32_1wire_out(struct stm32_1wire_priv_s *priv, + int offset, uint32_t value) +{ + putreg32(value, priv->config->usartbase + offset); +} + +/**************************************************************************** + * Name: stm32_1wire_recv + * + * Description: + * This method will recv one byte on the USART + * + ****************************************************************************/ + +static int stm32_1wire_recv(struct stm32_1wire_priv_s *priv) +{ + return stm32_1wire_in(priv, STM32_USART_RDR_OFFSET) & 0xff; +} + +/**************************************************************************** + * Name: stm32_1wire_send + * + * Description: + * This method will send one byte on the USART + * + ****************************************************************************/ + +static void stm32_1wire_send(struct stm32_1wire_priv_s *priv, int ch) +{ + stm32_1wire_out(priv, STM32_USART_TDR_OFFSET, (uint32_t)(ch & 0xff)); +} + +/**************************************************************************** + * Name: stm32_1wire_set_baud + * + * Description: + * Set the serial line baud. + * + ****************************************************************************/ + +static void stm32_1wire_set_baud(struct stm32_1wire_priv_s *priv) +{ +#if defined(CONFIG_STM32_STM32F30XX) || defined(CONFIG_STM32_STM32F37XX) + /* This first implementation is for U[S]ARTs that support oversampling + * by 8 in additional to the standard oversampling by 16. + */ + + uint32_t usartdiv8; + uint32_t cr1; + uint32_t brr; + + /* In case of oversampling by 8, the equation is: + * + * baud = 2 * fCK / usartdiv8 + * usartdiv8 = 2 * fCK / baud + */ + + usartdiv8 = ((priv->config->apbclock << 1) + (priv->baud >> 1)) / priv->baud; + + /* Baud rate for standard USART (SPI mode included): + * + * In case of oversampling by 16, the equation is: + * baud = fCK / usartdiv16 + * usartdiv16 = fCK / baud + * = 2 * usartdiv8 + */ + + /* Use oversamply by 8 only if the divisor is small. But what is small? */ + + cr1 = stm32_1wire_in(priv, STM32_USART_CR1_OFFSET); + if (usartdiv8 > 100) + { + /* Use usartdiv16 */ + + brr = (usartdiv8 + 1) >> 1; + + /* Clear oversampling by 8 to enable oversampling by 16 */ + + cr1 &= ~USART_CR1_OVER8; + } + else + { + DEBUGASSERT(usartdiv8 >= 8); + + /* Perform mysterious operations on bits 0-3 */ + + brr = ((usartdiv8 & 0xfff0) | ((usartdiv8 & 0x000f) >> 1)); + + /* Set oversampling by 8 */ + + cr1 |= USART_CR1_OVER8; + } + + stm32_1wire_out(priv, STM32_USART_CR1_OFFSET, cr1); + stm32_1wire_out(priv, STM32_USART_BRR_OFFSET, brr); + +#else + + /* This second implementation is for U[S]ARTs that support fractional + * dividers. + */ + + uint32_t usartdiv32; + uint32_t mantissa; + uint32_t fraction; + uint32_t brr; + + /* Configure the USART Baud Rate. The baud rate for the receiver and + * transmitter (Rx and Tx) are both set to the same value as programmed + * in the Mantissa and Fraction values of USARTDIV. + * + * baud = fCK / (16 * usartdiv) + * usartdiv = fCK / (16 * baud) + * + * Where fCK is the input clock to the peripheral (PCLK1 for USART2, 3, 4, 5 + * or PCLK2 for USART1) + * + * First calculate (NOTE: all stand baud values are even so dividing by two + * does not lose precision): + * + * usartdiv32 = 32 * usartdiv = fCK / (baud/2) + */ + + usartdiv32 = priv->config->apbclock / (priv->baud >> 1); + + /* The mantissa part is then */ + + mantissa = usartdiv32 >> 5; + brr = mantissa << USART_BRR_MANT_SHIFT; + + /* The fractional remainder (with rounding) */ + + fraction = (usartdiv32 - (mantissa << 5) + 1) >> 1; + brr |= fraction << USART_BRR_FRAC_SHIFT; + stm32_1wire_out(priv, STM32_USART_BRR_OFFSET, brr); +#endif +} + +/**************************************************************************** + * Name: stm32_1wire_set_apb_clock + * + * Description: + * Enable or disable APB clock for the USART peripheral + * + * Input parameters: + * priv - A reference to the 1-Wire driver state structure + * on - Enable clock if 'on' is 'true' and disable if 'false' + * + ****************************************************************************/ + +static void stm32_1wire_set_apb_clock(struct stm32_1wire_priv_s *priv, + bool on) +{ + const struct stm32_1wire_config_s *config = priv->config; + uint32_t rcc_en; + uint32_t regaddr; + + /* Determine which USART to configure */ + + switch (config->usartbase) + { + default: + return; + +#ifdef CONFIG_STM32_1WIRE1 + case STM32_USART1_BASE: + rcc_en = RCC_APB2ENR_USART1EN; + regaddr = STM32_RCC_APB2ENR; + break; +#endif +#ifdef CONFIG_STM32_1WIRE2 + case STM32_USART2_BASE: + rcc_en = RCC_APB1ENR_USART2EN; + regaddr = STM32_RCC_APB1ENR; + break; +#endif +#ifdef CONFIG_STM32_1WIRE3 + case STM32_USART3_BASE: + rcc_en = RCC_APB1ENR_USART3EN; + regaddr = STM32_RCC_APB1ENR; + break; +#endif +#ifdef CONFIG_STM32_UART4 + case STM32_UART4_BASE: + rcc_en = RCC_APB1ENR_UART4EN; + regaddr = STM32_RCC_APB1ENR; + break; +#endif +#ifdef CONFIG_STM32_UART5 + case STM32_UART5_BASE: + rcc_en = RCC_APB1ENR_UART5EN; + regaddr = STM32_RCC_APB1ENR; + break; +#endif +#ifdef CONFIG_STM32_1WIRE6 + case STM32_USART6_BASE: + rcc_en = RCC_APB2ENR_USART6EN; + regaddr = STM32_RCC_APB2ENR; + break; +#endif +#ifdef CONFIG_STM32_UART7 + case STM32_UART7_BASE: + rcc_en = RCC_APB1ENR_UART7EN; + regaddr = STM32_RCC_APB1ENR; + break; +#endif +#ifdef CONFIG_STM32_UART8 + case STM32_UART8_BASE: + rcc_en = RCC_APB1ENR_UART8EN; + regaddr = STM32_RCC_APB1ENR; + break; +#endif + } + + /* Enable/disable APB 1/2 clock for USART */ + + if (on) + { + modifyreg32(regaddr, 0, rcc_en); + } + else + { + modifyreg32(regaddr, rcc_en, 0); + } +} + +/**************************************************************************** + * Name: stm32_1wire_init + * + * Description: + * Setup the 1-Wire hardware, ready for operation with defaults + * + ****************************************************************************/ + +static int stm32_1wire_init(FAR struct stm32_1wire_priv_s *priv) +{ + const struct stm32_1wire_config_s *config = priv->config; + uint32_t regval; + int ret; + + /* Enable USART APB1/2 clock */ + + stm32_1wire_set_apb_clock(priv, true); + + /* Configure CR2 */ + /* Clear STOP, CLKEN, CPOL, CPHA, LBCL, and interrupt enable bits */ + /* Set LBDIE */ + + regval = stm32_1wire_in(priv, STM32_USART_CR2_OFFSET); + regval &= ~(USART_CR2_STOP_MASK | USART_CR2_CLKEN | USART_CR2_CPOL | + USART_CR2_CPHA | USART_CR2_LBCL | USART_CR2_LBDIE); + regval |= USART_CR2_LBDIE; + stm32_1wire_out(priv, STM32_USART_CR2_OFFSET, regval); + + /* Configure CR1 */ + /* Clear TE, REm, all interrupt enable bits, PCE, PS and M */ + /* Set RXNEIE */ + + regval = stm32_1wire_in(priv, STM32_USART_CR1_OFFSET); + regval &= ~(USART_CR1_TE | USART_CR1_RE | USART_CR1_ALLINTS | + USART_CR1_PCE | USART_CR1_PS | USART_CR1_M); + regval |= USART_CR1_RXNEIE; + stm32_1wire_out(priv, STM32_USART_CR1_OFFSET, regval); + + /* Configure CR3 */ + /* Clear CTSE, RTSE, and all interrupt enable bits */ + /* Set ONEBIT, HDSEL and EIE */ + + regval = stm32_1wire_in(priv, STM32_USART_CR3_OFFSET); + regval &= ~(USART_CR3_CTSIE | USART_CR3_CTSE | USART_CR3_RTSE | USART_CR3_EIE); + regval |= (USART_CR3_ONEBIT | USART_CR3_HDSEL | USART_CR3_EIE); + stm32_1wire_out(priv, STM32_USART_CR3_OFFSET, regval); + + /* Set baud rate */ + + priv->baud = RESET_BAUD; + stm32_1wire_set_baud(priv); + + /* Enable Rx, Tx, and the USART */ + + regval = stm32_1wire_in(priv, STM32_USART_CR1_OFFSET); + regval |= (USART_CR1_UE | USART_CR1_TE | USART_CR1_RE); + stm32_1wire_out(priv, STM32_USART_CR1_OFFSET, regval); + + /* Configure pins for USART use */ + + stm32_configgpio(config->data_pin); + + ret = irq_attach(config->irq, config->vector); + if (ret == OK) + { + up_enable_irq(config->irq); + } + + return ret; +} + +/**************************************************************************** + * Name: stm32_1wire_deinit + * + * Description: + * Shutdown the 1-Wire hardware + * + ****************************************************************************/ + +static int stm32_1wire_deinit(FAR struct stm32_1wire_priv_s *priv) +{ + const struct stm32_1wire_config_s *config = priv->config; + uint32_t regval; + + up_disable_irq(config->irq); + irq_detach(config->irq); + + /* Unconfigure GPIO pins */ + + stm32_unconfiggpio(config->data_pin); + + /* Disable RXNEIE, Rx, Tx, and the USART */ + + regval = stm32_1wire_in(priv, STM32_USART_CR1_OFFSET); + regval &= ~(USART_CR1_UE | USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE); + stm32_1wire_out(priv, STM32_USART_CR1_OFFSET, regval); + + /* Clear LBDIE */ + + regval = stm32_1wire_in(priv, STM32_USART_CR2_OFFSET); + regval &= ~USART_CR2_LBDIE; + stm32_1wire_out(priv, STM32_USART_CR2_OFFSET, regval); + + /* Clear ONEBIT, HDSEL and EIE */ + + regval = stm32_1wire_in(priv, STM32_USART_CR3_OFFSET); + regval &= ~(USART_CR3_ONEBIT | USART_CR3_HDSEL | USART_CR3_EIE); + stm32_1wire_out(priv, STM32_USART_CR3_OFFSET, regval); + + /* Disable USART APB1/2 clock */ + + stm32_1wire_set_apb_clock(priv, false); + + return OK; +} + +/**************************************************************************** + * Name: stm32_1wire_sem_init + * + * Description: + * Initialize semaphores + * + ****************************************************************************/ + +static inline void stm32_1wire_sem_init(FAR struct stm32_1wire_priv_s *priv) +{ + sem_init(&priv->sem_excl, 0, 1); + sem_init(&priv->sem_isr, 0, 0); +} + +/**************************************************************************** + * Name: stm32_1wire_sem_destroy + * + * Description: + * Destroy semaphores. + * + ****************************************************************************/ + +static inline void stm32_1wire_sem_destroy(FAR struct stm32_1wire_priv_s *priv) +{ + sem_destroy(&priv->sem_excl); + sem_destroy(&priv->sem_isr); +} + +/**************************************************************************** + * Name: stm32_1wire_sem_wait + * + * Description: + * Take the exclusive access, waiting as necessary + * + ****************************************************************************/ + +static inline void stm32_1wire_sem_wait(FAR struct stm32_1wire_priv_s *priv) +{ + while (sem_wait(&priv->sem_excl) != 0) + { + ASSERT(errno == EINTR); + } +} + +/**************************************************************************** + * Name: stm32_1wire_sem_post + * + * Description: + * Release the mutual exclusion semaphore + * + ****************************************************************************/ + +static inline void stm32_1wire_sem_post(FAR struct stm32_1wire_priv_s *priv) +{ + sem_post(&priv->sem_excl); +} + +/**************************************************************************** + * Name: stm32_1wire_exec + * + * Description: + * Execute 1-Wire task + ****************************************************************************/ +static int stm32_1wire_process(struct stm32_1wire_priv_s *priv, + FAR const struct stm32_1wire_msg_s *msgs, + int count) +{ + irqstate_t irqs; + struct timespec abstime; + int ret; + + /* Lock out other clients */ + + stm32_1wire_sem_wait(priv); + + priv->result = ERROR; + + for (int indx = 0; indx < count; indx++) + { + switch (msgs[indx].task) + { + case ONEWIRETASK_NONE: + priv->result = OK; + break; + + case ONEWIRETASK_RESET: + /* Set baud rate */ + + priv->baud = RESET_BAUD; + stm32_1wire_set_baud(priv); + + /* Atomic */ + + irqs = irqsave(); + priv->msgs = &msgs[indx]; + stm32_1wire_send(priv, RESET_TX); + irqrestore(irqs); + + /* Wait */ + + clock_gettime(CLOCK_REALTIME, &abstime); + abstime.tv_sec += BUS_TIMEOUT; + sem_timedwait(&priv->sem_isr, &abstime); /* break on timeout if TX line closed to GND */ + break; + + case ONEWIRETASK_WRITE: + /* Set baud rate */ + + priv->baud = TIMESLOT_BAUD; + stm32_1wire_set_baud(priv); + + /* Atomic */ + + irqs = irqsave(); + priv->msgs = &msgs[indx]; + priv->byte = priv->msgs->buffer; + priv->bit = 0; + stm32_1wire_send(priv, (*priv->byte & (1 << priv->bit)) ? WRITE_TX1 : WRITE_TX0); + irqrestore(irqs); + + /* Wait */ + + clock_gettime(CLOCK_REALTIME, &abstime); + abstime.tv_sec += BUS_TIMEOUT; + sem_timedwait(&priv->sem_isr, &abstime); /* break on timeout if TX line closed to GND */ + break; + + case ONEWIRETASK_READ: + /* Set baud rate */ + + priv->baud = TIMESLOT_BAUD; + stm32_1wire_set_baud(priv); + + /* Atomic */ + + irqs = irqsave(); + priv->msgs = &msgs[indx]; + priv->byte = priv->msgs->buffer; + priv->bit = 0; + stm32_1wire_send(priv, READ_TX); + irqrestore(irqs); + + /* Wait */ + + clock_gettime(CLOCK_REALTIME, &abstime); + abstime.tv_sec += BUS_TIMEOUT; + sem_timedwait(&priv->sem_isr, &abstime); /* break on timeout if TX line closed to GND */ + break; + } + + if (priv->result != OK) /* break if error */ + { + break; + } + } + + /* Atomic */ + + irqs = irqsave(); + priv->msgs = NULL; + ret = priv->result; + irqrestore(irqs); + + /* Release the port for re-use by other clients */ + + stm32_1wire_sem_post(priv); + + return ret; +} + +/**************************************************************************** + * Name: stm32_1wire_isr + * + * Description: + * Common Interrupt Service Routine + ****************************************************************************/ + +static int stm32_1wire_isr(struct stm32_1wire_priv_s *priv) +{ + uint32_t sr, dr; + + /* Get the masked USART status word. */ + + sr = stm32_1wire_in(priv, STM32_USART_SR_OFFSET); + + /* Receive loop */ + + if ((sr & USART_SR_RXNE) != 0) + { + dr = stm32_1wire_recv(priv); + + if (priv->msgs != NULL) + { + switch (priv->msgs->task) + { + case ONEWIRETASK_NONE: + break; + + case ONEWIRETASK_RESET: + priv->msgs = NULL; + priv->result = (dr != RESET_TX) ? OK : -ENODEV; /* if read RESET_TX then no slave */ + sem_post(&priv->sem_isr); + break; + + case ONEWIRETASK_WRITE: + if (++priv->bit >= 8) + { + priv->bit = 0; + if (++priv->byte >= (priv->msgs->buffer + priv->msgs->buflen)) /* Done? */ + { + priv->msgs = NULL; + priv->result = OK; + sem_post(&priv->sem_isr); + break; + } + } + + /* Send next bit */ + + stm32_1wire_send(priv, (*priv->byte & (1 << priv->bit)) ? WRITE_TX1 : WRITE_TX0); + break; + + case ONEWIRETASK_READ: + if (dr == READ_RX1) + { + *priv->byte |= (1 << priv->bit); + } + else + { + *priv->byte &= ~(1 << priv->bit); + } + + if (++priv->bit >= 8) + { + priv->bit = 0; + if (++priv->byte >= (priv->msgs->buffer + priv->msgs->buflen)) /* Done? */ + { + priv->msgs = NULL; + priv->result = OK; + sem_post(&priv->sem_isr); + break; + } + } + + /* Recv next bit */ + + stm32_1wire_send(priv, READ_TX); + break; + } + } + } + + /* Bounce check. */ + + if ((sr & (USART_SR_ORE | USART_SR_NE | USART_SR_FE)) != 0) + { +#if defined(CONFIG_STM32_STM32F30XX) || defined(CONFIG_STM32_STM32F37XX) + /* These errors are cleared by writing the corresponding bit to the + * interrupt clear register (ICR). + */ + + stm32_1wire_out(priv, STM32_USART_ICR_OFFSET, + (USART_ICR_NCF | USART_ICR_ORECF | USART_ICR_FECF)); +#else + /* If an error occurs, read from DR to clear the error (data has + * been lost). If ORE is set along with RXNE then it tells you + * that the byte *after* the one in the data register has been + * lost, but the data register value is correct. That case will + * be handled above if interrupts are enabled. Otherwise, that + * good byte will be lost. + */ + + (void)stm32_1wire_recv(priv); +#endif + + if (priv->msgs != NULL) + { + priv->msgs = NULL; + priv->result = ERROR; + sem_post(&priv->sem_isr); + } + } + + /* Bounce check. LIN break detection */ + + if ((sr & USART_SR_LBD) != 0) + { + sr &= ~USART_SR_LBD; + stm32_1wire_out(priv, STM32_USART_SR_OFFSET, sr); + + if (priv->msgs != NULL) + { + priv->msgs = NULL; + priv->result = ERROR; + sem_post(&priv->sem_isr); + } + } + + return OK; +} + +#ifdef CONFIG_STM32_1WIRE1 +static int up_interrupt_1wire1(int irq, void *context) +{ + return stm32_1wire_isr(&stm32_1wire1_priv); +} +#endif +#ifdef CONFIG_STM32_1WIRE2 +static int up_interrupt_1wire2(int irq, void *context) +{ + return stm32_1wire_isr(&stm32_1wire2_priv); +} +#endif +#ifdef CONFIG_STM32_1WIRE3 +static int up_interrupt_1wire3(int irq, void *context) +{ + return stm32_1wire_isr(&stm32_1wire3_priv); +} +#endif +#ifdef CONFIG_STM32_1WIRE4 +static int up_interrupt_1wire4(int irq, void *context) +{ + return stm32_1wire_isr(&stm32_1wire4_priv); +} +#endif +#ifdef CONFIG_STM32_1WIRE5 +static int up_interrupt_1wire5(int irq, void *context) +{ + return stm32_1wire_isr(&stm32_1wire5_priv); +} +#endif +#ifdef CONFIG_STM32_1WIRE6 +static int up_interrupt_1wire6(int irq, void *context) +{ + return stm32_1wire_isr(&stm32_1wire6_priv); +} +#endif +#ifdef CONFIG_STM32_1WIRE7 +static int up_interrupt_1wire7(int irq, void *context) +{ + return stm32_1wire_isr(&stm32_1wire7_priv); +} +#endif +#ifdef CONFIG_STM32_1WIRE8 +static int up_interrupt_1wire8(int irq, void *context) +{ + return stm32_1wire_isr(&stm32_1wire8_priv); +} +#endif + +/**************************************************************************** + * Name: stm32_1wire_reset + * + * Description: + * 1-Wire reset pulse and presence detect. + * + ****************************************************************************/ + +static int stm32_1wire_reset(FAR struct onewire_dev_s *dev) +{ + struct stm32_1wire_priv_s *priv = ((struct stm32_1wire_inst_s *)dev)->priv; + const struct stm32_1wire_msg_s msgs[1] = + { + [0].task = ONEWIRETASK_RESET + }; + + return stm32_1wire_process(priv, msgs, 1); +} + +/**************************************************************************** + * Name: stm32_1wire_write + * + * Description: + * Write 1-Wire data + * + ****************************************************************************/ + +static int stm32_1wire_write(FAR struct onewire_dev_s *dev, const uint8_t *buffer, + int buflen) +{ + struct stm32_1wire_priv_s *priv = ((struct stm32_1wire_inst_s *)dev)->priv; + const struct stm32_1wire_msg_s msgs[1] = + { + [0].task = ONEWIRETASK_WRITE, + [0].buffer = (uint8_t *)buffer, + [0].buflen = buflen + }; + + return stm32_1wire_process(priv, msgs, 1); +} + +/**************************************************************************** + * Name: stm32_1wire_read + * + * Description: + * Read 1-Wire data + * + ****************************************************************************/ + +static int stm32_1wire_read(FAR struct onewire_dev_s *dev, uint8_t *buffer, int buflen) +{ + struct stm32_1wire_priv_s *priv = ((struct stm32_1wire_inst_s *)dev)->priv; + const struct stm32_1wire_msg_s msgs[1] = + { + [0].task = ONEWIRETASK_READ, + [0].buffer = buffer, + [0].buflen = buflen + }; + + return stm32_1wire_process(priv, msgs, 1); +} + +/**************************************************************************** + * Name: stm32_1wire_exchange + * + * Description: + * 1-Wire reset pulse and presence detect, + * Write 1-Wire data, + * Read 1-Wire data + * + ****************************************************************************/ + +static int stm32_1wire_exchange(FAR struct onewire_dev_s *dev, bool reset, + const uint8_t *txbuffer, int txbuflen, + uint8_t *rxbuffer, int rxbuflen) + +{ + int result = ERROR; + struct stm32_1wire_priv_s *priv = ((struct stm32_1wire_inst_s *)dev)->priv; + + if (reset) + { + const struct stm32_1wire_msg_s msgs[3] = + { + [0].task = ONEWIRETASK_RESET, + + [1].task = ONEWIRETASK_WRITE, + [1].buffer = (uint8_t *)txbuffer, + [1].buflen = txbuflen, + + [2].task = ONEWIRETASK_READ, + [2].buffer = rxbuffer, + [2].buflen = rxbuflen + }; + + result = stm32_1wire_process(priv, msgs, 3); + } + else + { + const struct stm32_1wire_msg_s msgs[2] = + { + [0].task = ONEWIRETASK_WRITE, + [0].buffer = (uint8_t *)txbuffer, + [0].buflen = txbuflen, + + [1].task = ONEWIRETASK_READ, + [1].buffer = rxbuffer, + [1].buflen = rxbuflen + }; + + result = stm32_1wire_process(priv, msgs, 2); + } + return result; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: up_1wireinitialize + * + * Description: + * Initialize one 1-Wire port + * + ****************************************************************************/ + +FAR struct onewire_dev_s *up_1wireinitialize(int port) +{ + struct stm32_1wire_priv_s *priv = NULL; /* Private data of device with multiple instances */ + struct stm32_1wire_inst_s *inst = NULL; /* Device, single instance */ + int irqs; + + /* Get 1-Wire private structure */ + + switch (port) + { +#ifdef CONFIG_STM32_1WIRE1 + case 1: + priv = &stm32_1wire1_priv; + break; +#endif +#ifdef CONFIG_STM32_1WIRE2 + case 2: + priv = &stm32_1wire2_priv; + break; +#endif +#ifdef CONFIG_STM32_1WIRE3 + case 3: + priv = &stm32_1wire3_priv; + break; +#endif +#ifdef CONFIG_STM32_1WIRE4 + case 4: + priv = &stm32_1wire4_priv; + break; +#endif +#ifdef CONFIG_STM32_1WIRE5 + case 5: + priv = &stm32_1wire5_priv; + break; +#endif +#ifdef CONFIG_STM32_1WIRE6 + case 6: + priv = &stm32_1wire6_priv; + break; +#endif +#ifdef CONFIG_STM32_1WIRE7 + case 7: + priv = &stm32_1wire7_priv; + break; +#endif +#ifdef CONFIG_STM32_1WIRE8 + case 8: + priv = &stm32_1wire8_priv; + break; +#endif + default: + return NULL; + } + + /* Allocate instance */ + + if (!(inst = kmm_malloc(sizeof(struct stm32_1wire_inst_s)))) + { + return NULL; + } + + /* Initialize instance */ + + inst->ops = &stm32_1wire_ops; + inst->priv = priv; + + /* Initialize private data for the first time, increment reference count, + * power-up hardware and configure GPIOs. + */ + + irqs = irqsave(); + + if (priv->refs++ == 0) + { + stm32_1wire_sem_init(priv); + stm32_1wire_init(priv); + } + + irqrestore(irqs); + return (struct onewire_dev_s *)inst; +} + +/**************************************************************************** + * Name: up_1wireuninitialize + * + * Description: + * Uninitialize an 1-Wire port + * + ****************************************************************************/ + +int up_1wireuninitialize(FAR struct onewire_dev_s *dev) +{ + struct stm32_1wire_priv_s *priv = ((struct stm32_1wire_inst_s *)dev)->priv; + int irqs; + + ASSERT(dev); + + /* Decrement reference count and check for underflow */ + + if (priv->refs == 0) + { + return ERROR; + } + + irqs = irqsave(); + + if (--priv->refs) + { + irqrestore(irqs); + kmm_free(priv); + return OK; + } + + irqrestore(irqs); + + /* Disable power and other HW resource (GPIO's) */ + + stm32_1wire_deinit(priv); + + /* Release unused resources */ + + stm32_1wire_sem_destroy(priv); + + /* Free instance */ + + kmm_free(dev); + return OK; +} diff --git a/arch/arm/src/stm32/stm32_1wire.h b/arch/arm/src/stm32/stm32_1wire.h new file mode 100644 index 0000000000..3052849ac0 --- /dev/null +++ b/arch/arm/src/stm32/stm32_1wire.h @@ -0,0 +1,113 @@ +/************************************************************************************ + * arch/arm/src/stm32/stm32_1wire.h + * + * Copyright (C) 2016 Aleksandr Vyhovanec. All rights reserved. + * Author: Aleksandr Vyhovanec + * + * 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 NuttX 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. + * + ************************************************************************************/ + +#ifndef __ARCH_ARM_SRC_STM32_STM32_1WIRE_H +#define __ARCH_ARM_SRC_STM32_STM32_1WIRE_H + +/************************************************************************************ + * Included Files + ************************************************************************************/ + +#include + +#include "chip.h" + +#if defined(CONFIG_STM32_STM32L15XX) +# include "chip/stm32l15xxx_uart.h" +#elif defined(CONFIG_STM32_STM32F10XX) +# include "chip/stm32f10xxx_uart.h" +#elif defined(CONFIG_STM32_STM32F20XX) +# include "chip/stm32f20xxx_uart.h" +#elif defined(CONFIG_STM32_STM32F30XX) || defined(CONFIG_STM32_STM32F37XX) +# include "chip/stm32f30xxx_uart.h" +#elif defined(CONFIG_STM32_STM32F40XX) +# include "chip/stm32f40xxx_uart.h" +#else +# error "Unsupported STM32 UART" +#endif + +/************************************************************************************ + * Pre-processor Definitions + ************************************************************************************/ + +/* Check 1-Wire and U(S)ART conflicting */ + +#if defined(CONFIG_STM32_1WIRE1) && defined(CONFIG_STM32_USART1) +# undef CONFIG_STM32_1WIRE1 +#endif +#if defined(CONFIG_STM32_1WIRE2) && defined(CONFIG_STM32_USART2) +# undef CONFIG_STM32_1WIRE2 +#endif +#if defined(CONFIG_STM32_1WIRE3) && defined(CONFIG_STM32_USART3) +# undef CONFIG_STM32_1WIRE3 +#endif +#if defined(CONFIG_STM32_1WIRE4) && defined(CONFIG_STM32_UART4) +# undef CONFIG_STM32_1WIRE4 +#endif +#if defined(CONFIG_STM32_1WIRE5) && defined(CONFIG_STM32_UART5) +# undef CONFIG_STM32_1WIRE5 +#endif +#if defined(CONFIG_STM32_1WIRE6) && defined(CONFIG_STM32_USART6) +# undef CONFIG_STM32_1WIRE6 +#endif +#if defined(CONFIG_STM32_1WIRE7) && defined(CONFIG_STM32_UART7) +# undef CONFIG_STM32_1WIRE7 +#endif +#if defined(CONFIG_STM32_1WIRE8) && defined(CONFIG_STM32_UART8) +# undef CONFIG_STM32_1WIRE8 +#endif + +/* Is there a 1-Wire enabled? */ + +#if defined(CONFIG_STM32_1WIRE1) || defined(CONFIG_STM32_1WIRE2) || \ + defined(CONFIG_STM32_1WIRE3) || defined(CONFIG_STM32_1WIRE4) || \ + defined(CONFIG_STM32_1WIRE5) || defined(CONFIG_STM32_1WIRE6) || \ + defined(CONFIG_STM32_1WIRE7) || defined(CONFIG_STM32_1WIRE8) +# define HAVE_1WIRE 1 +#endif + +/************************************************************************************ + * Public Types + ************************************************************************************/ + +/************************************************************************************ + * Public Data + ************************************************************************************/ + +/************************************************************************************ + * Public Functions + ************************************************************************************/ + +#endif /* __ARCH_ARM_SRC_STM32_STM32_1WIRE_H */ diff --git a/include/nuttx/1wire.h b/include/nuttx/1wire.h new file mode 100644 index 0000000000..fe2dd87a7b --- /dev/null +++ b/include/nuttx/1wire.h @@ -0,0 +1,215 @@ +/**************************************************************************** + * include/nuttx/1wire.h + * + * Copyright (C) 2016 Aleksandr Vyhovanec. All rights reserved. + * Author: Aleksandr Vyhovanec + * + * 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 NuttX 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. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_1WIRE_H +#define __INCLUDE_NUTTX_1WIRE_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Name: ONEWIRE_RESET + * + * Description: + * Reset pulse and presence detect. Each write operational will be an 'atomic' + * operation in the sense that any other 1-Wire actions will be serialized + * and pend until this write completes. + * + * Input Parameters: + * dev - Device-specific state data + * + * Returned Value: + * 0: success, <0: A negated errno + * + ****************************************************************************/ + +#define ONEWIRE_RESET(d) ((d)->ops->reset(d)) + +/**************************************************************************** + * Name: ONEWIRE_WRITE + * + * Description: + * Send a block of data on 1-Wire. Each write operational will be an 'atomic' + * operation in the sense that any other 1-Wire actions will be serialized + * and pend until this write completes. + * + * Input Parameters: + * dev - Device-specific state data + * buffer - A pointer to the read-only buffer of data to be written to device + * buflen - The number of bytes to send from the buffer + * + * Returned Value: + * 0: success, <0: A negated errno + * + ****************************************************************************/ + +#define ONEWIRE_WRITE(d,b,l) ((d)->ops->write(d,b,l)) + +/**************************************************************************** + * Name: ONEWIRE_READ + * + * Description: + * Receive a block of data from 1-Wire. Each read operational will be an 'atomic' + * operation in the sense that any other 1-Wire actions will be serialized + * and pend until this read completes. + * + * Input Parameters: + * dev - Device-specific state data + * buffer - A pointer to a buffer of data to receive the data from the device + * buflen - The requested number of bytes to be read + * + * Returned Value: + * 0: success, <0: A negated errno + * + ****************************************************************************/ + +#define ONEWIRE_READ(d,b,l) ((d)->ops->read(d,b,l)) + +/**************************************************************************** + * Name: ONEWIRE_EXCHANGE + * + * Description: + * Reset pulse and presence detect, send a block of data and receive a block + * of data from 1-Wir. Each write operational will be an 'atomic' + * operation in the sense that any other 1-Wire actions will be serialized + * and pend until this write completes. + * + * Input Parameters: + * dev - Device-specific state data + * reset - Reset pulse and presence detect + * txbuffer - A pointer to the read-only buffer of data to be written to device + * txbuflen - The number of bytes to send from the buffer + * rxbuffer - A pointer to a buffer of data to receive the data from the device + * rxbuflen - The requested number of bytes to be read + * + * Returned Value: + * 0: success, <0: A negated errno + * + ****************************************************************************/ + +#define ONEWIRE_EXCHANGE(d,r,tx,tl,rx,rl) ((d)->ops->exchange(d,r,tx,tl,rx,rl)) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* The 1-Wire vtable */ + +struct onewire_dev_s; +struct onewire_ops_s +{ + int (*reset)(FAR struct onewire_dev_s *dev); + int (*write)(FAR struct onewire_dev_s *dev, FAR const uint8_t *buffer, + int buflen); + int (*read)(FAR struct onewire_dev_s *dev, FAR uint8_t *buffer, + int buflen); + int (*exchange)(FAR struct onewire_dev_s *dev, bool reset, + FAR const uint8_t *txbuffer, int txbuflen, + FAR uint8_t *rxbuffer, int rxbuflen); +}; + +/* 1-Wire private data. This structure only defines the initial fields of the + * structure visible to the 1-Wire client. The specific implementation may + * add additional, device specific fields after the vtable. + */ + +struct onewire_dev_s +{ + const struct onewire_ops_s *ops; /* 1-Wire vtable */ +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: up_1wireinitialize + * + * Description: + * Initialize the selected 1-Wire port. And return a unique instance of struct + * struct onewire_dev_s. This function may be called to obtain multiple + * instances of the interface, each of which may be set up with a + * different frequency and slave address. + * + * Input Parameter: + * Port number (for hardware that has multiple 1-Wire interfaces) + * + * Returned Value: + * Valid 1-Wire device structure reference on succcess; a NULL on failure + * + ****************************************************************************/ + +FAR struct onewire_dev_s *up_1wireinitialize(int port); + +/**************************************************************************** + * Name: up_1wireuninitialize + * + * Description: + * De-initialize the selected 1-Wire port, and power down the device. + * + * Input Parameter: + * Device structure as returned by the up_1wireinitialize() + * + * Returned Value: + * OK on success, ERROR when internal reference count mismatch or dev + * points to invalid hardware device. + * + ****************************************************************************/ + +int up_1wireuninitialize(FAR struct onewire_dev_s *dev); + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __INCLUDE_NUTTX_1WIRE_H */ From 52c6cb17994cd7ed9c2f6985b635ebfbfc6c2439 Mon Sep 17 00:00:00 2001 From: Aleksandr Vyhovanec Date: Wed, 25 May 2016 09:04:03 -0600 Subject: [PATCH 4/4] Fix typographical naming error in STM32 U[S]ART bit defintiions. --- arch/arm/src/stm32/chip/stm32f20xxx_uart.h | 2 +- arch/arm/src/stm32/chip/stm32f30xxx_uart.h | 26 +++++++++++----------- arch/arm/src/stm32/chip/stm32f40xxx_uart.h | 2 +- arch/arm/src/stm32/chip/stm32l15xxx_uart.h | 2 +- arch/arm/src/stm32/stm32_lowputc.c | 6 ++--- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/arch/arm/src/stm32/chip/stm32f20xxx_uart.h b/arch/arm/src/stm32/chip/stm32f20xxx_uart.h index 5e5b4449de..4da5c0af2e 100644 --- a/arch/arm/src/stm32/chip/stm32f20xxx_uart.h +++ b/arch/arm/src/stm32/chip/stm32f20xxx_uart.h @@ -199,7 +199,7 @@ #define USART_CR3_RTSE (1 << 8) /* Bit 8: RTS Enable */ #define USART_CR3_CTSE (1 << 9) /* Bit 9: CTS Enable */ #define USART_CR3_CTSIE (1 << 10) /* Bit 10: CTS Interrupt Enable */ -#define USART_CR1_ONEBIT (1 << 11) /* Bit 11: One sample bit method enable */ +#define USART_CR3_ONEBIT (1 << 11) /* Bit 11: One sample bit method enable */ /* Guard time and prescaler register */ diff --git a/arch/arm/src/stm32/chip/stm32f30xxx_uart.h b/arch/arm/src/stm32/chip/stm32f30xxx_uart.h index 9a55a921a7..5593e6848f 100644 --- a/arch/arm/src/stm32/chip/stm32f30xxx_uart.h +++ b/arch/arm/src/stm32/chip/stm32f30xxx_uart.h @@ -218,19 +218,19 @@ #define USART_CR3_RTSE (1 << 8) /* Bit 8: RTS Enable */ #define USART_CR3_CTSE (1 << 9) /* Bit 9: CTS Enable */ #define USART_CR3_CTSIE (1 << 10) /* Bit 10: CTS Interrupt Enable */ -#define USART_CR1_ONEBIT (1 << 11) /* Bit 11: One sample bit method enable */ -#define USART_CR1_OVRDIS (1 << 12) /* Bit 12: Overrun Disable */ -#define USART_CR1_DDRE (1 << 13) /* Bit 13: DMA Disable on Reception Error */ -#define USART_CR1_DEM (1 << 14) /* Bit 14: Driver enable mode */ -#define USART_CR1_DEP (1 << 15) /* Bit 15: Driver enable polarity selection */ -#define USART_CR1_SCARCNT_SHIFT (17) /* Bit 17-19: Smartcard auto-retry count */ -#define USART_CR1_SCARCNT_MASK (7 << USART_CR1_SCARCNT_SHIFT) -#define USART_CR1_WUS_SHIFT (20) /* Bit 20-21: Wakeup from Stop mode interrupt */ -#define USART_CR1_WUS_MASK (3 << USART_CR1_WUS_SHIFT) -# define USART_CR1_WUS_ADDRMAT (0 << USART_CR1_WUS_SHIFT) /* Active on address match */ -# define USART_CR1_WUS_STARTBIT (2 << USART_CR1_WUS_SHIFT) /* Active on Start bit */ -# define USART_CR1_WUS_RXNE (3 << USART_CR1_WUS_SHIFT) /* Active on RXNE */ -#define USART_CR1_WUFIE (1 << 22) /* Bit 22: Wakeup from Stop mode interrupt enable */ +#define USART_CR3_ONEBIT (1 << 11) /* Bit 11: One sample bit method enable */ +#define USART_CR3_OVRDIS (1 << 12) /* Bit 12: Overrun Disable */ +#define USART_CR3_DDRE (1 << 13) /* Bit 13: DMA Disable on Reception Error */ +#define USART_CR3_DEM (1 << 14) /* Bit 14: Driver enable mode */ +#define USART_CR3_DEP (1 << 15) /* Bit 15: Driver enable polarity selection */ +#define USART_CR3_SCARCNT_SHIFT (17) /* Bit 17-19: Smartcard auto-retry count */ +#define USART_CR3_SCARCNT_MASK (7 << USART_CR3_SCARCNT_SHIFT) +#define USART_CR3_WUS_SHIFT (20) /* Bit 20-21: Wakeup from Stop mode interrupt */ +#define USART_CR3_WUS_MASK (3 << USART_CR3_WUS_SHIFT) +# define USART_CR3_WUS_ADDRMAT (0 << USART_CR3_WUS_SHIFT) /* Active on address match */ +# define USART_CR3_WUS_STARTBIT (2 << USART_CR3_WUS_SHIFT) /* Active on Start bit */ +# define USART_CR3_WUS_RXNE (3 << USART_CR3_WUS_SHIFT) /* Active on RXNE */ +#define USART_CR3_WUFIE (1 << 22) /* Bit 22: Wakeup from Stop mode interrupt enable */ /* Baud Rate Register */ diff --git a/arch/arm/src/stm32/chip/stm32f40xxx_uart.h b/arch/arm/src/stm32/chip/stm32f40xxx_uart.h index 824ea9515b..9693baa507 100644 --- a/arch/arm/src/stm32/chip/stm32f40xxx_uart.h +++ b/arch/arm/src/stm32/chip/stm32f40xxx_uart.h @@ -217,7 +217,7 @@ #define USART_CR3_RTSE (1 << 8) /* Bit 8: RTS Enable */ #define USART_CR3_CTSE (1 << 9) /* Bit 9: CTS Enable */ #define USART_CR3_CTSIE (1 << 10) /* Bit 10: CTS Interrupt Enable */ -#define USART_CR1_ONEBIT (1 << 11) /* Bit 11: One sample bit method enable */ +#define USART_CR3_ONEBIT (1 << 11) /* Bit 11: One sample bit method enable */ /* Guard time and prescaler register */ diff --git a/arch/arm/src/stm32/chip/stm32l15xxx_uart.h b/arch/arm/src/stm32/chip/stm32l15xxx_uart.h index dff9244933..4b85b837c7 100644 --- a/arch/arm/src/stm32/chip/stm32l15xxx_uart.h +++ b/arch/arm/src/stm32/chip/stm32l15xxx_uart.h @@ -191,7 +191,7 @@ #define USART_CR3_RTSE (1 << 8) /* Bit 8: RTS Enable */ #define USART_CR3_CTSE (1 << 9) /* Bit 9: CTS Enable */ #define USART_CR3_CTSIE (1 << 10) /* Bit 10: CTS Interrupt Enable */ -#define USART_CR1_ONEBIT (1 << 11) /* Bit 11: One sample bit method enable */ +#define USART_CR3_ONEBIT (1 << 11) /* Bit 11: One sample bit method enable */ /* Guard time and prescaler register */ diff --git a/arch/arm/src/stm32/stm32_lowputc.c b/arch/arm/src/stm32/stm32_lowputc.c index 4307c45748..fcde5cd477 100644 --- a/arch/arm/src/stm32/stm32_lowputc.c +++ b/arch/arm/src/stm32/stm32_lowputc.c @@ -273,9 +273,9 @@ # define USART_CR3_CLRBITS \ (USART_CR3_EIE | USART_CR3_IREN | USART_CR3_IRLP | USART_CR3_HDSEL | \ USART_CR3_NACK | USART_CR3_SCEN | USART_CR3_DMAR | USART_CR3_DMAT | \ - USART_CR3_RTSE | USART_CR3_CTSE | USART_CR3_CTSIE | USART_CR1_ONEBIT | \ - USART_CR1_OVRDIS | USART_CR1_DDRE | USART_CR1_DEM | USART_CR1_DEP | \ - USART_CR1_SCARCNT_MASK | USART_CR1_WUS_MASK | USART_CR1_WUFIE) + USART_CR3_RTSE | USART_CR3_CTSE | USART_CR3_CTSIE | USART_CR3_ONEBIT | \ + USART_CR3_OVRDIS | USART_CR3_DDRE | USART_CR3_DEM | USART_CR3_DEP | \ + USART_CR3_SCARCNT_MASK | USART_CR3_WUS_MASK | USART_CR3_WUFIE) # else # define USART_CR3_CLRBITS \ (USART_CR3_CTSIE | USART_CR3_CTSE | USART_CR3_RTSE | USART_CR3_EIE)