Go Down

Topic: Using 595 shift register with I2C (Read 493 times) previous topic - next topic

winterwurst

Hello,

I'm trying to send serial data to a SN74LV595AD shift register using the i2c interface of the arduino. I connected SCL to latch and SDA to data like in picture attached. Using Wire.h for i2c I'm unsure what address to use for the shift register in beginTransmission(), as there is none provided in the data sheet, nor can I seem to find anything related on the internet.

I can not use SPI for this, since I already intend to use it for something else and shiftOut is too slow.

Is there a way to send bytes to the LV595 over the i2c bus?

Thanks!

holmes4


vaj4088

There may be a way to fake this, but faking ACK (or NACK) is going to be tricky.

dougp

If you must use I2C one option is a MCP23017.  IIRC there's also an 8-bit version under a different part number.
So two neutrinos went into a bar.  Nothing happened.  They were just passing through.

johnwasser

SPI can be used for more than one device.  They just need separate SlaveSelect pins and any digital output pin will do.
Send Bitcoin tips to: 1G2qoGwMRXx8az71DVP1E81jShxtbSh5Hp

vaj4088

Unfortunately, the SN74LV595AD shift register does not have a matching SlaveSelect pin  You or I might be tempted to use the RCLK pin, but I am uncertain that the OP "gets" it.

bperrybap

#6
May 19, 2018, 05:16 pm Last Edit: May 19, 2018, 05:18 pm by bperrybap
You need to use a chip that actually supports i2c.
While you could use the MCP23017 or the MCP23008 (8 bit version), I wouldn't.
I would use the PCF8574. Due to the way it works, it will always be faster than the MCP parts at a given i2c clock speed.

The cheapest and easiest solution is to buy an i2c LCD backpack which has a PCF8574 on it.
They are available on ebay for less than $1 USD shipped to your door.
--- bill

winterwurst

Thanks for the answers.

Unfortunately, the SN74LV595AD shift register does not have a matching SlaveSelect pin  You or I might be tempted to use the RCLK pin, but I am uncertain that the OP "gets" it.
I actually used SPI with the shift register before and wired up latch to the SS pin which made it work quiet nice. It's just that I now use SPI for my SD card. Today I tried using a Software SPI Library, but it's just too slow. For what I have in mind I need to be able to output data to a R2R ladder DAC at a rate of 44.1 kHz.

You need to use a chip that actually supports i2c.
While you could use the MCP23017 or the MCP23008 (8 bit version), I wouldn't.
I would use the PCF8574. Due to the way it works, it will always be faster than the MCP parts at a given i2c clock speed.

The cheapest and easiest solution is to buy an i2c LCD backpack which has a PCF8574 on it.
They are available on ebay for less than $1 USD shipped to your door.
--- bill
This sounds very promising. Since I intend to use a LCD in my application anyways: Do you think it would be smarter to use the PCF8574 extender of the backpack to replace it with my shift register and feed my R2R ladder with, or should I use the backpack with the LCD directly so I can hook it up via I2C and feed my R2R ladder by using PORTD output of the arduino instead? I think both could work here, but I'm wondering which solution might be faster, because reading an SD, output the data at the sample frequency and simultaneously operate a LCD might get a bit much for my arduino to handle? (trying to build some kind of an music player)

Thanks!

bperrybap

I don't fully understand your project or where you are going.

If you want fast i/o, then it depends on which Arduino board you are using and how you are controlling the pins.

The AVR  based boards that use the m328 or the mega 2560 use the Arduino IDE bundled AVR core.
That core is extremely slow with pin i/o functions like digitalRead(), digitalWrite() due to the way they implemented the code.
Other cores, like the Teensy core are MUCH faster like 40X faster
Then there are other processors like the pic32, esp8266, STM32, arm, etc... that are MUCH faster than the AVR and also have better core code so pin i/o control is much faster when using the standard digitalRead()/digitalWrite() functions.

When it comes to LCD control, how the library talks to the LCD can make a big difference in overall performance/speed.
For example, when using an AVR m328 with the bundled LiquidCrystal library, even though it is using direct pin control, it is slower than using my hd44780 library with an i2c backpack. This is because of combination of things.
But it is mainly due to the poor/slow digitalWrite() code in the IDE bundled AVR core, and that my hd44780 library is smarter in how it talks to the LCD.

--- bill




DKWatson

#9
May 23, 2018, 12:44 pm Last Edit: May 23, 2018, 12:46 pm by DKWatson
I played around with this a bit since last night. I use the 595 quite a bit and have always used the shiftOut function.
Code: [Select]
void shiftOut(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder, uint8_t val)
{
      uint8_t i;

      for (i = 0; i < 8; i++)  {
            if (bitOrder == LSBFIRST)
                  digitalWrite(dataPin, !!(val & (1 << i)));
            else     
                  digitalWrite(dataPin, !!(val & (1 << (7 - i))));
                 
            digitalWrite(clockPin, HIGH);
            digitalWrite(clockPin, LOW);           
      }
}

