作者: ken

ESP8266 MultiPWMs Lib ver.0.8

No Comments

在筆者使用 v.0.7 版當中,所遇到的問題進行修正。詳情請見程式碼。
而筆者將會實作使用一張直流馬達的驅動電路板作為本版函式庫的範例,並置於下一篇文章。文章名稱很響亮,“完美演譯「直流馬達驅動板」的軟體控制”,由於太響亮,所以這樣的文章標題就當然會改一下低調一點。不過,此函式庫的使用方式,真的值得參考下一篇文章的。

20210106 更新

函式庫中,使用 GPIO0 來作為脫離現用 PWM 的 GPIO pin 的新對象。不過恰巧,在 ESP8266 中就單屬 GPIO0 無法作為 input pin。而 UART TX/RX,通常我們使用的是 TX,所以 RX 勉強可被我們使用為此 parking 的新對象,若沒問題,下一版將改為這支腳位。

Esp8266MultiPwms.h v.0.8

// Esp8266MultiPwms.h ver.0.8
// https://waterfalls.ddns.net
// by Ken Woo
// 2020.12.31




/*
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 stop_all method
            e. fix destructor bug
            f. modify the sync function
ver.0.4     a. fix some bugs
            b. mainly fulfill sync functions
ver.0.5     a. fix some bugs
            b. big change of the code structure
            c. add sync func capable of more than 2 waveforms
            d. add setPeriod() so as to dynamically change dc or frequency.
            e. fix bug as to seamlessly the waveform change dc or freq.
            f. the waveforms synced still some becomes complement offset, i.e.,
            f1. q_offset becomes period-q_offset, or no-clue deviated. under investigation.
ver.0.6     a. setDC has the timing shifting problem/wrong reload counter which caused by CDPAV. fixed.
            b. sync funcs have wrong parameter calculation. fixed.
            c. the test is accurate at 80Hz.
ver.0.7     a. the file of the code divided into 2 .h, .cpp, for Arduino IDE Lib use.
            b. add supplementary functions.
            c. let objects reusable after removePwm(). reassign is now available too.
            d. fixed the new object still needs to define tr_low_pos, etc., parameters. otherwise is incorrect before setDC called.
            e. sync before isr running hence applicable, however incorrect currently.
ver.0.8     a. setDC/setPeriod 0% or 100% could affect the stall level, fixed.
            b. modify the setDC/setPeriod once called could start to run from stall state, now no effect to the stall state.
            c. in isr, the moment of the high-level-end as to do something which is incorrect, now fixed.
            d. add detachGpio() for more flexible use. user must keep in mind which gpio is detached and setGpio() again if reuse.
todo.       a. avoid to allocate memory on external ram or flash.
known bugs. a. max period now shrinks to 1.5 seconds. wdt-reset if over.
            b. IRAM_ATTR or non-global static pwm objects probably cause wdt-reset.
            c. doing the sync before pwm-isr running is incorrect. use syncs after some pwm was Resume() is no concern and correctly synced.
            d. stalled pwm will start to run after sync. pause it after sync is a suggested way.
            e. if wdt-reset, probably heavy loading and firstly try to ease. now ok test 13 pwms running and 5 doing variations in it.
            f. there is an expected one-step pulse while syncing which could eliminated by setDC(0/100/see below).
            g. please report any bugs or recommendations of refinements.
*/


// usages and constraints
// 1. set dc to the proper state before sync(ref 5.), i.e., 0% or 100%; after sync, all the pwms synced are going to run even it were paused priorly.
// 2. always use Resume() to run/re-run pwms.
// 3. new pwm objects are at the stopped state, once setDC/setPeriod/sync will commence to run before v.0.7(incl.).
// v.0.8 hereafter, setDC/setPeriod will no longer affect the pwm state(pause/stop); but syncs still do and not plan to modify so far.
//
// 4. Especially NOTE that setDC(0% or 100%) will relinquish the stall level because they use the same control rout.
// so, do not setDC 0% or 100% while at the stall state otherwise the level comes up with DC and the state still at stall.
// once did, Resume() will let back to correct(running) or setDC to other than 0% or 100% back to correct(remains stall).
// setDC 0/100 suitable for running pwm. the counterpart is as setStopLevel() first then stall. so it is better not to mix.
// simply put, in client code, the setDC 0/100 and stall have better not both exist in the course of a procedure unless considered.
//
// 5. analogy to 4., the syncs after called, will relinquish the stall state OR THE dc of 0%/100% because they use the same control rout.
// so, if a high level or low level is required before and after this sync, currently the only viable way is to setDC(0/100) and then
// followed by setStopLevel(high/low) where lead into a special state and then syncs. after synced, the level will stay at the desired.
// and then, setDC(0/100) again yields the special state back to normal. remember to setStopLevel() back to the user default if required.
// example is as follows:
// SetDC_all(100); // *** this is must, or there will be a one-step pulse accross pwms while syncing which severely damage cmos.
// M1.setStopLevel(true); // ** this is accompanying with the must.
// M2.setStopLevel(true);
// M3.setStopLevel(true);
// M4.setStopLevel(true);
// cMultiPwms::SyncStart(M1);
// cMultiPwms::SyncNext(M2, 200);
// cMultiPwms::SyncNext(M3, 400);
// cMultiPwms::SyncNext(M4, 600);
// cMultiPwms::SyncEnd(M1);
// SetDC_all(100);
// M1.setStopLevel(false); // supposed we want to be at low when stopped which used later.
// M2.setStopLevel(false);
// M3.setStopLevel(false);
// M4.setStopLevel(false);


