This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CCS/TMS320F28069M: The Beginner's Guide to Datalogging in GUI Composer Using Streamsaver.

Part Number: TMS320F28069M

Tool/software: Code Composer Studio

This is a statement rather than a question.  The GUI Composer and the Streamsaver widget are impressive tools.  However, the documentation is sorely lacking.  I found it hard as a beginner to figure out how to use it effectively, and wasted days on trial and error coding experiments to figure out what worked.

To save others from this unhappy experience, I've written a beginners guide, attached and copied in full below.

____________________________________________________________________________________________

The Beginner's Guide to Data Logging in GUI Composer Using Streamsaver.txt

BACKGROUND:

I wrote this as a guide for someone trying to do datalogging using GUI Composer for the first time.
I found the learning process to take much longer than necessary due to lack of documentation, so
I've recorded what I learned here.

My background is an analog circuit designer. My coding skills are about what you would expect
from an analog guy - i.e. elementary. I started this project with no experience in microcontroller
programming except a little Arduino programming, and no experience with Code Composer Studio,
GUI Composer, or JavaScript. I may not be great at coding but I like to write. So I have
written this for those who are as noobish as me.

I am no expert. I am sure there are errors and omissions in this guide. I hope those who are
more knowledgeable will add their corrections and additions.

INTRODUCTION:

All the following assumes you are using a Code Composer Studio (CCS) project, and are connected to a TI
controller with the XDS interface over the USB connector.

Code Composer Studio is the programming environment for the TI microcontrollers, and is based on the
Eclipse Integrated Development Environment (IDE).

The XDS interface is a JTAG debug probe. It connects the PC to the controller over a USB line. It
uses the JTAG serial interface on the controller to program the memory of the controller and to read or
write program variables and internal registers during operation. There are several different versions:
XDS-100, XDS-110, etc, but the differences between them are of no particular concern here as long as you
are using the right one for your controller.

The GUI Composer (GC) is a TI Cloud Tool for writing a GUI interface to your controller project. It can talk to your
controller over the XDS in the same manner that the debugger in CCS does. This gives it access
to the global variables in your CCS project. It can read and write those global variables, allowing
you to control your project from the PC and log data while it is running. The elements in GUI Composer
are called widgets.

Streamsaver is a special GUI Composer widget that allows streaming of data to a file on the computer that is
running the browser. It cannot be found in the GC Component Palette, and has to be installed by manually
editing the index.gui file and a JavaScript file. It does not appear as a graphical object in the GUI, but
can be controlled by regular buttons or other widgets in the GUI.

STARTING POINT:

Start with the "XDS_SaveVariablesToFile" example.

- First, you have to log in to the TI site using
your myTI user credentials, or register if you don't already have a password.

- Go to the TI Cloud Tools Gallery (https://dev.ti.com/gallery/) and search for "XDS". Here is the direct link:
dev.ti.com/.../

- Import it into your GUI Composer using the dial icon at the bottom left corner.

There are two files used in a GUI Composer with Streamsaver application:
- The index.gui file, which controls all the GUI widgets
- You can switch between the GUI editor and the text editor for this file using the "<>" button in the toolbar.
- A JavaScript file, which implements the Streamsaver functions, and any data processing functions you want to add.

** TIP **
Save your files frequently. The GUI Composer will log you out or change servers occasionally with no warning, and
you can lose unsaved edits.

ADDING STREAMSAVER TO YOUR PROJECT:

This assumes you have a new or existing project you want to use Streamsaver in.
Streamsaver involves certain additions to the index.gui file in your project, and the
addition of a new JavaScript file into your project folder, as described below.

Install Streamsaver in your index.gui file:

1) Add a new JavaScript file from the menu: File -> New -> Application JavaScript File
It can be named anything; the name isn't important.

2) Add a link to Streamsaver in your index.gui file, in the top section with the other link statements.
Add the following line anywhere in this section:
<link rel="import" href="components/ti-widget-streamsaver/ti-widget-streamsaver.html">