This is the Arduino code which is slow because of the digitalWrite calls, so speed it up and you can try this:
Code: [Select]
/*
This runs at full clock speed. If it needs to slow down, uncomment the
delay and substitute xxx with a value. This also requires that the
clock pin and the latch pin are on the same port as the data pin. If
no latching is required, just make the latchPin some useless number
like 255 - be careful not to use an actual pin number.

On the Uno, 100,000 iterations takes 1,930,452us = 19.3us/shift
This is a shift and latch equal to 51.8 kBps or 412 kbps
*/

void shift_out( volatile uint8_t *port,
        uint8_t dataPin,
uint8_t clockPin,
uint8_t latchPin,
uint8_t bitOrder,
uint8_t val)
{
uint8_t i, bit;
 
for (i = 0; i < 8; i++) 
{
if (bitOrder == LSBFIRST) // get the bit to shift based on order
bit = !!(val & (1 << i));
else
bit = !!(val & (1 << (7 - i)));

bit?(*port |= (1 << dataPin)):(*port &= ~(1 << dataPin));    // shift out

*port |= (1 << clockPin); // toggle the clock pin
*port &= ~(1 << clockPin);
//_delay_us(xxx);
}
*port |= (1 << latchPin); //toggle the latch pin
*port &= ~(1 << latchPin);
}
Live as if you were to die tomorrow. Learn as if you were to live forever. - Mahatma Gandhi

vaj4088

In two places, I see !!.  What does that accomplish?

bperrybap

#11
May 23, 2018, 05:25 pm Last Edit: May 23, 2018, 07:57 pm by bperrybap
The code in shiftOut() is awful.
Not because of it using digitalWrite() [which is the source of quite a bit of overhead] but because of the way it is actually written. i.e. it is a bad design.

Doing things in the critical real-time path kills performance and the way that code was written, it does everything in the real-time path.

For example, the code should test for the shift direction once up front and then split off into two loops rather than doing that test on every single bit shift.

The code also runtime calculates bit positions inside the loop based on bit position and direction which is due to the decision mentioned above.
This adds lots of unnecessary overhead.
The current code is also abusing the digitalWrite() interface which states that the value parameter argument is either HIGH or LOW, not 1 or 0, or zero, non zero, or true or false.

Regardless of how the pin is actually manipulated (which will be faster by using indirect port i/o which is what is in the proposed shift_out() code), those things mentioned above should have been corrected long ago.

i.e. it is much faster to split out the code into two loops that know the direction of the shift inside the loop,
then shift the actual value a single bit position and then AND the shifted value with a constant value like 1 or 0x80 depending on the direction being shifted rather than AND the original value with a calculated bit mask for each bit position.


Eliminating the runtime calculations will benefit the proposed shift_out() code as well; however there is a major issue with the proposed code.
The proposed code is using indirect port i/o and is using non atomic operations to modify the port register contents. If any pin inside that port is also being used by other code such as a library that modifies the port register contents in an ISR, then that shift_out() code will cause corruption of the port register.
It isn't the ISR that creates the register corruption, it is the shift_out() code because it is failing to ensure that it modifies the port register atomically.

--- bill


DKWatson

As Bill has suggested, to protect yourself from yourself and for atomicity on a uniprocessor platform, when entering your function disable interrupts. You can then read and push the register contents onto a stack, perform the function, pop and restore the registers, then enable interrupts.
Live as if you were to die tomorrow. Learn as if you were to live forever. - Mahatma Gandhi

winterwurst

Hi guys,

thanks a lot for your elaborate answers!

The idea of an optimized shiftOut ist great. I combined both of your suggestions (DKWatson and bill) and this is what I ended up with.

