10-10-2025, 01:07 PM
(Modification du message : 12-10-2025, 11:20 PM par michy.
Raison de la modification: correction
)
Bonjour,
la première étape, c'est la répartition nombre de demi-onde passante / nombre de demi onde pour obtenir le % de routage,
en y regardant de prés, la routine André se contente d’arrêter les estimations quand la valeur est dans la tolérance 0.4%
cette procédure arrive a dire que pour obtenir 8% le ratio 2/24 est acceptable (erreur 0.3333) si on pousse le ratio à 2/25 c'est parfait avec écart de 0%
c'est valable pour d'autres situations
pour corriger la situation, j'ai ajouté une boucle supplémentaire qui commence par remplir le tableau avec tolérance 0 puis pour les cas pas encore couvert tolérance 0.1% et ainsi de suite jusque 0.4%, je fini par un tour qui patch les cas non couvert en utilisant la valeur du % précédent
l'étape suivant c'est la répartition des demi-onde passante ou bloqué
La logique utilisé par André c'est de mettre toute les demi-onde passante au début du cycle
l'inconvénient c'est de potentiel clignotement (quand c'est de longue séquence, ça devient visible pour l’œil humain) [++++et perturbation système de mesure sur courte période]
La logique LoLo69 c'est une répartition à la volée en utilisant la formule : ((pos * on_count) % total < on_count)
c'est beaucoup mieux, avec quelques situations non appréciable
=> cas 50%, on ne laisse passer que les demi- ondes d'une seul polarité (même sur enchainement de plusieurs séquence à 50%)
=> cas 17,26,47,48,49,51,52,53,74,83 majorité de demi-onde de même polarité (compensé par les demi ondes inverses lors de la séquence suivante)
Je propose de répartir des moments de conduction en onde complète (demi positive suivi de demi négative) séparé par des tempos régulièrement espacées (et placer une demi-onde isolé dans les cas impaires), ça commence a se compliquer pour trouver une formule qui fait ça à la volée
Avec un nombre sur 64 bits,
je code les bits a 1 quand ça doit être passant et 0 quand c'est bloqué / utilisation du nombre de bit correspondant la tabPulseSinusTotal[] (qui ne doit pas être plus que 64 !) => c'est pour ça que les cas 1% et 99% qui était sur 73 demi onde se trouve avec les valeurs de 0% et 100% respectivement [comme on est dans des cas extrêmes, l'impact devrait être faible]
reste a utiliser les tables tabPulseSinusTotal[] et tabsequenceMultiSinus[] pour charge la séquence a utiliser en multisinus pour l'action adéquate (différentiation de traitement pour multisinus et train de sinus)
pour activer ou pas le SSR par période de 10ms ça se passe dans GestionIT_10ms, où il faut différencier le traitement multisinus et train de sinus
Je n'ai pas encore tout testé, il y a peut être quelques coquilles ...
la première étape, c'est la répartition nombre de demi-onde passante / nombre de demi onde pour obtenir le % de routage,
en y regardant de prés, la routine André se contente d’arrêter les estimations quand la valeur est dans la tolérance 0.4%
cette procédure arrive a dire que pour obtenir 8% le ratio 2/24 est acceptable (erreur 0.3333) si on pousse le ratio à 2/25 c'est parfait avec écart de 0%
c'est valable pour d'autres situations
pour corriger la situation, j'ai ajouté une boucle supplémentaire qui commence par remplir le tableau avec tolérance 0 puis pour les cas pas encore couvert tolérance 0.1% et ainsi de suite jusque 0.4%, je fini par un tour qui patch les cas non couvert en utilisant la valeur du % précédent
Code :
// dans setup()
#define MINSEQLEN 2 //++MS
#define MAXSEQLEN 64 //++MS
#define BASEPRECIS 0 //++MS
#define FINPRECIS 4 //++MS
for (int I = 0; I <= 50; I++) { // on ne traite que la moitié, l’autre partie c’est symétrique //++MS
tabPulseSinusTotal[I] = 0xFF;
tabPulseSinusOn[I] = 0xFF;
target = float(I) / 100.0f;
float tolerance = 0.001f; //++MS
for (int K = BASEPRECIS ; K <= FINPRECIS ; K++) { // on fait plusieurs passe en augmentant la tolérance a l'erreur (premier tour recherche la correspondance parfaite) //++MS
if (tabPulseSinusTotal[I] < 0xFF ) break; // on a déjà trouver une solution avec une tolérance plus faible
tolerance = 0.001f * K;
for (int T = MINSEQLEN; T < MAXSEQLEN; T++) {
for (int N = max(0, (int)(T * target) - 1); N <= T; N++) { // démarre la recherche d'une cohérence juste avant la cible théorique
if (T % 2 == 1 || N % 2 == 0) { // Valeurs impaires du total [le cycle suivant rétablira la neutralité électrique] ou pulses pairs pour éviter courant continu
vrai = float(N) / float(T);
erreur = fabs(vrai - target);
if (erreur <= tolerance) {
tabPulseSinusTotal[I] = T;
tabPulseSinusOn[I] = N;
break; // sort de la boucle for N //++MS
}
}
} // for N
if (erreur <= tolerance) break; // sort de la boucle for T //++MS
} // for T
if (erreur <= tolerance) break; // sort de la boucle for K //++MS
} // for K
} // for I
// patch pour solutions non trouvées (on utilise la valeur précédente s'il n'y a pas de solution) //++MS
for (int I = 1; I <= 50; I++) {
if (tabPulseSinusTotal[I] == 0xFF) { // pas trouver de solution
tabPulseSinusTotal[I] = tabPulseSinusTotal[I - 1]; // on prend la valeur précédente
tabPulseSinusOn[I] = tabPulseSinusOn[I - 1];
}
}
// double la durée pour les cas impair 'court' ça devient un cas pair
for (int i = 0; i <= 50; i++) {
if ((tabPulseSinusOn[i] % 2 || tabPulseSinusTotal[i] % 2) && tabPulseSinusTotal[i] <= 32) { // avec 32, en doublant, on sait encoder la séquence sur 64 bits
tabPulseSinusOn[i] *= 2;
tabPulseSinusTotal[i] *= 2;
}
}
// propage la table de 51 a 100
for (int I = 51; I <= 100; I++) {
tabPulseSinusTotal[I] = tabPulseSinusTotal[100 - I];
tabPulseSinusOn[I] = tabPulseSinusTotal[I] - tabPulseSinusOn[100-I];
}
// cas particuliers 1 et 99 on force à 1/64
tabPulseSinusOn[1] = 1;
tabPulseSinusTotal[1] = 64;
tabPulseSinusOn[99] = 63;
tabPulseSinusTotal[99] = 64;
l'étape suivant c'est la répartition des demi-onde passante ou bloqué
La logique utilisé par André c'est de mettre toute les demi-onde passante au début du cycle
l'inconvénient c'est de potentiel clignotement (quand c'est de longue séquence, ça devient visible pour l’œil humain) [++++et perturbation système de mesure sur courte période]
La logique LoLo69 c'est une répartition à la volée en utilisant la formule : ((pos * on_count) % total < on_count)
c'est beaucoup mieux, avec quelques situations non appréciable
=> cas 50%, on ne laisse passer que les demi- ondes d'une seul polarité (même sur enchainement de plusieurs séquence à 50%)
=> cas 17,26,47,48,49,51,52,53,74,83 majorité de demi-onde de même polarité (compensé par les demi ondes inverses lors de la séquence suivante)
Je propose de répartir des moments de conduction en onde complète (demi positive suivi de demi négative) séparé par des tempos régulièrement espacées (et placer une demi-onde isolé dans les cas impaires), ça commence a se compliquer pour trouver une formule qui fait ça à la volée
Avec un nombre sur 64 bits,
Code :
uint64_t tabsequenceMultiSinus[101]; // en multisinus représentation binaire des moments passant et des moments bloqués (nombre de bits utiles dans PulseTotal[])
je code les bits a 1 quand ça doit être passant et 0 quand c'est bloqué / utilisation du nombre de bit correspondant la tabPulseSinusTotal[] (qui ne doit pas être plus que 64 !) => c'est pour ça que les cas 1% et 99% qui était sur 73 demi onde se trouve avec les valeurs de 0% et 100% respectivement [comme on est dans des cas extrêmes, l'impact devrait être faible]
Code :
// réparti les demi ondes passantes ou bloquées de manière ~équilibré en faisant des groupes demi-alternance+ et demi-alternance- associés //++MS
for (int i = 0; i <= 50; i++) { // premiere moitié de la table avec majorité de OFF //++MS
uint64_t tmp = 0ull;
char shift = 0;
uint8_t nbActif = tabPulseSinusOn[i];
if (((nbActif + 1) / 2) != 0) // anti division par zero
shift = ((tabPulseSinusTotal[i] + 1) - nbActif) / ((nbActif + 1) / 2);
else
shift = tabPulseSinusTotal[i] - 1;
char nbON = 0;
char p = 0; // position
switch (nbActif) {
case 0 :
tmp = 0; // pas de ON tout est a zéro
nbON = 0;
p = 0;
break;
case 1 :
tmp = 0b1; // une seule demi alternance a ON
nbON = 1;
p = 1;
break;
case 2 :
tmp = 0b11; // Un alternance complete a ON
nbON = 2;
p = 2;
break;
default: // jusque 50 on commence par 2 demi onde activées puis on shift xx demi onde en OFF
tmp = 0b11;
char nbON = 2;
char p = 2;
while ((p + shift + 2) < tabPulseSinusTotal[i]) {
tmp <<= (shift + 1); // decale vers la gauche avec des 0
p += shift + 1; // nombre de bit vers la gauche
if (nbON < nbActif) { // si on a pas encore injecté tous les bits a mettre a 1
tmp |= 0b1; // met le dernier bit a 1 si on a pas encore le compte
nbON++;
}
tmp <<= 1; // decale de 1 a gauche en inserant un zero
p++;
if (nbON < nbActif) { // si on a pas encore injecté tous les bits a mettre a 1
tmp |= 0b1; // met le dernier bit a 1 si on a pas encore le compte
nbON++;
}
}
}
tabsequenceMultiSinus[i] = 0ull;
for (int j = 0; j < tabPulseSinusTotal[i]; j++) { // traduit la sequence sur 64 bits (uniquement les tabPulseSinusTotal[i] bits de faible poids sont utilisés)
tabsequenceMultiSinus[i] <<= 1;
tabsequenceMultiSinus[i] |= uint64_t((tmp >> j) & 0b1);
}
} //++MS
for (int i = 51; i <= 100; i++) { // seconde partie de la table, avec une majorité de passant
uint64_t tmp = 0ull;
char shift = 0;
uint8_t nbInActif = tabPulseSinusOn[i];
if (((nbInActif + 1) / 2) != 0 ) // anti division par zero
shift = ((tabPulseSinusTotal[i] + 1) - nbInActif) / ((nbInActif + 1) / 2);
else
shift = tabPulseSinusTotal[i] - 1;
char nbOFF = 0;
char p = 0; // position
tmp = 0b11;// au dela de 50 on commence par 2 demi onde activées puis on shift xx demi onde en OFF
nbOFF = 2;
p = 2;
while ((p + shift + 1) < tabPulseSinusTotal[i]) {
tmp <<= (shift + 1); // decale vers la gauche avec des 0
p += shift + 1; // nombre de bit vers la gauche
if (nbOFF < nbInActif) { // si on a pas encore injecté tous les bits a mettre a 1
tmp |= 0b1; // met le dernier bit a 1 si on a pas encore le compte
nbOFF++;
}
tmp <<= 1; // decale de 1 a gauche en inserant un zero
p++;
if (nbOFF < nbInActif) { // si on a pas encore injecté tous les bits a mettre a 1
tmp |= 0b1; // met le dernier bit a 1 si on a pas encore le compte
nbOFF++;
}
}
tabsequenceMultiSinus[i] = 0ull;
for (int j = 0; j < tabPulseSinusTotal[i]; j++) { // traduit la sequence sur 64 bits (uniquement les tabPulseSinusTotal[i] bits de faible poids sont utilisés)
tabsequenceMultiSinus[i] <<= 1;
tabsequenceMultiSinus[i] |= uint64_t((tmp >> j) & 0b1);
}
}
reste a utiliser les tables tabPulseSinusTotal[] et tabsequenceMultiSinus[] pour charge la séquence a utiliser en multisinus pour l'action adéquate (différentiation de traitement pour multisinus et train de sinus)
Code :
void GestionOverproduction() { // chaque 200ms (soit adaptation 5 fois par seconde)
static uint8_t prevpercent[NBACTIONMAX]; // 0 a 100
Code :
case 2 : //ACT_MULTISIN: // 2:Multi Sinus
int tmp = 100 - Retard[i];
if (tmp == prevpercent[i]) { // on continu la même séquence si le % de contrôle n’a pas changer,
break;
}
PulseComptage[i] = 0; // sinon on arrête la séquence en cours et on redémarre a début de la nouvelle séquence avec un nouveau % d'ouverture
PulseOn[i] = tabPulseSinusOn[tmp];
PulseTotal[i] = tabPulseSinusTotal[tmp];
sequenceHalfWave[i] = tabsequenceMultiSinus[tmp] ;
prevpercent[i] = tmp;
break;
case 3: //ACT_TRAINSIN: // 3:Train de Sinus
PulseOn[i] = 100 - Retard[i];
PulseTotal[i] = 99; //Nombre impair pour éviter courant continu
break;
case 4: //ACT_PWM: // 4:PWM
Vout = int32_t(RetardF[i] * 2.55);
if (OutOn[i] == 1) Vout = 255 - Vout;
ledcWrite(Gpio[i], Vout);
break;
pour activer ou pas le SSR par période de 10ms ça se passe dans GestionIT_10ms, où il faut différencier le traitement multisinus et train de sinus
Code :
void IRAM_ATTR GestionIT_10ms() { // en IRAM, (c'est appelé depuis une ISR), les variables modifiées doivent être volatile
Code :
case 2: // ACT_MULTISIN: // 2:MultiSinus -> répartition des demi-onde passante et bloqué sur la durée nécessaire pour obtenir le % d'ouverture requis
if (Gpio[i] >= 0) {
if (PulseComptage[i] <= PulseTotal[i]) {
if ((sequenceHalfWave[i] >> PulseComptage[i]) & 0b1) {
digitalWrite(Gpio[i], OutOn[i]);
} else {
digitalWrite(Gpio[i], OutOff[i]); //Stop
}
PulseComptage[i] = PulseComptage[i] + 1;
}
if (PulseComptage[i] >= PulseTotal[i]) {
PulseComptage[i] = 0; // relance une nouvelle séquence
}
}
break;
case 3: // ACT_TRAINSIN: // 3:Train de sinus -> le bloc de demi onde passante au début complétés par une séquence de demi onde bloqués
if (Gpio[i] >= 0) { //Gpio valide
if (PulseComptage[i] < PulseOn[i]) {
digitalWrite(Gpio[i], OutOn[i]);
} else {
digitalWrite(Gpio[i], OutOff[i]); //Stop
}
PulseComptage[i] = PulseComptage[i] + 1;
if (PulseComptage[i] >= PulseTotal[i]) {
PulseComptage[i] = 0; // relance une nouvelle séquence
}
}
break;
case 4: // ACT_PWM: // 4:PWM ne dépend pas IT 10ms
break;
Je n'ai pas encore tout testé, il y a peut être quelques coquilles ...
Merci André
,
Routeur V15.09b (since V2.01) / 1xESP32 (IP fixe) / Source UxI / 5 actions
Panneaux 1680Wc
1 Triac : ECS 2000W
1 SSR (multi) : ECS 1800W
1 SSR (On-Off) : Circulateur plancher chauffant eau 50W
1 SSR (multi) : circuit d'eau 1500W
1 SSR (multi) : Ultime 2000W

Routeur V15.09b (since V2.01) / 1xESP32 (IP fixe) / Source UxI / 5 actions
Panneaux 1680Wc
1 Triac : ECS 2000W
1 SSR (multi) : ECS 1800W
1 SSR (On-Off) : Circulateur plancher chauffant eau 50W
1 SSR (multi) : circuit d'eau 1500W
1 SSR (multi) : Ultime 2000W