Variable bindings support interfacing to fixed-length variables or buffers in the target from the host. However, variable bindings aren’t appropriate for situations where data is transferred as a data stream, such as serial/UART data, audio data, and so on. For these types of interfacing between the host and target, Virtuoso streams are needed.
Virtuoso “Up Streams” and “Down Streams” are the same in terms of the type of data sent between the target and host. They only differ in how they are accessed by the consumer of the data stream. In the case where the target wants to send data to the host, the target application code needs to call a function to have the framework send the data to the host. The host, in turn, needs an event notification from the target that stream data is available. Due to the MVVM concept of the View in the host being above the view model and the model, the data flow is conceptualized as going up from the target to the host, thus streams sending data from the target to the host are called “Up Streams”, whereas data flowing from the host down to the target are called “Down Streams”.
An Up Stream is created by opening the Target Model Builder for the target that will be sourcing the stream data, selecting the Up Streams tab and then selecting “New”, either by clicking the New button in the lower-left corner, or by right-clicking in the tab area and selecting new, or by typing CTRL+N.
The New Up Stream creation wizard simply prompts you to specify an Up Stream name that is unique to the target, and which is compatible with C variable naming conventions. Thus, we can simply enter “MyUpStream” to create an Up Stream on my target named MyUpStream:
When you create an Up Stream on a target the Up Stream is added as an output port on the target in the schematic editor, as shown below.
Additionally, the Virtuoso.h header file in the target project is re-rendered by the framework, and now includes a new UpStream handle definition, as shown:
To send data to the host from the target C code, you just need to call either the WriteBufferToVirtuosoUpStream function or the WriteStringToVirtuosoUpStream function, using the hMyUpStream handle as the first argument. Use the ‘Buffer’ function when you’re sending fixed-length data, and use the ‘String’ function for null-terminated ASCII strings.
Now that we’ve created an UpStream on our target, we can open our TargetModel class and find the UpStream Property.
The property that is rendered is an ISourceStream interface defined in the Virtuoso.Port.Standard assembly, which we can view by right-clicking on IStreamSource and selecting “Go To Definition…”.
The IStreamSource interface consists of a Stream Name, a DataReceived event, and a method for flushing the stream. The “Up” reference for Up Streams reflect the relationship of the data flow between the target and the host, that data is flowing up from the target to the host. However, the interface object that is exposed, “IStreamSource”, is more generic, because the consumer of the up stream in general doesn’t care whether the data source is from a target or another component created in the host. For example, a console window displaying ASCII data as it comes in could display debug messages printed from the target, or data coming in from the host’s RS-232 port. Therefore, host components used in the framework generally are only concerned with whether data is being sourced or consumed.
Another important thing to note is that the UpStream exposed on the target model is an IStreamSource interface read-only property. Or said differently, the UpStream is exposed as a read-only property on the target model, and this property is itself an IStreamSource interface which is its own object. This provides the loose coupling of dependency management discussed in the Variable Binding section.
For downstream data going from the host to the target, a method is called to pass the data into the framework, which generates a callback in the Target. To create a down stream, first implement a function in the target to handle the downstream data when it is received. The pattern for this function is as shown in the notes in the Virtuoso.h header file:
// Call the ISR whatever you want
void MyDownStreamISR(unsigned char * pSrc, int Length)
// Do something with the data
This function is usually located in a virtual driver C file and doesn’t require declaration in a header file. Build your target so the Framework knows the function is there. Then open the target model builder and go to the Down Streams tab. Right click to add a new Down Stream, and then start typing the name of your function and you will see it show up.
Select your callback, and then specify a down stream name, which is set to the name of your callback ISR. You can change the name to something different if you like, for example instead of “MyDownStreamISR” you could name the Down Stream “MyDownStream”. Click Done, and the down stream will be created and available as an input port on the target schematic component.
You can see the down stream on the C# target model as an IStreamConsumer read-only property:
We can see the definition of the IStreamConsumer interface by right-clicking on it and selecting “Go To Definition…”:
Here we see that in addition to a few administrative methods and a StreamName property, we have a method to send data to the stream consumer to be consumed. When the ProcessData method is called on the IStreamConsumer interface, the framework internally calls the data received handler in the target to process the data.
It is important to note that while in the most general sense, the UpStream and DownStream features of the framework provide data stream communications, the implementation also supports message-based communication. This is because there is always a one-to-one correspondence between when data is passed into the framework and when data is pushed to the destination in either the target or the framework. If you pass the buffer “Hello World” into a down stream’s SendData method, the target callback will be called exactly once with the “Hello World” buffer referenced. If you break it up into 11 separate function calls, passing in one character at a time, the target callback will be called 11 times. Thus it’s important to note that the interface readily supports message based communication, and it is up to the implementer of the objects using the IStreamSource and IStreamConsumer interfaces to coordinate on messaging conventions.
The last thing to be aware of regarding streams is the threading model. When the target writes data to an Up Stream, this data is picked up and notified to the host in a background thread. Thus, the callback in the host is executed on the background thread. Usually this can be processed directly in the background thread, however be aware that there could be instances where you need to dispatch the handling of the data to the display thread. When dispatching to the display thread, you should use the function Application.Current.Dispatcher.BeginInvoke, so that the data handling code is executed asynchronously and does not hang up and slow down the background thread.
Similarly, when sending data to the target from the host in a Down Stream, the callback handler will not be executing on the target thread, but will by default be executing in parallel with the target thread. Often the code that parses the data can do so in parallel with the target thread, for example if the results of the parsed data or the data itself is added to a ring buffer which is polled in the target thread. In this case, you will still need to be aware of potential race conditions when the ring buffer or other data interfacing resource is being accessed, and use the EnterCriticalSection and LeaveCriticalSection functions available in Virtuoso.h to synchronize access to non-thread-safe resources.
Callback functions executed in the target do not execute on the target thread by default. It is up to the developer to either use the EnterCriticalSection and LeaveCriticalSection functions or provide supervisor support to simulate the execution of the callbacks in a simulated interrupt service routine. For the purposes of flexibility of the framework, those issues are left to the developer to implement. Virtuoso does not at this time provide direct support for thread context switching supervisor services.