ESP8266 MultiPWMs ver.0.4

No Comments

按,前一版信旦地說已完善了,沒想到才過一天就出了 0.4 版,並且也是埋頭苦熬了一整天才硬擠了出來。筆者真的意想不到,自己認為的終版(前版),竟與現實終版差距這麼遙遠!
本版修正一些東西,並讓 sync functions 有像點樣了/更接近實用性;原先的呢?其實也不賴,程式內詳說明。
但本版仍待測試,及,筆者已不敢說這是終版了,但真的希望是的。故近期將作定案定論。

// Esp8266MultiPwms ver.0.4
// https://waterfalls.ddns.net
// by Ken Woo
// 2020.12.06




/*
ver.0.1    initial release
ver.0.2    a. fixed many bugs
           b. add sync function
ver.0.3    a. add speedup
           b. change class name from cMultiPwm to cMultiPwms
           c. add constructors
           d. add pause_all method
           e. fix destructor bug
           f. modify the sync function
ver.0.4    a. fix some bugs
           b. mainly fulfill sync functions
*/


// PWM class: using MultiTimersV0.4 to generate four 192us timers which are sequentially sync-offset by 48us to one another.
// which are approaching to and are regarded as 200us/50us. Such a facility forms a single 50us timer with 4 sequential ISRs.
// PWM waveforms whereas ought to be transition of the processes will be evenly settled in the 4 ISRs,
// unless there are specified ones to be explicitly offset or synced to another, which determines the position.
// note that explicitly sync offset is restricted only to same period PWM.
// duty cycle 70% means headed high 70% first then low, inverted duty cycle 70% means headed low 70% then high.
// sync or offset counts for headed beginning.
// so, PWM periods are restricted to multiples of 200us(and at least 200us, at most 0.8s; in order to have fixed positions),
// in addition, the duty cycle step must be multiple of 50us, is restricted too.
// for example, 2.2ms PWM with 45 steps is allowed; 2200/200=11 is integer, 2200/44/50=1 is integer,
// step0 0%, step1 <=2.27%(100%/44), step2(<=100%*2/44)..., step44(>100%*43/44) 100%.
// or with 12 steps, each step is 200us, and the like.
// the highest one is 200us/5kHz with 5 steps(0%, 0%< <=25%, 25%< <=50%, 50%< <=75%, 75%< <=100%).
// the number of PWMs depends on bits of id; you can rewrite it for unlimited PWMs theoretically.

#ifndef _c_MULTI_PWMS_H_
    #define _c_MULTI_PWMS_H_

    #include"Esp8266HwSwTimers.h"

#endif // _c_MULTI_PWMS_H_


#define C_MULTI_PWMS_DEBUG 1

#if C_MULTI_PWMS_DEBUG
    unsigned z1, z2, z3, z4;
#endif




class cMultiPwms{

    typedef struct sPwmObj{
        unsigned counter:       12; // counter, reload to it.
        unsigned rsv1_not_use:  20; // reserved 1. it should be 0 and do not use.

        unsigned rsv2:          20; // reserved 2.
        unsigned reload_high:   12; // reload count for high level. 200us is one-round, 200x4096=0.8s.

        unsigned freeze:        1;  // will not access this obj.
        unsigned stopit:        1;  // will delete this obj.
        unsigned accepted:      1;  // this bit will be set if isr accepted freeze or stopit.
        unsigned is_fixed_pos:  1;  // is it a fixed position by a synced PWM; the whom synced must fixed too.
        unsigned is_high_level: 1;  // the current level counted is high? it will be toggling.
        unsigned is_inverted:   1;  // we use it at the final waveform, so it affects nothing.
        unsigned stop_h_or_l:   1;  // high or low when stopped.
        unsigned tr_high_pos:   2;  // the position at timer ISR[] for transiting to high level, it exists with low-counting.
        unsigned tr_low_pos:    2;  // the position at timer ISR[] for transiting to low level, coexists with high-counting.
        unsigned gpio:          4;  // the gpio pin number
        unsigned id:            5;  // id in order to search. for unlimited new/delete, must maintain it. but i prefer not.
        unsigned reload_low:    12; // reload count for low level. 200us is one-round, 200x4096=0.8s.
        // note the relationship of is_high_level, tr_high_pos, tr_low_pos.
        // is_high_level indicats currently is high counting or low. tr_high_pos: currently is low level counting,
        // it is going to transit to high level, so it is positioned at tr_high_pos, and vice versa.
        // so, the duty-cycle count allocates on reload_high, and when which positioning at tr_high_pos, is active, it is counting low,
        // when count-up, loading the reload_high to counter, transits the level from low to high, and finally hands over the control
        // to tr_low_pos for it active. after a while the count is up again, it is responsible for transiting from high to low,
        // then reload reload_low, and then changing position to tr_high_pos again alternatively.

        sPwmObj(){
            (reinterpret_cast<unsigned*>(this))[0]=0;
            (reinterpret_cast<unsigned*>(this))[1]=0;
            (reinterpret_cast<unsigned*>(this))[2]=0;
        }
    } sPwmObj;


