/* * Copyright (C) 2001-2003 by egnite Software GmbH. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * For additional information see http://www.ethernut.de/ * */ /*! * \file arch/avr/dev/spidigio.c * \brief AVR external shift register I/O support. * * \verbatim * $Id: spidigio.c 4706 2012-10-06 17:42:01Z haraldkipp $ * \endverbatim */ /* * This header file specifies the hardware port bits. You * need to change or replace it, if your hardware differs. */ #include /* * The following header file contains the prototypes of * all global functions, which this module provides. */ #include /* * Determine ports, which had not been explicitly configured. */ #ifndef SPIDIGIO_SOUT_BIT #define SPIDIGIO_SOUT_BIT 5 #define SPIDIGIO_SOUT_AVRPORT AVRPORTD #define SPIDIGIO_SIN_BIT 6 #define SPIDIGIO_SIN_AVRPORT AVRPORTD #define SPIDIGIO_SCLK_BIT 7 #define SPIDIGIO_SCLK_AVRPORT AVRPORTD #define SPIDIGIO_LDI_BIT 7 #define SPIDIGIO_LDI_AVRPORT AVRPORTB #define SPIDIGIO_LDO_BIT 5 #define SPIDIGIO_LDO_AVRPORT AVRPORTB #endif #if (SPIDIGIO_SOUT_AVRPORT == AVRPORTB) #define SPIDIGIO_SOUT_PORT PORTB #define SPIDIGIO_SOUT_DDR DDRB #elif (SPIDIGIO_SOUT_AVRPORT == AVRPORTD) #define SPIDIGIO_SOUT_PORT PORTD #define SPIDIGIO_SOUT_DDR DDRD #elif (SPIDIGIO_SOUT_AVRPORT == AVRPORTE) #define SPIDIGIO_SOUT_PORT PORTE #define SPIDIGIO_SOUT_DDR DDRE #elif (SPIDIGIO_SOUT_AVRPORT == AVRPORTF) #define SPIDIGIO_SOUT_PORT PORTF #define SPIDIGIO_SOUT_DDR DDRF #endif #if (SPIDIGIO_SIN_AVRPORT == AVRPORTB) #define SPIDIGIO_SIN_PORT PORTB #define SPIDIGIO_SIN_PIN PINB #define SPIDIGIO_SIN_DDR DDRB #elif (SPIDIGIO_SIN_AVRPORT == AVRPORTD) #define SPIDIGIO_SIN_PORT PORTD #define SPIDIGIO_SIN_PIN PIND #define SPIDIGIO_SIN_DDR DDRD #elif (SPIDIGIO_SIN_AVRPORT == AVRPORTE) #define SPIDIGIO_SIN_PORT PORTE #define SPIDIGIO_SIN_PIN PINE #define SPIDIGIO_SIN_DDR DDRE #elif (SPIDIGIO_SIN_AVRPORT == AVRPORTF) #define SPIDIGIO_SIN_PORT PORTF #define SPIDIGIO_SIN_PIN PINF #define SPIDIGIO_SIN_DDR DDRF #endif #if (SPIDIGIO_SCLK_AVRPORT == AVRPORTB) #define SPIDIGIO_SCLK_PORT PORTB #define SPIDIGIO_SCLK_DDR DDRB #elif (SPIDIGIO_SCLK_AVRPORT == AVRPORTD) #define SPIDIGIO_SCLK_PORT PORTD #define SPIDIGIO_SCLK_DDR DDRD #elif (SPIDIGIO_SCLK_AVRPORT == AVRPORTE) #define SPIDIGIO_SCLK_PORT PORTE #define SPIDIGIO_SCLK_DDR DDRE #elif (SPIDIGIO_SCLK_AVRPORT == AVRPORTF) #define SPIDIGIO_SCLK_PORT PORTF #define SPIDIGIO_SCLK_DDR DDRF #endif /* SPIDIGIO_SCLK_AVRPORT */ #if (SPIDIGIO_LDO_AVRPORT == AVRPORTB) #define SPIDIGIO_LDO_PORT PORTB #define SPIDIGIO_LDO_DDR DDRB #elif (SPIDIGIO_LDO_AVRPORT == AVRPORTD) #define SPIDIGIO_LDO_PORT PORTD #define SPIDIGIO_LDO_DDR DDRD #elif (SPIDIGIO_LDO_AVRPORT == AVRPORTE) #define SPIDIGIO_LDO_PORT PORTE #define SPIDIGIO_LDO_DDR DDRE #elif (SPIDIGIO_LDO_AVRPORT == AVRPORTF) #define SPIDIGIO_LDO_PORT PORTF #define SPIDIGIO_LDO_DDR DDRF #endif /* SPIDIGIO_LDO_AVRPORT */ #if (SPIDIGIO_LDI_AVRPORT == AVRPORTB) #define SPIDIGIO_LDI_PORT PORTB #define SPIDIGIO_LDI_DDR DDRB #elif (SPIDIGIO_LDI_AVRPORT == AVRPORTD) #define SPIDIGIO_LDI_PORT PORTD #define SPIDIGIO_LDI_DDR DDRD #elif (SPIDIGIO_LDI_AVRPORT == AVRPORTE) #define SPIDIGIO_LDI_PORT PORTE #define SPIDIGIO_LDI_DDR DDRE #elif (SPIDIGIO_LDI_AVRPORT == AVRPORTF) #define SPIDIGIO_LDI_PORT PORTF #define SPIDIGIO_LDI_DDR DDRF #endif /* SPIDIGIO_LDI_AVRPORT */ /*! * \addtogroup xgSpiDigIo */ /*@{*/ static ureg_t us_loops = 1; /*! * \brief Loop for a small number of microseconds. * * This call will not release the CPU and will not switch to another * thread. It simply executes a number of NOP (no operation) assembly * statements. * * The routine is quite limited. Depending on the value of the * global variable us_loop, which is calculated in SpiDigitalInit(), * and based on the CPU clock. On a 14 MHz standard Ethernut its * value is 3, allowing a maximum delay of 85 microseconds. * * \param us Approximate delay in microseconds. * */ static INLINE void delay_us(ureg_t us) { ureg_t _cnt = us * us_loops; while (_cnt--) { /* * A no-operation assembly statement is used here. * Without this statement, the compiler may completely * wipe out the loop during optimization. */ _NOP(); } } /*! * \brief Toggle shift register clock to perform a shift. * * Shifting is done on the falling edge of the clock line. * * The required puls width is 150 ns for the UCN5841 output and only * 25 ns for the SN74HC165 input shift register hardware. However, * longer cables and additional noise filters may increase the required * minimum pulse length. On the reference hardware the rising edge of * the RC filters used is about 3 microseconds. */ static INLINE void ShiftDigital(void) { /* Switch clock line low. */ cbi(SPIDIGIO_SCLK_PORT, SPIDIGIO_SCLK_BIT); /* Four microseconds delay. */ delay_us(4); /* Switch clock line back high. */ sbi(SPIDIGIO_SCLK_PORT, SPIDIGIO_SCLK_BIT); /* Four microseconds delay. */ delay_us(4); } /*! * \brief Query digital inputs. * * SpiDigitalInit() must have been called by the application before * calling this function. * * This routine does not check the validity of the parameter. * * \param num Number of bits to query, typically 8, 16, 24 or the maximum * value of 32. This number should exactly match the number of * input pins. If it is lower, only the most significant bits * are returned. However, this may be used by an application * to scan these bits more often with reduced overhead. * * \return Binary value of the requested inputs. Only the specified number * of bits are used. Bit 0 is the one, which has been shifted out * last. */ uint32_t SpiDigitalGet(ureg_t num) { uint32_t bits = 0; cbi(SPIDIGIO_SOUT_PORT, SPIDIGIO_SOUT_BIT); /* * Toggle the input strobe. The shift register will latch * the current value at the parallel inputs into the shift * register. */ sbi(SPIDIGIO_LDI_PORT, SPIDIGIO_LDI_BIT); delay_us(4); cbi(SPIDIGIO_LDI_PORT, SPIDIGIO_LDI_BIT); delay_us(4); /* Loop for the specified number of bits. */ while (num--) { /* Shift the resulting value first. */ bits <<= 1; /* * The shift register's serial output pin is connected to * the port input pin. If this input is high, set the least * significant bit of the resulting value. Otherwise leave * it zero. */ if (bit_is_set(SPIDIGIO_SIN_PIN, SPIDIGIO_SIN_BIT)) { bits |= 1; } /* * This will toggle the clock line, presenting the next bit * at the shift register's serial output pin. */ ShiftDigital(); } return bits; } /*! * \brief Set digital outputs. * * SpiDigitalInit() must have been called by the application before * calling this function. * * This routine does not check the validity of any parameter. * * \param num Number of bits to set, typically 8, 16, 24 or 32, which is * the maximum. The number must not be lower than the number * of shift register output bits. * \param bits The bit value to set. Only the number of bits specified are * used, of which the most significant bit is shifted in first. */ void SpiDigitalSet(ureg_t num, uint32_t bits) { uint32_t mask; /* Nothing to do, if the number of bits is zero. */ if (num) { /* * Create the bit mask of the most significant bit. Note the UL * specifier. Most compilers will use integers by default, when * calculating of the right side. They do not consider the left * side. In our case this would create unexpected results, if * integers are 16 bit only. */ mask = 1UL << (num - 1); /* Loop for the specified number of bits. */ while (num--) { /* * The shift register input is connected to the CPU output. * If the currently masked bit is set, then set the CPU * output pin to high level. Otherwise set the output * pin to low. */ if (bits & mask) { /* Set bit instruction. */ sbi(SPIDIGIO_SOUT_PORT, SPIDIGIO_SOUT_BIT); } else { /* Clear bit instruction. */ cbi(SPIDIGIO_SOUT_PORT, SPIDIGIO_SOUT_BIT); } /* Let the value get settled. */ delay_us(4); /* Toggle the shift register clock line. */ ShiftDigital(); /* Left shift the mask by one. */ mask >>= 1; } /* * Toggle the output strobe line. The shift register will latch * the shifted value and present it at its parallel output pins. */ cbi(SPIDIGIO_LDO_PORT, SPIDIGIO_LDO_BIT); delay_us(4); sbi(SPIDIGIO_LDO_PORT, SPIDIGIO_LDO_BIT); } } /*! * \brief Return the number of shifts required to get an expected bit * value at the input. * * When calling this function, it is assumed, that the shift register is * already filled with the complement of the input bit. * * If the search mode is zero, then the function will return the number * of shifts until the bit value at the input appears at the output. * This can be used to detect the total size of the shift register. * * If the search mode is set to one, then the input strobe will be * toggled before shifting and the function returns the number of shifts * until the first unmodified bit appears. This can be used to detect * the number of inputs. This method requires, that the parallel shift * register inputs do not change during shifting. If they do change, then * the resulting number will be lower than expected. The routine may be * called several times to compensate this problem. * * \param num Total number of shifts, should be 8, 16, 24 or 32. * \param bit Input bit, either 0 or 1. * \param smode Search mode. * * \return The number of shifts. */ static ureg_t CountDigitalShifts(ureg_t num, ureg_t bit, ureg_t smode) { ureg_t i; ureg_t rc = 0; /* * Toggle input strobe if we are searching the last modified bit. * Input lines are latched on the falling edge. */ if (smode) { sbi(SPIDIGIO_LDI_PORT, SPIDIGIO_LDI_BIT); delay_us(4); cbi(SPIDIGIO_LDI_PORT, SPIDIGIO_LDI_BIT); } /* * Set the shift register input. */ if (bit) { sbi(SPIDIGIO_SOUT_PORT, SPIDIGIO_SOUT_BIT); } else { cbi(SPIDIGIO_SOUT_PORT, SPIDIGIO_SOUT_BIT); } delay_us(4); /* * Do the requested number of shifts and watch the requested bit * position. */ for (i = 0; i < num; i++) { if (bit_is_set(SPIDIGIO_SIN_PIN, SPIDIGIO_SIN_BIT)) { if (bit) { if (smode) { rc = i + 1; } else if (rc == 0) { rc = i + 1; } } } else { if (bit == 0) { if (smode) { rc = i + 1; } else if (rc == 0) { rc = i + 1; } } } ShiftDigital(); } return rc; } /*! * \brief Initialize the digital I/O shift register interface. * * This routine must be called before using the interface. * * Automatically detects the number of digital inputs and outputs. However, * the method used is limited and may fail on fast changing inputs. The * application should check the result for plausibility. It is save to * call the routine more than once. * * \param inputs Pointer to an 8-bit value, where the number of * detected inputs will be stored. * \param outputs Pointer to an 8-bit value, where the number of * detected outputs will be stored. */ void SpiDigitalInit(ureg_t * inputs, ureg_t * outputs) { ureg_t total = 0; ureg_t i; ureg_t cnt; /* * Determine the delay loop count based on the CPU clock. */ if ((us_loops = (NutGetCpuClock() + 500000UL) / 4000000UL) < 1) { us_loops = 1; } /* * Set serial data output line direction. */ sbi(SPIDIGIO_SOUT_DDR, SPIDIGIO_SOUT_BIT); /* * Set serial data input line direction and enable pullup. */ sbi(SPIDIGIO_SIN_PORT, SPIDIGIO_SIN_BIT); cbi(SPIDIGIO_SIN_DDR, SPIDIGIO_SIN_BIT); /* * Clock. Input data is shifted on the falling, output data on the * rising edge. */ sbi(SPIDIGIO_SCLK_PORT, SPIDIGIO_SCLK_BIT); sbi(SPIDIGIO_SCLK_DDR, SPIDIGIO_SCLK_BIT); /* * UCN5841 output strobe. Shift register data appears on the output * pins as long as this line is held high. */ sbi(SPIDIGIO_LDO_PORT, SPIDIGIO_LDO_BIT); sbi(SPIDIGIO_LDO_DDR, SPIDIGIO_LDO_BIT); /* * SN74HC165 input strobe. Data at input pins is latched in the shift * register on the falling edge. */ cbi(SPIDIGIO_LDI_PORT, SPIDIGIO_LDI_BIT); sbi(SPIDIGIO_LDI_DDR, SPIDIGIO_LDI_BIT); /* * Fill the shift register with zeros. Then shift in ones until the * first appears on the output. This gives us the total size plus one * of the shift register. */ CountDigitalShifts(32, 0, 0); total = CountDigitalShifts(32, 1, 0) - 1; /* * Determine the number of inputs. We do this five times for zeros * and ones and take the maximum count. This way we compensate * changing inputs while counting. */ *inputs = 0; for (i = 0; i < 5; i++) { if ((cnt = CountDigitalShifts(total, 0, 1)) > *inputs) { *inputs = cnt; } if ((cnt = CountDigitalShifts(total, 1, 1)) > *inputs) { *inputs = cnt; } } *outputs = total - *inputs; } /*@}*/