ESP8266 HW Timer1-MultiTimers Lib ver.0.1

No Comments

因為有所增益,所以要改版。同時至少也算是新增功能;新增了一個同步函式。再附上兩支測試程式,從中將發現端倪。並且再試著從 compensation 的兩支開關,來比對差異。
簡單講結論,各 timers 做了同步之後,將使誤差率大幅下降。
其次,從 test1 的測試結果,有平均約 62% 的誤差率折算就是 97us 的週期,如此幾乎可稱,我們可同時擁有 6 支 100us 的計時器,若用於 pwm,以解析度 10% 而言,將有最小是 1K 的頻率:100us(準位最小時間) x 2(high/low) x (100%/10%),得 500Hz,但不用這樣算,而是 100us(準位最小時間) x (1~9)(該 pwm 的 11 種 duty),使得 pwm 的各種 duty 都能由 (i, 10-i) 所構成,即成 1K 頻率。
這在鈍於 1K 頻率的 pwm 裝置下,就算我們的計時器不準確也將遊刃有餘了吧我想。

6 支 60us 的計時器,同步間隔是 10us

// test 1
#include"Esp8266HwSwTimers.h"


unsigned x=0;
unsigned ay1;
unsigned times[1]={60};
float a1;


void IRAM_ATTR user1(void)
{
    static unsigned z=micros();
    ay1=micros()-z;
    z=micros();
    a1=abs(ay1-times[0])/(float)(times[0])*100.0f;
    x^=0x01;
    //delayMicroseconds(random(3)+3);
}

bool Timeout_4s(){
    static int current_time = millis();
    int new_time = millis();
    if (new_time < (current_time + 4000)) return false;
    current_time = new_time;
    return true;
}


cHwTimer t[6]={
    cHwTimer(60, user1),
    cHwTimer(60, user1),
    cHwTimer(60, user1),
    cHwTimer(60, user1),
    cHwTimer(60, user1),
    cHwTimer(60, user1)
};


void setup() {
  Serial.begin(115200);
  printf("\r\nstarting evaluations...\r\n");

    t[0].ForceHaltForSync(t[5], 10);delay(1000);
    t[1].ForceHaltForSync(t[5], 20);delay(1000);
    t[2].ForceHaltForSync(t[5], 30);delay(1000);
    t[3].ForceHaltForSync(t[5], 40);delay(1000);
    t[4].ForceHaltForSync(t[5], 50);delay(1000);
}


int transit=0, idx=0;

void pr(float c){
    printf("[% 3.4f%%]", c*100.f);
    transit=0;
}


void loop() {

    if (Timeout_4s()){
        if (!transit){t[idx++].Resume(); transit=1;}
        if (transit==1){
            if (idx>5) idx=0;
            t[idx].ProbeAccuracy(2, 2, pr);
            printf("\r\nTimer%d: ", idx+1);
            transit=2;
        }
    }

    delay(200);
}

/* output
20:26:13.627 -> Timer1: [ 63.2353%]
20:26:17.645 -> Timer2: [ 62.8995%]
20:26:21.652 -> Timer3: [ 62.5805%]
20:26:25.660 -> Timer4: [ 62.5765%]
20:26:29.701 -> Timer5: [ 61.5397%]
20:26:33.676 -> Timer6: [ 63.2481%]
20:26:37.692 -> Timer1: [ 63.2398%]
20:26:41.703 -> Timer2: [ 62.9563%]
20:26:45.711 -> Timer3: [ 62.5865%]
20:26:49.721 -> Timer4: [ 62.5707%]
20:26:53.727 -> Timer5: [ 61.4916%]
20:26:57.732 -> Timer6: [ 63.2558%]
*/

6 支 60us 的計時器,同步間隔是 4us

// test 2
#include"Esp8266HwSwTimers.h"


unsigned x=0;
unsigned ay1;
unsigned times[1]={60};
float a1;


void IRAM_ATTR user1(void)
{
    static unsigned z=micros();
    ay1=micros()-z;
    z=micros();
    a1=abs(ay1-times[0])/(float)(times[0])*100.0f;
    x^=0x01;
    //delayMicroseconds(random(3)+3);
}

bool Timeout_4s(){
    static int current_time = millis();
    int new_time = millis();
    if (new_time < (current_time + 4000)) return false;
    current_time = new_time;
    return true;
}


cHwTimer t[6]={
    cHwTimer(60, user1),
    cHwTimer(60, user1),
    cHwTimer(60, user1),
    cHwTimer(60, user1),
    cHwTimer(60, user1),
    cHwTimer(60, user1)
};


