diff --git a/arch/arm/src/stm32/chip/stm32f20xxx_uart.h b/arch/arm/src/stm32/chip/stm32f20xxx_uart.h index 5e5b4449de0..4da5c0af2e0 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 9a55a921a72..5593e6848ff 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 824ea9515b5..9693baa5077 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 dff92449335..4b85b837c76 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_1wire.c b/arch/arm/src/stm32/stm32_1wire.c new file mode 100644 index 00000000000..d81feb7999e --- /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 00000000000..3052849ac05 --- /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/arch/arm/src/stm32/stm32_lowputc.c b/arch/arm/src/stm32/stm32_lowputc.c index 4307c45748e..fcde5cd477f 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) diff --git a/drivers/lcd/pcf8574_lcd_backpack.c b/drivers/lcd/pcf8574_lcd_backpack.c new file mode 100644 index 00000000000..09ccdfcd2b9 --- /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 00000000000..d5573ea377e --- /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/1wire.h b/include/nuttx/1wire.h new file mode 100644 index 00000000000..fe2dd87a7b7 --- /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 */ diff --git a/include/nuttx/lcd/pcf8574_lcd_backpack.h b/include/nuttx/lcd/pcf8574_lcd_backpack.h new file mode 100644 index 00000000000..437d71d64fe --- /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 */