// PWM class: using ESP8266 MultiTimersV0.4 to generate four 196us timers which are sequentially sync-offset by 49us 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 13s; in order to have fixed positions),
// (however, since there are delays in this code, such that by test, the max period is as 1.5seconds to prevent from wdt-reset).
// 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.




#include"Esp8266HwSwTimers.h"
#ifndef _c_MULTI_PWMS_H_
#define _c_MULTI_PWMS_H_


// switches
#define C_MULTI_PWMS_DEBUG 0
#define MULTIPWMS_PREVENT_WDT_RESET 1
#define MULTIPWMS_GPIO_PARKING 0 // for efficiency, a gpio number as default is used; it should be INPUT and not used by others.




#if C_MULTI_PWMS_DEBUG
    extern unsigned z1, z2, z3, z4;
    extern unsigned t1, t11, t2, t22, t3, t33, t4, t44, ttt, ttt1;
    extern unsigned w;
    extern int syncoffset_d[16]; // after test, it is more analogy to oscope, so fine ref. it tracks offset from id2 to id16 against id1.
#endif


class cMultiPwms{

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

        unsigned n_reload_high: 16; // the new reload count for high level.
        unsigned reload_high:   16; // reload count for high level. 200us is one-round, 200x65536=13seconds, max period.

        unsigned n_reload_low:  16; // the new reload count for low level.
        unsigned reload_low:    16; // reload count for low level. 200us is one-round, 200x65536=13seconds.

        unsigned freeze:        1;  // the first 4 stall bits are mutually exclusive, each would have accepted set if isr acked. freeze is pause.
        unsigned stopit:        1;  // mainly indicates this obj will be deleted, essentially stop timing.
        unsigned is_new_reload: 1;  // has a new reload value arrived. used it by read and clear.
        unsigned is_aside_duty: 1;  // it is the duty 0% or 100%
        unsigned accepted:      1;  // this bit will be set if isr accepted freeze, etc., every round. so clear it before set stall.
        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, invert it, so it affects nothing.
        unsigned stall_h_or_l:  1;  // dictates isr to be at high or at low when stall, freeze, stopped or need to do some other things.
        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 n_tr_low_pos:  2;  // the new position at timer ISR[] for transiting to low level, used to renew in isr.
        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 rsv2:          9;  // reserved 2.
        // 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. 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. it is pointer process, so not much cost.

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


    typedef unsigned (*xptr_sPwmObj)[4];

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

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

    #define OP_RLD_NEWHIGH(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[1]<<=16)

    #define OP_RLD_NEWLOW(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]<<=16)