    typedef unsigned (*xptr_sPwmObj)[3];

    #define OP_RLD_HIGH(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[0]=\
        (*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[1]>>20)

    #define OP_RLD_LOW(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[0]=\
        (*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]>>20)

    #define OP_SET_LVL_HIGH(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]|=0x10)

    #define OP_SET_LVL_LOW(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]&=0xFFFFFFEF)

    #define OP_COUNTER_DEC(sNode_ex) (--(*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[0])

    #define VAL_COUNTER(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[0])

    #define OP_GET_TR_HIGH_POS(sNode_ex) (((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]>>7)&0x3)

    #define OP_GET_TR_LOW_POS(sNode_ex) (((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]>>9)&0x3)

    #define OP_GET_GPIO(sNode_ex) (((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]>>11)&0xF)

    #define OP_TEST_STOP_COND(sNode_ex) !((((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]>>2)^\
                                            (*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2])&0x10)

    #define IS_NEED_LOOKINTO(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]&0xF)

    #define IS_HIGH_LEVEL(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]&0x10)

    #define IS_INVERTED(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]&0x20)


    typedef struct sNode{
        sPwmObj data;
        sNode *next;

        sNode(): next(0){};
    } sNode;


    typedef struct sGcNode{ // exclusively used for stall objects.
        unsigned tmp;
        sNode *obj;
        unsigned mass; //// this is a bad trick need improvement; identical fields are must.
        sGcNode *next;

        sGcNode(): next(0){};
    } sGcNode;


    IRAM_ATTR static sNode** node_get_conn_pt(sNode **head){ // get the tail-next address so we can store data in it.
        sNode *a=*head;
        while (a){
            head=&(a->next);
            a=a->next;
        }
        return head;
    };


    int node_get_length(sNode *head){ // evaluate the linkedlist length
        int i=0;
        for (; head; i++, head=head->next);
        return i;
    };


    void node_add(sNode **head, sNode *a){ // attach a node to the tail
        while (*head) head=&((*head)->next);
        *head=a;
    };


    void node_delete(sNode *head){ // delete entire linkedlist
        for (sNode *i; head; i=head->next, freeMemory(head), head=i);
    };


    IRAM_ATTR static sNode** node_pwm_dec_cnt(sNode **head){ // possibly process several times by caller.
        sNode *a=*head;
        for (; a && VAL_COUNTER(a->data)>1; OP_COUNTER_DEC(a->data), head=&(a->next), a=a->next); // 1 is minimum.
        if (a) return head;
        return 0;
    };


    sNode** node_pwm_find_parent(unsigned id, unsigned pos){ // find the parent of the node having this id.
        sNode **i;
        for (i=&(pwms[pos]); *i && ((*i)->data).id!=id; i=&((*i)->next));
        if (!*i) return 0;
        return i;
    };


    IRAM_ATTR static void node_pwm_move_to(sNode **src, unsigned new_pos){ // move this src node to attach to pwms[new_pos].
        sNode *obj=*src;
        *src=obj->next;
        *node_get_conn_pt(&(pwms[new_pos]))=obj;
        obj->next=0;
        /// priorly excluded.
        /// sNode **a=node_get_conn_pt(&(pwms[new_pos]));
        /// if (&((*src)->next)!=&((*a)->next)){*a=*src;*src=(*src)->next;(*a)->next=0;}
    };


    unsigned wellAdd(sNode *a){ // add to proper ISR position, return the position.
        unsigned i=0, j=0, pos=0, min=-1;
        for (; i<4; i++){
            if (pwms[i]){
                if ((j=node_get_length(pwms[i]))<min){
                    pos=i;
                    min=j;
                }
            }
            else {pos=i; break;} // empty
        }
        node_add(&(pwms[pos]), a);
        return pos;
    };


    void configTimer(){
        timers[0].setTimer(192, cMultiPwms::timerISR0);
        timers[1].setTimer(192, cMultiPwms::timerISR1);
        timers[2].setTimer(192, cMultiPwms::timerISR2);
        timers[3].setTimer(192, cMultiPwms::timerISR3);
        timers[1].ForceHaltForSync(timers[0], 48);
        timers[2].ForceHaltForSync(timers[1], 48);
        timers[3].ForceHaltForSync(timers[2], 48);
    };


    void* getMemory(int obj_size){ // use sizeof(unsigned) and alignment; little endian.
        int a=(obj_size+sizeof(unsigned)-1)/sizeof(unsigned);
        void *b=malloc(sizeof(unsigned)*a);
        if (b && (unsigned(b)/sizeof(unsigned)*sizeof(unsigned)==unsigned(b))){
            for (; a--; ((unsigned*)b)[a]=0);
            return b;
        }
        free(b);
        return 0;
    };

    void freeMemory(void *b){free(b);};


    bool ack(){return (owner->data).accepted;}; // used for waiting to acked

    void nack(){
        (owner->data).freeze=0;
        // no need to delay
        (owner->data).accepted=0;
    };


    bool resume(){/*if (!uid) return false; */nack(); return true;}; // rather failed than blocked.
    bool pause(){if (!uid) return false; (owner->data).freeze=1; return true;}; // nonblocking


    static cHwTimer timers[4];
    static sNode* pwms[4];
    static unsigned pause_all; // to avoid conflict
    static unsigned gid;
    static sGcNode *memo_park; // for stall objs collection.

    sNode *owner;
    unsigned period;
    unsigned steps; // 0% is not counted. however we need it be a step.
    unsigned uid;


    public:


    cMultiPwms(): owner(0), uid(0){};

    ~cMultiPwms(){
        if (!owner) return;

        pause_all=1;
        if (timers[0].isActive()) delayMicroseconds(75); // wait for isr entered.

        sNode **n;
        int time_cnt=10;
        while (pause_all<2 && time_cnt--) delayMicroseconds(25); // no patient to wait
        // if memory leak, (the below not found, isr is stopped or moving object), however not much to waste.

        n=node_pwm_find_parent(uid, OP_GET_TR_HIGH_POS(*owner));
        if (!n) n=node_pwm_find_parent(uid, OP_GET_TR_LOW_POS(*owner));
        if (!n){

        #if C_MULTI_PWMS_DEBUG
                printf("\r\nx[MEM]x\r\n");
        #endif // C_MULTI_PWMS_DEBUG

                pause_all=0;
                return;
        }

        *n=(*n)->next;
        pause_all=0;
        freeMemory(owner);
    };

    cMultiPwms(unsigned gpio, unsigned period_us, unsigned set_steps,\
        bool inverted=false, bool stop_high=false): owner(0), uid(0){ // if a step is 1%, use 100 steps, not 101 steps.
        configPwm(gpio, period_us, set_steps, inverted, stop_high);
    };

    cMultiPwms(const cMultiPwms &a): owner(0), uid(0){
        // default is GPIO0. the gpio is not copied and must assigned it later.
        // also there is not sync for the new object to the existing object.

        if (!a.uid || !a.owner) return;

        configPwm(0, a.period, a.steps, ((a.owner)->data).is_inverted, ((a.owner)->data).stop_h_or_l);
    };

    const cMultiPwms& operator=(const cMultiPwms &b){ // it is the same as copy-constructor, for not initial yet.
        if (!b.uid || !b.owner || this->uid || this->owner) return *this;
        configPwm(0, b.period, b.steps, ((b.owner)->data).is_inverted, ((b.owner)->data).stop_h_or_l);
        return *this;
    };


    bool configPwm(unsigned gpio, unsigned period_us, unsigned set_steps,\
        bool inverted=false, bool stop_high=false){ // if a step is 1%, use 100 steps, not 101 steps.

        if (period_us%200 || !set_steps || period_us%set_steps || period_us/set_steps%50) return false;
        if (uid || owner) return false;

        owner=(sNode*)getMemory(sizeof(sNode));
        if (!owner) return false;

        period=period_us;
        steps=set_steps;
        uid=++gid;
        if (gid>=31){ //// the last one. not fault but is going to fault.
            printf("\r\ndepleted\r\n");
            freeMemory(owner);
            uid=0;
            owner=0;
            return false;
        }

        (owner->data).id=uid;
        (owner->data).is_inverted=!!inverted;
        (owner->data).is_high_level=0; // at the outset, it is going to high, so it is under low before beginning.
        (owner->data).stop_h_or_l=!!stop_high;
        setGpio(gpio); //// (owner->data).gpio=gpio;
        (owner->data).freeze=1; // paused
        (owner->data).tr_high_pos=wellAdd(owner); // at the outset, raise high then immed change pos to tr_low_pos.
        return true;
    };


    bool setGpio(unsigned gpio){
        if (!uid) return false;
        (this->owner->data).gpio=gpio;
        return true;
    }


    void setInvert(bool invert_or_not){
        if (!uid) return;
        (owner->data).is_inverted=!!invert_or_not;
    };

    bool setStallLevel(bool high_or_low){ // note that it will return the prior state
        if (!uid) return false;
        bool tmp=!!((owner->data).stop_h_or_l);
        (owner->data).stop_h_or_l=!!high_or_low;
        return tmp;
    };

    void setDC(float percentage, unsigned set_by_steps=0){ // will by steps if it is nonzero
        if (!uid) return;
        if (!set_by_steps){
            (percentage*=steps)/=100.0f;
            if ((set_by_steps=unsigned(percentage))<percentage) set_by_steps++;
        }
        if (set_by_steps>steps) set_by_steps=steps;

        set_by_steps*=period/steps/50; // times the magnifier to get the native count

        // important note that the counting for high, time-up is decided at tr_low_pos, but time starts at tr_high_pos.
        // different position means different time, that is the problem.
        // integer quotient, 0 remainder, means integer rounds starts from pos and ends at pos.
        // remainder if any means the last round that is not a complete round and ends at tr_pos other than pos.
        // so, quotient+1 would be the final count.
        // however one more to consider, if remainder is 0, expected time-up and count-up are exactly matched at the same pos,
        // but what if in such case, we take other position for end? yes, beyond or behind the expected time when count is up.
        // so how to do right the code here(tr_high_pos and tr_low_pos are diff pos, could the COUNT handle it all correctly)?
        // that is right, nothing to do about this problem since tr_low_pos of its position had taken care, think about it.
        // yet the other problem to think about, the boundary condition. when counter becomes 1 which is minimum because
        // we either add 1 if remainder or quotient is nonzero/because 0% is excluded, is for tr_low_pos to make decision,
        // hence the 1 means time is up; the 1 represents time spent from tr_high_pos to tr_low_pos under a round(equal if same pos).
        // however what about the time span for low level at the condition of high level counts is only 1 and high-counts > low-counts?
        // it could happen for example period=200us, step=4, dc=25~75%/count-always-1, low should be 0 by calculation.
        // as a whole if calculation lead to 0 for low level, our decision making uses >1 in node_pwm_dec_cnt could cover each condition,
        // that is, time spent is correct.

        WaitForPause();

        if (!set_by_steps){ // 0%, it paused for isr ignores.
            digitalWrite((owner->data).gpio, (owner->data).is_inverted);
            return;
        }
        else if (set_by_steps==period/50){ // 100%, it paused for isr ignores.
            digitalWrite((owner->data).gpio, !(owner->data).is_inverted);
            return;
        }

        int pos=(owner->data).tr_high_pos;
        int tr_pos=set_by_steps%4;
        int count=set_by_steps/4;

        if (!tr_pos) tr_pos=pos;
        else {
            count++;
            tr_pos+=pos;
            if (tr_pos>3) tr_pos-=4;
        }

        (owner->data).tr_low_pos=tr_pos; // this is the "duty-cycle count" cast into "tr_low_pos".
        (owner->data).reload_high=count;
        ////(owner->data).reload_low=period/200-count;
        (owner->data).reload_low=(period/50-set_by_steps+3)/4;
        (owner->data).counter=1; // force to reload for starting.

        Resume();

        if (!timers[0].isActive()) configTimer();
    };


    void Pause(){this->pause();}; // nonblocking

    bool WaitForPause(){ // wait at most 0.8s.
                         // suitable for timer is running or not.
                         // however, in isr will run out counter then pause.
                         // so, not the isr load more is wait here more.
        if (this->pause())
            for (int i=0; !ack() && i<1100; i++){ // 0.8s
                if ((owner->data).counter>10) delayMicroseconds(750);
                else delayMicroseconds(25);
            }
        return ack();
    };

    void Resume(){this->resume();};


    static void traverseLinkedList(){
#if C_MULTI_PWMS_DEBUG
        int c=0;
        for (int i=0; i<4; i++){
            sNode *a=pwms[i];
            while (a){
                printf("id, gpio, level, tr_high_pos, tr_low_pos, rld_high, rld_low, counter,\r\n\
                    (%d, %d, %d, % 2d, % 2d, %d, %d, %d)\r\n",\
                    (*a).data.id,\
                    (*a).data.gpio,\
                    (*a).data.is_high_level,\
                    (*a).data.tr_high_pos,\
                    (*a).data.tr_low_pos,\
                    (*a).data.reload_high,\
                    (*a).data.reload_low,\
                    (*a).data.counter);
                a=a->next;

                c++;
            }
            printf("(%d) ----\r\n", i);
        }
        printf("The 4 ISRs (%d nodes in it) each costs us time (%d %d %d %d)\r\n\r\n\r\n", c, z1, z2, z3, z4);
#endif // C_MULTI_PWMS_DEBUG
    };


    void Sync(cMultiPwms &base, unsigned offset_us){
        // the most important note that, this is a relatively sync, which means the two sync here,
        // will deviate from other waveforms since we use stop here.
        // if more than 2 pwms need to sync, instead to use SyncStart, SyncNext, SyncEnd of a static process;
        // also they not only deviate from the others but also stalled till call SyncEnd().
        //
        // think about we want to synchronize to the base, head(h-level) to head(h-level) or an offset of head to head.
        // what if an offset of l-level-head? my conclusion is not necessary because low-level will change by dc,
        // we can set inverse-dc then invert for the purpose after synced.

        if (!uid || !base.uid || (uid==base.uid) || !owner || !base.owner) return;
        if ((period!=base.period) || (period<=offset_us) || offset_us%50) return;

        offset_us/=50; // note that one count is 50us for native count.

        bool tmp_base_lvl=base.setStallLevel(0); // store the original state and set to stall at low
        bool tmp_this_lvl=this->setStallLevel(0); // store the original state and set to stall at low

        // ---- stall 2 nodes in isr
        base.WaitForPause();
        this->WaitForPause();

        pause_all=1;
        if (timers[0].isActive()) delayMicroseconds(75); // wait for isr entered.
        int time_cnt=10;
        while (pause_all<2 && time_cnt--) delayMicroseconds(25); // no patient to wait
        // ---- stall entire isr

        unsigned a=((base.owner)->data).tr_high_pos;
        unsigned b=(owner->data).tr_high_pos=(a+offset_us)%4;
        (owner->data).tr_low_pos=(((base.owner)->data).tr_low_pos+offset_us)%4;
        (owner->data).counter=((base.owner)->data).counter+offset_us/4; // counter beyond and behind and mid?

        // fix the problem that isr position could affect counter value.
        unsigned c=pause_all-2;
        if ((a<b && c<=b && c>a) || (b<a && c<=b) || (b<a && c>a)) (owner->data).counter++;

        base.setStallLevel(tmp_base_lvl); // restore
        this->setStallLevel(tmp_this_lvl); // restore

        // ---- resume nodes
        base.Resume();
        this->Resume();

        switch (pause_all){
            case 2: pause_all=6; break;
            case 3: pause_all=7; break;
            case 4: pause_all=8; break;
            case 5: pause_all=9; break;
        }
        time_cnt=10;
        while (pause_all && time_cnt--) delayMicroseconds(25); // no patient to wait
        pause_all=0;
        // ---- resume entire isr
    };


    static bool SyncStart(cMultiPwms &base){ // note that all the participator will stalled before end
        if (!base.uid || !base.owner || memo_park) return false;
        memo_park=(sGcNode*)base.getMemory(sizeof(sGcNode));
        if (!memo_park) return false;

        memo_park->tmp=!!base.setStallLevel(0);
        memo_park->obj=base.owner;
        memo_park->next=0;
        // the first node of its data/tmp stores stop_h_or_l of each node, so, at most 32 nodes synced,
        // and the other nodes of its tmp stores offset,
        // and the node pointer stores obj address.

        base.WaitForPause();
        return true;
    };


    static bool SyncNext(cMultiPwms &follower, unsigned offset_us){
        if (!follower.uid || !follower.owner) return false;
        if ((follower.period<=offset_us) || offset_us%50) return false;
        if (!memo_park) return false;

        sGcNode *a=(sGcNode*)follower.getMemory(sizeof(sGcNode));
        if (!a) return false;

        memo_park->tmp<<=1;
        memo_park->tmp|=!!follower.setStallLevel(0);
        a->tmp=offset_us/50;
        a->obj=follower.owner;
        a->next=0;

        follower.node_add((sNode**)(&memo_park), (sNode*)(a));
        follower.WaitForPause();
        return true;
    };


    static bool SyncEnd(cMultiPwms &base){ // must specify the base actually for me to use non-static funcs.
        if (!base.uid || !base.owner || !memo_park) return false;

        int len=base.node_get_length((sNode*)(memo_park));

        if (memo_park->obj!=base.owner) return false; // still chances
        if (len==1){ // no chance
            (memo_park->obj->data).stop_h_or_l=(memo_park->tmp&0x1); // restore
            (memo_park->obj->data).freeze=0;
            (memo_park->obj->data).accepted=0;
            base.node_delete((sNode*)(memo_park));
            memo_park=0;
            return false;
        }


        // ---- stall entire isr
        pause_all=1;
        if (timers[0].isActive()) delayMicroseconds(75); // wait for isr entered.
        int time_cnt=10;
        while (pause_all<2 && time_cnt--) delayMicroseconds(25); // no patient to wait
        // ---- stall entire isr


        unsigned a=(memo_park->obj->data).tr_high_pos;
        unsigned d=(memo_park->obj->data).tr_low_pos;
        unsigned e=(memo_park->obj->data).counter;

        (memo_park->obj->data).stop_h_or_l=((memo_park->tmp>>(len-1))&0x1); // restore first first
        (memo_park->obj->data).freeze=0;
        (memo_park->obj->data).accepted=0;

        for (sGcNode *iter=memo_park->next; iter; iter=iter->next, len--){
            unsigned b=(iter->obj->data).tr_high_pos=(a+iter->tmp)%4;
            (iter->obj->data).tr_low_pos=(d+iter->tmp)%4;
            (iter->obj->data).counter=e+iter->tmp/4;

            // fix the problem that isr position could affect counter value.
            unsigned c=pause_all-2;
            if ((a<b && c<=b && c>a) || (b<a && c<=b) || (b<a && c>a)) (iter->obj->data).counter++;
            (iter->obj->data).stop_h_or_l=((memo_park->tmp>>(len-2))&0x1); // restore
            (iter->obj->data).freeze=0;
            (iter->obj->data).accepted=0;
        }


        // ---- resume entire isr
        switch (pause_all){
            case 2: pause_all=6; break;
            case 3: pause_all=7; break;
            case 4: pause_all=8; break;
            case 5: pause_all=9; break;
        }
        time_cnt=10;
        while (pause_all && time_cnt--) delayMicroseconds(25); // no patient to wait
        pause_all=0;
        // ---- resume entire isr


        base.node_delete((sNode*)(memo_park));
        memo_park=0;
        return true;
    };


    IRAM_ATTR static void trigger(sNode **u){
        while (u=node_pwm_dec_cnt(u)){
            if (IS_NEED_LOOKINTO((*u)->data) && OP_TEST_STOP_COND((*u)->data)){
                // when freeze or stop is asserted, we also make sure we stall at the
                // desired level. here would flush out the counter(becomes 1) then true.
                if (((*u)->data).freeze) ((*u)->data).accepted=1;
                if (*u) u=&((*u)->next);
            }
            else if (IS_HIGH_LEVEL((*u)->data)){ // if going to low counting
                digitalWrite(OP_GET_GPIO((*u)->data), IS_INVERTED((*u)->data));
                OP_SET_LVL_LOW((*u)->data);
                OP_RLD_LOW((*u)->data);
                if (OP_GET_TR_HIGH_POS((*u)->data)!=OP_GET_TR_LOW_POS((*u)->data))
                    node_pwm_move_to(u, OP_GET_TR_HIGH_POS((*u)->data));
                else if (*u) u=&((*u)->next);
            }
            else { // if going to high counting
                digitalWrite(OP_GET_GPIO((*u)->data), !IS_INVERTED((*u)->data));
                OP_SET_LVL_HIGH((*u)->data);
                OP_RLD_HIGH((*u)->data);
                if (OP_GET_TR_HIGH_POS((*u)->data)!=OP_GET_TR_LOW_POS((*u)->data))
                    node_pwm_move_to(u, OP_GET_TR_LOW_POS((*u)->data));
                else if (*u) u=&((*u)->next);
            }
        }
    };


