TS-4200 Software Development

From Technologic Systems Manuals

Most of our examples are going to be in C, but Debian will include support for many more programming languages. Including (but not limited to) C++, PERL, PHP, SH, Java, BASIC, TCL, and Python. Most of the functionality from our software examples can be done from using system calls to run our userspace utilities. For higher performance, you will need to either use C/C++ or find functionally equivalent ways to perform the same actions as our examples. Our userspace applications are all designed to go through a TCP interface. By looking at the source for these applications, you can learn our protocol for communicating with the hardware interfaces in any language.

The most common method of development is directly on the SBC. Since debian has space available on the SD card, we include the build-essentials package which comes with everything you need to do C/C++ development on the board.


Vim is a very common editor to use in Linux. While it isn't the most intuitive at a first glance, you can run 'vimtutor' to get a ~30 minute instruction on how to use this editor. Once you get past the initial learning curve it can make you very productive. You can find the vim documentation here.

Emacs is another very common editor. Similar to vim, it is difficult to learn but rewarding in productivity. You can find documentation on emacs here.

Nano while not as commonly used for development is the easiest. It doesn't have as many features to assist in code development, but is much simpler to begin using right away. If you've used 'edit' on Windows/DOS, this will be very familiar. You can find nano documentation here.


We only recommend the gnu compiler collection. There are many other commercial compilers which can also be used, but will not be supported by us. You can install gcc on most boards in Debian by simply running 'apt-get update && apt-get install build-essential'. This will include everything needed for standard development in c/c++.

You can find the gcc documentation here. You can find a simple hello world tutorial for c++ with gcc here.

Build tools

When developing your application typing out the compiler commands with all of your arguments would take forever. The most common way to handle these build systems is using a make file. This lets you define your project sources, libraries, linking, and desired targets. You can read more about makefiles here.

If you are building an application intended to be more portable than on this one system, you can also look into the automake tools which are intended to help make that easier. You can find an introduction to the autotools here.

Cmake is another alternative which generates a makefile. This is generally simpler than using automake, but is not as mature as the automake tools. You can find a tutorial here.


Linux has a few tools which are very helpful for debugging code. The first of which is gdb (part of the gnu compiler collection). This lets you run your code with breakpoints, get backgraces, step forward or backward, and pick apart memory while your application executes. You can find documentation on gdb here.

Strace will allow you to watch how your application interacts with the running kernel which can be useful for diagnostics. You can find the manual page here.

Ltrace will do the same thing with any generic library. You can find the manual page here.

1 Cross Compiling

While you can develop entirely on the board itself, if you prefer to develop from another x86 compatible Linux system we have a cross compiler available. For this board you will want to use this toolchain. To compile your application, you only need to use the version of GCC in the cross toolchain instead of the version supplied with your distribution. The resulting binary will be for ARM.

[user@localhost]$ /opt/arm-2008q3/bin/arm-none-linux-gnueabi-gcc hello.c -o hello
[user@localhost]$ file hello
hello: ELF 32-bit LSB executable, ARM, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.14, not stripped

This is one of the simplest examples. If you want to work with a project, you will typically create a makefile. You can read more about makefiles here. Another common requirement is linking to third party libraries provided by Debian on the board. There is no exact set of steps you can take for every project, but the process will be very much the same. Find the headers, and the libraries. Sometimes you have to also copy over their binaries. In this example, I will link to sqlite from Debian (which will also work in the Ubuntu image).

Install the sqlite library and header on the board:

apt-get update && apt-get install -y libsqlite3-0 libsqlite-dev

This will fetch the binaries from the internet and install them. You can list the installed files with dpkg:

dpkg -L libsqlite3-0 libsqlite3-dev

The interesting files from this output will be the .so files, and the .h files. In this case you will need to copy these files to your project directory.

I have a sample example with libsqlite3 below. This is not intended to provide any functionality, but just call functions provided by sqlite.

#include <stdio.h>
#include <stdlib.h>
#include "sqlite3.h"

int main(int argc, char **argv)
	sqlite3 *db;
	char *zErrMsg = 0;
	int rc;
	printf("opening test.db\n");
	rc = sqlite3_open("test.db", &db);
		fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
		fprintf(stderr, "SQL error: %s\n", zErrMsg);
	printf("closing test.db\n");
	return 0;

