xC code examples

Contents

Started 2Feb2018, updated 13May2021

This page is in group Technology (plus My XMOS pages) and is a blog note with some XMOS xC code examples. I also have some ported code that I thought may be placed here. I also am describing some problems.  None of this is on GitHub. I guess that the parent note is xC is C plus x.

For the TIOBE index 016:[10]: “xC programming”.

Fold handling with Collapse-O-Matic plugin

I am using Collape-O-Matic (here) on many pages.

Expand All (for searching)
Collapse All

Typical fold

This text 123456789 will only be found with browser search when the fold is expanded

Some musts

In my opinion, bool needs to be defined.

bool

No C99

Just add this in some a global.h file (or similar name):

typedef enum {false,true} bool; // 0,1

A wild example:

bool my_bool1 = true;
bool my_bool2 = false;
   
if (my_bool1) {
    my_bool2 = false;
} if (my_bool2 == false) {
    my_bool1 = true;
} else {
    my_bool1 = !my_bool2;
    my_bool2 = false;
}

(It doesn’t look like there is any problem in having the -std=c99 defined also in this case. See below:)

Compiling C as C99 but still no _Bool

C99 has a built-in type called (read about it in Wikipedia, here).

To activate C99 just add -std=c99 to XCC_FLAGS in the Makefile in xTIMEcomposer (read about it in the xTIMEcomposer user guide, from here). Then add:

#include <stdbool.h>

You must remove the bool type definition above. The stdbool.h comes with the xTIMEcomposer installation, and here is the contents:

// Copyright (c) 2008 Eli Friedman
/* Don't define bool, true, and false in C++, except as a GNU extension. */
#ifndef __cplusplus
    #define bool _Bool
    #define true 1
    #define false 0
#elif defined(__GNUC__) && !defined(__STRICT_ANSI__)
    /* Define _Bool, bool, false, true as a GNU extension. */
    #define _Bool bool
    #define bool  bool
    #define false false
    #define true  true
#endif

#define __bool_true_false_are_defined 1

I am surprised to see that true and false are defined with defines, and not as part of a set. I remember some ten years ago that we cooperated with a company that used the Motorola HC08; the compiler they used didn’t support enums. Maybe that’s the reason? Friedman has commented “GNU extension” in the code, maybe the answer lies there? I see that -std=gnu99 also is possible for XCC, but I get the same result.

C99 points to 1999, quite some years ago, and it’s now obsoleted for C11. There is no option for C11 in the XCC compiler.

Adding the file ref and the flags should make sense. But it doesn’t. I get this:

../src/__arduino_on_xmos_test.xc:193:5: error: use of undeclared identifier `_Bool'

This is unresolved. But I’ll query at xCore Exchange. Update: I didn’t have to, there already was a post that I just added to, see Wot no bool? started by CousinItt. The answer turned out to be that _Bool is available in ‘C99’ but not in ‘xC’, thus the header file <stdbool.h> won’t work with ‘xC’. 

A general problem with testing against “true”

In the “Wot no bool” post referred to above robertxmos points out a case where

#define FALSE 0
#define TRUE !FALSE
// Some code
if (hasItem(i) == TRUE) { /* 32bit comparison is only true if index was '0' */ }
if (hasItem(i))         { /* convert to 1bit, hence all index values are true */ }

are not equal. Read it there. He shows two problems:

  1. There is one representation of FALSE but many of TRUE, since the language does not support bool, and it internally will do logical conversions to 1 bit
  2. The example he shows uses a function that returns an index [0..(N-1)] but it’s -1 if something searched for is not found. This is standard C practice, but it’s bad practice. It invites problems. See this code:

And a code example to solve it

An example in xC where the problem shown in “Wot no bool” is not present. This compiles and runs. It’s because there is no implicit or explicit type conversions. Observe the parameter list for return values and how nice arrays may be handled in xC, like I don’t have to use any pointers in this case.  (However, there is a problem, see USE_WRONG_RETURN_LIST explained below the code)

#include <stdio.h>
#include <iso646.h> // For "not" etc

#define USE_BOOL_TYPEDEF // No difference since no type conversions done
#ifdef USE_BOOL_TYPEDEF
    typedef enum {false,true} bool; // 0,1 
#else
    #define bool unsigned // long, int, unsigned, char, bool ok 
    #define false 0
    #define true 1 // Or "not false"
#endif

typedef unsigned index_t; // long, int, unsigned, char, bool compile ok..
// #define USE_WRONG_RETURN_LIST  // ..with this defined

{index_t, bool} isItemIn (
    const char            search_for, 
    const char            text[], 
    static const unsigned len) {

    index_t  index     = 0;
    bool     found     = false;
    bool     searching = true;

    while (searching) {
        if (text[index] == search_for) {
            found     = true;
            searching = false;
        } else {
            index++;
            if (index == len) {
                searching = false;
            } else {} // No code, maybe next round
        }
    }
    return {index, found}; // According to {index_t, bool} of return list
}

int main () {
    const char text[] = {'A','B','C','D','E','F','G','H','I','J'};
    index_t  index;
    bool     found;

    {index, found} = isItemIn ('C', text, sizeof(text));
    if (found) {
        printf("C found in %u\n", index);
    } else {
        printf("C not found\n");
    }
    #ifdef USE_WRONG_RETURN_LIST
        {found, index} = isItemIn ('X', text, sizeof(text));
    #else
        {index, found} = 
isItemIn ('X', text, sizeof(text));
    #endif
    if (not found) {
        printf("X not found\n");
    } else {
        printf("X found in %u\n", index);
    }
    // LOG with 
    // USE_WRONG_RETURN_LIST not defined
    // USE_BOOL_TYPEDEF or not no difference
    //   C found in 2
    //   X not found
    return 0;
}

Defining USE_WRONG_RETURN_LIST shows that the compiler will use any integer type for any definition I may have of bool. So, in this case I won’t get any help from the compiler. I may compile wrong code. I guess that’s not much worse than having any list of (..,signed,signed,..) parameter in any function, where (..,degC,degF,..) or (..,degF,degC,..) may be done wrongly. However, there is a small difference. With USE_WRONG_RETURN_LIST the compiler could have taken it if bool were understood by the compiler.

So, not even a typedef of the bool makes it a unique type to the compiler. It’s still an integer-type type.

I added this code and comment to the Wot no bool? thread. The answer from robertxmos was:

Correct, typedef should be viewed as a handy alias.
One could argue that instead of tightening a type system it does the opposite!
If you want unique types you need to reach for struct/union (class if using c++ & interface if using xC)
Even ‘enum’ is weak (unlike the ‘enum class’ in c++11)

And easier-to-get-right code to also solve it

This was triggered by the answer above.

Since interface is not for the below code, I collected the {index,found} into the struct isItemIn_return_t. I guess it’s less error prone since it’s easier to assign x.index=index than x.index=found. Even if I don’t do that, I do use it explicitly like isItemIn_return.index++. Doing it wrongly with isItemIn_return.found++ is easy to spot since I would think to increment a verb would ring a bell.

This uses the same amount of code and data as the above code. Maybe this indicates that the compiler also builds a struct for the parameter list? I kind of like it:

#include <stdio.h>
#include <iso646.h> // For "not" etc
typedef enum {false,true} bool; // 0,1

typedef unsigned index_t; // long, int, unsigned, char, bool compile ok.. 
typedef struct { // easier to get right than the anonymous return parameter list index_t index; bool found; } isItemIn_return_t;

isItemIn_return_t isItemIn
(
    const char            search_for,
    const char            text[],
    static const unsigned len) {

    isItemIn_return_t isItemIn_return = {0, false}; 
    // Not type safe as {true, 0} will compile

    bool searching = true;

    while (searching) {
        if (text[isItemIn_return.index] == search_for) {
            isItemIn_return.found = true;
            searching = false;
        } else {
            isItemIn_return.index++;
            if (isItemIn_return.index == len) {
                searching = false;
            } else {} // No code, maybe next round
        }
    }
    return isItemIn_return;
}

int main () {
    const char text[] = {'A','B','C','D','E','F','G','H','I','J'};
    isItemIn_return_t isItemIn_retval;

    isItemIn_retval = isItemIn ('C', text, sizeof(text));
    if (isItemIn_retval.found) {
        printf("C found in %u\n", isItemIn_retval.index);
    } else {
        printf("C not found\n");
    }

    isItemIn_retval = isItemIn ('X', text, sizeof(text));

    if (not isItemIn_retval.found) {
        printf("X not found\n");
    } else {
        printf("X found in %u\n", isItemIn_retval.index);
    }
    // LOG with
    // USE_BOOL_TYPEDEF or not no difference
    //   C found in 2
    //   X not found
    return 0;
}

Data types long long, float, double and long double

These data types are supported, even if the xTIMEcomposer user guide (document XM009801A, 2015/10/29 chapter XS1 Data Types) (see xC is C plus x [12]) says that they are not. Here is some code showing it:

#include <stdio.h>
void test (void) {

    long        mylong     = 0xfffffffe; // -2
    long long   mylonglong = 0xfffffffe; // Is 4294967294 since 64 bits

    for (unsigned i=0; i<5; i++) {
        printf ("LL(%u): mylong:%ld - mylonglong:%lld\n", i, mylong, mylonglong);

        mylong++;
        mylonglong++;
    }

    float       myfloat      = 16777214.0; // -2 = 0xfffffe (all 24 fraction bits if EEEE 754 single-precision)
    int         myfloatint   = (int) myfloat;
    double      mydouble     = myfloat;
    long double mylongdouble = myfloat;

    for (unsigned i=0; i<5; i++) {
        printf ("F(%u) myfloat:%f - myfloatint:%d - mydouble:%lf - mylongdouble:%llf\n",
                i, myfloat, myfloatint, mydouble,  mylongdouble);

        myfloat      = myfloat + 1.0;
        myfloatint   = (int) myfloat;
        mydouble     = mydouble + 1.0;
        mylongdouble = mylongdouble + 1.0;
    }
}
int main () {
    par {
        test ();
    }
    return 0;
}

Here is the printout:

LL(0): mylong:-2 - mylonglong:4294967294
LL(1): mylong:-1 - mylonglong:4294967295
LL(2): mylong:0 - mylonglong:4294967296
LL(3): mylong:1 - mylonglong:4294967297
LL(4): mylong:2 - mylonglong:4294967298
F(0) myfloat:16777214.000000 - myfloatint:16777214 - mydouble:16777214.000000 - mylongdouble:16777214.000000
F(1) myfloat:16777215.000000 - myfloatint:16777215 - mydouble:16777215.000000 - mylongdouble:16777215.000000
F(2) myfloat:16777216.000000 - myfloatint:16777216 - mydouble:16777216.000000 - mylongdouble:16777216.000000
F(3) myfloat:16777216.000000 - myfloatint:16777216 - mydouble:16777217.000000 - mylongdouble:16777217.000000
F(4) myfloat:16777216.000000 - myfloatint:16777216 - mydouble:16777218.000000 - mylongdouble:16777218.000000

It shows that the 23-bit fraction field of myfloat does not wrap around on overflow! It maxes out at 16777216.000000. See this discussed in the xCore Exchange post xC and long long, float, double and long double. Thanks and courtesy to “akp” there!

See Wikipedia’s IEEE 754 single-precision binary floating-point format: binary32 and Double-precision floating-point format

When to pass by reference, and when not

Observe that in order to modify a parameter inside a function or an interface function you have to use a reference parameter by adding the ‘&’ (like in C++). The below is standard procedure, you need a pointer (=reference) unless it’s a basic array (where element[0] is an implicit pointer). But one certainly need to overlearn or overtest this to get it right:

#include <stdio.h>

typedef struct {unsigned arr[1];} my_array_in_struct_t; typedef enum {zero, one} my_enum_e;
void my_func (my_enum_e & myval) { // NO WARNING IF '&' FORGOTTEN AND NEEDED..
    myval = one ; // ..SINCE WE WANT TO MODIFY THE ORIGINAL
}
my_enum_e my_func_x (my_enum_e myval) { // NO REFERENCE NEEDED BY USAGE
    myval = one ;  // ORIGINAL OBVIOUSLY NOT UPDATED
    return myval; // PROPER RETURN VALUE EXPLICITLY UPDATES THE ORIGINAL
}
void my_func_y (
        unsigned arr[1], // REFERENCE NOT NEEDED FOR ARRAY!
        my_array_in_struct_t & my_array_in_struct) // REFERENCE NEEDED FOR STRUCT!
{
    arr[0] = 1; // UPDATES ORIGINAL
    my_array_in_struct.arr[0] = 1; // UPDATES ORIGINAL
}

void test (void) {
    my_enum_e myval = zero ;
    printf ("myval:%d, ", myval);

    my_func (myval);
    printf ("myval:%d\n", myval);
    // Prints: "myval:0, myval:1"
    myval = zero;
    printf ("myval:%d, ", myval);
    myval = my_func_x (myval);
    printf ("myval:%d\n", myval);
    // Also prints "myval:0, myval:1"
    unsigned arr[1];
    my_array_in_struct_t my_array_in_struct;
    arr[0]=0;
    my_array_in_struct.arr[0] = 0;

    printf ("arr:%d, my_array_in_struct:%d - ", arr[0], my_array_in_struct.arr[0]);
    my_func_y
    (arr, my_array_in_struct);
    printf ("arr:%d, my_array_in_struct:%d\n", arr[0], my_array_in_struct.arr[0]);
    // Prints "arr:0, my_array_in_struct:0 - arr:1, my_array_in_struct:1"
}

int main() {
    par {
        test();
    }
    return 0;
}

This is of course well described in the note 141 ref [1] (XMOS Programming Guide, 5 Data handling and memory safety, 5.1 Extra data handling features, 5.1.1 References)

Repeating: forgetting an & in front of a function parameter is legal and causes no error messages and compiles and runs fine even if you think you update elements in that param! Even if the code looks like it updates elements it doesn’t! Here is yet another example:

void Update_Daytime_Hours (light_sunrise_sunset_context_t context) { // Needs &context to work!
    context.hours = 24; // No update happens!
}

Timer handling

Some xC timer macros

See this discussed in Know your timer’s type.

typedef signed int time32_t; // signed int (=signed) or unsigned int (=unsigned) both ok, as long as they are monotoneously increasing
                             // xC/XMOS 100 MHz increment every 10 ns for max 2exp32 = 4294967296, 
                             // ie. divide by 100 mill = 42.9.. seconds

#define AFTER_32(a,b) ((a-b)>0)

#ifdef DO_ASSERTS
    #define ASSERT_DELAY_32(d) do {if (d > INT_MAX) fail("Overflow");} while (0) // Needs <so646.h<, <limits.h> and <xassert.h>
    // INT_MAX is 2147483647 is what fits into 31 bits or last value before a signed 32 bits wraps around
#else
    #define ASSERT_DELAY_32(d)
#endif

#define NOW_32(tmr,time) do {tmr :> time;} while(0) // A tick is 10ns
// “Programming xC on XMOS Devices” (Douglas Watt)
//     If the delay between the two input values fits in 31 bits, timerafter is guaranteed to behave correctly,
//     otherwise it may behave incorrectly due to overlow or underflow. This means that a timer can be used to
//     measure up to a total of 2exp31 / (100 mill) = 21s.

Repeated busy-poll with timeout

Timeout intro

Time/timer handling has been extensively discussed in Know your timer’s type, also for xC. The background material is there.

Some times we want to read from f.ex. a register on an I/O chip until some value is seen. However, we can’t read forever, so we need to wrap the “busy-poll” in some timeout-mechanism to avoid it repeating forever.

Below there are four chapters, with full code (for xTIMEcomposer) and logs:

  1. C that works with and xC that fails
    Cannot be used!
    The C-code for the Arduino is “blocking” and there is no support for multi-threading. However, my first trial to port this to xC failed
  2. xC that works with 0.65536 ms resolution
    Use this “MILLIS” pattern if you can accept about 152 ticks per 100 ms.
    But a version where the 1 ms resolution of the Arduino millis() had to be ported as 0.65536 ms ticks in xC works. The problem is how “modulo 1 ms” arithmetics is done when the timer word with is 32 bits and it increments by the XMOS processor every 10 ns. So 10 ns  * 2exp16 -> 0.65536 ms. This timing is done “inline”,  but since anything xC is multi-threading (multi-task) that doesn’t really harm it. The problem is I can’t order a 100 ms timeout (but I could do 152 ticks = 99.61472 ms)
  3. xC that works with 1 ms resolution
    Use this standard xC pattern if you want to use XC’s construct as it is.
    Here we have proper 1 ms resolution, done by “proper” handling by an xC timer, by storing a future timeout-value in proper 32 bits width. The code is “inline” as above
  4. xC server with state-based timing serving two clients
    Use this standard xC pattern if you only have a timeout per task.
    Here the timing is handled in a proper select case. I guess this is closest to idiomatic xC. The server serves two clients, one that fails and one that succeeds in reading a register. Impressingly the handling of the two clients is “fair”, ie. none of them jam the other
  5. xC true MILLIS function (courtesy akp)
    Use this real MILLIS pattern if you can accept a local state per task. Uses intermediate 64 bit values.
    Having done all the four code examples above I started a thread about this at the XCore Exchange. “akp” had a very smart solution that does a true MILLIS and works and looks like the Arduino code. I copied it back here, see  xC true MILLIS function (courtesy akp)

For me this chapter started with use of the Arduino millis(); timeout code in a library. Not that I  haven’t made timeouts before, in several languages and using several mechanisms (like a built-in timer/counter HW unit and its interrupt drivers or (the opposite) using CSP-like channel timeouts (like the idiomatic xC select case timeouts)) – and the xC language even has language support for this through the timer primitive. But there was still something new about the mechanism built with the Arduino millis() function:

C that works with and xC that fails

Cannot be used!

Here is an example from LowPowerLab’s RFM69 library (here, file RFM69.cpp). I have added some parenthesis to remove my thinking from operator precedence that I am utterly bad on (partly because I was “raised” on occam that doesn’t have it! (to the complaints of those who hate (too many!?) parenthesis levels)):

uint32_t txStart = millis();
while ((digitalRead(_interruptPin) == 0) && (millis() - txStart < RF69_TX_LIMIT_MS)); 
// wait for DIO0 to turn HIGH signalling transmission finish

The millis() value “Returns the number of milliseconds since the Arduino board began running the current program. This number will overflow (go back to zero), after approximately 50 days.” (2exp32 ms = 4294967296 ms = 4294967,296 seconds = 1193,046471111111111 hours = 49,71026962962963 days).

I made a test of this code (there) at the EDA playground. It doesn’t run the code above, it just tests the wrap-around properties. It works perfectly fine also when the value wraps around, if I use (A) int32_t (wraps around after MAXPOS(int32_t) 0x7FFFFFFF to MAXNEG(int32_t) 0x80000000) or use (B) uint32_t (wraps around at after MAXPOS(uint32_t) 0xFFFFFFFF to zero). As seen in the Know your timer’s type blog note, as long as the value is monotonously increasing it doesn’t matter. How the most significant bit is interpreted is irrelevant. (I have not insert the code here.)

By the way, I have started to discuss the library porting to xC at My aquarium’s data radioed through the shelf, chapter “Porting to XMOS xC”.

If I made a  millis() function in xC it did not work! The problem with the below code is that it does not make any correct modulo some-word-width type of arithmetics, since the timer tick is 10 ns and not 1 ms. I can’t just divide! So this does not handle wrap-around correctly:

// http://www.xcore.com/viewtopic.php?f=26&t=6470
#include <stdio.h>
#include <xs1.h>
#include <iso646.h>
#include <timer.h> // delay_milliseconds(200), XS1_TIMER_HZ etc

typedef enum {false,true} bool;

#define DEBUG_PRINT_TEST 1
#define debug_print(fmt, ...) do { if(DEBUG_PRINT_TEST) printf(fmt, __VA_ARGS__); } while (0)

// https://www.arduino.cc/reference/en/language/functions/time/millis/
signed millis () {
    timer tmr;
    signed current_time;
    tmr :> current_time;
    return (current_time / XS1_TIMER_KHZ); 
// Never works! XS_TIMER_KHZ is 100*1000
 }

unsigned digitalRead (void) {
    return (0);
}

#define RF69_TX_LIMIT_MS 100
#define TEST_TYPE signed 
// Too wide and does not match millis()
#define MILLIS() millis()

void test_task (void) {

    TEST_TYPE millis_;
    TEST_TYPE txStart;
    unsigned  testCnt = 0;
    bool      not_timed_out;

    while (testCnt < 500) {
        txStart = MILLIS();
        do {
            millis_ = MILLIS();
            not_timed_out = (millis_ - txStart) < RF69_TX_LIMIT_MS;
            debug_print    ("testCnt(%d), millis(%d), txStart(%d), millis_-txStart(%d), timedOut(%d)\n",
                    testCnt, millis_, txStart, millis_ - txStart, !not_timed_out);
            delay_milliseconds(20);
        } while ((digitalRead() == 0) && not_timed_out);
        testCnt++;
    }
}

int main () {
    par {test_task (); }
    return 0;
}

/* DOES NOT WORK!
testCnt(351), millis(42729), txStart(42629), millis_-txStart(100), timedOut(1)
testCnt(352), millis(42749), txStart(42749), millis_-txStart(0), timedOut(0)
testCnt(352), millis(42769), txStart(42749), millis_-txStart(20), timedOut(0)
testCnt(352), millis(42789), txStart(42749), millis_-txStart(40), timedOut(0)
testCnt(352), millis(42809), txStart(42749), millis_-txStart(60), timedOut(0)
testCnt(352), millis(42829), txStart(42749), millis_-txStart(80), timedOut(0)
testCnt(352), millis(42849), txStart(42749), millis_-txStart(100), timedOut(1) LAST TIMEOUT EVER!
testCnt(353), millis(42869), txStart(42869), millis_-txStart(0), timedOut(0)
testCnt(353), millis(42890), txStart(42869), millis_-txStart(21), timedOut(0)
testCnt(353), millis(42910), txStart(42869), millis_-txStart(41), timedOut(0)
testCnt(353), millis(42930), txStart(42869), millis_-txStart(61), timedOut(0)
testCnt(353), millis(0), txStart(42869), millis_-txStart(-42869), timedOut(0)
testCnt(353), millis(20), txStart(42869), millis_-txStart(-42849), timedOut(0)
testCnt(353), millis(40), txStart(42869), millis_-txStart(-42829), timedOut(0)
testCnt(353), millis(60), txStart(42869), millis_-txStart(-42809), timedOut(0)
testCnt(353), millis(80), txStart(42869), millis_-txStart(-42789), timedOut(0)
testCnt(353), millis(100), txStart(42869), millis_-txStart(-42769), timedOut(0)
testCnt(353), millis(120), txStart(42869), millis_-txStart(-42749), timedOut(0)
testCnt(353), millis(140), txStart(42869), millis_-txStart(-42729), timedOut(0)
*/

xC that works with 0.65536 ms resolution

Use this “MILLIS” pattern if you can accept about 152 ticks per 100 ms.

However, the next code works. But it has to be, not millis(,) but 65536-micros(), that I have called millis_0_65536. This compares to xC/XMOS 100 MHz system ticks, ie. 10 ns shifted down 16 bits (or divided by 65536). This wraps a full 32 bits xC timer down to a 16 bits value. The arithmetics is then modulo short (16 bits), done so by the compiler. In addition I had to define a diff variable that was also of type short (16 bits), if not it did not work. I guess, since the millis_ - txStart was built as a primitive type with (32 bits) difference, not a derived type (16 bits) difference. Anyhow, this works, but not with 1 ms resolution, but with 0.65536 ms resolution, so I have defined a “fast millisecond” here

See https://www.teigfam.net/oyvind/home/technology/165-xc-code-examples/#xc_that_works_with_065536_ms_resolution and http://www.xcore.com/viewtopic.php?f=26&t=6470

#include <stdio.h>
#include <xs1.h>
#include <iso646.h>
#include <timer.h> // delay_milliseconds(200), XS1_TIMER_HZ etc

typedef enum {false,true} bool;

#define DEBUG_PRINT_TEST 1
#define debug_print(fmt, ...) do { if(DEBUG_PRINT_TEST) printf(fmt, __VA_ARGS__); } while (0)
// DEFINING A FMS-TICK AS A "fast ms" (fms, FMS) - AND IT IS 0.65536 ms (2exp16=65536) // One fms-tick is 100 MHz XMOS system-tick (10 ns) into a 16 bits word every 65536 system-tick
//
typedef signed short time16_fms_t; // fms=fast ms
//
#define FAST_MILLIS_PER_10MS    15 //   10 / .65536
#define FAST_MILLIS_PER_100MS  153 //  100 / .65536
#define FAST_MILLIS_PER_1S    1526 // 1000 / .65536
//
#define MS_TO_FMS(ms) ((ms*FAST_MILLIS_PER_1S)/1000)
time16_fms_t fms()
{ // "MILLIS": fms=fast ms. Returns one tick as 0.65536 ms (10ns * 65536)
    timer tmr;           // 32 bits
    signed current_time; // 32 bits
    tmr :> current_time; // 32 bits
    return (time16_fms_t) (current_time >> 16); // 16 bits. Keep sign bit (or use / 65536)
}

unsigned digitalRead (void) {
    return (0);
}

#define RF69_TX_LIMIT_MS    100
#define RF69_TX_LIMIT_FMS   MS_TO_FMS (RF69_TX_LIMIT_MS)

void 
test_task
(void) {

    time16_fms_t now_fms;
    time16_fms_t txStart_fms;
    time16_fms_t diff_fms;

    unsigned  testCnt = 0;
    bool      not_timed_out;
    debug_print ("100 ms is %d ticks\n",RF69_TX_LIMIT_FMS);

    while (testCnt < 500) {
        txStart_fms = fms();
        do {
            now_fms = fms();
            diff_fms = now_fms - txStart_fms;
            not_timed_out = diff_fms < RF69_TX_LIMIT_FMS;
            debug_print    ("testCnt(%d), now_fms(%d), txStart_fms(%d), diff_fms(%d), timedOut(%d)\n",
                    testCnt, now_fms, txStart_fms, diff_fms, !not_timed_out);
            delay_milliseconds (RF69_TX_LIMIT_MS/2); // TRUE 50 ms!
        } while ((digitalRead() == 0) && not_timed_out);
        testCnt++;
    }
}

int 
main() {
    par {
test_task
 ();
    }
    return 0;
}
/* WORKS:
100 ms is 152 ticks
testCnt(0), now_fms(581), txStart_fms(581), diff_fms(0), timedOut(0)
testCnt(0), now_fms(657), txStart_fms(581), diff_fms(76), timedOut(0)
testCnt(0), now_fms(734), txStart_fms(581), diff_fms(153), timedOut(1)
testCnt(1), now_fms(810), txStart_fms(810), diff_fms(0), timedOut(0)
...
testCnt(139), now_fms(32586), txStart_fms(32434), diff_fms(152), timedOut(1)
testCnt(140), now_fms(32663), txStart_fms(32663), diff_fms(0), timedOut(0)
testCnt(140), now_fms(32739), txStart_fms(32663), diff_fms(76), timedOut(0)
testCnt(140), now_fms(-32719), txStart_fms(32663), diff_fms(154), timedOut(1)
testCnt(141), now_fms(-32643), txStart_fms(-32643), diff_fms(0), timedOut(0)
testCnt(141), now_fms(-32567), txStart_fms(-32643), diff_fms(76), timedOut(0)
testCnt(141), now_fms(-32490), txStart_fms(-32643), diff_fms(153), timedOut(1)
*/

xC that works with 1 ms resolution

Use this standard xC pattern if you want to use XC’s contruct as it is.

The next code works, and with the expected 1 ms resolution. I am more used to comparing against the future timeout time than comparing against used time being below some max allowed. So, here is some test code in xC showing this, that works! It looks more verbose, but it basically isn’t. Also, there is no milliseconds timer here, there are all 32 bit 100 MHz (10 ns ticks) values, ie. the basic timer of xC/XMOS. A ms is exactly 100*1000 10 ns cycles. The first part is test_task and it has a lot of prints. The other is the same functionality in real_task with little printing:

#include <stdio.h>
#include <xs1.h>
#include <iso646.h>
#include <timer.h> // delay_milliseconds(200), XS1_TIMER_HZ etc

typedef enum {false, true} bool;

#define DEBUG_PRINT_TEST 1
#define debug_print(fmt, ...) do { if(DEBUG_PRINT_TEST) printf(fmt, __VA_ARGS__); } while (0)

#define AFTER_32(a,b) ((a-b)>0)
#define LEFT_32(a,b)  (b-a)
#define NOW_32(t,n) do {t :> n;} while(0) // A tick is 10ns (100 MHz timer in xC). 32 bits wide

#define WAIT_FOR_REGISTER_MS 1000
#define NO_REGISTER_VALUE    0
#define NO_REGISTER          NO_REGISTER_VALUE
#define SOME_REGISTER       (NO_REGISTER_VALUE+1)

unsigned readRegister (const unsigned reg) {
    delay_milliseconds(200);
    return (reg);
}

{bool, signed} test_task (void) { // return_return_read_ok, return_timed_out_cnt
    unsigned read_value;

    timer tmr; // A tick is 10ns (100 MHz timer in xC). 32 bits wide
    // Rotates from 0 to MOSTPOS then one ++ to MOSTNEG then up to zero again
    // MOSTPOS =  2exp31-1 = 2147483647 = 0x7FFFFFFF
    // MOSTNEG = -2exp31   =-2147483648 = 0x80000000, then finally to 0xFFFFFFFF(-1)

    signed   now_time_ticks;
    signed   start_time_ticks;
    signed   timeout_at_ticks;
    bool     timed_out      = false;
    bool     return_read_ok = false ;
    unsigned return_timed_out_cnt = 0;

    debug_print ("%s\n", "test_task started");

    while ((return_timed_out_cnt < 30) and (not return_read_ok)) {

        NOW_32 (tmr, start_time_ticks);
        timeout_at_ticks = start_time_ticks + (WAIT_FOR_REGISTER_MS * XS1_TIMER_KHZ);
        debug_print ("RESTART START(%d) and TIMEOUTAT(%d)\n",
                start_time_ticks, timeout_at_ticks);

        do {
            read_value = readRegister (NO_REGISTER); // Try SOME_REGISTER
            return_read_ok = (read_value != NO_REGISTER_VALUE);

            if (return_read_ok) {
                // Finished
            } else {
                NOW_32 (tmr, now_time_ticks);
                timed_out = AFTER_32 (now_time_ticks, timeout_at_ticks);

                if (timed_out) {
                    return_timed_out_cnt++;
                } else {} // Continue
            }

            debug_print ("  START(%d), NOW(%d), LEFT(%d), TIMEDOUT(%d) - TIMEOUTS(%d)\n",
                    start_time_ticks,
                    now_time_ticks,
                    LEFT_32(now_time_ticks, timeout_at_ticks),
                    timed_out,
                    return_timed_out_cnt);
        } while ((not return_read_ok) and (not timed_out));
    }
    debug_print ("test_task finished with READOK(%d) and TIMEOUTS(%d)\n",
            return_read_ok, return_timed_out_cnt);
    return  {return_read_ok, return_timed_out_cnt};
}

/*
RESTART START(2039473606) and TIMEOUTAT(2139473606)
  START(2039473606), NOW(2059478404), LEFT(79995202), TIMEDOUT(0) - TIMEOUTS(20)
  START(2039473606), NOW(2079486402), LEFT(59987204), TIMEDOUT(0) - TIMEOUTS(20)
  START(2039473606), NOW(2099494400), LEFT(39979206), TIMEDOUT(0) - TIMEOUTS(20)
  START(2039473606), NOW(2119502398), LEFT(19971208), TIMEDOUT(0) - TIMEOUTS(20)
  START(2039473606), NOW(2139510396), LEFT(-36790), TIMEDOUT(1) - TIMEOUTS(21)
RESTART START(2139518043) and TIMEOUTAT(-2055449253)
  START(2139518043), NOW(-2135444355), LEFT(79995102), TIMEDOUT(0) - TIMEOUTS(21)
  START(2139518043), NOW(-2115436255), LEFT(59987002), TIMEDOUT(0) - TIMEOUTS(21)
  START(2139518043), NOW(-2095428155), LEFT(39978902), TIMEDOUT(0) - TIMEOUTS(21)
  START(2139518043), NOW(-2075420055), LEFT(19970802), TIMEDOUT(0) - TIMEOUTS(21)
  START(2139518043), NOW(-2055411955), LEFT(-37298), TIMEDOUT(1) - TIMEOUTS(22)
RESTART START(-2055404238) and TIMEOUTAT(-1955404238)
  START(-2055404238), NOW(-2035399198), LEFT(79994960), TIMEDOUT(0) - TIMEOUTS(22)
  START(-2055404238), NOW(-2015390996), LEFT(59986758), TIMEDOUT(0) - TIMEOUTS(22)
  START(-2055404238), NOW(-1995382794), LEFT(39978556), TIMEDOUT(0) - TIMEOUTS(22)
  START(-2055404238), NOW(-1975374592), LEFT(19970354), TIMEDOUT(0) - TIMEOUTS(22)
  START(-2055404238), NOW(-1955366390), LEFT(-37848), TIMEDOUT(1) - TIMEOUTS(23)
*/

{bool, signed} real_task (void) { // return_return_read_ok, return_timed_out_cnt
    unsigned read_value;

    timer tmr; // A tick is 10ns (100 MHz timer in xC). 32 bits wide
    // Rotates from 0 to MOSTPOS then one ++ to MOSTNEG then up to zero again
    // MOSTPOS =  2exp31-1 = 2147483647 = 0x7FFFFFFF
    // MOSTNEG = -2exp31   =-2147483648 = 0x80000000, then finally to 0xFFFFFFFF(-1)

    signed   start_time_ticks;
    signed   timeout_at_ticks;
    bool     timed_out      = false;
    bool     return_read_ok = false ;
    unsigned return_timed_out_cnt = 0;

    debug_print ("%s\n", "real_task started");

    tmr :> start_time_ticks;
    timeout_at_ticks = start_time_ticks + (WAIT_FOR_REGISTER_MS * XS1_TIMER_KHZ);

    do {
        read_value     = readRegister (NO_REGISTER); // Try SOME_REGISTER
        return_read_ok = (read_value != NO_REGISTER_VALUE);

        if (return_read_ok) {
            // Finished
        } else {
            signed now_time_ticks;
            tmr :> now_time_ticks;
            timed_out = AFTER_32 (now_time_ticks, timeout_at_ticks);

            if (timed_out) {
                return_timed_out_cnt++;
            } else {} // Continue
        }
    } while ((not return_read_ok) and (not timed_out));

    debug_print ("real_task finished with READOK(%d) and TIMEOUTS(%d)\n",
            return_read_ok, return_timed_out_cnt);
    return  {return_read_ok, return_timed_out_cnt};
}

int main() {
    par {
        test_task();
        real_task();
    }
    return 0;
}

/*
real_task started
test_task started
RESTART START(38716970) and TIMEOUTAT(138716970)
  START(38716970), NOW(58721387), LEFT(79995583), TIMEDOUT(0) - TIMEOUTS(0)
  START(38716970), NOW(78728680), LEFT(59988290), TIMEDOUT(0) - TIMEOUTS(0)
  START(38716970), NOW(98735973), LEFT(39980997), TIMEDOUT(0) - TIMEOUTS(0)
  START(38716970), NOW(118743266), LEFT(19973704), TIMEDOUT(0) - TIMEOUTS(0)
real_task finished with READOK(0) and TIMEOUTS(1)
  START(38716970), NOW(138750700), LEFT(-33730), TIMEDOUT(1) - TIMEOUTS(1)
RESTART START(138757765) and TIMEOUTAT(238757765)
  START(138757765), NOW(158762323), LEFT(79995442), TIMEDOUT(0) - TIMEOUTS(1)
  START(138757765), NOW(178769897), LEFT(59987868), TIMEDOUT(0) - TIMEOUTS(1)
  START(138757765), NOW(198777471), LEFT(39980294), TIMEDOUT(0) - TIMEOUTS(1)
  START(138757765), NOW(218785045), LEFT(19972720), TIMEDOUT(0) - TIMEOUTS(1)
  START(138757765), NOW(238792619), LEFT(-34854), TIMEDOUT(1) - TIMEOUTS(2)
*/

xC server with state-based timing serving two clients

Use this standard xC pattern if you only have a timeout per task.

This code is the most complete, and of course it works. There are two clients asking for registers  from a server. Client[0] asks for “register 0” that always fails (with “ok(0)” in the log) – and client[1] asks for “register 1” that always succeeds (with “ok(1)” in the log).

The timing section of interest here is this select case in Deliver_register_server_task:

case (state==s_read) => tmr when timerafter (timeout_at_ticks) :> void:

That section reads the register when (state==s_read) and finishes if the reading succeeds, but does retries every 100 ms until retrial_cnt is 5. This way to do a timeout I guess is the one I have used the most.

#include <stdio.h>
#include <xs1.h>
#include <iso646.h>
#include <timer.h> // delay_milliseconds(200), XS1_TIMER_HZ etc
#define DEBUG_PRINT_TEST 1
#define debug_print(fmt, ...) do { if(DEBUG_PRINT_TEST) printf(fmt, __VA_ARGS__); } while (0)

typedef enum {false, true} bool;

typedef interface read_reg_if {
    [[guarded]]              void            read_register (const unsigned reg_num);
    // Guard needed so that clients shall not overlap

    [[notification]] slave   void            read_register_ready (void);
    [[clears_notification]] {unsigned, bool} get_register (void); // Returns value, ok
} read_reg_if;

unsigned digitalRead (const unsigned reg_num) {
    unsigned reg_value = reg_num; // Easy rule to code by. 0 causes "ok(0)"/ERR and 1 causes "ok(1)"/OK
    return (reg_value);
}

#define NUM_CLIENTS 2

typedef enum {c_next, c_wait} client_state_e;
#define CLIENT_POLL_EVERY_SECS 4

[[combinable]] void 
Read_register_client_task (client read_reg_if i_read_reg, const unsigned iof_client) {

    timer          tmr;
    signed         timeout_at_ticks;
    client_state_e state = c_next;
    signed         num_services = 0; // iof_client[0,1] are actually served FAIR!
                                     // And I have no private code to ensure it!

    tmr:> timeout_at_ticks;
    timeout_at_ticks += CLIENT_POLL_EVERY_SECS * XS1_TIMER_HZ;

    while(1) {
        select {
            case (state==c_next) => tmr when timerafter (timeout_at_ticks) :> void: {
                timeout_at_ticks += CLIENT_POLL_EVERY_SECS * XS1_TIMER_HZ; // No time skew
                num_services++;
                debug_print ("Client    [%d] read_register pending with num_services(%d)\n",
                        iof_client, num_services);

                i_read_reg.read_register(iof_client); // Any number that's unique

                unsigned now_log_timer_ticks; // Using unsigned to avoid minus sign:
                tmr :> now_log_timer_ticks;
                debug_print ("[%5u ms]\nClient    [%d] read_register sent\n",
                        now_log_timer_ticks/XS1_TIMER_KHZ, // Max 42949
                        iof_client);
                state = c_wait;
            } break;

            case (state==c_wait) => i_read_reg.read_register_ready() : {
               unsigned register_value;
               bool     register_ok;

               {register_value, register_ok} = i_read_reg.get_register();
               debug_print ("Client    [%d] got register(%d) ok(%d)\n",
                       iof_client, register_value, register_ok);
               state = c_next;
            } break;
        }
    }
}

typedef enum {s_next, s_read, s_deliver} server_state_e; // s_deliver not used explcitly
#define SERVER_TIMEOUT_MS 1000
#define SERVER_RETRY_MS    200

[[combinable]] void 
Deliver_register_server_task (server read_reg_if i_read_reg[NUM_CLIENTS]) {

    timer          tmr;
    signed         timeout_at_ticks;
    server_state_e state = s_next;
    unsigned       register_value;
    int            index_of_client;
    bool           register_ok;
    unsigned       reg_num;
    unsigned       retrial_cnt = 0;

    while(1) {
        select {
            case (state==s_next) => i_read_reg[int index_of_client_].read_register (const unsigned reg_num_) : {

                index_of_client = index_of_client_;
                reg_num = reg_num_;
                tmr :> timeout_at_ticks; // Immediately digitalRead
                retrial_cnt = 0;
                state = s_read;
                debug_print ("Server for[%d] read_register\n", index_of_client);
            } break;

            case (state==s_read) => tmr when timerafter (timeout_at_ticks) :> void: {
                register_value = digitalRead(reg_num);
                if (register_value == 0) {
                    retrial_cnt++;
                    if (retrial_cnt == (SERVER_TIMEOUT_MS/SERVER_RETRY_MS)) { // ==5
                        debug_print ("Server for[%d] retry(%d) max\n", index_of_client, retrial_cnt);
                        register_ok = false;
                        i_read_reg[index_of_client].read_register_ready();
                        state = s_deliver;
                    } else {
                        debug_print ("Server for[%d] retry(%d)\n", index_of_client, retrial_cnt);
                        timeout_at_ticks += SERVER_TIMEOUT_MS * XS1_TIMER_KHZ; // No time skew
                    }
                } else {
                    debug_print ("Server for[%d] register == OK \n", index_of_client);
                    register_ok = true;
                    i_read_reg[index_of_client].read_register_ready();
                    state = s_deliver; // Not tested, but leads to get_register
                }
            } break;

            case i_read_reg[int index_of_client_].get_register (void) -> {unsigned return_register_value, bool return_ok}: {

                // No need to guard with (state==s_deliver) since that protocol is ensured by client
                // usage and the client/server rule with [[notification]] and [[clears_notification]]
                // would cause deadlock if not followed

                debug_print ("Server for[%d] register value(%d) ok(%d) sent\n",
                        index_of_client, register_value, register_ok);
                return_register_value = register_value;
                return_ok = register_ok;
                state = s_next ;
            } break;
        }
    }
}

int main () {
    read_reg_if i_read_reg[NUM_CLIENTS];
    par {
        Read_register_client_task (i_read_reg[0], 0); // Will fail    with "ok(0)"/ERR
        Read_register_client_task(i_read_reg[1], 1); // Will succeed with "ok(1)"/OK
        Deliver_register_server_task (i_read_reg);
    }
    return 0;
}

And here is some of the log. You can see that the select is “fair” since num-services actually follow each other for client[0] and client[1]. This is automatically done by how the code is built by the compiler, I guess – since I haven’t done anything to ensure this. This is rather impressing, especially perhaps since the server does different jobs for each of them! But I assume it has to do with some queuing, see “pending”. I have discussed fair or fairness a lot in Nondeterminism.

Server for[0] read_register
[14918 ms]
Client    [0] read_register sent
Server for[0] retry(1)
Server for[0] retry(2)
Server for[0] retry(3)
Server for[0] retry(4)
Client    [1] read_register pending with num_services(112)
Server for[0] retry(5) max
Server for[0] register value(0) ok(0) sent
Client    [0] got register(0) ok(0)
Server for[1] read_register
Client    [0] read_register pending with num_services(112)
Server for[1] register == OK 
[18918 ms]
Client    [1] read_register sent
Server for[1] register value(1) ok(1) sent
Client    [1] got register(1) ok(1)
Server for[0] read_register
[18918 ms]
Client    [0] read_register sent
Server for[0] retry(1)
Server for[0] retry(2)
Server for[0] retry(3)
Server for[0] retry(4)
Client    [1] read_register pending with num_services(113)
Server for[0] retry(5) max
Server for[0] register value(0) ok(0) sent
Client    [0] got register(0) ok(0)
Server for[1] read_register
Client    [0] read_register pending with num_services(113)
Server for[1] register == OK

xC true MILLIS function (courtesy akp)

Use this real MILLIS pattern if you can accept a local state per task. Uses intermediate 64 bit values.

The code below was suggested by “akp” at the XCore Exchange, in the already mentioned thread that I started: Porting the Arduino millis(), possible?. It uses one local state per task and that state stores the values that need to survive between each call. It also uses a 64 bit intermediate value. The code in a comment (by me) one page 3 (dated  Thu Mar 22, 2018 9:42 pm) probably is the final code (still courtesy and thanks to akp!). However, I must admit that have made some new typedefs and defines here because I have learned to like this style:

#include <xs1.h>
#include <stdio.h>
#include <iso646.h>
#include <timer.h> // delay_milliseconds(200), XS1_TIMER_HZ etc

typedef enum {false, true} bool;

#define DEBUG_PRINT_TEST 1
#define debug_print(fmt, ...) do { if(DEBUG_PRINT_TEST) printf(fmt, __VA_ARGS__); } while (0)

typedef unsigned long      systick32_t; 
typedef unsigned long long tmptick64_t; 
typedef unsigned long      millis32_t;

#define extend_32to64(tohigh32,tolow32) ((((tmptick64_t)tohigh32)<<32) bitor (tmptick64_t)tolow32)
#define XS1_TIMER_KHZ_LL ((tmptick64_t)XS1_TIMER_KHZ)

typedef struct millis_state_t {
    systick32_t tick_hi;
    systick32_t last_tick;
} millis_state_t;

#define MILLIS(s) millis_p(s)

millis32_t millis_p (millis_state_t &millis_state) {
    timer tmr;
    tmptick64_t total_ticks;
    systick32_t current_tick;
    tmr :> current_tick;
    if (current_tick < millis_state.last_tick) {
        ++millis_state.tick_hi;
    } else {}
    millis_state.last_tick = current_tick;
    total_ticks = extend_32to64 (millis_state.tick_hi,current_tick);
    return (millis32_t)(total_ticks / XS1_TIMER_KHZ_LL);
}

unsigned digitalRead (void) {
    delay_milliseconds (200);
    return (0);
}

#define RF69_TX_LIMIT_MS 1000

void test_task (int task_num) {

    millis32_t     millis_;
    millis32_t     txStart;
    unsigned       testCnt = 0;
    bool           not_timed_out;
    millis_state_t millis_state = {0,0};

    while (testCnt < 2000) {
        txStart = MILLIS(millis_state);
        do {
            millis_ = MILLIS(millis_state);
            not_timed_out = (millis_ - txStart) < RF69_TX_LIMIT_MS;
            debug_print ("task %d: testCnt(%d), millis(%d), txStart(%d), millis_-txStart(%d), timedOut(%d)\n",
                    task_num, testCnt, millis_, txStart, millis_ - txStart, !not_timed_out);
            delay_milliseconds(20);
        } while ((digitalRead() == 0) and not_timed_out);
        testCnt++;
    }
}

int main() {
    par {
        test_task(0);
        test_task(1);
    }
    return 0;
}

timerafter to now and skew

This chapter is about timerafter (must be after some time in the future).

Even if the below code makes sense, if you want to trigger immediately. But timerafter("yesterday") does not imply any waiting, which the after implies – and  in effect timeafter("a cycle or two ago") does nothing more that trigger the select case (like ALT TRUE & SKIP in occam). xC doesn’t have that construct, but this is probably as “cheap” (costs little):

tmr :> time_ticks; // IMMEDIATELY
while (1) {
    select {
        case tmr when timerafter (time_ticks) :> void : { 
            // NOW
        } break;
    }
}

This is described in “Programming xC on XMOS Devices” by Douglas Watt (xC is C plus x , ref [14], page 18) like this:

A programming error may be introduced by inputting the new time instead of ignoring it with a cast to void, as in when timerafter(event_time) :> time; Because the processor completes the input shortly after the time specified is reached, this operation actually increments the value of time by a small additional amount. This amount may be compounded over multiple loop iterations, leading to signal drift and ultimately a loss of synchronisation with a receiver. (Added by me: see next chapter: this is not a fault in the xC language!)

timerafter to now and skew code

Don’t use the now output for anything else than it being now!

Should you in any way use it to calculate the next timeout you will get time skew. This is the sum of all lost ticks.  Here is the code to show the skew:

// Almost as XMOS Programming Guide XM004440A 2015/9/18 page 33

#include <xs1.h>
#include <stdio.h>
#include <iso646.h>
#include <timer.h> // delay_milliseconds(200), XS1_TIMER_HZ etc

#define DEBUG_PRINT_TEST 1
#define debug_print(fmt, ...) do { if(DEBUG_PRINT_TEST) printf(fmt, __VA_ARGS__); } while (0)

// See Know your timer's type
// ABOUT SIGNED OR UNSIGNED:
// signed int (=signed) or unsigned int (=unsigned) both ok, as long as they are monotoneously increasing. xC/XMOS 100 MHz increment every 10 ns for max 2exp32 = 4294967296, ie. 42.9.. seconds
typedef signed int time32_t;

#define ONE_SECOND_TICKS (1000 * XS1_TIMER_KHZ) // MAX INC IS 2exp31/(100 mill) = 21.47 seconds.
// #define TICKS 2147483648 // 2exp31 THIS IS THE LONGEST REPEATED PRECISE at 2147483648 (not 2exp31-1): 

void test_task (void) {

    timer     tmr;
    time32_t  event_time; // Name from XMOS lib_i2c, both time stamps are used

    tmr :> event_time;
    debug_print ("\ntimerafter time %08X and ONE_SECOND_TICKS is %08X (%d)\n", event_time, ONE_SECOND_TICKS, ONE_SECOND_TICKS);

    while (1) {
        select {
            case tmr when timerafter (event_time) :> time32_t now: {
                debug_print ("timerafter event_time %08X :> now %08X, lost %d ticks\n", event_time, now, now-event_time);

                now += ONE_SECOND_TICKS; // FUTURE TIMEOUT
                debug_print ("\ntimerafter time %08X\n", event_time);
            } break;
        }
    }
}

int main () {
    par {
        test_task();
    }
    return 0;
}

And here is the log. Observe that the 1 second increment 05F5E100 has these nice 00 as LSB, so it’s easy to see that the xC timerafter mechanism in itself has no skew whatsoever (57 as LSB). Repeated addition with a constant doesn’t usually have skew, but it’s nice to see it when applied to a timer as well:

timerafter event_time 0244E557 and ONE_SECOND_TICKS is 05F5E100 (100000000)
timerafter event_time 0244E557 :> now 0244F005, lost 2734 ticks

timerafter event_time 083AC657
timerafter event_time 083AC657 :> now 083AC659, lost 2 ticks

timerafter event_time 0E30A757
timerafter event_time 0E30A757 :> now 0E30A759, lost 2 ticks

timerafter event_time 14268857
timerafter event_time 14268857 :> now 14268859, lost 2 ticks

It’s interesting to see that the first now is way off from the timeout. I assume this has to do with the set-up of the select etc.?

Conclusion is that perhaps just outputting to void is less misleading:

while (1) {
    select {
        case tmr when timerafter (time) :> void: {
        ...
}

timerafter with possibly ZERO delay to test max speed

This code is inspired from the My xC combined combinable notes (chapter Top_20 is fair). The living code (where this test code was present only during the tests) is at https://www.teigfam.net/oyvind/blog_notes/217/src_txt/top_occam_like_xc.h.txt. All the code is at 217:[The code(download)].

It solves the problem of what might happen if you want to test how fast things run go without any delay, but in the product you want to do thing on a more slow basis.

This code solves shows how to solve the problem that will arise for a case where it is legal to set the delay to ZERO or even as long as 1 sec.  Let’s look at the uncluttered always working code first:

void Timer_test_node_short (out buffered port:4 outP4_leds) {

    timer    tmr;
    time32_t time_ticks;
    time32_t time_start_ticks;
    time32_t time_stop_ticks;
               
    unsigned leds = BOARD_LEDS_INIT;

    outP4_leds <: leds;
    
    #define TIMEOUT_TICKS 0 // use XS1_TIMER_HZ XS1_TIMER_KHZ XS1_TIMER_MHZ

    tmr :> time_ticks;

    while (1) {
        select {
            case tmr when timerafter (time_ticks) :> time_start_ticks : { // No skew here

                // Do something here. Let's assume it takes 1.5 us

                // Swap LED, observe on oscilloscope
                leds xor_eq BOARD_LED_MASK_GREEN_ONLY; // J1.7 CH1 XCORE-200-EXPLORER
                outP4_leds <: leds; // Seen always, for any TIMEOUT_TICKS value
                    
                tmr :> time_stop_ticks;
                
                { // timerafter always has to be "the future", that value must NEVER lag behind!
                    const time32_t time_used_ticks = time_stop_ticks - time_start_ticks;
                    if (time_used_ticks >= TIMEOUT_TICKS) {
                        // TIMEOUT_TICKS == 0 typically here
                        tmr :> time_ticks; // This seems to take "no time"
                    } else {
                        // TIMEOUT_TICKS >= 2us typically here
                        // TIMEOUT_TICKS == 0   never arrives here
                        time_ticks += TIMEOUT_TICKS; // No skew 
                    }
                }                
            } break;
        }
    }
}

Then the code I used to test with (I removed the “Do something here” code):

void Timer_test_node (out buffered port:4 outP4_leds) {

    timer    tmr_1;
    time32_t time_ticks_1;
    time32_t time_start_ticks_1;
    time32_t time_stop_ticks_1;
    
    timer    tmr_2;
    time32_t time_ticks_2;
    
    timer    tmr_3;
    time32_t time_ticks_3;
    
    time32_t time_2m1_prev_ticks;
    time32_t time_3m1_prev_ticks;
          
    unsigned leds = BOARD_LEDS_INIT;

    outP4_leds <: leds;
    
    #define TIMEOUT_TICKS 0 // use XS1_TIMER_HZ XS1_TIMER_KHZ XS1_TIMER_MHZ
    debug_print ("T1,T2,T3 TIMEOUT_TICKS %u\n", TIMEOUT_TICKS);
    
    #define STOPS_EVERY_22_TO_44_SECS 1 // if TIMEOUT_TICKS==0, 2exp32=4294967296, 
                                        // every about 22 to 44 secs stopped

    tmr_1 :> time_ticks_1;
    
    time_ticks_2        = time_ticks_1;
    time_ticks_3        = time_ticks_1;
    time_2m1_prev_ticks = 0;
    time_3m1_prev_ticks = 0;

    while (1) {
        select {
            case tmr_1 when timerafter (time_ticks_1) :> time_start_ticks_1 : { // No skew here

                // Do something here. Let's assume it takes 1.5 us

                // Swap LED, observe on oscilloscope
                leds xor_eq BOARD_LED_MASK_GREEN_ONLY; // J1.7 CH1 XCORE-200-EXPLORER
                outP4_leds <: leds; // Not seen half of the time if 
                                       // STOPS_EVERY_22_TO_44_SECS (and TIMEOUT_TICKS==0)
                    
                tmr_1 :> time_stop_ticks_1;
                
                { // timerafter always has to be "the future", that value must NEVER lag behind!
                    const time32_t time_used_ticks_1 = time_stop_ticks_1 - time_start_ticks_1;
                    if (time_used_ticks_1 >= TIMEOUT_TICKS) {
                        // TIMEOUT_TICKS == 0 typically here
                        #if (STOPS_EVERY_22_TO_44_SECS == 1)
                            // Meaningless, just adding zero, ie. time "stops" 
                            // during 22-44 secs, then starts again
                            time_ticks_1 += TIMEOUT_TICKS; 
                        #else
                            tmr_1 :> time_ticks_1; // This seems to take "no time"
                        #endif
                    } else {
                        // TIMEOUT_TICKS >= 2us typically here
                        // TIMEOUT_TICKS == 0   never arrives here
                        time_ticks_1 += TIMEOUT_TICKS; // No skew 
                    }
                }                
            } break;
            
            #if (TIMEOUT_TICKS != 0) // trigger all three if 0 is meaningless
            case tmr_2 when timerafter (time_ticks_2) :> void : {  
                // No skew if round trip is larger than code in between while(1), ..
                // .. but will.. STOPS_EVERY_22_TO_44_SECS
                const time32_t time_2m1_ticks = time_ticks_2 - time_ticks_1;
                debug_print ("  T2=%8x T1=%8x T2-T1=%8x (%d)\n", 
                        time_ticks_2, time_ticks_1, time_2m1_ticks, 
                        time_2m1_prev_ticks-time_2m1_ticks);
                time_ticks_2 += TIMEOUT_TICKS;
                time_2m1_prev_ticks = time_2m1_ticks;
            } break;

            case tmr_3 when timerafter (time_ticks_3) :> time_ticks_3 : { // Will add skew
                // Will RUN_FOREVER
                const time32_t time_3m1_ticks = time_ticks_3 - time_ticks_1;
                debug_print ("  T3=%8x T1=%8x T3-T1=%8x (%d)\n", 
                        time_ticks_3, time_ticks_1, time_3m1_ticks, 
                        time_3m1_prev_ticks-time_3m1_ticks);
                time_ticks_3 += TIMEOUT_TICKS;
                time_3m1_prev_ticks = time_3m1_ticks;
            } break;
            #endif
        }
    }
}

The log looks like this where one would see that the skew is in fact visible.

=== TIMEOUT_TICKS 1 sec
T1,T2,T3 TIMEOUT_TICKS 100000000 (1s)
  T3=254740E T1=25473EA T3-T1=24 (-36)
  T2=25473EA T1=25473EA T2-T1=0 (0)
  T2=84A54EA T1=84A54EA T2-T1=0 (0)
  T3=84A6613 T1=E4035EA T3-T1=FA0A3029 (99995643)
  T2=E4035EA T1=E4035EA T2-T1=0 (0)
  T3=E4048B4 T1=143616EA T3-T1=FA0A31CA (-417)
  T2=143616EA T1=143616EA T2-T1=0 (0)
  T3=14362AC6 T1=1A2BF7EA T3-T1=FA0A32DC (-274)
  T2=1A2BF7EA T1=1A2BF7EA T2-T1=0 (0)
  T3=1A2C0C0F T1=2021D8EA T3-T1=FA0A3325 (-73)
  T2=2021D8EA T1=2021D8EA T2-T1=0 (0)
  T3=2021EE2A T1=2617B9EA T3-T1=FA0A3440 (-283)
  T2=2617B9EA T1=2617B9EA T2-T1=0 (0)
  T3=2617CFE8 T1=2C0D9AEA T3-T1=FA0A34FE (-190)
  T2=2C0D9AEA T1=2C0D9AEA T2-T1=0 (0)
  T3=2C0DB0EB T1=32037BEA T3-T1=FA0A3501 (-3)
  T2=32037BEA T1=32037BEA T2-T1=0 (0)
  T3=320391EE T1=37F95CEA T3-T1=FA0A3504 (-3)
  T2=37F95CEA T1=37F95CEA T2-T1=0 (0)
  T3=37F972F1 T1=3DEF3DEA T3-T1=FA0A3507 (-3)
  T2=3DEF3DEA T1=3DEF3DEA T2-T1=0 (0)
  T3=3DEF53F4 T1=43E51EEA T3-T1=FA0A350A (-3)
  T2=43E51EEA T1=43E51EEA T2-T1=0 (0)
 
=== TIMEOUT_TICKS 100 ms
T1,T2,T3 TIMEOUT_TICKS 10000000 (100 ms)
  T3=25AFD8C T1=25AFD68 T3-T1=24 (-36)
  T2=25AFD68 T1=25AFD68 T2-T1=0 (0)
  T2=2F393E8 T1=2F393E8 T2-T1=0 (0)
  T3=2F3A502 T1=38C2A68 T3-T1=FF677A9A (9995658)
  T2=38C2A68 T1=38C2A68 T2-T1=0 (0)
  T3=38C3D23 T1=424C0E8 T3-T1=FF677C3B (-417)
  T2=424C0E8 T1=424C0E8 T2-T1=0 (0)
  T3=424D461 T1=4BD5768 T3-T1=FF677CF9 (-190)
  T2=4BD5768 T1=4BD5768 T2-T1=0 (0)
  T3=4BD6B2A T1=555EDE8 T3-T1=FF677D42 (-73)
  T2=555EDE8 T1=555EDE8 T2-T1=0 (0)
  T3=55602C5 T1=5EE8468 T3-T1=FF677E5D (-283)
  T2=5EE8468 T1=5EE8468 T2-T1=0 (0)
  T3=5EE9A02 T1=6871AE8 T3-T1=FF677F1A (-189)
  T2=6871AE8 T1=6871AE8 T2-T1=0 (0)
  T3=6873085 T1=71FB168 T3-T1=FF677F1D (-3)
  T2=71FB168 T1=71FB168 T2-T1=0 (0)
  T3=71FC708 T1=7B847E8 T3-T1=FF677F20 (-3)
  T2=7B847E8 T1=7B847E8 T2-T1=0 (0)
  T3=7B85D8B T1=850DE68 T3-T1=FF677F23 (-3)
  T2=850DE68 T1=850DE68 T2-T1=0 (0)
  T3=850F40E T1=8E974E8 T3-T1=FF677F26 (-3)
  T2=8E974E8 T1=8E974E8 T2-T1=0 (0)
  T3=8E98A91 T1=9820B68 T3-T1=FF677F29 (-3)
  T2=9820B68 T1=9820B68 T2-T1=0 (0)
  T3=9822114 T1=A1AA1E8 T3-T1=FF677F2C (-3)

=== TIMEOUT_TICKS 0 
Using oscilloscope to see LED output

delay_ticks instead of timerafter

Same project as refererred to above. If something really needs to run at zero delay, ie. as fast as possible, it’s even in that case faster to use a select case timerafter than a delay_ticks in-line, like this. First the general delay function, that allows ZERO timout:

typedef struct time_admin_t {
    timer    tmr;
    time32_t time_ticks;
    time32_t delay_ticks; // May become negative during calculations
} time_admin_t;

void delay_no_skew (
         time_admin_t   &time_admin, 
         const unsigned round_trip_ticks) { // round_trip_ticks >= 0
    
    time32_t now_ticks;
    time32_t used_ticks;
    
    time_admin.tmr :> now_ticks;
    
    used_ticks             = (now_ticks - time_admin.time_ticks) - time_admin.delay_ticks; 
    time_admin.delay_ticks = round_trip_ticks - used_ticks; // First this.. 

    // Delay for as much as is left until next time stamp. 
    // Negative delay means we're here faster than round_trip_ticks,
    // which means we must do the best, which is delay by zero. Time always increases!
    //
    time_admin.delay_ticks = max (time_admin.delay_ticks, 0); //..then this 
    
    delay_ticks (time_admin.delay_ticks); // Don't include delay_ticks in calc of next used_ticks

    time_admin.time_ticks = now_ticks;
}

Then the code where this was used. I haven’t removed the cluttering code around, but you see how delay_no_skew is called:

void Client_node_3_composite_par_root__ ( // With DELTA_TIME_TICKS=0 round trip about 1.32 us
        const unsigned      iof_row,
        const unsigned      iof_col,
        chanend             chan_0,
        chanend             chan_1,
        chanend             chan_2,
        out buffered port:4 outP4_leds) {

    node_context_t context;
    time_admin_t time_admin; // For delay_no_skew

    Init (iof_row, iof_col, context);
   
    unsigned leds = BOARD_LEDS_INIT;

    outP4_leds <: leds;

    DEBUG_PRINT_BANNER_ROOT (context, CLIENT_ROOT_STR, "Client_node_3_composite_par_root");
    
    XSCOPE_INT (VALUE, XSCOPE_SCALE_MAX);
    XSCOPE_INT (VALUE, XSCOPE_SCALE_MIN);

    time_admin.tmr :> time_admin.time_ticks;
    time_admin.delay_ticks = 0;

    while (1) {
        par
        {
            { // subtask 1
                chan_0 <: context.value;
                chan_0 :> context.values[0];
            }
            {  // subtask 2
                chan_1 <: context.value;
                chan_1 :> context.values[1];
            }
            {  // subtask 3
                chan_2 <: context.value;
                chan_2 :> context.values[2];
            }
        }

        Node_Calculate (context);

        DEBUG_PRINT_VALUES_WITH_TIME (context, CLIENT_ROOT_STR, time_admin.tmr);

        // Swap LED
        leds xor_eq BOARD_LED_MASK_GREEN_ONLY; // J1.7 CH1 XCORE-200-EXPLORER
        outP4_leds <: leds;

        XSCOPE_INT (VALUE, leds ? XSCOPE_VALUE_TRUE : XSCOPE_VALUE_FALSE); 

        // timerafter always has to be "the future", that value must NEVER lag behind!
        delay_no_skew (time_admin, DELTA_TIME_TICKS);
    }
}

inline timerafter without select case

Observe that this is possible! But the same problem of having control of the timerafter value applies (it has to be in the future), as discussed in the above chapters. Here is an example from the XMOS lib_i2c code:

// In file i2c_master_single_port.xc, function i2c_master_single_port

if (ack == 0) {
  for (int j = 0; j < m; j++){
    unsigned char data = 0;
    timer tmr;
    for (int i = 8; i != 0; i--) {
      int temp = high_pulse_sample(p_i2c, bit_time,
                                   SCL_HIGH, SDA_HIGH, other_bits_mask,
                                   fall_time);
      data = (data << 1) | temp;
    }
    buf[j] = data;

    tmr when timerafter(fall_time + bit_time/4) :> void;
    // ACK after every read byte until the final byte then NACK.
    if (j == m-1)
      p_i2c <: SDA_HIGH | SCL_HIGH | other_bits_mask;
    else {
      p_i2c <: SCL_HIGH | other_bits_mask;
    }
    // High pulse but make sure SDA is not driving before lowering
    // scl
    tmr when timerafter(fall_time + bit_time/2 + bit_time/32) :> void;
    wait_for_clock_high(p_i2c, SCL_HIGH, fall_time, bit_time + 1);
    p_i2c <: SDA_HIGH | other_bits_mask;
    fall_time = fall_time + bit_time;
  }
}

 

[[combinable]] on same core timing

This is a follow up from the above chapter. I thought, what happens if I start two instances of the above test_task and let them run by half a second out of phase?

Some of what I saw was so new to me that I decided to post this as [[combinable]] on same core timing on XCore Exchange. I have some ponderings there that I hope somebody will respond to.

xC may calculate interrupt latency even without interrupt functions

Observe that this is not a fault in the xC language! Observe that there is nothing wrong with the fact that the now time is not the same as the time that it waited until. This is by xC language’s design, and since these time values are not equal because timing-out and scheduling will take some time (and they may differ by quite a lot), it’s some times crucial to get that value. I did find an example in the XMOS lib_i2c library, file i2c_master_async.xc. In the explanation of adjust_for_slip we read ..”However, if the task falls behind due to other processing on the core then this function will adjust both the next event time and the fall time reference to slip so that subsequent timings are correct.” They know both the event_time (a name that I stole from that example) and now, so it’s possible to do something by knowing the slip. It’s about as easy as this: if we don’t control a pin with some timed port statement, then, when we should have changed a pin at event_time but are not allowed before now, then we are still able to do something in the code to compensate. I assume that just reading the system timer again would not have been as accurate as the timerafter‘s output to now by language design.

One should be aware of this. Tuning by placing tasks alone on a core or share a core with other tasks with [[combinable]] would yield different results. I have made a positive point of the fact that xC doesn’t have interrupt functions (like occam didn’t have them either) in a lecture at NTNU in 2018 (Thinking about it: Channels more than connect threads. They protect them) – with the view that interrupts are difficult and it’s hard to find worst case timing. They are prioritised and nested, and you need to save and restore registers that you’d use in your interrupt. An interrupt event is what’s connected on the sender side of a chan or connected to changes of a port (pin(s) or its local timeouts), or timer timeouts for that matter (as here). This scheme will have much faster response (faster is always difficult to defend, so I’d say cleaner instead) – available out of the box in the xC universe. The lost time shown here is a close as xC comes to ant “interrupt latency”. The two ticks you lose in some examples here are 2 * 10 ns = 20 ns. Give me any other processor where this is possible, with this elegant a scheme! One logical core is possible per “interrupt”, and they would have guaranteed max latency!

But still I have those interesting values here…:

combinable on same core timing code

#include <platform.h> // core
#include <stdio.h>
#include <timer.h> // delay_milliseconds(200), XS1_TIMER_HZ etc

#define DEBUG_PRINT_TEST 1
#define debug_print(fmt, ...) do { if(DEBUG_PRINT_TEST) printf(fmt, __VA_ARGS__); } while (0)

typedef signed int time32_t;

#define ONE_SECOND_TICKS (1000 * XS1_TIMER_KHZ)

#define TEST_TIMER_VALUES_SKEW_PLACED 2

#if (TEST_TIMER_VALUES_SKEW_PLACED==0)
    #define INFO "UNPLACED"
    #define COMBINABLE
    #define PLACED_0
    #define PLACED_1
#elif (TEST_TIMER_VALUES_SKEW_PLACED==1)
    #define INFO "PLACED"
    #define COMBINABLE [[combinable]]
    #define PLACED_0 on tile[0].core[0]:
    #define PLACED_1 on tile[0].core[1]:
#elif (TEST_TIMER_VALUES_SKEW_PLACED==2)
    #define INFO "PLACED_SAMECORE"
    #define COMBINABLE [[combinable]]
    #define PLACED_0 on tile[0].core[0]:
    #define PLACED_1 on tile[0].core[0]:
#else
    #error
#endif

COMBINABLE // or not!
void test_task (const unsigned task_id, const unsigned initial_delay_ticks) {

    timer     tmr;
    time32_t  event_time; // Name from XMOS lib_i2c, both time stamps are used
    debug_print ("%s from #%u\n", INFO, task_id);

    tmr :> event_time; // Showing something else than delay_milliseconds(ms) etc:
    select {case tmr when timerafter (event_time + initial_delay_ticks) :> void: break;}

    // ONLY IN BOTTOM LOG: tmr :> event_time; // Reset time is CRUCIAL!

    while (1) {
        select {
            case tmr when timerafter (event_time) :> time32_t now: {
                debug_print ("Event_time %08X :> now %08X, #%u lost %d ticks\n", event_time, now, task_id, now-event_time);
                now += ONE_SECOND_TICKS; // FUTURE TIMEOUT, same value as "time" above
            } break;
        }
    }
}

int main () {
    par {
        PLACED_0 /* or not! */ test_task (0, 0); // No initial delay
        PLACED_1 /* or not! */ test_task (1, ONE_SECOND_TICKS/2);
    }
    return 0;
}

Observe that a tick is 10 ns. So, we’d mostly lose 20 ns. So an error of 1 ms would creep up after 50000 counts here, 13.88.. hours if I’ve got the arithmetics right:

UNPLACED from #1
UNPLACED from #0
Event_time 02491CC5 :> now 02491CD9, #0 lost 20 ticks
Event_time 02491687 :> now 05440715, #1 lost 50000014 ticks
Event_time 083EF787 :> now 083EF789, #1 lost 2 ticks
Event_time 083EFDC5 :> now 083EFDC7, #0 lost 2 ticks
Event_time 0E34D887 :> now 0E34D889, #1 lost 2 ticks
Event_time 0E34DEC5 :> now 0E34DEC7, #0 lost 2 ticks

PLACED from #1
PLACED from #0
Event_time 02472CC3 :> now 02472CD6, #0 lost 19 ticks
Event_time 024726B0 :> now 0542173E, #1 lost 50000014 ticks
Event_time 083D07B0 :> now 083D07B2, #1 lost 2 ticks
Event_time 083D0DC3 :> now 083D0DC5, #0 lost 2 ticks
Event_time 0E32E8B0 :> now 0E32E8B2, #1 lost 2 ticks
Event_time 0E32EEC3 :> now 0E32EEC5, #0 lost 2 ticks
Event_time 1428C9B0 :> now 1428C9B2, #1 lost 2 ticks

Here is there is an in-swing with 2089 and then constant 1072 ricks lost. This also happens when the timer overflows after 42 seconds. Could this be some “intereference” between the schedulings? Observe that the two tasks share a joined, common select since they run on the same code:

PLACED_SAMECORE from #0
PLACED_SAMECORE from #1
Event_time 02405B6B :> now 053B539D, #0 lost 50001970 ticks
Event_time 024062CA :> now 053B666A, #1 lost 50004896 ticks
Event_time 08363C6B :> now 08363C75, #0 lost 10 ticks
Event_time 083643CA :> now 08364BF3, #1 lost 2089 ticks
Event_time 0E2C1D6B :> now 0E2C1D75, #0 lost 10 ticks
Event_time 0E2C24CA :> now 0E2C2CF3, #1 lost 2089 ticks
Event_time 1421FE6B :> now 1421FE75, #0 lost 10 ticks
Event_time 142205CA :> now 14220D7E, #1 lost 1972 ticks
Event_time 1A17DF6B :> now 1A17DF75, #0 lost 10 ticks
Event_time 1A17E6CA :> now 1A17EE7E, #1 lost 1972 ticks
Event_time 200DC06B :> now 200DC075, #0 lost 10 ticks
Event_time 200DC7CA :> now 200DCF7E, #1 lost 1972 ticks
..
Event_time FC97456B :> now FC974575, #0 lost 10 ticks
Event_time FC974CCA :> now FC97547E, #1 lost 1972 ticks
Event_time 028D266B :> now 028D2675, #0 lost 10 ticks
Event_time 028D2DCA :> now 028D35F3, #1 lost 2089 ticks
Event_time 0883076B :> now 08830775, #0 lost 10 ticks
Event_time 08830ECA :> now 088316F3, #1 lost 2089 ticks
Event_time 0E78E86B :> now 0E78E875, #0 lost 10 ticks
Event_time 0E78EFCA :> now 0E78F7F3, #1 lost 2089 ticks
Event_time 146EC96B :> now 146EC975, #0 lost 10 ticks
Event_time 146ED0CA :> now 146ED87E, #1 lost 1972 ticks

See what happens when I reset time tmr :> time;  just before the while loop. There is no swinging in:

PLACED_SAMECORE from #0
PLACED_SAMECORE from #1
Event_time 02AE25CB :> now 05A91DF6, #0 lost 50001963 ticks
Event_time 05A91DA5 :> now 05A930C3, #1 lost 4894 ticks
Event_time 08A406CB :> now 08A406D5, #0 lost 10 ticks
Event_time 0B9EFEA5 :> now 0B9EFEAF, #1 lost 10 ticks
Event_time 0E99E7CB :> now 0E99E7D5, #0 lost 10 ticks
Event_time 1194DFA5 :> now 1194DFAF, #1 lost 10 ticks
...
Event_time FA0A26A5 :> now FA0A26AF, #1 lost 10 ticks
Event_time FD050FCB :> now FD050FD5, #0 lost 10 ticks
Event_time 000007A5 :> now 000007AF, #1 lost 10 ticks
Event_time 02FAF0CB :> now 02FAF0D5, #0 lost 10 ticks
Event_time 05F5E8A5 :> now 05F5E8AF, #1 lost 10 ticks
Event_time 08F0D1CB :> now 08F0D1D5, #0 lost 10 ticks

Client-server and call-based notification, placement and chanend numbers

Also see

  • XCore: Number of chanends on different boards? (in note 098) There are 32 chanends per tile. This is not a library of chanends, it’s hw architecture and it’s in the instruction set; the number cannot be extended. Some XMOS processors have two tiles and 64 chanends (an overview of them is XMOS series of processors (also in note 098)). A standard chan takes two chanends. An interface also uses chanends, but the answer is 1 or 2 or 3! See below
  • XCore: Calculating number of chanends (in note 098) It’s difficult and the algorithm for sharing, communicating and synchronising between tasks that XMOS use probably is one of their competitive factors. Of course, this is very closely related to the intricacies of the architecture of the XCORE processors. So we just have to go by examples. See below
  • Using a chanend in both directions (in note 141) The code example there save you one chanend. But you must do the bookkeeping yourself!
  • The algorithm is complicated by the fact that “If a distributed task is connected to several tasks, they cannot safely change its state concurrently. In this case the compiler implicitly uses a lock to protect the state of the task.” (Note 141 [1]). I should have no examples of locks being used here, but I wouldn’t know for sure

Summary table

RolesInterfaceSemanticsPlacement#timers#chanends
client/serverstart call
[notification]
[[clears_notificaltion]]
server non-blocking
notifies client when ready
Auto (none)34
N clients/server--"--
All clients are fair!
--"----"--4
(1 less on xCORE-200)
1 added per client
7
3 added per client
call-basedcall with return paramsserver call blocks
until it returns
Auto (none)33
call-based--"----"--Same core21
call-based--"----"--Different cores33

The table above shows a summary of the resource usage of the code in the folds below.

A comment about client-server non-blocking and call-based blocking roles

I go through two different kind of architectures here

Client-server non-blocking roles

Observe that a system built with tasks that relate to each other as client-server only is also deadlock free.

The first architecture a type where the client(s) initiate data collection with start_collect_data and this call will not block, ie. it will not wait until data has been produced. In my case producing data is just and increment of data (with data++), which the server does in due time (in this case: immediately). When data has been produced the server sends a data_ready [[notification]] (no data) to the client, who waits in a select and has been allowed to do other things in the meantime. The client then picks up the data with with the get_data, which [[clears_notification]] for the atomic protection that the compiler builds in that period.

No select on state only

Observe the error message that xC gives us if we try with a state only select component: “error: input from a variable that is neither a channel, port nor timer” (error messages are listed in xC is C plus x). In other words: we cannot have a state only in a select case. I tried have tried this several times, and it never works: case (send_handle) :> void. I miss this, it’s in CSPm ((ready == is_ready) & SKIP, see FDR2 notes) and Promela (:: (A == true), see Promela).

It would have been nice to have. But then..

..I assume that xC is built like this very much on purpose. When all selects are events then there’s a different algorithm than if the scheduler would  have to go up another round in the while to see if there is a state only to satisfy, even recursively. I guess this would require advanced state analysis like the formal verification tools mentioned above do, and which an executable language typically wont’ do. And timing analysis would be difficult. This is the reason that I had to use timers in my examples. They look malplaced, but in real life it doesn’t make too much of a difference. Excepts it does eat a timer, a scarce resource!

Aside: Promela has a concept of runnable models, but that’s because of how it’s built: an executable model would really execute until it’s deadlocked or livelocked or whatever. It’s easy to think of it as code. I think we’re supposed to think of it as code. Besides, the SPIN tool builds a new tool (as new C code that’s compiled to a binary) that is the model verifier. On the other side, in CSPm and FDR4 (see here), there is the model only. No executable thinking with the model. FDR4 simply verifies the model. No execution of the model. But xC is am executable language. I guess that makes much of the difference.

Call-based blocking roles

This is like a Remote procedure call (RPC, see Remote procedure call), where data is returned from the server on the single call there is. If the server now needs a long time to fetct the data then the client should be ok about that, per design! It the client uses all its fecthed data every 1000 ms an and it would block 500 of those ms then it’s no problem! Per design any max response time by this client (if it’s a server for other task) is then allowed to be max 500 ms. If not acceptable, then use the client-server model. I have discussed blocking to a larger extent at Not so blocking after all.

Client-server interface

Client-server interface code

Observe that DEBUG_PRINT_TEST 1 or 0 does not change the number of cores, timers and chanends.

See http://www.xcore.com/viewtopic.php?f=26&t=6729

#include <platform.h> // core
#include <stdio.h>
#include <timer.h> // delay_milliseconds(200), XS1_TIMER_HZ etc

#define DEBUG_PRINT_TEST 1
#define debug_print(fmt, ...) do { if(DEBUG_PRINT_TEST) printf(fmt, __VA_ARGS__); } while (0)

typedef enum {false,true} bool;
typedef signed int time32_t;
typedef unsigned data_t;

typedef interface notify_if_t {
    void start_collect_data (void);
    [[notification]] slave void data_ready (void);
    [[clears_notification]] data_t get_data (void);
} notify_if_t; 

[[combinable]]
void server_task (server notify_if_t i_notify) {
    timer     tmr;
    time32_t  time;
    bool      collectData = false;
    data_t    data = 0;

    tmr :> time;

    while (1) {
        select {
            case i_notify.start_collect_data (void) : {
                collectData = true;
                debug_print ("\n%s\n", "start");
                tmr :> time; // immediately
            } break;
            case (collectData == true) => tmr when timerafter (time) :> void : {
                collectData = false;
                data++; // This is supposed to take a while, that's why we used notification: to get it decoupled
                debug_print ("inc %u\n", data);
                i_notify.data_ready();
            } break;
            case i_notify.get_data (void) -> data_t return_Data : {
                debug_print ("sent %u\n", data);
                return_Data = data;
            } break;
        }
    }
}
[[combinable]]
void client_task (client notify_if_t i_notify) {
    timer     tmr;
    time32_t  time;
    bool      expect_notification = false;

    tmr :> time; // immediately

    while (1) {
        select {
            case (expect_notification == false) => tmr when timerafter (time) :> void : {
                i_notify.start_collect_data();
                expect_notification = true;
            } break;
            case (expect_notification == true) => i_notify.data_ready() : {
                data_t data = i_notify.get_data();
                debug_print ("got %u\n", data);
                expect_notification = false;
                time += XS1_TIMER_HZ; // 1 second
            } break;
        }
    }
}

int main() {
    /*
    Constraint check for tile[0]:
      Cores available:            8,   used:          2 .  OKAY
      Timers available:          10,   used:          3 .  OKAY
      Chanends available:        32,   used:          4 .  OKAY
      Memory available:       65536,   used:      10092 .  OKAY (4052 if DEBUG_PRINT_TEST 0)
        (Stack: 1080, Code: 8278, Data: 734)
    Constraints checks PASSED.
    */
    notify_if_t i_notify;
    par {
        server_task(i_notify); client_task(i_notify);
    }
    return 0;
}

N clients client-server interface code

You can set the number of clients. There is one timer and three chanends added per client. Plus, the clients are treated fair (open all folds in this note and search for “fair”). Also observe that the the XCORE-200 platform there is one less chanend. I assume this has to do with some added instruction or another use of lock instructions (doubtful). I have queried about this at XCore Exhange, see Num timers for startKIT and eXplorerKIT (27May2018). (The code also displays an issue when DO_PLACED == 1. I have reported this to XMOS (27May2018, Ticket 31198). The response is discussed in xC is C plus x [Replicated par and placements].)

#include <platform.h> // core
#include <stdio.h>
#include <timer.h> // delay_milliseconds(200), XS1_TIMER_HZ etc

#define DEBUG_PRINT_TEST 1
#define debug_print(fmt, ...) do { if(DEBUG_PRINT_TEST) printf(fmt, __VA_ARGS__); } while (0)

typedef enum {false,true} bool;
typedef signed int time32_t;
typedef unsigned data_t;

typedef interface notify_if_t {
    [[guarded]] void start_collect_data (void);
    [[notification]] slave void data_ready (void);
    [[clears_notification]] data_t get_data (void);
} notify_if_t;

#define NUM_CLIENTS 2 // DO_PLACED 0: MAX 7. 1 timer and 3 chanends per client

[[combinable]]
void server_task (server notify_if_t i_notify[NUM_CLIENTS]) {
    timer     tmr;
    time32_t  time;
    bool      collectData = false;
    bool      session = false; // Necessary!
    data_t    data = 0;
    int       session_index_of_client;

    debug_print ("%s\n", "server_task");
    tmr :> time; // immediately

    while (1) {
        select {
            case (session == false) => i_notify[int index_of_client].start_collect_data (void) : {
                collectData = true;
                session = true;
                debug_print ("%d started\n", index_of_client);
                session_index_of_client = index_of_client;
                tmr :> time; // immediately
            } break;
            case (collectData == true) => tmr when timerafter (time) :> void : {
                collectData = false;
                data++; // This is supposed to take a while, that's why we used notification: to get it decoupled
                debug_print ("%d produced %u\n", session_index_of_client, data);
                i_notify[session_index_of_client].data_ready();
            } break;
            case i_notify[int index_of_client].get_data (void) -> data_t return_Data : {
                debug_print ("%d sent %u\n", index_of_client, data);
                session = false;
                return_Data = data;
            } break;
        }
    }
}
[[combinable]]
void client_task (client notify_if_t i_notify, const int index_of_client) {
    timer     tmr;
    time32_t  time;
    bool      expect_notification = false;
    unsigned  cnt = 0;
    debug_print ("client_task %d\n", index_of_client);

    tmr :> time; // immediately

    while (1) {
        select {
            case (expect_notification == false) => tmr when timerafter (time) :> void : {
                debug_print ("\n%d START\n", index_of_client);
                i_notify.start_collect_data();
                expect_notification = true;
            } break;
            case (expect_notification == true) => i_notify.data_ready() : {
                data_t data = i_notify.get_data();
                cnt++;
                debug_print ("%d GOT %u is #%u\n", index_of_client, data, cnt);
                expect_notification = false;
                time += XS1_TIMER_HZ; // 1 second
            } break;
        }
    }
}

int main () {
    #define DO_PLACED 0 // 1 simply does not work!

    notify_if_t i_notify[NUM_CLIENTS];
    #if (DO_PLACED == 0)
        /*
        STARTKIT:
        NUM_CLIENTS 1 Cores 2  Timers 3  Chanends  4  OKAY.
        NUM_CLIENTS 2 Cores 3  Timers 4  Chanends  7  OKAY.
        NUM_CLIENTS 3 Cores 4  Timers 5  Chanends 10  OKAY.
        NUM_CLIENTS 4 Cores 5  Timers 6  Chanends 13  OKAY.
        NUM_CLIENTS 5 Cores 6  Timers 7  Chanends 16  OKAY.
        NUM_CLIENTS 6 Cores 7  Timers 8  Chanends 19  OKAY.
        NUM_CLIENTS 7 Cores 8  Timers 9  Chanends 22  OKAY.

        XCORE-200-EXPLORER has one less timer (xTIMEcomposer 14.3.3):
        NUM_CLIENTS 1 Cores 2  Timers 2  Chanends  4  OKAY.
        NUM_CLIENTS 2 Cores 3  Timers 3  Chanends  7  OKAY.
        NUM_CLIENTS 3 Cores 4  Timers 4  Chanends 10  OKAY.
        NUM_CLIENTS 4 Cores 5  Timers 5  Chanends 13  OKAY.
        NUM_CLIENTS 5 Cores 6  Timers 6  Chanends 16  OKAY.
        NUM_CLIENTS 6 Cores 7  Timers 7  Chanends 19  OKAY.
        NUM_CLIENTS 7 Cores 8  Timers 8  Chanends 22  OKAY.
        */
        par {
            server_task(i_notify);
            par (size_t i = 0; i < NUM_CLIENTS; i++) {
                client_task (i_notify[i], i);
            }
        }
    #elif (DO_PLACED == 1)
        /*
        NUM_CLIENTS 1 Cores 1  Timers 2  Chanends 2  OKAY.  Constraints checks PASSED. BUT DOES NOT RUN!
        NUM_CLIENTS 2 Cores 1+ Timers 2+ Chanends 3+ MAYBE. PASSED WITH CAVEATS.       DOES NOT RUN
        NUM_CLIENTS 3 Cores 1+ Timers 2+ Chanends 4+ MAYBE. PASSED WITH CAVEATS.       DOES NOT RUN
        Constraint check for tile[0]:
          Cores available:            8,   used:          1+.  MAYBE
          Timers available:          10,   used:          2+.  MAYBE
          Chanends available:        32,   used:          4+.  MAYBE
          Memory available:       65536,   used:      12460+.  MAYBE
            (Stack: 1112+, Code: 10380, Data: 968)
        Constraints checks PASSED WITH CAVEATS.
        */
        par {
            on tile[0].core[0]: server_task(i_notify);
            par (size_t i = 0; i < NUM_CLIENTS; i++) {
                on tile[0].core[0]: client_task (i_notify[i], i);
            }
        }
    #elif ((DO_PLACED == 2) && (NUM_CLIENTS == 7))
        // XMOS code during Ticket 31198
        /*
        STARTKIT:
        NUM_CLIENTS 7 Cores 8  Timers 9  Chanends 22  OKAY.

        XCORE-200-EXPLORER has one less timer (xTIMEcomposer 14.3.3):
        NUM_CLIENTS 7 Cores 8  Timers 8  Chanends 22  OKAY.
        */
        par {
            on tile[0].core[0]: server_task(i_notify);
            // Using the par replicator index for core placement is unimplemented (14.3.3)
            on tile[0].core[1]: client_task (i_notify[0], 0);
            on tile[0].core[2]: client_task (i_notify[1], 1);
            on tile[0].core[3]: client_task (i_notify[2], 2);
            on tile[0].core[4]: client_task (i_notify[3], 3);
            on tile[0].core[5]: client_task (i_notify[4], 4);
            on tile[0].core[6]: client_task (i_notify[5], 5);
            on tile[0].core[7]: client_task (i_notify[6], 6);
        }
    #endif
    return 0;
}

N clients client-server interface log

This is the log with NUM_CLIENTS 7 and DO_PLACED 0. You see how 6 starts queuing up, then 0-5 before 6 is taken by the first “6 started”. Rather interesting:

6 START

0 START

1 START

2 START

3 START

4 START

5 START
6 started
6 produced 358
6 sent 358
6 GOT 358 is #52
0 started
0 produced 359
0 sent 359
0 GOT 359 is #52
1 started
1 produced 360
1 sent 360
1 GOT 360 is #52
2 started
2 produced 361
2 sent 361
2 GOT 361 is #52
3 started
3 produced 362
3 sent 362
3 GOT 362 is #52
4 started
4 produced 363
4 sent 363
4 GOT 363 is #52
5 started
5 produced 364
5 sent 364
5 GOT 364 is #52

Call-based interface

Call-based interface code

Observe that DEBUG_PRINT_TEST 1 or 0 does not change the number of cores, timers and chanends.

#include <platform.h> // core
#include <stdio.h>
#include <timer.h> // delay_milliseconds(200), XS1_TIMER_HZ etc

#define DEBUG_PRINT_TEST 1
#define debug_print(fmt, ...) do { if(DEBUG_PRINT_TEST) printf(fmt, __VA_ARGS__); } while (0)

typedef enum {false,true} bool;
typedef signed int time32_t;
typedef unsigned data_t;

typedef interface call_if_t {
    data_t collect_and_get_data (void);
} call_if_t;

[[combinable]]
void server_task (server call_if_t i_call) {
    data_t data = 0;

    while (1) {
        select {
            case i_call.collect_and_get_data (void) -> data_t return_Data : {
                data++; // This should take as little time as possible since it blocks
                return_Data = data;
                debug_print ("sent %u\n", data);
            } break;
        }
    }
}
[[combinable]]
void client_task (client call_if_t i_call) {
    timer     tmr;
    time32_t  time;

    tmr :> time; // immediately

    while (1) {
        select {
            case tmr when timerafter (time) :> void : {
                debug_print ("%s", "\nasked\n");
                data_t data = i_call.collect_and_get_data();
                debug_print ("got %u\n", data);
                time += XS1_TIMER_HZ; // 1 second
            } break;
        }
    }
} 
int main() {
#define DO_PLACED 2 // 1 is OPTIMAL

    call_if_t i_call;
    par {
    #if (DO_PLACED == 0)
 server_task(i_call); client_task(i_call);
       /* Constraint check for tile[0]:
          Cores available:            8,   used:          2 .  OKAY
          Timers available:          10,   used:          3 .  OKAY
          Chanends available:        32,   used:          3 .  OKAY
          Memory available:       65536,   used:       9816 .  OKAY (3840 if DEBUG_PRINT_TEST 0)
            (Stack: 1032, Code: 8066, Data: 718)
        Constraints checks PASSED. */
    #elif (DO_PLACED == 1)
 on tile[0].core[0]: server_task(i_call); on tile[0].core[0]: client_task(i_call);
        /* Constraint check for tile[0]:
          Cores available:            8,   used:          1 .  OKAY
          Timers available:          10,   used:          2 .  OKAY
          Chanends available:        32,   used:          1 .  OKAY
          Memory available:       65536,   used:       9760 .  OKAY (4128 if DEBUG_PRINT_TEST 0)
            (Stack: 656, Code: 8350, Data: 754)
        Constraints checks PASSED. */
    #elif (DO_PLACED == 2) 
 on tile[0].core[0]: server_task(i_call); on tile[0].core[1]: client_task(i_call);
        /* Constraint check for tile[0]:
          Cores available:            8,   used:          2 .  OKAY
          Timers available:          10,   used:          3 .  OKAY
          Chanends available:        32,   used:          3 .  OKAY
          Memory available:       65536,   used:       9952 .  OKAY (3960 if DEBUG_PRINT_TEST 0)
            (Stack: 1048, Code: 8174, Data: 730)
        Constraints checks PASSED. */
    #endif
    }
    return 0;
}

Replicated select case index not in range of input array

See Replicated select case index not in range of input array in XCore Exchange forum (30Dec2020)

Replicated select case index not in range of input array code

The code below has always given me iof_client [0..2]

conn_if_t all_conns [3]; // parameter in setup of Server1

void Server1 (
    server conn_if_t i_conns[3]); {

    while (1) {
        select {
            case i_conns[unsigned iof_client].do_temp (const unsigned val_in) -> unsigned val_return : {
                // iof_client is [0..2]
                ...

However, the below gives me iof_connection [0..23]. In other words, one index per outer interface, like an ip address so to say.

conn_if_t all_conns [8][3]; // parameter in setup of Servers8 are now all_conns[0], all_conns[1], all_conns[2], ie. I have 8 of Servers8

void Servers8 (
    server conn_if_t i_conns[3]); {
   
   unsigned my_array [3];
   
    while (1) {
        select {
            case i_conns[unsigned iof_connection].do_temp (const unsigned val_in) -> unsigned val_return : {
                // iof_client is [0..23], in groups of [0..2], [3..5], ... [21,22,23] for the 8 Servers8
                const unsigned iof_client = (iof_connection % 3); // [0..2] to avoid 'out of range' crash
                my_array[iof_client] = val_in;
                ...

Is this right? I assume it is.. But why?

But I couldn’t find any reference to it in any of the documentation..?

More on normal, combined and distributed tasks

I have several examples of this here and on the xC is C plus x note. I have also tried to explain som of this in the CPA 2018 fringe presentation. In May2019 I also will add some explanation in the lecture here.

Scheduling

The main point with this code is to show that only the normal task type will, inside a local loop, allow to be descheduled and another task to be scheduled instead. The other task types all run to completion.

You will need a board (startKIT or XCORE-200 eXplorerKIT) and make a button, or use any one that would be present on your board. Both those boards have buttons. You just need to change the XS1_PORT_1N port address. Look in your board’s manual.

#include <platform.h>
#include <stdio.h>
#include <stdint.h> // uint8_t
#include <timer.h>  // delay_milliseconds(200), XS1_TIMER_HZ

typedef signed int time32_t;

typedef enum {pressed, released} button_e; // pressed=0=low, releassed=1=high

#define DEBUG_PRINT_TEST_TOP  1
#define DEBUG_PRINT_TEST      1
#define DEBUG_PRINT_TEST_PRI  1
#define DEBUG_PRINT_TEST_SPIN 1

#if (DEBUG_PRINT_TEST_TOP == 1)
    #define debug_print_top(fmt, ...) do { if(DEBUG_PRINT_TEST_TOP==1) printf(fmt, __VA_ARGS__); } while (0)
#else
    #define debug_print_top(fmt, ...)
#endif

#if (DEBUG_PRINT_TEST == 1)
    #define debug_print(fmt, ...) do { if(DEBUG_PRINT_TEST==1) printf(fmt, __VA_ARGS__); } while (0)
#else
    #define debug_print(fmt, ...)
#endif

#if (DEBUG_PRINT_TEST_PRI == 1)
    #define debug_print_pri(fmt, ...) do { if(DEBUG_PRINT_TEST_PRI==1) printf(fmt, __VA_ARGS__); } while (0)
#else
    #define debug_print_pri(fmt, ...)
#endif

#if (DEBUG_PRINT_TEST_SPIN == 1)
    #define debug_print_spin(fmt, ...) do { if(DEBUG_PRINT_TEST_SPIN==1) printf(fmt, __VA_ARGS__); } while (0)
#else
    #define debug_print_spin(fmt, ...)
#endif

#define MAIN_PAR_POS 0 // 0: "released" is in spin dots .... since one core each
                       // 1: "released" is in never spin dots .... since [[combinable]]: RFM69_driver and IRQ_interrupt_task
                       // 2: "released" is in never spin dots .... since [[combinable]]: RFM69_driver and IRQ_interrupt_task
                       // 3: "released" is in never spin dots .... since [[combinable]]: RFM69_driver and IRQ_interrupt_task is [[distributable]]

#define SPIN 0 // 0: repeated delay_milliseconds(1) (would cause a yield I assume)
               // 1: busy-burning cycles (no yield I assume)

// == HOW I TEST ==
//
// 1. Start debug
// 2. Press button
// 3. Release it immediately or wait until I see spin .... or delay ****
// 4. Result: Only in MAIN_PAR_POS==0 I see "released" inside the spin or delay fields
//            All tasks have their own core, so IRQ_interrupt_task will be scheduled, bit no so when combinable or distributable
// 5. Deadlock? I have not seen the kind!

#if (MAIN_PAR_POS>0)
    [[combinable]]
#endif
void IRQ_interrupt_task (
        in port p_button_in,
        chanend c_irq_update_out)
{
    debug_print_pri ("%s\n", "IRQ_interrupt_task");
    button_e port_current_val = 0;
    while(1) {
        select {
            case p_button_in when pinsneq(port_current_val) :> port_current_val: {
                #if (MAIN_PAR_POS==0)
                    debug_print ("button %s", (port_current_val== pressed) ? "pressed\n" : "released");
                    // Since "released" is inside ... or *** it looks nicer without line shift
                #else
                    debug_print ("button %s\n", (port_current_val==pressed) ? "pressed" : "released");
                #endif
                c_irq_update_out <: port_current_val;
                debug_print ("%s\n", "button sent");
            } break;
        }
    }
}

typedef interface radio_if_t {
    signed get_radio_val (void);
} radio_if_t;

// Normal task always, so we call it "top"
void System_task (
        chanend c_irq_update_in,
        client  radio_if_t i_radio)
{
    debug_print_top ("\nMAIN_PAR_POS %u, debug_print_top %u, debug_print %u, debug_print_pri %u, debug_print_spin %u, spin %u\n\n",
            MAIN_PAR_POS,
            DEBUG_PRINT_TEST_TOP,
            DEBUG_PRINT_TEST,
            DEBUG_PRINT_TEST_PRI,
            DEBUG_PRINT_TEST_SPIN,
            SPIN);
    debug_print_top ("%s\n", "  System_task");

    button_e button;
    while(1) {
        select {
            case c_irq_update_in :> button: {
                if (button== pressed) {
                    signed radio_val;
                    debug_print_top ("%s\n", "  __-- pressed seen");
                    radio_val = i_radio.get_radio_val();
                    debug_print_top ("done %u ms\n", radio_val);
                } else {
                    debug_print ("%s\n", "  --__ released seen");
                }
            } break;
        }
    }
}

void Spin_ms (unsigned spin_array[], static const unsigned spin_array_len, const unsigned ms)
{
    debug_print_spin ("%s", "    ");
    timer tmr;
    time32_t time_start, time_stop;
    tmr :> time_start;
    #define STARTKIT_O2 207 // startKIT and optimalisation -O2 (no printing)
    for (unsigned outer_cnt=0; outer_cnt<ms; outer_cnt++) {
        debug_print_spin ("%s", ((outer_cnt%100)==0) ? "\n    " : ".");
        for (unsigned inner_cnt=0; inner_cnt<STARTKIT_O2; inner_cnt++) {
            for (unsigned index=0; index<spin_array_len; index++) {
                spin_array[index%spin_array_len] = index;
            }
        }
    }
    debug_print_spin ("%s", "\n");
    tmr :> time_stop;
    debug_print ("    spinned %u ms\n", (time_stop - time_start)/XS1_TIMER_KHZ);
}

#define SPIN_ARRAY_LEN 100

#if (MAIN_PAR_POS==3)
    [[distributable]]
#elif (MAIN_PAR_POS>0)
    [[combinable]]
#endif
void RFM69_driver (
        server radio_if_t i_radio)
{
    debug_print_pri ("%s\n", "    RFM69_driver");

    signed radio_val_ms = 0;

    while(1) {
        select {
            case i_radio.get_radio_val() -> signed return_radio_val : {
                debug_print      ("%s\n", "    serve");
                debug_print_spin ("%s", "    ");
                #if (SPIN==0)
                    for (unsigned ms_cnt=0; ms_cnt<radio_val_ms; ms_cnt++) {
                        delay_milliseconds(1);
                        debug_print_spin ("%s", ((ms_cnt%100)==0) ? "\n    " : "*");
                    }
                    debug_print ("\n    delayed %u ms\n", radio_val_ms);
                #elif (SPIN==1)
                    unsigned spin_array[SPIN_ARRAY_LEN];
                    Spin_ms (spin_array, SPIN_ARRAY_LEN, radio_val_ms);
                #endif
                return_radio_val = radio_val_ms;
                radio_val_ms += 100;
                if (radio_val_ms > 1500) {
                    radio_val_ms = 0;
                    debug_print ("%s", "*\n*\n*\n");
                }
                debug_print ("%s\n", "    served");
            } break;
        }
    }
}
port inP_button_left = on tile[0]: XS1_PORT_1N;
// P1N0, X0D37 B_Left on my little button board connected on the startKIT

int main(void) {
    chan c_irq_update; // of button_e
    radio_if_t i_radio;
    #if (MAIN_PAR_POS==0) // Constraints: C:8/3 T:10/4 C:32/5 M:11576 S:1920 C:8656 D:1000
        par {
            IRQ_interrupt_task (inP_button_left, c_irq_update);
            System_task        (c_irq_update, i_radio);
            RFM69_driver       (i_radio);
        }
    #elif (MAIN_PAR_POS==1) // Constraints: C:8/2 T:10/3 C:32/5 M:11520 S:1560 C:8900 D:1060
        par {
            par {
                System_task (c_irq_update, i_radio);
            }
            [[combine]]
            par {
                IRQ_interrupt_task (inP_button_left, c_irq_update);
                RFM69_driver       (i_radio);
            }
        }
    #elif (MAIN_PAR_POS==2) // Constraints: C:8/2 T:10/3 C:32/5 M:11648 S:1576 C:9008 D:1064
        par {
            par {
                on tile[0]: System_task (c_irq_update, i_radio);
            }
            on tile[0]: {
                [[combine]]
                par {
                    IRQ_interrupt_task (inP_button_left, c_irq_update);
                    RFM69_driver       (i_radio);
                }
            }
        }
    #elif (MAIN_PAR_POS==3)
        par {
            on tile[0]: System_task (c_irq_update, i_radio);
            on tile[0]: {
                [[combine]]
                par {
                    IRQ_interrupt_task (inP_button_left, c_irq_update);
                    RFM69_driver       (i_radio); // [[distributable]]
                }
            }
        }
    #else
        #error No code for MAIN_PAR_POS
    #endif
    return 0;
}

Scheduling log

This is the log of the above code compiled with MAIN_PAR_POS 0. See the MAIN_PAR_POS defined above, that’s where the explanation is.

I pressed the button and held and released it at various intervals, rather at random:

.gdbinit: No such file or directory.
connect --adapter-id 0zGcIk5WkhKBj --xscope-realtime --xscope-port localhost:10101
0x00010000 in _start ()
load
Loading section .text, size 0x154 lma 0x10000
Loading section .cp.rodata, size 0x18 lma 0x10154
Loading section .ctors, size 0x4 lma 0x1016c
Loading section .dtors, size 0x4 lma 0x10170
Loading section .dp.data, size 0x14 lma 0x10174
Loading section .dp.rodata, size 0x4 lma 0x10188
Start address 0x10000, load size 396
Transfer rate: 48 KB/sec, 66 bytes/write.
Loading section .crt, size 0xa2 lma 0x10000
Loading section .init, size 0x1a lma 0x100a2
Loading section .fini, size 0x2e lma 0x100bc
Loading section .text, size 0x211c lma 0x100ec
Loading section .eh_frame, size 0x24 lma 0x12208
Loading section .cp.rodata, size 0x192 lma 0x1222c
Loading section .cp.const4, size 0x24 lma 0x123c0
Loading section .cp.rodata.cst4, size 0x30 lma 0x123e4
Loading section .cp.rodata.string, size 0x4a lma 0x12414
Loading section .ctors, size 0xc lma 0x12460
Loading section .dtors, size 0x8 lma 0x1246c
Loading section .dp.data, size 0x70 lma 0x12474
Loading section .dp.rodata, size 0x4 lma 0x124e4
Start address 0x10000, load size 9442
Transfer rate: 124 KB/sec, 726 bytes/write.
info program
Program stopped at 0x10000.
It stopped with signal SIGTRAP, Trace/breakpoint trap.
Type "info stack" or "info registers" for more information.

MAIN_PAR_POS 0, debug_print_top 1, debug_print 1, debug_print_pri 1, debug_print_spin 1, spin 0

IRQ_interrupt_task
    RFM69_driver
  System_task
button releasedbutton sent
  --__ released seen
button pressed
button sent
  __-- pressed seen
    serve
    
    delayed 0 ms
    served
done 0 ms
button releasedbutton sent
  --__ released seen
button pressed
button sent
  __-- pressed seen
    serve
    
    *************************************************************************************button released**************
    delayed 100 ms
    served
done 100 ms
button sent
  --__ released seen
button pressed
button sent
  __-- pressed seen
    serve
    
    *******************************************button released********************************************************
    ***************************************************************************************************
    delayed 200 ms
    served
done 200 ms
button sent
  --__ released seen
button pressed
button sent
  __-- pressed seen
    serve
    
    **************************************************button released*************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    delayed 300 ms
    served
done 300 ms
button sent
  --__ released seen
button pressed
button sent
  __-- pressed seen
    serve
    
    ****************************************************************************************button released***********
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    delayed 400 ms
    served
done 400 ms
button sent
  --__ released seen
button pressed
button sent
  __-- pressed seen
    serve
    
    *********************************************************************button released******************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    delayed 500 ms
    served
done 500 ms
button sent
  --__ released seen
button pressed
button sent
  __-- pressed seen
    serve
    
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    delayed 600 ms
    served
done 600 ms
button releasedbutton sent
  --__ released seen
button pressed
button sent
  __-- pressed seen
    serve
    
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    *************************************button released**************************************************************
    ***************************************************************************************************
    delayed 700 ms
    served
done 700 ms
button sent
  --__ released seen
button pressed
button sent
  __-- pressed seen
    serve
    
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    *******************************************************************************button released********************
    ***************************************************************************************************
    ***************************************************************************************************
    delayed 800 ms
    served
done 800 ms
button sent
  --__ released seen
button pressed
button sent
  __-- pressed seen
    serve
    
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    *button released**************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    delayed 900 ms
    served
done 900 ms
button sent
  --__ released seen
button pressed
button sent
  __-- pressed seen
    serve
    
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************button released************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    delayed 1000 ms
    served
done 1000 ms
button sent
  --__ released seen
button pressed
button sent
  __-- pressed seen
    serve
    
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ****************************button released***********************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    delayed 1100 ms
    served
done 1100 ms
button sent
  --__ released seen
button pressed
button sent
  __-- pressed seen
    serve
    
    ***************************************************************************************************
    *************************************************************button released**************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    delayed 1200 ms
    served
done 1200 ms
button sent
  --__ released seen
button pressed
button sent
  __-- pressed seen
    serve
    
    **button released*************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    delayed 1300 ms
    served
done 1300 ms
button sent
  --__ released seen
button pressed
button sent
  __-- pressed seen
    serve
    
    *************************************************************************************button released**************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    delayed 1400 ms
    served
done 1400 ms
button sent
  --__ released seen
button pressed
button sent
  __-- pressed seen
    serve
    
    **************************************************************************************************button released*
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    ***************************************************************************************************
    delayed 1500 ms
*
*
*
    served
done 1500 ms
button sent
  --__ released seen

Running code examples of all task types

#include <platform.h> // core
#include <timer.h>    // delay_milliseconds(..), XS1_TIMER_HZ etc

// The simplest form of interface, no use of
// [[guarded]], [[notification]] slave, or [[clears_notification]]
// used for session type interfaces
//
typedef interface con_if_t {
    unsigned set_get (const unsigned value);
} con_if_t;

// Like occam and go tasks. One per XCore logical core.
// Expensive, so is most often a much more complex tast or one with very
// hard timing requirements
//
void my_normal_task (
        server  con_if_t    i_con,
        chanend             c_chan_in, // Used unidirectionally here
        in buffered port:32 p_pin) {

    timer    tmr; // one tick every 10 ns
    signed   time_ticks;
    unsigned my_value = 1;
    unsigned c_value_in;
    unsigned pin_val;

    p_pin :> pin_val; // read pin
    tmr :> time_ticks;
    time_ticks += XS1_TIMER_HZ;

    while (1) { // looping not required
        select {
            case tmr when timerafter (time_ticks) :> void : {
                my_value = -my_value;
            } break;
            case p_pin when pinsneq (pin_val) :> pin_val: {
                // change on pin
                my_value = 0;
            } break;
            case c_chan_in :> c_value_in : {
                my_value = c_value_in;
            } break;
            case i_con.set_get (const unsigned value_in) ->
                    unsigned value_return : {
                value_return = my_value;
                my_value = value_in;
            } break;
        }
        my_value++; // As function «calculate» for the others
                    // Shared state may be handled here
    }
}


// Instead if the common state my_value++ above,
// which is not allowed. For the same semantics,
// one call for each select case
//
unsigned calculate (const unsigned value) {
    return (value+1);
}

// Several of these may be combined in one select by the compiler
// First level of restrictions apply
//
[[combinable]]
void my_combinable_task (
        client  con_if_t i_con_0,
        client  con_if_t i_con_1,
        chanend          c_chan_out, // Used unidirectionally here
        in port          p_pin) {

    timer    tmr; // one tick every 10 ns
    signed   time_ticks;
    unsigned my_value = 1;
    unsigned pin_val;

    p_pin :> pin_val; // read pin
    tmr :> time_ticks;
    time_ticks += XS1_TIMER_HZ;

    while (1) { // looping required
        select {
            case tmr when timerafter (time_ticks) :> void : {
                my_value = -my_value;
                my_value = calculate (my_value);
                my_value = i_con_0.set_get (my_value);
                c_chan_out <: my_value;
            } break;
            case p_pin when pinsneq (pin_val) :> pin_val: {
                // change on pin
                my_value = 0;
                my_value = calculate (my_value);
                my_value = i_con_1.set_get (my_value);
            } break;
            // chanend ok, not used in example
            // Server interface ok, not used in example
        }
    }
}

// Called as inline function, on the caller's stack
// Third level of resitrictions apply
// When DISTRIBUTABLE only interfaces allowed as communication params
// No side effects with system, like timers, channels or ports
//
[[distributable]]
void my_distributable_task (server con_if_t i_con) {

    unsigned my_value = 1;

    while (1) { // looping required
        select {
            case i_con.set_get (const unsigned value_in) ->
                    unsigned value_return : {
                value_return = my_value;
                my_value = value_in;
                my_value = calculate (my_value);
            } break;
        }
    }
}


in  buffered port:32 p_pin_b = on tile[0]: XS1_PORT_1M;
in  port             p_pin   = on tile[0]: XS1_PORT_1N;

int main (void) {

    con_if_t i_con_a;
    con_if_t i_con_b;
    chan c_chan;

    par {
        my_normal_task (i_con_a, c_chan, p_pin_b);

        [[combine]]
        par {
            my_combinable_task (i_con_a, i_con_b, c_chan, p_pin);
        }

        // Observe there is no par here:
        [[distribute]]
        my_distributable_task (i_con_b);
    }

    return 0;
}

Composite task must be of normal type

To avoid “error: statement in combinable par must be a call to a combinable function” this task must be normal, ie. not [[combinable]] or [[distributable]]. I assume the reason is that a par not in main is not allowed, so that “flattening out” of the task hierachy is not done (as the SPoC Southampton Portable occam Compiler 016:[08] did. But then it only had normal tasks.) For all I know, for xC, maybe flattening would have clashed with the task types other than normal.

The code example should not really need any more context to get the point across:

// Composite pair task
// This kind of task can only be NORMAL since COMBINABLE must be built with while(1)-select
// However, as NORMAL it does not take any other resources than i_conn_internal
//
void Client_Server_pair (
        const iof_pos_t  iof_row,
        const iof_pos_t  iof_col,
        client conn_if_t i_conn_hor, // Left or right
        const unsigned   pos_x_0,
        const unsigned   pos_y_0,
        const unsigned   pos_z_0,
        server conn_if_t i_conns_server [NUM_CONNS_PER_PAIR], // DIM 2
        const unsigned   pos_x_1,
        const unsigned   pos_y_1,
        client conn_if_t i_conn_ver,
        const unsigned   pos_x_2,
        const unsigned   pos_y_2,
        const unsigned   pos_z_2) { // Above or below

    conn_if_t i_conn_internal; // DIM 1, sum DIM 3:
    par {
        Client_node_alt (
                iof_row, iof_col,
                i_conn_hor,      pos_x_0, pos_y_0, pos_z_0,
                i_conn_internal, pos_x_1, pos_y_1,
                i_conn_ver,      pos_x_2, pos_y_2, pos_z_2);
        Server_node_alt (
                iof_row, iof_col+1,
                i_conn_internal,
                i_conns_server);
    }
} // Client_Server_pair_task

Comments welcomed

A 4-bit port and array of 1-bit ports match in a non-consistent manner

I came across this when I was going to get the lib_spi to run on a startKIT. The problem is with the CS-pins that are mapped onto an array of 1-bit ports in the original, but on the startKIT there is one CS and one EN mapped as first and second bit of a 4-bit port. When I disregarded the EN pin the code ran fine with the CS bit going low and high like it should.

But when I made a branch of the code and added an spi_master_2 (aside: full code here. This code takes care of both CS and EN bits, so it uses masks instead of 1-bit ports) I saw the situation. It’s like this (below).

The first bit of a 4-bit port is compatible with the first 1-bit port of the 1-bit port array. But there it correctly stops: the second bit of a 4-bit port is not compatible with the second 1-bit port of the 1-bit port array. From this follows that it should not have been allowed to send the 4-bit port instead of the array of 1-bit ports.

The type checker should, as far as I can see, not have allowed the compilation below. It even works for that first bit!

The semantics of p_ss[0] <: 1; with XS1_PORT_4C outputs 0001 binary but then it’s completely undefined, as with p_ss[1] <: 1; (but this latter is not compilable as I am not able to initialise p_ss for NUM_SPI_SLAVES 2 in the XS1_PORT_4C case. That’s fine! What is not fine it compiles and runs for NUM_SPI_SLAVES 1 in the XS1_PORT_4C case.

Defining PORT_PARAM_2 makes sense!

#include <platform.h>
#include <xs1.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <iso646.h>
#include <xccompat.h> // REFERENCE_PARAMs

// Example from lib_spi:
void spi_master (
        out port p_ss[num_slaves],
        static const size_t num_slaves)
{
    for(unsigned i=0;i<num_slaves;i++)
        p_ss[i] <: 1;
}

// #define XMOS_10848_PORT_PARAM_2

#ifdef XMOS_10848_PORT_PARAM_2
    #define NUM_SPI_SLAVES 2
    out port p_ss[NUM_SPI_SLAVES] = {XS1_PORT_1A, XS1_PORT_1B};
#else
    #define NUM_SPI_SLAVES 1
    out port p_ss[NUM_SPI_SLAVES] = {XS1_PORT_4C};
#endif

int main() {
    par {
        spi_master (p_ss, NUM_SPI_SLAVES);
    }
    return 0;
}

This also was posted on XCore Exchange at A 4-bit port and array of 1-bit ports match in a non-concistent manner (20Feb2018). There are some valuable comments there!

Port as parameter in interface call

This should be legal code, but the 14.3.2 compiler causes a run-time crash:

#include <platform.h>
#include <xs1.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <iso646.h>
#include <xccompat.h> // REFERENCE_PARAMs

typedef enum {false,true} bool;
typedef interface my_if {
    void x_crashes (out port p_spi_aux_in_call);
    void y (void);
} my_if;

[[combinable]]
void my_client (client my_if i_my, out port p_spi_aux_in_call) {
    timer    tmr;
    unsigned current_time;
    bool     now = true;

    tmr :> current_time;
    while (1) {
        select {
            case tmr when timerafter(current_time) :> void: {
                current_time += XS1_TIMER_HZ; // Once per second
                if (now) {
                    i_my.y (); // This first run always succeeds
                } else {
                    i_my.x_crashes (p_spi_aux_in_call); 
                    // Run-time crash: ET_ILLEGAL_RESOURCE
                }
                now = not now;
            } break;
        }
    }
}

[[combinable]]
void 
my_server
(server my_if i_my, out port p_spi_aux_in_init) {
    while (1) {
        select {
            case i_my.x_crashes (out port p_spi_aux_in_call): {
                printf("i_my.x crashes in line above\n"); // So never gets here
                break;
            }
            case i_my.y (void): {
                printf("i_my.y here\n");
                break;
            }
        }
    }
}

out port p_spi_aux_in_init = XS1_PORT_4C;
out port p_spi_aux_in_call = XS1_PORT_4D;

int main() {
    my_if i_my;
    par {
        on tile[0].core[0]: 
my_server
(i_my, p_spi_aux_in_init);
        on tile[0].core[1]: 
my_client
(i_my, p_spi_aux_in_call);
    }
    return 0;
}

There is more in XCore Exchange Crashing on port as parameter in interface call. Firstly it should not cause a run-time crash to send the port along in an interface call and secondly there is another problem about stack calculation that I have only mentioned in that post.

Code by others ported to xC by me

Adafruit SSD1306 on XMOS board

I have been asked about the code that makes it possible to use the SSD1306 on XMOS boards.

The Adafruit monochrome 128×32 I2C OLED graphic display is their product id 931 (here), containing module UG-2832HSWEG02 with chip SSD1306 from Univision Technology Inc. You will find Univision’s data sheets in the said url as well.

I have wrapped my code into a zip, but it is not at all a “runnable” test program. I had that in my early xTIMEcomposer version control (Git) system, but I didn’t want to publish it here because my latest code is the greatest. Instead I have just pulled some files out of my aquarium project “as is”. Observe that the broader part of the SSD1306 code have been written by Adafruit (some based on Univision’s pseudo code), and then modified by me. I probably should have made a branch on their GitHub node (here). The reason I haven’t done it is because I think it too much different. Maybe this is a recipe of how not to do things, but I’ll give it a try and publish it here. The code works and has been taking care of my fishes for some time now. Disclaimer: It may be easier to port the Cpp and C files more directly (as I explained in the chapter above) but I didn’t do that. So here are .xc files for .cpp etc.

Observe that I have used the older XMOS code [module_i2c_master] and not the newer [lib_i2c], which is more advanced.

Here’s the code, which I guarantee nothing about and have no responsibility for: here (rev3). It’s rev3 (5Feb2018) after some fixes I got from a guy who actually got his 128×64 display up and running based on this code. I had my 128×32 display up. Only a single display, the code is not general for a number of displays, too many defines of sizes etc. Should be easy to fix though. This guy has done a great job, because it’s not a “runnable” file set I have supplied, he did have to make some small include files himself, end remove some of the aquarium code. Should you find out that I have not included something very important then please mail me. Also, should you end up with a runnable test system for f.ex. the startKIT, maybe we should join forces and do something on GitHub? This guy did, but it’s now in a product proper, not to be published.

XMOS code comments and errata

lib_spi

In https://www.xmos.com/support/libraries/lib_spi 3.0.2 User Guide (spi.pdf) the code example in chapter 2.1 should be like this (fixes in red):

// My comment:              =  OBS ports, see comment below
in buffered port:32 p_miso =  XS1_PORT_1A;  // was out
out port p_ss[1]            = {XS1_PORT_1B};
out buffered port:32 p_sclk =  XS1_PORT_1C;  // was 22
out buffered port:32 p_mosi =  XS1_PORT_1D;
clock clk_spi               =  XS1_CLKBLK_1;
int main(void) {
  spi_master_if i_spi[1];
  par {
    spi_master(i_spi, 1, p_sclk, p_mosi, p_miso , p_ss, 1, clk_spi);
    my_application(i_spi[0]);
}
return 0; }


SPI_MODE_0 and SPI_MODE_1 assumed swapped in lib_spi ::

By careful examination to the best of my ability I have come to the assumption that lib_spi 3.0.2 has got SPI_MODE_0 and SPI_MODE_1 swapped all over. (If I am wrong nothing is better!) In the documentation and in the code. I have posted this to XCore Exchange, see SPI_MODE_0 and SPI_MODE_1 assumed swapped in lib_spi (22Feb2018).

The headings of SPI modes show the two first wrongly. The correct is in green (and Mode 0 is SPI_MODE_0 etc.):

  • “1.1.1 Mode 0 – CPOL: 0 CPHA 1″ → Mode 1 in heading and figure description
  • “1.1.2 Mode 1 – CPOL: 0 CPHA 0″ → Mode 0 i in heading and figure description

I have run the code with all four SPI modes and scoped the result, and then compared that to the Wikipedia SPI mode curve example picture here. My document is here.

Some examples from the code. The typedef in spi.h also has its comments wrong:

/** This type indicates what mode an SPI component should use */
typedef enum spi_mode_t {
  SPI_MODE_0, /**< SPI Mode 0 - Polarity = 0, Clock Edge = 1 CORRECT: Clock Edge = 0*/
  SPI_MODE_1, /**< SPI Mode 1 - Polarity = 0, Clock Edge = 0 CORRECT: Clock Edge = 1*/
  SPI_MODE_2, /**< SPI Mode 2 - Polarity = 1, Clock Edge = 0 */
  SPI_MODE_3, /**< SPI Mode 3 - Polarity = 1, Clock Edge = 1 */
} spi_mode_t;

And here is another example. I assume the code has to be modified to get it correct. For now I can just swap the SPI_MODE_0 and SPI_MODE_1</tt and use one for the other.

static void get_mode_bits(spi_mode_t mode, unsigned &cpol, unsigned &cpha){
    switch(mode){
        case SPI_MODE_0:cpol = 0; cpha= 1; break;
        case SPI_MODE_1:cpol = 0; cpha= 0; break;
        case SPI_MODE_2:cpol = 1; cpha= 0; break;
        case SPI_MODE_3:cpol = 1; cpha= 1; break;
    }
}


MSB is output first ::

In <spi.h> it says, as comment to transfer8 and transfer32 wrongly that:

The data will be transmitted least-significant bit first. (sic)

Here is the code, and it does the opposite of that comment. However, this also corresponds to the correct figures in spi.pdf: most-significant bit first (MSBFIRST):

// uint8_t data is the data to be output by transfer8_sync_zero_clkblk
for(unsigned i=0;i<8;i++){ 
    // Code omitted 
    if(!isnull(mosi)){ 
        partout_timed(mosi, 1, data>>7, time); 
            data<=1;
    }
    // Code omitted
}

The first bit to output is data>>7 which is BIT7 or MSB. Then BIT6 is shifted left into BIT7 and output next. There is no doubt: MSB first!

Handling button presses

See code in My button presses vs bounce vs EMI notes.

More

I’ll be back with the ports used on the startKIT breakout board (here) and on my XCORE-200 eXplorerKIT breakout board (here and here).

XMOS FreeRTOS port

See XMOS FreeRTOS port

Softblinking LED and PWM

See My softblinking PWM notes

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.