Being both client and server in the same select

Technical questions regarding the XTC tools and programming with XMOS.
Post Reply
User avatar
aclassifier
Respected Member
Posts: 483
Joined: Wed Apr 25, 2012 8:52 pm
Contact:

Being both client and server in the same select

Post by aclassifier »

In module lib_startkit_suport, file startkit_adc.xc and task adc_task there is a single select with

* server support as i_adc.trigger
* two different timers as timerafter
* client support by one signal from startkit_adc over a channel from the hardware ADC task as get_adc_data(c_adc..
* server support as i_adc.read

I have tried to mix being a server and a client in a single select but I can't seem to get it working. I have an example of the typical case I end up with putting the client code as a nested repeatable select, where I communicate with adc_task. The example is here (Nested select code (be sure to open the fold)), but forget its context, that's not the scope now.

Once I nest selects the task cannot be [[combinable]] and it can't be placed on a tile.core. I have tried different placements to get the code running. By the way, why is it so? Doesn't [[combinable]] basically merge all selects of all tasks on a core, and it should be possible to flatten out any nested select? Or is this really the point: flattening out selects can't be done. So, the requirement to end a [[combinable]] with the end of a while() select(..) only has to do with the merging, not also any flattening?

I get no complaint from the compiler when I do this. But I haven't succeed (yet?)

Is there something I need to be aware of to get this working? I have tried using [[guarded]] or not in the interface to use state variables to control valid cases, but no.

Is there anything I need to be aware of if I mix interface, channels, IO and timers in a single select? For me it's only interfaces.

Or are you saying that if it's compilable it's also basically runnable, and that I should try again with some modifed code? If the answer is yes then I would not give up and then I promise to publish both code snippets (since my nested select certainly works).

This post probably is related to Using both client and server interfaces as parameters Topic is solved that is solved.


Gothmag
XCore Addict
Posts: 129
Joined: Wed May 11, 2016 3:50 pm

Post by Gothmag »

I have a project where a single while(1){select{ is used for multiple interfaces(client and server sides) as well as timer based events(2) so I don't see any reason it shouldn't work. The thread handles 0 physical I/O though. I think it all comes down to timing. If the thread is too busy you may find it not responding to some events. This has worked on both a startkit and an explorerkit.
User avatar
aclassifier
Respected Member
Posts: 483
Joined: Wed Apr 25, 2012 8:52 pm
Contact:

Post by aclassifier »

Gothmag wrote:I have a project where a single while(1){select{ is used for multiple interfaces(client and server sides) as well as timer based events(2) so I don't see any reason it shouldn't work.
Ok, so I should probably try harder, then.
Gothmag wrote:The thread handles 0 physical I/O though. I think it all comes down to timing. If the thread is too busy you may find it not responding to some events
What do you mean with this? What does this imply with respect to par placement? And should it make any difference whether I use one select or nested selects? Probably not, if it works with nested?

Thanks!
Gothmag
XCore Addict
Posts: 129
Joined: Wed May 11, 2016 3:50 pm

Post by Gothmag »

I think nested statements are going to change the absolute timing requirements for the core since it's essentially a guard for multiple events. So if you have 15 events and a single select it will find 1 of the 15 ready and execute, or continue waiting. If you have 5 events and another select its only got 6 options. Once you hit the nested select you're no longer going to run the code for an event in the select 1 level up. It's similar to forcing ordered execution and your nested select events are lower priority, except you've got a group of high and a group of low.

I think if your program is ok nested but not if its all the events laid out you have a couple of options. The first is to continue to do what you're doing. If you're still using the adc, it's slow. Waiting for that is slightly slower still. Another way to do it would be having another thread do all that work with the other one calling for the result when needed. You could do ordered execution, use notifications, or use a default case. Those are just to keep the adc thread from not responding to your calls. So it would first respond to calls for data, and run the actual adc task otherwise.

Essentially almost every call is blocking and if a thread is handling an event it can't respond to any others, it needs to finish first. I've had an issue with certain events only being called very irregularly(10's of minutes late) and fixed by having another thread to handle some of that work. So to make the code easier to follow I'd recommend off-loading what you can to another thread. It might be easier to make suggestions if you did post the code, what board you're using, and possibly tools version. If it is a timing issue it's impossible to find without the code.
User avatar
aclassifier
Respected Member
Posts: 483
Joined: Wed Apr 25, 2012 8:52 pm
Contact:

Post by aclassifier »

Thanks a lot! A will indeed try to code it with one select again and see if I can get it working. I will come back on this point. Thanks!
User avatar
aclassifier
Respected Member
Posts: 483
Joined: Wed Apr 25, 2012 8:52 pm
Contact:

Post by aclassifier »

Here is the code that I can work with DO_NESTED_SELECT defined.

I have gone from nested selects to flattened out states before and it has always worked. This is a program transformation matter, really. If I've got that right then I believe the semantics (less timing) would be the same.

The code is using XMOS adc_task with zero as last parameter meaning it's only query based.

Disclaimer: there may still be some wrong coding there that I haven't resolved. But I fear it's deep XC

Code: Select all

/*
 * adc_startKIT_Client.h
 *
 *  Created on: 28. mars 2016
 *      Author: Øyvind Teig
 */

#ifndef ADC_STARTKIT_CLIENT_H_
#define ADC_STARTKIT_CLIENT_H_

#define DO_NESTED_SELECT // Comment this and it will not work

#define NUM_ELEMENTS(array) (sizeof(array) / sizeof(array[0])) // Kernighan & Pike p22

#define NUM_STARTKIT_ADC_INPUTS 4

typedef interface lib_startkit_adc_commands_if {
    [[guarded]] void trigger (void);
    [[guarded]] [[clears_notification]] {unsigned int, unsigned int} read (unsigned short adc_val[NUM_STARTKIT_ADC_INPUTS]);
    [[notification]] slave void notify (void);
} lib_startkit_adc_commands_if;

// Reads a number of samples from the startKIT adc and takes the necessary mean value over them
// to filter the effect of the processor's analogue grounding error:
//
// See XMOS Bug Issue 8216 "startKIT ADC problem" filed by Øyvind Teig,
// described at http://www.teigfam.net/oyvind/home/technology/098-my-xmos-notes/#daily_vi_xtimecomposer_1410
// most probably handled by XMOS document number XM-004900-DA bug ref. #15246 (as communicated to Teig)
//

#ifndef DO_NESTED_SELECT
[[combinable]] // But it doesn't make any difference for the error case
#endif
void My_startKIT_ADC_Client (
   client startkit_adc_acquire_if      i_startkit_adc_down,
   server lib_startkit_adc_commands_if i_startkit_adc_up,
   const unsigned int                  Num_of_data_sets);

#else
    #error Nested include ADC_STARTKIT_CLIENT_H_
#endif
And this is the XC file:

Code: Select all

/*
 * adc_startKIT_Client.xc
 *
 *  Created on: 28. mars 2016
 *      Author: Øyvind Teig
 */
#define INCLUDES
#ifdef INCLUDES
#include <platform.h>
#include <xs1.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <iso646.h>
#include <xccompat.h> // REFERENCE_PARAMs
#include "startkit_adc.h"
#include "param.h"
#include "adc_startKIT_client.h"
#endif

#define DEBUG_PRINT_STARTKIT_ADC_CLIENT 0 // Cost 0.3k
#define debug_printf(fmt, ...) do { if(DEBUG_PRINT_STARTKIT_ADC_CLIENT) printf(fmt, __VA_ARGS__); } while (0)

typedef struct tag_startkit_adc_user_vals {
    unsigned short x[NUM_STARTKIT_ADC_INPUTS]; // 16 bits as sizeof (unsiged short) is 2
    unsigned int mean_sum[NUM_STARTKIT_ADC_INPUTS]; // 32 bits as sizeof (unsiged int) is 4
    unsigned int mean_cnt;
    unsigned int adc_cnt;
    unsigned int no_adc_cnt;
} t_startkit_adc_user_vals;

typedef enum t_client_state {
    ADC_AWAIT_TRIGGER_FROM_UP,
    ADC_AWAIT_DATA_FROM_DOWN,
    ADC_AWAIT_READ_FROM_UP
} t_client_state;

#ifndef DO_NESTED_SELECT
[[combinable]] // But it doesn't make any difference for the error case
#endif
void My_startKIT_ADC_Client (
   client startkit_adc_acquire_if    i_startkit_adc_down,
   server lib_startkit_adc_commands_if i_startkit_adc_up,
   const unsigned int                Num_of_data_sets) // NUM_STARTKIT_ADC_NEEDED_DATA_SETS
{
    t_startkit_adc_user_vals adc_vals;
    t_client_state           client_state = ADC_AWAIT_TRIGGER_FROM_UP;

    unsigned int data_set_cnt = 0;

    debug_printf("%s", "My_startKIT_ADC_Client started\n");

    while(1){
        select{
            case (client_state == ADC_AWAIT_TRIGGER_FROM_UP) => i_startkit_adc_up.trigger(): {
                debug_printf ("ADC %d values?\n", Num_of_data_sets);
                for (int i=0; i < NUM_ELEMENTS(adc_vals.mean_sum); i++) {
                    adc_vals.mean_sum[i] = 0;
                }
                adc_vals.mean_cnt = 0;
                adc_vals.adc_cnt = 0;
                adc_vals.no_adc_cnt = 0;

                data_set_cnt = 1; // First set
#ifdef DO_NESTED_SELECT // This works
                while (data_set_cnt <= Num_of_data_sets) {
                    i_startkit_adc_down.trigger(); // Get next data set
                    select {
                        case i_startkit_adc_down.complete(): {
                            if (i_startkit_adc_down.read (adc_vals.x)) {
                                debug_printf ("ADC raw [%d]=", data_set_cnt);
                                for (int i=0; i < NUM_ELEMENTS(adc_vals.mean_sum); i++) {
                                    debug_printf ("(%d,", adc_vals.x[i]);
                                    adc_vals.mean_sum[i] += adc_vals.x[i];
                                    debug_printf ("%d) ", adc_vals.mean_sum[i]);
                                }
                                debug_printf ("%s", "\n");
                                adc_vals.mean_cnt++; // Equally many for each
                                adc_vals.adc_cnt++;
                            } else {
                                adc_vals.no_adc_cnt++; // This may happen, but ask XMOS why (freerunning and polled at the same time?)
                            }

                            if (data_set_cnt == Num_of_data_sets) {
                                debug_printf ("ADC %d values ready\n", Num_of_data_sets);
                                i_startkit_adc_up.notify();
                            } else {
                                // No code: get next data with i_startkit_adc_down.trigger above
                            }
                        } break;
                    }
                    data_set_cnt++;
                }
                client_state = ADC_AWAIT_READ_FROM_UP;
#else // This does not work
                i_startkit_adc_down.trigger(); // Get first data set. Error: I don't get that first data set!
                client_state = ADC_AWAIT_DATA_FROM_DOWN;
            } break;

            //case (client_state == ADC_AWAIT_DATA_FROM_DOWN) => i_startkit_adc_down.complete(): { // No help
            case i_startkit_adc_down.complete(): {
                if (i_startkit_adc_down.read (adc_vals.x)) {
                    debug_printf ("ADC raw [%d]=", data_set_cnt);
                    for (int i=0; i < NUM_ELEMENTS(adc_vals.mean_sum); i++) {
                        debug_printf ("(%d,", adc_vals.x[i]);
                        adc_vals.mean_sum[i] += adc_vals.x[i];
                        debug_printf ("%d) ", adc_vals.mean_sum[i]);
                    }
                    debug_printf ("%s", "\n");
                    adc_vals.mean_cnt++; // Equally many for each
                    adc_vals.adc_cnt++;
                 } else {
                    adc_vals.no_adc_cnt++; // This may happen, but ask XMOS why (freerunning and polled at the same time?)
                 }

                 if (data_set_cnt == Num_of_data_sets) {
                     debug_printf ("ADC %d values ready\n", Num_of_data_sets);
                     i_startkit_adc_up.notify();
                     client_state = ADC_AWAIT_READ_FROM_UP;
                 } else {
                     data_set_cnt++;
                     i_startkit_adc_down.trigger(); // Get next data set
                 }
#endif
            } break;

            case (client_state == ADC_AWAIT_READ_FROM_UP) => i_startkit_adc_up.read (unsigned short return_adc_mean_vals[NUM_STARTKIT_ADC_INPUTS]) -> {unsigned int adc_cnt, unsigned int no_adc_cnt}: {
                debug_printf ("ADC %d values: ", Num_of_data_sets);
                unsigned short offsets [NUM_STARTKIT_ADC_INPUTS] = {OFFSET_ADC_INPUTS_STARTKIT};

                for (int i=0; i<NUM_ELEMENTS(return_adc_mean_vals); i++) {
                    if (adc_vals.mean_cnt == 0) { // Only theoretically possible if all did no_adc_cnt++
                        return_adc_mean_vals[i] = 0;
                    } else {
                        return_adc_mean_vals[i] = (unsigned short) (adc_vals.mean_sum[i]/adc_vals.mean_cnt);

                        // Offsets measured on whole set with nothing connected on startKIT ADC J2
                        // No underflow on unsigned short, i.e. it cannot go negative:
                        if (return_adc_mean_vals[i] <= offsets[i]) {
                            return_adc_mean_vals[i] = 0;
                        } else {
                            return_adc_mean_vals[i] -= offsets[i];
                        }
                        debug_printf ("(%d,%d) ", adc_vals.mean_sum[i], (unsigned short) (adc_vals.mean_sum[i]/adc_vals.mean_cnt));
                    }
                }
                debug_printf ("%s", "\n");
                client_state = ADC_AWAIT_TRIGGER_FROM_UP;
                {adc_cnt = adc_vals.adc_cnt; no_adc_cnt = adc_vals.no_adc_cnt;} // return
            } break;
        } // select
    } // while
}
--
Øyvind Teig
Trondheim (Norway)
https://www.teigfam.net/oyvind/home/
Post Reply