0

UPDATE Clock speed code has been tweaked as per the suggestion from @timemage due to inability for Wire library to reduce to below 30304Hz. Unfortunately problems still persist: Nothing printed out to serial and LED working only after disconnecting and reconnecting the circuit(stops updating after reseting arduino)..

UPDATE Thanks for all replys. I have left the circuit the same and have used the following code where i have tried to reduce the i2c clock speed to 1khz but unfortunately having no luck.The EEPROM returns nothing and the pixel fails to work initially, only when i disconnect the circuit(this is simple because i am using a magnetic USB connector) it seems to work fine(until i restart the arduino). If the consensus is that i may have more luck if I alter the library then i may look into it although it sounds a bit out of my depth...

#include <Wire.h>

#define disk1 0x50    //Address of eeprom chip
unsigned int address0 = 0; //address to store the data

#include <Adafruit_NeoPixel.h>


//pixels pin
#define sda_pin A4
#define scl_pin A5

Adafruit_NeoPixel strip(1, sda_pin, NEO_GRB + NEO_KHZ800);

 
void setup() {

  Serial.begin(9600);

  Wire.begin();

  ////UPDATED CODE TO REDUCE I2C CLOCK SPEED TO 1KHZ////
  TWSR |= 0x03; // chose divide-by-64 prescaler
  TWBR  = 0x80; // closest to 1 kHz from below at 16 MHz with /64 prescaler.


  Serial.println(readEEPROM(disk1, address0), DEC);
  Wire.end();
  pinMode(scl_pin, INPUT);



  strip.begin();           // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.show();            // Turn OFF all pixels ASAP
  strip.setBrightness(100); // Set BRIGHTNESS to about 1/5 (max = 255)

}

void loop() {
  rainbow(10);
}




void rainbow(int wait) {

  static unsigned long timer = 0;
  static long firstPixelHue = 0;

  if (millis() - timer > wait) {
    timer = millis();

    for (int i = 0; i < strip.numPixels(); i++) { // For each pixel in strip...

      int pixelHue = firstPixelHue + (i * 65536L / strip.numPixels());

      strip.setPixelColor(i, strip.gamma32(strip.ColorHSV(pixelHue)));
    }
    strip.show(); // Update strip with new contents


    if (firstPixelHue < 5 * 65536) { //incrememnt
      firstPixelHue += 256;
    } else firstPixelHue = 0; //reset

  }
}




byte readEEPROM(int deviceaddress, unsigned int eeaddress )
{
  byte rdata = 0xFF;


  const uint8_t i2c_base_address = 0x50;
  const uint8_t upper_three_eeprom_adress_bits = (eeaddress >> 8) & 0x7;
  const uint8_t i2c_address = i2c_base_address | upper_three_eeprom_adress_bits;
  Wire.beginTransmission(i2c_address);
  Wire.write(eeaddress & 0xFF); // address LSB

  Wire.endTransmission();

  Wire.requestFrom(deviceaddress, 1);

  if (Wire.available()) rdata = Wire.read();

  return rdata;
}

ORIGINAL POST:

I have created a circuit which includes an AT24C16 EEPROM which I only need to read from and an addressable LED (WS2812 IC), which I will be talking to (Using an Arduino Nano).

My original intention was to attach the LED's DIN pin to either SCL or SDA. I thought that since I only needed to read the EEPROM once (and then control the LED), I would be able to share the connection. After creating the circuit, it proved an incorrect assumption.

I am limited to 4 (nets?) total due to the adapter I am using between the Arduino Nano and the rest of the circuit (repurposed magnetic 4 wire USB cable). Could anyone propose a setup which would allow me with only these 4 connections, to read from the EEPROM and write to the LED? As I mentioned, I only need a single read from the EEPROM to identify it and then I will only be writing to the pixel. Many thanks!

Attached is the schematic.

circuit

11
  • The problem may be that the input impedance of the WS2812 is low enough, as to pull the weak 35kOhm pull-up resistor below the voltage needed for the EEPROM to read as HIGH. I'd first give it another go with proper value pull-up resistors for I2C. Something as low as a 1kOhm. After that you could try adding a buffer circuit to the WS2812s input.
    – Gerben
    Commented Feb 1, 2021 at 15:58
  • It seems like you have perfectly good answers already, but I am curious as to what you meant by "it proved an incorrect assumption." Presumably, you did something that served as proof. What exactly was that?
    – timemage
    Commented Feb 1, 2021 at 17:20
  • the circuit worked(could read the EEPROM) and then i added the LED and it stopped working(could no longer read EEPROM or control LED) Commented Feb 1, 2021 at 18:27
  • ps. thanks for all these replys, very interesting, ive not been able to test today but will get back to you as soon as i have) Commented Feb 1, 2021 at 18:31
  • Try the setClock fix suggestion in my "answer" and if that doesn't make one of the proper answers work for you.
    – timemage
    Commented Feb 5, 2021 at 2:01