void setup() {
  Serial.begin(115200);
  printf("\r\nstarting evaluations...\r\n");

    t[0].ForceHaltForSync(t[5], 4);delay(1000);
    t[1].ForceHaltForSync(t[5], 8);delay(1000);
    t[2].ForceHaltForSync(t[5], 12);delay(1000);
    t[3].ForceHaltForSync(t[5], 16);delay(1000);
    t[4].ForceHaltForSync(t[5], 20);delay(1000);
}


int transit=0, idx=0;

void pr(float c){
    printf("[% 3.4f%%]", c*100.f);
    transit=0;
}


void loop() {

    if (Timeout_4s()){
        if (!transit){t[idx++].Resume(); transit=1;}
        if (transit==1){
            if (idx>5) idx=0;
            t[idx].ProbeAccuracy(2, 2, pr);
            printf("\r\nTimer%d: ", idx+1);
            transit=2;
        }
    }

    delay(200);
}

/* output
20:22:35.465 -> Timer1: [ 47.2419%]
20:22:39.474 -> Timer2: [ 47.1608%]
20:22:43.513 -> Timer3: [ 47.3421%]
20:22:47.520 -> Timer4: [ 47.1656%]
20:22:51.560 -> Timer5: [ 46.1488%]
20:22:55.571 -> Timer6: [ 47.2113%]
20:22:59.578 -> Timer1: [ 47.1558%]
20:23:03.622 -> Timer2: [ 47.1396%]
20:23:07.630 -> Timer3: [ 47.0932%]
20:23:11.638 -> Timer4: [ 47.1183%]
20:23:15.648 -> Timer5: [ 46.1475%]
20:23:19.687 -> Timer6: [ 47.1822%]
*/

Esp8266HwSwTimers.h ver.0.1

// Esp8266HwSwTimers.h ver.0.1
#include"ESP8266WiFi.h"
#ifndef _c_HW_TIMER_
#define _c_HW_TIMER_


typedef enum {
    DIVDED_BY_1     = 0,        // timer clock
    DIVDED_BY_16    = 4,        // divided by 16
    DIVDED_BY_256   = 8,        // divided by 256
} time_predived_mode;

typedef enum {                  // timer interrupt mode
    TM_LEVEL_INT    = 1,        // level interrupt
    TM_EDGE_INT     = 0,        // edge interrupt
} time_int_mode;

typedef enum {
    FRC1_SOURCE     = 0,
    NMI_SOURCE      = 1,
} frc1_timer_source_type;

// not special, seems to prevent from calculation overflowing
// the t uses 1us
#define US_TO_RTC_TIMER_TICKS(t)        \
            ((t) ?                      \
            (((t) > 0x35A) ?            \
            (((t)>>2) * ((APB_CLK_FREQ>>4)/250000) + ((t)&0x3) * ((APB_CLK_FREQ>>4)/1000000))  : \
            (((t) *(APB_CLK_FREQ>>4)) / 1000000)) : \
            0)

#define FRC1_ENABLE_TIMER   BIT7
#define FRC1_AUTO_LOAD      BIT6


// multiple timers (max 6) share a single HW Timer.
// timers can be synced at the same time(but not suggest, rather, do in client) and can be offset(1.67s max).
// B syncs to A which means B's isr is going to issued once after B's sync call, at the same time by A's isr issuing(both timing still unaffect).
// B's offset sync call which means the moment A's isr issued, and then offset-value of time past would issue B's isr.
// so, once B's sync called, B's isr would not according to B's timing, once isr being issued will back to normal timing(reloaded).
// since the timeout-reload and isr servicing have the payload around 4us in average(div-16 used).
// the lib constraints user to only between 30us(33.3KHz) to 1.67s(0.6Hz) a safe range, with 1us resolution.
// the timers timeout under a span of time of PAYLOAD_US(4us) will be served as well at a call of main isr.
// here is the scenario, suppose there is B timer within 4us to be served after A having served at this call,
// B will be served as well at this call with a delay. if C got to be served within 4us after B served,
// delay and serve C as well and so on. So there are cases stay at a main ISR call too long, but not too bad.
// so, the minimal period as a constraint for every timer and the max 6 timers could ease such situations.
// such a strategy keeps timing accuracy and minimally affect to entire main system.
// for efficiency, the hw counter reg is 23-bit, we have 9, say, 3 bits(for max 6 timers) as for an identifier tailing to the counter value.
// however, 2^9-1 timers at most could be used in if you willing to try; wdt reset is foreseeing(stay in a single isr too long).


