How to communicate between tasks with interfaces
version
1.0.0
scope
Example.
This code is provided as example code for a user to base their code on.
description
How to communicate between tasks with interfaces
boards
Unless otherwise specified, this example runs on the SliceKIT Core Board, but can easily be run on any XMOS device by using a different XN file.
Interfaces provide the most structured and flexible method of inter-task communication. An interface defines what kind of messages can be passed and what data is passed with them. For example, the following interface declaration defines two message types:
interface my_interface { void msgA(int x, int y); void msgB(float x); };
Messages can take the same arguments that any C function can. The arguments define what data is sent with the message. If an array argument is given for an intra-tile communication then the array is not copied but the compiler will optimize it to just pass a reference to the array, which allows the recipient to access the array in memory.
Message passing is done over unidirectional connections obeying an interface protocol. One end of the connection is declared as the client end and sends messages and one end is the server end which receives messages.
To send messages, a task can take an argument which is the client end of an interface connection and then use that variable to send messages:
void task1(interface my_interface client c) { // c is the client end of the connection, // let's send a message to the other end. c.msgA(5, 10); }
Code can wait to receive messages at the server end with the select construct. The select will wait until a message arrives:
void task2(interface my_interface server c) { // wait for either msgA or msgB over connection c. select { case c.msgA(int x, int y): printf("Received msgA: %d, %d\n", x, y); break; case c.msgB(float x): // handle the message printf("Received msgB: %f\n", x); break; } }
Note how the select lets you handle several different types of message. The language extension also lets the compiler allocate local variables for the incoming data that the message handler can access.
Code can wait for many different types of messages (using different interface types) from many different sources. Once one of the messages has been received and the select has handled the event, the code will continue on.
When tasks are run you can join them together by declaring an instance of an interface and passing it as an argument to both tasks:
int main(void) { interface my_interface c; par { task1(c); task2(c); } return 0; }
The types of the functions tell the tools which end is the server and which is the client. The compiler also checks that each connection gets exactly one server and one client end.