3) Add the following line in the bottom of the index.gui file, among the other ti-widget statements:
<ti-widget-streamsaver id="ti_widget_streamsaver"></ti-widget-streamsaver>

Linking the GUI to the JavaScript:

Setting up the links between the GUI buttons and the JavaScript functions.

There are three buttons in the GUI, labeled "SAVE START", "SAVE STOP", AND "SAVE ABORT".
Each of these buttons will trigger a function call in the JavaScript file.
You can also add other buttons to call other functions in the JavaScript file, for example,
a "SAVE DATA" function that will save some data that you specify when the button is clicked.

** IMPORTANT **
You must set the "onclick" property of the buttons to link them to the JavaScript functions:
- In the text editor for the index.gui file, find the "ti_widget_button" statements for the buttons you
want to link to JavaScript functions.
- Each button must have an "onclick" property assigned to call its corresponding function in the JavaScript file.
For example, the "SAVE START" button calls "var saveStart = function(filename)" in the JavaScript.
To link the button to the function, the "SAVE START" button must include this property:
onclick="saveStart()"
- The onclick property does not correspond to any field in the GUI editor for the ti_widget button.
The only way to add or edit the onclick property is in the text editor.

HOW THINGS WORK:

GUI Bindings:

- A GUI widget can be bound to a global variable in your controller code. The widget can display the variable, and can
also be used to change the variable.
- To set a binding between a widget value and a code variable, open the properties menu for that widget. For example,
add a new Number Box Widget. Click the link symbol that looks like (-) to the right of the value property field.
Make sure the first box says "ti_model_program". Then type in the name of your global variable in the second box.
- In some widgets the property that can be bound to a variable is called something other than "value". For example, you
would bind the "checked" property for a toggle switch, or the "on" property for an LED.
- Save and run the GUI. If it is working right, the new number box should display the value of the global variable
in the controller code. You can click on the box and change the value.

IQ values:

IQ values are often used in integer math in the controller code. The real value of a Qn integer X is equal to X/2^n.
To display the real value of a IQ number in a widget binding, append ".$qn".
Example: the binding of a numberbox to display the real value of Q12 variable Buck_IL1 is "Buck_IL1.$q12"
To convert an Qn integer in JavaScript, divide by 2^n as follows:
var Number_real = Number_q12 / Math.pow(2, 12)

JavaScript Bindings:

The JavaScript code can also have bindings to the GUI widgets. The JavaScript code can read and write the values of
the GUI widgets that they are bound to.

The mechanism for making a binding is the gc.databind.registry.bind method. Its use is described in the example newfile_0.js
in the "XDS_SaveVariablesToFile" example.
When you create a new JavaScript file using the menu File -> New -> Application JavaScript File, it contains a lot of
commented out templates for this and other methods. Here is my simplified version of it. In its simplest form the method
call consists of an input binding, an output binding, and a function to compute the output value from the input value.
The following code reads the state of the INPUT numberbox, multiplies it by 2, and puts it in the OUTPUT numberbox.

gc.databind.registry.bind(
// output binding
'widget.OUTPUT.value', // A numberbox labeled OUTPUT. MUST preceded the widget name with "widget."
// input binding
'widget.INPUT.value', // A numberbox labeled INPUT. MUST preceded the widget name with "widget."
// getter function - returns the output value based on the input value.
function(value)
{
return 2*value; // returns the computed value to the output binding.
}
);

It is also possible to bind directly to a controller global variable instead of a widget value. Simply use the controller
global variable in quotes as an input or output binding. In the following example, the input binding in the previous example
has been replaced with a binding to a controller global variable.

gc.databind.registry.bind(
// output binding
'widget.OUTPUT.value', // A numberbox labeled OUTPUT. MUST preceded the widget name with "widget."
// input binding
'Buck_IL1', // A controller global variable
// getter function - returns the output value based on the input value.
function(value)
{
return 2*value; // returns the computed value to the output binding.
}
);

