Signal builder
This chapter focuses on the script functions related to building logic signals. Building signals is used for two different purposes:
- Building demonstration signals (when no device is connected). Demonstration signals are very useful when working on a protocol decoder, and they allow specific and repeatable signals to be generated and decoded without having to rely on specific, hard to find or costly hardware.
- Building logic patterns to be generated via a logic analyzer devices that support logic pattern generator feature (like the SQ series logic analyzers). This can be useful if one needs to generate special tests patters and study how a specific device reacts to those patterns. It can also be useful to generate serial communication patterns, like a group of UART words or an I2C packet.
Important note: Since signals are built, and then generated at a later time, it is currently impossible the dynamically change the generated patterns on the fly. To achieve such results, one would need to use another class of devices, that can run a firmware, like the Arduino platform.
There are two main approaches when it comes to logic signals builder scripts. The first is to build the signals from scratch, specifying the high and low levels and the number of sample. This is the simplest approach, but it can become tedious if you wish to generate more sophisticated logic patters. The second approach is to use “builder objects” that are exposed by some scripts. For example, the CAN bus protocol script exposes a builder object that encapsulate all the complexities of CAN bus signal building, like CRC calculations and bit stuffing.
Finally, please note that the signal builder script only focuses on building the signals. Device configuration (like which channel is set as input or output), Logic level (3.3V or 5V), and drive type (push-pull or open drain) can only be set by the user in ScanaStudio internal.
Entry point functions
As described above, two possible use cases exist when it comes to signal builder scripts. The first is when a user adds a protocol script to a demo workspace (no actual device connected) and hits the “run” button. At this moment, ScanaStudio will generate arbitrary signals, and then, will check if the protocol script attached to this workspace has a on_build_demo_signals()
functions and call it.
The second use case, is when a user want to explicitly run the signal builder, which is only possible for devices that support logic pattern generation.
function on_build_signals()
{
// function called when ScanaStudio needs build signals using this script
}
function on_build_demo_signals()
{
// Function called when ScanaStudio needs to build demo signals
}
Sampling rate and device memory
When building signals, at the most basic level, we’re appending samples, one after the other, in a long memory buffer. Those samples are later played back (generated) to output electrical signals. The sampling frequency (the speed at which those samples are played) will define the time of each sample. The total number of samples, will define the length of the pattern. If the user decides to loop this pattern, then the total number of samples will determine the periodicity at which the pattern repeats itself. Finally, one has also to take into consideration that the device that will generate the signals may have limited embedded memory.
All those parameters are very important and need to be taken into consideration. For that reasons, the following functions were created.
ScanaStudio.builder_get_sample_rate()
Description: This function returns the sampling rate used to playback (generate) the samples.
Context: Signal builder
ScanaStudio.builder_get_maximum_samples_count()
Description: This function returns the maximum number of samples that can be stored in a device’s internal memory. If the device has no memory limitation (i.e. streaming the signals directly from the computer), this function will return -1 (at the time this document is written, there are no devices with such unlimited memory).
Context: Signal builder
ScanaStudio.builder_get_samples_acc(channel_index)
Description: This function simply return the total number of samples appended for a specific channel. This function is usually used to stop the script after a channel’s memory become full.
Parameters:
- channel_index: the 0-based index of the concerned channel.
Context: Signal builder
Basic signal builder functions
ScanaStudio.builder_add_samples(channel_index,logic_level,samples_count)
Description: This function appends samples to a channel
Parameters:
- channel_index: 0-based index of the channel to which samples should be appended.
- logic_level: set to 0 to append low level samples, 1 for high level samples.
- samples_count: the total number of samples to append (should be greater than 0)
Return value: Returns
Context: Signal builder
ScanaStudio.builder_add_cycles(channel_index,duty_cycle,samples_per_cycle,cycles_count)
Description: This function appends a cycle (one period of a signal) to a channel
Parameters:
- channel_index: 0-based index of the channel to which cycles should be appended.
- duty_cycle: As the name implies, this is a fraction that represents the ratio between the high and low level samples in that cycle.
- samples_per_cycle: The total number of samples in that cycle
- cycles_count: The total number of cycles
Example
The code below will create 10 cycles, with a 50% duty cycle on channel 4 (remember, channel index is 0 based). Each cycle will have 50 samples, 25 of them with a high level, and 25 with a low level.
ScanaStudio.builder_add_cycles(3,0.5,50,10);
Context: Signal builder
Implementing a builder object
It’s often a good idea to group all the “helper” functions used to build signals for a specific protocol or application in a JavaScript object called ScanaStudio.BuilderObject
. This has two main advantages:
- Your script will be neatly organized: all signal building functions will be grouped in one place
- Your script will be useable by other scripts since it exposes such a “signal builder” object.
This builder object has to have his exact name (ScanaStudio.BuilderObject
), and thus, there may only be one builder object in a given script. Here is an example implementation of such a builder object:
function on_build_signals()
{
// function called when ScanaStudio needs build signals using this script
var my_builder = ScanaStudio.BuilderObject;
mu_builder.configure(1,115200);
my_builder.generate_one_byte(10);
my_builder.generate_one_byte(20);
my_builder.generate_one_byte(30);
}
ScanaStudio.BuilderObject = {
my_ch: 0,
my_baud : 9600,
generate_one_byte : function(data_byte)
{
// Build the data byte accodring to your protocol
// ex: ScanaStudio.builder_add_samples(my_ch,1,20)
},
configure: function(channel,baud)
{
this.my_ch = channel;
this.my_baud = baud;
}
};
Although this script does not generate any meaningful data, its simplistic approach will help you to understand the usage of the builder object. As you can see, all variables and functions related to signal building are grouped in one object. The ability to instantiate several objects (like the object my_builder
) gives a lot of flexibility and allows easy code recycling. For instance, another builder object may be instantiated in the on_build_demo_signals()
entry point function.
Using a builder object from another script
Making use of an existing builder object from another script implies calling the function ScanaStudio.load_builder_object(script)
to load the builder object. Then, this object can be used as any other JavaScript object.
Let’s assume we want to use the builder from the previous example in another script. Let’s assume the previous example belongs to a script called “my_script.js”:
function on_build_signals()
{
// function called when ScanaStudio needs build signals using this script
var my_builder = ScanaStudio.load_builder_object("my_script.js");
my_builder.my_ch = 1;
my_builder.my_baud = 115200;
my_builder.generate_one_byte(10);
my_builder.generate_one_byte(20);
my_builder.generate_one_byte(30);
}
As you can see, we only had to change a single line (compared to the previous example), which is the line that loads the builder object. Past this point, the my_builder object behaves exactly as if it was included in your script. As a matter of fact, behind the hood, ScanaStudio will actually appends the builder object to the end of your script before it’s executed.
One big advantage of this method is that you may load different builder objects from different files, hence building very sophisticated signals using few, simple, easy to read lines of code.
Putting it all together
Example 1
Suppose we want to write a script that builds a 25% duty cycle square wave, on channel 2 (channel index 1), with a frequency of 100KHz. The example code below shows how this could be achieved.
function on_build_signals()
{
var samples_per_cycle = ScanaStudio.builder_get_sample_rate() / 100e3;
var maximum_cycles_count = Math.floor(ScanaStudio.builder_get_maximum_samples_count() / samples_per_cycle);
ScanaStudio.builder_add_cycles(1,0.25,samples_per_cycle,maximum_cycles_count);
}
Example 2
This overly simplified example shows how BuilderObject
can be used to encapsulate the functions related to servo motor signal generation. The example below may need more development to do a good job controlling a servo motor, but we kept it simple for the purpose of making it easier to follow.
Please note that the ScanaStudio.BuilderObject
could be replaced by any other object name, it would perfectly work for this example, but it would not allow it to be used by other scripts via the load_builder_object(script.js)
function. For this reason, it’s recommend to use this exact naming convention for the BuilderObject
.
function on_build_signals()
{
var servo_signal_builder = ScanaStudio.BuilderObject;
servo_signal_builder.set_channel(2);
servo_signal_builder.generate_one_cycle(-90);
servo_signal_builder.generate_one_cycle(0);
servo_signal_builder.generate_one_cycle(90);
}
ScanaStudio.BuilderObject = {
servo_ch: 0,
min_pulse_ms: 1,
max_pulse_ms: 2,
min_angle: -90,
max_angle: +90,
set_channel : function (ch)
{
this.servo_ch = ch;
},
generate_one_cycle : function(angle)
{
var samples_per_20ms = (20e-3) * ScanaStudio.builder_get_sample_rate();
var angle_ratio = ((angle-this.min_angle)/(this.max_angle - this.min_angle))
var pulse_width_ms = this.min_pulse_ms + (angle_ratio * (this.max_pulse_ms - this.min_pulse_ms));
var duty_cycle = pulse_width_ms / 20;
ScanaStudio.builder_add_cycle(this.servo_ch,duty_cycle,samples_per_20ms);
}
};