2

I'm using an Arduino Mega to control a CS1237 ADC. I'm sending a signal to the clock pin and after each clock pulse, waiting 1ms and then reading the response, according to the datasheet I found (via https://github.com/SiBangkotan/CS1237-ADC-cpp-library). This seems to be working in some capacity, because when I do Serial.println() for each bit received, and for the resulting dataword, I get a 24 bit dataword that matches the 24 separate bits I got. However, when I take out the extra debugging uses of Serial.println() that print each bit as they are received, I get a different dataword as well. It's 20 bits of all 1's every single time, instead of 24 bits of various 1's and 0s. I cant figure out why this extra debugging output in the serial communication channel should change the dataword that comes into the serial monitor?

Here's my setup and pre-setup code:

// Using pins 2 and 3 on the Arduino, since 0 and 1 are used to talk to the USB port.
int ndrdy = 2;
int clck = 3;

// the setup routine runs once when you press reset:
void setup() {
    // initialize serial communication at 9600 bits per second:
    Serial.begin(9600);
    while (!Serial) {
        ; // wait for serial port to connect. Needed for native USB port only
    }
    // make the drdy's pin an input and clock an output:
    pinMode(ndrdy, INPUT);
}

Here's the relevant code:

void loop() {
  // Hacky way of waiting for the signal that !DRDY is ready.
  while(digitalRead(ndrdy) == LOW) {
    // do nothing until pin pulls high.
  }
  while(digitalRead(ndrdy) == HIGH) {
    // keep doing nothing until pin goes low again.
  }
  // now data is ready, we can read

  long dataword = 0;

  for(int i = 0; i < 24; i++) {
    digitalWrite(clck, HIGH);
    delayMicroseconds(1);
    digitalWrite(clck, LOW);
    int new_bit = digitalRead(ndrdy);
    dataword <<= 1;       // shift everything one place to the left
    dataword |= new_bit;  // add the new bit to the newly empty place
  }

  // There's a total of 27 bits but we don't care about the last 3.
  // Write HIGH 3 times to flush it out.
  for (int i = 0; i < 3; i++) {
    digitalWrite(clck, HIGH);
    delayMicroseconds(1);
    digitalWrite(clck, LOW);
  }

  // Send out the data to the USB serial out:
  Serial.println(dataword, BIN);
}

The output of this in serial monitor is

13:44:45.685 -> 11111111111111111111
13:44:45.685 -> 11111111111111111111
13:44:45.718 -> 11111111111111111111
13:44:45.751 -> 11111111111111111111
13:44:45.751 -> 11111111111111111111
13:44:45.785 -> 11111111111111111111
13:44:45.818 -> 111111111111111111111
13:44:45.852 -> 11111111111111111111
13:44:45.852 -> 11111111111111111111
13:44:45.885 -> 11111111111111111111
13:44:45.918 -> 111111111111111111111
13:44:45.918 -> 11111111111111111111
13:44:45.951 -> 11111111111111111111

...and so forth. However, when I add an extra Serial.println(new_bit); just before the closing bracket of the for(int i = 0; i < 24; i++) loop, I get output like this in the Arduino IDE's serial monitor (shown with timestamps turned on):

14:41:19.992 -> 0
14:41:19.992 -> 1
14:41:19.992 -> 1
14:41:19.992 -> 1
14:41:19.992 -> 1
14:41:19.992 -> 1
14:41:19.992 -> 1
14:41:19.992 -> 1
14:41:19.992 -> 1
14:41:19.992 -> 0
14:41:19.992 -> 1
14:41:20.025 -> 1
14:41:20.025 -> 1
14:41:20.025 -> 1
14:41:20.025 -> 1
14:41:20.025 -> 0
14:41:20.025 -> 0
14:41:20.025 -> 1
14:41:20.025 -> 1
14:41:20.025 -> 1
14:41:20.025 -> 1
14:41:20.025 -> 1
14:41:20.058 -> 0
14:41:20.058 -> 1
14:41:20.058 -> 11111111011111001111101
14:41:20.091 -> 0
14:41:20.091 -> 1
14:41:20.091 -> 1
14:41:20.091 -> 1
14:41:20.091 -> 1
14:41:20.091 -> 1
14:41:20.091 -> 1
14:41:20.091 -> 1
14:41:20.091 -> 1
14:41:20.091 -> 0
14:41:20.125 -> 1
14:41:20.125 -> 1
14:41:20.125 -> 1
14:41:20.125 -> 1
14:41:20.125 -> 1
14:41:20.125 -> 0
14:41:20.125 -> 0
14:41:20.125 -> 1
14:41:20.125 -> 1
14:41:20.125 -> 1
14:41:20.125 -> 1
14:41:20.158 -> 1
14:41:20.158 -> 0
14:41:20.158 -> 1
14:41:20.158 -> 11111111011111001111101

This doesn't happen if I'm Serial.println()-ing anything other than the new_bit on that line, eg if I do Serial.println(dataword); or if I introduce a small delay instead of doing the serial print. In those cases, it still does the twenty 1's output. I can't figure out what is wrong with the serial communication, since it seems like reading from the ADC is going OK. If I introduce a delay of 5000us or more, then there is a change in the contents of dataword, which seems to then become a function of the length of the delay. I.e. the content of dataword is constant for each delay length (5000us, 6000us, 10000us, and 20000us are what I tried). If the delay is long enough, it goes back to being all 1's.

  • What happens when instead of putting Serial.println(new_bit) just before the closing bracket of the for() loop, you put a small delay? – ocrdu Nov 12 '20 at 13:06
  • For your bitbanging the `clck` signal you use `delayMicroseconds(1)` and a `digitalRead` as timing. The last 3 bits are missing a timing for the LOW phase of clck.Is that OK and as intended? – datafiddler Nov 12 '20 at 13:19
  • @ocrdu Changing the `Serial.println(new_bit);` to has no effect; it's the same as if there is nothing there. Changing the `Serial.println(new_bit);` to `Serial.println(new_bit);` is the same as just deleting that line. @datafiddler it shouldn't matter how fast the `clck` signal is sent as long as I don't send another until after I read the data from the first one, AFAICT. This 1us delay puts me inside the max clock speed in the datasheet. – the_pie_in_the_sky_is_a_lie Nov 12 '20 at 13:36
  • The Serial.println() that makes it work introduces a delay. You very probably need a delay on both HIGH and LOW, and you only have one now. Did you try it? – ocrdu Nov 12 '20 at 13:41
  • @ocrdu Yes I tried introducing a delay. It did not have any effect. I made some weird copy/paste error in my previous comment and didn't notice it in time to edit. But yes I did try changing `Serial.println(new_bit);` to `delayMicroseconds(1);` and it was the same as if I'd simply deleted `Serial.println(new_bit);`and not replaced with anything. – the_pie_in_the_sky_is_a_lie Nov 12 '20 at 13:48
  • And a longer delay, in both places? – ocrdu Nov 12 '20 at 13:49
  • @ocrdu I tried it also with `delayMicroseconds(10);` in both places and it is the same as with `delayMicroseconds(1);` – the_pie_in_the_sky_is_a_lie Nov 12 '20 at 13:53
  • And did you do that in both for loops? Which for loop is the one where inserting the Serial.println(new_bit); makes things work? – ocrdu Nov 12 '20 at 13:57
  • It should also be noted that if I `Serial.println(dataword);` each time I get a new bit, it gives the twenty 1's output, whereas if I `Serial.println(new_bit);` in the exact same spot in the code, it gives the twenty-four variegated 1's and 0's ouput. – the_pie_in_the_sky_is_a_lie Nov 12 '20 at 13:57
  • @ocrdu By "the for loop" I meant the `for(int i = 0; i < 24; i++)` loop. – the_pie_in_the_sky_is_a_lie Nov 12 '20 at 13:58
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/224469/discussion-between-the-pie-in-the-sky-is-a-lie-and-ocrdu). – the_pie_in_the_sky_is_a_lie Nov 12 '20 at 14:08
  • Hev you tried `dataword |= (new_bit & 1);` ?? – Michaël Roy Nov 13 '20 at 03:44
  • @MichaëlRoy Tried it. No effect. – the_pie_in_the_sky_is_a_lie Nov 13 '20 at 06:18
  • @TomServo No need to be derisive. I am new to this. If you have a suggestion of something I can improve or troubleshoot please share it. – the_pie_in_the_sky_is_a_lie Nov 13 '20 at 06:19