More complicated bindings can be used. Multiple bindings for both inputs and outputs are possible. It is also possible to
make a reverse function (a "setter" function) that will modify the input bindings based on the output of the output bindings.
Some of these possibilities are illustrated in examples below.

The gc.databind.registry.bind method is event driven. It is called when one of the binding values change. It does nothing
if the binding values are constant. (It may only be called if one of the input bindings change if only a "getter" function
is used.)

GUI Bindings + JavaScript Bindings:

A GUI widget can have both a binding to the controller variable and a binding to the JavaScript code. This allows the
JavaScript code to read and write controller code global variables through the GUI bindings. Alternatively, both the
widget and the JavaScript can bind to the same controller global variable.

For example:
- In the GUI, number box widget labeled "IL" is bound to controller global variable "Buck_IL1".
- In a JavaScript gc.databind.registry.bind, the input binding is 'widget.IL.value'.
- The controller global variable, the value in the number box widget, and the input binding value in the JavaScript
databinding will all have the same value, and can be read or written in all three places
(controller code, GUI number box, JavaScript databinding).

The preceding example discusses a sort of serial binding: the widget is bound to the controller global variable, and the
JavaScript value is bound to the widget value. It is also possible to bind a widget and a JavaScript databinding to the
same global variable in parallel.
- In the GUI, number box widget labeled "IL" is bound to controller global variable 'Buck_IL1'.
- In a JavaScript gc.databind.registry.bind, the input binding is global variable 'Buck_IL1'.
- The controller global variable, the value in the number box widget, and the input binding value in the JavaScript
databinding will all have the same value, and can be read or written in all three places
(controller code, GUI number box, JavaScript databinding).

(Other information I have seen on the E2E forum suggests that the JavaScript can interact directly with the controller code
global variables. However, I have only succeeded in linking JavaScript to the controller global variables in the
context of the gc.databind.registry.bind method.)

Data Processing using JavaScript Bindings:

The gc.databind.registry.bind method is event driven and is called every time one of the bindings changes. This makes
it a good place to put code that is processing data from the controller. You can put code in the function that has nothing
to do with computing the specific value for the output binding. Below is an example with extra processing code added to the
getter function. In addition to computing the output value from the input value, it updates three JavaScript global variables based
on the current widget values, and calls a datalogging function. These additional functions don't necessarily have anything to
do with the output binding, but are put here because this code runs when the bindings change.

var X;
var Y;
var Z;
.
.
.
gc.databind.registry.bind(
// output binding
'widget.OUTPUT.value', // A numberbox labeled OUTPUT. MUST preceded the widget name with "widget."
// input bindings, arranged as a multi-element object in name:value pairs
{
in: 'widget.INPUT.value', // A numberbox labeled INPUT. MUST preceded the widget name with "widget."
x: 'widget.X.value', // A numberbox labeled X. Bound to a control code variable
y: 'widget.Y.value', // A numberbox labeled Y. Bound to a control code variable
z: 'widget.Z.value' // A numberbox labeled Z. Bound to a control code variable
},
// getter function - updates JavaScript variables, calls a datalogging function, and computes the output value.
function(value)
{
X = value.x; // assign the value of widget x to the JavaScript variable X
Y = value.y; // assign the value of widget y to the JavaScript variable Y
Z = value.z; // assign the value of widget z to the JavaScript variable Z
saveLogData() // call a JavaScript data logging function

return 2*value.in; // returns the computed value to the output binding.
}
);

You might wonder if you are using the getter function only to do datalogging whether an output binding
is needed. It appears that some output binding is needed to make the code run, but it doesn't
have to do anything useful. The output binding can be an invisible number box, for example.

Streamsaver Functions:

