Tsctl
Files | |
---|---|
Latest tsctl sources |
Overview
Tsctl was created as a way to ease software development, allow programs developed for one board to be easily re-used on other boards, and provide a way to control boards from any language and operating system that supports TCP/IP. You can also write your program to talk directly to the hardware via libtsctl. Tsctl has a consistent client API across all access methods. Currently it supports programming in C, Python, shell, Javascript, and Java, as well as any other language in which you can write code to talk to a TCP socket. tsctl is object oriented, and currently defines classes for AIO (Analog I/O, i.e. DAC and ADC), Bus (useful for direct register access), CAN, DIO, EDIO (Extended DIO for PWM, Quadrature counting and more), Pin, Time (timekeeping), TWI, and SPI hardware as well as a System class. Tsctl server runs as a single (user-space) multi-threaded server instance on the board to provide access to board features over TCP/IP.
The older canctl, dioctl, and spictl servers are implemented as libtsctl applications to provide the ability to run those applications on any hardware supported by tsctl.
Support
The following boards are currently supported by tsctl:
TS-Socket CPU |
---|
TS-4200 |
TS-4500 |
TS-4700 |
TS-4800 |
TS-Socket Baseboards |
TS-8100 |
TS-8160 |
TS-8200 |
TS-8390 |
TS-8820 |
TS-8900 |
TS Single Board Computers |
TS-7500 |
TS-7550 |
TS-7552 |
TS-7553 |
TS-7558 |
PC-104 Peripheral Boards |
TS-RELAY8 |
TS-DIO24 |
Design Philosophy
Technologic System develops and manufactures single-board computers. Newly designed boards have features and configuration intended to meet newly emerging needs in the marketplace, as well as to respond to customer feedback concerning what is useful to them. However, over time many boards developed by Technologic Systems share in common certain features such as Ethernet, serial ports, CAN, and so forth. Some of these features are well supported by the Linux operating system which our boards ship with. However some are not, and in other cases the interface to accessing a particular feature has changed over time, sometimes dramatically.
Technologic System products are targeted towards the embedded market. The software development model in this space is often quite different from the rest of the software industry. Embedded devices typically go through a development cycle which terminates in a "frozen" state for all software on the board. Unlike other parts of the industry, upgrades or change of any kind in the state of the software as shipped is undesirable as it represents a high degrees of risk, risk that assumptions other software relies on will no longer be valid resulting in "breakage" of previously working software. As such, it is important that Technologic System provide software for the features on its board which reaches a frozen state quickly, allowing developers using our products to confidently freeze their own software in a timely manner.
At the same time, there are other important, but sometimes conflicting goals. These are:
- To bring new products to market quickly.
- To provide software for all the features on the board.
- To have a uniform interface to each feature allowing code developed for one board to work on another.
To meet these goals, tsctl does the following:
- defines object classes, which encapsulates a specific piece of TS hardware behind a standard, uniform API across all supported TS products.
- provides easy extensibility to add new objects and new API calls on existing objects without breaking any existing code.
- provides a single, centralized, self-contained, backward compatible binary for each platform containing all TS functionality (which conforms to the tsctl spec)
- utilizes pthreads, which allows for high performance locking of individual resources
- provides a consistent interface across C, text and binary protocols, and various languages facilitates backward compatibility at the command line and protocol level with canctl, dioctl, and spictl
- optimizes individual objects for maximum performance where applicable, i.e. CAN which can transmit and receive close to wire speed at 1Mbps via the high performance canctl protocol.
- provides the new tsctl network protocol which allows all objects to be used through a single port, providing sequentiality and simultaneous access to multiple objects through the same protocol and connection.
Getting Started
The tsctl command line client for direct access
To help you get up and running with Technologic Systems hardware as fast as possible we provide tsctl, a libtsctl client/server application.
First, download the latest pre-compiled tsctl binary for your platform.
For the TS-4500, TS-7552, TS-7553 or TS-7558, download the "cavium" binary here
For the TS-4200, TS-4700, or TS-4800, download the "noncavium" binary here
Install the binary anywhere in your PATH. Before installing, you may wish to make sure you do not already have an older version of tsctl by attempting to run the "tsctl" command. If the command is found, be sure to replace the existing binary with the newly downloaded one in order to ensure you are running the most up to date copy.
Now you can see immediate results. Locate the red and green LEDs on your board. For TS-SOCKET boards these LEDs are usually on the base board (e.g. the TS-8XXX series) while TS-75XX boards have the LEDs directly on the board. Type the following commands:
tsctl DIO SetAsync GREEN_LED LOW tsctl DIO SetAsync RED_LED LOW tsctl DIO SetAsync GREEN_LED HIGH tsctl DIO SetAsync RED_LED HIGH
You should see the green and red LEDs go off and on. (If they are already off then the first two commands will have no effect.)
Now let's talk about how the above commands work. Unlike many Linux utilities, tsctl provides direct access to a number of different functions across several classes using a "generic to specific" way of invoking the functionality you want.
First, you specify the host that you want to invoke the functionality on. This part is optional. The host is specified as the @ symbol followed by an IP address or host name, and indicates that the TCP/IP protocol is to be used. If no host is specified (as in the examples above), localhost is used if the tsctl server is running on the board, otherwise direct access is used.
Second, you specify what class of functionality you want to use. The available classes, in alphabetical order, are AIO, Bus, CAN, DIO, DIORaw, EDIO, Mode, Pin, SPI, System, Time, and TWI. If you specify "?" instead of a class, the available classes will be shown.
Third, you specify which function in the given class you want to invoke. If you specify "?" the list of functions available for that class will be shown. Above, we specified the SetAsync function.
Fourth, you specify the parameters for the function. If you specify "?" the expected parameters will be shown. The format of any parameter is quite flexible: you can enter a numeric value in octal, decimal, hexadecimal, or using a special string name which has a mapping to a value in the System lookup table, which we will discuss later. The SetAsync function takes two parameters: the DIO number and the state to set the DIO to. We used a special string for each: GREEN_LED, for example, having an entry in the mapping that corresponds to the DIO number for the GREEN_LED, and LOW/HIGH being enumerated values for 0 and 1, respectively.
Note that the SetAsync function does not returned any data, so the above commands produce no output. For our next example, let's use the GetAsync function to obtain the current state of the green LED pin:
tsctl DIO GetAsync GREEN_LED
When you run the above command, what output will you get? The answer to this is that it depends on the current output mode. The default mode is to output a name=value pair for each function, where the name is the name of the function you invoked, with an underscore and a number after it, and where the value is formatted in hexadecimal for integers and as an escaped, quoted string for arrays of 8-bit integers, and colon separated hexadecimal integers for all other arrays. If the output is an enumerated value, the default is to print the string corresponding to the enum value if the enum is not a flags enum, and otherwise to print the string value for each flag set separated by a plus symbol. The output mode can be changed by calling functions in the Mode class.
In the case of GetAsync, the value returned is an ordinary (non-flags) enumerated value, which can be one of the following: HIGH,LOW, INPUT, INPUT_LOW, and INPUT_HIGH. So in the following example we see the default output if the green LED was on:
$ tsctl DIO GetAsync GREEN_LED GetAsync_0=HIGH
You might be wondering why there is an underscore and a number after the function name in the output. The reason is that tsctl allows you to issue multiple commands in a single invocation of tsctl. Each output has a unique name (accomplished by appending a different number for each command) in order to allow the results to be read by a shell script and assigned to shell variables. There are two schemes that can be used. By default (for backward compatibility), each function has its own counter, starting at 0, and incrementing by 1 each time it is called. The second method that can be used is (for slightly better performance) to use a single counter for all functions, so that this counter will increment once each time any command is called.
There are several ways that you can issue multiple commands with only a single invocation of tsctl. The first is to string together multiple tsctl commands by separating each one with a semi-colon. Since most shells use the semi-colon to separate shell commands, usually you will need to quote the semi-colon. So for example you could get the value of both the red and green LED with only a single invocation of tsctl as follows:
tsctl DIO GetAsync GREEN_LED \; DIO GetAsync RED_LED
Depending on your shell, the following should also work:
tsctl "DIO GetAsync GREEN_LED;DIO GetAsync RED_LED"
Note that no space is necessary on either sides of the semi-colon. The only white-space required is to separate each argument, and this white-space can be in any amount. The only thing to be aware of is that the newline character both terminates a command and triggers an invocation of all commands on the current line, so it is a bit more efficient to separate commands with semi-colons rather than white space unless you need a given output before issuing the next command.
If the red LED is on and the green LED is off, the default output of either of the above commands will look like this:
GetAsync_0=LOW GetAsync_1=HIGH
Another way to issue multiple commands to a single invocation of tsctl is by reading the commands directly from the standard input, or from a file.
To read commands directly from the standard input until end of file (e.g. CTRL+D is pressed) invoke tsctl as follows:
tsctl --
To read commands directly from a file, invoke tsctl as follows:
tsctl -f filename
The format of each command when reading from standard input or a file is identical to that of the command line. Every command always is of the form:
@host class command arguments...
where @host is optional.
The Command Stack
Sometimes you may want to issue several commands, and the first part of each command will be the same. As a simple example, consider reading the example of reading the red and green LEDs given above. If read from a file, these commands would look like this:
DIO GetAsync GREEN_LED DIO GetAsync RED_LED
Each command starts with a common prefix, namely DIO GetAsync. To reduce the amount of typing (and length of the command) needed, tsctl provides a "command stack".
The tsctl command stack can be thought of as follows. When you specify a command, it consists of several words, separated by whitespace, starting with the (optional) host name and continuing until all the arguments have been specified. Conceptually, as each word is read, it is pushed onto a stack. Once a complete command is on the stack, it is executed. However, if the current command terminates without a complete command being present, then the current command stack is then saved as the context for future commands. Each future command will then be read with this context, this pre-initialized stack. Each time an empty command is encountered, the last element on the stack - if present - is popped off.
In the above example the result would look like this:
DIO GetAsync GREEN_LED RED_LED
The first line effectively selects the DIO class and the GetAsync function. Subsequent commands then operate in this context and only need to specify the arugments to the GetAsync function. If we wanted to use another DIO function we would enter a blank line to pop the "GetAsync" function, and if we were done with DIO we could enter another blank line to pop the "DIO" class from the stack. Any extra blank lines would be ignored.
If you wanted to issue both commands in the same server invocation you could simply the above set of commands to the following:
DIO GetAsync;GREEN_LED;RED_LED
This can be a very efficient method for calling the same function repeatedly with the same arguments. For instance, suppose you wanted to read the SYSCON registers at offset 0,2,4,6,8,and 10 of Bus 0, and then call System function ModelId you could do so as follows:
Bus Peek16;0;2;4;6;8;10;;;System Model
The empty commands are used to pop the command stack, two pops are necessary to remove first the "Peek16" functiom, and then the "Bus" class, so that the System class can be used.
Due to the command stack, it is not considered an error if there is a partial command left when tsctl exits. It is important to remember this to avoid confusion in case you accidentally specify a partial command. For instance, the following command will generate no output and no error, because it does not do anything.
tsctl Bus Peek16
The last way to issue multiple commands with a single invocation of tsctl is to start the tsctl shell, as follows:
tsctl
There are a few differences between running the tsctl shell versus providing commands directly from the command line or from a file. The readline library is used to allow command editing and history. Starting with version 0.91, output in shell mode is in decimal (Mode Dec) and the values from each command are printed without an an assigment (Mode NoAssign).
This will print a startup message and then give the tsctl shell prompt, "tsctl> ". To supress the startup message use the "--quiet" option:
tsctl --quiet
Note: UNIMPLEMENTED
The tsctl command shell displays "tsctl" plus the current command stack (if any) before the prompt. The above example of using the command stack has been extended (to show popping the stack) in the tsctl shell.
$ tsctl tsctl 0.91-ts (Nov 30 2012 22:06:57) Type "?" to get context-sensitive help. tsctl> DIO tsctl DIO> GetAsync tsctl DIO GetAsync> GREEN_LED HIGH tsctl DIO GetAsync> RED_LED LOW tsctl DIO GetAsync> tsctl DIO> tsctl>
You can also use semi-colons to put multiple commands or parts of a command on a single line.
To exit the tsctl shell, press CTRL+D, or enter the word "end" by itself on the current line with no extra whitespace.
You can control the format of the output of each command using the Mode class. The output mode is divided into two parts: the encoding and the base.
The output encoding is one of the following: Raw, Newline, Assign, and JSON.
- Raw encoding outputs each value returned by the command in the current base, one after the other with no other characters in between each value. It is mainly intended for base -1 and base 0, as with all other bases it would be difficult or impossible to determine where one value ended and the next started.
- Newline encoding outputs each value returned by the command in the current base, with a new-line character after each value.
- Assign encoding outputs a "name=value" pair for each value returned by the command, where value is in the current base, and name is the name of the function, with an underscore and numeric count appended.
- JSON encoding wraps each output in a "name":value format, and separates each element with a comma as well as wrapping arrays in brackets and the entire reply in braces.
Output base is one of the following: -1 (escaped binary), 0 (binary), 2 (ASCII binary), 8 (octal), 10 (decimal), or 16 (hexadecimal). Separate bases are active for character strings and other numbers. Note that for ASCII base output (2 and above) there are no identifying characters to tell what base it is. For instance, there is no "0x" preceding the hexadecimal value in base 16 mode.
- Base 0 is the raw internal, little-endian "binary" representation of the value. Although it is the actual binary format of the value this should not be confused with base 2, which is the ASCII representation of the binary value, which consists of only '0' and '1' characters.
- Base -1 is the same as Base 0, except that only 8-bit values in the range of 32 and 126 (excluding 92, which is the ASCII backslash character) are output as-is. All other values are output as a backslash followed by three octal digits representing the ASCII value of the byte.
In addition, there input modes, and is divided into two parts: the encoding and the base.
The input encoding is one of the following: HTTP and Command.
- Command encoding indicates that the server immediately processes input as commands. This is the default and only mode available in client mode.
- HTTP encoding is only available through the TCP port. In this mode, an HTTP POST header is assumed to occur before the start of any commands. Two empty newlines terminate the header and put the connection back into Command mode.
The input base is either Binary or Text.
The tsctl client, whether invoked as a command-line sequence of commands, as the tsctl client, or reading commands from the standard input or a file, defaults to input encoding Command, input base Text, output encoding Assign, output base 16.
You can find more information in the tsctl "Getting Started" guide for the hardware you are using.
The tsctl command line client for remote access
The previous section alluded to being able to specify an @host parameter to tsctl commands. In order for this to work, it is first necessary to run the tsctl server on the board you want to control. The tsctl server listens for connections on various TCP ports, providing the ability to control the board remotely. Client code was be written in C, HTML/Javascript, Python, Javascript, or any other language that can talk to a TCP/IP socket or which can generate an HTTP POST. However, to start with, we will use the tsctl client itself.
First, start the tsctl server on the board you wish to control:
tsctl --server
Note: | If you link against uclibc, the server must be manually backgrounded due to issues with the daemon() call interacting with pthreads in uclibc. |
Once the server is started, you can now invoke tsctl functionality remotely, by adding the @host parameter to any tsctl command. If you are running the tsctl client on the same board as the server, communication will still be over TCP/IP by specifying localhost (or 127.0.0.1) as the host. If you do not specify a host and the server is running, localhost will be used automatically. By using the client/server access mechanism rather than direct access you gain the added flexibility of being able to simultaneously control the board from multiple processes or machines, or to control multiple boards from the same client.
Let's look at our previous example of controlling the LEDs, this time done using the client/server approach:
$ tsctl tsctl> @localhost DIO tsctl @localhost DIO> SetAsync GREEN_LED LOW tsctl @localhost DIO> SetAsync GREEN_LED HIGH
The only difference from the previous usage is that we have added the host name before the class. The tsctl program distinguishes between whether the first argument is a host or a class by case. All classes start with an upper-case letter, so if the first argument starts with an upper-case letter it is assumed to be a class. Therefore, you must specify the host using either a numeric IP address or with a host name that starts with a lower-case letter. Host names are usually specified using lower case anyway but since they are case insensitive all host names can be distinguished from class names by tsctl.
When the tsctl sever has started, it listens on two TCP ports: 5002 and 8000. Port 5002 is intended for use by the tsctl and other clients, and defaults to input encoding Command, input base Binary, output encoding Raw, output base 0. Port 8000 is intended for use by HTML/Javascript clients, and defaults to input encoding HTTP, input base Text, output encoding JSON, output base 10.
Internally, tsctl converts all (available) commands to a binary encoding before executing them. This provides for a uniform consistency in execution time between text and binary mode, as all the overhead of parsing the text command occurs before execution starts. It simplifies server design, as a single interpreter of the binary protocol is sufficient in all cases. Likewise the data returned from executing commands is in binary format, which is then rendered in the current output mode. However, since the reply data does not come back all at once in some cases (depending on the command, loading, network latency and other condition), reply data is collected as much as possible at once and then rendered as output before repeating the process until all reply data has been output. This provides for both responsive output even when certain commands (such as Time delays) produce noticable delays before giving output, while providing for maximal uniformity and performance similar to the request.
The separate internal conversion process also provides for the ability to pre-compile tsctl text commands to binary format ahead of time. This makes possible the ease of writing readable text commands with the greater performance of issuing commands in binary format. To this end, these are several command-line options provided for compiling and using pre-compiled commands:
- --compile reads all commands provided and then produces a binary output containing two sections. The first contains the binary format the for requests and the second section containing the look-up table. Support is planned for outputting these sections in a format suitable for reading in subsequent invocations of tsctl, or for putting in your own C, Python, Javascript, etc. program.
- --binary skips reading commands and instead reads the output of --compile as its input. It then performs the look-up specified in the look-up table, and then directly interprets the binary commands to produce output.
- --skip-lookup is only used in conjunction with --binary, and causes the look-up step of that option to not be performed.
- --lookup-only is used only in conjunction with --binary. It only performs only the look-up stage, and then outputs the resulting binary protocol for subsequent use.
- --raw-output skips the output rendering. The raw binary reply data from the server is output. The result is the same as if raw binary output mode is explicitly specified in the request for the entire duration of the requests.
- --raw-untagged disables output tagging. Implies and automatically enables the --raw-output option, as the normal output stages rely on tagging to render output.
Note: UNIMPLEMENTED
Using the tsctl server from HTML/Javascript
The tsctl server (tsctl --server) has built in support for HTML/Javascript access by acting in a very limited capacity as a web server on port 8000 on the board the server is started on. It does so by accepting (and ignoring) any and all HTTP headers sent, which are terminated by two empty new-lines. Then follows the actual data, which it interprets in the same manner as from the tsctl command line or from a file. The main difference between the tsctl web server and the command-line client is that the former defaults to "Mode JSON" while the latter defaults to "Mode Assign" or (for the tsctl shell) to "Mode NoAssign".
Note: The tsctl web server returns the header "Access-Control-Allow-Origin: *". This allows you to create a single HTML page that can control multiple devices.
The first step to using the tsctl with HTML and Javascript is to create the interface in HTML. This can consist of form elements or any other interface mechanism available to the web browser(s) you are creating your page for. As a simple example, suppose you want to create a web page to display the current A/D value on the board. You might create the following markup in your HTML page:
ADC value = <span id='adc1'></span>V
The creates an empty span with an id we can use to fill in the value later. Next, you will need to write some javascript code to talk to the tsctl server to fetch the A/D value.
First, here is a Javascript function that will create an XMLHttpRequest for talking to the server, then make the request, and when the reply has been received it will call the callback passed.
function AJaXRequest(url,parms,success,failure) { var ob; var callback = function() { if (this.readyState == 4) { if (typeof success == 'function') success(this.responseText) } } if (window.XMLHttpRequest) { ob=new XMLHttpRequest() } else if (window.ActiveXObject) { ob=new ActiveXObject("Microsoft.XMLHTTP") } if (ob) { ob.onreadystatechange=callback; ob.open("POST",url,(callback != null)); ob.setRequestHeader("Content-type", "application/x-www-form-urlencoded") try { ob.send(parms) } catch (e) { if (typeof failure == 'function') failure(e) } } return ob; }
If you are already using your own javascript library such as jQuery, you may already have such a function available to you.
Next, here is another function using the above definition that will send the tsctl command to get ADC channel 1 and then put the results in the 'adc1' span created above, assuming a 10-bit A/D with a 0-10V range:
function getADC1(server) { AJaXRequest(server,"AIO Get 1\nend\n", function(reply) { var ret = JSON.parse(reply) document.getElementById('adc1').innerHTML = Math.round(ret["AIO_Get_0"] * 10000 / 32768)/1000 }) }
The server parameter is the URL of the tsctl server, such as "http://192.168.0.50:8000". Note that the actual command to be sent has newlines quoted ("\n"), and ends with the "end" command. This is a special command that causes the tsctl server to immediately terminate the connection. Without this command the browser may hang until a timeout occurs to close the connection.
The data returned by the server uses the same name/value pairing that the command line does. Therefore if, for example, a second "AIO Get" command was issued in the above request, the reply would be in the ret["AIO_Get_1"] field of the reply.
Using the tsctl server from Python
Coming Soon!
Using the tsctl server from Java
Coming Soon!
Writing your own libtsctl direct access application in C
While other sections cover how to get started controlling our boards without needing to write any code (using the tsctl command line), you may wish to develop your own application to directly access the Technologic Systems hardware. This is the superior solution if you need high performance and do not need the ability to control the board functions remotely. Note that libtsctl is designed to work best when only a single application at a time is using it. If multiple applications are linked against libtsctl with the idea of being run simultaneously, special care is needed to be taken to prevent them from "stepping on each other's toes".
Direct access applications can be written in any language that has the capability to interface with C. We provide an (unsupported) SWIG example to demonstrate how it is possible to do this from Python. However in this section we will discuss writing directly in C.
To begin, first get the latest source code from GitHub: https://github.com/embeddedTS/libtsctl
The top-most directory of the project contains the Makefile and the source code to the sample apps. The ts directory contains the libtsctl source code. The dioctl.config directory contains the config files for all the architecture, and a script to create the compressed version of these for inclusion into libtsctl - note that this is already done so unless you want to modify an existing config you will not need to touch this directory.
Once you have the source code on your system there are two approaches to adding libtsctl to your application:
1. Adding your application to the libtsctl directory
This is probably the simplest approach, especially if your application consists of only a single source code file. All of the sample applications included with libtsctl use this approach. Your application will need to include "libtsctl.h", and you will need to add a couple of entries to the supplied Makefile, one to direct builds of your application to the proper sub-directory, and the other to list the dependencies for your project. The easiest way to do this is probably to copy/paste/modify an existing entry for one of the sample apps provided. For example:
MapDump: $(DIR)/MapDump @true $(DIR)/MapDump: $(addprefix $(DIR)/,MapDump.o Arch.o NoThread.o $(ARCHLIBS) libtsctl.a) -lbz2
In the above example, you would replace "MapDump" with the name of your application source file - if there are multiple source files you would replace MapDump.o with an object file for each source file. If you need pthreads you would change NoThread.o to PThread.o and add -lpthread to the end. You will need pthreads if you are running the CAN server in your application, even if your own app does not use pthreads.
2. Linking libtsctl in your own project directory
This is the ideal choice if you already have a build process in place for your application and simply want to drop in libtsctl support.
The first step is to build all the libraries (e.g. "make libtsctl.a") and then copy libtsctl.a and libtsctl-export.h (renaming it libtsctl.h) to somewhere that your project build process expects them, and the library for each architecture you want to support. For instance, if you have a TS-4200 with a TS-8100 base board you would need libtsctl.a, libts4200.a and libts81x0.a. Then, include libtsctl.h anywhere you need it, and link against the .a files. Note that because of circular dependencies between libtsctl.a and the architecture support libraries you will either need to link against libtsctl twice (once at the beginning and once at the end) or wrap all the libraries with -Wl,--start-group" and "-Wl,--end-group" in order to avoid undefined symbol errors, e.g.
-ltsctl -lts4200 -lts81x0 -ltsctl
or
-Wl,--start-group -ltsctl -lts4200 -lts81x0 -Wl,--end-group
Note that in some cases, in addition to libtsctl.h you may also need to copy and include "Array.h" (if any libtsctl functions used take or return an "Array", which by convention is denoted by the "*" in the pointer being bound to the type e.g. with no spaces.
Using the tsctl server from C
Writing a C application to talk to a tsctl server uses much of the same API as a direct access client application. The main difference is that instead of including "libtsctl.h", you will instead include "NetTsctl.h", which is a short-cut to include all of the Net class files. (The file "NetTsctl-export.h" in the net/ directory embeds everything into a single file so you can easily import it into your own project.) Then, instead of calling the class Init functions such as DIOInit, you will first call TsctlClient for each tsctl server you wish to connect to, following by the Net Init function (such as NetDIOInit) for each class on that server. Unlike the direct access counterparts, Net classes can throw exceptions which should be caught as shown in the next section.
When you write code to use the libnettsctl API your code does not need to run on a Technologic Systems board, as the API operates entirely over TCP/IP and does not depend on any direct hardware access.
The TsctlClient function takes two arguments: the host name of the tsctl server (such as "127.0.0.1", or "localhost"), and the tsctl Net Mode. The latter is one of the following:
- NetModeBlocking
- In this mode, each call to a tsctl class function will block until the server returns completion.
- NetModeNonBlocking
- In this mode, functions which return void and also return no data (i.e. pass no non-const pointers or arrays) will return immediately, without waiting for the server to reply. This can improve client performance by increasing the rate at which commands can be sent, while still invoking the functionality without delay.
- NetModeQueued
- In this mode, functions which return void and also return no data (i.e. pass no non-const pointers or arrays) will queue the command for transmission to the server and return immediately. No commands will be sent until either a function is called which does return data, or else the transmission buffer is filled up. The default buffer size is 1436 bytes, but this can be changed by editing Net2.c.
- This mode is useful for cases where overhead to the server should be minimized (by not sending each command in a separate TCP packet), or where several commands must be sent to the server all at once. For instance, using NetModeQueued, it is possible to send a command to turn off a relay, wait for the relay to actually physically turn off, and then turn it on again, even if the relay controls the power to the machine issuing the command. In other modes this would not work, because as soon as the command to turn off the relay was sent there would be a race between the board being shut down and the next command being sent, and even if the machine was able to send the delay command, it would never be able to send the command to turn the relay back on.
The tsctl TCP Net classes generate exceptions in case of network errors, such as an unexpectedly closed connection or the server no longer being reachable. Exceptions are implemented using the setjmp/longjmp calls. The tsctl connection struct returned by TsctlClient contains the space to hold the exception variable used by setjmp. A reasonable approach would be to wrap a sequence of calls to tsctl Net classes in a setjmp condition as follows:
tsctl *conn; ... conn = TsctlClient(host,mode); ...
if (!setjmp(conn->exception)) { // make API calls here } else { // handle exception here }
A couple of sample programs are included which demonstrate how to create an application to talk to the tsctl server. NetTest2 sends several commands to determine the round trip latency for sending commands. NetTest adds the functionality of printing the Model Id, Base Board Id, and then toggling the red LED on the board 100 times. After this, it does a Map test.
For use in your own projects, simply copy net/NetTsctl-export.h into your own project, renaming it to NetTsctl.h. Then, build libnettsctl.a (e.g. "make libnettsctl.a") and import it into your project.
Concepts
Objects
Tsctl uses objects to perform most important functionality. These objects are divided into classes which correspond to various resources available on the board: AIO, Bus, CAN, DIORaw, DIO, EDIO, Pin, System, Time, TWI, and SPI. These objects are implement in C using a structure which contains a pointer to the API for the class. To use an object, it is first necessary to call its initialization function to get a pointer to the object. Objects are referenced by initialization by number. Some numbers for a given class are standard across all hardware, while others are specified to a given board.
Locking
libtsctl is design to make it possible to share resources. It does this by allowing resources to be locked. A lock can be a shared lock, such as might be appropriate for reading a value, or it can be an exclusive lock, which would be appropriate for changing a value.
All resources that provide an interface to lock and unlock them must be locked before use and unlocked after use. This is because in some cases the locking mechanism, in addition to acquiring shared or exclusive access to the resource, also performs per access initialization. For instance, multi-function pins are automatically set to the correct function during locking.
From the perspective of a client there are object locks, which in the server ultimately result in one or more system locks being held, under control of the object being locked and any sub-objects it contains. All tsctl objects which provide a Lock function offer the client to hold an object lock on that object. Each class defines the circumstances under which the client must hold an object lock. A client should not hold an object lock any longer than necessary.
- Bus: the Bus lock must be held before while other functions in the Bus are called.
- Time: does not use locks
- Pin: the lock for a given Pin must be held while ModeSet is called on that pin
- DIORaw: the lock for a given DIO must be held while any function is called for it
- DIO: the lock for a given DIO must be held while any function is called for it
- TWI: the TWI lock must be held before other functions in TWI are called.
- CAN does not use locks
- SPI: the SPI lock must be held before other functions in TWI are called.
The Lock function has several flags. (TO DO)
A connection may hold the same lock more than once, a so-called "recursive" lock. Each lock must be released the same number of times as it is acquired before the connection truly has released the lock. In the server, the connection either holds a system lock or it doesn't; the server keeps track of the system lock count and only acquires the system lock if the count is being incremented from zero, and only releases it if the count is being decremented to zero. (UNIMPLEMENTED)
The server automatically releases all locks held by a connection when it detects that the connection has closed and has read all pending opcodes from the connection.
While a connection can hold multiple locks at once, they must be acquired one at a time.
A server object may acquire additional locks during the course of calling other functions in it. If such a call would cause a deadlock an exception will be generated on the connection, which will close the connection. (UNIMPLEMENTED)
dioctl.config
The libtsctl library contains a mechanism to map arbitrary strings to numeric values. The naming of this file is historical, deriving from the original dioctl program. The primary function of this file is to map DIO names to logical DIO numbers for convenience and cross-platform usage. For instance, the name "GREEN_LED" is defined to refer to the primary green on many Technologic Systems boards. In addition, this file is used to define DIO attributes, pin mappings, and even wire routing for loopback testing.
The dioctl.config file must be installed in the /etc directory. The source tarball contains all available config files in libtsctl-src/config. Each individual architecture has its own dioctl.config file. In the source directory, the arch name is prepended to ".dioctl.config" to distinguish config files.For example ts4200-ts81x0.dioctl.config is the dioctl.config file for arch ts4200-ts81x0. These names are to identify the individual files to the user - when a config file is installed it must be renamed to dioctl.config and it must be in the /etc directory on the board from the viewpoint of the running program.
Note: The "logical DIO number" referenced in this section refers to the DIO number for DIO instance 0, which is the "Aggregate" DIO object combining all DIOs individual DIO banks into a single linear set of logical pins. This also corresponds to the pin numbers taken by functions of the Pin object.
The dioctl.config file contains one assignment per line of text. Each assignments is one of the following:
# Comment
- All lines which begin with the "#" character are comments and are ignored. Config files with multiple sub-architectures typically separate each sub-architecture with a comment indicating the arch to follow.
NAME=#
- The specific name maps to the given number. In some cases the name corresponds to a human readable term like "GREEN_LED" while in others it corresponds to a DIO or pad name on a chip (e.g. "PA6"). All text lines which do not conform to subsequent forms must fall into this category.
- If the specified # is not a number, it is treated as a previously defined name. This allows for aliases to connector pins, which have different logical DIO based on the TS-SOCKET board in use, but which have a common name on the base board.
attrib.Connector.Name.CNAME=#
- This defines the name of a connector. The string CNAME is the name of the connector, and the number (#) is the connector number. A TS-SOCKET board has two connectors, named CN1_ and CN2_ per the schematic. Additional architectures tack additional connectors on. Comments in the config file indicate when definitions for a new sub-architecture begins.
attrib.Connector.x.y=#
- This defines the mapping between a connector pin and the logical DIO number. The x and y parts of the line are numbers indicating the connector number and connector pin, respectively.
- The tsctl library (specifically the System object, through which the map is exposed) automatically creates NAME=# mappings for each connector definition. The name is the connector name followed by the pin number, and the # is the same as given in the connector. So for example, if connector 1 is "CN1_" then pin 5 on that connector would be named "CN1_5".
attrib.Connector.#.Pins=n
- This is an informational attribute which says that the given connector # has n pins. Not every pin needs to correspond to a logical DIO.
attrib.Connector.Count=n
- This is an informational attribute which says that there are n connectors defined.
The following attributes are used for DIO testing. Wires are defines as sets of one or more pins that are connected together. On some baseboards connect two or more pins together so they are electrically a short. These are used for testing DIOs by setting all the DIOs on the wire except one to input and verifying that each DIO can drive and the other DIOs correctly read the value driven. In some cases, such as when no base board is present, a special harness must be made which uses jumpers or physical wires between header pins.
The ID field is the model number of the base board, if present, or the CPU board otherwise. For example, "8200" for the TS-8200, or "7552" for the TS-7552.
attrib.ID.Wire.MaxConnections=n
- This attribute is used for DIO testing. Wires are defines as sets of one or more pins that are connected together. The n value is a number indicating the maximum number of connections to any wire defined.
attrib.ID.Wire.Count=n
- This entry indicates that there are a total of n wires defined.
attrib.ID.Wire.Connector.x.y=n
- This entry defines connection y of wire x to be connected to connector n.
attrib.ID.Wire.Pin.x.y=#
- This entry defines connection y of wire x to be connected to pin n on the connector defined by the attribute above.
attrib.ID.Wire.Drive.x.y=#
- This entry defines connection y of wire x to be connected in such a way that its drive capabilities are specified by #. This is used when DIOs are externally limited - for instance, a DIO might be connected on a base board through a buffer. In this case, even though the DIO on the CPU board can function as both an input and an output, the test harness limits it to an output. The entry is used so that the DIO test knows that it should not expect the DIO to work as an input, even though it will report that is has that capability.
- The # value must be one of the following (from DIO.h):
-
- 1 - Input Only
- 2 - Can only Drive High
- 4 - Can only Drive Low
- 6 - Output Only (Can only Drive High or Low)
- 7 - No restrictions (as this is the default, normally this value is omitted)
Threads
Certain parts of libtsctl, such as the CAN server, required threads. However, most of the library does not require threads to run. Therefore, if your program does not need threads and none of the functionality in libtsctl that you are using requires threads, it is not necessary to link against (e.g.) pthreads.
To accomodate either threads or thread-less programming, libtsctl abstracts away threads into the Thread object. If you need to use threads, you must include PThread.o in your dependencies, while if you do no, include NoThread.o; there are pre-defined build targets libtsctl-pthread.a and libtsctl.a which do the same thing, respectively. You may also need to #define THREAD_USE_POSIX however this is planned to be deprecated soon.
The Thread object encapsulates thread related tasks and implements them in PThread.c to use pthreads, and NoThread.c to use no-ops for most operations.
If your program uses threads and libtsctl, you must call the ThreadInit() function before calling any other thread related functions.
If you need to use threads in your program which call libtsctl function, you must use the Thread object.
The Thread API defines the following functions:
int ThreadInit();
Call this function to initialize the Thread sub-system before calling any other thread functions, either in the Thread object, or pthreads.
void ThreadFini(void *threadptr);
The parameter passed is a pointer to the Thread object. This function properly disposes of the internals of the Thread object passed.
Thread *ThreadNew(char *name,int inst,ThreadFunction func,int socket, void *data,ThreadDestructor destor);
Create and start a new thread. The first and second parameters are for the purpose of identification; they can be whatever the user desires. The third parameter is a pointer to a function taking a void pointer and returning void; this function will be called in the new thread. The socket parameter is used if the thread is to be a server function, normally this is only meaningful if the func parameter is the "Server" function present in PThread.c. The data parameter is any additional data to be passed to the new thread. Note that the pointer to the thread function "func" is not this pointer, it is a pointer to the Thread struct, to get the data pointer dereference the data element of that structure. The final parameter is a pointer to a function to act as a destructor, performing any necessary shutdown before the thread terminates. It can be null if not required.
int ThreadEnterBlock();
This function is provided for the case of a server which may need to enter a system call which may block, but which is connected to a socket and needs to terminate that system call if the socket disconnects. This prevents sockets from lingering on long-blocking system calls such as sleeps.
int ThreadLeaveBlock();
This function is to be called after finished the blocking call following a ThreadEnterBlock call.
unsigned ThreadMutexAllocate(int);
This function allocates the specified number of mutexes, and returns the mutex number of the first one. If multiple mutexes are allocated they will have sequentially assigned numbers.
unsigned ThreadLockAllocate(int);
This function allocates the specified number of read/write locks, and returns the lock number of the first one. If multiple locks are allocated they will have sequentially assigned numbers.
unsigned ThreadMutexCount();
This function returns the total number of mutexes allocated.
unsigned ThreadLockCount();
This function returns the total number of locks allocated.
int ThreadMutexLock(unsigned num,int mode);
This function tries to lock the specified mutex number with the given mode.
The mode **LOCK_TRY** makes the call non-blocking: if the call would block a negative error code is returned. Otherwise, the call blocks until the lock is acquired. The mode **LOCK_FOD** makes the call fail if a deadlock would occur - this is currently unimplemented.
int ThreadMutexUnlock(unsigned num);
This function releases a mutex that has previously been acquired by ThreadMutexLock.
void ThreadUnlockAll(Thread *);
This function releases all mutexes and locks currently held by the thread. It is intended to be used from a destructor function or similar place where it is inconvenient to have to track what mutexes and locks are currently held.
int ThreadWait(int (*f)(int),unsigned num,int mode);
This function uses the specific lock number *num* to wait for a signal, at which time it calls the function passed by reference in the first argument. The *mode* argument is currently unused and should be 0.
int ThreadSignalIf(int (*f)(int),unsigned num);
This function calls the function passed by reference in the first parameter, passing the *num* parameter as argument. If the function returns true, then a signal is sent to the thread waiting on the specific lock number *num*. Returns true if the function was called, otherwise it returns false.
int ThreadLockW(unsigned num,int mode);
This function attempts to acquire a write (exclusive) lock for the specified lock number. Note that lock numbers exist in a different space from mutex numbers, so lock 0 and mutex 0 are not the same thing. If **TRY_LOCK** is passed as the mode, then the function will return failure immediately if the lock would block, otherwise the call blocks until the lock is acquired.
int ThreadLockR(unsigned num,int mode);
This function attempts to acquire a read (non-exclusive) lock for the specified lock number. Note that lock numbers exist in a different space from mutex numbers, so lock 0 and mutex 0 are not the same thing. If **TRY_LOCK** is passed as the mode, then the function will return failure immediately if the lock would block, otherwise the call blocks until the lock is acquired.
int ThreadUnlockW(unsigned num);
This function releases the write lock previously acquired with the ThreadLockW function.
int ThreadUnlockR(unsigned num);
This function releases the read lock previously acquired with the ThreadLockR function.
Thread *ThreadLockHolder(unsigned num);
This function returns a pointer to the thread holding the specified lock, or null if no thread currently holds it. If multiple threads hold locks, a read lock holder will be returned.
Thread *ThreadMutexHolder(unsigned num);
This function returns a pointer to the thread holding the specified mutex, or null if no thread currently holds it.
tsctl C API
Coming Soon!
tsctl TCP protocol
Coming Soon!
tsctl command line reference
Coming Soon!
Appendicies
Modular Build System
The libtsctl build system is modular, containing a make file to eliminate the need to re-build parts that have already been compiled. This is in contrast to previous (beta) versions which embedded the entire libtsctl project into a single source file (using #include on .c files).
The build process has three layers. First, there are all the architecture specific files. These get compiled first into object files, and then all the object files for each architecture is assembled into a single library (.a) file. There is also a library file for all the libtsctl utility functionality, which in turn gets combined with the desired Thread object to produce libtsctl.a. This library file is then linked against the libtsctl application to produce the final executable. The final link stage also includes linking against libbz2, which is used to decompress the dioctl.config files embedded in the binary, and libpthread if the PThread.o Thread object was chosen to support pthreads in libtsctl. The tsctl app also links against libreadline and libcurses (used by libreadline) to support command history in the tsctl shell.
In some cases it may be difficult due to versioning or other issues to install the required libraries. To accomodate this, you can build without support for bzip2 compression by defining "BZ2=no". You can also build without support for command history and editing by defining "READLINE=no".
The build system supports implementing of custom architectures outside the libtsctl build proper. see: Implementing_Custom_libtsctl_Architectures.
In addition, it is possible to build a libtsctl app outside of the libtsctl proper simply by including libtsctl.h (TO DO: separate libtsctl.h for this purpose which is self-contained) and then linking against a libtsctl.a compiled for the desired platform.
Hardware Map
Coming Soon!
Initialization Scheme
The libtsctl sub-system initializes itself on demand, deferring initialization of resources until they are requested or needed by a resource that has been requested. All initialization is initiated by a user call to one of the class Init functions (DIOInit, CANInit, etc.) All class init functions are funneled through the ClassInit function in Arch.c, which initializes by the given class number (an enum) and an instance number. The ClassInit function first calls ArchInit to obtain an instance of the Arch object for the top-level piece of hardware present - this will be the board that has the main CPU on it.
The ArchInit function detects the top-level architecture and calls the corresponding initialization function for that architecture. The ts.c file contains most of the code that actually detects the various CPU and model IDs. The Arch.c file uses conditional defines (#ifdef) to include support for various platforms. The build process constructs these defines from the SUPPORT variable in the makefile, which also can be passed on the command line. In this way it is trivial to include or exclude support for various architectures for any given build.
After the top-level architecture has been detected and returned, ClassInit then tries (returning NULL if it fails) to find the requested instance of the specified class by iterating through the functions returned by the Function function of the Arch class. The basic idea is that any given system may be comprised of multiple architectures which combine to form a given platform. For instance, a TS-4200 CPU board might be connected to a TS-8160 base board which in turn has a TS-RELAY8 PC-104 board attached. Each of these boards would be a separate architecture, and combined they would form the platform TS-4200 + TS-8160 + TS-RELAY8. The Arch class encapsulates functions which return information about the architecture:
void *Function(int class,int inst);
This function returns all the class initialization function for the given instance of the given class. For instance, passing class=ClassSystem and inst=0 will return a function to call to initialize and return a pointer to instance 0 of the System class. By iterating through the class and instance values it is possible to obtain all initialization functions for a class.
Arch *NextSubArch();
This function returns the next (sub)architecture in the platform. All the architectures in the platform are chained together to form a linked list of sorts. This function allows one to iterate through this list.
void *NextSubSetSet(Arch *);
This function is for internal use. During initialization, an architecture will call this function in its sub-architecture objects to set up the chain. This function should not be called outside of initialization.
int CANBusId(int inst);
This function returns the instance of the Bus class containing the CAN registers for the specified CAN instance.
The instance number of a given class stack so that instances implemented by the top-level (CPU) Arch are followed by the instances implemented by the sub-architectures. So for example if the CPU Arch implements 5 busses they would be accessible using BusInit(n) where n is between 0 and 4. The first Bus implemented by the next sub-architecture would be n=5, and so on. In addition, sub-architectures can override instances of their parent. This is typically done in the Pin class, where an architecture like a base board must override certain pins to enable them (e.g. CAN on the TS-81X0) but pass other requests through to the parent class. Internally, this is done by negative instances: ClassInit will first initialize a class, then pass object to the negative value (-1-n) function of the sub-architecture, if it exists, which returns the new class to continue up the chain.
During the actual initialization of the top-level architecture, the architecture initialization function is responsible for detecting and initializing any potential sub-architectures. Some boards do not have any possible sub-architectures and thus do not do this. At the moment the only sub-architecture that can be detected is the TS-SOCKET baseboard. This is accomplished by the top-level architecture (which must be a TS-SOCKET CPU board) calling the ArchBBInit() function in Arch.c. This function detects if a supported baseboard is present, and if so, it calls the appropriate base board initialization function and returns the Arch object for that base board. Likewise, the Base Board initialization function must detect and initialize any sub-architectures it supports. The only sub-architecture for the TS-SOCKET baseboards is currently the PC-104 peripheral board. The ArchPC104Init() function must be called by the Base Board initialization function to detect and initialize any PC-104 boards. This function detects and initializes all supported PC-104 boards, links them, and in addition adds dummy objects in order to fix PC-104 DIO and other objects in fixed positions based on their I/O address. For instance, there can be up to 4 TS-RELAY8 boards, so if one is found at the second I/O address, a dummy instance is put in the first I/O address so that adding a new board at the first address will not cause DIO numbers to change.
All architecture initialization functions should only run once, then save their return value in a local static variable so that subsequent calls merely returns the already initialized object. The except to this is if initialization fails, in which case a subsequent call will try again.
Implementing Custom Architectures
The libtsctl build system has been created in such a way that it is possible to implement a custom architecture which is not part of the tsctl source tree. This is useful because typically a piece of custom hardware is specific to a single customer, and thus it is not appropriate to distribute support for such a custom product to anyone but that customer. This section describes how to add such support.
The first step is to create the required files to support the architecture in a directory outside of the libtsctl directory. The files that will typically be needed include:
- NameArch.c for each corresponding architecture implemented, which contains the top-level declarations of all the modules needed to implement the architecture, as well as the initialization functions of all the objects and the Arch object for the class representing the architecture.
- ArchCustom.c which contains the following functions:
TS_CPU TSCustomCPUGet(char *cpuinfo);
This function is passed a buffer containing the text from /proc/cpuinfo (up to 4k bytes). The function must return CPU_CUSTOM if it detects the custom board it supports, either from the passed cpuinfo buffer, or using whatever means are necessary to detect the board. Otherwise it must return CPU_UNKNOWN.
Arch *ArchCustomInit(TS_CPU cpu);
This function is passed the CPU detected previously. If the passed value is CPU_CUSTOM, then this function must return an instance of custom architecture object it has implemented, which must be returned from the corresponding architecture specific initialization function. Otherwise it must return 0.
Arch *ArchCustomBBInit(int model);
This function is passed the base board ID detected by ArchBBInit. If the custom architecture implements support for one or more baseboards, then it must call ArchBBInit during the initialization of the top-level (CPU) architecture. This function must then use the detected model id to determine which custom base board architecture initialization function to call and return the pointer that call returns.
Note: Currently there is no support for implementing support for custom PC-104 peripherals.
- Makefile
The Makefile must specified a build rule for reaching the libtsctl library for the custom architecture. This rule must look something like this:
$(DIR)/libtscustom.a: $(addprefix $(DIR_CUSTOM)/,CustomArch.o ArchCustom.o OtherObject.o tscustom_dioctl_config.o) ar -r $@ $^
- Any other files needed to implement objects used by the architecture, or to implement utility functions used by the architecture.
After the support files are created, it is necessary to pass variables and CFLAGS to make to tell it about the custom architecture. These CFLAGS are:
-DARCH_CUSTOM
informs the build process that our custom architecture exists and implements the functions in the files specified above
The variables are:
DIR=custom_out_dir
' specifies the sub-directory to contain the intermediate and product files for the architecture. Replace "custom_out_dir" with the name of the actual directory to use.DIR_CUSTOM=custom_dir
specifies the path to the directory containing the custom files. Replace "custom_dir" with the name of the actual directory.SUPPORTED="custom"
tells the build process to compile in support for the custom architecture. If you need other architectures supported too they must be added to the list.
Compiling tsctl
The latest source code can always be found on GitHub.
Once you have downloaded the source code, unpack it somewhere that the compiler can find it. For tsctl, it is recommended to compile directly on the board you are going to use, both to avoid issues with cross-compilers and to make it easier to get the dependencies needed to compile.
Before compiling tsctl, you will need to make sure that you have the following Debian packages installed (using `apt-get install <pkgname>` from the fullboot prompt to install any missing dependencies):
- libbz2-dev, used for decompressing the dioctl.config files
- libreadline5-dev used by the tsctl shell for provide command editing and history
If you encounter difficulties installing Debian packages, you may need to update your board to point to the correct URL. See Lenny installing software.
Note: You can avoid the need for the libreadline5-dev dependency and forgo tsctl shell command editing and history if you prefix your build with "READLINE=no". This can alleviate situations where it is difficult or impossible to install libreadline5-dev.
Note: You can avoid the need for the libbz2-dev dependency if you prefix your build with "BZ2=no". You will need to first enter the dioctl.config/ directory and run "./mkdioctlconfig nocompress" to re-generate uncompressed config files. The resulting binary will be larger than would be the case if bz2 compression of the config files was used.
After dependencies are installed, you will need to let the build process know about what architecture you are running. Currently there are only two defined: "cavium" and "noncavium". The "cavium" architecture only applies to Cavium CN2132 CPU boards (e.g. the TS-4500 and TS-75XX boards); "noncavium" covers everything else. The distinction is mainly made because the "cavium" architecture does not have the branch-exchange instructions and thus binaries generated for non-cavium boards will fault when these instructions are encountered. Unless you need the "cavium" architecture you don't have to do anything for this step, because "noncavium" is the default. If you are compiling for "cavium", prepend the "make" command with "ARCH=cavium ".
Next, decide whether or not you want to compile with debug symbols. Normally this is not needed, but if you are experiencing any issues with tsctl it can be helpful to have these symbols for core dumps or to run under gdb. To enable debug symbols, prepend the "make" command with "DEBUG=1 ".
If you want to build a binary that supports all architectures your compiler can target, then just run "make" with any (additional) assignments prepending it. However it is also possible to create a smaller binary that only supports the architectures you wish. The word "architecture" in this context means a single Technologic Systems board, whether it be a CPU board, base board, or PC-104 peripheral board. To define what architectures to compile support for, prepend an assignment to the "SUPPORT" variable making it equal all the boards to support. For instance, to support only the TS-4700 with a TS-81x0 base board, use "SUPPORT='4700 81x0'". Consult the provided Makefile for the names of the architectures available; the default assignment is within the first 20 lines.