// switches
#define C_HW_TIMER_DEBUG    0
#define SYNC_WAITING        0 // (careful!) if enabled, sync could avoid mem-fault however, timings could affected at a syncing
#define COMPENSATION        0 // independent to COMPENSATION_ADV, a little bit income
#define COMPENSATION_ADV    0 // (careful!) much unstable and probably erroneous. it's rear part of COMPENSATION, but can disabled alone.
#define TIMER_NUM           6 // up to number of timers simul used(recommended max 6, here setting is allowable 15)
#define ID_MASK_VAL         0x07 // to extract id from counter value. note that id is from 1 to TIMER_NUM.(recommended 0x07)
#define ID_MASK_BIT         3 // number of bits to extract id from counter value(recommended 3 bits)
#define PAYLOAD_US          4 // payload in us to subtract(recommended 4us)
#define INTRUDE_TIME_CNT    100 // the counts(20us*5) safely/enough-time for access data when syncing
#define LO_BOUND_US         30
#define UP_BOUND_US         1670000

// no need to modify
#define PAYLOAD_CNT         ((PAYLOAD_US)*5) // payload in counts to subtract, since 1us=5x0.2us; 1us has 5 counts
#define LO_BOUND_CNT        ((LO_BOUND_US)*5)
#define UP_BOUND_CNT        ((UP_BOUND_US)*5)
#define OP_GET_CNT(x)       ((x)>>ID_MASK_BIT)
#define OP_GET_ID(x)        ((x)&ID_MASK_VAL)
#define OP_MERGE_CNTID(cnt, id) (((cnt)<<ID_MASK_BIT)|(id))
#define OP_HEAP_TOP_CNT     OP_GET_CNT(timer_obj.current[1])
#define OP_HEAP_TOP_ID      OP_GET_ID(timer_obj.current[1])
#define CNT_TO_US(x)        (((x)+2)/5)
#define US_TO_CNT(x)        ((x)+((x)<<2))


#if C_HW_TIMER_DEBUG
    extern int hw_timer_overflow, hw_timer_stay_time_us1, hw_timer_stay_time_us;
#endif

#if SYNC_WAITING
    extern int sync_spin; // using bool is inhibited by IxRAM_ATTR
#endif


class cHwTimer{

#if C_HW_TIMER_DEBUG
    public: // remove it when formal use; it is only for debug and print
#endif

    typedef void (*void_func1)();
    typedef void (*void_func2)(float);
    typedef struct{
        unsigned reload[TIMER_NUM+1];           // num of counts for each timer to be reloaded(payload is evaled).
                                                // since we support one-shot, it's better as to use this array
                                                // and the reload[0] to be always 0 for the procedure reloading 0,
                                                // which automatically stop.
                                                // simply to say, set reload[i] to 0(not 0, should use loc to deallocate) or
                                                // the current[i] being reloaded from reload[0] yields current[i] stop counting.
        unsigned current[TIMER_NUM+1];          // current nit counts for each timer to be reloaded(incl. payload is evaled).
                                                // this array is dedicated for using in heap funcs,
                                                // so, the current[0] is the current number of running timers.
                                                // timers occupy current[1] to current[number_of_timers],
                                                // which are current[1] to current[ current[0] ].
                                                // note that this array is entirely maintained by heap funcs called in main isr,
                                                // but there are cases we have to intrude on this array(danger, by syncing funcs).
        void_func1 isr[TIMER_NUM+1];            // callback functions.
                                                // the up two arrays have distinct purpose of the [0] element,
                                                // so is the isr[0] which used for the current isr going to run.
                                                // Besides, isr[i] equals zero uniquely denotes the timer slot is empty.
    } sHwTimerObj;

    typedef struct{
        unsigned load_us;                       // the load in microseconds.
        unsigned in_use;                        // the loc id.
        unsigned run_seconds;                   // how long for run.
        unsigned cnt;                           // for average calculation.
        unsigned var1;                          // for static storage, cpu time.
        void_func2 probe_callback;              // main-loop's callback function with one argument.
        float error_rate;                       // for accumulation and average.
    } sTimerProber;

        static sHwTimerObj timer_obj;
        static sTimerProber timer_prober;
        static void main_isr();
        static void default_isr();
        static void prober_isr();
        static void min_heap_insertion(unsigned key, unsigned *t);
        static unsigned min_heap_removal(unsigned *t);

