Visualizing/Graphing the output from the Atmel Studio simulator

1 post / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi everyone!
this is my first post on this site so I hope im posting it in the right place. For the last couple of days I have been playing around with the USI (Universal serial interface) module on my Attiny45 in order to create signals to communicate with other chips that use SPI communications. The Datasheet is really helpful and contains the necessary code to make your Attiny SPI master/slave in no time. The datasheet also explains how exactly the USI interface works but I thought that getting a visual on whats going on would provide even more insight as to what is exactly happening every clock cycle. Well the simulator feature in Atmel Studio (im using version 7) does exactly that, but it would be even more useful to be able to generate a graph that captures the program over many clock cycles rather than just looking cycle by cycle.
I did some googling and found out about stimuli files and how they can be used (among other things) to log your program into a text file. This is cool feature but producing a graph out of the log file requires some program to convert the data to a format that can be easily interpreted and graphed (for instance in Excel).

Well im no programmer but with a little bit of effort I wrote the program in visual studio 2017 C++ to do this. By the way I already found a program (http://www.dresco.co.uk/LogAnalyser) that was written a while back but I think the log file format back then was different and I could not get it working. So here is how I approached this problem:

 

Step One: Create the stimuli file

Create a text file and paste in the following code: 

$log PINB
$startlog output.stim
#70
$stoplog
$break

The code tells the simulator that PINB should be logged, that the log file name is "output.stim", to log over 70 clock cycles then stop logging and  stop the program. Im logging just one register here but you can log as many as you want (worked for me with up to four). Just add " $log REGISTER_NAME " after the first line. Name the text file "StimuliFile.stim" and save it.

 

 

Step Two: Write the program you wish to simulate

Open Atmel Studio 7 and create a new solution for your microcontroller. Write the program that you want to upload to you microcontroller. Im using the following program which continuously transmits a byte over the USI interface. :

#include <avr/io.h>
#define DI PB0	//Data In on PB0 (pin 5)
#define DO PB1	//Data Out on PB1 (pin 6)
#define CLK PB2 //Clock on PB2 (pin 7)
#define CE PB3	//Chip enable on PB3 (pin 2)

void FastSpiWrite(uint8_t data)
{
		USIDR=data;		//load the data to USIDR
		PORTB|=(1<<CE); //enable chip

	//transmit
		USICR=(1<<USIWM0)|(0<<USICS0)|(1<<USITC);
		USICR=(1<<USIWM0)|(0<<USICS0)|(1<<USITC)|(1<<USICLK);
		USICR=(1<<USIWM0)|(0<<USICS0)|(1<<USITC);
		USICR=(1<<USIWM0)|(0<<USICS0)|(1<<USITC)|(1<<USICLK);
		USICR=(1<<USIWM0)|(0<<USICS0)|(1<<USITC);
		USICR=(1<<USIWM0)|(0<<USICS0)|(1<<USITC)|(1<<USICLK);
		USICR=(1<<USIWM0)|(0<<USICS0)|(1<<USITC);
		USICR=(1<<USIWM0)|(0<<USICS0)|(1<<USITC)|(1<<USICLK);
		USICR=(1<<USIWM0)|(0<<USICS0)|(1<<USITC);
		USICR=(1<<USIWM0)|(0<<USICS0)|(1<<USITC)|(1<<USICLK);
		USICR=(1<<USIWM0)|(0<<USICS0)|(1<<USITC);
		USICR=(1<<USIWM0)|(0<<USICS0)|(1<<USITC)|(1<<USICLK);
		USICR=(1<<USIWM0)|(0<<USICS0)|(1<<USITC);
		USICR=(1<<USIWM0)|(0<<USICS0)|(1<<USITC)|(1<<USICLK);
		USICR=(1<<USIWM0)|(0<<USICS0)|(1<<USITC);
		USICR=(1<<USIWM0)|(0<<USICS0)|(1<<USITC)|(1<<USICLK);

		PORTB&=~(1<<CE); //disable chip

}

int main(void)
{

	DDRB|=(1<<DO)|(1<<CLK)|(1<<CE); //make Data out, clock and Chip enable pins outputs
	DDRB&=~(1<<DI); //make DI (data in) pin input if there is any input

	uint8_t data=0b10101100; //Data to be written

	 while (1) 

    {
		 FastSpiWrite(data); // write WData
    }
}

 

Step Three : Simulate and log

Click the Debug menu and then click "Start Debugging and Break". Make sure you choose the simulator as your debug tool. This starts the simulation. At this point I am going to change the state of PB0 (configured as Data In) to 0 so that the input will be held low, as it will be in the actual circuit. This is only for this particular simulation and is not a general step. For this I click the "I/O" button in the toolbar to bring up the I/O window. Then Click "I/O Port (PORTB)" and then click the zeroth bit in the PINB Register to toggle it from 1 to 0.

Now for the stimuli file. Go to "Debug > Set Stimlifile" and open the stimuli file "StimuliFile.stim" that you have created in step 1. Now go to "Debug > Execute Stimulifile". Now you can run the program step by step by clicking F11 or just press F5 if you dont want to wait. After 70 cycles (you can monitor the number of cycles in the by clicking the "Processor Status" button in the toolbar) the stimuli files will stop its action and at this point you can end  the simulation by clicking the red stop button or from the debug menu.You will notice that "output.stim" was created in the folder you used to save your stimuli file. You are done with Atmel Studio so you may close it if you wish.

 

Open output.stim with a text editor. It should look like this :

.
.
.
#2
PINB = 0x10
#11
PINB = 0x12
#2
PINB = 0x1a
.
.
.

The way you read this is : After 2 cycles PINB changed from its last state to 0x10. 11 Clock cycles after that, PINB changed from 0x10 to 0x12, and so on. Notice that you do not get the absolute number of cycles that have passed, but the number of cycles relative to the last change. Another representation of this would thus be :

 

Time PINB
t0 last value
t1=t0+2 0x10
t2=t1+11 0x12
t3=t2+2 0x1a

 

 

So whats left to do now is just convert the output file that we got to a form similar to this which can be easily understood and graphed.

 

Step Four: Converting the log file

Open a new c++ file and copy the following code into it and save :

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//Read a an avr simulator stimuli log file and convert it to a plotable table.
// Currently 10 registers are assumed but any number is possible with by modifying the RegNameArray[10] string array.
//
//   Todo: Expand the register pin name database
//
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include <fstream>
#include <iostream>
#include <string>
using namespace std;

string PINB_Bits[8] = { "PB7","PB6" ,"PB5","PB4","PB3" ,"PB2","PB1" ,"PB0" };
string USICR_Bits[8] = { "USISIE","USIOIE","USIWM1","USIWM0","USICS1","USICS0","USICLK","USITC" };
string USISR_Bits[8] = { "USISIF","USIOIF","USIPF","USIDC","USICNT3","USICNT2","USICNT1","USICNT0" };
string USIDR_Bits[8] = { "D7","D6","D5","D4","D3","D2","D1","D0" };

////////////// functions that are used for the intermediate conversion step//////////////
char get_Line_Datatype(string Line)
{
	if (Line.at(0) == '#') {
		return 'd';
	}
	else {
		return 'r';
	}
}
string get_Register_Name(string Line)
{
	//search the the string for the position of the first space.
	int numberofchars = Line.find(" ");
	string temp;
	temp = Line.substr(0, numberofchars);
	return temp;

}
string get_Register_Value(string Line)
{
	//search the the string for the position of the 0x space.
	int valueposition = Line.find("0x");
	string temp;
	temp = Line.substr(valueposition, 4);
	return temp;

}
string get_Delay(string Line)
{
	//search the the string for the position of the delay information, we dont have to search, we know it is 1.

	string temp;
	temp = Line.substr(1, 2);
	return temp;
}

void convert_structure(string filename)
{
	string CurrentLine;
	string Current_Delay;
	ifstream InputFile(filename); //open the input file
	ofstream OutputFile("converted.txt");
	OutputFile << '\n';
	if (InputFile.is_open())
	{

		while (getline(InputFile, CurrentLine))		//load the current line
		{								            // check which datatype it is
			if (get_Line_Datatype(CurrentLine) == 'd')
			{
				Current_Delay = get_Delay(CurrentLine);
				OutputFile << "\n#" << Current_Delay << '\n';

			}

			else
			{
				OutputFile << get_Register_Name(CurrentLine) << " = " << get_Register_Value(CurrentLine) << '\t';
			}

		}
	}

	OutputFile << "\n#10";

	InputFile.close();
	OutputFile.close();
}

////////////// functions that are used for the second conversion step/////////////////////

int get_FileLength(string filename)
{
	ifstream InputFile(filename); //open the input file and make it readable
	string line;
	int i = 0;
	while (getline(InputFile, line)) //
	{
		i++;
	}
	InputFile.close();
	return i;
}
string get_Line(string filename, int line_number)
{
	ifstream InputFile(filename); //open the input file and make it readable
	string line;
	for (int i = 1; i <= line_number; i++)
	{
		getline(InputFile, line);
	}

	InputFile.close();

	return line;

}
string get_Reg_Value(string Line, string Reg)
{
	int regnamelenght = Reg.length();

	if (Line.find(Reg) != Line.npos) //if Reg can be found in the Line string
	{
		int Regposition = Line.find(Reg); //find the position of Reg in the string
		string temp; //create a temp string
					 //temp = Line.substr(Regposition + regnamelenght + 3, 4);
		temp = Line.substr(Regposition + regnamelenght + 5, 2);
		// 3 because of 3 characters in " = "
		//length of substring is 4 because "0xAA" is four characters long
		return temp;
	}
	else
	{
		return "0";
	}
}
void hexToBinArray(char x, int temparray[4])
{
	//int temparray[4];
	if (x == 'f') { temparray[0] = 1; temparray[1] = 1;  temparray[2] = 1;  temparray[3] = 1; }
	if (x == 'e') { temparray[0] = 1; temparray[1] = 1;  temparray[2] = 1;  temparray[3] = 0; }
	if (x == 'd') { temparray[0] = 1; temparray[1] = 1;  temparray[2] = 0;  temparray[3] = 1; }
	if (x == 'c') { temparray[0] = 1; temparray[1] = 1;  temparray[2] = 0;  temparray[3] = 0; }
	if (x == 'b') { temparray[0] = 1; temparray[1] = 0;  temparray[2] = 1;  temparray[3] = 1; }
	if (x == 'a') { temparray[0] = 1; temparray[1] = 0;  temparray[2] = 1;  temparray[3] = 0; }
	if (x == '9') { temparray[0] = 1; temparray[1] = 0;  temparray[2] = 0;  temparray[3] = 1; }
	if (x == '8') { temparray[0] = 1; temparray[1] = 0;  temparray[2] = 0;  temparray[3] = 0; }
	if (x == '7') { temparray[0] = 0; temparray[1] = 1;  temparray[2] = 1;  temparray[3] = 1; }
	if (x == '6') { temparray[0] = 0; temparray[1] = 1;  temparray[2] = 1;  temparray[3] = 0; }
	if (x == '5') { temparray[0] = 0; temparray[1] = 1;  temparray[2] = 0;  temparray[3] = 1; }
	if (x == '4') { temparray[0] = 0; temparray[1] = 1;  temparray[2] = 0;  temparray[3] = 0; }
	if (x == '3') { temparray[0] = 0; temparray[1] = 0;  temparray[2] = 1;  temparray[3] = 1; }
	if (x == '2') { temparray[0] = 0; temparray[1] = 0;  temparray[2] = 1;  temparray[3] = 0; }
	if (x == '1') { temparray[0] = 0; temparray[1] = 0;  temparray[2] = 0;  temparray[3] = 1; }
	if (x == '0') { temparray[0] = 0; temparray[1] = 0;  temparray[2] = 0;  temparray[3] = 0; }

}

///////////////////////////////////////////////////////////////////////////////////////////
string RegNametoBits(string RegName, int i)
{
	if (RegName == "PINB")
	{
		return PINB_Bits[i];

	}
	if (RegName == "USICR")
	{
		return USICR_Bits[i];

	}
	if (RegName == "USIDR")
	{
		return USIDR_Bits[i];

	}
	if (RegName == "USISR")
	{
		return USISR_Bits[i];

	}
	else {
		string temp = to_string(7-i);
		return RegName+temp; }
}

int main()
{
	////////////// Perform intermediate conversion to the file converted.txt //////////////

	convert_structure("AVRLog.txt");
	int File_Length = get_FileLength("converted.txt");
	//cout << get_FileLength("converted.txt");
	cout << "Input file was converted to converted.txt" << '\n';
	////////////////////////////////////////////////////////////////////////////////////////

	////////////// Acquire logged register names from user//////////////
	int NumOfRegisters = 0;
	cout << "how many registers were contained in the logfile?\n";
	cin >> NumOfRegisters;
	string RegNameArray[10]; //holds the names of the logged registers (change [5] if more registers are available)
	cout << "type in the names of the  registers please.\n";
	for (int i = 0; i < NumOfRegisters; i++)
	{
		cout << "Reg" << i + 1 << ":";
		cin >> RegNameArray[i];
	}
	/////////////////////////////////////////////////////////////////////

	///////////////////// second conversion process to AVRLog1.txt///////////////////////////////////////
	string line;
	int delay;
	int time = 0;
	int nextdelay;
	string RegValueArray[10] = { "00","00","00","00","00","00","00","00","00","00" };
	int temparray[4];

	cout << "   converted file length:" << File_Length << "\n ";

	ofstream outputfile("AVRLog1.txt");

	outputfile << "delay  " << "time  ";    // column headers
	for (int i = 0; i < NumOfRegisters; i++)
	{
		for (int k = 0; k < 8; k++)
		{
			outputfile << RegNametoBits(RegNameArray[i], k) << "  ";
		}
	}
	outputfile << '\n';

	///////////////////////////////////////////////////////////////////////////////////////////////////////////
	for (int i = 3; i <= File_Length - 2; i = i + 2)
	{
		line = get_Line("converted.txt", i);
		delay = stoi(get_Delay(line));
		time = time + delay;
		line = get_Line("converted.txt", i + 1);

		for (int k = 0; k < NumOfRegisters; k++)
		{
			if (get_Reg_Value(line, RegNameArray[k]) != "0")
			{
				RegValueArray[k] = get_Reg_Value(line, RegNameArray[k]);
			}
		}

		line = get_Line("converted.txt", i + 2);
		nextdelay = stoi(get_Delay(line));
		for (int j = 0; j < nextdelay; j++)
		{
			outputfile << delay << "  " << (time + j) << "  ";
			for (int k = 0; k < NumOfRegisters; k++)
			{

				for (int m = 0; m < 2; m++)
				{
					hexToBinArray(RegValueArray[k][m], temparray);
					for (int n = 0; n < 4; n++)
					{
						outputfile << temparray[n] << "  ";
					}
				}

			}
			outputfile << '\n';
		}

		outputfile << delay << "  " << (time + nextdelay) << "  ";
		for (int k = 0; k < NumOfRegisters; k++)
		{
			for (int m = 0; m < 2; m++)
			{
				hexToBinArray(RegValueArray[k][m], temparray);
				for (int n = 0; n < 4; n++)
				{
					outputfile << temparray[n] << "  ";
				}
			}

		}
		outputfile << '\n';

	}

	outputfile.close();

	return 0;
}

 

In the same folder where you saved this file create a new text file called "AVRLog.txt" and copy the content of output.stim into it. This file will be the input to the C++ program. Compile the C++ program and execute. You will be prompted to type in the number of registers you logged and their names. Type in 1 and PINB if you are using my code. If everything was OK with the program two new files will be created in your project folder. The file "AVRLog1.txt" that the program created contains the data in the desired form and now you just have to graph it.

 

Step Five : Graphing and comparing with reality

I use OriginLab to plot my data because you can just drag and drop a text file to import it, but you can do it with other programs as well. After you import it to your program you should see a large table having 10 columns. A delay column (we dont need this one, I just used it to check the program is doing what it should be doing), a time column and one column for each of the 8 bits of the PINB register (see screenshot). Since I am only interested in PB1, PB2 and PB3 I delete the other PINB columns (the ones that I keep are highlighted in the screenshot in grey). For clarity, I also rename the headers to their functional names. To make the waveforms appear stacked above each other I add an offset to the column values which just shifts the waveform in the vertical direction. The waveforms I got can be seen in the left side of the following screenshot:

The time units on the x-axis depend on what clock the chip is using. In the simulation the clock frequency is 1MHz so every unit corresponds to 1 micro second. As a check I uploaded the program to my Attiny45 and probed the signals on a scope. Im using the internal 8 MHz RC Oscillator divided by 8 to get to 1 MHz. Comparing both waveforms you can see that the agreement is perfect and so the program works! For instance you can see the clock signal has a period of 2 micro seconds which is exactly what I measured on the scope using the cursors (see right side of screenshot above).  The relative position of the waveforms in time is also in perfect agreement with what I measured so I can quite safely say the program gives a good indication of what waveforms you should expect to see on your lines.

 

This was the simplest simulation possible because it only logs one register but I already used the program to graph four registers and it worked just as well. The next step would be to include input in the stimuli file and simulate  incoming signals on the PB0 (DI/MISO) pin  (for example a slave device that outputs data to be read by the master) but I havent tried it out yet.

 

Well... That's it. Thanks for reading!

 

 

Some comments about the C++ program

 

-If more than 10 registers are tp be logged change the index in the RegNameArray[10] string array to the desired number. Do the same thing for the RegValueArray[10] string array

 

-It is always assumed that the initial register values are 0x00 so keep that in mind when you try to interpret the first few time steps in your graphs (you could change this if you like in the RegValueArray[10] string array).

-At the end of the data the state from the last step is held for 10 clock cycles (this was just for my convenience but it isnt a part of the simulation) so keep that in mind when you interpret the last few time steps in your graph. 

- Note the conversion program must know the name of the registers you have logged and the names of the pins assigned to that register if you want it to spit out the pin names as the column headers automatically. If it is a register that is not in the database you will just get the name of the register followed by the pin number as you column header. To add new registers you should do the following : If you want PORTB then add the following line in the before your main file
 

string PORTB_Bits[8] = { "PB7","PB6" ,"PB5","PB4","PB3" ,"PB2","PB1" ,"PB0" };

 

then go to the RegNametoBits() function definition and add 

if (RegName == "PORTB")
    {
        return PORTB_Bits[i];
    }