The Streamsaver template contains 4 function calls: to start, stop, and abort the streamsaver, and to save data.
These functions can be called by binding them to a button on the GUI, or by calling them from somewhere else in the
JavaScript code.
Note: to bind these functions to a GUI element, the property " onclick="function_name()" " must be manually added
to the properties of the button widget that will call that function. See ADDING STREAMSAVER TO YOUR PROJECT above
for more details.

var saveStart = function(filename) {
if (!filename) filename = 'mydata1.txt';
mystream = streamSaver.createWriteStream(filename);
mywriter = mystream.getWriter();
frame_cnt = 0;
};
var saveStop = function() {
if (mywriter) {
mywriter.close();
mywriter = null;
LOGisRunning = 0;
}
};
var saveAbort = function() {
if (mywriter){
mywriter.abort('reason');
mywriter = null;
LOGisRunning = 0;
}
};
var saveData = function(data) {
if (mywriter) {
mywriter.write(data);
}
};

Saving your Data:

To save your own data you can have a function that calls saveData() after formatting your data as you please.
If you want to save text data that can be read in a text editor or imported into a spreadsheet, you
must use the TextEncoder function to convert the byte streams into ASCII strings. Here is an example of
my code that saves text data:

var LOGisRunning = 0; // set to 1 after data acquisition begins
var frame_cnt; // counts lines in data file
var Vset_old; // the set voltage on the buck controller
var VOdata; // the output voltage of the buck controller
var ILdata; // the output current of the buck controller

var saveLogData = function() {
var uint8array;
// only run this if the streamsaver object "mywriter" exists
if (mywriter){
if (!LOGisRunning){
// first time through write header to text file
uint8array = encoder.encode('Frame NO., Vset (V), VO (V), IL (A)' + "\n");
saveData(uint8array);
LOGisRunning = 1;
} else {
// assemble the ouput data string and call the saveData function
uint8array = encoder.encode(frame_cnt + ', ' + Vset_old + ', ' + VOdata + ', ' + ILdata + "\n");
saveData(uint8array);
frame_cnt++;
}
}
};

The example code in XDS_SaveVariablesToFile skips a couple of steps shown above. It doesn't use
the saveData() function, electing instead to call mywriter.write() directly from a gc.databind.registry.bind
method. I've reformatted and commented the code from the example:

gc.databind.registry.bind(
// the output binding is a widget
'widget.ti_widget_textbox.value',
// the input binding is a controller global variable
"blinkCounter",
// getter function encodes data then calls mywriter.write()
function(value)
{
// if dropdown widget is set to save blinkCounter, and streamsaver object "mywriter" exists
if (templateObj.$.ti_widget_droplist.selectedValue == "blinkCounter" && mywriter) {
// encode the data
var encodedData = myTextEnc.encode(value.toString() + '\n');
// write to streamsaver
mywriter.write(encodedData);
}
// return the value of blinkCounter to the textbox widget
return value;
}
);

EXAMPLES of gc.databind.registry.bind() CALLS IN JAVASCRIPT

Example 1: Calculate something using data from the controller.

- The goal is to multiply the global variables Buck_IL1 and Buck_Vout1.
- These are global variables in the controller C code, not the JavaScript file
- Numberbox widgets are used to bind to the global variables from the controller:
- A numberbox widget labeled IL is bound to the Buck_IL1 variable.
The binding of its value is set to ti_model_program: Buck_IL1.$q12. The ".$q12 indicates a Q12 variable.
- Another numberbox widget labeled VOUT is bound to the Buck_Vout1 variable, which is also a Q12 variable.
- The numberbox values bound to the JavaScript are the real values, not the Q12 integers.
- A third numberbox widget labeled POWER is used to display the output power. It has no bindings.
- A gc.databind.registry.bind method in the JavaScript is used to read the current and voltage numberboxes,
perform the calculation, and display the result in a numberbox.
- It takes two bindings for its input and a single binding for its output

