Está en la página 1de 24

XMOS startKIT: Introduction, Terminology/Architecture,

Getting Started and Example Programs


Posted by shabaz in Internet of Things on May 5, 2014 12:56:00 AM

This is part 1 in a series of XMOS startKIT posts. The following topics are covered here:
Introduction/Terminology/Architecture
Running Tasks in Parallel
XMOS and IoT
Installing the development environment xTIMEcomposer
Building and running an Example Program
Example: Controlling a Servo
Example: Multi-tasking
Troubleshooting/Debugging

Part 2 is called XMOS startKIT: Building an XMOS and Raspberry Pi Robot XMP-1 and it covers these topics:
Robot introduction. Hardware and Software overview and Circuit/Wiring diagram
Serial Peripheral Interface (SPI) introduction
Connecting the Raspberry Pi to the XMOS startKIT
SPI implementation on the XMOS and Raspberry Pi boards
Implementing multiple servo control
Creating a Raspberry Pi web server and application using Javascript and Node.js
Hardware assembly of a robot

Part 3 is called XMOS startKIT: XMOS and Raspberry Pi Oscilloscope XAE 1000 and it covers these topics:
Oscilloscope Introduction
SPI transmit and receive between the Raspberry Pi and the XMOS startKIT
Using the analog to digital converter (ADC) on the startKIT
Real-time traces/graphics in a web browser

Introduction
This post documents how to get started with XMOS startKIT which is an interesting board recently released (well, 5 months
ago). The board is only slightly larger than a credit card (less than a centimetre longer) and packs an XMOS chip with multiple
processors (more on that later), built-in 4-channnel ADC, touch sensors, integrated programmer/debugger, LEDs, switch and
places for connectors. It costs under 12 including VAT (also available from Newark).
It was found that the XMOS startKIT is fantastic at handling timed events and multitasking. The development environment is
fairly straightforward to use (it is based on Eclipse), and the programming language is C with a few extensions to handle
multitasking requirements. Today, if you want to handle input/output at a relatively high speed with very high accuracy and easeof-use, XMOS devices are very high up on the list of suitable devices.
The startKIT has a connector placeholder which is ideal for communicating to the Raspberry Pi, however for these first steps the
board was used standalone. (Note - to use with a Raspberry Pi, a DIL pin header strip is required and a ribbon cable assembly; the
board is not intended to be plugged on top).
For some initial experiments the XMOS board was used to connect to a hobby servo, and the on-board LEDs were used to test
multitasking capabilities. A great debugging tool called XScope was also tried out briefly. Information on all that is further
below.

First, some terminology needs to be addressed. It can be confusing, because the terminology used to describe XMOS technology
has changed over time. So, the terminology and architecture is discussed below briefly. It can be skipped if you want to just get
on with setting up the XMOS programming environment and running some demo code. You probably do want to read it at a later
stage though.

XMOS Terminology and Architecture


We are used to hearing about multi-cored chips which have multiple processors per chip, or at least multiple instances of most
parts of a complete processor. In XMOS terminology, the term core means multiple instances of some parts of a complete
processor and the sharing of some things not present on non-XMOS processors. This may sound like a bad thing, but read on and
you'll find it isn't. In fact the implementation is a good thing for certain tasks; the kind of tasks that embedded systems often
require. (Ill call these cores XMOS cores from now on, to make it clear). Totally unique non-shared instances of groups of
XMOS cores can be placed on the same silicon; these unique instances are called tiles in XMOS terminology.
A diagram can say a thousand words; the diagram below (taken from the xCORE architecture PDF document) shows in red the
XMOS cores (8) per tile (two tiles in this diagram).