5 Answers 5

1

My working details, per-request

Setup

So, the setup is the same as you what you have in its essentials:

  • The code is your code with the near-1 kHz tweak. That said, I believe I also tested it at the default rate and that worked as well. In any case, the images that show the LED changing were taken while that 1 kHz adjustment was made.

  • The UNO and Nano are functionally identical for this. They use different serial transceivers which has nothing to do with anything. The Nano will typically have a lower "5V" than the UNO when USB powered owing to the Nano using a diode where the UNO has a FET. Which is unlikely to matter either. They're otherwise the same device so far as the setup goes.

  • The 24LC02B uses the same pinout and I2C address and command-sequences as your AT24C16. It is a smaller memory, not that it makes a difference; we're reading from address 0. I did not find I necessary to add external pull-up resistors and it worked, so it seemed unnecessary to add them to demonstrate that it can work since adding them should only improve it's chances for working correctly.

  • The LEDs are the Adafruit ADA1643 12 Neopixel Ring. We're both using the first LED of a string, doesn't really matter how long the are. We're only lighting one "pixel" so I just powered it from USB. Have no idea what you're doing there. But for one LED lit, I doubt a difference there would matter much.

  • You'll see a resistor connecting SDA at the EEPROM to IN on the addressable LED(s). I put that there in case by some chance the EEPROM could drive SDA low while the addressable LED code was driving the LED data line high. while. I doubt that can happen, but it was a small thing to do. For what it's worth, I did run it briefly without it in series, and it also worked fine that way as well. I'm only telling you this so you have all the information and not because I think it matters; I don't.

Running

It prints out 72. This is the first value in my EEPROM. The 'H' of "Hello world" basically. And then it goes on to cycle the first addressable LED of the set through all of the colors.

Configuration Pictures

Overall setup Top-down view of breadboard Breadboard close-ups Colors

2
  • OK, thanks again for taking the time to do this, i am indebted to you! I tried your exact setup and....still didnt work! But....on a whim i decided to change the LED, and it worked! It turns out that i have 2 addressable LED strips, one is a 60 led / meter strip and the other 30 led / meter strip. the 60/m ones work and the 30/m ones dont! how flipping weird(and frustrating) is that!? so, this is now sloved, albeit in a rather convoluted fashion. Do you have any suggestions on how i should update this in the most helpful way? should i create another answer or is it a bit too rough? Commented Feb 7, 2021 at 10:45
  • Maybe this is all you're seeing.
    – timemage
    Commented Feb 7, 2021 at 12:18
0

The AT24C16 is an I2C bus device but the WS2812 is not. If you can tolerate spurious outputs on the WS2812 LEDs, you can read the AT24C16 using I2C by addressing the device properly. The WS2812 will "listen" and may display things as that traffic is flowing past it.

When you are finished with the AT24C16 you will probably need to disable the I2C controller (assuming you are using that) and then hold the SCL line HIGH and then communicate with the WS2812 using the SDA line only. The AT24C16 will ignore this non-I2C traffic because the SCL line is static a this point.

1
  • my initial thinking was that there may be some 'spurious outputs' from the led while the EEPROM is being read but since it only takes a short time it would be ok. unfortunately the EEPROM fails to read when the pixel is connected as per the schematic. Commented Feb 4, 2021 at 17:49
0

This is just a hacky idea how you could solve this.

Decrease the I2C frequency below 10kHz or - as you only want to read the eeprom once at start - even down to 1kHz. This way the WS2812 will see a series of low pulses with a duration >50us which it should take as a reset signal. Practically, the WS2812 will ignore such a signal.

But there's a different problem: the EEPROM will write to the bus occasionally whenever it receives something that looks like its address. This in turn can

  • interfere with WS2812 communication.
  • damage the EEPROM's I2C interface if you control the WS2812 with push/pull signal (by setting gpios HIGH/LOW).