gc.databind.registry.bind(
// output binding
'widget.POWER.value', // numberbox labeled POWER with no bindings, used to display computed power
// input binding - the input binding is a two element object, with the values written as name:value pairs.
{
il: 'widget.IL.value', // the value of numberbox labeled IL is bound to ti_model_program: Buck_IL1.$q12 (.$q12 indicates a Q12 value)
vo: 'widget.VO.value', // the value of numberbox labeled VO is bound to ti_model_program: Buck_Vout1.$q12 (.$q12 indicates a Q12 value)
},
// getter
function(value)
{
return value.il * value.vo; // returns the computed power to the numberbox labeled POWER.
}
);

Example 2: Adding datalogging functions:

- This example adds some extra code for updating JavaScript global variables and calls a function to save the data.
- A third element is added to the input bindings. There is still a single output binding.

gc.databind.registry.bind(
// output binding
'widget.POWER.value', // numberbox labeled POWER with no bindings, used to display computed power
// input bindings - the input binding is a two element object, with the values written as name:value pairs.
{
il: 'widget.IL.value', // the value of numberbox labeled IL is bound to ti_model_program: Buck_IL1.$q12 (.$q12 indicates a Q12 value)
vo: 'widget.VO.value', // the value of numberbox labeled VO is bound to ti_model_program: Buck_Vout1.$q12 (.$q12 indicates a Q12 value)
vs: 'widget.VSET.value' // the value of numberbox labeled VSET is bound to ti_model_program: Buck_Vset1.$q12 (.$q12 indicates a Q12 value)
},
// getter
function(value)
{ // the following lines store the data from the widgets with bindings in global JavaScript variables and call saveLogData
ILdata = value.il; // save the current value in a JavaScript variable
VOdata = value.vo; // save the voltage value in a JavaScript variable
Vset_old = Vset_new; // update the set voltage variables
Vset_new = value.vs; // save the new set value in a JavaScript variable
if (Vset_new != Vset_old){ saveLogData(); } // if the set voltage has changed, call the data logging function shown in an example above
return value.il * value.vo; // returns the computed power to the numberbox labeled POWER.
}
);

Example 3: Multiple input bindings to multiple output bindings

- This example simply maps the values from a 3-element input binding to a 3-element output binding.
- All the widgets are numberboxes.
- The input numberboxes are labeled Vstart, Vend, Vinc
- The output numberboxes are labeled Vtest1, Vtest2, Vtest3
- The input numberboxes are bound to controller code global variables.
- The output numberboxes are not bound to controller code global variables, but could be.
- The output bindings are only refreshed when an input binding changes.

gc.databind.registry.bind(
// output binding is a three element object in name:value pairs
{
t1: 'widget.Vtest1.value',
t2: 'widget.Vtest2.value',
t3: 'widget.Vtest3.value',
},
// input binding is a three element object in name:value pairs
{
v1: 'widget.Vstart.value',
v2: 'widget.Vend.value',
v3: 'widget.Vinc.value',
},
// getter
function(value)
{ // function simply maps the input values to the output values
return {t1: value.v1, t2: value.v2, t3: value.v3};
}
);

