My XC softblinking PWM notes

Started 28Jun2020. 20Jul2020 in work, more to do (ie. preparing it for a lecture for NTNU)

This note is in group Technology and XMOS pages. Standard disclaimer. Even if my writing style is stream of consciousness I hope that it’s possible to read this note from the top.

Teaser

Fig.1 – Two LEDs (low signal is “on”). Period: upper 4 secs, lower 3 secs. Range: upper 10-80%, lower 0-100%. 1% = 10 msec i.e. 100 Hz. (It is somewhat stuttering since it’s a screen clip from a browser being served by the SIGLENT scope’s web server)

This article is about a multi-core softblinker. Yes, it is.

Intro

This note started with my code for a soft-blinking LED, as shown in My processor-to-analogue audio equaliser notes chapter “The softblinking LED PWM”.

To my surprise I could not find anything about this on Wikipedia, so I added a sub-chapter in the Pulse-width modulation article, here: Soft-blinking LED indicator.

A soft blinker or soft-blinker or softblinker soft blinks, soft-blinks or softblinks, right? Can’t be too wrong? I have also seen this mentioned as “On old laptops, the logo used to pulse instead of flashing” (p96) and “A wearable pulsing glow” (p124) of [1]. I actually still have one of these olde Apple laptops, the iBook from 2005. It’s stacked away as a memorabilia. It was the first time I ever saw a softblinking LED. But if it pulsed it was more like a bear just after falling into hibernation! Vveeryy ssllooow.

I assume that most softblinkers in an embedded system (like Arduino AVR/Mega/ARM or Raspberry Pi) or would be implemented with

  1. Built-in PWM hardware and perhaps interrupt code to handle it
  2. Adding some code in a processor’s timer tick interrupt function
  3. Using a separate hardware timer/counter for this purpose and interrupt code to handle it

I assume that user code and perhaps library code would be used.

Aside: I have myself implemented softblinking status LEDS in two AVR based embedded products. This was for my employer Autronica Fire and Security. The code was written in C, both with CSP inspired runtime systems that we wrote ourselves. First, on the BSD-330: BS-100 fire loop to AutroSafe AL_Com protocol converter. And second, on the BNA-180 AutroKeeper: Safe Return to Port (SRtP, Wikipedia here) Dual Safety fire detection system (patented, see here)). I used method 2 and 3 above for these softblinkers. (BSD330, BS100, ALCom, BNA180, AutroKeeper)
(This doesn’t brake my Work disclaimer, here. Plus: I did write several published papers based on these products’ technical solutions, see here).

However, I also assume that for real-time systems with a real-time kernel or OS, the alternatives above would also be used. Seldom would one see the type of solution that I will show in this blog note. I have coded in XC, and this code would run on any xCORE board. (I will stay with the “XMOS pages” reference at the top of this note for all general questions you may have. Plus my Standard disclaimer, it’s also there.)

  1. An XC task uses an XC timer event to output to the LED port. It takes parameters to set the on/off ratio. This is thepwm_for_LED_task
  2. Another XC task takes care of dynamically setting the on/off ratio of the pwm_for_LED_task. This is also done with an XC timer. This is the  softblinker_task. It takes parameters for setting the visible traits of the softblinking
  3. I have also made a task that merges those two tasks, it’s called softblinker_pwm_for_LED_taskpwm_for_LED_task + softblinker_task

There is an MSC (Message sequence chart) later. I will also discuss different configurations of the tasks, plus the pros and cons with a merged task contra the two tasks, especially seen in the light that the xTIMEcomposer mapper tool is actually able to merge the two first tasks under the hood. Aside: I must admit, if I had had anything half as interesting as this to work with at the time I retired, I might have stayed longer. Cause now I have already used this tool for several years.

Fig.2 – My LEDs

Depending on how the tasks are placed on the logical cores the set of two pairs of the above (as seen in the teaser video) takes 1-2 logical cores (that may again be shared with other tasks) and 1-2 xCORE hardware timers (I have 10 timers on the xCORE-200 Explorer board’s processor). The xTIMEcomposer tools have an extraordinary way to minimise the not unlimited hardware resources

About XC