It would be safe to drive the WS2812s with an open drain signal (switching between LOW and INPUT), but that might require modifications to the WS2812 library. In any case you should use the SDA line to control the WS2812 and there must be no signal on SCL while controlling the light (the EEPROM won't communicate if it receives no clock signal). The easiest might be to deactivate I2C entirely once you don't need it anymore and explicitly set the SCL pin to input mode. You then should be fine using SDA in normal push/pull mode and the library should work just as normal.

1
  • hi there, thanks very much for this,I have updated my question in response. Commented Feb 4, 2021 at 17:47
0

This won't work with 24Cxx EEPROMS.
The I2C bus works this way:

  • Master writes out an address.
  • Slave takes over the SDA line and pulls it high or low to indicate an ACK or NAK.
  • Master writes out a command for reading from an address.
  • Again Slave takes over SDA and ACKs the command.
  • Master is continuing to pulse SCL while Slave puts out data to SDA.
  • After every byte (or word) Master has to take over SDA to confirm the data has been received.

So there is no way to tell the EEPROM to just vomit all its data to a bus without someone actively listening and ACK-ing. Which in turn changes the data on the bus.


It might work with 25Cxx EEPROMS.

You would need something different which has a much simpler communication scheme.
An 25Cxx which uses SPI for example, supports reading in a sequence, without constant ACK-ing. You will have to just initialize the read and keep clocking on SCK. No back and forth. The data will be output to MISO in sequence.

But then still you will have to get the timings right. WS2812 are not very forgiving in that regard. That is why most people use libraries that have handcrafted Assembler code in them to get the timinigs right.

And I know what I am talking about as I wrote some myself (not suitable for anyone but me).
https://kwasi-ich.de/blog/2019/11/15/pwm_rider/

3
  • "Won't work with 24Cxx EEPROMS" - I think it does as I wrote in my answer
    – Sim Son
    Commented Feb 2, 2021 at 14:07
  • "the EEPROM to just vomit all its data to a bus without someone actively listening and ACK-ing" - no one will ACK here, WS2812 are no I2C devices and other I2C devices won't ACK if they don't get addressed
    – Sim Son
    Commented Feb 2, 2021 at 14:09
  • Yes, I misunderstood the intention. I thought the data for the WS2812 should be comming from the EEPROM directly via SDA. MCU telling EEPROM to output its data to the bus where WS2812 would listen.
    – Kwasmich
    Commented Feb 2, 2021 at 14:13
0

setClock(1000) Problem

This is sort of answer material, but it's meant to be supplementary to the other answers that more directly address your question and quite well, I think, though I'm unable to test right now. So if you find this helpful, vote it up, but mark one of the others accepted if this tweak makes it work.

Specifically to address this content in your update:

I have tried to reduce the I2C clock speed to 1khz but unfortunately having no luck.

And your line:

Wire.setClock(1000);

I was curious to know exactly how setClock maps its argument onto the values of clock rate generating parameters in the hardware registers and whether or not 1 kHz might not be too slow for an AVR running at 16 MHz. That is, perhaps the lower bound might be say 50 kHz, at which I'd've suggested a bit-banging I2C library in a comment to your question; however that's not the case.

In investigating this, I found that setClock fails spectacularly to do anything sensible in the AVR Arduino core library if given an argument of below 30304. Requested rates below this figure wind up yielding rates much higher than this owing to how they're calculating the clock rate with the TWI/I2C/"Wire" prescaler (TWPS1 and TWPS0 in TWSR) and the bit-rate register (TWBR).

I dug in to see what causes setClock to behave poorly for low rates, and there's not a lot to it. The I2C/Wire prescaler is set to divide-by-1 and left that way. setClock calls twi_setFrequency, which calculates TWBR using this expression:

TWBR = ((F_CPU / frequency) - 16) / 2;

At 30304 Hz and above, this generates something within 1% of the desired frequency. Along the x-axis is the requested rate. The y-axis shows the effective rate. Unsurprisingly, roughly linear. 30304 Hz and above

But below 30304 Hz, things go sideways. Values of 30303 or less when fed into that expression try to calculate a TWBR of 256 or greater. But, it's an 8-bit register, so it rolls over. To prevent that a different prescaler would need to be selected. But, it isn't and the following craziness results: Below 30304 Hz

What setClock(1000) is actually doing in your code is setting a rate of 125 kHz; I have verified this on a scope.

Suggestion

You can get an approximation of a 1 kHz clock rate by manually setting the hardware registers yourself, replacing your setClock line with the following:

TWSR |= 0x03; // chose divide-by-64 prescaler
TWBR  = 0x80; // closest to 1 kHz from below at 16 MHz with /64 prescaler.

This should give you a TWI/I2C rate of about 975.61 Hz. In the datasheet for the ATMega328P you'll see this is selecting the divide-by-64 prescaler, and taking 128 ticks of that. You can find the equation in the datasheet, that if you fill these values and your UNO clock rate into, gives the expression 16e6 / (16 + 2 * 0x80 * 64), which gives you the 975.61 figure above. I've verified that it is approximately that on a scope.

Give that a try. See if when you do, whether or not one of the answers doesn't work for you. I expect one will.

1
  • Thanks again for such a thorough investigation, its enormously appreciated. Unfortunately the problem still persists, I have updated my question in response. Commented Feb 5, 2021 at 9:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.