To build this with the external libraries I have the makefile below. This will have to be adjusted for your toolchain path. In this example I placed the headers in external/include and the library in external/lib.

CFLAGS=-c -Wall

all: sqlitetest

sqlitetest: sqlitetest.o
        $(CC) sqlitetest.o external/lib/libsqlite3.so.0 -o sqlitetest
sqlitetest.o: sqlitetest.c
        $(CC) $(CFLAGS) sqlitetest.c -Iexternal/include/

        rm -rf *o sqlitetest.o sqlitetest

You can then copy this directly to the board and execute it. There are many ways to transfer the compiled binaries to the board. Using a network filesystem such as sshfs or NFS will be the simplest to use if you are frequently updating data, but will require more setup. See your linux distribution's manual for more details. The simplest network method is using ssh/sftp. You can use winscp if from windows, or scp from linux. Make sure you set a password from debian for root or set up a shared key. Otherwise the ssh server will deny connections. From winscp, enter the ip address of the SBC, the root username, and the password you have set or the use of a shared key. This will provide you with an explorer window you can drag files into.

Note: Setting up a password for root is only feasible on the uSD image.

For scp in linux, run:

#replace with your app name and your SBC IP address
scp sqlitetest root@

After transferring the file to the board, execute it:

ts:~# ./sqlitetest 
opening test.db
closing test.db

2 Kernel Compile Guide

WARNING: Backup any important data on the board before continuing.

For adding new support to the kernel, or recompiling with more specific options you will need to have an X86 compatible linux host available that can handle the cross compiling. Compiling the kernel on the board is not supported or recommended. Before building the kernel you will need to install a few support libraries on your workstation:



yum install ncurses-devel ncurses
yum groupinstall "Development Tools" "Development Libraries"


apt-get install build-essential libncurses5-dev libncursesw5-dev

For other distributions, please refer to their documentation to find equivalent tools.

Set up the Sources and Toolchain

# Download the cross compile toolchain (EABI)from Technologic Systems:

# Extract to current working directory:
tar xvf arm-2008q3-72-arm-none-linux-gnueabi-i686-pc-linux-gnu.tar.bz2

# Download the Kernel sources
wget ftp://ftp.embeddedarm.com/ts-socket-macrocontrollers/ts-4200-linux/sources/linux-2.6.36-ts-src-latest.tar.gz

# Extract the Kernel Sources
gzip -dc linux-2.6.36-ts-src-latest.tar.gz | tar xf -

cd linux-2.6.36-release/

Configure the Sources

The kernel sources need a few variables to be exported.

# Set the CROSS_COMPILE variable to the absolute path to the toolchain.  This may be different for your system:
export CROSS_COMPILE=/opt/4200/arm-2008q3/bin/arm-none-linux-gnueabi-

# Normally, ARCH will be set based on your build hosts architecture.
export ARCH=arm

This sets up the default configuration that we ship with for the TS-4200

make ts4200_defconfig

This will bring up a graphical menu where you can edit the configuration to include support for new devices. For Example, to include support for a Prolific USB to serial adapter you would go to 'Device Drivers -> USB Support-> USB Serial Support' and then select 'USB Prolific 2303 Single Port Serial Driver'. Since the kernel only has a limited space try to build build drivers as modules whenever possible.

make menuconfig

Build the kernel Once you have it configured, start building. This usually takes a few minutes.

make && make modules

The new kernel will be at "arch/arm/boot" in a compressed format called zImage. The uncompressed version is simply called Image. With the default partitioning scheme it is REQUIRED that the kernel be < 2620416 bytes in size. If you need to shorten the size, try including your changes to the kernel as modules instead. Otherwise you will need to resize the kernel partition to account for the size difference.

Install the kernel Now that you have a kernel you can install it as you would our stock. See the #Backup / Restore section for examples on writing this to disk.

Install Modules Script to make directory and install modules


The build-module-bundles.sh script is meant to be run as a user (not root) and will create directories and install modules to them. The directory structure is created at /home/`whoami`/src/ts-4200/dist/<ts4200 kernel release number>/modules-install/. In that directory is initrd-modules/, lib/, and modules-<ts4200 kernel release number>.tgz.

initrd-modules/modules.tar.gz is a tarball that contains a minimal number of modules. This tarball needs to be copied to the initrd partition of the boot media. The boot process of the board will automatically un'tar this and insert any necessary modules.