    #define OP_RLD_NEWTRLOWPOS_C(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[3]=\
        ((((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[3]>>2)&0x00000C00)|\
        ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[3]&0xFFFFF3FB))) // indication bit also cleared.

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

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

    #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)))[3]>>8)&0x3)

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

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

    /// #define OP_GET_STALL_LVL(sNode_ex) (((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[3]>>7)&0x1)
    #define OP_GET_STALL_LVL(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[3]&0x80)

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

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

    #define IS_FREEZE(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[3]&0x1)

    #define IS_STOP(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[3]&0x2)

    #define IS_RLD_NEW(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[3]&0x4)

    #define IS_ASIDE_DUTY(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[3]&0x8)

    #define IS_HIGH_LEVEL(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[3]&0x20)

    #define IS_INVERTED(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[3]&0x40)


    typedef struct sNode{
        sPwmObj data;
        sNode *next;

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


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

        sGcNode(): obj(0), 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 pwms_pos){ // find the parent of the node having this id.
        sNode **i;
        for (i=&(pwms[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);

    bool setStallLevel(bool high_or_low);

    void configTimer();

    void* getMemory(int obj_size);

    void freeMemory(void *b);


    bool ack(){
        // used for waiting for acked. it is set by isr and read by client code.
        // this accepted bit will be repeatedly set,
        // so as to get closer between isr and client code.
        // client code clear it, and wait it be true and know they are how closer.
        // since for example, max period 13s, it probably needs to wait for 13s then acked.
        return (owner->data).accepted;
    };

    void nack(){ // clear the stall state; mainly clear the accepted bit.
        // there are 4 candidate bits, but only freeze, stopit bits needed for cleared.

        /// [CDPAV]
        isr_sneak(); ///
        (owner->data).freeze=0; // if pause, the timing will not lose.
        (owner->data).stopit=0; // if stop, the timing will lose but it still can be resumed.
        // for the renew bit, clear by client demands indirectly, for the aside_duty, setdc to clear.
        (owner->data).accepted=0; // no need to delay
    };

    void resume(){nack();};
    void pause(){isr_sneak(); (owner->data).freeze=1; (owner->data).accepted=0;}; // nonblocking
    void stop(){isr_sneak(); (owner->data).stopit=1; (owner->data).accepted=0;}; // nonblocking
    void renew(){isr_sneak(); (owner->data).is_new_reload=1; (owner->data).accepted=0;}; // nonblocking
    void aside_duty(){isr_sneak(); (owner->data).is_aside_duty=1; (owner->data).accepted=0;}; // nonblocking
    void de_aside_duty(){isr_sneak(); (owner->data).is_aside_duty=0; (owner->data).accepted=0;}; // nonblocking

    void isr_sneak(){ // let client code spin waiting for isr leaved, used for critical data accessing(50us). [CDPAV].
        if (!timers[0].isRunning() || stop_all) return;
        isr_key=1;
        while (isr_key) delayMicroseconds(5); // the delay is must or wdt-reset.
    };

    static void enter_global_stop();

    static void leave_global_stop();

    static void enter_global_stop_wait();

    static void leave_global_stop_wait();


    static cHwTimer timers[4];
    static sNode* pwms[4];
    static unsigned stop_all; // global stop for PWMs. to avoid conflict.
    static unsigned isr_key; // this is the only one way to prevent isr and client code from conflict.
    static unsigned gid; // it could be maintained for the purpose of unlimited new/delete, but need more storage.
    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 stop_h_l; // stop at high or low, default is low.
    unsigned uid;


    public:


    void removePwm(){ // there are times when use static objects but need to discard during runtime could call. also could reuse.
        if (!owner) return;

        waitForStop();
        enter_global_stop_wait();

        sNode **n=node_pwm_find_parent(uid, (owner->data).tr_high_pos);
        if (!n) n=node_pwm_find_parent(uid, (owner->data).tr_low_pos);
        if (!n){ // could not happen.

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

            leave_global_stop_wait();

            return;
        }

        *n=(*n)->next;

        leave_global_stop_wait();

        freeMemory(owner);

        owner=0;
    };


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

    ~cMultiPwms(){
        removePwm();
    };

    cMultiPwms(unsigned gpio, unsigned period_us, unsigned set_steps,\
        bool inverted=false, bool stop_high=false):\
        owner(0), uid(0), stop_h_l(!!stop_high){ // 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) return;

        configPwm(MULTIPWMS_GPIO_PARKING, a.period, a.steps, ((a.owner)->data).is_inverted, a.stop_h_l);
    };

    const cMultiPwms& operator=(const cMultiPwms &b){
        if (!b.uid) return *this;
        unsigned copy_gpio;

        if (this->owner){
            copy_gpio=(this->owner->data).gpio;
            this->removePwm();
        }
        else copy_gpio=MULTIPWMS_GPIO_PARKING;

        configPwm(copy_gpio, b.period, b.steps, ((b.owner)->data).is_inverted, b.stop_h_l);
        return *this;
    };


    bool configPwm(unsigned gpio, unsigned period_us, unsigned set_steps, bool inverted, bool stop_high);


    bool setGpio(unsigned gpio){
        if (!owner) return false;

        /// [CDPAV]
        isr_sneak(); ///
        (owner->data).gpio=gpio;
        return true;
    };


    bool setInvert(bool invert_or_not){
        if (!owner) return false;

        /// [CDPAV]
        isr_sneak(); ///
        (owner->data).is_inverted=!!invert_or_not;
        return true;
    };


    bool setStopLevel(bool stop_high){ // assign the level when stopped or deallocated.
        if (uid){stop_h_l=stop_high; return true;}
        return false;
    };


    bool setDC(float percentage, unsigned set_by_steps=0);

    bool setPeriod(unsigned new_period, unsigned new_steps, float percentage, unsigned set_by_steps=0);


    // the pause, stop and resume are paired use.
    void Pause(){isr_sneak(); (this->owner->data).stall_h_or_l=this->stop_h_l; this->pause();}; // nonblocking
    void Stop(){isr_sneak(); (this->owner->data).stall_h_or_l=this->stop_h_l; this->stop();}; // nonblocking
    void Resume(){this->resume();};

    void waitForStop(){ // spin waiting for stopped to make sure isr has encountered the stop state.
        if (!uid || !owner || !timers[0].isRunning()) return;

        Stop();
        while (!ack()) delayMicroseconds(200);

        ////delay(2); // if no it, frequently exception(28) wdt-rst(2). i do not know why!!!
    };


    static void traverseLinkedList();


    bool Sync(cMultiPwms &base, unsigned offset_us);

    static bool SyncStart(cMultiPwms &base);

    static bool SyncNext(cMultiPwms &follower, unsigned offset_us);

    static bool SyncEnd(cMultiPwms &base);


    static void trigger(sNode **u);
    static void timerISR0();
    static void timerISR1();
    static void timerISR2();
    static void timerISR3();


    // ===================== supplementary functions, ordinary functions.

    static bool Timeout(unsigned ms, unsigned &store_start_time_us, bool one_shot=false, bool one_shot_lasting=false);
    unsigned getId() const{return uid;};
    bool isAlive() const{return !!owner;};
    bool detachGpio(){setGpio(MULTIPWMS_GPIO_PARKING);};
};


#endif // _c_MULTI_PWMS_H_

Esp8266MultiPwms.cpp v.0.8

// Esp8266MultiPwms.cpp ver.0.8
// https://waterfalls.ddns.net
// by Ken Woo
// 2020.12.31




#include"Esp8266MultiPwms.h"


#if C_MULTI_PWMS_DEBUG
    IRAM_ATTR unsigned z1, z2, z3, z4;
    IRAM_ATTR unsigned t1, t11, t2, t22, t3, t33, t4, t44, ttt, ttt1;
    IRAM_ATTR unsigned w;
    IRAM_ATTR int syncoffset_d[16]; // after test, it is more analogy to oscope, so fine ref.
#endif

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


unsigned cMultiPwms::wellAdd(cMultiPwms::sNode *a){ // add to proper ISR position, return the position.
                            // considering that, do it while runtime it is not much effective, right.
                            /// and it is also a sort of critical data potentially access violation([CDPAV]).
                            /// so, isr_sneak() needs to add, or the global_stop, or none.
                            /// which one is up to user to rewrite since it concerns about latency and efficiency.
    enter_global_stop_wait(); ///
    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);
    leave_global_stop_wait(); ///
    return pos;
}


bool cMultiPwms::setStallLevel(bool high_or_low){
    // set the stayed level when freeze, stop, renew, and aside dc, etc.
    // default/normally would be low since tasks to do usually at the time of low-ended.
    // this function/bit is used when demanded, e.g., stopped at certain level. however,
    // set-dc hence ignores this bit.
    // note that it will return the prior set state.

    /// [CDPAV]
    isr_sneak(); ///
    bool tmp=!!((owner->data).stall_h_or_l);
    (owner->data).stall_h_or_l=!!high_or_low;
    return tmp;
}


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


void* cMultiPwms::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 cMultiPwms::freeMemory(void *b){free(b);}


void cMultiPwms::enter_global_stop(){ // it belongs to nonblocking.
    #if C_MULTI_PWMS_DEBUG
        static bool halt=0;
        while (halt); // it should not happen; if so, check the client code to avoid.
        halt=true;
    #endif // C_MULTI_PWMS_DEBUG

    // ----
    delayMicroseconds(75); // since isr int at each 50us. 75us is a prepare time if timer not running-ready.
    if (timers[0].isRunning() && !stop_all){
        stop_all=1;
        delayMicroseconds(25); // wait for isr entered.
        int time_cnt=10;
        while (stop_all<2 && time_cnt--) delayMicroseconds(25); // no patient to wait
    }
    else stop_all=2; // specifically for the case timer not running.
    // ----

    #if C_MULTI_PWMS_DEBUG
        halt=false;
    #endif // C_MULTI_PWMS_DEBUG
}

void cMultiPwms::leave_global_stop(){
    #if C_MULTI_PWMS_DEBUG
        static bool halt=0;
        while (halt); // it should not happen; if so, check the client code to avoid.
        halt=true;
    #endif // C_MULTI_PWMS_DEBUG

    if (timers[0].isRunning()){
        // ---- resume entire isr, which starting from where paused.
        switch (stop_all){
            case 2: stop_all=6; break;
            case 3: stop_all=7; break;
            case 4: stop_all=8; break;
            case 5: stop_all=9; break;
            default: while (1);
        }
        int time_cnt=10;
        while (stop_all && time_cnt--) delayMicroseconds(25); // no patient to wait
        // ---- resume entire isr
    }
    else stop_all=6; // specifically for the case timer not running.

    #if C_MULTI_PWMS_DEBUG
        halt=false;
    #endif // C_MULTI_PWMS_DEBUG
}

void cMultiPwms::enter_global_stop_wait(){ // unfortunately stay and wait is better.
    #if C_MULTI_PWMS_DEBUG
        static bool halt=0;
        while (halt); // it should not happen; if so, check the client code to avoid.
        halt=true;
    #endif // C_MULTI_PWMS_DEBUG

    // ----
    if (timers[0].isRunning() && !stop_all){
        stop_all=1;
        while (stop_all<2) delayMicroseconds(100); // the delay is must or wdt-reset.
    }
    else stop_all=2; // specifically for the case timer not running.
    // ----

    #if C_MULTI_PWMS_DEBUG
        halt=false;
    #endif // C_MULTI_PWMS_DEBUG
}

void cMultiPwms::leave_global_stop_wait(){ // accompanying with enter_global_stop_wait().
    #if C_MULTI_PWMS_DEBUG
        static bool halt=0;
        while (halt); // it should not happen; if so, check the client code to avoid.
        halt=true;
    #endif // C_MULTI_PWMS_DEBUG

    if (timers[0].isRunning()){
        // ---- resume entire isr, which starting from where paused.
        switch (stop_all){
            case 2: stop_all=6; break;
            case 3: stop_all=7; break;
            case 4: stop_all=8; break;
            case 5: stop_all=9; break;
            default: while (1);
        }
        while (stop_all) delayMicroseconds(100);
        // ---- resume entire isr
    }
    else stop_all=6; // specifically for the case timer not running.

    #if C_MULTI_PWMS_DEBUG
        halt=false;
    #endif // C_MULTI_PWMS_DEBUG
}


bool cMultiPwms::configPwm(unsigned gpio, unsigned period_us, unsigned set_steps,\
    bool inverted, bool stop_high){ // if a step is 1%, use 100 steps, not 101 steps. this func is only for initiallized use.

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

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

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

    #if C_MULTI_PWMS_DEBUG
        printf("\r\npwm object id %d created/recreated, max 31.\r\n", uid);
    #endif

    (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).stall_h_or_l=stop_h_l; // default is 0 unless explicitly specified by setStallLevel within this code.
    setGpio(gpio);
    (owner->data).accepted=0; // for stall

    // ---- fixed at v.0.7 to stop it and fake the parameters which brings the advantage capable of setting sync before pwm running.
    // prior bug also at tr_low_pos is 0, etc., in a freezed state.
    // since the node is running once wellAdd, however in stopped state parameters are still intact in isr and safe.
    (owner->data).stopit=1; // stopped
    (owner->data).counter=1; /// 1 rather than 0 might affects sync.
    (owner->data).reload_high=(this->period/this->steps/50+3)/4;
    (owner->data).reload_low=(this->period/50-this->period/this->steps/50+3)/4;
    // ----

    (owner->data).tr_high_pos=wellAdd(owner); // at the outset, raise high then immed change pos to tr_low_pos.

    // ---- fixed at v.0.7
    (owner->data).tr_low_pos=((owner->data).tr_high_pos+this->period/this->steps/50)%4;
    // ----

    return true;
}


bool cMultiPwms::setDC(float percentage, unsigned set_by_steps){ // will by steps if it is nonzero
    // you would see that any PWM would start to work after a setDC is called.

    if (!owner) return false;
    if (!set_by_steps){
        (percentage*=this->steps)/=100.0f;
        if ((set_by_steps=unsigned(percentage))<percentage) set_by_steps++;
    }
    if (set_by_steps>this->steps) set_by_steps=this->steps;

    set_by_steps*=this->period/this->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.

    if (!set_by_steps){ // 0%
        setStallLevel((owner->data).is_inverted); // set it stall at low
        aside_duty();
        set_by_steps=this->period/this->steps/50; // fake it to be duty low long enough.
    }
    else if (set_by_steps==this->period/50){ // 100%
        setStallLevel(!(owner->data).is_inverted); // set it stall at high
        aside_duty();
        set_by_steps=this->period/this->steps/50; // fake it to be duty low long enough.
    }
    else {
        setStallLevel(this->stop_h_l); // must recover it because it might be altered previously.
        de_aside_duty();
    }

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

    // considering the count,
    // since encountering the count either 0 or 1 represents count-up,
    // so if quotient is 1, means needs at least a round, which by this cases,
    // if no remainder, quotient is the result, fitted.
    // if remainder(needs a round and a few advances<4),
    // add one up is the result as also fits the case if quotient 0,
    // because quotient 0 and 1 are the same just mention above.
    int count=set_by_steps/4;

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

    while ((owner->data).is_new_reload) delayMicroseconds(this->period/this->steps); // let previous committed.

    unsigned tmp1=(this->period/50-set_by_steps+3)/4;
    isr_sneak(); // the bug fixed at v.0.6. it is needed or data access violation could happen. [CDPAV].
    (owner->data).n_tr_low_pos=tr_pos; // this is the "duty-cycle count" cast into "tr_low_pos".
    (owner->data).n_reload_high=count;
    ////(owner->data).n_reload_low=period/200-count;

    // it is the time counted by tr_high_pos, but time start of the counting is at tr_low_pos,
    // so, the native counts is the time for level-low counted by tr_high_pos and must base
    // on the relative position between tr_high_pos and tr_low_pos.
    // one certain condition is the native counts certainly end up at tr_high_pos.
    // another certainly that position of tr_high and tr_low are within 4 which as the remainder.
    // add one up if remainder holds such conditions.
    (owner->data).n_reload_low=tmp1;
    renew();

    // priorly we did not stall however we still need resume for new object./////
    /////Resume();

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

    return true;
}


bool cMultiPwms::setPeriod(unsigned new_period, unsigned new_steps, float percentage, unsigned set_by_steps){ // will by steps if it is nonzero
    // for simplicity, user should reassign the current duty cycle, new one is fine.
    // you would see that any PWM would start to work after a setPeriod is called.

    if (!uid || !owner || (this->steps==new_steps) && (new_period==this->period)) return false;
    if (new_period%200 || !new_steps || new_period%new_steps || new_period/new_steps%50 || new_period>13000000) return false;

    this->period=new_period;
    this->steps=new_steps;

    if (!set_by_steps){
        (percentage*=this->steps)/=100.0f;
        if ((set_by_steps=unsigned(percentage))<percentage) set_by_steps++;
    }
    if (set_by_steps>this->steps) set_by_steps=this->steps;

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

    if (!set_by_steps){ // 0%
        setStallLevel((owner->data).is_inverted); // set it stall at low
        aside_duty();
        set_by_steps=this->period/this->steps/50; // fake it to be duty low long enough.
    }
    else if (set_by_steps==this->period/50){ // 100%
        setStallLevel(!(owner->data).is_inverted); // set it stall at high
        aside_duty();
        set_by_steps=this->period/this->steps/50; // fake it to be duty low long enough.
    }
    else {
        setStallLevel(this->stop_h_l); // must recover it because it might be altered previously.
        de_aside_duty();
    }

    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;
    }

    while ((owner->data).is_new_reload) delayMicroseconds(this->period/this->steps); // let previous committed.

    unsigned tmp1=(this->period/50-set_by_steps+3)/4;
    isr_sneak(); // the bug fixed at v.0.6. it is needed or data access violation could happen. [CDPAV].
    (owner->data).n_tr_low_pos=tr_pos; // this is the "duty-cycle count" cast into "tr_low_pos".
    (owner->data).n_reload_high=count;
    (owner->data).n_reload_low=tmp1;
    renew();

    // priorly we did not stall however we still need resume for new object./////
    /////Resume();

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

    return true;
}