The Beginner's Guide to Datalogging in GUI Composer Using Streamsaver.txt
Fullscreen
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
The Beginner's Guide to Datalogging in GUI Composer Using Streamsaver.txt
BACKGROUND:
I wrote this as a guide for someone trying to do datalogging using GUI Composer for the first time.
I found the learning process to take much longer than necessary due to lack of documentation, so
I've recorded what I learned here.
My background is an analog circuit designer. My coding skills are about what you would expect
from an analog guy - i.e. elementary. I started this project with no experience in microcontroller
programming except a little Arduino programming, and no experience with Code Composer Studio,
GUI Composer, or JavaScript. I may not be great at coding but I like to write. So I have
written this for those who are as noobish as me.
I am no expert. I am sure there are errors and omissions in this guide. I hope those who are
more knowledgeable will add their corrections and additions.
INTRODUCTION:
All the following assumes you are using a Code Composer Studio (CCS) project, and are connected to a TI
controller with the XDS interface over the USB connector.
Code Composer Studio is the programming environment for the TI microcontrollers, and is based on the
Eclipse Integrated Development Environment (IDE).
The XDS interface is a JTAG debug probe. It connects the PC to the controller over a USB line. It
uses the JTAG serial interface on the controller to program the memory of the controller and to read or
write program variables and internal registers during operation. There are several different versions:
XDS-100, XDS-110, etc, but the differences between them are of no particular concern here as long as you
are using the right one for your controller.
The GUI Composer (GC) is a TI Cloud Tool for writing a GUI interface to your controller project. It can talk to your
controller over the XDS in the same manner that the debugger in CCS does. This gives it access
to the global variables in your CCS project. It can read and write those global variables, allowing
you to control your project from the PC and log data while it is running. The elements in GUI Composer
are called widgets.
Streamsaver is a special GUI Composer widget that allows streaming of data to a file on the computer that is
running the browser. It cannot be found in the GC Component Palette, and has to be installed by manually
editing the index.gui file and a JavaScript file. It does not appear as a graphical object in the GUI, but
can be controlled by regular buttons or other widgets in the GUI.
STARTING POINT:
Start with the "XDS_SaveVariablesToFile" example.
- First, you have to log in to the TI site using
your myTI user credentials, or register if you don't already have a password.
- Go to the TI Cloud Tools Gallery (https://dev.ti.com/gallery/) and search for "XDS". Here is the direct link:
https://dev.ti.com/gallery/view/11101/XDS_SaveVariablesToFile/ver/1.0.0/
- Import it into your GUI Composer using the dial icon at the bottom left corner.
There are two files used in a GUI Composer with Streamsaver application:
- The index.gui file, which controls all the GUI widgets
- You can switch between the GUI editor and the text editor for this file using the "<>" button in the toolbar.
- A JavaScript file, which implements the Streamsaver functions, and any data processing functions you want to add.
** TIP **
Save your files frequently. The GUI Composer will log you out or change servers occasionally with no warning, and
you can lose unsaved edits.
ADDING STREAMSAVER TO YOUR PROJECT:
This assumes you have a new or existing project you want to use Streamsaver in.
Streamsaver involves certain additions to the index.gui file in your project, and the
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

  • Ciaran,

    We very much appreciate you taking the time to write this guide and post it here. I am sure it will greatly benefit many others working with GUI Composer. 

    Thank you!

  • You're welcome.

    Here are a few other points that are obvious to an experienced programmer but might not be obvious to a beginner:

    MULTIPLE DATABIND STATEMENTS:

    You can have many gc.databind.registry.bind() method statements, each one to do a specific task.

    DATABIND STATEMENTS WITH NO OUTPUT (TARGET) BINDING:

    You can use 'null' as the output binding and omit the return statement:

        gc.databind.registry.bind('null', 'InputBinding',   
            function(value) 
            {  
               saveData(InputBinding);
    } );

    START AND STOP STREAMSAVER IN JAVASCRIPT CODE:

    Rather than having to press a button to start and stop the Streamsaver you can call saveStart or saveStop from databind functions.

        gc.databind.registry.bind(  //databind with function to start streamsaver before starting a variable sweep
            // output binding
            'ScanGo',           // global variable that starts the voltage sweep
            // input binding
            'widget.ScanGo.checked',    // toggle button widget
            // getter 
            function(value) 
            {  
                if (value) saveStart(); // start streamsaver
                return value;              // set the ScanGo controller global variable to start a variable sweep in controller
            }
        ); 
    gc.databind.registry.bind( //databind with function to stop the streamsaver when a global variable changes from 1 to 0. // output binding (none) 'null', // input binding 'ScanGo', // global variable that starts the voltage scan // getter function(value) { ScanGoOld = ScanGoNew; ScanGoNew = value; if (ScanGoOld && !ScanGoNew) saveStop(); // scan was running but now it's stopped, so stop data logging } );