| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512 |
- /*
- * 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 <cfg/arch/avr.h>
- /*
- * The following header file contains the prototypes of
- * all global functions, which this module provides.
- */
- #include <dev/spidigio.h>
- /*
- * 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;
- }
- /*@}*/
|