        char loc;                               // identifies loc in array, please note that the loc starts from 1 to TIMER_NUM.
                                                // loc 0 is reserved for one-shot identification.
                                                // loc -1 identifies for uninitialized value.
        void_func1 func_for_pause;

        void arm(unsigned period_us, void(*isr)(), bool periodically){
            for (unsigned i=1; i<=TIMER_NUM; i++){

                // please especially note that we use isr[i] to identify whether a timer slot is empty.
                // if isr[i]==0 means empty; else if ==default_isr means pause, else means running timer for servicing.
                // and we use who uses reload[0] as to identify one-shot(the id field is 0 and reload from reload[0] which is 0)
                
                if (!timer_obj.isr[i]){ // search for an empty slot
                    unsigned cnt=US_TO_RTC_TIMER_TICKS(period_us-PAYLOAD_US);
                    timer_obj.isr[i]=isr;
                    func_for_pause=isr;
                    cnt=OP_MERGE_CNTID(cnt, i);
                    if (periodically) timer_obj.reload[i]=cnt;
                    else timer_obj.reload[i]=i; // id without count is used to deallocate when it reloads, for one-shot.
                    loc=i;
                    min_heap_insertion(cnt, timer_obj.current);
                    return;
                }
            }
        };

        void set(unsigned period_us, void(*isr)(), bool periodically){
            loc=-1;
            func_for_pause=default_isr;
            if (!isr || period_us<LO_BOUND_US || period_us>UP_BOUND_US || (timer_obj.current[0]==TIMER_NUM)) return;

            arm(period_us, isr, periodically);

            if (!(RTC_REG_READ(FRC1_CTRL_ADDRESS)&FRC1_ENABLE_TIMER)){ // timer is disabled, regarded as a brand new use.

                timer_obj.isr[0]=default_isr;
                
                ETS_FRC_TIMER1_INTR_ATTACH(main_isr, NULL);
                /////ETS_FRC_TIMER1_NMI_INTR_ATTACH(main_isr);
                RTC_REG_WRITE(FRC1_CTRL_ADDRESS, FRC1_ENABLE_TIMER /*| FRC1_AUTO_LOAD*/ | DIVDED_BY_16 | TM_EDGE_INT);
                TM1_EDGE_INT_ENABLE();
                ETS_FRC1_INTR_ENABLE();
            }
        };

        static void emergent_shutdown(){
            // stop the counter reg counting
            RTC_REG_WRITE(FRC1_CTRL_ADDRESS, RTC_REG_READ(FRC1_CTRL_ADDRESS)&~(FRC1_ENABLE_TIMER));
        };

        static void safe_resume(){
            // continue from emergent_shutdown
            RTC_REG_WRITE(FRC1_CTRL_ADDRESS, RTC_REG_READ(FRC1_CTRL_ADDRESS)|(FRC1_ENABLE_TIMER));
        };

        const cHwTimer& operator=(const cHwTimer &a){loc=a.loc; func_for_pause=a.func_for_pause; return *this;};

    public:
        cHwTimer(): loc(-1), func_for_pause(default_isr){};
        cHwTimer(const cHwTimer &a): loc(a.loc), func_for_pause(a.func_for_pause){};
        cHwTimer(unsigned period_us, void(*isr)(), bool periodically=true){set(period_us, isr, periodically);};

        bool setTimer(unsigned period_us, void(*isr)(), bool periodically=true){
            if (isActive()) Stop();
            delay(UP_BOUND_US/1000); // one-shot is not active, however any timer needs to flush.
            set(period_us, isr, periodically);
            if (loc<=0) return false;
            return true;
        };

        void ForceHaltForSync(const cHwTimer &ref, unsigned offset_delay_us){
            if (loc>0 && ref.loc>0){
                unsigned i, k;
                k=US_TO_CNT(offset_delay_us);

                // since we use non-auto reload mode, it's a reasonable treatment to read and write
                // counter's reg with the same value for restart.

                emergent_shutdown();
                unsigned a=RTC_REG_READ(FRC1_LOAD_ADDRESS);
 
                    if (i=timer_obj.current[0]){
                        unsigned m, n, z;
                        while (i){
                            z=OP_GET_ID(timer_obj.current[i]);
                            if (z==ref.loc) m=i;
                            else if (z==loc) n=i;
                            --i;
                        }
                        if ((z=OP_GET_CNT(timer_obj.current[m])+k)<=UP_BOUND_CNT){
                            timer_obj.current[n]=OP_MERGE_CNTID(z+k, loc);
                        }
                    }

                safe_resume();
                RTC_REG_WRITE(FRC1_LOAD_ADDRESS, a);
            }
        };