I am not bringing this all up from the bottom, since I have written some notes before:

The code (1)

I have publishedd all of the code (below), but here are the three tasks I mentioned previously. Remember, this is XC, and I run it on the xCORE-200 explorer board. First the PWM task pwm_for_LED_task – containing a crash course of XC.

pwm_for_LED_task

First the interface definition followed by some typedefs:

typedef unsigned                          percentage_t; // [0..100]
typedef enum {scan_none, scan_continuous} scan_type_e;
typedef enum {active_high, active_low}    port_pin_sign_e;

typedef interface pwm_if {
    void set_LED_intensity (const percentage_t percentage);
} pwm_if;

There is no asynchronous pattern in pwm_if. A single, synchronous “call” is defined: set_percentage. Actually it’s a kind of “remote procedure call” that contains synchronisation between the client sender and the server receiver. This is the server. When both are ready, the interface call is done. Below this there are synchronous channels, a state machine or locks. When I use interface the tool selects the best way to do it. We will see this below, where the same code, configured differently, may use zero to 7 chanends. The xCORE tile [0] contains 32 hardware chanends.

Then a typedef and some macros. Observe that several more macros and typedefs will be seen in the final zipped package.

#define XS1_TIMER_MHZ 100U                      // From the system: "timer.h"
#define XS1_TIMER_KHZ ((XS1_TIMER_MHZ) * 1000U) // From the system: "timer.h"
//
#define SOFTBLINK_DEFAULT_ONE_PERCENT_MS  30 // 30 ms goes to 100 in 3.0 seconds, when this is the timing:
#define PWM_ONE_PERCENT_TICS              (100 * XS1_TIMER_MHZ) // 100 us 1% on is a pulse of 100 us every  10 ms 100  Hz works fine, no blinking = perfect softblink
#define PWM_PORT_PIN_SIGN                 active_low
#define SOFTBLINK_DEFAULT_MAX_PERCENTAGE  100
#define SOFTBLINK_DEFAULT_MIN_PERCENTAGE  0

typedef enum {activated, deactivated} port_is_e;

#define ACTIVATE_PORT(sign)   do {outP1 <: (1 xor sign);} while (0) // to activated:   0 = 1 xor 1 = [1 xor active_low]
#define DEACTIVATE_PORT(sign) do {outP1 <: (0 xor sign);} while (0) // to deactivated: 1 = 0 xor 1 = [0 xor active_low]
//

Below is the real Pulse Width Modulation, pwm_for_LED_task task. (If you make your browser’s teks smaller you should be able to bring all the lines into one screen.) Update 18Jul2020: I have a new version of this code that I will show later on. One where 200 ms is 5.00 Hz sharp.

[[combinable]]
void pwm_for_LED_task (
        server pwm_if       if_pwm,
        out buffered port:1 outP1)
{
    // --- pwm_context_t for softblinker_pwm_for_LED_task
    timer           tmr;
    time32_t        timeout;
    port_pin_sign_e port_pin_sign;
    unsigned        pwm_one_percent_ticks;
    time32_t        port_activated_percentage;
    bool            pwm_running;
    port_is_e       port_is;
    // ---

    port_pin_sign = PWM_PORT_PIN_SIGN;

    debug_print ("port_pin_sign %u\n", port_pin_sign);

    pwm_one_percent_ticks     = PWM_ONE_PERCENT_TICS;
    port_activated_percentage = 100;                  // This implies [1], [2] and [3] below
    pwm_running               = false;                // [1] no timerafter (doing_pwn when not 0% or not 100%)
    port_is                   = activated;            // [2] "LED on"
                                                      //
    ACTIVATE_PORT(port_pin_sign);                     // [3]

    while (1) {
        // #pragma ordered // May be used if not [[combinable]] to assure priority of the PWM, if that is wanted
        #pragma xta endpoint "start"
        select {
            case (pwm_running) => tmr when timerafter(timeout) :> void: {
                if (port_is == deactivated) {
                    #pragma xta endpoint "stop"
                    ACTIVATE_PORT(port_pin_sign);
                    timeout += (port_activated_percentage * pwm_one_percent_ticks);
                    port_is  = activated;
                } else {
                    DEACTIVATE_PORT(port_pin_sign);
                    timeout += ((100 - port_activated_percentage) * pwm_one_percent_ticks);
                    port_is  = deactivated;;
                }
            } break;

            case if_pwm.set_LED_intensity (const percentage_t percentage) : {

                port_activated_percentage = percentage;

                if (port_activated_percentage == 100) { // No need to involve any timerafter and get a short off blip
                    pwm_running = false;
                    ACTIVATE_PORT(port_pin_sign);
                } else if (port_activated_percentage == 0) { // No need to involve any timerafter and get a short on blink
                    pwm_running = false;
                    DEACTIVATE_PORT(port_pin_sign);
                } else if (not pwm_running) {
                    pwm_running = true;
                    tmr :> timeout; // immediate timeout
                } else { // pwm_running already
                    // No code
                    // Don't disturb running timerafter, just let it use the new port_activated_percentage when it gets there
                }
            } break;
        }
    }
}

