Help about a bug

Hello ,
I'm using Arduino uno board trying to make oscilloscope based on excellent Zeitnitz scope program Soundcard Scope , but cant find why are both channels connected as one (A0 and A1 inputs acting in the same time as one channel) , can someone help me to find a bug in this program ? Thanks

//  Scope program
//  =============
// Author: C. Zeitnitz 2017
//
// Program samples analog or digital pins, depending on configuration (by clear text commands) with the rate given below (sampleRate).
// Date are send in binary format via the serial port (USB) to the attached computer.
//
// Analog data: 10bit resolution: 2 bytes per channel in binary format (low byte before high byte). 10 Bits of ADC are stores in 
//              8bit of the low byte and 2 LSBits of the high byte. Upper nibble of high byte contains the channel number.
//              low byte: aaaaaaaa  high byte: nnnn00aa (a: analog bit; n: channel bit).
//              For 2 channels and 10kHz sampling rate this results in 40000 bytes per second = 320000 BAUD
//
// Digital output: bits are packed in a single byte. The LSB corresponds to channel 0. Max 8 channels can be read
//              data rate equals the sampling rate: 10kHz = 10000 bytes per second = 80000 BAUD
//
// Control via serial port from PC
//
// List of commands (NOT case sensitive):
//  ?     return name of application
//  v     return version
//  b     blink LED to identify device
//  cmX   configure DAQ mode: X=a - analog ; X=d - digital
//  cbX   configure ADC resolution (X=8 - 8bit; X=a - 10bit)
//  ao    acquire once (single value)
//  anXX  acquire n values given by XX (hex)
//  ac    acquire continuously 
//  #     stop acquisition
//  !     do nothing
//
// BAUD rate of serial port
#define baudRate        1000000
//

#define NAME    "Arduino-Scope"
#define VERSION "V0.1 CZ 2017"

// define the used digital pins (up to 8 channels)
const byte DigiPins[]  = {PD3, PD4};

// define the used ADC channels
//                       A0, A1
const byte ADCChan[]  = { 0,  1};
#define ADCREF  INTERNAL          // specify the source of the reference voltage e.g. INTERNAL (1.1V for ATmega168, ATmega328). 
                                  // The external circuit has to take the correct reference voltage into account!!    
                                  
#define sysClock        16000000  // CPU clock to derive timer/ADC settings - adjust to your Arduino board
#define sampleRate      10000     // sampling rate for ADC/digital port (max approx. 15kHz w/o changing ADC conversion time)
#define bytesPerSample  2         // 10bit requires 2 bytes per sample

#define DAQLedFreq      2         // Hz of blink frequency while taking data

#define chartoupper(c) (c &= 0xDF}      // clear bit 5 to get to upper case
#define chartolower(c) (c |= 0x20)      // set bit 5 to get to lower case

boolean active;             // flag of active DAQ

#define modeAnalog  0
#define modeDigital 1

byte    mode;               // mode (analog, digital)
bool    is10Bit;            // true if 10Bit data from ADC 
byte    data[bytesPerSample]; // buffer to send to serial port
byte    activeCh;           // current channel to acquire
byte    nSamples;           // number of samples to acquire
byte    sendBytes;
byte    nDch,nAch;

void setup() {
  // configure the reference voltage for the ADC
   analogReference(ADCREF); 
  
  // init serial port with high rate (its USB after all!)
  Serial.begin(baudRate);
  while (!Serial);

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

  // number of analog/digital channels determined from array size
  nDch = sizeof(DigiPins);
  nAch = sizeof(ADCChan);

  // setup digital pins
  for (byte i = 0; i < nDch; ++i) {
    pinMode(DigiPins[i], INPUT);
    digitalWrite(DigiPins[i], HIGH); // switch pull-up on
  }

  activeCh = 0;
  mode = modeAnalog;
  is10Bit=true;
  startDAQ(1); // start DAQ to init ADC and reference voltage
  active = false; 
}

// two interrupt routines triggered by timers (1 and 2)
// - to get data from ADC and increase channel counter / read digital pins and send data to PC
// - to toggle the LED in order to indicate data acquisition
//
ISR(TIMER1_COMPA_vect)          // DAQ interrupt (once per channel)
{
  byte i;
  bool fsend;
  fsend=false;
  if (active) {
    if (mode == modeAnalog) {          // read data from ADC
      if ((ADCSRA & _BV(ADIF)) != 0) { // DAQ active and ADC conversion done
        if(is10Bit) {                 
          data[0]  = ADCL;             // get low byte and 
          data[1]  = ADCH;             // get high byte of ADC value (ADC data right aligned)
          data[1] |= (activeCh << 4);  // store channel number in upper nibble of high byte
        }
        else data[0]  = ADCH;          // 8bit mode: get high byte only (ADC data left aligned) 
        if (++activeCh >= nAch) activeCh = 0; // go to next channel
        restartADC();
        Serial.write(data, sendBytes); // send sample to USB serial port
        fsend=true;
      }
    }
    else if (mode == modeDigital) { // read status of all digital ports
      data[0] = 0;
      for (i = 0; i < nDch; ++i) {
        if (digitalRead(DigiPins[i]) == HIGH) data[0] |= 1 << i;
      }
      Serial.write(data[0]); // send sample to USB serial port
      fsend=true;
    }
    if (fsend && activeCh == 0 && nSamples > 0) { // number of requested samples taken?
      --nSamples;
      active = nSamples > 0;
    }
  }
}