#if !C_MULTI_PWMS_DEBUG
    IRAM_ATTR static void timerISR0(){
        if (!pause_all) trigger(&(pwms[0]));
        else if (pause_all==1){ // do what to do as follows when pause all.
            // pause_all is 1 set by user, means every isr can ack it into 2~5 and paused, such that where paused is known.
            // moreover pause_all could be set 6~9 by user means only isr0~3 can ack it and reset it and be the first one in.
            // the timing would be affected when use such a method however fine than nothing could do.
            // pause_all is 2~5 to ack back to user.
            // care must be taken where the pause_all were used whether or not possibly temporally lead to conflict!
            pause_all=2;
        }
        else if (pause_all==6){ // 6cue isr0
            pause_all=0;
            trigger(&(pwms[0]));
        }
    };
    IRAM_ATTR static void timerISR1(){
        if (!pause_all) trigger(&(pwms[1]));
        else if (pause_all==1){
            pause_all=3;
        }
        else if (pause_all==7){ // 7cue isr1
            pause_all=0;
            trigger(&(pwms[1]));
        }
    };
    IRAM_ATTR static void timerISR2(){
        if (!pause_all) trigger(&(pwms[2]));
        else if (pause_all==1){
            pause_all=4;
        }
        else if (pause_all==8){ // 8cue isr2
            pause_all=0;
            trigger(&(pwms[2]));
        }
    };
    IRAM_ATTR static void timerISR3(){
        if (!pause_all) trigger(&(pwms[3]));
        else if (pause_all==1){
            pause_all=5;
        }
        else if (pause_all==9){ // 9cue isr3
            pause_all=0;
            trigger(&(pwms[3]));
        }
    };