I have highlighted the task name and the two guards (=case) in the conditional choice select. Observe that this is not a switch case! Each case here waits for something to happen. Consider them as names of events, or hooks for interface calls (or channels, not see here) from the outside. Since I have told the mapper (with [[combinable]]) that the code may be shared with other tasks on one of the logical cores, then there are requirements. The last statement of the task must be a while(1) statement containing a single select statement. By “sharing the some logical core” the tool would merge the selects from different tasks, as I have actually done in softblinker_pwm_for_LED_task. Such merging would not be possible if any of the potentially merged task had any common code that were to run after any of the selects. This would be like inserting a pin in an old clock work of cooperating gears. I must do all I need to do in the select cases.

Fig.7 – A double LED tower, housing one LED strip each, in the making

So, the code above is rather clean. But I would also have written it like this without the [[combinable]] – which would have allowed code after select – this is the most general task type (as Go always has and occam had). The code contains a timer tmr. This is a 32 bits hardware timer with a resolution of 10 ns (so I can have timerafter of max about 21 seconds). As we will also see, the tool may select to share HW timers. My examples will show the usage of 1-5 hardware timers.

Confused? Yes, I was too! See CPA 2018 fringe (ref above).

To wait while the LED is on or while it is off, the select case timerafter goes to some percentage and then to reciprocal percentage value. 80% on would in my case be 80% low and then 20% high. I get the logic active level by xor‘ing with a 0 or 1.

Most of the time this task will not take any cycles. It is 100% idle. It will only run after a select case event has been triggered. A timeout or the mentioned interface call.