        bool Sync(const cHwTimer &ref, unsigned offset_delay_us){
            // this func might fail, if so, affect nothing
            // this func might fault, if so, mem-fault or wdt-reset
            if (loc>0 && ref.loc>0){
                
                // this step can be dangerous or long-hold because main isr may alter this array at the same time.
                unsigned i, j, k;
                k=US_TO_CNT(offset_delay_us)+1;
                j=10; // for at most delay 200us, which only affect this object is never mind.

                do {
                    // there is a threshold time to ensure we have enough time to do something.
                    // so we check the minimum time of the upcoming timer, which longer than 20us.
                    // in the following if-condition, we can use flag to prevent main isr from access array and fault,
                    // however main isr spin-waiting could disturb all timer timings, of course could wdt-reset.

                    #if SYNC_WAITING
                        sync_spin=true;
                    #endif

                    if ((i=timer_obj.current[0]) && (OP_HEAP_TOP_CNT>INTRUDE_TIME_CNT)){
                        unsigned m, n, z;
                        while (i){
                            z=OP_GET_ID(timer_obj.current[i]);
                            if (z==ref.loc) m=i;
                            else if (z==loc) n=i;
                            --i;
                        }
                        if ((z=OP_GET_CNT(timer_obj.current[m])+k)<=UP_BOUND_CNT){
                            timer_obj.current[n]=OP_MERGE_CNTID(z+k, loc); // need not count on payload

                            #if SYNC_WAITING
                                sync_spin=false;
                            #endif

                            return true;
                        }

                        #if SYNC_WAITING
                            sync_spin=false;
                        #endif

                    }

                    #if SYNC_WAITING
                        sync_spin=false;
                    #endif

                    delayMicroseconds(20);
                } while (--j);
            }
            return false;
        };

        bool ProbeAccuracy(unsigned isr_loading_us, unsigned run_seconds, void (*callback)(float result)){
            // assign a loading with how many isr_loading_us of time spent to run some isr.
            // how long to run this probe by run_seconds.
            // when timeout, callback would be called with percentage argument. user can print it out the percentage.
            // *** note that only for running timers and one probe at a time.
            // *** especially note that after callback being called, using Resume() to get back original sub-isr.

            if (!isRunning() || (timer_prober.in_use>0)) return false;
            timer_prober.load_us=isr_loading_us;
            timer_prober.in_use=loc;
            timer_prober.probe_callback=callback;
            timer_prober.cnt=0;
            timer_prober.error_rate=0;
            timer_prober.run_seconds=micros()+run_seconds*1000000;
            timer_prober.var1=micros();
            timer_obj.isr[loc]=prober_isr;
            return true;
        };

        bool isActive(){return (loc>0) && (timer_obj.reload[loc]>loc);}; // is valid timer

        bool isRunning(){return (loc>0) && timer_obj.isr[loc] && (timer_obj.isr[loc]!=default_isr);}; // is timer servicing

        bool Pause(){
            
            #if C_HW_TIMER_DEBUG
                printf("\r\n\r\ntimer(%d) paused\r\n\r\n", loc);
            #endif
            
            if (isRunning()){timer_obj.isr[loc]=default_isr; return true;}
            return false;
        };

        bool Resume(){
            
            #if C_HW_TIMER_DEBUG
                printf("\r\n\r\ntimer(%d) resumed\r\n\r\n", loc);
            #endif
            
            if (isActive()){timer_obj.isr[loc]=func_for_pause; return true;}
            return false;
        };

        bool Stop(){

            #if C_HW_TIMER_DEBUG
                printf("\r\n\r\ntimer(%d) stopped\r\n\r\n", loc);
            #endif

            if (loc>0){
                timer_obj.reload[loc]=loc;
                if (timer_prober.in_use==loc) timer_prober.in_use=0;
                loc=-1;
                return true;
            }
            return false;
        };

        static unsigned numMaxTimers(){return TIMER_NUM;};

        static unsigned numTimers(){
            int i, j;
            for (i=1, j=0; i<=TIMER_NUM; i++) if (timer_obj.isr[i]) j++;
            return j;
        };

#if !C_HW_TIMER_DEBUG
        ~cHwTimer(){Stop();}; // if weird, mark this(cases for test only), since destructor possibly distroys static vars as well.
#endif