void cMultiPwms::traverseLinkedList(){
    #if C_MULTI_PWMS_DEBUG
        int c=0;
        #if MULTIPWMS_PREVENT_WDT_RESET
            enter_global_stop_wait(); // if so, waveform not consecutive.
            printf("\r\n[counter pos=%d]\r\n", stop_all-2);
        #endif // MULTIPWMS_PREVENT_WDT_RESET
        for (int i=0; i<4; i++){
            sNode *a=pwms[i];
            while (a){ // might failed/wdt-reset if some object is moving. instead, could use stop_all then print.
                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);
        }
        #if MULTIPWMS_PREVENT_WDT_RESET
            leave_global_stop_wait();
        #endif // MULTIPWMS_PREVENT_WDT_RESET

        printf("the [sync offset]:\r\n");
        printf("[%d][%d][%d][%d]\r\n", 0, syncoffset_d[1], syncoffset_d[2], syncoffset_d[3]);
        printf("[%d][%d][%d][%d]\r\n", syncoffset_d[4], syncoffset_d[5], syncoffset_d[6], syncoffset_d[7]);
        printf("[%d][%d][%d][%d]\r\n", syncoffset_d[8], syncoffset_d[9], syncoffset_d[10], syncoffset_d[11]);
        printf("[%d][%d][%d][%d]\r\n", syncoffset_d[12], syncoffset_d[13], syncoffset_d[14], syncoffset_d[15]);

        printf("ISR lost(%u/%u). The 4 ISRs (%d nodes in it) each costs us time (%d %d %d %d)\r\n\r\n\r\n",\
        ttt, ttt1, c, z1, z2, z3, z4);
    #endif // C_MULTI_PWMS_DEBUG
}


