Project showcase
DigitalAnalyzer

DigitalAnalyzer © GPL3+

Analyze digital inputs on up to six channels using the ATmega328 (e.g. Arduino Uno / Nano / Ethernet / Mini).

  • 1,313 views
  • 4 comments
  • 6 respects

Components and supplies

Apps and online services

About this project

Did you ever ask yourself if ISP works correctly? Or is the PWM-output really correct? I first wanted to buy a digital oscilloscope to record the output of some signals to know what's really going on. Then I found out, that there are digital (or logical) analyzers and I thought, that I can manange to program this by my own. And this is the output of my work and I hope it is helpful to you. Simply follow the instructions on your Serial monitor (or below) and enjoy!

For testing purposes I included a frequency generator, which is also helpful to test other electrical parts.

Note: If you are using an older board with anATmega48/88/168, youneedtoadjustthevariablemaxSamplesatthebeginningofthesourcecode.

User guide

There are two modes: FastMode and DirectMode.

While using FastMode you need to send the number of samples to be recorded via SerialMonitor to the Controller (e.g. '140'). This amount of samples will be recorded and printed afterwards. It is possible to detect about 100, 000 samples per second, however (as SRAM as limited) only 300 samples can be recorded.

While using DirectMode, each detected Sample will be printed directly to the SerialMonitor. It is possible to detect about 200 samples per second without running into a buffer overflow. Faster samplerates will lead to a buffer overlow and recording will be stopped automatically.

To call DirectMode, you need to send a '0' via SerialMonitor to the controller.

There are six channels that can be recorded (pins 2 - 7). To set the supervised channels, enter e.g. 'c245' to observe pins 2, 4 and 5. The input pins are internally connected to pull-up resistors. It doesn't matter how many channels are observed, the possible recordable samplerate is not effected.

The current recording can be aborted by sending 'x' to the controller. The recored samples will be printed out.

It is possible to call a frequncy generator on pin 11. To enable it, you need to send e.g. 'f123.456' via SerialMonitor, where the number represents the frequency (in this case 123.456 Hz). A frequency between 0.007451 and 8, 000, 000 Hz can be chosen, however not infintely variable. The exact output frequency is printed below. The maximum recording speed (samplerate) is not influenced by the frequency generator.

All inputs need to be completed with a 'LineFeed' (see setting in SerialMonitor). You can copy the output for example into Microsoft Excel to plot diagrams and analyze the digital signals. I created an Excel file with macros to import the data from your clipboard: DigtialAnalyzer.xlsm (Be aware that.xlsm-files can countain malicious software! Check the VBA-Code (Alt + F11) to see that my file only copies the clipboard content to the worksheet and does some further calculations).

Set up a baudrate of 115, 200 for the serial communication.

Limitations

It is possible to record up to 100, 000 samples / second. However, it is only possible to record 300 samples in total until the internal SRAM runs out of memory.

Internals

The input pins are internally connected to pull-up resistors.

This programm uses Timers1 and 2, Timer0 is disabled. Therefore millis(), micros() and delay() won't work. I chose to do this to make this a most possible lightweight program with best performance.

Timer1 is used to measure the time of the input signals with a prescale of 8, which leads to an accuracy of 0.5us.

Timer2 is used for the frequency generator. Its prescaler is adjusted according to the desired frequency in order to reach the closest possible output frequency. For frequencies higher than 30.5 Hz the PWM-mode CTC is used to toggle the output pin 11. This does not use any CPU performance. For slower frequencies a overflow interrupt routine is executed to toggle the output pin, which doesn't use much CPU as it occures only quite rare.

Look ahead

I'm planning to extend the Excel-file to directly communicate with the Arduino.

Code

Source CodeArduino
/*
 Name:			DigitalAnalyzer.ino
 Version:		1.1
 Created:		Jan 2019
 Last Updated:	25. Feb. 2019
 Author:		dr.shihan

 Analyze digital inputs on up to six channels using the Atmega 328 (e.g. Arduino Uno / Nano / Ethernet / Mini).
 https://create.arduino.cc/projecthub/drshihan/digitalanalyzer-a8754a


 Change Log:

 V1.1:
 - DirectMode could not be exited and calling FastMode after DirectMode didn't work out correctly
 - corrected the frequency generator for frequencies below 30 Hz

*/

