// Interleaved Oscilloscope for Arduino UNO - easily achieve 20kHz wavefunctions
// Arduino Sketch by Claudio Lenz Cesar(CLC) - Instituto de Fisica - UFRJ (25/July/2020)
// Based on the osciloscope Sketch by Veldekiaan at https://create.arduino.cc/projecthub/Veldekiaan/sampling-scope-frequency-counter-2e4196
// Need to Install "Processing" and the new Processing Sketch (added a Save Ch button and comands '<','>' to increase PWM freq.) or use his Processing Sketch (.pde) in the link above
// and change the first 3 TimebaseSets (lines ~229) to
/*
new TimebaseSet(1, 0.0001, 475, 0.000003), // 0 com Interleaving x5
new TimebaseSet(2, 0.0001, 140, 0.000015),
new TimebaseSet(5, 0.0001, 350, 0.000015),
*/
// and lines 5 and 7 to:
/*
boolean scope = true; // start with Scope instead of Counter
static final String SerialPort ="/dev/cu.usbserial-RCBB_4OK6T4"; // Change this to match your Arduino port
*/
// The original Veldekiaan's project triggered (Interrupt) in a PWM port square signal (I changed the port: needed that for the DDS AD9833 control)
// Here I keep the PWM pin5 but disconnected the trigger input Pin2 (can still use it)
// I added a trigger Interrupt based on the AnalogComparator (pins6[+] and 7[-]) (lines 215...)
// FOr this realization, the AC+ = internal reference = 1.1V and the sinewave generated by the AD9833 (at about 3V) is also input in AC-=pin7[-]
// I increased the clock for the ADC, using Prescaler=04. The conversion then takes about 13,5 cycles = 13,5*(4/16) s= 3,4s
// Using an interleaving factor of x5 (in the 100s scale only), I set the readings after each trigger to be happen at 15s time interval (timer interrupt)
// Then, after getting many points in a single AC trigger (single wave), I restart again, but now getting the ADC readings after a time delay of 3s, then again at 6s, and so on
// Connections: For the AD9833:13=CLK,11=DATA, 10=FSYNC
// D5=saida PWM, ligada num led+300ohm p/ GND (piscando to rapido que s vemos intensidade mdia) e tambm na A5 para ler no osciloscpio
// D4=sada do trigger, ligada num LED+300ohm p/ GND = deve ficar piscando
// pino7 = entrada AIN1 para comparador: ligada na sada do AD9833 ou Onda de Entrada = A0 (entrada analgica) e um cap entre Aref & GND
// pino6 ser no utilizavel pois vou ligar o AIN0 tenso interna de referncia de 1.1V como nvel de trigger
// If you do not use a function generator, connect the PWM output (D5) to the AIN- = pin7 input for trigger.
//
/* Comandos para o AD9833 inseridos aqui: linhas ~36, ~555 -> no Setup
// Pins for SPI communication with the AD9833 IC
#include <SPI.h>
#include <MD_AD9833.h>
#define DATA 11 ///< SPI Data pin number
#define CLK 13 ///< SPI Clock pin number
#define FSYNC 10 ///< SPI Load pin number (FSYNC in AD9833 usage)
#define CS_DIGIPOT 9 // MCP41010 chip select - digital potentiometer. Not used in this implementation (use default)
MD_AD9833 AD(FSYNC); // Hardware SPI = AD9833
unsigned long fnu; // Frequncia da Onda Senoidal do AD9833 (linha 557)
*/
/* Veldekiaan's scope:
6-channel oscilloscope
Operation mode:
'#': frequency counter
'*': oscilloscope
'!': reset
Trigger modes:
'E': rising edge
'F': falling edge
Sweep mode:
'C': continuous
'D': single sweep
Time base:
100, 200, 500 us
1, 2, 5 ms
10, 20, 50 ms
100, 200, 500 ms
1, 2, 5 s
10, 20, 50 s
100, 200 s
Timebase is identified by characters 'a'-'t'
Channel:
Channels are selected by '0'-'5'
Counter time base
'G': 1 period, clock divider 1024
'H': 1 period, clock divider 256
'I': 1 period, clock divider 64
'J': 10 periods, clock divider 1024
...
'U': 10000 periods, clock divider 64
*/
#define ScopeMode '*'
#define CounterMode '#'
#define InitialMode '*'
// Trigger input pin (only 2 or 3)
#define TriggerPin 2 // digital trigger still available: but use it or AnalogComparator trigger
#define MaxSamples 1000
// Pin to connect an LED showing counter measuring intervals
// original: #define CounterPin 13 // => save for programing AD9833
#define CounterPin 4
// Pin to generate a PWM signal (5 or 6 only)
#define PwmPin 5 // com o AnalogComp o pino 6 passa a ser o AIN0 e agora a saida PWM ser no pino5
#define PwmFreq65k 0x1 // 62.5 kHz = 16MHz/(1=prescaler * 256)
#define PwmFreq7k 0x2 // 7.8125 kHz = 16MHz/(8=prescaler * 256)
#define PwmFreq976 0x3 // 976.56 Hz = 16MHz/(64=prescaler * 256)
#define PwmFreq244 0x4 // 244.14 Hz = 16MHz/(256=prescaler * 256)
#define PwmFreq061 0x5 // 61.035 Hz = 16MHz/(1024=prescaler * 256)
// Timer and interrupt settings para trigger Externo no Pino 2
#define INTBIT B00000001
#define TRIGCLR B00000001
#define TRIGRISE B00000011
#define TIMERCTCA B00000000
#define TIMERCTCB B10001000
#define TIMERCNTA B00000000
#define TIMERCNTB B00000000
#define TIMERNOCLK B11111000
#define TIMERPS0001 B00000001 // clock do timer = clock do Arduino = 16 MHz
#define TIMERPS0256 B00000100
#define TIMERPS1024 B00000101
#define ADCINIT B10000111 // ADEN=1,enable ADC: isso j resolve o Anal.Comp., ADC=0 No Comea Conversao, ADATE=0 (sem AutoTrigger), ADIF=0, ADIE=0, ADSprescaler=111=128
// o programa no mexe em ADCSRB, portantos bit ADTS2,1,0 so 000 = free runing (to rpido quanto possvel)
#define ADCSELECT B01100000 // original: 5V
// #define ADCSELECT B11100000 // CLC: mudar para 1.1V de referncia (aumenta resoluo p/ ondas de pico <1.1V)
#define ADCSTART B01000000
#define ADCPSCLR B11111000
#define ADCPS002 B00000001 // CLC
#define ADCPS004 B00000010 // CLC
#define ADCPS008 B00000011 // CLC
#define ADCPS016 B00000100 // 76.9 kSps ? Tem que mexer tambm em CNT do trigger do contador
#define ADCPS032 B00000101
#define ADCPS064 B00000110
#define ADCPS128 B00000111
#define ADCREADY B00010000
#define CLEARADIF B10101111 // Does not do anything ?- na operao ADCSRA &= CLEARADIF, limpa os bits 6(ADC StartConversion) e 4(ADIntFlag) e mantm os outros do ADCSRA
boolean scope = true;
//The interrupt setting depends on the choice of the trigger pin: 2 (INT0) ou 3(INT1)
byte intBit = (INTBIT << (TriggerPin == 2 ? 0 : 1));
// Current mode variables
boolean continuousSweep = true;
byte currentChannel = 0;
char currentBase;
// Sample variables
volatile byte sample[MaxSamples];
byte timerPrescaler;
int samples;
volatile int index=0;
volatile int writeIndex;
// Interleaving variables (for the 100s scale: CLC)
volatile boolean FlagItlvd = false; // Flag whether to use of not interleaving: only made TRUE at 100s/Div
volatile int itlvdCnt=0; // CLC = interleaved count=0,1,..,4. Vou usar na amostragem mais rpida: 5 leituras com timedelays
volatile int itlvdCntMax=5; // CLC = 5 interleaved points
volatile int tdelay=3; // CLC = interleaving delay in microseconds
volatile int indexWrt; // CLC
// Frequencey counter current ode variables
int periodCount;
volatile int periods = 0;
volatile unsigned long count;
volatile byte counterDiv = 5;
// PWM output value ("-" and "+" changes this duty cycle)
unsigned int pwm = 128;
/* Initialize the Analog-Digital Converter */
void initAdc()
{
ADCSRA = ADCINIT;
ADMUX = ADCSELECT;
}
/* Read a sample from the ADC */
void readAdc()
{
unsigned int result = 0;
ADCSRA |= ADCSTART; // Start conversion
while ((ADCSRA & ADCREADY) == 0);
sample[index*(FlagItlvd ? itlvdCntMax : 1)+itlvdCnt+1] = ADCH; // 8-bit sample size for speed
index++;
// ADCSRA &= CLEARADIF; no faz nada
}
/* Trigger Interrupt Service Routine . Pino 2 INT0 e pino 3 INT1 */
#if (TriggerPin == 2)
ISR(INT0_vect)
#else
ISR(INT1_vect)
#endif
{
if (scope)
{
EIMSK &= ~intBit; // Disable trigger interrupt first; (zera o INT1 ou INT0)
EIFR |= intBit;// Clear pending interrupts (escreve 1 em EIFR(INTFR1 ou INTF0) e assim limpa)
readAdc(); // Read first sample immediately
TCNT1 = 0; // Reset timer
TCCR1B |= timerPrescaler; // Start timer now
}
else
{
int c = TCNT1;
TCNT1 = 0;
TCCR1B = counterDiv; // Start counter
count += c; // Add current timer to total count
periods++; // Another period counted
if (periods > periodCount) // If all periods counted for a measurment...
{
TCCR1B = 0; // ... Stop counter
writeCount(count); // Report value to PC
counterReset(); // Reset counter for next measurement
}
}
}
/* Trigger with the Analog Comparator - CLC */
ISR(ANALOG_COMP_vect) {
// ACSR |= (1<<ACI); // clear Analog Comparator interrupt
if (scope)
{
// EIMSK &= ~intBit; // Disable trigger interrupt first; (zera o INT1 ou INT0)
// EIFR |= intBit; // Clear pending interrupts (escreve 1 em EIFR(INTFR1 ou INTF0) e assim limpa)
bitClear(ACSR,ACIE); // DISABLE ADC interrupt first = CLC
bitSet(ACSR,ACI); // Clear Pending Interrupt = CLC
digitalWrite(CounterPin, !digitalRead(CounterPin)); // toggle state of Pin 4
delayMicroseconds(tdelay*itlvdCnt); // CLC= delay for interleaving
readAdc(); // Read first sample immediately
TCNT1 = 0; // Reset timer
TCCR1B |= timerPrescaler; // Start timer now
}
}
/* Handle the end of a sweep */
void stopSweep()
{
TCCR1B &= TIMERNOCLK; // Set clock select to 0 (no clock)
index++;
writeData(); // Write sampled data to serial connection
if (continuousSweep)
{
scopeReset(); // Restart automatically in continuous sweep mode
}
}
/* Reset the scope for a new sweep */
void scopeReset()
{
TCCR1B &= TIMERNOCLK; // Stop the timer by setting clock select to 0 (no clock)
Serial.print((char) 0xFF); // Mark end of sweep to console
index = 0; // Reset sweep data
itlvdCnt=0;
writeIndex = 0;
// CLC: faz a 1a converso que demora mais. No guarda os dados
ADCSRA |= ADCSTART; // Start conversion
while ((ADCSRA & ADCREADY) == 0); // espera terminar
EIFR |= intBit; // Reset trigger interrupt flag
EIMSK |= intBit; // Enable interrupts on trigger input
bitSet(ACSR,ACI); // Clear Pending Interrupt = CLC
bitSet(ACSR,ACIE); // ENABLE ADC again = CLC
// Wait for trigger signal interrupt
}
/* Reset the frequency counter to start another measurement */
void counterReset()
{
digitalWrite(CounterPin, HIGH - digitalRead(CounterPin)); // Toggle indicator LED
periods = 0; // Reset counted periods
count = 0UL; // Reset total timer counts
TCNT1 = 0; // Reset timer
EIFR != intBit;
}
/* Interrupt Service Routine for timer OCR compare match : next sampling reading after the usual time delay */
ISR(TIMER1_COMPA_vect)
{
readAdc(); // Read next ADC sample and store it
if (index >= samples) // algo tem que ocorrer se pegou toda a sample dessa vez
if ((!FlagItlvd) || (itlvdCnt+1 >= itlvdCntMax)) {stopSweep();} // se no tem interleaving: pegou toda a amostra: STOP
else { // seno ... vai pro prximo indice de interleaving
itlvdCnt++;
TCCR1B &= TIMERNOCLK; // tem que parar o clock interrupt: select to 0 (no clock)
index=0; // reseta o indice para interleaved e, reabilita e espera novo triger do ADC:
bitSet(ACSR,ACI); // Clear Pending Interrupt = CLC
bitSet(ACSR,ACIE); // ENABLE AComp interrupt again = CLC
// Serial.print("\n Ix,It="); Serial.print(index); Serial.print(itlvdCnt); // debug
}
}
/* Set the sample time for the selected time base.
* The selection is done with a single character 'a'-'t'.
*/
void setSampleTime(char c)
{
unsigned int cnt;
currentBase = c; // Store the time base as current
ADCSRA &= ADCPSCLR; // Clear prescaler
// Set ADC prescaler
switch (c)
{
case 'a':
case 'b':
case 'c':
case 'd':
ADCSRA |= ADCPS004; //CLC teste = melhor
// ADCSRA |= ADCPS008; //CLC teste
// ADCSRA |= ADCPS016; // Original
break;
case 'e':
ADCSRA |= ADCPS032;
break;
case 'f':
ADCSRA |= ADCPS064;
break;
default:
ADCSRA |= ADCPS128;
break;
}
// Set #samples
switch (c)
{
case 'a':
samples = 95; FlagItlvd=true; // Interleaving somente na escala de 100s/div
// original samples = 48;
break;
case 'b':
samples = 140; FlagItlvd=false; // 200s/div scale
// samples = 91;
break;
case 'c':
// samples = (50 << (c - 'a'));
samples = 350; FlagItlvd=false; // 500s/div scale
break;
case 'd':
case 'e':
samples = 400; FlagItlvd=false;
break;
default:
samples = (c >= 'o' ? 1000 : 500); FlagItlvd=false;
break;
}
// Set timer prescaler
timerPrescaler = (c <= 'j' ? TIMERPS0001 : (c <= 'r' ? TIMERPS0256 : TIMERPS1024));
// Set counter max value
switch (c)
{
case 'a':
cnt = 240; //240=15*16 para 15s de converso (o interleaved entra de 3 em 3 s, itlvdcnt=0,1,2,3,4). 100s/div
break;
case 'b':
case 'c':
// cnt = 400; // No TIMERPS001 isso daria 400/(16MHz)=25 s; enquanto a converso deve durar 13,5 ciclos de clock do ADC, o que no Prescaler ADCPS008 daria 7s
// cnt = 336; // ORIGINAL (CLC)
cnt = 240; //240=15*16 para 15s de converso . 200s/div and 500s/div
break;
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
cnt = 400 << (c - 'd'); // No TIMERPS001 isso daria 400/(16MHz)=25 s em c='d'e vai dobrando; 1ms/div=25s, 2ms/div=50s, 5ms/div=100s, 10ms/div=200s, 20ms/div=400s
break;
case 'i':
cnt = 16000; // 1ms
break;
case 'j':
cnt = 32000; // 2ms
break;
case 'k':
cnt = 250; // agora TIMERPS0256 => 250/(16M/256) = 4ms
break;
case 'l':
case 'm':
case 'n':
cnt = 625 << (c - 'l');// 10ms, 20ms, 40ms
break;
case 'o':
case 'p':
case 'q':
cnt = 3125 << (c - 'o'); //50ms, 0.1s, 0.2s
break;
case 's':
cnt = 15625; // s(prescaler=1024):1s
break;
case 'r':
case 't':
cnt = 31250;// r:0.5s, t(prescaler=1024): 2s
break;
}
OCR1A = cnt;
}
/* Set trigger mode to falling or rising edge no TriggerPin */
void setTriggerMode(char c)
{
if (c == 'F')
{
EICRA &= ~(TRIGCLR << (TriggerPin == 2 ? 0 : 2));
bitSet(ACSR,ACIS0); // CLC : Analog Comparator
}
else
{
EICRA |= TRIGRISE << (TriggerPin == 2 ? 0 : 2);
bitClear(ACSR,ACIS0); // CLC : Analog Comparator
}
}
/* Sweep mode (continuous or single) */
void setSweepMode(char c)
{
continuousSweep = (c == 'C'); // 'C' is continuous, 'S' is single
}
/* Set the channel '1'-'6' */
void setChannel(char c)
{
currentChannel = (c - '1'); // Internally, channels are 0-5
ADMUX &= B11110000;
ADMUX |= (currentChannel & 0x7); // Switch the ADC multiplexer to the channel pin
}
/* Start oscilloscope mode */
void setScope()
{
scope = true;
digitalWrite(CounterPin, LOW); // Switch off counter indicator
TCCR1A = TIMERCTCA; // Use Timer1 in 'match OCR' mode for sampling
TCCR1B = TIMERCTCB; // No clock, so no interrupts yet
TIMSK1 |= (1 << OCIE1A); // Enable timer1 compare interrupts
execute(currentBase); // Set the time base to the last used
scopeReset(); // Restart scope
}
/* Start frequency counter mode */
void setCounter()
{
scope = false;
periodCount = 1;
digitalWrite(CounterPin, HIGH); // Switch on counter indicator
TCCR1A = TIMERCNTA; // Use Timer1 in normal mode for counting
TCCR1B = 0; // Hold timer
TIMSK1 &= ~(1 << OCIE1A); // Disable timer1 compare interrupts
EIFR |= intBit;
EIMSK |= intBit; // Enable external interrupt
counterReset(); // Restart frequency counter
}
/* Set the number of periods to count for determining frequency */
void setPeriods(char c)
{
int s = c - 'G';
int p = s / 3; // Period count 1, 10, 100, 1000 or 10000
counterDiv = 5 - (s % 3); // Clock divider 64, 256 or 1024 for accuracy
periodCount = 1;
for (int per = 0; per < p; per++)
{
periodCount *= 10;
}
}
/* Handle command characters sent from the console */
void execute(char c)
{
switch (c)
{
case '!':
break;
case 'E':
case 'F':
setTriggerMode(c);
break;
case 'C':
case 'D':
setSweepMode(c);
break;
case '#':
setCounter();
break;
case '*':
setScope();
break;
case '-':
if (pwm > 0)
{
pwm--;
}
analogWrite(PwmPin, pwm);
break;
case '+':
if (pwm < 255)
{
pwm++;
}
analogWrite(PwmPin, pwm);
break;
case '<':
if ((TCCR0B & 0x07) > 0x02)
{TCCR0B = (TCCR0B & 0xF8) | ((TCCR0B & 0x07)-0x1);}
break;
case '>':
if ((TCCR0B & 0x07) < 0x05)
{TCCR0B = (TCCR0B & 0xF8) | ((TCCR0B & 0x07)+0x1);}
break;
default:
if (c >= '1' && c <= '6')
{
setChannel(c);
}
else if (islower(c))
{
setSampleTime(c);
}
else
{
setPeriods(c);
}
break;
}
if (scope)
{
scopeReset();
}
else
{
counterReset();
}
}
/* Send all available samples to the console */
void writeData()
{ if (FlagItlvd) {indexWrt=samples*itlvdCntMax+1;} else {indexWrt=index;}
for (writeIndex=1; writeIndex < indexWrt; writeIndex++)
{
Serial.print((char) sample[writeIndex]); // Original= vai o binrio
// Serial.print("\n"); Serial.print(sample[writeIndex]); // aqui vai o nmero em decimal
}
Serial.print((char) (0xFF)); // Send all ones to mark end of transmission
}
/* Writes the count value for the defined number of periods in 4 bytes, LSB first */
void writeCount(unsigned long cnt)
{
unsigned long c = cnt;
for (int d = 0; d < 4; d++)
{
Serial.print((char) (c & 0xFF));
c >>= 8;
}
Serial.print((char) (0xFF)); // Send all ones to mark end of transmission
}
/* Standard set-up */
void setup()
{
// original: Serial.begin(115200); // Fast serial connection
Serial.begin(115200);
/* AD9833:Begin
AD.begin();
fnu = 2000UL; // 10000UL = 10kHz
AD.setFrequency(MD_AD9833::CHAN_0, fnu);
// AD9833:End
*/
pinMode(TriggerPin, INPUT_PULLUP); // The trigger input
pinMode(CounterPin, OUTPUT); // The frequency counter indicator LED
pinMode(PwmPin, OUTPUT); // A PWM source for testing
TIMSK0 = 0; // Disbable other timer interrupts
TIMSK2 = 0;
/* mudei d0 2o pro 1o abaixo */
// TCCR0B = (TCCR0B & 0xF8) | PwmFreq7k;
// TCCR0B = (TCCR0B & 0xF8) | PwmFreq976; // Set pin 5/6 PWM frequency
TCCR0B = (TCCR0B & 0xF8) | PwmFreq061; // para as contantes de tempo R+C longas!!!
// External interrupt for trigger signal (in TrigPin =2)
EIMSK &= ~intBit; // Disable trigger interrupt first; (zera o INT1 ou INT0)
EIFR |= intBit; // Clear pending interrupts (escreve 1 em EIFR(INTFR1 ou INTF0) e assim limpa)
EICRA = TRIGRISE << (TriggerPin == 2 ? 0 : 2); // Start with rising edge
initAdc(); // Set up the analog inputs and the ADC
// AnalogComparator: Begin (CLC)
// ; AIN0=Bandgap=1.1V
pinMode(7, INPUT);
DIDR1 = B00000011; // disable digital input to AIN1/0
/* ACSR =
(0 << ACD) | // Analog Comparator: Enabled
(1 << ACBG) | // ou no:Analog Comparator Bandgap Select: bandgap 1.1V in AIN0 (positive input)
(0 << ACO) | // Analog Comparator Output: OFF
(1 << ACI) | // Analog Comparator Interrupt Flag: Clear Pending Interrupt
(1 << ACIE) | // Analog Comparator Interrupt: Enabled
(0 << ACIC) | // Analog Comparator Input Capture: Disabled
(1 << ACIS1) | (0 << ACIS0); // Analog Comparator Interrupt Mode: Comparator Interrupt on Rising Output Edge
*/
ACSR = B01011010; // enable AC, AIN0=1.1V, ACO=off, ClearPendindInt, ACintEnable,ACInputCapt=Disabled, falling edge do comparador=rising edge do meu sinal
// AnalogComparator: End (CLC)
// Set the default controls
execute('E'); // Rising edge trigger
execute('C'); // mudei para Single sweep - Continuous sweep
execute('1'); // Channel A0
/* mudei de 'h' pra 'd'*/
execute('h'); //
execute('G'); // Counter time base at 1x/1024
execute(InitialMode); // Start in selected initial mode
analogWrite(PwmPin, pwm); // Switch on PWM signal
}
/* Standard loop */
void loop()
{
if (Serial.available()) // If a command was sent from the console, ...
{
execute(Serial.read()); // ...handle it here
}
}
Comments
Please log in or sign up to comment.