byte prescale;
ISR(TIMER2_COMPA_vect) {         // LED blink interrupt
  if(++prescale==0 && active) { // pre-scale blinking by 256
    ToggleLED(LED_BUILTIN);
  }
}

void setupTimer() {
  noInterrupts();           // disable all interrupts

  // setup timer1 (16 bit) interrupt to retrieve ADC or digital data with equidistant timing
  TCNT1  = 0;
  TCCR1A  = 0b00000000;   // COM1x1=0, COM1x0=0 : PWM deactivated ; WGM11=0, WGM10=0, WGM12=1(TCCRyB,)WGM13=0(TCCRyB): CTC mode
  byte presc = 0b00000001; // last 3 bits: prescaler 001=1; 010=8; 011=64; 100=256; 101=1024;
  TCCR1B  = 0b00001000 | presc;
  TIMSK1  = _BV(OCIE1A);  // enable timer compare interrupt for compare with OCR1A
  if (mode == modeAnalog)  OCR1A  = sysClock / sampleRate / nAch / presc; // calculate timer value for interrupt (analog) 
  if (mode == modeDigital) OCR1A  = sysClock / sampleRate / presc; // calculate timer value for interrupt (digital) 

  // timer 2 used to blink lED while taking data
  TCNT2  = 0;
  TCCR2A  = 0b00000010;   // COM2x1=0,COM2x0=0: OC2A/B normal port ; WGM20=0, WGM21=1, WGM22=0(TCCR2B): CTC mode
  TCCR2B  = 0b00000111;   // last 3 bits: prescaler 001=1; 010=8; 011=32; 100=64; 101=128; 110=256; 111=1024;
  TIMSK2  = _BV(OCIE2A);  // enable timer compare interrupt for compare with OCR1A
  OCR2A   = sysClock / DAQLedFreq / 256 / 1024; // calculate timer value for LED blink. 256 is prescale factor inside of ISR routine

  interrupts();             // enable all interrupts
}

void startDAQ(byte n) {
  nSamples = n;
  activeCh = 0;
  sendBytes = is10Bit ? bytesPerSample : 1;
  setupTimer();

  // ADC initialisation 
  ADMUX   = 0b11000000 | ADCChan[0]; // use internal 1.1V reference; to use Vcc: 0b00xxxxxx; data right adjust (ADLAR=0)
  if(!is10Bit) ADMUX |= 0b00100000; // data left aligned (ADLAR=1) for 8 bit mode
  ADCSRA  = 0b00000101;  // prescaler for ADC: 001=2; 010=4; 011=8; 100=16; 101=32; 110=64; 111=128
  ADCSRA |= _BV(ADEN);  // enable ADC
  if (mode == modeAnalog) restartADC();
  active = true;
}

void restartADC() {
  ADMUX &= 0b11110000;  // clear channel bits before setting next channel
  ADMUX |= ADCChan[activeCh];
  ADCSRA |= _BV(ADIF);  // clear ADC interrrupt flag - yes, its supposed to be a 1 to clear it!
  ADCSRA |= _BV(ADSC);  // start conversion
}

void loop() {
  handleCommands();
}

// acquire commands:
// ================
// ac  - aquire data continuously (analog or digital)
// anxx - aquire one block of xx (HEX) samples
// ao  - aquire once
// ad  - acquire digital ports
void acquire() {
  char c, c2;
  byte n;
  if ((c = readNextChar()) != 0) {
    chartolower(c);
    switch ( c ) {
      case 'c':
        startDAQ(0);
        break;
      case 'n':
        if ((c = readNextChar()) != 0) c = aschex(c);
        if ((c2 = readNextChar()) != 0) c2 = aschex(c2);
        n = (c << 4) + c2;
        startDAQ(n);
        break;
      case 'o':
        startDAQ(1);
        break;
      default:
        break;
    }
  }
}

// Configure commands
// ==================
//  cmX   configure DAQ mode: X=a - analog ; X=d - digital
//  cbX   configure ADC resolution (X=8 - 8bit; X=a - 10bit)
void configure() {
  char c;
  if ((c = readNextChar()) != 0) {
    chartolower(c);
    switch ( c ) {
      case 'b': 
        active = false; 
        if ((c=readNextChar()) != 0) {
          if(chartolower(c)=='a') is10Bit = true;
          else if(chartolower(c)=='8') is10Bit=false;          
        }
        break;
      case 'm': 
        active = false; 
        if ((c=readNextChar()) != 0) {
          if(chartolower(c)=='a') mode = modeAnalog; 
          else if(chartolower(c)=='d') mode = modeDigital;
        } 
        break;
      default: break;
    }
  }
}