Now the contents of lib/ can be copied to the root of the TS-4200. It is also possible to copy over modules-<ts4200 kernel release number>.tgz to the TS-4200 and unpack it in the root linux directory. You may want to remove any old modules on the board in /lib/modules/* before copying them to the board to rule out any incompatibilities. Once you boot up to the board, you need to run 'depmod' once to calculate module dependencies. You can then run 'modprobe' with the device drivers you've added. For the Prolific adapter added in the example, this would be:

modprobe pl2303

3 Getting Started with tsctl

First, download and install the latest version of tsctl as documented in the Getting Started Guide.

In the examples below you can follow along by typing the commands (the portion after the prompt) and expect to see the output below the prompt. Note that while the results should be similar, in some cases you might not see exactly the same results due to variations in execution.

Let's start the tsctl shell:

$ tsctl
tsctl 0.93-ts (Dec  7 2012 15:34:29)
Type "?" to get context-sensitive help.

Let's check that we really have a TS-4200.

tsctl> System ModelId

That doesn't look like 4200! The reason is that the ModelId (and BaseBoardId) commands return 0x4200 (hexadecimal 4200), but the tsctl shell defaults to decimal output. (Note that the command line defaults to hexadecimal!)

We can change to hexadecimal output using the mode command. There is no output from this command.

tsctl> Mode Hex

Now let's try again.

tsctl> System ModelId

In addition to the base the output is represented in, you can also change the general format. The tsctl shell defaults to "NoAssign" mode in which only the input or inputs are printed, each on a separate line. The "Assign" mode (which is the default for the command line) prints a descriptive name=value pair for each value output.

tsctl> Mode Assign

Now let's re-run the previous command. If your version of tsctl is linked against libreadline, you can use the up-arrow twice to pull the System ModelId command back instead of typing it out.

tsctl> System ModelId

Let's run the command again.

tsctl> System ModelId

Notice that the name changed slightly. The first part of the name is the class, the second it the field name (which is also the function name in cases where the value is directly returned at the C API level), and the third is a number. This number is the index of the number of times this class function has been called during the current invocation of tsctl.

Let's switch back to NoAssign mode. Although the field names can be useful if you aren't familiar with what the output fields mean, mostly Assign mode is meant for evaluating by the shell to set variables.

tsctl> Mode NoAssign

Now let's see what base board we have. Your output will differ depending on what you actually have installed.

tsctl> System BaseBoardId

Let's switch back to decimal output.

tsctl> Mode Dec

Now, let's enter the System class onto the command stack so that we don't have to type it repeatedly.

tsctl> System
tsctl System>

Note that our first command, "System" was incomplete, which caused tsctl to push it onto the command stack. This can be used to reduce typing when repetitive sequences start with the same partial command. To pop an element off the stack in the shell, enter an empty line.

One feature of libtsctl is the System Map, which contains name/value pairs. The name is any string (8-bit Array) and the value is an integer. First, let's see how many entries are stored in the table.

tsctl System> MapLength
tsctl System>

The entries in the table are stored sorted by name (case-insensitively). Entries are used to store DIO names, enumerated values, attributes, and user-defined name/value pairs. If you want to see the entire table you can get each entry one at a time by index number, starting at 1:

tsctl System> MapGet 1
tsctl System>

The first line contains the name, while the second contains the value.

To get several entries at once let's first put the function name on the command stack

tsctl System> MapGet
tsctl System MapGet>

One feature of tsctl is the ability to separate commands by a semi-colon. In the shell (e.g. bash) this requires quoting the semi-colon; in the tsctl shell it does not. Let's get the next ten entries:

tsctl System MapGet> 2;3;4;5;6;7;8;9;10;11
tsctl System MapGet>

What happened here is that each number got appended as the parameter to the System MapGet function.

Hit enter on an empty line to pop the MapGet function off the command stack.

tsctl System MapGet> 
tsctl System>

Let's look at some of the attributes available. We can find the name of a connector by number as follows:

tsctl System> MapLookupPartial attrib.Connector.Name. 2
tsctl System>

How many connectors are there?

tsctl System> MapLookup attrib.Connector.Count
tsctl System>

Depending on your system, you may have more than this! How many pins does connector 1 have?

tsctl System> MapLookup attrib.Connector.1.Pins
tsctl System>

Most boards have a green and red LEDs, but the DIO number differs from board to board. Let's see what DIO numbers they are on this board. If the lookup fails, a negative value will be returned.

tsctl System> MapLookup;GREEN_LED;RED_LED;;
tsctl System>

Note that we used two semi-colons with nothing between them to pop the MapLookup function back off the stack.

What connector is the GREEN_LED on? We can determine this by searching the connector attribute for a value corresponding to the DIO number of GREEN_LED. In the above case, that value is 128. However we can also use GREEN_LED, as the tsctl text interface will automatically translate it to the correct value:

tsctl System> MapLookupPartial attrib.Connector. GREEN_LED
tsctl System>

Note that in a few rare cases the above lookup will conflict with another attribute and may not work. This is because MapLookupPartial looks for a name starting with the specified string, having the specified value. If we specified a value of "100" we might match "attrib.Connector.1.Pins", "attrib.Connector.2.Pins", or "attrib.Connector.1.8" as these all have a value of 100.

The interpretation of 2.8 is "Connector 2, Pin 8". This is also known as "CN2_8", by combining the name of the connector with the pin number on that connector.

tsctl System> MapLookup CN2_8
tsctl System>

NOTE: For widest cross-platform compatibility it is recommended to perform lookups based on connectors, rather than board specific DIO names.

How many DIO are on the board?

tsctl System> MapLookup attrib.DIO.Count
tsctl System>

If you have a peripheral board such as a baseboard or PC-104 board with supported DIO, you will see a higher number than this. Note that this number counts all raw, internally addressable DIO, regardless of whether or not they are brought out to pins. As such the actual number of usable DIO will frequently be lower than the number contained in this attribute.

Let's pop the System class from the command stack.

tsctl System> 

What revision of the FPGA is on the board?

tsctl> System FPGARevision

We can read the I2C (TWI) temp sensor on the TS-4200 with tsctl. First let's switch to hexadecimal output for Arrays of bytes.

tsctl> Mode AHex

Next, we need to make sure that the pins that are used for TWI are correctly set up, as they are frequently multi-function pins. The easiest way to make sure the pins are set correctly is to lock the function you are going to use. As part of locking the pin will be initialized to the correct function. For some boards (notably the TS-4800) the TWI must always be locked during use as it uses an underlying operating system file to perform its functionaity.

tsctl> TWI Lock 0 0

Now verify that the temperature sensor is present at device address 0x49.

tsctl> TWI Read 0x49 1 7 2

If the bytes read back are not 0x01:0x90 (the first value returned is the result code), then the temperature sensor is not present, or there is another problem with the TWI bus. Assuming we get the correct response back, we can next send the commands to start an aquisition, and read back the raw temperature data from the sensor.

tsctl> TWI Write;0x49 1 1 0x40:0x0;0x49 0 0 0x40:0x0;;Read 0x49 1 0 2;;

The value returned can be converted to a temperature as follows:

tempC = (byte[0] * 256 + byte[1]) / 128
tempC = (0x12 * 256 + 0x60) / 128
tempC = 36.75C

Note: The ts8160ctl sample application provides the above TWI temp sensor reading functionality using the -t option.

Be sure to unlock any resource when you are done. Best practice is to hold a lock for the minimum amount of time necessary.

tsctl> TWI Unlock 0 0

There are also several timing based functions you can use. For instance, you can delay for a specific amount of time. You should see a delay of approximately 1 second (1,000,000 microseconds) when running the command below:

tsctl> Time;Delay 1000000
tsctl Time>

Note that the Delay function does not return a value. If you want to see the actual number of microseconds delayed, use the Wait function instead:

tsctl Time> Wait 1000000;;

The only difference between Wait and Delay is that the former returns the number of microseconds actually spend waiting, and the latter returns no value. This is an important nuance in the TCP classes, as in certain modes functions that return no data are not called until subsequent functions that do return data are called. This allows for things such as commanding a relay to cycle power on the board issuing the command (so long as it isn't the board interpreting the command), since otherwise the command to restore power would not be sent before power was cut.

Short delay times are generally only useful when using direct access from C. Otherwise, the overhead of parsing the command, sending it across TCP, and interpreting it on the server will be significant in comparison to the amount of time to delay.