        // the following 3 funcs are user functions for easy access,
        // in addition, if you want to not bind to objects,
        // you can comment off the destructor,
        // such that timers can still run even objects are destructed.
        // such case is suitable for one-shot test; non-oneshot hence has to be stopped explicitly.
        static void StopAll(){for (int i=1; i<=TIMER_NUM; i++) timer_obj.reload[i]=i;};
        static void PauseAll(){for (int i=0; i<=TIMER_NUM; i++) timer_obj.isr[i]=default_isr;}; // main isr may access at the same time.
        static void ResumeAll(){/*can not resume all*/};
};


#endif // _c_HW_TIMER_

Esp8266HwSwTimers.cpp ver.0.1

// Esp8266HwSwTimers.cpp ver.0.1
#include"Esp8266HwSwTimers.h"

#if C_HW_TIMER_DEBUG
    IRAM_ATTR int hw_timer_overflow, hw_timer_stay_time_us1, hw_timer_stay_time_us;
#endif

#if SYNC_WAITING
    IRAM_ATTR int sync_spin=false; // using bool is inhibited by IxRAM_ATTR
#endif

IRAM_ATTR cHwTimer::sHwTimerObj cHwTimer::timer_obj={
    {0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0},
    {0, 0, 0, 0, 0, 0, 0}
};

IRAM_ATTR cHwTimer::sTimerProber cHwTimer::timer_prober={
    0, // load_us
    0, // in_use
    0, // run_seconds
    0, // cnt
    0, // var1
    0, // probe_callback
    0  // error_rate
};

IRAM_ATTR void cHwTimer::default_isr(){
    #if C_HW_TIMER_DEBUG
        printf("\r\n-\r\n");
    #endif
}

IRAM_ATTR void cHwTimer::prober_isr(){

    #if C_HW_TIMER_DEBUG
        printf("\r\nprober\r\n");
    #endif

    unsigned ref=micros()-timer_prober.var1;
    timer_prober.var1=micros();
    ref=US_TO_CNT(ref);

    unsigned diff=OP_GET_CNT(timer_obj.reload[timer_prober.in_use]);
    if (diff>ref) diff-=ref;
    else diff=ref-diff;

    timer_prober.cnt++;
    timer_prober.error_rate+=float(diff)/float(OP_GET_CNT(timer_obj.reload[timer_prober.in_use]));

    diff=micros();
    if (timer_prober.run_seconds>(diff+timer_prober.load_us)){
        diff-=timer_prober.var1;
        if (timer_prober.load_us>diff) delayMicroseconds(timer_prober.load_us-diff);
    }
    else {
        timer_prober.probe_callback(timer_prober.error_rate/float(timer_prober.cnt));
        timer_obj.isr[timer_prober.in_use]=default_isr;
        timer_prober.in_use=0;
    }
}