#else
    IRAM_ATTR static void timerISR0(){
        static int x;
        x=micros();
        if (!pause_all) trigger(&(pwms[0]));
        else if (pause_all==1){ // do what to do as follows when pause all.
            // pause_all is 1 set by user, means every isr can ack it into 2~5 and paused, such that where paused is known.
            // moreover pause_all could be set 6~9 by user means only isr0~3 can ack it and reset it and be the first one in.
            // the timing would be affected when use such a method however fine than nothing could do.
            // pause_all is 2~5 to ack back to user.
            // care must be taken where the pause_all were used whether or not possibly temporally lead to conflict!
            pause_all=2;
        }
        else if (pause_all==6){ // 6cue isr0
            pause_all=0;
            trigger(&(pwms[0]));
        }
        z1=micros()-x;
    };

    IRAM_ATTR static void timerISR1(){
        static int x;
        x=micros();
        if (!pause_all) trigger(&(pwms[1]));
        else if (pause_all==1){
            pause_all=3;
        }
        else if (pause_all==7){ // 7cue isr1
            pause_all=0;
            trigger(&(pwms[1]));
        }
        z2=micros()-x;
    };

    IRAM_ATTR static void timerISR2(){
        static int x;
        x=micros();
        if (!pause_all) trigger(&(pwms[2]));
        else if (pause_all==1){
            pause_all=4;
        }
        else if (pause_all==8){ // 8cue isr2
            pause_all=0;
            trigger(&(pwms[2]));
        }
        z3=micros()-x;
    };

    IRAM_ATTR static void timerISR3(){
        static int x;
        x=micros();
        if (!pause_all) trigger(&(pwms[3]));
        else if (pause_all==1){
            pause_all=5;
        }
        else if (pause_all==9){ // 9cue isr3
            pause_all=0;
            trigger(&(pwms[3]));
        }
        z4=micros()-x;
    };