// written for ATmega 48/88/168/328 (e.g. Arduino Uno / Nano)
// according to the available SRAM on your controller, you need to adjust the 'maxSamples' (see below)

#define FastMode 0
#define DirectMode 1
int Mode = FastMode;

#define maxSamples 300 //set this according to the available SRAM on your Controller (ATmega48: 30; ATmega88 & ATmega168: 130; ATmega328: 300)
unsigned int Samples;

byte Channels[6] = { 2, 3, 4, 5, 6, 7 }; //Input Pins (2-7)
byte ChannelCount = sizeof(Channels) / sizeof(Channels[0]);
volatile unsigned long TimestampBuffer[maxSamples];
volatile byte ChannelBuffer[maxSamples];
volatile unsigned int WritePosition = 0;
unsigned int ReadPosition = 0;
volatile bool BufferOverflow = false;

volatile bool Recording = false;
volatile bool Sending = false;

volatile unsigned int tmr1ovf = 0;

double f; //output frequency on Pin 11
int prescaler; // for frequency generator
int tmr2ovf = 0;
int tmr2ovf_switch = 0;

String Input;
int i, j;


void setup() {
	Serial.begin(115200);
	for (i = 0; i < ChannelCount; i++) {
		pinMode(Channels[i], INPUT_PULLUP);
	}
	Help();

	//disable timer0 with micros, millis, etc. for not getting distracted
	TIMSK0 = 0;
}


void loop() {
	if (Serial.available() > 0) {
		Input = Serial.readStringUntil('\n');
	}
	else {
		Input = "";
	}

	if (Input != "") {

		// abort current recording
		if ((Input[0] == 'x') && Recording) {
			StopRecording();
			Help();
			return;
		}

		if (Recording) {
			Serial.println(F("I'm already recording. Please abort the recording with 'x'."));
			return;
		}

		// channel selection
		else if (Input[0] == 'c') {
			ChannelCount = Input.length() - 1;
			if (ChannelCount > 6) {
				Serial.println(F("There are at most 6 channels that can be observed (pins 2 - 7)."));
				return;
			}
			for (i = 0; i < ChannelCount; i++) {
				Channels[i] = Input[i + 1] - 48; //ASCII-conversion from (char) to (int)
				if ((Channels[i] < 2) || (Channels[i] > 7)) {
					Serial.println(F("Only pins 2 - 7 can be chosen."));
					ChannelCount = 0;
					return;
				}
				pinMode(Channels[i], INPUT_PULLUP);
			}

			Serial.print(F("Active Channels ("));
			Serial.print(ChannelCount);
			Serial.print(F("): "));
			if (ChannelCount > 0) { Serial.print(Channels[0]); }
			for (i = 1; i < ChannelCount; i++) {
				Serial.print(F(", "));
				Serial.print(Channels[i]);
			}
			Serial.println();
			Help();
		}


		// frequency generator:
		else if (Input[0] == 'f') {
			pinMode(11, OUTPUT);

			//using Timer 2 with CTC-Mode
			f = Input.substring(1).toDouble();
			if (f < 0.007451) { // turn off
				f = 0;
				TCCR2A = 0b00000000;
				TCCR2B = 0b00000000;
				Serial.println(F("Frequency generator turned off."));
				return;
			}
			else if (f < 30.517578125) {
				prescaler = 1024;
				TCCR2A = 0b00000010;
				TCCR2B = 0b00000111;
				TCNT2 = 0;
				OCR2A = 15; // there is a overflow every 1,024ms
				TIMSK2 = 0b00000010;

				tmr2ovf_switch = 488.28125 / f;
				Serial.print(F("Current frequency set to [Hz]: "));
				f = 488.28125 / tmr2ovf_switch;
				Serial.println(f,3);
				Help();
				return;
			}
			else if (f < 122.0703125) {
				prescaler = 1024;
				TCCR2B = 0b00000111;
			}
			else if (f < 244.140625) {
				prescaler = 256;
				TCCR2B = 0b00000110;
			}
			else if (f < 488.28125) {
				prescaler = 128;
				TCCR2B = 0b00000101;
			}
			else if (f < 976.5625) {
				prescaler = 64;
				TCCR2B = 0b00000100;
			}
			else if (f < 3906.25) {
				prescaler = 32;
				TCCR2B = 0b00000011;
			}
			else if (f < 31250.0) {
				prescaler = 8;
				TCCR2B = 0b00000010;
			}
			else if (f < 8000000.0) {
				prescaler = 1;
				TCCR2B = 0b00000001;
			}
			else {
				f = 8000000.0;
				prescaler = 1;
				TCCR2B = 0b00000001;
			}

			TCCR2A = 0b01000010;
			OCR2A = (byte)(8000000.0 / prescaler / f) - 1;
			Serial.print(F("Current frequency set to [Hz]: "));
			f = 8000000.0 / prescaler / (1 + OCR2A);
			Serial.println(f,3);
			Help();
		}


		// recording instructions
		else if (Input.toInt() >= 0L) {
			Samples = Input.toInt();
			if (Samples > maxSamples) {
				Samples = maxSamples;
				Serial.print(F("Maximum Samples: "));
				Serial.println(maxSamples);
				Mode = FastMode;
			}
			else if (Samples == 0) {
				Mode = DirectMode;
			}
			else
			{
				Mode = FastMode;
			}
			StartAnalyzing(); 
		}
	}


	// output
	if (Mode == FastMode) {
		if (Sending) {
			Send();
			Sending = false;
			Help();
		}
		delay(500); //only relevant while recording in FastMode 
	}
	else if (Recording) { // && (Mode == DirectMode)
		while (ReadPosition != WritePosition) { // if recording is stopped, the remaining data will also be sent
			if (ReadPosition == 0) {
				SendLine(TimestampBuffer[ReadPosition], ChannelBuffer[maxSamples - 1], ChannelBuffer[ReadPosition]);
			}
			else {
				SendLine(TimestampBuffer[ReadPosition], ChannelBuffer[ReadPosition - 1], ChannelBuffer[ReadPosition]);
			}
			ReadPosition++;
			if (ReadPosition == maxSamples) {
				ReadPosition = 0;
			}
			if (Serial.available() > 0) {
				return;
			}
		}
		if (BufferOverflow) {
			Serial.println("!!! Bufferoverflow !!!");
			BufferOverflow = false;
			Mode = FastMode;
			Help();
		}
	}
}