bool cMultiPwms::Sync(cMultiPwms &base, unsigned offset_us){ // must at least a setDC call before sync.
    // the most important note that, this is a relatively sync, which means the two sync here,
    // will deviate from other waveforms since we use stall 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 false;
    if ((period!=base.period) || (period<=offset_us) || offset_us%50) return false;

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

    // stop 2 nodes in isr for they are in the transition state, to high.
    base.waitForStop();
    this->waitForStop();

    enter_global_stop_wait();

    unsigned a=((base.owner)->data).tr_high_pos;
    unsigned old_pos=(owner->data).tr_high_pos;
    unsigned b=(owner->data).tr_high_pos=(a+offset_us)%4;
    if (b!=old_pos) node_pwm_move_to(node_pwm_find_parent(this->uid, old_pos), b);

    //// ---- at v.0.6. bug fixed.
    //// (owner->data).tr_low_pos=((owner->data).tr_low_pos+offset_us)%4;
    //// 
    //// the low-position and counter calculations are wrong since at setDC(), the complete calculation of parameters,
    //// which are fine-adjusted. here should do so too.
    //// therefore let to get the dc first then recalculate.
    unsigned dc=(owner->data).reload_high;
    unsigned low_pos=(owner->data).tr_low_pos;
    if (low_pos!=old_pos) dc--;
    dc<<=2;
    if (old_pos>low_pos) low_pos+=4;
    dc+=(low_pos-old_pos);

    low_pos=dc%4;
    unsigned count=dc/4;
    if (!low_pos) low_pos=b;
    else {
        count++;
        low_pos+=b;
        if (low_pos>3) low_pos-=4;
    }
    (owner->data).reload_high=count; // the new reload_high
    (owner->data).reload_low=(this->period/50-dc+3)/4; // the new reload_low
    (owner->data).tr_low_pos=low_pos; // the new tr_low_pos
    //// ----

    (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=stop_all-2;
    if ((a<b && c<=b && c>a) || (b<a && c<=b) || (b<a && c>a)) (owner->data).counter++;

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

    // resume entire isr
    leave_global_stop_wait();

    return true;
}

bool cMultiPwms::SyncStart(cMultiPwms &base){ // note that if no SyncEnd, there will be a memory leak.
    if (!base.uid || !base.owner || memo_park) return false;
    memo_park=(sGcNode*)base.getMemory(sizeof(sGcNode));
    if (!memo_park) return false;

    memo_park->tmp_offset=0; // store offset, native count
    memo_park->tmp_stop_h_l=base.stop_h_l; // store stop state
    memo_park->tmp_period=base.period; // store period
    memo_park->obj=base.owner; // store node address
    memo_park->next=0; // store next sGcNode address

    return true;
}

bool cMultiPwms::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;

    a->tmp_offset=offset_us/50;
    a->tmp_stop_h_l=follower.stop_h_l;
    a->tmp_period=follower.period;
    a->obj=follower.owner;
    a->next=0;

    follower.node_add((sNode**)(&memo_park), (sNode*)(a));

    return true;
}