The device on the startKIT also happens to contain two tiles, each with 8 XMOS cores. One tile is available for user programs
(tile #1), the other one (tile #0) is dedicated as a debugger on the startKIT.
XMOS have taken many of the essential bits of operating systems (i.e. traditionally software-based) and have implemented them
in hardware. This means that you dont need to run (say) uC/OS or other lightweight real-time OSs, and you can still achieve
multi-tasking.

In addition, you get ultra-low jitter, latency and the ability to prescribe timing (things still extremely difficult to achieve with
traditional real-time OSs on conventional processors) for one of the main things youd want an embedded processor to handle input/output.
This is the kind of stuff that is possible with FPGAs, and with non-XMOS processors running at a very high speed, but XMOS
devices allow you to do this automatically with no software overhead, and with a standard programming language C with a
few extensions. The extensions are designed to be as simple as possible to use (almost FPGA test-bench like) for handling I/O
timing, and the message-passing/process synchronisation concepts are expressed as close as possible to the way conventional
OSs handle this.
This all sounds impressive (and it is), but XMOS devices take this a step further and make the message-passing/process
synchronisation transparent to the programmer, so that the actual execution can occur between XMOS cores residing on the same
tile, or on separate tiles on the same physical chip, or on separate chips connected with wire links. The hardware responsible for
all that is known as xCONNECT (shown in blue on the right side in the diagram above). Again, some terminology needs
explaining; xCONNECT is a term that appears to encompass links and switches. The links and switches allow the connection
between the cores, regardless of where they reside. The links and switches are used to create point-to-point virtual circuits or
channels which can be opened up for as long as required, to pass information. The channels can traverse multiple physical chips
to get to the correct destination. Each chip is connected to its closest neighbours using one or more sets of wire links (for a short
distance of the order of several centimetres; for a longer distance additional circuitry would be required). At any one time
multiple channels can exist; there is a quality of service scheme that exists too.
Back to the XMOS cores in a tile; as mentioned earlier, each XMOS core has some dedicated hardware, and some shared
hardware. The dedicated part is a separated set of registers per XMOS core. The shared part is memory and pipeline, but also a
hardware scheduler used for implementing the process state and dispatching instructions into the pipeline in a round-robin
fashion. Another major part of the secret sauce concerns the ability to timestamp events. XMOS instructions can be used to let an
XMOS core sleep until one of a number of events occurs (such as a pin going high); when the event does occur, there is no delay
waiting for a context-switch (which entails copying registers to memory and vice-versa) to occur as with a regular software-based
OS. The scheduler merely needs to change the state so that that the sleeping XMOS core can execute. This is therefore fast and
saves many clock cycles.
You may be wondering how timing can be guaranteed if XMOS cores can sleep. It is true that when many XMOS cores are
asleep, the remainder ones will run faster. However, you can guarantee that the amount of processing time that each running one
will receive will be a minimum of 1/x processor cycles, where x is the total number of XMOS cores currently running within a
tile. The guarantee allows you to know beforehand if your code can execute in time to handle input/output. Actual I/O will still be
timed precisely because I/O is time stamped. You can schedule input to be captured at specific times, or to be sent out at specific
times. If the XMOS core needs to wait until that time occurs, then it will do so, allowing more processing time for the remainder
running XMOS cores.
The timestamping is simply a breeze to use. The following line of code will instruct the device to set the pin led logical high
after time 100, and then low after a further time 100. Put into a loop, the LED would flash with precise timing.
+ expand sourceview plain

1.
2.

led @ 100 <: 1


led @ 100 <: 0

In short, this is by far the easiest to use yet most effective way of implementing timed I/O that Ive ever seen with any
microprocessor and OS. With non-XMOS devices high-level constructs could be used to implement a similar style of I/O
programming, but they would not be able to define timing down to the nanosecond level with low jitter. With XMOS, you can.
PWM could be implemented in a similar way. Here, a PWM output is produced with on-time equal to the period width (set to a
range between 0 and 1000 in this example). There is a real-world example of this further down in the post, controlling a servo.
+ expand sourceview plain

1.
2.
3.
4.
5.

while(1)
{
t+=width;
outpin @ t <: 0;
t+=1000-width; outpin @ t <: 1;
}

Running Tasks in Parallel


Another common task is of course the handling of multiple tasks. As is clear from the earlier text, XMOS cores run in a
preemptive manner with round robin scheduling. From the main() function it is possible to assign processes to run on separate
XMOS cores using the following syntax:
+ expand sourceview plain

1.
2.
3.
4.
5.

par
{
task1();
task2();
}

It is possible to select specific tiles with some modified syntax (the tiles can be off-board if a configuration file is modified to
contain the node routing identifiers and routing table).
Since you may want to achieve some process synchronisation/communication between the cores, there are a few different
methods of doing this. One simple way is to create a channel (the concept of a channel was described earlier) and send data down
it. The receiving task can sit and wait for the message using a select statement.
The main() function can define a channel using the chan variable type, and the tasks can receive it as a parameter.
+ expand sourceview plain

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.

int
main(void)
{
chan c;
par
{
task1(c);
task2(c);
}
// ...
}

To send a message from one task to another:


+ expand sourceview plain

1.

c <: value;

To receive the message:


+ expand sourceview plain

1.
2.
3.
4.
5.
6.

select
{
case c :> inval:
printf(message received: %d\n, inval);
break;
}

The select statement will cause execution to wait for that XMOS core, until a message is received on the channel. When a
message is received, the hardware scheduler will make the XMOS core active again.

XMOS and IoT


Similar to any other device selection, a determination would need to be made if the XMOS device is suitable for the desired
application. The device on the startKIT has 8 XMOS cores and 64kbyte of memory on-chip. This is fine for many applications
(e.g. audio, motor control) but not ideal for some other applications (for example video capture may require a larger buffer).
Where XMOS devices would absolutely excel in an Internet of Things (IoT) world would be for handling the hundreds of
protocols in systems that exist today that are not connected to the Internet. XMOS devices make superb protocol engines since
timing is easy to implement, and quick to code up without relying on interrupt service routines like with conventional CPUs.
The rest of this post covers installing and getting started with the XMOS startKIT. A few example programs are explored, as well
as some debugging capabilities.

Installing xTIMEcomposer
For this, youll need to register at xmos.com and download xTIMEcomposer Community version from their support pages.
(The screenshots below are for Windows, but the Linux version looked identical. Windows, Mac and Linux are supported, but for
Linux check to ensure your particular distribution is supported).
Double-click to install the software, and accept the defaults where prompted. When installed, start it up.
For documentation, there will be a link in the Windows start menu, which opens a browser with all the online (mostly PDF)
documentation, and this will be used later. All the documentation there is useful and relevant.
At the workspace prompt, put in something appropriate for your needs, and select it to be a default if you wish.

Import an Example Program


As a first step, import an example project. To do this, select Community tab as shown here, and then traverse to The spinning
bar, right-click and import into the workspace.

The example is imported from github. This requires a folder for the import. I created and selected a folder as shown here.

Once the project has been imported, you can expand the project hierarchy to view the source code (main.xc file) as shown below.

Building the project


To build the project, first select the top level of the project hierarchy (app_spinning_bar) and then click on the hammer icon, or
select Project->Build Project.

If all goes well with the build, success is reported at the console at the bottom, as shown below. Youll also see the built file
(app_spinning_bar.xe) in the bin folder in the project hierarchy, and you can view it in Windows Explorer too if desired as shown
in the screenshot here.

Plugging in the startKIT


Now you can plug in the startKIT into your PC if youve not already done so. Note that there is currently a software bug that
means that the USB port on the PC must be USB 2.0, not USB 3.0. Some modern laptops have mostly USB 3.0 ports but may
have a USB 2.0 port perhaps on the back (e.g. Lenovo does this with some ThinkPads). So, plug the startKIT into a USB 2.0 port
and avoid any USB 3.0 ports for now.
When plugged into a port, youll see a message appear to indicate that the device is correctly installed.

Windows device manager could also be checked to confirm all is ok:

Running a Program
In order to run the code, first select Run->Run Configurations, and then double-click on xCORE Application as shown here.

Once youve double-clicked, you should see a display as shown below. You should not have to make any changes here, but just
observe to make sure all the highlighted areas look similar. Notice the Target drop-down should say something like XMOS
startKIT connected to . If it does not, then click Refresh List. If that doesnt work either, then perhaps the startKIT is
plugged into a USB 3.0 port by mistake.

When Run is clicked, the spinning bar program will get transferred to the startKIT and will begin executing.
From now on, to run or stop the program directly from the main view, you can use the run and stop icons as shown here:

Programming the FLASH


To permanently store the program in FLASH, select Run->Flash Configurations. In the window that appears, double-click on
xCORE Application as shown here:

The display should look as shown here, but confirm all the marked items, and then click Apply and then click Flash.

When you clicked Flash, the console displays some text in red. The warning can be ignored:

Once the console says xflash succeeded as shown above, the USB cable can be disconnected and reconnected to make the
program run.
To re-program the FLASH after making any code changes and recompiling, the lightning shaped icon can be used:

Controlling a Servo
I wrote some quick code to experiment with timing with XMOS. XMOS handles timing quite uniquely, almost hardware testbench language style. Typically with a microcontroller, a clock is set up to interrupt the microcontroller and execute an interrupt
service routine that can then be used to control input/output. For easier control of the exact timing some microcontrollers have
dedicated PWM units with quite a bit of flexibility, but nothing like XMOS have implemented.
As an example with XMOS, for servo PWM, it is possible to use these lines in a loop, where width is a variable corresponding to
microseconds in this case:
+ expand sourceview plain

1.
2.

t+=width;
servo @ t <: 0;
t+=20000-width; servo @ t <: 1;

The code above will set the servo PWM signal low at a period of value width, and high at a period of value 20000-width.
The entire code is pasted below. To use it, you can connect up a servo to the startKIT as shown in the photo:

I used a small, especially low-power servo. For a larger servo you should use an external power supply, do not rely on the USB
supply.
This was the code that was used (note: copying will result in hidden non-text characters in this html content being copied, so
either manually type, or use the text file attached to this post):
+ expand sourceview plain

1.
2.

#include <xs1.h>
#include <stdio.h>

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.

clock clk = XS1_CLKBLK_1;


out buffered port:1 servo = XS1_PORT_1F;
int main(void)
{
int i;
unsigned int t=0;
unsigned int width=1500;
configure_out_port(servo, clk, 0);
set_clock_ref(clk);
set_clock_div(clk, 50); // 1MHz
start_clock(clk);
while(1)
{
printf("Enter servo position in microseconds (1500 is center):\n");
scanf("%d", &width);
for (i=0; i<200; i++) // loop for a while
{
t+=width; servo @ t <: 0;
t+=20000-width; servo @ t <: 1;
}
t+=width; servo @ t <: 0;
}
return 0; // warning on this line is ok
}

The code is designed to be run using the xTIME Composer console, and when run it will prompt the user to enter a value in
microseconds. When this is done, the servo will move to the corresponding position. The code is not practical, because when
disconnected from the PC there is no console capability. However it is absolutely great for code debugging or for testing out
some hardware such as a servo as in this example.
Enter the code into xTIME Composer, build it as before, and then run it. The console will prompt in red text. Click inside the
console window, and type an integer value as shown here:

Once the value is entered, the servo will move to an appropriate position.

A Multitasking Example

In order to experiment with multiple tasks, the simple program here was written, which flashes an X symbol and a + symbol
in a pattern. It will flash the X a few times, then flash the + a few times, and then repeat. The code could be written in a single
process of course, but it is implemented in two tasks to see how multi-XMOS cores are handled.
The first bit of the code just defines the LED patterns that will be used:
+ expand sourceview plain

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.

#include <xs1.h>
/*
* LEDs: the patterns for each bit are:
* 0x80000 0x40000 0x20000
* 0x01000 0x00800 0x00400
* 0x00200 0x00100 0x00080
*
* To get the desired value, OR the bits that
* you want to remain OFF
*/
port leds = XS1_PORT_32A;
unsigned int leds_value[]={
0xE1F80, // Blank
0xA0280, // Plus symbol
0x41500}; // X symbol
port pin = XS1_PORT_1F; // for debugging with a scope

The next part of the code defines a task which just periodically sends a message to another task:
+ expand sourceview plain

1. void task1(chanend c)
2. {
3.
while(1)
4.
{
5.
delay_milliseconds(1000);
6.
c <: 2;
7.
delay_milliseconds(1000);
8.
c <: 1;
9.
}
10. }

The receiving task is shown below. Its not particularly tidy code, but the interesting bit is the select statement. Notice that it
waits for two different types of events. The first event is timer expiry every 100 msec. The other event is a message received via
the channel. When either event occurs, the hardware scheduler will automatically put the task into running state.
+ expand sourceview plain

1.
2.
3.
4.
5.
6.
7.
8.
9.

void task2(chanend c)
{
int i;
unsigned char led_sel=1;
unsigned char toggle=0;
timer t;
unsigned int time;
t:>time;
while(1)

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. }

{
select
{
case t when timerafter(time+(100*1E5)) :> time: // 100*1E5 is 100msec
if (toggle & 0x01)
{
leds <: leds_value[led_sel];
pin <: 1;
}
else
{
leds <: leds_value[0];
pin <: 0;
}
toggle^=0x01;
break;
case c :> i:
if (i==1)
led_sel=1;
else
led_sel=2;
break;
} // end select
} // end while

The main() function is responsible for indicating that both tasks need to run in parallel:
+ expand sourceview plain

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.

int
main(void)
{
chan c;
par
{
task2(c);
task1(c);
}
return(0);
}

Thats it! When run, the behaviour defined earlier will execute.

Troubleshooting XMOS programs


The printf and scanf statements were used earlier, and they are great for testing out software. Other tools are available too. Of
particular interest is a tool that can be used for debugging, profiling and pulling out useful performance detail. It was explored
very briefly. The tool is called XScope.
It can of course be difficult troubleshooting multi-threaded programs because you dont know where each thread has got to, so
typically people tend to use printf statements everywhere. With XScope it is possible to plot user-defined states.

To use XScope, the header file xscope.h needs to be included in the program. Also, double-clock on the Makefile in the Project
Explorer, and you should see a XMOS Application Makefile Editor appear.

Traverse to the Xcc Flags section and append -fxscope to the flags list as shown above, and then click File->Save.
Then, go to Run->Configurations and change the Target I/O options setting from JTAG to xSCOPE.

Next, click on the XScope tab, and change the Mode from Disabled to Offline [XScope] Mode as shown below. The, click
Apply and then Close.

Now the code can be modified to insert the instrumentation. In the main() function, the following line can be added:
+ expand sourceview plain

1.

xscope_register (1, XSCOPE_STATEMACHINE , " State Transitions ", XSCOPE_UINT, " State ");

Now, whenever there is a state change, the following code can be added, where x is a number that will represent the state.
+ expand sourceview plain

1.

xscope_probe_data(0, x);

I inserted such a line in the main() function, twice in task1 (after each delay_milliseconds function) and in task2 to record the
LED toggled state.
When the code is compiled and run, a file gets created called xscope.xmt. Click Terminate to stop the program, and you will see
the file in the Project Explorer. Double-click the file, and the Offline Scope will appear where the console view was. Check the
State checkbox. You will see a view similar to the one shown below (you can use the zoom keys to the right of the console view
to expand in). This shows the states and the time spent in each state. The states are shown on the vertical axis (marked 0,1, 2,
etc). Time is on the horizontal axis.

XScope is capable of a lot more. It can also plot analog values. For example, the xscope_register function can be called as
+ expand sourceview plain

1.

xscope_register(1, XSCOPE_CONTINUOUS , " Continuous Value 1", XSCOPE_UINT , " Value ");

The same xscope_probe_data command as before will now plot analog values. Multiple sets of data can be captured
simultaneously using this syntax:
+ expand sourceview plain

1.
2.
3.

xscope_register (3, XSCOPE_STATEMACHINE , " State Transitions ", XSCOPE_UINT, " State ",
XSCOPE_CONTINUOUS , " Continuous Value 1", XSCOPE_UINT , " Value ",
XSCOPE_CONTINUOUS , " Continuous Value 2", XSCOPE_UINT , " Value ");

Here is an example of analog values output:

Summary
It was fairly painless to begin to use the startKIT, and although some learning is needed to make full use of it, I think it will be
worth it.
At under 12 the startKIT is an extremely low cost board, and will excel at anything timing related.
For IoT projects, the startKIT would be the ideal device for implementing the hundreds of protocols that exist with industrial
equipment, some of which would be timing critical.
For home use, the startKIT is perfect for connecting to the Raspberry Pi (this will be explored later).

También podría gustarte