1 Answers1

2

From looking at the datasheet...

First when the chip boots... Al pins are input by default. You do not set your clock pin mode, so you have no clock. Also, the ADC could take up to 300 milliseconds to wake up. That is part of your boot sequence, the chip should be ready when you exit setup(). You may also include setting of any ADC registers in setup() as well. See datasheet startup sequence @ figures 5 and 6.

Also, if you want to try lower clock speeds, do not leave clck high longer than 100us

From datasheet, 2.5: "When SCLK goes from low to high and stays high for more than 100μs, the CS1237 entersPowerDown mode, which consumes less than 0.1μA. When SCLK goes back low, the chip will resume normal operation."

void setup() 
{
    // initialize serial communication at 9600 bits per second:
    Serial.begin(9600);
    while (!Serial) {}
    
    // make the drdy's pin an input and clock an output:
    // remove pullup on ndrdy
    digitalWrite(ndrdy, LOW);
    pinMode(ndrdy, INPUT);

    digitalWrite(clck, LOW);
    pinMode(clck, OUTPUT);

    // wait for ADC to end its own boot sequence.
    while (digitalRead(ndrdy)) {}
    while (!digitalRead(ndrdy)) {}
}

The chart "figure 7" of the datasheet says:

Wait until /DRDY is low, wait for duration t4 (which is 0), so no wait is OK, then loop for each bit:

  • set clock high
  • wait at least for duration t6 (455 ns)
  • read input bit.
  • set clock low.
  • clock must be held low for least duration t5 (455 ns) before next clock.