I selected not to run the timerafter when the LED is 100% on or 100% off. I think I needed to do this to avoid any of the limits containing a blip of a few cycles. 100% is 100%, not 99.99…%. I do this by just setting the LED and then switching the timerafter case off with the guard pwm_running. (Google’s Go language does not have a boolean condition because the designers considered setting the guard to nil was the general case (see example in XCHANs: Notes on a New Channel Type (Appendix)). However, the boolean guard is closer to the origins of both Go and XC: CSP (see Towards a taxonomy(?) of CSP-based systems.)

Architecture

MSC

fig5_209_message_sequence_chart_msc_softblinker_pwm_oyvind_teig
Fig.5 – Message sequence diagram (MSC) of the full architecture (PDF, JPG)

Aside: Fig.5 before Fig.3? That’s because I made them in that sequence! It then follows from that and my narrative tyle (here).

Fig.5 (above) shows how the tasks talk to each other. (The naming is somewhat different from the code shonw here, to be updated).

Taskgraph

fig3_209_xc_taskgraph_pwm_softblinker_xmos_oyvind_teig
Fig.3 – Taskgraph of config 221 (PDF)

Placement on cores

I also have some some button code, and a client that will handle the buttons. At the time of writing these buttons don’t do anything. But I have included them all here to better display the concept of XC. Tasks are communicating over synchronous channels or interfaces, and there is an asynchronous interface pattern that’s also covered. I have to define the roles: client or server. These tasks are shown in the above figure:

1 * softblinker_pwm_button_client_task
3 * button_task
2 * pwm_for_LED_task
2 * softblinker_task

Since I have two LEDs (may also have one) and serve each LED through the two tasks in bullet 1 and 2 above (may also use the task in bullet 3 above).

The very nice thing is that I can select whether I want my code to run on an xCORE logical code alone (with deterministic timing) or if I may share my task’s code with other tasks. I have defined all my softblinking tasks to potentially be allowed to run shared. I do this with the [[combinable]] decoration. (More about this in earlier notes.)

Fig.4 (Press for PDF) (JPG)

This figure shows the 8 configs I have looked further into. The first (221) is already shown. The others are three more with the two tasks for each of two LEDS (above the red, dotted line) (222, 223, 224). Then there are three configs with a merged task for each of the two LEDS, until the lower dotted line (212, 213, 214). At the bottom there is one LED and combined task (113). In the full code, alse (123) is shown. More legend in the PDF:

I figured that it was best to study this large figure off-line, therefore press the figure to see the PDF. I have shown the config code (not from the main.xc source, but from the built xTIMEcomposer .build/main.xi lower part. This is the preprocessor output, actually the one that’s compiled.

I don’t think there are many computer architectures in the world where the same code may be moved around by the system like this, depending on what I as a user, would want. The occam language had a similar configuration language, but it didn’t do the under-the-hood placement. It had less in hardware (both the transputer and the xCORE have the task scheduler in hardware. But much of the functionality has been moved by the architects David May and Ali Dixon et.al from microcode to real hardware. XMOS come after INMOS, you see.) I am a fan. (Disclaimer)

The code (2)

I showed the PWM-only code above. Here is the rest of the code. The softblinker_task and the combined softblinker_pwm_for_LED_task task.

softblinker_task

First the interface, so that we know how to talk with it from the outside:

typedef interface softblinker_if {              //  FULLY
    void set_LED_intensity_range (               // ON  OFF (opposite if port_pin_sign_e set opposite)
            const percentage_t min_percentage,  // 100   0
            const percentage_t man_percentage); // 100   0
    void set_LED_period_ms (const unsigned period_ms); // between two max or two min
} softblinker_if;

There are three interface calls. None of them return any values. No asynchronous session type defined since it’s not needed. Then the code:

[[combinable]]
void softblinker_task (
        client pwm_if         if_pwm,
        server softblinker_if if_softblinker)
{
    // --- softblinker_context_t for softblinker_pwm_for_LED_task
    timer        tmr;
    time32_t     timeout;
    bool         pwm_running;
    unsigned     pwm_one_percent_ticks;
    signed       now_percentage;
    percentage_t max_percentage;
    percentage_t min_percentage;
    signed       inc_percentage;
    // ---

    pwm_running = false;
    pwm_one_percent_ticks = SOFTBLINK_DEFAULT_PERIOD_MS * XS1_TIMER_KHZ;
    now_percentage        = SOFTBLINK_DEFAULT_MAX_PERCENTAGE; // [-1..101]
    max_percentage        = SOFTBLINK_DEFAULT_MAX_PERCENTAGE;
    min_percentage        = SOFTBLINK_DEFAULT_MIN_PERCENTAGE;
    inc_percentage        = (-1); // [-1,+1]

    tmr :> timeout;
    timeout += pwm_one_percent_ticks;

    while (1) {
        select {
            case (pwm_running) => tmr when timerafter(timeout) :> void: {
                bool min_set;
                bool max_set;

                timeout += pwm_one_percent_ticks;

                now_percentage += inc_percentage; // [0..100] but [-1..101] possible if 0 or 100 was just set in set_LED_intensity
                {now_percentage, min_set, max_set} =
                        in_range_signed_min_max_set (now_percentage, min_percentage, max_percentage); // [0..100]

                if ((min_set) or (max_set)) { // Send 100 and 0 only once
                    inc_percentage = (-inc_percentage); // Change sign for next timeout to scan in the other direction
                } else {
                    if_pwm.set_LED_intensity ((percentage_t) now_percentage); // [0..100]
                }

            } break;

            case if_softblinker.set_LED_intensity_range (const percentage_t min_percentage_, const percentage_t max_percentage_): {
                debug_print ("set_LED_intensity %u %u\n", min_percentage_, max_percentage_);

                // Conflict with jumping above or below present range resolved with in_range_signed_min_max_set in timerafter

                                                                 // Also overflow/underflow problems solved there:
                min_percentage = (percentage_t) min_percentage_; //   0 here and min_percentage may be decremented to  -1 in timerafter
                max_percentage = (percentage_t) max_percentage_; // 100 here and max_percentage may be incrmeneted to 101 in timerafter

                if (max_percentage == min_percentage) {
                    pwm_running = false;
                    if_pwm.set_LED_intensity (max_percentage);
                } else if (not pwm_running) {
                    pwm_running = true;
                    tmr :> timeout; // immediate timeout
                } else { // pwm_running already
                    // No code
                    // Don't disturb running timerafter
                }
            } break;

            case if_softblinker.set_LED_period_ms (const unsigned period_ms): {
                debug_print ("set_LED_period_ms %u\n", period_ms/MS_PER_PERCENT_TO_PERIOD_MS_FACTOR);

                pwm_one_percent_ticks = ((period_ms/MS_PER_PERCENT_TO_PERIOD_MS_FACTOR) * XS1_TIMER_KHZ);
            } break;
        }
    }
}

I will not explain this as thoroughly as the pwm_for_LED_task. But it follows the same template.

softblinker_pwm_for_LED_task

If you just about grasped the pwm_for_LED_task and the softblinker_task then this is the merged task. I wrapped the two sets of local variables in the previous tasks into softblinker_context_t and pwm_context_t so it’s easier to understand what’s happening.

With conditional compilation I make sure that either two two tasks above are mapped in, and on the cores – or this one only.

typedef struct pwm_context_t {
    timer           tmr;
    time32_t        timeout;
    port_pin_sign_e port_pin_sign;
    unsigned        pwm_one_percent_ticks;
    time32_t        port_activated_percentage;
    bool            pwm_running;
    port_is_e       port_is;
} pwm_context_t;

typedef struct softblinker_context_t {
    timer        tmr;
    time32_t     timeout;
    bool         pwm_running;
    unsigned     pwm_one_percent_ticks;
    signed       now_percentage;
    percentage_t max_percentage;
    percentage_t min_percentage;
    signed       inc_percentage;
} softblinker_context_t;

I have not changed any of the code, however the interface call if_pwm.set_LED_intensity in softblinker_task has been replaced by a function call set_LED_intensity. A task can of course not send an interface call to itself. It would have deadlocked, since it is synchronous.

void set_LED_intensity (
        pwm_context_t       &pwm_context,
        out buffered port:1 outP1,
        const percentage_t  percentage)
{
    pwm_context.port_activated_percentage = percentage;

    if (pwm_context.port_activated_percentage == 100) { // No need to involve any timerafter and get a short off blip
        pwm_context.pwm_running = false;
        ACTIVATE_PORT(pwm_context.port_pin_sign);
    } else if (pwm_context.port_activated_percentage == 0) { // No need to involve any timerafter and get a short on blink
        pwm_context.pwm_running = false;
        DEACTIVATE_PORT(pwm_context.port_pin_sign);
    } else if (not pwm_context.pwm_running) {
        pwm_context.pwm_running = true;
        pwm_context.tmr :> pwm_context.timeout; // immediate timeout
    } else { // pwm_running already
        // No code
        // Don't disturb running timerafter, just let it use the new port_activated_percentage when it gets there
    }
}

Then the final, merged task. The interface if_softblinker has already been defined, so we know how to talk with it from the outside. The conditional choice select cases now are 4:

[[combinable]]
void softblinker_pwm_for_LED_task (
        server softblinker_if if_softblinker,
        out buffered port:1   outP1)
{

    pwm_context_t         pwm_context;
    softblinker_context_t softblinker_context;

    softblinker_context.pwm_running           = false;
    softblinker_context.pwm_one_percent_ticks = SOFTBLINK_DEFAULT_PERIOD_MS * XS1_TIMER_KHZ;
    softblinker_context.now_percentage        = SOFTBLINK_DEFAULT_MAX_PERCENTAGE; // [-1..101]
    softblinker_context.max_percentage        = SOFTBLINK_DEFAULT_MAX_PERCENTAGE;
    softblinker_context.min_percentage        = SOFTBLINK_DEFAULT_MIN_PERCENTAGE;
    softblinker_context.inc_percentage        = (-1); // [-1,+1]

    pwm_context.port_pin_sign             = PWM_PORT_PIN_SIGN;
    pwm_context.pwm_one_percent_ticks     = PWM_ONE_PERCENT_TICS; // 10 uS. So 99% means 990 us activated and 10 us deactivated
    pwm_context.port_activated_percentage = 100;                  // This implies [1], [2] and [3] below
    pwm_context.pwm_running               = false;                // [1] no timerafter (doing_pwn when not 0% or not 100%)
    pwm_context.port_is                   = activated;            // [2] "LED on"
                                                                  //
    ACTIVATE_PORT(pwm_context.port_pin_sign);                     // [3]

    softblinker_context.tmr :> softblinker_context.timeout;
    softblinker_context.timeout += softblinker_context.pwm_one_percent_ticks;

    while (1) {
        // #pragma ordered // May be used if not [[combinable]] to assure priority of the PWM, if that is wanted
        select {
            case (pwm_context.pwm_running) => pwm_context.tmr when timerafter(pwm_context.timeout) :> void: {
                if (pwm_context.port_is == deactivated) {
                    ACTIVATE_PORT(pwm_context.port_pin_sign);
                    pwm_context.timeout += (pwm_context.port_activated_percentage * pwm_context.pwm_one_percent_ticks);
                    pwm_context.port_is  = activated;
                } else {
                    DEACTIVATE_PORT(pwm_context.port_pin_sign);
                    pwm_context.timeout += ((100 - pwm_context.port_activated_percentage) * pwm_context.pwm_one_percent_ticks);
                    pwm_context.port_is  = deactivated;;
                }
            } break;

            case (softblinker_context.pwm_running) => softblinker_context.tmr when timerafter(softblinker_context.timeout) :> void: {
                bool min_set;
                bool max_set;

                softblinker_context.timeout += softblinker_context.pwm_one_percent_ticks;

                softblinker_context.now_percentage += softblinker_context.inc_percentage; // [0..100] but [-1..101] possible if 0 or 100 was just set in set_LED_intensity
                {softblinker_context.now_percentage, min_set, max_set} =
                        in_range_signed_min_max_set (softblinker_context.now_percentage, softblinker_context.min_percentage, softblinker_context.max_percentage); // [0..100]

                if ((min_set) or (max_set)) { // Send 100 and 0 only once
                    softblinker_context.inc_percentage = (-softblinker_context.inc_percentage); // Change sign for next timeout to scan in the other direction
                } else {
                    set_LED_intensity (pwm_context, outP1, (percentage_t) softblinker_context.now_percentage); // [0..100]
                }

            } break;

            case if_softblinker.set_LED_intensity_range (const percentage_t min_percentage_, const percentage_t max_percentage_): {
                debug_print ("set_LED_intensity %u %u\n", min_percentage_, max_percentage_);

                // Conflict with jumping above or below present range resolved with in_range_signed_min_max_set in timerafter

                                                                                     // Also overflow/underflow problems solved there:
                softblinker_context.min_percentage = (percentage_t) min_percentage_; //   0 here and min_percentage may be decremented to  -1 in timerafter
                softblinker_context.max_percentage = (percentage_t) max_percentage_; // 100 here and max_percentage may be incrmeneted to 101 in timerafter

                if (softblinker_context.max_percentage == softblinker_context.min_percentage) {
                    softblinker_context.pwm_running = false;
                    set_LED_intensity (pwm_context, outP1, softblinker_context.max_percentage);
                    // No code, timerafter will do it
                } else if (not softblinker_context.pwm_running) {
                    softblinker_context.pwm_running = true;
                    softblinker_context.tmr :> softblinker_context.timeout; // immediate timeout
                } else { // pwm_running already
                    // No code
                    // Don't disturb running timerafter
                }
            } break;

            case if_softblinker.set_LED_period_ms (const unsigned period_ms): {
                debug_print ("set_LED_period_ms %u\n", period_ms/MS_PER_PERCENT_TO_PERIOD_MS_FACTOR);

                softblinker_context.pwm_one_percent_ticks = ((period_ms/MS_PER_PERCENT_TO_PERIOD_MS_FACTOR) * XS1_TIMER_KHZ);
            } break;
        }
    }
}

The code (download)

The XC code is at https://www.teigfam.net/oyvind/blog_notes/209/code/_softblinker_pwm.zip. However, I have obfuscated the url and added an underscore just before the .zip. You should remove the underscore by hand.

It contains the XMOS xTIMEcomposer project _Softblinker_PWM. No password. It also contains the full history with the git versioning. The system is built with XMOS xTIMEcomposer (14.4.1).

The source files listed:

_Softblinker_PWM.h
_Softblinker_PWM.xc
_globals.h
_texts_and_constants.h
_version.h
button_press.h
button_press.xc
main.xc
maths.h
maths.xc
pwm_softblinker.h
pwm_softblinker.xc

For XC code that really does something with the buttons (and a display), I have some here: My processor-to-analogue audio equaliser notes @ The code (download).

Next

I plan to handle the buttons with functionality that would change the parameters of the softblinking.

External connections

Even if the xCORE-200 explorerKIT contains two user buttons (two inputs on 4-bits port XS1_PORT_4E) and one green LED and one RGB LED (1+3 outputs, all on 4-bits port XS1_PORT_4F) I chose to solder my own. I wanted one more button and two LEDs that felt softer to look at, and I wanted them all to be connected to 1-bit ports so that I could assign them to separate tasks. In XC it is not allowed to send the same port to more than one task. So much safer than a port just being an address. I must admit, I have seen some of that. But then, XC knows what a port is (next chapter).

fig6_209_external_connections_oyvind_teig_

Fig.6 – External connections of two LEDs and three buttons (PDF, JPG)

xCORE extended port functions

This may turn out to be a large chapter, later on. My code in pwm_for_LED_task only uses the 1-bit port pin naïvely. It either outputs low or high. And it’s driven by an XC timer.

It’s not driven by a clock connected to the port pin, which is possible on the xCORE.  Any port on these devices may be connected to one of the (six) clock blocks. By default all pins are connected to clock block 0. Each of the ports have a shift register and a 16-bits counter. Ports are 1, 4, 8, 16 or 32 bits wide. In XC one can wait for a pin, or make it behave like a clock signal. A port output may be directly timed and even timestamped, so that one output statement may be time-wise connected to another, disregarding how many XC code cycles were used for the XC code in between. If this XC code is a Normal task (not using [[combinable]] or [[distributable]], ie. running on a logical core by itself, see the CPA 2018 fringe note reference (above)) then the timing of the port (logical core’s ability to run the cycles) is deterministic. There is no interrupt that clutters it all, because there is no such thing as an interrupt. The deterministic system of the xCORE takes care of this functionality. XMOS has said that the XCORE architecture is between ASIC and standard microprocessors. It certainly is. Each port also has a buffer of one position. Ports may also be synchronised to each other.

My PWM is rather nice, I think. But it would have an upper MHz limit. I assume that by building a PWM using more of the port infrastructure might make it spin faster. But then, faster than almost as fast as possible? Well, it might at least serve as an example of port usage.

I will query about this at the XCore Exchange forum.

Timing analysis

The XCC compiler is able to validate timing requirements during compilation. Not is my first time to test this out. I found 141:[21] (XMOS Timing Analyzer Manual) and 142:[22] (AN00192: Getting Started with Timing Analysis in xTIMEcomposer Studio) to help me start. It wasn’t too hard to get going.

I wanted to find the guaranteed max loop time of the select in pwm_for_LED_task. I followed the recipes and set start point and stop point at line 1 and 6 (my names). I compiled as config (225) (above)). The tool inserted two #pragma for me. Nice, since they can’t be on any line. The tool showed which ones. I think it’s basically on I/O lines.

    while (1) {
        #pragma xta endpoint "start"
        select {
            case (pwm_running) => tmr when timerafter(timeout) :> void: {
                if (port_is == deactivated) {
                    #pragma xta endpoint "stop"
                    ACTIVATE_PORT(port_pin_sign);
                    timeout += (port_activated_percentage * pwm_one_percent_ticks);
                    port_is  = activated;
                } else {
                    DEACTIVATE_PORT(port_pin_sign);
                    timeout += ((100 - port_activated_percentage) * pwm_one_percent_ticks);
                    port_is  = deactivated;;
                }
            } break;

First I had it analyze the timing from start to stop, but it gave little meaning. So I had it export to an xta script file: my_script.xta. 1.0 µs required for the loop time should be ok, just for a start. My PWM_ONE_PERCENT_TICS is 100 µs, a light year away.

analyze loop start
set required - 1.0 us

I compiled with configuration (223). This was rather essential, it has to know how the code runs. I don’t know the details about that yet. So very nice to see the result presented. I just love this:

..
xta: .././2020_07_03_B_script.xta:7: warning: route(0)     Pass with 14 unknowns, Num Paths: 12, Slack: 760.0 ns, Required: 1.0 us, Worst: 240.0 ns, Min Core Frequency: 120 MHz
..

I can also xscope the timing inside xTIMEcomposer if I use the simulator. I haven’t tried that on this example yet.

Summary

This became a rather code centric note. I have shown a function that softblinks a single LED that is instantiated as two tasks when two LEDS need to softblink independently. I have shown that a LED may get a new on/off-ratio value, typically (linearly) every 30 ms, so that it softblinks from zero to max, in percentage steps, in 3 seconds.

I have not shown the SW external to this (but the full code may be downloaded), except fragments from main.xc, where the task structure is configured in different ways. What follows from this has also been shown, although rather superficially.

It has also been interesting to see how two easy to read tasks have been merged into one not so easy to read single task. The tool’s configuration has helped us understand that in some cases using this merged task is not optimal. But sometimes it is optimal, though. Especially since the tool, in some cases, builds a merged task (from [[combinable]] tasks) under the hood. The simplicity of two tasks with some communication between them (coupling) is valued against the increased inner complexity (cohesion) of the combined task.

Not dimming in general

The purpose of this note was to show some idiomatic “pure task-type” pulse width modulated (PWM) dimming – for anything that is dimmable that way. Like LEDs. But dimming in general is not covered.

I remember that I used to build 230V AC dimming boxes when I was young, implemented with a TRIAC, a potmeter and an RC network, to control the phase for the controlling gate. This was for incandescent light bulbs, something we used to use for lighting. Not so young I learning how the company I worked for, Autronica, before my time, had used transductors to control the lighting of the largest cinema in Norway (Colosseum kino in Oslo, after the fire in 1963). I haven’t studied how modern, dimmable LED lamps are dimmed. But I just assume that there is some kind of PWM in charge. For sure, my variac (variable transformer, a type of autotransformer) is not of much use. Maybe this could be a follow-up from this note?

User forums

XCore Exchange forum

The looks of code. The understanding

Lastly an aside:

        ,,, 
       (o o)   
---oOO--(_)--OOo---
I started this with line editors. A full screen of code could be seen (listed) only on the 80 char wide screen. Before I left the teletype I had to save to punched paper tape. After some years I discovered that the screens had gotten wider. And do I miss a real folding editor?
:

Yes: Wishes for a folding editor. With it I could bring any number of lines into a single screen. Hide any number of indents away from scope. With three arrows down and passing of a fold the line number could go from 42 to 273. And a fold was a native citizen of the editor. Not just hiding as the Eclipse does on the xTIMEcomposer. It’s nice, but not near winf32 that I used on Windows until I retired. But at my home office I have macOS (with Windows and Parallels Desktop, but winf32 became too awkward across the operating systems. But I haven’t given up on it, have I..) And yes, I have read my Code Complete by Steve McConnell.

References

Wiki-refs: Pulse-width modulationSoft-blinking LED indicator. Transductor. TRIAC

Much referencing in the About XC chapter above.

Numbered refs

  1. Make: Electronics. Second ed. By Charles Platt at Maker Media. Tenth release, 7Sep2018, ISBN 978-1-680-45026-2. See https://www.makershed.com/products/make-electronics-2ed

Leave a Reply

Your email address will not be published. Required fields are marked *

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