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