bool cMultiPwms::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
        base.node_delete((sNode*)(memo_park));
        memo_park=0;
        return true; // should return false or true?
    }

    for (sGcNode *iter1=memo_park; iter1; iter1=iter1->next){
        // stop each node ensuring it stays at cycle-end.
        base.isr_sneak();
        (iter1->obj->data).stall_h_or_l=iter1->tmp_stop_h_l; // set stopped level state.
        (iter1->obj->data).stopit=1;
        (iter1->obj->data).accepted=0;
        if (timers[0].isRunning()) do delayMicroseconds(200); while (!(iter1->obj->data).accepted);
        ////delay(2); // if no it, frequently exception(28) wdt-rst(2). i do not know why!!! again, must have!
    }

    // stall entire isr
    base.enter_global_stop_wait();

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

    // resume first first; it is ok since all nodes global stopped.
    (memo_park->obj->data).freeze=0;
    (memo_park->obj->data).stopit=0;
    (memo_park->obj->data).accepted=0;

    for (sGcNode *iter=memo_park->next; iter; iter=iter->next){
        unsigned old_pos=(iter->obj->data).tr_high_pos;
        unsigned b=(iter->obj->data).tr_high_pos=(a+iter->tmp_offset)%4;
        if (b!=old_pos) node_pwm_move_to(base.node_pwm_find_parent((iter->obj->data).id, old_pos), b);

        //// ---- at v.0.6. bug fixed.
        //// (iter->obj->data).tr_low_pos=((iter->obj->data).tr_low_pos+iter->tmp_offset)%4;
        //// 
        //// the low-position and counter calculations are wrong since at setDC(), the complete calculation of parameters,
        //// which are fine-adjusted. here should do so too.
        //// therefore let to get the dc first then recalculate.
        unsigned dc=(iter->obj->data).reload_high;
        unsigned low_pos=(iter->obj->data).tr_low_pos;
        if (low_pos!=old_pos) dc--;
        dc<<=2;
        if (old_pos>low_pos) low_pos+=4;
        dc+=(low_pos-old_pos);

        low_pos=dc%4;
        unsigned count=dc/4;
        if (!low_pos) low_pos=b;
        else {
            count++;
            low_pos+=b;
            if (low_pos>3) low_pos-=4;
        }
        (iter->obj->data).reload_high=count; // the new reload_high
        (iter->obj->data).reload_low=(iter->tmp_period/50-dc+3)/4; // the new reload_low
        (iter->obj->data).tr_low_pos=low_pos; // the new tr_low_pos
        //// ----

        (iter->obj->data).counter=e+iter->tmp_offset/4;

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

        // resume
        (iter->obj->data).freeze=0;
        (iter->obj->data).stopit=0;
        (iter->obj->data).accepted=0;
    }

    // resume entire isr
    base.leave_global_stop_wait();

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