void handleCommands(void)
{
  char c;
  if (Serial.available()) {
    c = Serial.read();
    chartolower(c);
    switch ( c ) {
      case '?': Serial.println(NAME);             break;    // return welcome string
      case 'v': Serial.println(VERSION);          break;    // return version
      case 'b': blink_LED();                      break;    // blink LED 5 times for identification
      case 'a': acquire();                        break;    // start ADC and activate data acquisition
      case 'c': configure();                      break;    // configure aquisition
      case '#': active = false; digitalWrite(LED_BUILTIN, LOW); break;    // stop acquisition
      case '!': break;  // this is the keep alive command to check the serial connection - DO NOTHING
      default:  break;
    }
  }
}

void blink_LED() {
  for (int i = 0; i < 5; ++i) {
    digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
    delay(200);                       // wait for a second
    digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
    delay(200);                       // wait for a second
  }
}

void ToggleLED(int LED) {
  if(digitalRead(LED) == HIGH) 
    digitalWrite(LED, LOW); 
  else
    digitalWrite(LED, HIGH); 
}

char  readNextChar() {
  int n = 100;
  while (!Serial.available() && --n > 0) delay(10);
  return n == 0 ? 0 : Serial.read();
}

// convert hex digit "0..9,A..F" to value 0..15
#define A2xa 0x41-0x0a
#define a2xa 0x61-0x0a

char aschex(char c) {
  if (c >= '0' && c <= '9') c -= '0';
  else if (c >= 'a' && c <= 'f') c -= a2xa;
  else if (c >= 'A' && c <= 'F') c -= A2xa;
  else c = 0;
  return c;
}

From link you provided:

Thanks, I'll try that too...

I don't understand your description of the problem. What voltage do you have attached to A0 and A1? What output are you getting from your sketch that shows the problem?

The problem is, voltage attached to A0 only or A1 only , any acceptable voltage, lets say 0.5V, is shown on both channels on the screen, CH1 and CH2 like they're connected together somehow.
Thanks

Are the channel inputs terminated in a fixed high impedance to ground such as 1M ohm, such as is always the case with a real oscilloscope? You can't leave them floating.

Isn't this backward? You can't begin a Serial object that doesn't exist yet.

The serial object always exists; Serial.begin() does not create the object :wink:

Although examples might be wrong, the example ( if(Serial) - Arduino Reference ) first initialises the Serial object and next test if the communication channel is open.

This is the relevant code (to my knowledge) in CDC.cpp for devices with native USB support.

// This operator is a convenient way for a sketch to check whether the
// port has actually been configured and opened by the host (as opposed
// to just being connected to the host).  It can be used, for example, in 
// setup() before printing to ensure that an application on the host is
// actually ready to receive and display the data.
// We add a short delay before returning to fix a bug observed by Federico
// where the port is configured (lineState != 0) but not quite opened.
Serial_::operator bool() {
	bool result = false;
	if (_usbLineInfo.lineState > 0) 
		result = true;
	delay(10);
	return result;
}

Is your Uno on a non-conductive surface?

Not on every architecture. When serial is emulated, 'Serial' points to 'null' until the object is created. At least that is what I have read. But I will closely examine your post. It seems to me that what you and I are asserting can both be true. Before the object exists, 'if(Serial)' is equivalent to 'if(null)' and after it exists, the method you posted would be called. I admit I'm not sure, haven't tested this.

That is normal. All of the analogRead() pins share a single Sample/Hold capacitor. When a pin is not sourcing or sinking enough current to charge or discharge the Sample/Hold capacitor the voltage stored in the capacitor will not change.

When you read a disconnected analog input you will generally get the same value as the previously-used connected analog input +/- whatever electrical noise the disconnected pin picks up.

Yes, that is why I asked what I did in post #6. With termination (load) resistors, the capacitor can discharge.

Can you give an example of such an architecture?

The object has to exist; if it doesn't, the begin method will crash (on a PC you would get a null pointer exception).

From the same file

Serial_ Serial;

I've tested both versions on a Leonardo and the behaviour is the same; IDE 1.8.5. Just moved the Serial.begin() around for testing.

void setup() {

  pinMode(LED_BUILTIN, OUTPUT);

  Serial.begin(115200);
  while (!Serial) {
    digitalWrite(LED_BUILTIN, HIGH);
    delay(200);
    digitalWrite(LED_BUILTIN, LOW);
    delay(200);
  }
}

void loop() {

  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  delay(1000);
}

while (!Serial) does not test the existence of the object, it uses operator bool() of the class.

AVR HardwareSerial.h

class HardwareSerial ... {
... 
    operator bool() { return true; }
...
};

AVR CDC.cpp

// This operator is a convenient way for a sketch to check whether the
// port has actually been configured and opened by the host (as opposed
// to just being connected to the host).  It can be used, for example, in
// setup() before printing to ensure that an application on the host is
// actually ready to receive and display the data.
// We add a short delay before returning to fix a bug observed by Federico
// where the port is configured (lineState != 0) but not quite opened.

Serial_::operator bool() {
    bool result = false;
    if (_usbLineInfo.lineState > 0)
        result = true;
    delay(10);
    return result;
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.