// pin change 2 - 7 interrupt
ISR(PCINT2_vect) {
	WritePosition += 1;
	ChannelBuffer[WritePosition] = PIND;
	TimestampBuffer[WritePosition] = mymicros();

	if (Mode == FastMode) {
		if (WritePosition >= Samples) {
			StopRecording();
		}
	}

	else { // if (Mode == DirectMode)
		if (WritePosition == ReadPosition - 10) {
			StopRecording();
			BufferOverflow = true;
		}
		if (WritePosition == maxSamples - 1) {
			WritePosition = 65535U;
			
		}
	}
}

// timer1 overflow interrupt (for mymicros())
ISR(TIMER1_OVF_vect) {
	tmr1ovf++;
}

// timer2 overflow interrupt (for frequency generator with slow frequencies below 30.517578125 Hz)
ISR(TIMER2_COMPA_vect) {
	tmr2ovf++;
	if (tmr2ovf == tmr2ovf_switch) {
		PORTB ^= 0b00001000; //toggle pin 11
		tmr2ovf = 0;
	}
}

unsigned long mymicros() {
	return ((unsigned long)tmr1ovf << 16) + TCNT1;
}


void StartAnalyzing() {
	Serial.print(F("Time[us]"));
	for (j = 0; j < ChannelCount; j++) {
		Serial.print(F(" Channel_"));
		Serial.print(Channels[j]);
	}
	Serial.println();

	WritePosition = 0;
	ReadPosition = 0;

	cli();

	PCMSK2 = 0; // Pin Change Mask Register 2: Only set interrupt for selected Channels (Pins)
	for (i = 0; i < ChannelCount; i++) {
		PCMSK2 |= 1 << Channels[i];
	}
	TCNT1 = 0; // reset timer1 to 0
	tmr1ovf = 0; // reset timer1 to 0
	TimestampBuffer[0] = mymicros();
	Recording = true;

	ChannelBuffer[0] = PIND;
	TCCR1A = 0b00000000;
	TCCR1B = 0b00000010; // start timer1 with prescaler 8 = 2MHz = 0.5us
	TCCR1C = 0b00000000;
	PCICR = 0b00000100; // Pin Change Interrupt Control Register: Set PCI0 for Pins 0 to 7
	TIMSK1 = 0b00000001; // enable timer1 overflow interrupt
	
	sei();
}