IRAM_ATTR void cMultiPwms::trigger(cMultiPwms::sNode **u){
    while (u=node_pwm_dec_cnt(u)){ // when true, count is up, time to transition.
        /// probably might improve by using an unsigned to cap the u and refresh at the end.
        if (IS_HIGH_LEVEL((*u)->data)){ // now high, if going to low counting.
            // right here is at the end of high level,
            // it is the right time for us to do something or change something.
            if (IS_NEED_LOOKINTO((*u)->data)){ // it means this is at the stall.
                // for the stall issued, we have some tasks to do,
                // in such cases the waveform could stopped or still running.
                // so, here inside, no waveform, unless explicitly do toggling if required.
                // but most cases, reload and level-data alternating have to do in order to keep sync timing.
                // another important note that, leave away without do something, it will enter again only this level-end.
                // same is the other level-end.
                if (IS_RLD_NEW((*u)->data)) digitalWrite(OP_GET_GPIO((*u)->data), IS_INVERTED((*u)->data));
            }
            else 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 u=&((*u)->next);
        }
        else { // now low, if going to high counting; also, the end of a finished cycle.
            if (IS_NEED_LOOKINTO((*u)->data)){
                if (IS_RLD_NEW((*u)->data)){ // set-dc ignores setting the level.
                    OP_RLD_NEWHIGH((*u)->data);
                    OP_RLD_NEWLOW((*u)->data);
                    OP_RLD_NEWTRLOWPOS_C((*u)->data);

                    OP_DO_ACCEPT((*u)->data);
                    continue;
                }
                else digitalWrite(OP_GET_GPIO((*u)->data), OP_GET_STALL_LVL((*u)->data));

                if (IS_STOP((*u)->data)){ // skip this one, no state changes.
                    OP_DO_ACCEPT((*u)->data);
                    u=&((*u)->next);
                    continue;
                }
                ///// else if (IS_FREEZE((*u)->data));
                ///// else if (IS_ASIDE_DUTY((*u)->data));
                OP_DO_ACCEPT((*u)->data);
            }
            else digitalWrite(OP_GET_GPIO((*u)->data), !IS_INVERTED((*u)->data));
            OP_SET_LVL_HIGH((*u)->data);
            OP_RLD_HIGH((*u)->data);

            #if C_MULTI_PWMS_DEBUG
                w=micros();
                if (((*u)->data).id==1) syncoffset_d[0]=w;
                else syncoffset_d[((*u)->data).id-1]=w-syncoffset_d[0];
            #endif // C_MULTI_PWMS_DEBUG

            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 u=&((*u)->next);
        }
    }

    isr_key=0; // extermely note that, in client code, if access critical data, violation did happen.
    // so, must use this facility isr_sneak() if potential violations exist.
}


