ESP8266 MultiPWMs ver.0.5
全新改版。並又比前一版更完善了,但這句話好像在前面各版都說過XD。不過走到這版,“完善”這句話也不想也不會再說了。因為是真的有下一版;目前本版作 sync 後,波形還是會飄,筆者還查不出為什麼;自己若說沒問題是示波器的問題就自己先打兩耳光再說XD。真的目前沒譜待下篇文章分曉。。。希望有下篇文章。。。一定有的,沒改版也會抓出問題點!本版細節請見扣內說明。相信本版,您用了嘴角都會不自覺上揚。。。把 ESP8266 的能力又提升了一階。。。前提是 BUG 有抓出來!筆者就附上支影片,看了就會笑。。。此主程式是有 16 支 PWM waveforms 同時在跑喔。。。
本版隨附主程式的 demo1
本版隨附主程式的 demo2
// Esp8266MultiPwms ver.0.5
// https://waterfalls.ddns.net
// by Ken Woo
// 2020.12.13
/*
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.
todo. a. add sync function for new object.
a1. i.e., currently has upon stopped object implemented.
a2. for dynamically synced is hard to the code structure, mind no scheme to impl.
b. avoid to allocate memory on external ram or flash.
known bugs. a. q_offset becomes period-q_offset or deviated.
*/
// PWM class: using ESP82366 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 2.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.
#ifndef _c_MULTI_PWMS_H_
#define _c_MULTI_PWMS_H_
#include"Esp8266HwSwTimers.h"
#endif // _c_MULTI_PWMS_H_
// switches
#define C_MULTI_PWMS_DEBUG 0
#define MULTIPWMS_PREVENT_WDT_RESET 1
#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 p, q, w, w1;
IRAM_ATTR int syncoffset_d[16]; // after test, it is more analogy to oscope, so fine ref.
#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;
sNode *obj;
unsigned mass1;
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){ // add to proper ISR position, return the position.
unsigned i=0, j=0, pos=0, min=-1;
for (; i<4; i++){
if (pwms[i]){
if ((j=node_get_length(pwms[i]))<min){
pos=i;
min=j;
}
}
else {pos=i; break;} // empty
}
node_add(&(pwms[pos]), a);
return pos;
};
bool 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.
bool tmp=!!((owner->data).stall_h_or_l);
(owner->data).stall_h_or_l=!!high_or_low;
return tmp;
};
void 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* getMemory(int obj_size){ // use sizeof(unsigned) and alignment; little endian.
int a=(obj_size+sizeof(unsigned)-1)/sizeof(unsigned);
void *b=malloc(sizeof(unsigned)*a);
if (b && (unsigned(b)/sizeof(unsigned)*sizeof(unsigned)==unsigned(b))){
for (; a--; ((unsigned*)b)[a]=0);
return b;
}
free(b);
return 0;
};
void freeMemory(void *b){free(b);};
bool ack(){
// 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.
(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(){(owner->data).freeze=1; (owner->data).accepted=0;}; // nonblocking
void stop(){(owner->data).stopit=1; (owner->data).accepted=0;}; // nonblocking
void renew(){(owner->data).is_new_reload=1; (owner->data).accepted=0;}; // nonblocking
void aside_duty(){(owner->data).is_aside_duty=1; (owner->data).accepted=0;}; // nonblocking
void isr_sneak(){ // let client code spin waiting for isr leaved, used for critical data accessing(50us).
if (!timers[0].isRunning()) return;
isr_key=1;
while (isr_key) delayMicroseconds(5); // the delay is must or wdt-reset.
};
static void 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=1;
delayMicroseconds(25); // wait for isr entered.
int time_cnt=10;
while (stop_all<2 && time_cnt--) delayMicroseconds(25); // no patient to wait
}
// ----
#if C_MULTI_PWMS_DEBUG
halt=false;
#endif // C_MULTI_PWMS_DEBUG
};
static void 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
// ---- 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
#if C_MULTI_PWMS_DEBUG
halt=false;
#endif // C_MULTI_PWMS_DEBUG
};
static void 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=1;
while (stop_all<2) delayMicroseconds(100); // the delay is must or wdt-reset.
}
// ----
#if C_MULTI_PWMS_DEBUG
halt=false;
#endif // C_MULTI_PWMS_DEBUG
};
static void 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
// ---- 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
#if C_MULTI_PWMS_DEBUG
halt=false;
#endif // C_MULTI_PWMS_DEBUG
};
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:
cMultiPwms(): owner(0), uid(0), stop_h_l(0){};
~cMultiPwms(){
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);
};
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 || !a.owner) return;
configPwm(0, a.period, a.steps, ((a.owner)->data).is_inverted, a.stop_h_l);
};
const cMultiPwms& operator=(const cMultiPwms &b){ // it is the same as copy-constructor, only for not initial yet.
if (!b.uid || !b.owner || this->uid || this->owner) return *this;
configPwm(0, b.period, b.steps, ((b.owner)->data).is_inverted, b.stop_h_l);
return *this;
};
bool 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.
if (period_us%200 || !set_steps || period_us%set_steps || period_us/set_steps%50 || period_us>13000000) return false;
if (uid || owner) return false;
owner=(sNode*)getMemory(sizeof(sNode));
if (!owner) return false;
period=period_us;
steps=set_steps;
stop_h_l=stop_high;
uid=++gid;
if (gid>=31){ //// the last one. not fault but is going to fault.
printf("\r\ndepleted\r\n");
freeMemory(owner);
uid=0;
owner=0;
return false;
}
(owner->data).id=uid;
(owner->data).is_inverted=!!inverted;
(owner->data).is_high_level=0; // at the outset, it is going to high, so it is under low before beginning.
(owner->data).stall_h_or_l=0; // default is 0 unless explicitly specified by setStallLevel within this code.
setGpio(gpio);
(owner->data).accepted=0; // for stall
(owner->data).freeze=1; // paused
(owner->data).tr_high_pos=wellAdd(owner); // at the outset, raise high then immed change pos to tr_low_pos.
return true;
};
bool setGpio(unsigned gpio){
if (!uid) return false;
(owner->data).gpio=gpio;
return true;
};
bool setInvert(bool invert_or_not){
if (!uid) return false;
(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){ // will by steps if it is nonzero
// you would see that any PWM would start to work after a setDC is called.
if (!uid) 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=1; // 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=1; // fake it to be duty low long enough.
}
else (owner->data).is_aside_duty=0;
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;
}
(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=(this->period/50-set_by_steps+3)/4;
renew();
// priorly we did not stall however we still need resume for new object.
Resume();
if (!timers[0].isActive()) configTimer();
return true;
};
bool setPeriod(unsigned new_period, unsigned new_steps, float percentage, unsigned set_by_steps=0){ // 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=1; // 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=1; // fake it to be duty low long enough.
}
else (owner->data).is_aside_duty=0;
int pos=(owner->data).tr_high_pos;
int tr_pos=set_by_steps%4;
int count=set_by_steps/4;
if (!tr_pos) tr_pos=pos;
else {
count++;
tr_pos+=pos;
if (tr_pos>3) tr_pos-=4;
}
(owner->data).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=(this->period/50-set_by_steps+3)/4;
renew();
// priorly we did not stall however we still need resume for new object.
Resume();
if (!timers[0].isActive()) configTimer();
return true;
};
// the pause, stop and resume are paired use.
void Pause(){(this->owner->data).stall_h_or_l=this->stop_h_l; this->pause();}; // nonblocking
void Stop(){(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(){
#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("\r\nID#(1, 2) offset=%uus.\r\n", w);
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 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);
(owner->data).tr_low_pos=((owner->data).tr_low_pos+offset_us)%4;
(owner->data).counter=((base.owner)->data).counter+offset_us/4; // counter beyond and behind and mid?
// fix the problem that isr position could affect counter value.
unsigned c=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;
};
static bool 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->obj=base.owner; // store node address
memo_park->next=0; // store next sGcNode address
return true;
};
static bool SyncNext(cMultiPwms &follower, unsigned offset_us){
if (!follower.uid || !follower.owner) return false;
if ((follower.period<=offset_us) || offset_us%50) return false;
if (!memo_park) return false;
sGcNode *a=(sGcNode*)follower.getMemory(sizeof(sGcNode));
if (!a) return false;
a->tmp_offset=offset_us/50;
a->tmp_stop_h_l=follower.stop_h_l;
a->obj=follower.owner;
a->next=0;
follower.node_add((sNode**)(&memo_park), (sNode*)(a));
return true;
};
static bool SyncEnd(cMultiPwms &base){ // must specify the base actually for me to use non-static funcs.
if (!base.uid || !base.owner || !memo_park) return false;
int len=base.node_get_length((sNode*)(memo_park));
if (memo_park->obj!=base.owner) return false; // still chances
if (len==1){ // no chance
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.
(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;
do delayMicroseconds(200); while (!(iter1->obj->data).accepted);
delay(2); // if no it, frequently exception(28) wdt-rst(2). i do not know why!!!
}
// 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);
(iter->obj->data).tr_low_pos=((iter->obj->data).tr_low_pos+iter->tmp_offset)%4;
(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 static void trigger(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,
// or the other level-end.
}
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
w1=micros();
if (((*u)->data).id==1) p=w1;
else if (((*u)->data).id==2) q=w1;
if (q>=p) w=q-p;
else w=p-q;
if (((*u)->data).id==1) syncoffset_d[0]=micros();
else syncoffset_d[((*u)->data).id-1]=micros()-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;
};
#if !C_MULTI_PWMS_DEBUG
IRAM_ATTR static void 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 static void 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 static void 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 static void 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 static void 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 static void 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 static void 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 static void 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
};
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;
#if 0
cMultiPwms sss[16]={
{3, 800, 16},
{5, 800, 16},
{12, 800, 16},
{13, 800, 16},
/*{1, 120000, 10},
{1, 120000, 10},
{1, 120000, 10},
{1, 120000, 10},
{1, 322000, 10},
{1, 332000, 10},
{1, 342000, 10},
{1, 352000, 10},
{1, 1200, 4},
{1, 400, 4},
{1, 1600, 4},
{1, 200, 4},*/
};
bool Timeout(int seconds){
static int current_time = millis();
int new_time = millis();
if (new_time < (current_time + seconds*1000)) return false;
current_time = new_time;
return true;
}
void setup() {
Serial.begin(115200);
pinMode(3, OUTPUT);
pinMode(5, OUTPUT);
pinMode(12, OUTPUT);
pinMode(13, OUTPUT);
delay(5000);
unsigned a=micros();
sss[0].setDC(0, 4);
sss[1].setDC(0, 4);
sss[2].setDC(0, 4);
sss[3].setDC(0, 4);
/*sss[4].setDC(0, 1);
sss[5].setDC(0, 2);
sss[6].setDC(0, 3);
sss[7].setDC(0, 4);
sss[8].setDC(0, 5);
sss[9].setDC(0, 6);
sss[10].setDC(0, 7);
sss[11].setDC(0, 8);
sss[12].setDC(0, 2);
sss[13].setDC(0, 1);
sss[14].setDC(0, 3);
sss[15].setDC(0, 1);*/
unsigned b=micros();
cMultiPwms::SyncStart(sss[0]);
cMultiPwms::SyncNext(sss[1], 50);
cMultiPwms::SyncNext(sss[2], 100);
cMultiPwms::SyncNext(sss[3], 150);
cMultiPwms::SyncEnd(sss[0]);
/*cMultiPwms::SyncStart(sss[15]);
cMultiPwms::SyncNext(sss[4], 100);
cMultiPwms::SyncNext(sss[5], 150);
cMultiPwms::SyncNext(sss[6], 200);
cMultiPwms::SyncNext(sss[7], 250);
cMultiPwms::SyncNext(sss[8], 300);
cMultiPwms::SyncNext(sss[9], 350);
cMultiPwms::SyncNext(sss[10], 400);
cMultiPwms::SyncNext(sss[11], 450);
cMultiPwms::SyncNext(sss[12], 500);
cMultiPwms::SyncNext(sss[13], 550);
cMultiPwms::SyncNext(sss[14], 600);
cMultiPwms::SyncEnd(sss[15]);*/
unsigned c=micros();
printf("\r\n16 pwms, init take %dus, set sync take %dus\r\n\r\n", b-a, c-b);
}
void loop() {
if (Timeout(3)){
static unsigned s=0, y=0, z=0;
cMultiPwms::traverseLinkedList();
delay(1800);
if (y>16) y=0;
if (z>16) z=0;
sss[0].setDC(0, y);
sss[1].setDC(0, y);
sss[2].setDC(0, y);
sss[3].setDC(0, y++);
if (!(++s%21)){
/*cMultiPwms::SyncStart(sss[0]);
cMultiPwms::SyncNext(sss[1], 50*(z+1)%800);
cMultiPwms::SyncNext(sss[2], 50*(z+2)%800);
cMultiPwms::SyncNext(sss[3], 50*(z+3)%800);
cMultiPwms::SyncEnd(sss[0]);*/
sss[1].Sync(sss[0], 50*z);
sss[3].Sync(sss[2], (50*z+50)%800);
z++;
}
delay(800);
}
}
#else
cMultiPwms sss[16]={
{3, 800, 16},
{5, 800, 16},
{12, 800, 16},
{13, 800, 16},
{15, 2500000, 16},
{2, 800, 8},
{4, 800, 8},
{14, 800, 8},
{0, 800, 4},
{0, 800, 4},
{0, 800, 4},
{0, 800, 4},
{0, 800, 2},
{0, 800, 2},
{0, 800, 2},
{0, 800, 2},
};
bool Timeout(int seconds){
static int current_time = millis();
int new_time = millis();
if (new_time < (current_time + seconds*1000)) return false;
current_time = new_time;
return true;
}
void setup() {
Serial.begin(115200);
pinMode(3, OUTPUT);
pinMode(5, OUTPUT);
pinMode(12, OUTPUT);
pinMode(13, OUTPUT);
pinMode(15, OUTPUT);
pinMode(2, OUTPUT);
pinMode(4, OUTPUT);
pinMode(14, OUTPUT);
delay(5000);
unsigned a=micros();
sss[0].setDC(50);
sss[1].setDC(50);
sss[2].setDC(50);
sss[3].setDC(50);
sss[4].setDC(50);
sss[5].setDC(50);
sss[6].setDC(50);
sss[7].setDC(50);
sss[8].setDC(50);
sss[9].setDC(50);
sss[10].setDC(50);
sss[11].setDC(50);
sss[12].setDC(50);
sss[13].setDC(50);
sss[14].setDC(50);
sss[15].setDC(50);
unsigned b=micros();
cMultiPwms::SyncStart(sss[0]);
cMultiPwms::SyncNext(sss[1], 0);
cMultiPwms::SyncNext(sss[2], 50);
cMultiPwms::SyncNext(sss[3], 100);
cMultiPwms::SyncNext(sss[4], 150);
cMultiPwms::SyncNext(sss[5], 200);
cMultiPwms::SyncNext(sss[6], 250);
cMultiPwms::SyncNext(sss[7], 300);
cMultiPwms::SyncNext(sss[8], 350);
cMultiPwms::SyncNext(sss[9], 400);
cMultiPwms::SyncNext(sss[10], 450);
cMultiPwms::SyncNext(sss[11], 500);
cMultiPwms::SyncNext(sss[12], 550);
cMultiPwms::SyncNext(sss[13], 600);
cMultiPwms::SyncNext(sss[14], 650);
cMultiPwms::SyncNext(sss[15], 700);
cMultiPwms::SyncEnd(sss[0]);
unsigned c=micros();
printf("\r\n16 pwms, init take %dus, set sync take %dus\r\n\r\n", b-a, c-b);
}
void loop() {
static unsigned s=0, y=0, z=0;
//if (Timeout(2)){
// gen a valid freq
static int count_down=2500000, inv=1;
int the_dc=50, the_steps=4;
while ((count_down>=200) && count_down%200) count_down-=inv;
sss[4].setPeriod(count_down, the_steps, the_dc);
if (count_down>=100000) count_down-=49001;
else if (count_down>=50800) count_down=10998;
else if ((count_down>=201) && (count_down<=10997)) count_down-=inv;
else {inv=0-inv; count_down-=inv;}
cMultiPwms::traverseLinkedList();
delay(100);
s+=4;
if (s>100) s=0;
for (int i=0; i<4; i++) sss[i].setDC(s);
if (!(++y%35)){
cMultiPwms::SyncStart(sss[0]);
for (int j=1; j<4; j++)
cMultiPwms::SyncNext(sss[j], 50*(z+j-1)%800);
cMultiPwms::SyncEnd(sss[0]);
z++;
}
//delay(1800);
//}
}
#endif