Code: [Select]
shift_out (volatile uint8_t *port,
uint8_t dataPin,
uint8_t clockPin,
uint8_t latchPin,
uint8_t Bitorder,
uint8_t val)
{
uint8_t i, bit, j;

//cli(); // disable timers

if(*port == PORTB) j = 8;
if(*port == PORTC) j = 14;
if(*port == PORTD) j = 0;


if(Bitorder == MSBFIRST)
{
val = val >> 1;
for (i = 0; i < 8; i++) 
{
val = val << 1;
bit = !!(val & 0x80);
bit?(*port |= (1 << (dataPin - j))):(*port &= ~(1 <<(dataPin - j)));    // shift out

*port |= (1 << (clockPin - j)); // toggle the clock pin
*port &= ~(1 << (clockPin - j));
//_delay_us(xxx);
}
}
else
{
val = val << 1;
for (i = 0; i < 8; i++) 
{
val = val >> 1;
bit = !!(val & 0x01);
bit?(*port |= (1 << (dataPin - j))):(*port &= ~(1 <<(dataPin - j)));    // shift out

*port |= (1 << (clockPin - j)); // toggle the clock pin
*port &= ~(1 << (clockPin - j));
//_delay_us(xxx);
}
}
*port |= (1 << (latchPin - j)); //toggle the latch pin
*port &= ~(1 << (latchPin - j));

sei(); // allow timers


Went to my local university and the oscilloscope shows me an output frequency of >110kHz for 8 Bit shifting, meaning 110kByte/s! That's more than I could have hoped for, since it also works perfectly fine with my shift register and so I won't need to replace any components.

There are two things though that didn't work for me. The first one is not so important, but I thought i mention it anyway. As you can see I had to use the j parameter to adjust for the given port. For example if *port = PORTB and dataPin = D8

Code: [Select]
*port |= (1 << dataPin)
will be equal to *port |= 10000000 with PORTB only having 6 Pins. D8 is the first pin of PORTB so the j parameter helps to make it equal to *port |= 1. Maybe I understand something wrong here(?), but this helped me solve the this issue.

Eliminating the runtime calculations will benefit the proposed shift_out() code as well; however there is a major issue with the proposed code.
The proposed code is using indirect port i/o and is using non atomic operations to modify the port register contents. If any pin inside that port is also being used by other code such as a library that modifies the port register contents in an ISR, then that shift_out() code will cause corruption of the port register.
when entering your function disable interrupts. You can then read and push the register contents onto a stack, perform the function, pop and restore the registers, then enable interrupts.
Also I struggle with the realization of this. When I uncomment cli(); at the beginning of my function to disable interrupts, Portmanipulation is no longer possible and my voltmeter shows me around 2.47 V for the dataPin permanently.

Thanks for your time, it helped me a lot already.


bperrybap

#14
May 26, 2018, 12:19 am Last Edit: May 26, 2018, 12:22 am by bperrybap
That doesn't look like what I suggested as that is still doing lots of runtime calculations on bit masks.
If you want fast, you can't do that. You should calculate as much as you can up front so it is only done once.
You should check for a constant bit in the value (either bit 7 or 0) and then set the data pin either high or low based on that.
Then shift out the data bit in the shift register by bumping the clock pin.

I'm also not sure how that code can work as you shift the data the opposite direction before you start looping which will cause you to loose a bit on the end.
Also, this may not be doing what you might think:
Code: [Select]

 if(*port == PORTB) j = 8;
 if(*port == PORTC) j = 14;
 if(*port == PORTD) j = 0;

These are comparing the contents of two port registers vs the addresses of the port registers.
That is not a good way of checking since it takes more overhead to actually the read the contents and the compare them vs just comparing the addresses. Also, the contents of the registers could be changing if they are attached to external signal inputs so the tests might actually fail.

Compares like that should compare the address of the port register vs the contents of the port register.
i.e.
Code: [Select]
if(port == &PORTB) j = 8;
etc...



Here is some sample code that I did from an LCD library. (It is from fm's newLiquidCrystal library)
It is extremely fast as it optimizes down to minimal instructions.
It uses the AVR libC ATOMIC block code for the atomic register updates, while allow interrupts to sneak in between bits should they occur.
This offers fast output while minimizing ISR latency.

This code was further optimized by using some storage in the device object to precalculate some of the register pointers and bits like the clock port and clock bit, and data register and data bit.
(The actual module can be found here: https://bitbucket.org/fmalpartida/new-liquidcrystal/src/integration/FastIO.cpp )


Code: [Select]
void fio_shiftOut (fio_register dataRegister, fio_bit dataBit,
                   fio_register clockRegister, fio_bit clockBit,
                   uint8_t value, uint8_t bitOrder)
{
  int8_t i;

  if(bitOrder == LSBFIRST)
  {
    for(i = 0; i < 8; i++)
    {
      ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
      {
        if(value & 1)
        {
          fio_digitalWrite_HIGH (dataRegister, dataBit);
        }
        else
        {
          fio_digitalWrite_LOW (dataRegister, dataBit);
        }
        value >>= 1;
        fio_digitalWrite_HIGH (clockRegister, clockBit);
        fio_digitalWrite_LOW (clockRegister, clockBit);
      }
    }

  }
  else
  {
    for(i = 0; i < 8; i++)
    {
      ATOMIC_BLOCK (ATOMIC_RESTORESTATE)
      {
        if(value & 0x80)
        {
          fio_digitalWrite_HIGH (dataRegister, dataBit);
        }
        else
        {
          fio_digitalWrite_LOW (dataRegister, dataBit);
        }
        value <<= 1;
        fio_digitalWrite_HIGH (clockRegister, clockBit);
        fio_digitalWrite_LOW (clockRegister, clockBit);
      }
    }
  }
}


Note that there is no runtime bit mask calculations in the loop.
A single bit in the value is tested, and the Arduino pin connected to the data pin of the shift register is set to high or low.
Then the clock is strobed.

All the register locations and bits within the registers are pre-calculated.
In this case they are done prior to calling fio_shiftOut() for maximum performance but you also can do it up front in the actual shiftout() function and still achieve a large win.

--- bill

Go Up