串行监视器显示来自 Arduino Mega 的意外输入

Serial monitor showing unexpected input from Arduino Mega

我正在使用 Arduino Mega 来控制 CS1237 ADC。根据 datasheet I found (via https://github.com/SiBangkotan/CS1237-ADC-cpp-library),我正在向时钟引脚发送信号,并在每个时钟脉冲后等待 1ms,然后读取响应。这似乎在某种程度上起作用,因为当我对接收到的每个位执行 Serial.println() 时,对于生成的数据字,我得到一个 24 位数据字,与我得到的 24 个独立位相匹配。但是,当我去掉 Serial.println() 的额外调试用途(即在接收到每个位时打印它们)时,我也会得到一个不同的数据字。每次都是 20 位全 1,而不是 24 位各种 1 和 0。我不明白为什么串行通信通道中的这个额外调试输出会改变进入串行监视器的数据字?

这是我的设置和预设置代码:

// 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);
}

相关代码如下:

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);
}

串口监视器的输出是

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

...等等。但是,当我在 for(int i = 0; i < 24; i++) 循环的右括号之前添加一个额外的 Serial.println(new_bit); 时,我在 Arduino IDE 的串行监视器中得到这样的输出(显示时间戳已打开) :

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

如果我在该行 Serial.println()-ing 除 new_bit 之外的任何其他内容,则不会发生这种情况,例如,如果我这样做 Serial.println(dataword); 或者如果我引入一个小的延迟而不是进行串行打印。在那些情况下,它仍然会输出 20 个 1。我不知道串行通信出了什么问题,因为从 ADC 读取数据似乎没问题。如果我引入 5000us 或更多的延迟,那么 dataword 的内容就会发生变化,这似乎变成了延迟长度的函数。 IE。 dataword 的内容对于每个延迟长度都是恒定的(5000us、6000us、10000us 和 20000us 是我试过的)。如果延迟足够长,它会回到全 1。

通过查看数据表...

首先芯片启动时...默认输入Al引脚。你没有设置你的时钟引脚模式,所以你没有时钟。此外,ADC 可能需要长达 300 毫秒才能唤醒。这是启动顺序的一部分,当您退出 setup() 时芯片应该准备就绪。您还可以在 setup() 中包含任何 ADC 寄存器的设置。参见数据表启动顺序@图 5 和图 6。

此外,如果您想尝试较低的时钟速度,请不要让 clck 高电平超过 100us

来自数据表,2.5: "当SCLK由低变高并保持高100μs以上时,CS1237进入PowerDown模式,功耗小于0.1μA。当SCLK变回低后,芯片恢复正常工作。"

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)) {}
}

数据表的图表“图 7”表示:

等到/DRDY为低,等待时间t4(即0),所以不等待也可以,然后循环每个位:

  • 设置高时钟
  • 至少等待持续时间 t6(455 纳秒)
  • 读取输入位。
  • 将时钟设置为低电平。
  • 时钟必须在下一个时钟之前保持低电平至少持续时间 t5 (455 ns)。

您可以在时钟较低时读取数据位,但请注意数据库图。 8、时钟位一变低,第27位就失效了。根据经验,这暗示您应该在时钟高时阅读。有些数据表很难阅读,有些甚至是错误的。这就是我对这个的解释,但我可能是错的,你可能还想尝试在时钟高时阅读。

你的输入程序变成:

// 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(")");
    }

    // ...
}

一旦你 运行 顺利完成,你可以尝试通过制作你自己的 455ns 延迟函数来加快读取速度,不使用任何操作

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

实际延迟取决于您的时钟速度。通常,这些都是使用宏来实现的。

例如,在多行宏中。请注意行尾的反斜杠。这些应该是该行的最后一个字符,宏中不应有任何空行

  // 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();