drshihan
Published © GPL3+

DigitalAnalyzer

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

BeginnerShowcase (no instructions)2,763
DigitalAnalyzer

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
×1

Software apps and online services

Microsoft Excel

Story

Read more

Schematics

simple setup for self testing

Code

Source Code

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

Credits

drshihan

drshihan

0 projects • 1 follower

Comments