You could read the data bit while the clock is low, but note how on database fig. 8, the 27th bit becomes invalid as soon as the clock bit goes low. From experience, this hints that you are expected to read while clock is high. Some datasheets are very tricky to read, and some are even wrong. That's how I interpret this one, but I may be wrong, you may want to also try reading while clock is high.

Your input procedure then becomes:

// reads a 24 bit value from ADC, returns -1 if no data to read 
// note that this function does not wait, so your other processing 
// can still be responsive. 
long readADC() 
{
    // check if data is ready. 
    if (digitalRead(ndrdy))
        return -1;    

    long result = 0;

    // read 24 bits.
    for (int i = 0; i < 24; i++) 
    {
        // get ADC to output a bit.
        digitalWrite(clck, HIGH);
        delayMicroseconds(1);      

        // read it
        int new_bit = digitalRead(ndrdy);

        digitalWrite(clck, LOW);

        delayMicroseconds(1);      // this delay could be shorter, because of 
                                   // operations immediately taking some
                                   // time...  You may want to time it
                                   // using a scope, at least for the fun
                                   // of it.  On a slow 8-bit ATMega, it may not
                                   // be needed, there are move than 16 cycles
                                   // of processing below. plus 2 cycles for
                                   // jumping back to top of loop.
                                   // IS needed for sure at clock speeds above
                                   // 16 MHz.
        result <<= 1;
        result |= new_bit;
    }

    // emit 3 more clock cycles.
    for (int i = 0; i < 3; i++) 
    {
        digitalWrite(clck, HIGH);
        delayMicroseconds(1);      
        digitalWrite(clck, LOW);
        delayMicroseconds(1);
    }

    // note that the 27th clock cycle has set /DRDY high.
    // There is never any need to wait on /DRDY going high.

    return result;  // mask unwanted bits.
}


void loop()
{
    // ...

    long adcValue = readADC();

    if (adcValue >= 0)
    {
       // process ADC input
       Serial.print("ADC reading: ");
       Serial.print(adcValue);
       Serial.print(" (");
       Serial.print(adcValue, BIN);
       Serial.println(")");
    }

    // ...
}

Once you have this running smoothly, you can try and make reading a bit faster by making your own 455ns delay function, using no ops

#define NOOP() __asm__("nop\n\t")  // 1 operation cycle delay, for 8-bit ATMega, 
                                   // 1 op cycle == 1 clock cycle.
                       

The actual delay will depend on your clock speed. Usually, these are implemented using macros.

Example, in a multi-line macro. Note the backslash at the end of line. These should be the very last character of the line, and there should not be any blank lines in the macro

  // 500 ns delay @ 16MHz clock, on an 8-bit ATMega.
  #define NOOP() __asm__("nop\n\t")
  #define DELAY_500ns()   NOOP(); NOOP(); NOOP(); NOOP(); \ 
                          NOOP(); NOOP(); NOOP(); NOOP(); 
Michaël Roy
  • 6,338
  • 1
  • 15
  • 19
  • Ah, I had read the data sheet differently. This makes sense, and I tried it this way, but even running the code exactly as you have written, it still gives the all 1's output to the serial monitor. Am I understanding right that instead of the Arduino waiting, the noop's make the ADC itself wait? – the_pie_in_the_sky_is_a_lie Nov 13 '20 at 09:03
  • 1
    I did make some changes recently to setup(). – Michaël Roy Nov 13 '20 at 09:04
  • The no ops are no operation cycles on the arduino, they simply aste one operation cycle If you have a scope, you should connect it to the clock and data lines and check for activity there. – Michaël Roy Nov 13 '20 at 09:11
  • Ah, brilliant! That did it! One question. I don't understand what writing to the ndrdy pin does? What does it mean, "remove pullup on ndrdy"? – the_pie_in_the_sky_is_a_lie Nov 13 '20 at 09:17
  • 1
    When using the pins in INPUT_PULLUP mode, doing digitalWrite(xx, HIGH) activates the pullup. The pin is not in INPUT_PULLUP mode, but I like to setup everything as explicitly complete as possible during the boot sequence. This really helps in avoiding nasty surprises and hours, if not days of poking around. – Michaël Roy Nov 13 '20 at 09:22
  • 1
    @the_pie_in_the_sky_is_a_lie I've made some changed in readDAC(). Result was not accurate. – Michaël Roy Nov 13 '20 at 09:37