IRAM_ATTR void cHwTimer::main_isr(){

    #if C_HW_TIMER_DEBUG
        hw_timer_stay_time_us1=micros();
        static bool gate=false;
        if (gate){
            hw_timer_overflow++;
            gate=false;
        }
        else gate=true;
    #endif

    #if SYNC_WAITING
        while (sync_spin) yield();
    #endif

    unsigned discard_min, discard_min_cnt, i, j, diff;

#if 0 ///// read the old version to know about what new version does ///// this is old version
        while (timer_obj.current[0] && (OP_HEAP_TOP_CNT<=PAYLOAD_CNT)){////// old version /////////
            timer_obj.isr[0]();
            
            ////delayMicroseconds(CNT_TO_US(OP_HEAP_TOP_CNT));
            diff=micros()+CNT_TO_US(OP_HEAP_TOP_CNT);/////
            
            discard_min=min_heap_removal(timer_obj.current); // discard the used min one
            timer_obj.isr[0]=timer_obj.isr[OP_GET_ID(discard_min)]; // update the isr, which will run next time
            discard_min_cnt=OP_GET_CNT(discard_min); // it will join to calculation

            for (i=timer_obj.current[0]; i; i--){ // update all counters
                j=OP_GET_ID(timer_obj.current[i]); // get an id which needs to update
                timer_obj.current[i]=OP_MERGE_CNTID(OP_GET_CNT(timer_obj.current[i])-discard_min_cnt, j);
            }

            // add the new one that just discard
            i=timer_obj.reload[OP_GET_ID(discard_min)];
            if (i>TIMER_NUM) min_heap_insertion(i, timer_obj.current); // i possibly be 0 or id which are for one-aspect tackling
            else timer_obj.isr[OP_GET_ID(discard_min)]=0; // it must be the one-shot or stopped timer which must deallocated

            if (micros()<(diff-2)) delayMicroseconds(diff-micros());/////

            #if C_HW_TIMER_DEBUG
                else hw_timer_overflow++;
            #endif

        }////// old version /////////
#else ///// read the old version to know about what new version does ///// this is new version
        if (timer_obj.current[0] && (OP_HEAP_TOP_CNT<=PAYLOAD_CNT)){////// new version /////////
            discard_min_cnt=0;

            #if (COMPENSATION || COMPENSATION_ADV)
                unsigned compensation=0;
            #endif

            while (timer_obj.current[0] && (OP_HEAP_TOP_CNT<=(PAYLOAD_CNT+discard_min_cnt))){
                
                timer_obj.isr[0]();
                
                diff=micros()+CNT_TO_US(OP_HEAP_TOP_CNT-discard_min_cnt); // subtract past-time equals to amount time need to spend
                // (since cnt & us are not integer-convertable, so the diff value may smaller than what we want; where)
                // (as larger is ok, smaller means sub-isr will run in advance, but, still ok)
                
                discard_min=min_heap_removal(timer_obj.current); // discard the used min one
                j=OP_GET_ID(discard_min);
                timer_obj.isr[0]=timer_obj.isr[j]; // update the isr, which will run next time
                discard_min_cnt=OP_GET_CNT(discard_min); // update the past-time that is going to be(to subtract outside this loop)

                // add the new one that just discard
                i=timer_obj.reload[j];

                //// what if j and OP_GET_ID(i) are not equal?
                ////if (i>TIMER_NUM) min_heap_insertion(OP_MERGE_CNTID(OP_GET_CNT(i)+discard_min_cnt, OP_GET_ID(i)), timer_obj.current);
                if (i>TIMER_NUM) min_heap_insertion(OP_MERGE_CNTID(OP_GET_CNT(i)+discard_min_cnt, j), timer_obj.current);
                else timer_obj.isr[j]=0; // it must be the one-shot or stopped timer which must deallocated

                #if COMPENSATION
                ///////////////////////////////////////////////
                    if (micros()<(diff-3)){ // if here takes runtime more than 3us then will collapse!!!
                        if (compensation<(diff-3-micros())){
                            delayMicroseconds(diff-micros()-compensation);
                            compensation=0;

                        #if C_HW_TIMER_DEBUG
                            hw_timer_overflow--;
                        #endif

                        }
                        else compensation-=(diff-micros());
                    }
                    else {
                        if (micros()>diff) compensation+=(micros()-diff); // compensate only if greater

                    #if C_HW_TIMER_DEBUG
                        hw_timer_overflow++;
                    #endif

                    }
                    /// if true, you can compensate the previous ones who disturb later timers which means run this timer in advance,
                    /// however it's complicated. if false, it's the ones who will disturb later ones and need to accumulate the penalties.
                    /// since all are under PAYLOAD_CNT of time, you have a few us for compete and all are likely willing to over time.
                    /// and if penalty did calculated, it could be subtracted at the below for-loop as well as the above delay.
                ///////////////////////////////////////////////
                #else
                    if (micros()<(diff-2)) delayMicroseconds(diff-micros());

                    #if C_HW_TIMER_DEBUG
                        else hw_timer_overflow++;
                    #endif

                #endif

            }

            #if COMPENSATION_ADV
                compensation=US_TO_CNT(compensation); // convert us to cnt
            #endif

            for (i=timer_obj.current[0]; i; i--){ // update all counters
                j=OP_GET_ID(timer_obj.current[i]); // get an id which needs to update
                // since the discard_min_cnt which is a past-time not updated yet(postponed) in entire current[], so we do it now

            #if COMPENSATION_ADV
                // remaining compensation if any still needs to compensate in the entire current[].
                // however, (guess)this is a decremental step which yields many sw timers got 0 in current[] for instant triggering,
                // which caused hw timer repeatedly entering main-isr without rest which severely causing memory access fault;
                // that is, read/write from current[] would probably be wrong value.
                // in this aspect, you can observe here the current[i] value before and after, cases exist later larger than former.
                // or directly dump current[] in main loop.
                // in such case means overloading which is solved by accordingly, e.g., using less timers or sub-isr less monopolized.
                // note again, at if-condition, timer_obj.current[i] could get bigger value which is not signed/unsigned problem?!
                
                if (compensation<(OP_GET_CNT(timer_obj.current[i])-discard_min_cnt))
                    timer_obj.current[i]=OP_MERGE_CNTID(OP_GET_CNT(timer_obj.current[i])-discard_min_cnt-compensation, j);

                else {
                    //// else means needs to run immedly, how can i do, i choose do nothing.
                    //// timer_obj.current[i]=OP_MERGE_CNTID(8, j); // may wrong because others in it may less than 8.
                    //// some trials caused wdt-reset, so give up this and use below.
                    //// are aware or not, compensation still can compensate some other timers in current[], there did exist.
                    //// timer_obj.current[i]=j; // set cnt to 0. starvation possible, wdt-reset. if so comment this and use below.
                    timer_obj.current[i]=OP_MERGE_CNTID(OP_GET_CNT(timer_obj.current[i])-discard_min_cnt, j);

                    #if C_HW_TIMER_DEBUG
                        hw_timer_overflow++;
                    #endif

                }
            #else
                timer_obj.current[i]=OP_MERGE_CNTID(OP_GET_CNT(timer_obj.current[i])-discard_min_cnt, j);
            #endif

            }
        }////// new version /////////
#endif ///////////////////////////////////////////////////////////////////////////// change version

    if (timer_obj.current[0]){

        RTC_REG_WRITE(FRC1_LOAD_ADDRESS, OP_HEAP_TOP_CNT); // load the current min asap

        timer_obj.isr[0](); // service the last time of isr asap, which means needs to update var this time

        discard_min=min_heap_removal(timer_obj.current); // discard the used min one
        j=OP_GET_ID(discard_min);
        timer_obj.isr[0]=timer_obj.isr[j]; // update the isr, which will run next time
        discard_min_cnt=OP_GET_CNT(discard_min); // it will join to calculation
        
        for (i=timer_obj.current[0]; i; i--){ // update all counters
            timer_obj.current[i]=OP_MERGE_CNTID(OP_GET_CNT(timer_obj.current[i])-discard_min_cnt, OP_GET_ID(timer_obj.current[i]));
        }

        // add the new one that just discard
        i=timer_obj.reload[j];

        if (i>TIMER_NUM) min_heap_insertion(i, timer_obj.current); // i possibly be 0 or id which are for one-aspect tackling
        else timer_obj.isr[j]=0; // it must be the one-shot or stopped timer which must deallocated
    }
    else { // if here to do, no timer remains, the last one timer which belongs to this isr has been deallocated properly
        timer_obj.isr[0]();
        timer_obj.isr[0]=default_isr; // since hw timer still counting and intring

        RTC_REG_WRITE(FRC1_CTRL_ADDRESS, RTC_REG_READ(FRC1_CTRL_ADDRESS)&~(FRC1_ENABLE_TIMER)); // disable counter reg
        TM1_EDGE_INT_DISABLE(); // disable int
    }

    #if C_HW_TIMER_DEBUG
        gate=false;
        hw_timer_stay_time_us=micros()-hw_timer_stay_time_us1;
    #endif

}