#if !C_MULTI_PWMS_DEBUG
    IRAM_ATTR void cMultiPwms::timerISR0(){
        if (!stop_all) trigger(&(pwms[0]));
        else if (stop_all==1){ // do what to do as follows when stop all.
            // stop_all is 1 set by user, means every isr can ack it into 2~5 and stopped, such that where stopped is known.
            // moreover stop_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.
            // stop_all is 2~5 to ack back to user.
            // care must be taken where the stop_all were used whether or not possibly temporally lead to conflict!
            // note that a stop_all penality is 200us*n.
            stop_all=2;
        }
        else if (stop_all==6){ // 6cue isr0
            stop_all=0;
            trigger(&(pwms[0]));
        }
    }

    IRAM_ATTR void cMultiPwms::timerISR1(){
        if (!stop_all) trigger(&(pwms[1]));
        else if (stop_all==1){
            stop_all=3;
        }
        else if (stop_all==7){ // 7cue isr1
            stop_all=0;
            trigger(&(pwms[1]));
        }
    }

    IRAM_ATTR void cMultiPwms::timerISR2(){
        if (!stop_all) trigger(&(pwms[2]));
        else if (stop_all==1){
            stop_all=4;
        }
        else if (stop_all==8){ // 8cue isr2
            stop_all=0;
            trigger(&(pwms[2]));
        }
    }

    IRAM_ATTR void cMultiPwms::timerISR3(){
        if (!stop_all) trigger(&(pwms[3]));
        else if (stop_all==1){
            stop_all=5;
        }
        else if (stop_all==9){ // 9cue isr3
            stop_all=0;
            trigger(&(pwms[3]));
        }
    }

#else // C_MULTI_PWMS_DEBUG

    IRAM_ATTR void cMultiPwms::timerISR0(){
        static int x;
        x=micros(); ttt1++; t1=x; if ((t1-=t44)<24 || t1>72) ttt++;
        if (!stop_all) trigger(&(pwms[0]));
        else if (stop_all==1){
            stop_all=2;
        }
        else if (stop_all==6){ // 6cue isr0
            stop_all=0;
            trigger(&(pwms[0]));
        }
        z1=(t11=micros())-x;
    }

    IRAM_ATTR void cMultiPwms::timerISR1(){
        static int x;
        x=micros(); ttt1++; t2=x; if ((t2-=t11)<24 || t2>72) ttt++;
        if (!stop_all) trigger(&(pwms[1]));
        else if (stop_all==1){
            stop_all=3;
        }
        else if (stop_all==7){ // 7cue isr1
            stop_all=0;
            trigger(&(pwms[1]));
        }
        z2=(t22=micros())-x;
    }

    IRAM_ATTR void cMultiPwms::timerISR2(){
        static int x;
        x=micros(); ttt1++; t3=x; if ((t3-=t22)<24 || t3>72) ttt++;
        if (!stop_all) trigger(&(pwms[2]));
        else if (stop_all==1){
            stop_all=4;
        }
        else if (stop_all==8){ // 8cue isr2
            stop_all=0;
            trigger(&(pwms[2]));
        }
        z3=(t33=micros())-x;
    }

    IRAM_ATTR void cMultiPwms::timerISR3(){
        static int x;
        x=micros(); ttt1++; t4=x; if ((t4-=t33)<24 || t4>72) ttt++;
        if (!stop_all) trigger(&(pwms[3]));
        else if (stop_all==1){
            stop_all=5;
        }
        else if (stop_all==9){ // 9cue isr3
            stop_all=0;
            trigger(&(pwms[3]));
        }
        z4=(t44=micros())-x;
    }
#endif // !C_MULTI_PWMS_DEBUG


// ===================== supplementary functions, ordinary functions.

bool cMultiPwms::Timeout(unsigned ms, unsigned &store_start_time_us, bool one_shot, bool one_shot_lasting){
    if (one_shot && !store_start_time_us) return one_shot_lasting;
    if (micros()>=(ms*1000+store_start_time_us)){
        store_start_time_us=one_shot? 0: micros();
        return true;
    }
    return false;
}

Categories: Arduino

Tags: ,

PHP Code Snippets Powered By : XYZScripts.com