#endif // C_MULTI_PWMS_DEBUG

};


cHwTimer cMultiPwms::timers[4];
IRAM_ATTR cMultiPwms::sNode* cMultiPwms::pwms[4]={0, 0, 0, 0};
unsigned cMultiPwms::pause_all=0;
unsigned cMultiPwms::gid=0;
cMultiPwms::sGcNode* cMultiPwms::memo_park=0;


cMultiPwms sss[16]={
    {3, 800, 16},
    {5, 800, 16},
    {12, 800, 16},
    {13, 800, 16},

    {1, 120000, 10},
    {1, 120000, 10},
    {1, 120000, 10},
    {1, 120000, 10},

    {1, 322000, 10},
    {1, 332000, 10},
    {1, 342000, 10},
    {1, 352000, 10},

    {1, 1200, 4},
    {1, 400, 4},
    {1, 1600, 4},
    {1, 200, 4},
};


bool Timeout(int seconds){
    static int current_time = millis();
    int new_time = millis();
    if (new_time < (current_time + seconds*1000)) return false;
    current_time = new_time;
    return true;
}


void setup() {
    Serial.begin(115200);

    pinMode(3, OUTPUT);
    pinMode(5, OUTPUT);
    pinMode(12, OUTPUT);
    pinMode(13, OUTPUT);

    delay(5000);

    unsigned a=micros();

    sss[0].setDC(10, 4);
    sss[1].setDC(10, 4);
    sss[2].setDC(77, 4);
    sss[3].setDC(0, 4);
    
    sss[4].setDC(0, 1);
    sss[5].setDC(0, 2);
    sss[6].setDC(0, 3);
    sss[7].setDC(0, 4);
    
    sss[8].setDC(0, 5);
    sss[9].setDC(0, 6);
    sss[10].setDC(0, 7);
    sss[11].setDC(0, 8);
    
    sss[12].setDC(0, 2);
    sss[13].setDC(0, 1);
    sss[14].setDC(0, 3);
    sss[15].setDC(0, 4);

    unsigned b=micros();

    cMultiPwms::SyncStart(sss[0]);
    cMultiPwms::SyncNext(sss[1], 50);
    cMultiPwms::SyncNext(sss[2], 50);
    cMultiPwms::SyncNext(sss[3], 50);
    cMultiPwms::SyncEnd(sss[0]);

    cMultiPwms::SyncStart(sss[15]);
    cMultiPwms::SyncNext(sss[4], 100);
    cMultiPwms::SyncNext(sss[5], 150);
    cMultiPwms::SyncNext(sss[6], 200);
    cMultiPwms::SyncNext(sss[7], 250);
    cMultiPwms::SyncNext(sss[8], 300);
    cMultiPwms::SyncNext(sss[9], 350);
    cMultiPwms::SyncNext(sss[10], 400);
    cMultiPwms::SyncNext(sss[11], 450);
    cMultiPwms::SyncNext(sss[12], 500);
    cMultiPwms::SyncNext(sss[13], 550);
    cMultiPwms::SyncNext(sss[14], 600);
    cMultiPwms::SyncEnd(sss[15]);

    unsigned c=micros();

    printf("\r\n16 pwms, init take %dus, set sync take %dus\r\n\r\n", b-a, c-b);
}


void loop() {

    if (Timeout(7)){

        static unsigned s=0, y=0, z=0, w=0;

        cMultiPwms::traverseLinkedList();

        delay(2000);

        if (++y>16) y=0;
        if (z>16) z=0;
        if (w>16) w=0;

        sss[0].setDC(0, y);
        sss[1].setDC(0, y);
        sss[2].setDC(0, y);
        sss[3].setDC(0, y);

        if (!(++s%5)){
            sss[1].Sync(sss[0], 50*z);
            sss[3].Sync(sss[2], (50*z+50)%800);
            z++; w++;
        }
        delay(4000);
    }
}

Categories: Arduino

Tags: ,

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

PHP Code Snippets Powered By : XYZScripts.com