ESP8266 MultiPWMs ver.0.3
此程式應該完成得差不多了。此版修正一些 bug 並更加完善了。接著在測試與量測過後,將改成 Lib。而目前看來只有 sync function 比較有不確定性是不是正常。故以 Lib 發佈時應就有相當的確定性了。此程式特點是可派生 31 支 pwm (以上),程式中所附主程式是用了 16 支。至於準確度如何與 pwm 實際可用數量也是待筆者量測過後定論。至此,ESP8266 的基礎兩大古典問題算是順利解決了。筆者終於可以鬆一口氣了。
// Esp8266MultiPwms ver.0.3
// https://waterfalls.ddns.net
// by Ken Woo
// 2020.12.05
/*
ver.0.1 initial release
ver.0.2 a. fixed many bugs
b. add sync function
ver.0.3 a. add speedup
b. change class name from cMultiPwm to cMultiPwms
c. add constructors
d. add pause_all method
e. fix destructor bug
f. modify the sync function
*/
// PWM class: using MultiTimersV0.4 to generate four 192us timers which are sequentially sync-offset by 48us to one another.
// which are approaching to and are regarded as 200us/50us. Such a facility forms a single 50us timer with 4 sequential ISRs.
// PWM waveforms whereas ought to be transition of the processes will be evenly settled in the 4 ISRs,
// unless there are specified ones to be explicitly offset or synced to another, which determines the position.
// note that explicitly sync offset is restricted only to same period PWM.
// duty cycle 70% means headed high 70% first then low, inverted duty cycle 70% means headed low 70% then high.
// sync or offset counts for headed beginning.
// so, PWM periods are restricted to multiples of 200us(and at least 200us, at most 0.8s; in order to have fixed positions),
// in addition, the duty cycle step must be multiple of 50us, is restricted too.
// for example, 2.2ms PWM with 45 steps is allowed; 2200/200=11 is integer, 2200/44/50=1 is integer,
// step0 0%, step1 <=2.27%(100%/44), step2(<=100%*2/44)..., step44(>100%*43/44) 100%.
// or with 12 steps, each step is 200us, and the like.
// the highest one is 200us/5kHz with 5 steps(0%, 0%< <=25%, 25%< <=50%, 50%< <=75%, 75%< <=100%).
// the number of PWMs depends on bits of id; you can rewrite it for unlimited PWMs theoretically.
#ifndef _c_MULTI_PWMS_H_
#define _c_MULTI_PWMS_H_
#include"Esp8266HwSwTimers.h"
#endif // _c_MULTI_PWMS_H_
#define C_MULTI_PWMS_DEBUG 1
#if C_MULTI_PWMS_DEBUG
unsigned z1, z2, z3, z4;
#endif
class cMultiPwms{
typedef struct sPwmObj{
unsigned counter: 12; // counter, reload to it.
unsigned rsv1_not_use: 20; // reserved 1. it should be 0 and do not use.
unsigned rsv2: 20; // reserved 2.
unsigned reload_high: 12; // reload count for high level. 200us is one-round, 200x4096=0.8s.
unsigned freeze: 1; // will not access this obj.
unsigned stopit: 1; // will delete this obj.
unsigned accepted: 1; // this bit will be set if isr accepted freeze or stopit.
unsigned is_fixed_pos: 1; // is it a fixed position by a synced PWM; the whom synced must fixed too.
unsigned is_high_level: 1; // the current level counted is high? it will be toggling.
unsigned is_inverted: 1; // we use it at the final waveform, so it affects nothing.
unsigned stop_h_or_l: 1; // high or low when stopped.
unsigned tr_high_pos: 2; // the position at timer ISR[] for transiting to high level, it exists with low-counting.
unsigned tr_low_pos: 2; // the position at timer ISR[] for transiting to low level, coexists with high-counting.
unsigned gpio: 4; // the gpio pin number
unsigned id: 5; // id in order to search. for unlimited new/delete, must maintain it. but i prefer not.
unsigned reload_low: 12; // reload count for low level. 200us is one-round, 200x4096=0.8s.
// note the relationship of is_high_level, tr_high_pos, tr_low_pos.
// is_high_level indicats currently is high counting or low. tr_high_pos: currently is low level counting,
// it is going to transit to high level, so it is positioned at tr_high_pos, and vice versa.
// so, the duty-cycle count allocates on reload_high, and when which positioning at tr_high_pos, is active, it is counting low,
// when count-up, loading the reload_high to counter, transits the level from low to high, and finally hands over the control
// to tr_low_pos for it active. after a while the count is up again, it is responsible for transiting from high to low,
// then reload reload_low, and then changing position to tr_high_pos again alternatively.
sPwmObj(){
(reinterpret_cast<unsigned*>(this))[0]=0;
(reinterpret_cast<unsigned*>(this))[1]=0;
(reinterpret_cast<unsigned*>(this))[2]=0;
}
} sPwmObj;
typedef unsigned (*xptr_sPwmObj)[3];
#define OP_RLD_HIGH(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[0]=\
(*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[1]>>20)
#define OP_RLD_LOW(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[0]=\
(*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]>>20)
#define OP_SET_LVL_HIGH(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]|=0x10)
#define OP_SET_LVL_LOW(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]&=0xFFFFFFEF)
#define OP_COUNTER_DEC(sNode_ex) (--(*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[0])
#define VAL_COUNTER(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[0])
#define OP_GET_TR_HIGH_POS(sNode_ex) (((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]>>7)&0x3)
#define OP_GET_TR_LOW_POS(sNode_ex) (((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]>>9)&0x3)
#define OP_GET_GPIO(sNode_ex) (((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]>>11)&0xF)
#define IS_NEED_LOOKINTO(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]&0xF)
#define IS_HIGH_LEVEL(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]&0x10)
#define IS_INVERTED(sNode_ex) ((*reinterpret_cast<xptr_sPwmObj>(&(sNode_ex)))[2]&0x20)
typedef struct sNode{
sPwmObj data;
sNode *next;
sNode(): next(0){};
} sNode;
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, delete head, head=i);
};
IRAM_ATTR static sNode** node_pwm_dec_cnt(sNode **head){ // possibly process several times by caller.
sNode *a=*head;
for (; a && VAL_COUNTER(a->data)>1; OP_COUNTER_DEC(a->data), head=&(a->next), a=a->next); // 1 is minimum.
if (a) return head;
return 0;
};
sNode** node_pwm_find_parent(unsigned id, unsigned pos){ // find the parent of the node having this id.
sNode **i;
for (i=&(pwms[pos]); *i && ((*i)->data).id!=id; i=&((*i)->next));
if (!*i) return 0;
return i;
};
IRAM_ATTR static void node_pwm_move_to(sNode **src, unsigned new_pos){ // move this src node to attach to pwms[new_pos].
sNode *obj=*src;
*src=obj->next;
*node_get_conn_pt(&(pwms[new_pos]))=obj;
obj->next=0;
/// priorly excluded.
/// sNode **a=node_get_conn_pt(&(pwms[new_pos]));
/// if (&((*src)->next)!=&((*a)->next)){*a=*src;*src=(*src)->next;(*a)->next=0;}
};
unsigned wellAdd(sNode *a){ // add to proper ISR position, return the position.
unsigned i=0, j=0, pos=0, min=-1;
for (; i<4; i++){
if (pwms[i]){
if ((j=node_get_length(pwms[i]))<min){
pos=i;
min=j;
}
}
else {pos=i; break;} // empty
}
node_add(&(pwms[pos]), a);
return pos;
};
void configTimer(){
timers[0].setTimer(192, cMultiPwms::timerISR0);
timers[1].setTimer(192, cMultiPwms::timerISR1);
timers[2].setTimer(192, cMultiPwms::timerISR2);
timers[3].setTimer(192, cMultiPwms::timerISR3);
timers[1].ForceHaltForSync(timers[0], 48);
timers[2].ForceHaltForSync(timers[1], 48);
timers[3].ForceHaltForSync(timers[2], 48);
};
void* getMemory(int obj_size){ // use sizeof(unsigned) and alignment; little endian.
int a=(obj_size+sizeof(unsigned)-1)/sizeof(unsigned);
void *b=malloc(sizeof(unsigned)*a);
if (b && (unsigned(b)/sizeof(unsigned)*sizeof(unsigned)==unsigned(b))){
for (; a--; ((unsigned*)b)[a]=0);
return b;
}
free(b);
return 0;
};
void freeMemory(void *b){free(b);};
bool ack(){return (owner->data).accepted;}; // used for waiting to acked
void nack(){
(owner->data).freeze=0;
// no need to delay
(owner->data).accepted=0;
};
bool resume(){/*if (!uid) return false; */nack(); return true;}; // rather failed than blocked.
bool pause(){if (!uid) return false; (owner->data).freeze=1; return true;}; // nonblocking
static cHwTimer timers[4];
static sNode* pwms[4];
static unsigned pause_all; // to avoid conflict
static unsigned gid;
sNode *owner;
unsigned period;
unsigned steps; // 0% is not counted. however we need it be a step.
unsigned uid;
public:
cMultiPwms(): owner(0), uid(0){};
~cMultiPwms(){
if (!owner) return;
pause_all=1;
if (timers[0].isActive()) delayMicroseconds(75); // wait for isr entered.
sNode **n;
int time_cnt=10;
while (pause_all<2 && time_cnt--) delayMicroseconds(25); // no patient to wait
// if memory leak, (the below not found, isr is stopped or moving object), however not much to waste.
n=node_pwm_find_parent(uid, OP_GET_TR_HIGH_POS(*owner));
if (!n) n=node_pwm_find_parent(uid, OP_GET_TR_LOW_POS(*owner));
if (!n){
#if C_MULTI_PWMS_DEBUG
printf("\r\nx[MEM]x\r\n");
#endif // C_MULTI_PWMS_DEBUG
pause_all=0;
return;
}
*n=(*n)->next;
pause_all=0;
freeMemory(owner);
};
cMultiPwms(unsigned gpio, unsigned period_us, unsigned set_steps,\
bool inverted=false, bool stop_high=false): owner(0), uid(0){ // if a step is 1%, use 100 steps, not 101 steps.
configPwm(gpio, period_us, set_steps, inverted, stop_high);
};
cMultiPwms(const cMultiPwms &a): owner(0), uid(0){
// default is GPIO0. the gpio is not copied and must assigned it later.
// also there is not sync for the new object to the existing object.
if (!a.uid || !a.owner) return;
configPwm(0, a.period, a.steps, ((a.owner)->data).is_inverted, ((a.owner)->data).stop_h_or_l);
};
const cMultiPwms& operator=(const cMultiPwms &b){ // it is the same as copy-constructor, for not initial yet.
if (!b.uid || !b.owner || this->uid || this->owner) return *this;
configPwm(0, b.period, b.steps, ((b.owner)->data).is_inverted, ((b.owner)->data).stop_h_or_l);
return *this;
};
bool configPwm(unsigned gpio, unsigned period_us, unsigned set_steps,\
bool inverted=false, bool stop_high=false){ // if a step is 1%, use 100 steps, not 101 steps.
if (period_us%200 || !set_steps || period_us%set_steps || period_us/set_steps%50) return false;
if (uid || owner) return false;
owner=(sNode*)getMemory(sizeof(sNode));
if (!owner) return false;
period=period_us;
steps=set_steps;
uid=++gid;
if (gid>=31){ //// the last one. not fault but is going to fault.
printf("\r\ndepleted\r\n");
freeMemory(owner);
uid=0;
owner=0;
return false;
}
(owner->data).id=uid;
(owner->data).is_inverted=!!inverted;
(owner->data).is_high_level=0; // at the outset, it is going to high, so it is under low before beginning.
(owner->data).stop_h_or_l=!!stop_high;
setGpio(gpio); //// (owner->data).gpio=gpio;
(owner->data).freeze=1; // paused
(owner->data).tr_high_pos=wellAdd(owner); // at the outset, raise high then immed change pos to tr_low_pos.
return true;
};
bool setGpio(unsigned gpio){
if (!uid) return false;
(this->owner->data).gpio=gpio;
return true;
}
void setInvert(bool invert_or_not){
if (!uid) return;
(owner->data).is_inverted=!!invert_or_not;
};
void setDC(float percentage, unsigned set_by_steps=0){ // will by steps if it is nonzero
if (!uid) return;
if (!set_by_steps){
(percentage*=steps)/=100.0f;
if ((set_by_steps=unsigned(percentage))<percentage) set_by_steps++;
}
if (set_by_steps>steps) set_by_steps=steps;
set_by_steps*=period/steps/50; // times the magnifier to get the native count
// important note that the counting for high, time-up is decided at tr_low_pos, but time starts at tr_high_pos.
// different position means different time, that is the problem.
// integer quotient, 0 remainder, means integer rounds starts from pos and ends at pos.
// remainder if any means the last round that is not a complete round and ends at tr_pos other than pos.
// so, quotient+1 would be the final count.
// however one more to consider, if remainder is 0, expected time-up and count-up are exactly matched at the same pos,
// but what if in such case, we take other position for end? yes, beyond or behind the expected time when count is up.
// so how to do right the code here(tr_high_pos and tr_low_pos are diff pos, could the COUNT handle it all correctly)?
// that is right, nothing to do about this problem since tr_low_pos of its position had taken care, think about it.
// yet the other problem to think about, the boundary condition. when counter becomes 1 which is minimum because
// we either add 1 if remainder or quotient is nonzero/because 0% is excluded, is for tr_low_pos to make decision,
// hence the 1 means time is up; the 1 represents time spent from tr_high_pos to tr_low_pos under a round(equal if same pos).
// however what about the time span for low level at the condition of high level counts is only 1 and high-counts > low-counts?
// it could happen for example period=200us, step=4, dc=25~75%/count-always-1, low should be 0 by calculation.
// as a whole if calculation lead to 0 for low level, our decision making uses >1 in node_pwm_dec_cnt could cover each condition,
// that is, time spent is correct.
WaitForPause();
if (!set_by_steps){ // 0%, it paused for isr ignores.
digitalWrite((owner->data).gpio, (owner->data).is_inverted);
return;
}
else if (set_by_steps==period/50){ // 100%, it paused for isr ignores.
digitalWrite((owner->data).gpio, !(owner->data).is_inverted);
return;
}
int pos=(owner->data).tr_high_pos;
int tr_pos=set_by_steps%4;
int count=set_by_steps/4;
if (!tr_pos) tr_pos=pos;
else {
count++;
tr_pos+=pos;
if (tr_pos>3) tr_pos-=4;
}
(owner->data).tr_low_pos=tr_pos; // this is the "duty-cycle count" cast into "tr_low_pos".
(owner->data).reload_high=count;
////(owner->data).reload_low=period/200-count;
(owner->data).reload_low=(period/50-set_by_steps+3)/4;
(owner->data).counter=1; // force to reload for starting.
Resume();
if (!timers[0].isActive()) configTimer();
};
void Pause(){this->pause();}; // nonblocking
bool WaitForPause(){ // wait at most 0.8s.
// suitable for timer is running or not.
// however, in isr will run out counter then pause.
// so, not the isr load more is wait here more.
if (this->pause())
for (int i=0; !ack() && i<1100; i++){ // 0.8s
if ((owner->data).counter>10) delayMicroseconds(750);
else delayMicroseconds(25);
}
return ack();
};
void Resume(){this->resume();};
static void traverseLinkedList(){
#if C_MULTI_PWMS_DEBUG
int c=0;
for (int i=0; i<4; i++){
sNode *a=pwms[i];
while (a){
printf("id, gpio, level, tr_high_pos, tr_low_pos, rld_high, rld_low, counter,\r\n\
(%d, %d, %d, % 2d, % 2d, %d, %d, %d)\r\n",\
(*a).data.id,\
(*a).data.gpio,\
(*a).data.is_high_level,\
(*a).data.tr_high_pos,\
(*a).data.tr_low_pos,\
(*a).data.reload_high,\
(*a).data.reload_low,\
(*a).data.counter);
a=a->next;
c++;
}
printf("(%d) ----\r\n", i);
}
printf("The 4 ISRs (%d nodes in it) each costs us time (%d %d %d %d)\r\n\r\n", c, z1, z2, z3, z4);
#endif // C_MULTI_PWMS_DEBUG
};
void Sync(cMultiPwms &base, unsigned offset_us, bool same_level_at_head=false){ // note that one count is 50us for native count.
if (!uid || !base.uid || (uid==base.uid) || !owner || !base.owner) return;
if ((period!=base.period) || (period<=offset_us) || offset_us%50) return;
offset_us/=50;
base.WaitForPause();
this->WaitForPause();
pause_all=1;
if (timers[0].isActive()) delayMicroseconds(75); // wait for isr entered.
int time_cnt=10;
while (pause_all<2 && time_cnt--) delayMicroseconds(25); // no patient to wait
unsigned a=((base.owner)->data).tr_high_pos;
unsigned b=(owner->data).tr_high_pos=(((base.owner)->data).tr_high_pos+offset_us)%4;
(owner->data).tr_low_pos=(((base.owner)->data).tr_low_pos+offset_us)%4;
(owner->data).counter=(((base.owner)->data).counter+offset_us+3)/4; // counter beyond and behind and mid?
(owner->data).is_high_level=((base.owner)->data).is_high_level;
////if (same_level_at_head) (owner->data).is_high_level^=1;////hard to impl
// fix the problem that isr position could affect counter value.
unsigned c=pause_all-2;
if ((a<b && c<b && c>a) || (b<a && c<=b)) (owner->data).counter++;
switch (pause_all){
case 2: pause_all=6; break;
case 3: pause_all=7; break;
case 4: pause_all=8; break;
case 5: pause_all=9; break;
}
time_cnt=10;
while (pause_all && time_cnt--) delayMicroseconds(25); // no patient to wait
pause_all=0;
base.Resume();
this->Resume();
};
IRAM_ATTR static void trigger(sNode **u){
while (u=node_pwm_dec_cnt(u)){
if (IS_NEED_LOOKINTO((*u)->data)){ // and for stop check. here would flush out the counter then true.
if (((*u)->data).freeze) ((*u)->data).accepted=1;
if (*u) u=&((*u)->next);
}
else if (IS_HIGH_LEVEL((*u)->data)){ // if going to low counting
digitalWrite(OP_GET_GPIO((*u)->data), IS_INVERTED((*u)->data));
OP_SET_LVL_LOW((*u)->data);
OP_RLD_LOW((*u)->data);
if (OP_GET_TR_HIGH_POS((*u)->data)!=OP_GET_TR_LOW_POS((*u)->data))
node_pwm_move_to(u, OP_GET_TR_HIGH_POS((*u)->data));
else if (*u) u=&((*u)->next);
}
else { // if going to high counting
digitalWrite(OP_GET_GPIO((*u)->data), !IS_INVERTED((*u)->data));
OP_SET_LVL_HIGH((*u)->data);
OP_RLD_HIGH((*u)->data);
if (OP_GET_TR_HIGH_POS((*u)->data)!=OP_GET_TR_LOW_POS((*u)->data))
node_pwm_move_to(u, OP_GET_TR_LOW_POS((*u)->data));
else if (*u) u=&((*u)->next);
}
}
};
#if !C_MULTI_PWMS_DEBUG
IRAM_ATTR static void timerISR0(){
if (!pause_all) trigger(&(pwms[0]));
else if (pause_all==1){ // do what to do as follows when pause all.
// pause_all is 1 set by user, means every isr can ack it into 2~5 and paused, such that where paused is known.
// moreover pause_all could be set 6~9 by user means only isr0~3 can ack it and reset it and be the first one in.
// the timing would be affected when use such a method however fine than nothing could do.
// pause_all is 2~5 to ack back to user.
// care must be taken where the pause_all were used whether or not possibly temporally lead to conflict!
pause_all=2;
}
else if (pause_all==6){ // 6cue isr0
pause_all=0;
trigger(&(pwms[0]));
}
};
IRAM_ATTR static void timerISR1(){
if (!pause_all) trigger(&(pwms[1]));
else if (pause_all==1){
pause_all=3;
}
else if (pause_all==7){ // 7cue isr1
pause_all=0;
trigger(&(pwms[1]));
}
};
IRAM_ATTR static void timerISR2(){
if (!pause_all) trigger(&(pwms[2]));
else if (pause_all==1){
pause_all=4;
}
else if (pause_all==8){ // 8cue isr2
pause_all=0;
trigger(&(pwms[2]));
}
};
IRAM_ATTR static void timerISR3(){
if (!pause_all) trigger(&(pwms[3]));
else if (pause_all==1){
pause_all=5;
}
else if (pause_all==9){ // 9cue isr3
pause_all=0;
trigger(&(pwms[3]));
}
};
#else
IRAM_ATTR static void timerISR0(){
static int x;
x=micros();
if (!pause_all) trigger(&(pwms[0]));
else if (pause_all==1){ // do what to do as follows when pause all.
// pause_all is 1 set by user, means every isr can ack it into 2~5 and paused, such that where paused is known.
// moreover pause_all could be set 6~9 by user means only isr0~3 can ack it and reset it and be the first one in.
// the timing would be affected when use such a method however fine than nothing could do.
// pause_all is 2~5 to ack back to user.
// care must be taken where the pause_all were used whether or not possibly temporally lead to conflict!
pause_all=2;
}
else if (pause_all==6){ // 6cue isr0
pause_all=0;
trigger(&(pwms[0]));
}
z1=micros()-x;
};
IRAM_ATTR static void timerISR1(){
static int x;
x=micros();
if (!pause_all) trigger(&(pwms[1]));
else if (pause_all==1){
pause_all=3;
}
else if (pause_all==7){ // 7cue isr1
pause_all=0;
trigger(&(pwms[1]));
}
z2=micros()-x;
};
IRAM_ATTR static void timerISR2(){
static int x;
x=micros();
if (!pause_all) trigger(&(pwms[2]));
else if (pause_all==1){
pause_all=4;
}
else if (pause_all==8){ // 8cue isr2
pause_all=0;
trigger(&(pwms[2]));
}
z3=micros()-x;
};
IRAM_ATTR static void timerISR3(){
static int x;
x=micros();
if (!pause_all) trigger(&(pwms[3]));
else if (pause_all==1){
pause_all=5;
}
else if (pause_all==9){ // 9cue isr3
pause_all=0;
trigger(&(pwms[3]));
}
z4=micros()-x;
};
#endif // C_MULTI_PWMS_DEBUG
};
cHwTimer cMultiPwms::timers[4];
IRAM_ATTR cMultiPwms::sNode* cMultiPwms::pwms[4]={0, 0, 0, 0};
unsigned cMultiPwms::pause_all=0;
unsigned cMultiPwms::gid=0;
cMultiPwms sss[16]={
{D2, 800, 8},
{D3, 800, 8},
{D4, 20000, 4},
{D5, 2000, 8},
{D6, 120000, 10},
{D7, 120000, 10},
{D8, 120000, 10},
{D1, 120000, 10},
{D1, 322000, 10},
{D1, 332000, 10},
{D1, 342000, 10},
{D1, 352000, 10},
{D6, 1200, 4},
{D7, 400, 4},
{D8, 1600, 4},
{D1, 200, 4},
};
bool Timeout_2s(){
static int current_time = millis();
int new_time = millis();
if (new_time < (current_time + 2000)) return false;
current_time = new_time;
return true;
}
void setup() {
Serial.begin(115200);
pinMode(D2, OUTPUT);
pinMode(D3, OUTPUT);
pinMode(D4, OUTPUT);
pinMode(D5, OUTPUT);
delay(5000);
sss[0].setDC(10, 4);
sss[1].setDC(10, 1);
sss[1].Sync(sss[0], 0);
sss[2].setDC(77, 1);
sss[3].setDC(0, 7);
sss[4].setDC(0, 1);
sss[5].setDC(0, 2);
sss[6].setDC(0, 3);
sss[7].setDC(0, 4);
sss[8].setDC(0, 5);
sss[9].setDC(0, 6);
sss[10].setDC(0, 7);
sss[11].setDC(0, 8);
sss[12].setDC(0, 2);
sss[13].setDC(0, 1);
sss[14].setDC(0, 3);
sss[15].setDC(0, 4);
}
void loop() {
if (Timeout_2s()){
static unsigned y, z;
Serial.println(y);Serial.println(z);
cMultiPwms::traverseLinkedList();
delay(1000);
if (++y>8) y=0;
if (++z>2) z=0;
sss[0].setDC(0, y);
sss[1].setDC(0, y);
delay(1000);
sss[random(4)+4].Sync(sss[random(4)+4], random(120000/50)*50);
}
}