ESP8266 HW Timer1-MultiTimers Lib
Fulfill the ESP8266 MultiTimers code to as an Arduino library.
將這支多重計時器程式改成 Arduino 格式的函式庫,並且加上一些附屬的函式使其更完備可運用。並簡介之。
請直接看 public functions,或測試範例,相信再加上源碼內的註解,應該很容易理解如何去使用之。此處所用的測試主程式延用先前的 0.91 inch OLED 的那份程式。
而筆者是建議讀者可從前面的文章開始看起,試起,(含本篇共三篇),才更易上手及理解如何去善用之。
這是使用 ESP8266 HW Timer1 單 1 個計時器,模擬出(派生出)2 個至 2^9-1 個計時器的函式庫;理論上。而實際上,若 CPU 時脈提升,計時器中斷酬載降低,則有效計時器個數將能提昇。當前,筆者試過 15 顆計時器同時運作,在某些狀況下(即週期不要太低[>300us],同餘數不要太高[事實上高可能反而好,關鍵是要減少 isr 進出])還是能運作得相當順利。所謂順利,即時間偏移率愈小,週期不亂飄即是。
Esp8266HwSwTimers.h
#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 1 // independent to COMPENSATION_ADV, a little bit income
#define COMPENSATION_ADV 1 // (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();
}
};
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;
};
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
#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;
};
Test Code 測試主程式
#include"Esp8266HwSwTimers.h"
#include <ESP8266WiFi.h> //For ESP8266
#include <ESP8266mDNS.h> //For OTA
#include <WiFiUdp.h> //For OTA
#include <ArduinoOTA.h> //For OTA
#include <Arduino.h>
#include <U8g2lib.h> // make sure to add U8g2 library and restart Arduino IDE
#include <SPI.h>
#include <Wire.h>
#define OLED_SDA 2
#define OLED_SCL 14
#define OLED_RST 4
U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C u8g2(U8G2_R0, OLED_SCL, OLED_SDA , OLED_RST);
const char *text = "U8g2 OTA"; // scroll this text from right to left
//WIFI configuration
#define wifi_ssid "your_ssid"
#define wifi_password "your_password"
#define WiFi_hostname "ESP8266-TTGO"
//Necesary to make Arduino Software autodetect OTA device
WiFiServer TelnetServer(8266);
void setup_wifi() {
delay(100);
Serial.println("");
Serial.print("Connecting to ");
Serial.println(wifi_ssid);
WiFi.hostname(WiFi_hostname);
WiFi.begin(wifi_ssid, wifi_password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.print(" IP address: ");
Serial.println(WiFi.localIP());
Serial.print("Configuring OTA device...");
TelnetServer.begin(); //Necesary to make Arduino Software autodetect OTA device
ArduinoOTA.onStart([]() {Serial.println("OTA starting...");});
ArduinoOTA.onEnd([]() {Serial.println("OTA update finished!");Serial.println("Rebooting...");});
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {Serial.printf("OTA in progress: %u%%\r\n", (progress / (total / 100)));});
ArduinoOTA.onError([](ota_error_t error) {
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
else if (error == OTA_END_ERROR) Serial.println("End Failed");
});
ArduinoOTA.begin();
Serial.println("Wifi OK, OTA Ready on telnet 8266.");
}
char str1[17]; // ******** intended for a wiggling string on display
byte dir1; // ******** intended for a wiggling string on display
// a null-terminated string already in an frmsz-size frm-frame with or without leading spaces will be wiggling in it.
// dir stores 0(forward) or 1(backward, non-zero) represents current direction which will be auto reversed.
void TextInFrameWiggle(byte &dir, byte frmsz, char *frm){
for (int i=0, j=0; i<frmsz; i++){
if (frm[i]!=' '){ // find the 1st non-space character incl. 0
if (dir){ // backward
if (i){
do {frm[i-1]=frm[i];} while (frm[i++]);
return;
}
else dir=0;
}
// forward
j=i;
while (frm[j++]);
if (j-i==frmsz) return; // means no-leading-space string occupies entire frame
if (j==frmsz){ // time to turnaround
--i;
dir=1;
continue;
}
do {frm[j]=frm[j-1];} while (--j!=i);
frm[j]=' ';
return;
}
}
}
unsigned x=0;
unsigned ay1, ay2, ay3, ay4, ay5, ay6;
unsigned times[6]={1500, 1670000, 1670000, 200, 800010, 800000};
float a1, a2, a3, a4, a5, a6;
unsigned p=0, q=0;
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(10)+10);
}
void IRAM_ATTR user2(void)
{
static unsigned z=micros();
ay2=micros()-z;
z=micros();
a2=abs(ay2-times[1])/float(times[1])*100.0f;
x^=0x02;
delayMicroseconds(random(10)+10);
}
void IRAM_ATTR user3(void)
{
static unsigned z=micros();
ay3=micros()-z;
z=micros();
a3=abs(ay3-times[2])/float(times[2])*100.0f;
x^=0x04;
delayMicroseconds(random(10)+10);
}
void IRAM_ATTR user4(void)
{
static unsigned z=micros();
ay4=micros()-z;
z=micros();
a4=abs(ay4-times[3])/float(times[3])*100.0f;
x^=0x08;
delayMicroseconds(random(10)+10);
}
void IRAM_ATTR user5(void)
{
p=micros();
static unsigned z=micros();
ay5=micros()-z;
z=micros();
a5=abs(ay5-times[4])/float(times[4])*100.0f;
x^=0x10;
delayMicroseconds(random(10)+10);
}
void IRAM_ATTR user6(void)
{
q=micros()%(p+1);
static unsigned z=micros();
ay6=micros()-z;
z=micros();
a6=abs(ay6-times[5])/float(times[5])*100.0f;
x^=0x20;
delayMicroseconds(random(10)+10);
}
void (*test[6])()={
user1, user2, user3, user4, user5, user6
};
cHwTimer t[6];
cHwTimer *t0=0;
void setup() {
Serial.begin(115200);
setup_wifi();
u8g2.begin();
strcpy(str1, text); // ******** intended for a wiggling string on display
t[3].setTimer(200, user4);
t[4].setTimer(800010, user5);
t[5].setTimer(800000, user6);
t0=new cHwTimer(1500, user1);
}
int bbb=0;
void pr(float c){ printf("\r\n\r\nxxxxxxxxxxxxxxxxxx % 3.4f%% xxxxxxxxxxxxxxxxxx\r\n\r\n", c*100.f); bbb=2;}
void loop() {
ArduinoOTA.handle();
u8g2.clearBuffer(); // clear the internal memory
u8g2.setFont(u8g2_font_8x13B_mf); // choose a suitable font
TextInFrameWiggle(dir1, 17, str1); // ******** intended for a wiggling string on display
u8g2.drawStr(0,10,str1); // write something to the internal memory
IPAddress myip= WiFi.localIP();
String sFullip = String(myip[0]) + "." + myip[1] + "." + myip[2] + "." + myip[3];
u8g2.drawStr(0,28,sFullip.c_str()); // write something to the internal memory
u8g2.sendBuffer(); // transfer internal memory to the display
if (random(4)==2){
printf("\r\n-=change=-\r\n");
if ((random(3)==1) && !bbb){
static int yy=0;
t0->ProbeAccuracy(yy, 10, pr);
yy+=5;
bbb=1;
}
t[1].setTimer(1670000, user2, false);
t[2].setTimer(1670000, user3, false);
delay(random(100)+50);
t[4].Sync(t[5], 100000);
delay(random(100)+50);
t[5].Sync(t[4], 0);
delay(random(100)+50);
printf("(Max %d, Current %d)\r\n", cHwTimer::numMaxTimers(), cHwTimer::numTimers());
}
if ((random(2)==0) && (bbb==2)){t0->Resume(); bbb=0;}
printf("\r\nsync(%d)\r\n", q);
printf("(Max %d, Current %d)\r\n", cHwTimer::numMaxTimers(), cHwTimer::numTimers());
#if C_HW_TIMER_DEBUG
printf("\r\n[overflow=%d, stay=%d, isr_run=%X]\r\n", hw_timer_overflow, hw_timer_stay_time_us, x);
#endif
printf("[% 5u][% 5u][% 5u][% 5u][% 5u][% 5u]\r\n", times[0], times[1], times[2], times[3], times[4], times[5]);
printf("[% 5d][% 5d][% 5d][% 5d][% 5d][% 5d]\r\n", ay1-times[0], ay2-times[1], ay3-times[2], ay4-times[3], ay5-times[4], ay6-times[5]);
printf("[% 3.1f%%][% 3.1f%%][% 3.1f%%][% 3.1f%%][% 3.1f%%][% 3.1f%%]\r\n", a1, a2, a3, a4, a5, a6);
delay(800);
}
Test code 範例二,500~750us 共 6 個計時器的 error rate
#include"Esp8266HwSwTimers.h"
unsigned x=0;
unsigned ay1;
unsigned times[1]={500};
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_6s(){
static int current_time = millis();
int new_time = millis();
if (new_time < (current_time + 6000)) return false;
current_time = new_time;
return true;
}
cHwTimer t[6]={
cHwTimer(500, user1),
cHwTimer(550, user1),
cHwTimer(600, user1),
cHwTimer(650, user1),
cHwTimer(700, user1),
cHwTimer(750, user1)
};
void setup() {
Serial.begin(115200);
printf("\r\nstarting evaluations...\r\n");
}
int transit=0, idx=0;
void pr(float c){
printf("[% 3.4f%%]", c*100.f);
transit=0;
}
void loop() {
if (Timeout_6s()){
if (!transit){t[idx++].Resume(); transit=1;}
if (transit==1){
if (idx>5) idx=0;
t[idx].ProbeAccuracy(3, 5, pr);
printf("\r\nTimer%d: ", idx+1);
transit=2;
}
}
delay(200);
}
/* output
15:32:45.040 -> Timer2: [ 2.3376%]
15:32:51.041 -> Timer3: [ 2.3491%]
15:32:57.033 -> Timer4: [ 2.3583%]
15:33:03.064 -> Timer5: [ 2.3573%]
15:33:09.061 -> Timer6: [ 2.3605%]
15:33:15.053 -> Timer1: [ 2.3296%]
15:33:21.048 -> Timer2: [ 2.3351%]
15:33:27.073 -> Timer3: [ 2.3508%]
15:33:33.065 -> Timer4: [ 2.3521%]
15:33:39.058 -> Timer5: [ 2.3697%]
15:33:45.082 -> Timer6: [ 2.3718%]
15:33:51.076 -> Timer1: [ 2.3209%]
15:33:57.068 -> Timer2: [ 2.3383%]
15:34:03.093 -> Timer3: [ 2.3505%]
15:34:09.085 -> Timer4: [ 2.3560%]
15:34:15.077 -> Timer5: [ 2.3671%]
15:34:21.102 -> Timer6: [ 2.3617%]
*/