Contents
For the TIOBE index 016:[10]: «xC programming».
Resource page for kode24.no article
Kode24.no er en del av Scandinavia Online / Aller Media (Standard disclaimer)
Slik styrer han akvariet sitt med xC (This is how he controls his aquarium with xC)
Svar på en kommentar: «Den ble litt lang, ja. Men jeg ønsket at de som først trigget på innholdet skulle få igjen for «strevet». Og om du kjøper et kort og kommer deg opp på xC, kan du jo lese den om igjen etter noen uker. Pluss, tilbudet gjelder! Lykke til!»
Tilbakemelding fra kode24 14.april 2020. Artikkelen var lest av 1100! Om 5% har lest hele så blir det 55 personer! Det er bra i min verden!
References
Article in kode24.no 24. March 2020
www.kode24.no – Slik styrer han akvariet sitt med xC. Øyvind Teig gir deg full gjennomgang av hvordan han bygde XMOS-systemet. In Norwegian. (This is how he controls his aquarium with xC. Øyvind Teig gives you a full overview of how he built the XMOS system). Kode24 is a Norwegian web magazine for coders.
Try the Google translation of it (here) → observe that task may become bag!
Thanks to Sverre Hendseth, NTNU for a thorough reading and commenting before the final version for kode24.
The source to the article is here.
Democode
The tool used is XMOS xTIMEcomposer 14.4.1. It is free for download, but you have to register
It also contains a simulator and a scope. You can use them on this democode. However, timerafter(?) and ports will then not be simulated, so you need to rewrite or find other code examples for the simulator. Something to do if you are alone at home in these corona times?
The code (below) is in in the xC language. Papers about it from XMOS are referenced in the above link
The code runs on an xCORE-200 explorer kit (XMOS and mine My xCORE-200 eXplorerKIT notes (WiFi))
This code contains three TODOs. See Code example for article on the XMOS xCore Exchange. Update 10Aug2020: Some of them are actually resolved there.
More on xC
For more about xC, also see My XMOS pages
Disclaimer
Aquarium notes
My aquarium notes
My aquarium holiday automatic fish feeder (for granules)
My USB watchdog (and relay output) box
Code
Mail me (here) if you want the full code of the aquarium, and I will upload it to somewhere. I will publish it here.
Task viewer
Screen clips from xTIMEcomposer. Red text and build outputs are insets.
Task / data flow diagram
The code
/* * main.xc * * Created on: 12. feb. 2020 * Author: teig * Ver 0.75 2020.02.21 in buffered port:1 inP1_button, instead of in port inP1_button, * Ver 0.74 2020.02.15 https://xcore.com/viewtopic.php?f=26&t=7839 * Ver 0.73 2020.02.15 round_cnt_task added, but it cannot be [[distributable]] * Ver 0.72 2020.02.15 Works, before [[distributable]] * Ver 0.70 2020.02.14 outP4_leds is new * Ver 0.60 2020.02.13 inP1_button button added * Ver 0.50 2020.02.13 Starts workers in 4 sequences and workers simulate random work */ #include <platform.h> // core #include <stdio.h> // printf #include <timer.h> // delay_milliseconds(..), XS1_TIMER_HZ etc #include <random.h> // xmos. Also uses "random_conf.h" #include <iso646.h> // readability // --- // Control printing // See https://stackoverflow.com/questions/1644868/define-macro-for-debug-printing-in-c // --- #define DEBUG_PRINT_TEST 0 // [0->1] code about [5,12] kB #define debug_print(fmt, ...) do { if(DEBUG_PRINT_TEST) printf(fmt, __VA_ARGS__); } while (0) // --- // Define bool // BOOLEAN #include <stdbool.h> if C99 // See https://www.teigfam.net/oyvind/home/technology/165-xc-code-examples/#bool // --- typedef enum {false,true} bool; // 0,1 This typedef matches any integer-type type like long, int, // signed, unsigned, char, bool // --- // Define type equal to the width of xC "timer". This processor has 10 HW timers, // but the numbers needed in this code will (with NUM_WORKERS 4) be 2 timers if // all worker_task run on the same logical core (par [[combine]]) or 5 timers // if worker_task each have a logical core for themselves. // Both signed and unsigned (always int) will do, since both will wrap around on // "overflow" and the hex code will look the same. This way AFTER is well defined // since adding a value will trigger "timerafter" ticks into the future // --- typedef signed time32_t; // Ticks to 100 in 1 us // --- // Define number of workers. Is needed here because variable length arrays // are not permitted to tasks when they are [[combinable]] // --- #define NUM_WORKERS 4 // --- // Define data typedefs // --- typedef unsigned worked_ms_t; typedef struct log_t { unsigned cnt; unsigned log_started [NUM_WORKERS]; unsigned log_finished [NUM_WORKERS]; worked_ms_t log_worked_ms[NUM_WORKERS]; bool button_pressed; } log_t; // --- // do_print_log // Prints log if DEBUG_PRINT_TEST is 1. If DEBUG_PRINT_TEST is 0, this function // is not generated by the compiler // --- void do_print_log ( log_t log, unsigned const num_workers) { debug_print ("\ncnt %u %s\n", log.cnt, log.button_pressed ? "BUTTON" : ""); debug_print ("%s", "log.log_started "); for (unsigned ix=0; ix < num_workers; ix++) { debug_print ("%2u ", log.log_started[ix]); } debug_print ("%s", "\nlog.log_worked_ms "); for (unsigned ix=0; ix < num_workers; ix++) { debug_print ("%2u ", log.log_worked_ms[ix]); } debug_print ("%s", "\nlog.log_finished "); for (unsigned ix=0; ix < num_workers; ix++) { debug_print ("%2u ", log.log_finished[ix]); } debug_print ("%s", "\n"); } // --- // 1 BIT PORT // External button defined (button press pulls a pullup resistor down) // Board's buttons 4E.0 and 4E.1 could have been used, bit want to show 1-bit port // --- in buffered port:1 inP1_button = on tile[0]: XS1_PORT_1M; // External HW GPIO J1 P63 #define BUTTON_PRESSED 0 #define BUTTON_RELEASED 1 // --- // 4 BIT PORT // Internal LEDs defined. High is "on" // --- out buffered port:4 outP4_leds = on tile[0]: XS1_PORT_4F; // xCORE-200 explorerKIT GPIO J1 P5, P3, P1 #define BOARD_LEDS_INIT 0x00 #define BOARD_LED_MASK_GREEN_ONLY 0x01 // BIT0 #define BOARD_LED_MASK_RGB_BLUE 0x02 // BIT1 #define BOARD_LED_MASK_RGB_GREEN 0x04 // BIT2 #define BOARD_LED_MASK_RGB_RED 0x08 // BIT3 #define BOARD_LED_MASK_MAX_1 (BOARD_LED_MASK_GREEN_ONLY) #define BOARD_LED_MASK_MAX_2 (BOARD_LED_MASK_RGB_BLUE bitor BOARD_LED_MASK_MAX_1) #define BOARD_LED_MASK_MAX_3 (BOARD_LED_MASK_RGB_GREEN bitor BOARD_LED_MASK_MAX_2) #define BOARD_LED_MASK_MAX_4 (BOARD_LED_MASK_RGB_RED bitor BOARD_LED_MASK_MAX_3) #define BOARD_LED_MASK_MAX BOARD_LED_MASK_MAX_1 // _1, _2, _3 or _4 // --- // do_swipe_leds // Sets LEDs on the xCORE-200 explorerKIT board. There are two, one green only // and one RGB (with three lines). High is LED on // --- void do_swipe_leds ( out buffered port:4 outP4_leds, unsigned &?led_bits, // '&' is reference. Aside: pointer types: // no decoration (safe), "movable", "alias" and "unsafe" unsigned const board_led_mask_max) { if (isnull(led_bits)) { // Just to show a nullable type, shown with '?': outP4_leds <: BOARD_LED_MASK_GREEN_ONLY; } else { outP4_leds <: led_bits; // Output LED bits led_bits++; led_bits and_eq board_led_mask_max; // GREEN on and off and 3-coloured RGB LED } } // --- // round_cnt_task // Task that just outputs an incremented value, showing use of a chan // This takes two chanends and one logical core. // Plus one timer, for some reason TODO // --- // if [[distributable]] error message from compiler here void round_cnt_task (chanend c_cnt) { // chans are untyped in xC (but interface is typed++) unsigned cnt = 0; while (true) { cnt++; // Synchronous, blocking, no buffer overflow ever possible since there is no buffer: c_cnt <: cnt; } } // if [[distributable]] error message from compiler here // Not used task, but does the same as the task above void round_cnt_task_2 (chanend c_cnt) { // chans are untyped in xC (but interface is typed++) unsigned cnt = 0; timer tmr; time32_t time_ticks; // Ticks to 100 in 1 us tmr :> time_ticks; // Now while (true) { select { // The case passively waits on an event: case tmr when timerafter (time_ticks) :> time_ticks : { // time_tics always updated, so timerafter is always ready cnt++; // Synchronous, blocking, no buffer overflow ever possible since there is no buffer: c_cnt <: cnt; } break; } } } // --- // An interface is implemented by chanends, locks, calls or safe patterns set // up by the code generation. The particular _transaction_ pattern below enables // the compiler to set up that particular asynchronous pattern, based on // synchronous, blocking primitives. // --- typedef interface worker_if_t { void async_work_request (void); [[notification]] slave void finished_work (void); [[clears_notification]] worked_ms_t get_work_result (void); } worker_if_t; // --- // worker_task // NUM_WORKERS of these are started. They may share a logical core when // [[combine]] par or run on NUM_WORKERS logical cores if no [[combine]]. // The pattern starts with async_work_request and then simulates work for // some time, then sends a [[notification]] of finished_work and then the // clients responds with get_work_result which [[clears_notification]]. // The compiler will insert the correct code to allow only that pattern. // --- [[combinable]] void worker_task ( server worker_if_t i_worker, const unsigned index_of_server) { timer tmr; time32_t time_ticks; // Ticks to 100 in 1 us bool doCollectData = false; worked_ms_t sim_work_ms = 0; unsigned random_seed = random_create_generator_from_seed(index_of_server); // xmos unsigned random_work_delay_ms; debug_print ("worker_task %u\n", index_of_server); while (true) { select { // Each case passively waits on an event: case i_worker.async_work_request () : { doCollectData = true; random_work_delay_ms = random_get_random_number (random_seed) % 100; // [0..99] sim_work_ms = random_work_delay_ms; tmr :> time_ticks; // Immediately time_ticks += (sim_work_ms * XS1_TIMER_KHZ); // Simulate work } break; case (doCollectData == true) => tmr when timerafter (time_ticks) :> void : { // Now we have simulated that picking up log.log_worked_ms took random_work_delay_ms doCollectData = false; i_worker.finished_work(); } break; case i_worker.get_work_result (void) -> worked_ms_t worked_ms : { worked_ms = sim_work_ms; } break; } } } // --- // client_task // Asks for work from NUM_WORKERS worker_task (service requested // in different sequences) and results from workers, when they arrive, handled. // Each interface call is blocking and synchronous, but the net result of the // pattern is asynchronous worker_task assignments. // Log, a button and LEDs handled. // --- [[combinable]] void client_task ( client worker_if_t i_worker[NUM_WORKERS], in buffered port:1 inP1_button, out buffered port:4 outP4_leds, chanend c_cnt) { timer tmr; time32_t time_ticks; // Ticks to 100 in 1 us bool expect_notification_nums = 0; // xmos. Pseudorandom, so will look the same on and after each start-up unsigned random_seed = random_create_generator_from_seed(1); unsigned random_number; log_t log; bool allow_button = false; bool button_current_val = BUTTON_RELEASED; unsigned led_bits; // Init below.. led_bits = BOARD_LEDS_INIT; // ..here to avoid "not used" if "null" used instead log.button_pressed = false; debug_print ("%s", "client_task\n"); tmr :> time_ticks; time_ticks += (1 * XS1_TIMER_HZ); // 1 second before first timerafter while (true) { select { // Each case passively waits on an event: case (expect_notification_nums == 0) => tmr when timerafter (time_ticks) :> void : { random_number = random_get_random_number (random_seed); // Just trying to start randomly // Start as [0,1,2,3], [3,0,1,2], [2,3,0,1], [1,2,3,0]: for (unsigned ix=0; ix < NUM_WORKERS; ix++) { unsigned random_worker = random_number % NUM_WORKERS; // Inside [0..(NUM_WORKERS-1)] i_worker[random_worker].async_work_request(); // Now log.log_started in random sequence random_number++; // Next (but modulo NUM_WORKERS above) log.log_started[ix] = random_worker; } expect_notification_nums = NUM_WORKERS; // === Do something else while all worker_task work === } break; case (expect_notification_nums > 0) => i_worker[unsigned index_of_server].finished_work() : { // Server async_work_request entries protected by code and scheduler until this is run: log.log_worked_ms[index_of_server] = i_worker[index_of_server].get_work_result(); // async_work_request is not allowed again before the above line is run, // by compiler and code expect_notification_nums--; log.log_finished[expect_notification_nums] = index_of_server; if (expect_notification_nums == 0) { select { // Nested select case c_cnt :> log.cnt: {} break; } do_print_log (log, NUM_WORKERS); // Only if DEBUG_PRINT_TEST is 1 do_swipe_leds (outP4_leds, led_bits, BOARD_LED_MASK_MAX); // led_bits may be "null" // === Process received log.log_worked_ms, or just.. === tmr :> time_ticks; // ..repeat immediately allow_button = (log.cnt >= 10); } else {} } break; case allow_button => inP1_button when pinsneq(button_current_val) :> button_current_val: { // I/O pin changed value // Debouncing not done (best done in separate task, with its own timerafter) log.button_pressed = (button_current_val == BUTTON_PRESSED); // May not reach do_print_log } break; // default: {} break; // Only for busy poll! Not allowed in [[combinable]] function } } } // --- // main // Starts tasks // --- int main() { worker_if_t i_worker[NUM_WORKERS]; chan c_cnt; par { [[combine]] // NUM_WORKERS(4) = // [cores,timers,chanends]->[3,3,11], if no [[combine]] then ->[6,6,11] par (unsigned ix = 0; ix < NUM_WORKERS; ix++) { worker_task (i_worker[ix], ix); } client_task (i_worker, inP1_button, outP4_leds, c_cnt); round_cnt_task (c_cnt); } return 0; }
The code (download)
The code may be donwloaded from My xC code downloads page, part #202.
Log
client_task worker_task 0 worker_task 1 worker_task 2 worker_task 3 cnt 1 log.log_started 2 3 0 1 log.log_worked_ms 92 78 69 59 log.log_finished 0 1 2 3 cnt 2 log.log_started 1 2 3 0 log.log_worked_ms 18 25 77 58 log.log_finished 2 3 1 0 cnt 3 log.log_started 3 0 1 2 log.log_worked_ms 66 15 61 96 log.log_finished 3 0 2 1 cnt 4 log.log_started 2 3 0 1 log.log_worked_ms 56 86 16 30 log.log_finished 1 0 3 2 cnt 5 log.log_started 1 2 3 0 log.log_worked_ms 2 85 73 62 log.log_finished 1 2 3 0 cnt 6 log.log_started 0 1 2 3 log.log_worked_ms 9 92 83 50 log.log_finished 1 2 3 0 cnt 7 log.log_started 1 2 3 0 log.log_worked_ms 55 81 94 12 log.log_finished 2 1 0 3 cnt 8 log.log_started 0 1 2 3 log.log_worked_ms 53 60 30 55 log.log_finished 1 3 0 2 cnt 9 log.log_started 0 1 2 3 log.log_worked_ms 99 72 32 7 log.log_finished 0 1 2 3 cnt 10 log.log_started 3 0 1 2 log.log_worked_ms 97 63 61 23 log.log_finished 0 1 2 3 cnt 11 log.log_started 2 3 0 1 log.log_worked_ms 54 46 78 22 log.log_finished 2 0 1 3 cnt 12 log.log_started 1 2 3 0 log.log_worked_ms 32 85 54 67 log.log_finished 1 3 2 0 cnt 13 log.log_started 0 1 2 3 log.log_worked_ms 15 56 77 42 log.log_finished 2 1 3 0 cnt 14 log.log_started 2 3 0 1 log.log_worked_ms 17 18 38 81 log.log_finished 3 2 1 0 cnt 15 log.log_started 3 0 1 2 log.log_worked_ms 78 39 41 48 log.log_finished 0 3 2 1 cnt 16 log.log_started 1 2 3 0 log.log_worked_ms 39 57 71 77 log.log_finished 3 2 1 0 cnt 17 log.log_started 1 2 3 0 log.log_worked_ms 81 97 84 44 log.log_finished 1 2 0 3 cnt 18 log.log_started 2 3 0 1 log.log_worked_ms 56 58 97 67 log.log_finished 2 3 1 0 cnt 19 log.log_started 3 0 1 2 log.log_worked_ms 74 31 88 9 log.log_finished 2 0 1 3 cnt 20 log.log_started 0 1 2 3 log.log_worked_ms 55 64 93 78 log.log_finished 2 3 1 0 cnt 21 log.log_started 3 0 1 2 log.log_worked_ms 98 51 56 49 log.log_finished 0 2 1 3 cnt 22 log.log_started 1 2 3 0 log.log_worked_ms 92 73 74 19 log.log_finished 0 2 1 3 cnt 23 log.log_started 2 3 0 1 log.log_worked_ms 8 14 77 55 log.log_finished 2 3 1 0 cnt 24 log.log_started 0 1 2 3 log.log_worked_ms 41 72 47 34 log.log_finished 1 2 0 3 cnt 25 log.log_started 1 2 3 0 log.log_worked_ms 32 53 42 19 log.log_finished 1 2 0 3 cnt 26 log.log_started 0 1 2 3 log.log_worked_ms 12 64 29 97 log.log_finished 3 1 2 0 cnt 27 log.log_started 3 0 1 2 log.log_worked_ms 40 31 10 77 log.log_finished 3 0 1 2 cnt 28 log.log_started 2 3 0 1
Build log
Last list only when xCC_MAP_FLAGS
includes -report
10:11:23 **** Incremental Build of configuration Default for project _kode24_xcore200_1q2020 **** xmake CONFIG=Default all Checking build modules Using build modules: module_random Analyzing random_init.c Analyzing main.xc Analyzing random.xc Creating dependencies for random.xc Creating dependencies for main.xc Creating dependencies for random_init.c Compiling random_init.c Compiling main.xc Compiling random.xc Rebuild .build/_obj.rsp Creating _kode24_xcore200_1q2020.xe Constraint check for tile[0]: Cores available: 8, used: 3 . OKAY Timers available: 10, used: 3 . OKAY Chanends available: 32, used: 11 . OKAY Memory available: 262144, used: 12224 . OKAY (Stack: 1724, Code: 9494, Data: 1006) Constraints checks PASSED. Build Complete 10:11:26 Build Finished (took 2s.240ms)
makefile
# The TARGET variable determines what target system the application is # compiled for. It either refers to an XN file in the source directories # or a valid argument for the --target option when compiling TARGET = xCORE-200-EXPLORER # The APP_NAME variable determines the name of the final .xe file. It should # not include the .xe postfix. If left blank the name will default to # the project name APP_NAME = _kode24_xcore200_1q2020 # The USED_MODULES variable lists other module used by the application. USED_MODULES = module_random # The flags passed to xcc when building the application # You can also set the following to override flags for a particular language: # xCC_xC_FLAGS, xCC_C_FLAGS, xCC_ASM_FLAGS, xCC_CPP_FLAGS # If the variable xCC_MAP_FLAGS is set it overrides the flags passed to # xcc for the final link (mapping) stage. xCC_FLAGS = -O2 -g # The xCORE_ARM_PROJECT variable, if set to 1, configures this # project to create both xCORE and ARM binaries. xCORE_ARM_PROJECT = 0 # _kode24_xcore200_1q2020.txt appears in .build xCC_MAP_FLAGS = -Xmapper --map -Xmapper _kode24_xcore200_1q2020.txt -report # The VERBOSE variable, if set to 1, enables verbose output from the make system. VERBOSE = 0 XMOS_MAKE_PATH ?= ../.. -include $(XMOS_MAKE_PATH)/xcommon/module_xcommon/build/Makefile.common
Code: terminating and restarting worker_task
Search for worker_task
and see what I have done for xC testing. I added a variable workeing
in worker_task
, that terminates it when one session is over. It is then restarted again in worker_task_scheduler
. This shows that a par
can run from other par
. This version compiles to cores:6, timers:6, chanends:11. Now worker_task
cannot be [[combinable]], same for worker_task_scheduler
and the four par
components there. It has a high cost compared to cores:3, timers:3, chanends:11above. However, the compiler/mapper still does a great job in just adding 3 cores. I assume that the par
components in worker_task_scheduler
are mapped as the same cores that work_task.
/* * _xc_test_4_2020.xc * * Created on: 9. apr. 2020 * Author: teig */ #include <platform.h> // core #include <stdio.h> // printf #include <timer.h> // delay_milliseconds(..), XS1_TIMER_HZ etc #include <random.h> // xmos. Also uses "random_conf.h" #include <iso646.h> // readability // --- // Control printing // See https://stackoverflow.com/questions/1644868/define-macro-for-debug-printing-in-c // --- #define DEBUG_PRINT_TEST 1 // [0->1] code about [5,12] kB #define debug_print(fmt, ...) do { if(DEBUG_PRINT_TEST) printf(fmt, __VA_ARGS__); } while (0) // --- // Define bool // BOOLEAN #include <stdbool.h> if C99 // See https://www.teigfam.net/oyvind/home/technology/165-xc-code-examples/#bool // --- typedef enum {false,true} bool; // 0,1 This typedef matches any integer-type type like long, int, // signed, unsigned, char, bool // --- // Define type equal to the width of xC "timer". This processor has 10 HW timers, // but the numbers needed in this code will (with NUM_WORKERS 4) be 2 timers if // all worker_task run on the same logical core (par [[combine]]) or 5 timers // if worker_task each have a logical core for themselves. // Both signed and unsigned (always int) will do, since both will wrap around on // "overflow" and the hex code will look the same. This way AFTER is well defined // since adding a value will trigger "timerafter" ticks into the future // --- typedef signed time32_t; // Ticks to 100 in 1 us // --- // Define number of workers. Is needed here because variable length arrays // are not permitted to tasks when they are [[combinable]] // --- #define NUM_WORKERS 4 // --- // Define data typedefs // --- typedef unsigned worked_ms_t; typedef struct log_t { unsigned cnt; unsigned log_started [NUM_WORKERS]; unsigned log_finished [NUM_WORKERS]; worked_ms_t log_worked_ms[NUM_WORKERS]; bool button_pressed; } log_t; // --- // do_print_log // Prints log if DEBUG_PRINT_TEST is 1. If DEBUG_PRINT_TEST is 0, this function // is not generated by the compiler // --- void do_print_log ( log_t log, unsigned const num_workers) { debug_print ("\ncnt %u %s\n", log.cnt, log.button_pressed ? "BUTTON" : ""); debug_print ("%s", "log.log_started "); for (unsigned ix=0; ix < num_workers; ix++) { debug_print ("%2u ", log.log_started[ix]); } debug_print ("%s", "\nlog.log_worked_ms "); for (unsigned ix=0; ix < num_workers; ix++) { debug_print ("%2u ", log.log_worked_ms[ix]); } debug_print ("%s", "\nlog.log_finished "); for (unsigned ix=0; ix < num_workers; ix++) { debug_print ("%2u ", log.log_finished[ix]); } debug_print ("%s", "\n"); } // --- // 1 BIT PORT // External button defined (button press pulls a pullup resistor down) // Board's buttons 4E.0 and 4E.1 could have been used, bit want to show 1-bit port // --- in buffered port:1 inP1_button = on tile[0]: XS1_PORT_1M; // External HW GPIO J1 P63 #define BUTTON_PRESSED 0 #define BUTTON_RELEASED 1 // --- // 4 BIT PORT // Internal LEDs defined. High is "on" // --- out buffered port:4 outP4_leds = on tile[0]: XS1_PORT_4F; // xCORE-200 explorerKIT GPIO J1 P5, P3, P1 #define BOARD_LEDS_INIT 0x00 #define BOARD_LED_MASK_GREEN_ONLY 0x01 // BIT0 #define BOARD_LED_MASK_RGB_BLUE 0x02 // BIT1 #define BOARD_LED_MASK_RGB_GREEN 0x04 // BIT2 #define BOARD_LED_MASK_RGB_RED 0x08 // BIT3 #define BOARD_LED_MASK_MAX_1 (BOARD_LED_MASK_GREEN_ONLY) #define BOARD_LED_MASK_MAX_2 (BOARD_LED_MASK_RGB_BLUE bitor BOARD_LED_MASK_MAX_1) #define BOARD_LED_MASK_MAX_3 (BOARD_LED_MASK_RGB_GREEN bitor BOARD_LED_MASK_MAX_2) #define BOARD_LED_MASK_MAX_4 (BOARD_LED_MASK_RGB_RED bitor BOARD_LED_MASK_MAX_3) #define BOARD_LED_MASK_MAX BOARD_LED_MASK_MAX_4 // _1, _2, _3 or _4 // --- // do_swipe_leds // Sets LEDs on the xCORE-200 explorerKIT board. There are two, one green only // and one RGB (with three lines). High is LED on // --- void do_swipe_leds ( out buffered port:4 outP4_leds, unsigned &?led_bits, // '&' is reference. Aside: pointer types: // no decoration (safe), "movable", "alias" and "unsafe" unsigned const board_led_mask_max) { if (isnull(led_bits)) { // Just to show a nullable type, shown with '?': outP4_leds <: BOARD_LED_MASK_GREEN_ONLY; } else { outP4_leds <: led_bits; // Output LED bits led_bits++; led_bits and_eq board_led_mask_max; // GREEN on and off and 3-coloured RGB LED } } // --- // round_cnt_task // Task that just outputs an incremented value, showing use of a chan // This takes two chanends and one logical core. // Plus one timer, for some reason TODO // --- // if [[distributable]] error message from compiler here void round_cnt_task (chanend c_cnt) { // chans are untyped in xC (but interface is typed++) unsigned cnt = 0; while (true) { cnt++; // Synchronous, blocking, no buffer overflow ever possible since there is no buffer: c_cnt <: cnt; } } // if [[distributable]] error message from compiler here // Not used task, but does the same as the task above void round_cnt_task_2 (chanend c_cnt) { // chans are untyped in xC (but interface is typed++) unsigned cnt = 0; timer tmr; time32_t time_ticks; // Ticks to 100 in 1 us tmr :> time_ticks; // Now while (true) { select { // The case passively waits on an event: case tmr when timerafter (time_ticks) :> time_ticks : { // time_tics always updated, so timerafter is always ready cnt++; // Synchronous, blocking, no buffer overflow ever possible since there is no buffer: c_cnt <: cnt; } break; } } } // --- // An interface is implemented by chanends, locks, calls or safe patterns set // up by the code generation. The particular _transaction_ pattern below enables // the compiler to set up that particular asynchronous pattern, based on // synchronous, blocking primitives. // --- typedef interface worker_if_t { void async_work_request (void); [[notification]] slave void finished_work (void); [[clears_notification]] worked_ms_t get_work_result (void); } worker_if_t; // --- // worker_task // NUM_WORKERS of these are started. They may share a logical core when // [[combine]] par or run on NUM_WORKERS logical cores if no [[combine]]. // The pattern starts with async_work_request and then simulates work for // some time, then sends a [[notification]] of finished_work and then the // clients responds with get_work_result which [[clears_notification]]. // The compiler will insert the correct code to allow only that pattern. // --- //[[combinable]] void worker_task ( server worker_if_t i_worker, const unsigned index_of_server) { timer tmr; time32_t time_ticks; // Ticks to 100 in 1 us bool doCollectData = false; worked_ms_t sim_work_ms = 0; unsigned random_seed = random_create_generator_from_seed(index_of_server); // xmos unsigned random_work_delay_ms; bool working = true; debug_print ("worker_task %u\n", index_of_server); while (working) { select { // Each case passively waits on an event: case i_worker.async_work_request () : { doCollectData = true; random_work_delay_ms = random_get_random_number (random_seed) % 100; // [0..99] sim_work_ms = random_work_delay_ms; tmr :> time_ticks; // Immediately time_ticks += (sim_work_ms * XS1_TIMER_KHZ); // Simulate work } break; case (doCollectData == true) => tmr when timerafter (time_ticks) :> void : { // Now we have simulated that picking up log.log_worked_ms took random_work_delay_ms doCollectData = false; i_worker.finished_work(); } break; case i_worker.get_work_result (void) -> worked_ms_t worked_ms : { worked_ms = sim_work_ms; working = false; } break; } } } // --- // client_task // Asks for work from NUM_WORKERS worker_task (service requested // in different sequences) and results from workers, when they arrive, handled. // Each interface call is blocking and synchronous, but the net result of the // pattern is asynchronous worker_task assignments. // Log, a button and LEDs handled. // --- [[combinable]] void client_task ( client worker_if_t i_worker[NUM_WORKERS], in buffered port:1 inP1_button, out buffered port:4 outP4_leds, chanend c_cnt) { timer tmr; time32_t time_ticks; // Ticks to 100 in 1 us bool expect_notification_nums = 0; // xmos. Pseudorandom, so will look the same on and after each start-up unsigned random_seed = random_create_generator_from_seed(1); unsigned random_number; log_t log; bool allow_button = false; bool button_current_val = BUTTON_RELEASED; unsigned led_bits; // Init below.. led_bits = BOARD_LEDS_INIT; // ..here to avoid "not used" if "null" used instead log.button_pressed = false; debug_print ("%s", "client_task\n"); tmr :> time_ticks; time_ticks += (1 * XS1_TIMER_HZ); // 1 second before first timerafter while (true) { select { // Each case passively waits on an event: case (expect_notification_nums == 0) => tmr when timerafter (time_ticks) :> void : { random_number = random_get_random_number (random_seed); // Just trying to start randomly // Start as [0,1,2,3], [3,0,1,2], [2,3,0,1], [1,2,3,0]: for (unsigned ix=0; ix < NUM_WORKERS; ix++) { unsigned random_worker = random_number % NUM_WORKERS; // Inside [0..(NUM_WORKERS-1)] i_worker[random_worker].async_work_request(); // Now log.log_started in random sequence random_number++; // Next (but modulo NUM_WORKERS above) log.log_started[ix] = random_worker; } expect_notification_nums = NUM_WORKERS; // === Do something else while all worker_task work === } break; case (expect_notification_nums > 0) => i_worker[unsigned index_of_server].finished_work() : { // Server async_work_request entries protected by code and scheduler until this is run: log.log_worked_ms[index_of_server] = i_worker[index_of_server].get_work_result(); // async_work_request is not allowed again before the above line is run, // by compiler and code expect_notification_nums--; log.log_finished[expect_notification_nums] = index_of_server; if (expect_notification_nums == 0) { select { // Nested select case c_cnt :> log.cnt: {} break; } do_print_log (log, NUM_WORKERS); // Only if DEBUG_PRINT_TEST is 1 do_swipe_leds (outP4_leds, led_bits, BOARD_LED_MASK_MAX); // led_bits may be "null" // === Process received log.log_worked_ms, or just.. === tmr :> time_ticks; // ..repeat immediately allow_button = (log.cnt >= 10); } else {} } break; case allow_button => inP1_button when pinsneq(button_current_val) :> button_current_val: { // I/O pin changed value // Debouncing not done (best done in separate task, with its own timerafter) log.button_pressed = (button_current_val == BUTTON_PRESSED); // May not reach do_print_log } break; } } } void worker_task_scheduler (server worker_if_t i_worker[NUM_WORKERS]) { // cores,timers,chanends 6,6,11 par { while (true) { // Probably mapped on the same core as this: worker_task (i_worker[0], 0); } while (true) { worker_task (i_worker[1], 1); } while (true) { worker_task (i_worker[2], 2); } while (true) { worker_task (i_worker[3], 3); } } } void worker_task_scheduler_ (server worker_if_t i_worker[NUM_WORKERS]) { // cores,timers,chanends 6,6,11 while (true) { par (unsigned ix = 0; ix < NUM_WORKERS; ix++) { worker_task (i_worker[ix], ix); } } } // --- // main // Starts tasks // --- int main() { worker_if_t i_worker[NUM_WORKERS]; chan c_cnt; par { worker_task_scheduler (i_worker); client_task (i_worker, inP1_button, outP4_leds, c_cnt); round_cnt_task (c_cnt); } return 0; }