IRAM_ATTR void cHwTimer::min_heap_insertion(unsigned key, unsigned *t){ // t[0] must be the current size
    unsigned i=++t[0], j=(i>>1);
    for (; j && t[j]>key; t[i]=t[j], i=j, j>>=1);
    t[i]=key;

    // should use OP_GET_CNT(t[]) however, thinking about same count with diff id, A(111-001), B(111-000),
    // A is larger than B(or say B is smaller than A), no matter which one larger,
    // we ignore this case which still holds heap property since A==B in this fact. (in another words,)
    // (either A or B is larger, will be maintained, however A==B need not maintained by heap even it's maintained.)
    // and the other case A(111-uvw), B(110-xyz), no matter uvwxyz are, all lead to A>B which holds heap property.
    // therefore we needn't use OP_GET_CNT(t[]) within this func.
};

IRAM_ATTR unsigned cHwTimer::min_heap_removal(unsigned *t){ // t[0] must be the current size
    unsigned key=t[1];
    unsigned j=2, i=1, k=t[0]--;
    for (; j<k; i=j, j<<=1){
        if (t[j]>t[j+1]) ++j;
        t[i]=t[j];
    }
    
    for (j=(i>>1); j && t[j]>t[k]; t[i]=t[j], i=j, j>>=1);
    t[i]=t[k];
    
    return key;
};

Categories: Arduino

Tags: ,

發佈留言

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

PHP Code Snippets Powered By : XYZScripts.com