void StopRecording() {
	PCICR = 0; //turn of input interrupts
	if (Mode == FastMode) { Sending = true; }
	Recording = false;
	TCNT1 = 0; // reset timer1 to 0
	tmr1ovf = 0; // reset timer1 to 0
}


void Send() {
	for (i = 1; i < WritePosition; i++) {
		SendLine(TimestampBuffer[i], ChannelBuffer[i - 1], ChannelBuffer[i]);
	}
}

void SendLine(unsigned long t, byte chBefore, byte chAfter) {
	Serial.print(t / 2.0); //for prescaler 8 = 2MHz = 0.5 us
	for (j = 0; j < ChannelCount; j++) {
		Serial.print(" ");
		Serial.print((chBefore >> Channels[j]) & 1);
	}
	Serial.println();

	Serial.print(t / 2.0);//for prescaler 8 = 2MHz = 0.5 us
	for (j = 0; j < ChannelCount; j++) {
		Serial.print(" ");
		Serial.print((chAfter >> Channels[j]) & 1);
	}
	Serial.println();
}

void Help() {
	Serial.println();
	Serial.println(F("There are two modes: FastMode and DirectMode."));
	Serial.print(F("While using FastMode you need to send the number of samples to be recorded via SerialMonitor to the Controller (e.g. '140'). This amount of samples will be recorded and printed afterwards. It is possible to detect about 100,000 samples per second, however (as SRAM as limited) only "));
	Serial.print(maxSamples);
	Serial.println(F(" samples can be recorded."));
	Serial.println(F("While using DirectMode, each detected Sample will be printed directly to the SerialMonitor. It is possible to detect about 200 samples per second without running into a buffer overflow. Faster samplerates will lead to a buffer overlow and recording will be stopped automatically."));
	Serial.println(F("To call DirectMode, you need to send a '0' via SerialMonitor to the controller."));
	Serial.println(F("There are six channels that can be recorded (pins 2 - 7). To set the supervised channels, enter e.g. 'c245' to observe pins 2, 4 and 5. The input pins are internally connected to pull-up resistors. It doesn't matter how many channels are observed, the possible recordable samplerate is not effected."));
	Serial.println(F("The current recording can be aborted by sending 'x' to the controller. The recored samples will be printed out."));
	Serial.println(F("It is possible to call a frequncy generator on pin 11. To enable it, you need to send e.g. 'f123.456' via SerialMonitor, where the number represents the frequency (in this case 123.456 Hz). A frequency between 0.007451 and 8,000,000 Hz can be chosen, however not infintely variable. The exact output frequency is printed below. The maximum recording speed (samplerate) is not influenced by the frequency generator."));
	Serial.println(F("All inputs need to be completed with a 'LineFeed' (see setting in SerialMonitor)."));
	Serial.print(F("Active Channels ("));
	Serial.print(ChannelCount);
	Serial.print(F("): "));
	if (ChannelCount > 0) { Serial.print(Channels[0]); }
	for (i = 1; i < ChannelCount; i++) {
		Serial.print(F(", "));
		Serial.print(Channels[i]);
	}
	Serial.println();
	Serial.print(F("Current frequency [Hz]: "));
	Serial.println(f,3);
	Serial.println();
}

Schematics

simple setup for self testing
Schaltplan goikteyxa8

Comments

Similar projects you might like

Simple Ultrasonic Distance Measurer With LCD Display

Project showcase by onatto22

  • 5,697 views
  • 4 comments
  • 19 respects

Arduino Ohm Meter

Project showcase by lucian_vdo

  • 4,184 views
  • 4 comments
  • 11 respects

Version 2.0 Advanced Attendance System (Without Ethernet)

Project tutorial by Team GadgetProgrammers

  • 16,941 views
  • 45 comments
  • 64 respects

Arduino Sunflower: An Electronic Sun-Dancer

Project tutorial by SURYATEJA

  • 6,356 views
  • 4 comments
  • 20 respects

Cable Tester RJ45 (Ethernet)

Project tutorial by Andreaberri

  • 5,840 views
  • 0 comments
  • 10 respects
Add projectSign up / Login