Silicium 628 - dernière mise à jour de cette page: 3 mars 2009

PROGRAMMATEUR POUR LAVE LINGE
avec un ATmega32



Mise en garde préalable

Cette réalisation comprend deux parties:
  • une carte basse tension regroupant le microprocesseur et quelques composants.
  • une carte haute tension reliée au secteur 230V comprenant des triacs, mais aussi des circuits d'isolation galvanique entre le secteur et la partie basse tension, à savoir des optotriacs (MOC3043), des relais, ainsi qu'un transformateur d'alimentation. De ce fait cette carte doit être routée et réalisée avec le plus grand soin de façon à ce que la partie basse tension ne puisse en aucun cas entrer en contact avec le secteur 230V.
Pour cette raison je ne publierai pas les typons de réalisation des cartes mais seulement les schémas théoriques. A partir du même schéma, une implantation des composants ou une réalisation mal faite peut vous amener tout droit au cimetière! Si vous n'êtes pas habitué aux montages fonctionnant sur le secteur, je vous déconseille vivement de réaliser ce programmateur.
Je précise que je ne vends rien, je ne réalise rien pour les autres. Je me contente d'exposer ce que je fais pour moi, à titre d'exemple à ne PAS suivre, afin d'illustrer les extraordinaires possibilités des microcontrôleurs.
En revanche je fournis tous mes codes sorces en Pascal, commentés.


Principe

Le  microcontrôleur ATmega32 contrôle l'ensemble des organes.

1) En sortie:
  •  marche - arret, vitesse et sens de rotation du moteur (moteur "type universel" à balais et charbons)
  •  électrovannes d'admission d'eau
  •  pompe de vidange
  •  résistance de chauffage
  • Afficheur LCD 4lignes de 20 caractères (bleu).
  • Quelques LED de toutes les couleurs
2) En entrée:
  • capteur de niveau d'eau (celui d'origine, à contacts, je prévois de la remplacer par un capteur de pression)
  • dynamo tachymètrique sur l'axe du moteur (d'origine)
  • sonde de température résistive (d'origine)
  • signal de référence de phase du 50Hz secteur.
  • recepteur de télécommmande IR
  • boutons poussoirs.
La vitesse de rotation du moteur est asservie par le microcontrôleur. Sa tension d'alimentation 230V est découpée par un triac commandé par l'ATmega. En effet, directement alimenté en 230V le moteur tourne à la vitesse d'essorage. Il faut donc le ralentir pour le lavage. Mais l'asservissement de vitesse est rendu nécessaire du fait que le couple varie ENORMEMENT lors de chaque tour du tambour du à la répartition et aux déplacements du linge dans ce tambour. D'où la présence de la dynamo tachymètrique sur l'axe du moteur. l'ATmega mesure en permanence la vitesse réelle du tambour (par une fonction fréquencemètre programmée) et la compare à une consigne. Le résultat sert à régler l'angle de conduction du triac afin de maintenir la vitesse constante (asservissement de vitesse). C'est un des points les plus délicats du programme.

Le sens de rotation du moteur est choisi par permutation du branchement  du stator par rapport au rotor, par deux relais.
La pompe et les électrovannes sont commandées par des triacs.
La résistance de chauffage est commandée par un relais 230V - 10A.

Le microcontrôleur et les circuits basse tension (5V) sont isolés du secteur 230V:
  • l'alimentation par un transformateur 230V - 2x6V
  • les triacs par des opto-triacs à détection de passage à zéro MOC3043.
  • le triac de découpage de la tension du moteur par un opto-triac SANS détection de passage à zéro MOC3023 (réponse instantanée)
 Le tout est géré à distance par une télécommande pour TV ! (ça évite de toucher à quoi que ce soit pendant le fonctionnement de la machine)

Vue d'ensemble

programmateur electronique lave linge ATmega32
  • La petite carte tout en haut: c'est le programmateur AVR USB.
  • A gauche la carte haute tension.
  • A droite la carte logique avec l'ATmega32 et l'afficheur LCD.
  • et  la zappète TV (modèle universel en mode "TV") qui permet de mettre la machine en marche de choisir les programmes de lavage et bien d'autres choses.
programmateur electronique pour lave lingeUne carte d'afichage à LED disposées en cercle donne un petit air rétro.

C'est bien pratique pour apprécier du premier coup d'oeil où en est le cycle de lavage.
programmateur electronique lave linge en fontionL'ensemble des cartes reliées à la machine, le tout en fonctionnement.

Le schéma de la carte haute tension

schema de la carte 230V


Le schéma de la carte logique


Bientot seront rajoutées 18 LEDS pilotées pas deux CD4017 indiquant les 12 étapes des programmes de lavage.

La carte haute tension

Pour la partie haute de la carte, de gauche à droite:

-tout en haut en jaune les borniers de raccordement des (nombreux) fils du lave linge.
-le triac BT137 -600 de commande de la vitesse du  moteur
- les deux relais de commande du sens de rotation
- emplacement vide qui reçoit le relais de chauffage
- les triacs pour la pompe et les électrovannes

Viennent au dessous les optotriacs d'isolation secteur (petits boîtier blancs)
Le transfo d'alim. pour le 5V et quelques composants basse tension



La carte logique

carte logique programmateur lave linge ATmega32
  • Au centre de la carte se trouve le gros circuit 40 pins de l'ATmega32 (signalons au passage que la version CMS est minuscule en comparaison)
  • Le petit boîtier noir à sa gauche est le récepteur infra-rouge pour recevoir les ordres de la télécommande.
  • L' afficheur se situe en dehors de la carte proprement dite, il comporte bien 4 lignes et non deux comme il semblerait sur cette photo (le microprocesseur n'était pas encore programmé).

Le programme est en Pascal. 


implantation carte à LEDL' implantation des composants de la carte à LED

Documents techniques




Quelques points du programmes expliqués

Le principe du découpage de la tension alternative du secteur consiste à déclencher la conduction d'un triac après un délai très précis débutant  au début de chaque alternance.
Il faut donc connaître l'instant du début de l'alternance: cette information est obtenue par l'optocoupleur CNX34 suivi d'une mise en forme par un inverseur à hystérésis CD40106.
On applique ce signal sur l'entrée d'interruption externe INT0 de l' ATmega32 (pin16).

Cette interruption va faire partir le compteur du Timer2:



Ah ah ! déjà les choses se compliquent! Mais que fait donc cette interruption avec le registre TCCR2 ? Elle remet la sortie OC2 à zéro.  Qu'ès aquo la sortie OC2? J'explique:
Nous allons utiliser le compteur du Timer2 (8 bits) afin de créer le délai de conduction du triac. Ce timer est programmé en mode comparateur, il compare la valeur de son compteur interne (TCNT2) qui évolue avec le temps (l'horloge de l'ATmega) à la valeur que nous écrivons dans le registre OCR2. Lorsque la valeur du compteur TCNT2 atteint la valeur de OCR2, une "comparaison réussie" advient. Elle peut déclencher une interruption (interrupt Timer2COMP) dans laquelle on peut écrire un bout de code qui génère une impulsion vers le triac. C'est simple mais cela pose des problèmes  de "télescopage d'interruptions" dans un programme assez lourd (timings résultants peu précis).
Toutefois la documentation de l'ATmega fait apparaître une autre possibilité qui n'utilise pas d'interruption supplémentaire: lorsque la "comparaison  réussie" se produit le niveau logique de la sortie OC2 peut être changé sans que cela n'affecte le déroulement du programme. J'ai donc configuré cette sortie OC2 pour qu'elle passe à 1 logique lors de la comparaison ok. L'électronique pourra recueillir ce front montant pour générer l'impulsion. Reste à faire reseter OC2 un peu plus tard, par exemple lors de l'INT0. Simple pensais-je, il suffit d'écrire un '1' "dans" OC2.  Sauf que OC2 n'est pas le bit d'un registre, et que ce n'est pas accessible en écriture. (Le niveau logique sur OC2 et totalement distinct du portD7 qui dans cette configuration "n'existe plus"). Il a toutefois été prévu un mécanisme pour changer l'état logique d'OC2. Il faut configurer l'unité de comparaison du Timer2 afin que la sortie OC2 passe au niveau requis ('0' en ce qui nous concerne) puis provoquer une "comparaison virtuelle" en écrivant un '1' sur FOCE2 qui est le bit 7 du registre TCCR2. En réfléchissant à cette astuce en se plaçant dans l'état d'esprit d'un électronicien, on pourrait presque trouver ça logique!  Ensuite il faut bien sûr penser à reconfigurer l'unité de comparaison du Timer2 afin que la sortie OC2 passe au niveau '0' lors de la comparaison réelle afin de générer le signal lors de la période suivante. Les quelques lignes de codes vues plus haut doivent désormais vous paraître limpides. Sachez que pour le déduire de la documentation de l'ATmega en anglais j'y ai passé un mauvais quart d'heure!
Je viens d'écrire quelques lignes plus haut que les fronts montants que l'on génère de cette façon se produisent pour chaque PÉRIODE, un par période. C'est logique, l' INT0 se produit sur un front descendant du 40106, et il n'y a qu'un front descendant par période. Or il nous faut obtenir 2 tops de commande du triac par période ( un pour chaque alternance). On pourrait penser à configurer l'INT0 de façon à ce qu'elle se déclenche aussi bien sur les fronts montants que descendants (c'est possible avec les ATmega). Sauf que le signal issu de l'optocoupleur n'est pas forcément symétrique, d'ailleurs il N'EST pas symétrique. Ca conduirait donc à obtenir un angle de découpage différent pour chaque alternance avec une indétermination de pi (deux états possibles équiprobables). D'où l'intérêt de conserver un seul signal par période, pour une alternance toujours la même. Et pour l'autre alternance? un classique NE555 utilisé en monostable avec une tempo fixe de 10ms (pour 50Hz la période vaut 1/50Hz = 20ms) fait l'affaire. Ce NE555 associé à un CD40106 (voir schéma plus haut) génère ainsi les deux tops de déclenchements requis. L'ATmega se charge, lui, de les placer précisément dans le temps, en écrivant la valeur désirée du retard dans OCR2.

L'ATmega mesure en permanence la vitesse réelle du tambour (par une fonction périodemètre programmé, sur le tachymètre en bout d'arbre moteur) et la compare à une consigne. Le résultat sert à régler l'angle de conduction du triac afin de maintenir la vitesse constante. (asservissement logiciel)

La régulation de la température fait appel au convertisseur analogique/numérique de l'ATmega pour lire la valeur de la sonde (résistance CTN).

L'ATmega interprète également les signaux de la télécommande IR.

Je vous invite donc à étudier le source en Pascal, vous y trouverez  un assez large éventail des possibilités des ATmega et des "drivers" de l'outil de programmation des AVR en Pascal qu'est AVRco.
Le plus délicat  à été la gestion des nombreux timings et d'éviter d'emboiter les procédures comme des poupées russes afin de ne pas faire déborder la pile logicielle du uC dans certaines configurations.
Les procédures sont toujours appelées au niveau le plus bas possible, à des instants gérés par des flags.
Pour la petite histoire, dans une première version j'avais une boucle principale qui appelait le choix du programme qui appelait le remplissage qui appelait le lavage qui appelait la vidange qui appelait le rinçage qui... dois-je préciser que les interruptions temps réel saupoudrées sur tout ça finissaient immanquablement la lessive avant son terme ?



Voici le code source en Pascal:

(La version la plus récente est toujours celle figurant sous le lien plus haut sur cette page)
program lavelinge;
{===============================================================================
par Silicium628 - voir la description de cette realisation sur mon site
(chercher silicium628 sur Google, on ne peut pas le rater!)
versions: voir plus bas dans la partie "const" - derniere mise à jour 8 juillet 2008
La version est visible sur l'afficheur par la touche 9 de la telecommande IR
Quel plaisir de programmer un uControleur en PASCAL ! :-)
================================================================================
PRINCIPE:
--------------------------------------------------------------------------------
Le moteur est un modele dit "universel" (à charbons)
La vitesse du moteur en 220V est très grande
La vitesse est limitée par un decoupage de la tension secteur par un triac BT137 - 800
drivé par un optocoupleur MOC3023 (sans circuit de detection de passage a zero)
Le sens de rotation du moteur est changé par inversion du bobinage du stator
par rapport au rotor cablé en série. Cette inversion est obtenue au moyen de deux relais inverseurs.
Le moteur n'est pas alimenté si les relais sont tous deux au repos ou tous deux collés.
Toutefois cette deuxième configuration (tous deux collés) est sans interet et consomme
inutilement sur l'alim 5V. Elle n'est donc pas permise.
 
Le moteur est également stoppé par absence d'impulsions de commande sur l'optocoupleur
(et donc le triac)
 
IMPORTANT:
1) Les relais ne doivent jamais êtres commutés alors que des impulsions de commandes
du triac sont présentes, sous peine de destruction possible du triac.
La procedure consiste donc à arrêter les impulsions et à attendre quelques secondes
avant de commuter un relais.
2) Je déconseille d'envoyer des impulsions lorsque les deux relais sont au repos,
et donc lorsque le triac est en roue libre.
 
--------------------------------------------------------------------------------
REMARQUE: Triac sur charge inductive:
Lors de la conduction du triac, le courant dans une inductance s'établit progressivement.
Donc pas de di/dt important, et partant pas de surtension (e=-L.di/dt)).
Mais un dv/dt important et gênant.
 
Une coupure du courant dans une inductance provoque un di/dt qui genere mathematiquement une
surtension E=-L.di/dt. Oui mais un triac n'est pas un transistor! Un triac se bloque
naturellement de lui même lorsque le courant s'annule. DONC PAS DE SURTENSION au blocage contrairement
à ce qu'on lit ici ou là à longueur de journée.
Dans une self, la tension est en quadrature avec le courant. Donc le minimum de courant correspond
à une tension non nulle (aux bornes de la self, en série avec le secteur et en opposition
de phase avec celui-ci vu du triac, donc zéro volt vu du triac, me trompe-je ?)
Un circuit dit "snubber" (100nF 250V alternatif + 100 ohm en série) est cablé en //
entre A1 et A2 du triac. La fonction du condensateur est de maintenir un courant non nul dans le triac
à la mise en conduction, le courant passant dans le bobinage ne s'établissant que trop lentement.
Quant à la résistance en série avec le condensateur, elle sert à limiter le courant en question.
Sans la résistance, c'est la destruction assurée du triac. De même une résistance de trop
faible valeur fait veillir le triac prématurément et aboutit à une destruction plus ou moins rapide.
 
Il existe maintenent des triacs qui se passent de snubber...
--------------------------------------------------------------------------------
TEMPERATURES
degres      (Acquisition)  (compte tenu de mon schéma)
20          (112)
30          (85)
40          (69)
50          (58)
60          (44)
80          (39)
90          (35)
================================================================================
La carte electronique répond aux signaux d'une télécommande TV infrarouge universelle
Beaucoup de modèles conviennent, il faudra adapter les codes des touches dans ce
programme le cas écheant (voir les 'case octet_IR of') dans la partie "main"
Pour ma part, j'utilise une PHILIPS type 'UNIVERSAL SBC RU 252 II'
 
================================================================================
}
 
{ $W+ Warnings}            {Warnings off}
 
Device = mega32, VCC = 5;
 
Import SysTick, LCDport, RTclock, {TickTimer,} RC5Rxport, FreqCount, ADCPort;
 
From RTclock Import RTCtimer;
 
From System Import Int64, float;
 
Define
  ProcClock      = 16000000;        {Hertz}
  SysTick        = 10, Timer0;     {msec}   // ATTENTION: necessaire pour l'horloge RTC  et LCD
  RTclock        = iData, DateTime; {Time, DateTime}
  RTCtimer       = 4; // 4 cannaux
  RTCsource      = SysTick {, adj}; { adj = +/-100}
 
//  TickTimer      = Timer1;        //le Timer1 de l'ATmega32 est un timer 16bits
  FreqTimer      = Timer1; // (used 16bit Timer}
 
  StackSize      = 512, iData;     // (voir affi en runtime)
  FrameSize      = 512, iData;
  LCDport        = PortC;
  LCDtype        = 44780;
  LCDrows        = 4;               {rows}
  LCDcolumns     = 20;              {columns per line}
 
  RC5Rxport      = PinC, 7, negative; {Port, Pin#, polarity}
  RC5mode        = rc_6bit;        {command bit count}
 
  ADCchans       = [8], iData; {use only 1 Channel, ADC7}
  ADCpresc       = 128;
 
  Define_usr
  bouton_inc_h   = %00100000;
 
 
Implementation
 
//==============================================================================
{$IDATA}
 
type
  Tprog          = (couleur, blanc, laine, froid, rapide, test);
 
const
  version        : string    = '3.3';
//  icone_anime    : string    = '|/-\';
  Labels_numeros : array [1..15] of string[8] = ('eau', 'lavage', 'vidange', 'eau',
  'rincage1', 'vidange', 'eau',  'rincage2', 'vidange', 'eau',
  'rincage3', 'vidange', 'eau', 'rincage4', 'essorage');
 
  OCRmin_ESS     : byte      = 85; // vitesse la plus rapide
  OCRmin_purge   : byte      = 90;
  OCRmin_LAV     : byte      = 105;
 
  OCRmax_ESS     : byte      = 130;
  OCRmax_purge   : byte      = 130;
  OCRmax_LAV     : byte      = 130;
  OCRmax         : byte      = 130; // vitesse la plus lente
// attention 162 est la limite au dela de laquelle on retablit les alternances à 100%
// compte tenu de mon schéma
 
  P_cons_lav     : word      = 2000;
//  P_cons_defoul  : word      = 4000;
  P_cons_purge   : word      = 400;
  tps_rincage_max      : word = 90;   // secondes
 
{ Type Declarations }
 
 
 
//==============================================================================
{$IDATA}
 
var
//  delay          : SysTimer8;
  compteur1      : integer;
  ti             : byte;
  ticked         : boolean;
  timeout1       : boolean; // pour RTCtimer
  timeout2       : boolean; // pour RTCtimer
  timeout3       : boolean; // pour RTCtimer
  timeout4       : boolean; // pour RTCtimer
  stk1           : word;
  stk1_min       : word;
  fram_free      : word;
  fram_free_min  : word;
 
//  FreqMode       : tFreqCountMode;
//  Freq           : word;
  Periode        : word;
  OCR2_real      : float;
  str12          : string[12];
 
  rxAdr          : byte;
  rxCmd          : byte;
  adr1           : integer;
  cmd1           : integer;
  octet_IR       : byte;       //octet recu par le recepteur IR
  bitsix         : byte;
  memobitsix     : byte;
  nouveau        : boolean;    // pour l'anti rebond signaux IR
  acqui_ADC      : word;
  pos_bt         : byte;
 
  stop_imps      : boolean;     // force le blocage du triac
  relais1        : boolean;
  relais2        : boolean;
  EV1            : boolean;     // électrovanne 1 (admision d'eau pour le prélavage)
  EV2            : boolean;     // électrovanne 2 (admision d'eau pour le lavage et les rinçages)
  pompe          : boolean;
  NIV1           : boolean;     // pressostat
  NIV2           : boolean;     // pressostat
  TXTniv         : string[2];
  Nom_prg        : string[10];
  affi_requis    : boolean;
  temps_i        : byte;   // pour le generateur d'impulsions vers le triac moteur. compteur en secondes
  temps_ch_sens  : byte;   // pour le changement automatique de sens
  periode_ch_sens      : byte;
 
  lavage         : boolean;
  temps_lavage   : word;   // compteur en secondes
  tps_lavage_max : word;   // secondes
  timeOUT_lavage : boolean;
  tps_pause      : longword;   // pause reglable de qq secondes ou plus lors du changement de sens (longue pour la laine)
 
  temps_pompe    : word;     // compteur en secondes
  temps_EV2      : word;     // compteur en secondes
 
  rincage        : boolean;
  temps_rincage  : word;   // compteur en secondes
  timeOUT_rincage      : boolean;
  nb_rincage_max : byte;
 
  essorage       : boolean;
  temps_essorage : word;   // compteur en secondes
  tps_essr_max   : word;
  timeOUT_ess    : boolean;
//  F_purge        : word;
  P_cons_ess     : word;
  ajout_vt_esso  : word;
  accelere       : boolean;
 
  purge          : boolean;
  temps_purge    : word;   // compteur en secondes
  tps_purge_max  : word;
  timeOUT_purge  : boolean;
 
  temps_defoul   : word;
  tps_deffou_max : word;
  TimeOUT_defoul : boolean;
 
  temps_total    : byte;   // compteur en minutes
  num_requis     : byte;
  remplissage    : boolean;
 
  chauffage      : boolean;
  chauffe_enable : boolean;
  brassage       : boolean;
 
  defoulage      : boolean;
 
  vidange        : boolean;
  sens_enable    : boolean; // permet ou bloque le changement de sens automatique du moteur toutes les 15s
  TimeOUT_sens   : boolean;
  vert           : boolean;
  Temperat_max   : float;   // temperature de lavage
  prog1          : Tprog;
  numero         : byte;
  a_choisir      : boolean;
{--------------------------------------------------------------}
{functions }
 
procedure init_ports;  // ports perso
// 0 = entree, 1=sortie
begin
 
  portA:= %00000000;
  DDRA:=  %01111111;            // portA[7] = entree Thermistance
 
  portB:= %00000000;
  DDRB:=  %11111101;            // portB[1] = T1 = entree (pour le frequencemetre)
 
  DDRC:= DDRC and %01111111;
 
  portD       := %10100011;     // les 1 sur les pins en entrees activent les R de Pull Up (tirage à VCC)
  DDRD:= DDRD or %10011000;     // portD[6] en entree (ICP periodemetre)  portD[7] en sortie (OC2)
 
  SFIOR:= SFIOR  and %11111011; // p:50 (bit PUD du SFIOR)
// (the Pull-up Disable – PUD bit in SFIOR disables the pull-up function for all pins in all ports when set)
end;
 
 
procedure interroge_IR;
begin
  if RecvRC5(rxAdr, rxCmd) then    // interroge IR
    adr1:= integer(rxAdr);
    cmd1:= {%00111111 and} integer(rxCmd);
    octet_IR:= byte(cmd1);  // le bit6 (=64 decimal) est=1 un appui de touche de la zapette sur deux
    memobitsix:= bitsix;
    bitsix:= octet_IR and %01000000;
    if bitsix <> memobitsix then
      nouveau:= true;
    else nouveau:= false;
    endif;
    octet_IR:= octet_IR and %00111111; // on supprime l'info de repetition de l'octet
//    portx:= portx or ......;  // allume LED
  else
    octet_IR:= $FF;
//    portx:= portx and %......;  // eteint LED
  endif;
if nouveau = false then octet_IR:= $FF; endif; // pas de repetition auto dans cette application
end;
 
 
procedure InitINTs;
begin
{Les 3 bits de poids faible de TCCR2 selectionnent l'horloge et le prescaler. voir page 119 et 125 du pdf
Bit 6, 3 – WGM21:0 = 01 -> mode CTC voir p:125 du pdfCTC = "Clear Timer on Compare Match" (CTC) Mode
In "Clear Timer on Compare mode" (CTC mode, WGM21:0 = 2), the counter is cleared to zero when the
counter value (TCNT2) matches the OCR2. The OCR2 defines the top value for the counter, hence also its resolution.
}
  TCCR2 := TCCR2 or  %10001111;  // p:125
  TIMSK := TIMSK and %01111111;  // INT Timer2 comp disable;  Voir le datasheet ATmega32.pdf p:130}
  GICR  := GICR  or  %01000000;  // extINT0 enable; voir page 67 du pdf
  MCUCR := MCUCR or  %00000010;  // The falling edge of INT0 generates an interrupt request. p:67 du pdf
end;
 
 
(*
interrupt Timer2COMP;
{ Commande de l'angle de fermeture du triac
 Cette interruption n'est plus utilisée (abandonnée parce que trop de télescopages avec les autres)
 On utilise directement le signal sur la sortie OCE
 la sortie OC2 Genere un signal "carré" qui bascule à chaque comparaison reussie.
 un petit circuit (monostable avec un NE555 et des inverseurs 40106) génere ensuite 2 impulsions
 espacées de 10ms pour commander les deux alternances
 en 50Hz, la periode vaut 20ms (1/50Hz) et la demi période vaut donc 10ms
 REMARQUE: The OCF2 Flag is automatically cleared when the interrupt is executed. (p:116 du pdf)
}
begin
{
 portB:= portB or %00000001;  // POUR TEST
 udelay(20);                  // POUR TEST
 portB:= portB and %11111110; // POUR TEST
}
end;
*)
 
interrupt Int0;
// front descendant sur l'entree Int0
begin
  PushAllRegs;
  TCNT2:= 0;
  TCCR2:= TCCR2 or  %00110000;    // set OCE on compare match
  TCCR2:= TCCR2 or  %10000000;    // bit FOCE2 voir p:125 (force comparaison virtuelle pour RAZ OCE)
  // ce qui a pour effet de passer la sortie OCE à 1
  if not stop_imps then
    TCCR2:= TCCR2 and %11101111;    // clear OCE on compare match  (generera le signal sur OCE)
  endif;
  PopAllRegs;
end;
 
 
procedure Affiche_STACK_free;  // attention: ne pas appeller depuis une INT sinon fait planter
begin
  LCDxy(16, 3);
  Write(LCDout, IntToStr(stk1 : 3 : '0') );
//  LCDxy(16, 3);
//  Write(LCDout, IntToStr(fram_free : 3 : '0') ); fonction bugguée? chaque lecture consomme 1 octet!
//
end;
 
 
procedure vitesse_mini;
begin
  OCR2:= OCRmax;
  OCR2_real:= OCRmax;
end;
 
 
procedure relais1_on;
begin
  portA:= portA or %00000100;
  relais1:= true;
  LCDxy(0, 3);
  Write(LCDout, '>');
end;
 
 
procedure relais1_off;
begin
  portA:= portA and %11111011;
  relais1:= false;
  LCDxy(0, 3);
  Write(LCDout, ' ');
end;
 
 
procedure relais2_on;
begin
  portB:= portB or %00000100;
  relais2:= true;
  LCDxy(0, 3);
  Write(LCDout, '<');
end;
 
 
procedure relais2_off;
begin
  portB:= portB and %11111011;
  relais2:= false;
  LCDxy(0, 3);
  Write(LCDout, ' ');
end;
 
 
procedure EV2_on; // commande électrovanne2. //(on peut en rajouter... pour le prelavage par exemple...)
begin
  portA:= portA or %00000010;
  EV2:= true;
  temps_EV2:= 0;
  LCDxy(5, 3);
  Write(LCDout, 'EV');
  LCDxy(7, 3);
  Write(LCDout, '2');
end;
 
 
procedure EV2_off;
begin
  portA:= portA and %11111101;
  EV2:= false;
  if not(EV1) then  // le dernier ferme la porte en sortant!
    LCDxy(5, 3);
    Write(LCDout, '   ');
  endif;
  LCDxy(7, 3);
  Write(LCDout, ' ');  // chacun balaye devant sa porte
end;
 
 
procedure CHAUFFAGE_off;
begin
//  stk1:= GetStackFree;  if stk1 < stk1_min then stk1_min:= stk1; endif;
//  fram_free:= GetStackFree;  if fram_free < fram_free_min then fram_free_min:= fram_free; endif;
  portB:= portB and %11101111;  // relais chauffage
  chauffage:= false;
  periode_ch_sens:= 10;
end;
 
 
procedure CHAUFFAGE_on;
begin
//stk1:= GetStackFree;  if stk1 < stk1_min then stk1_min:= stk1; endif;
  if chauffe_enable then
    stop_imps:= true;
    portB:= portB or %00010000; // relais chaufage
    chauffage:= true;
    periode_ch_sens:= 30;
  endif;
end;
 
 
procedure POMPE_on;
begin
  Chauffage_OFF;
  portB:= portB or %00001000; // pompe
  pompe:= true;
  temps_pompe:= 0;
end;
 
 
procedure POMPE_off;
begin
  portB:= portB and %11110111;  // pompe
  pompe:= false;
end;
 
 
procedure detection_niveau_eau;  // j'ai supprimé la detection du niveau2 (3/4 de cuve, c'est trop)
begin
//stk1:= GetStackFree;  if stk1 < stk1_min then stk1_min:= stk1; endif;
  if (PinD and %00100000) = 0 then
    TXTniv:= 'Lo';  NIV1:= true; NIV2:= false;
//  elsif (PinD and %00100000) = 0 then
//    TXTniv:= 'Hi'; NIV1:= false; NIV2:= true;
  else
    NIV1:= false;
    NIV2:= false;
    TXTniv:= '--';
  endif;
end;
 
 
procedure RAZ_tempos;
begin
  // initialisation des compteurs avec des valeurs qui les bloquent
  temps_lavage:= 100*60;  // en secondes
  temps_rincage:= 100*60; // en secondes
  temps_pompe:= 0;
  temps_EV2:= 0;
//  temps_fin_vidange:= 100;
  temps_essorage:= 100*60; // en secondes
  temps_defoul:= 100*60;
  temps_total:= 100;
  temps_ch_sens:= 5;
  tps_pause:= 1;
 
  prog1:= couleur;
  Temperat_max:= 40;
  tps_lavage_max:= 10*60; // secondes
  tps_pause:= 1;
  tps_essr_max:= 4*60;    // secondes
  tps_purge_max:= 15;     // secondes
  tps_deffou_max:= 30;    // secondes
  periode_ch_sens:= 10;   // secondes
  ti:= 0;
end;
 
 
procedure init_variables;
begin
  stk1_min:= 1000;
  fram_free_min:= 1000;
  stop_imps:= true;
  remplissage:= false;
  lavage:= false;
  chauffe_enable:= false;
  brassage:= false;
  rincage:= false;
  nb_rincage_max:= 4;
  vidange:= false;
  essorage:= false;
  defoulage:= false;
  sens_enable:= false;
  a_choisir:= true;
  RAZ_tempos;
  affi_requis:= false;
//  F_purge:= 30;
  relais1_off;
  relais2_off;
  EV2_off;
  Pompe_off;
  OCR2:= 140; // [10..162] retard a la commutation du triac alimentant le moteur (162 = pas de tension)
  compteur1:= 0;
  Temperat_max:= 20;  // en degres ºC
end;
 
 
procedure eteint_toutes_LED;
begin
  portA:= portA or  %00001000; // RAZ des deux CD4017  (éteint tout because les Q0 ne sont pas câblés)
  udelay(1);
  portA:= portA and %11110111;
  udelay(1);
end;
 
 
procedure allume_LED(num : byte); //  n in [0..17]
var
  n              : byte;
begin
  portA:= portA or  %00001000; // RAZ des deux CD4017  (éteint tout because les Q0 ne sont pas câblés)
  udelay(1);
  portA:= portA and %11110111;
  udelay(1);
  inc(num);
 
  if num in [1..18] then
    if num < 10 then
      for n:= 1 to num do
        portA:= portA or  %00010000; // CLK du premier CD4017
        udelay(1);
        portA:= portA and %11101111;
        udelay(1);
      endfor;
    else
      for n:= 10 to num do
        portA:= portA or  %00100000; // CLK du second CD4017
        udelay(1);
        portA:= portA and %11011111;
        udelay(1);
      endfor;
    endif;
  endif;
end;
 
 
procedure STOP(str1 : string[20]);
begin
  init_variables;  // stop de tous les organes
  portA:= portA and %10111111;  // eteint LED essorage
  allume_LED(16);
  LCDclr;
  LCDxy(0, 0);  Write(LCDout, 'STOP logigiel ');
  LCDxy(0, 1);  Write(LCDout, str1);
  LCDxy(0, 1);  Write(LCDout, 'Appuyez bouton rouge');
  mdelay(1000);
repeat until  (PinD and %00000010) = 0; // attend appui sur bouton rouge avant de reseter
  system_reset;
end;
 
 
procedure detection_bouton_ROUGE;  // ARRET  et RESET du programme
begin
//stk1:= GetStackFree;  if stk1 < stk1_min then stk1_min:= stk1; endif;
//fram_free:= GetFrameFree;  if fram_free < fram_free_min then fram_free_min:= fram_free; endif;
 
  if (PinD and %00000010) = 0 then
    STOP('BT R');
  endif;
end;
 
 
procedure detection_bouton_VERT; // ABREGE le lavage ou le rincage en cours (passe au numero suivant=vidange)
begin
  if (PinD and %00000001) = 0 then
    vert:= true;
    LCDclr;
    LCDxy(10, 1);
    Write(LCDout, 'NEXT !');
    mdelay(500);
    LCDclr;
  else
    vert:= false;
  endif;
end;
 
(*
procedure ACQUI_vitessse;
begin
//Frequencemetre - acquisition de la vitesse du moteur
  if SemaStat(FreqCountSema) <> 0 then
    Freq:= GetFreqCounter;
  endif;
end;
*)
 
procedure ACQUI_vitessse;
begin
//Periodemetre - acquisition de la vitesse du moteur
  Periode:= GetTimeCounter;
end;
 
 
 
procedure TEMPO_s(nb_secondes : longword);
// boucle de tempo qui ne bloque pas l'arret d'urgence...
begin
  disableInts;
  RTCTimer_Load(1, nb_secondes); // canal1, 3secondes
  enableInts;
 
  RTCTimer_Start(1);  // start RTCTimer canal 1
  timeout1:= false; // sera mis à true par l'INT RTCTimer
  repeat
    if (PinD and %00000010) = 0 then // detection bouton rouge
      STOP('Bt_R'); // reset le programme
    endif;
    detection_bouton_vert;
  until timeout1 or vert;
  vert:= false;
end;
 
 
procedure acqui_analogique;
begin
//  LCDclr;
 
  acqui_ADC:= GetADC;
  //   acqui_ADC:= acqui_ADC shr 2;   // 8 bits au lieu de 10 de resolution
  case acqui_ADC of
    859..865 : pos_bt:= 0;   //   860
             |
    854..858 : pos_bt:= 1;  //    856
             |
    848..853 : pos_bt:= 2; //     850
             |
    842..847 : pos_bt:= 3;  //    844
             |
    836..841 : pos_bt:= 4;   //    838
             |
    827..835 : pos_bt:= 5;   //   830
             |
    816..826 : pos_bt:= 6;   //   822
             |
    806..815 : pos_bt:= 7;   //   811
             |
    791..805 : pos_bt:= 8;    //  799
             |
    776..790 : pos_bt:= 9;  //   785
             |
    756..775 : pos_bt:= 10;   //  768
             |
    736..755 : pos_bt:= 11;   //  749
             |
    711..735 : pos_bt:= 12;   //   724
             |
    671..710 : pos_bt:= 13;    //  693
             |
    631..670 : pos_bt:= 14;   //   653
             |
    551..630 : pos_bt:= 15;   //   603
             |
    501..550 : pos_bt:= 16;   //  537
             |
    400..500 : pos_bt:= 17;   //  445
             |
    250..350 : pos_bt:= 18;  //   310
             |
    80..100  : pos_bt:= 19;  //   92
             |
  endcase;
 
 
//    LCDxy(0, 1);
//    Write(LCDout, ByteToStr(n : 3));
 
end;
 
 
procedure regulation_TEMPERATURE;
var
  T              : float;
  t1             : integer;
begin
  //mesure de la temperature
  acqui_ADC:= GetADC;
  acqui_ADC:= acqui_ADC shr 2;   // 8 bits au lieu de 10 de resolution
  T:= (3500 / float(acqui_ADC)) - 11;  // voir feuille de calcul Ooo
 
  LCDxy(8, 3);
  if (T > 0) and (T < 100) then
    t1:= round(T);
    Write(LCDout, 'T=' + IntToStr(t1 : 2) + ' ');
 
    if (T < (Temperat_max - 5) ) and not(chauffage) then
      relais1_off;
      relais2_off;
      chauffage_ON;
    elsif (T > Temperat_max) and chauffage  then
      Chauffage_OFF;
    endif;
  endif;
end;
 
 
procedure Affiche_temps(nb_secondes : word);
var
  h, mn, sec     : byte;
  R1             : word;
  signe          : char;
begin
  h :           = byte(nb_secondes div 3600);
  R1 :          = nb_secondes mod 3600;
  mn :          = byte(R1 div 60);
  sec :         = byte(R1 mod 60);
  LCDxy(13, 0);
  LCDclrEOL;
  Write(LCDout, ByteToStr(mn : 2 : '0') + ':' + ByteToStr(sec : 2 : '0'));
end;
 
 
procedure change_SENS;
begin
//stk1:= GetStackFree;  if stk1 < stk1_min then stk1_min:= stk1; endif;
//fram_free:= GetFrameFree;  if fram_free < fram_free_min then fram_free_min:= fram_free; endif;
  vitesse_mini;    // pour ralentir
  stop_imps:= true;
  tempo_s(1);
  if relais1 and not relais2 then
    relais1_off;
    tempo_s(1);
    relais2_on;
  elsif relais2 and not(relais1) then
    relais2_off;
    tempo_s(1);
    relais1_ON;
  elsif not(relais1) and not(relais2) then   // si aucun
    relais1_ON;
  elsif relais1 and relais2 then  // si les deux
    relais2_off;
  endif;
 
  if chauffage = true then
    // brassage_pendant_chauffage;  debraye pour TEST
  else
    tempo_s(tps_pause);
    vitesse_mini;    // pour repartir lentement
//    stop_imps:= false;
  endif;
end;
 
 
procedure AFFICHAGES; // permsise toutes les secondes par le flag affi_requis.
begin
  affi_requis:= false;
  if lavage then
    Affiche_temps(tps_lavage_max - temps_lavage);
    if chauffe_enable then
      regulation_temperature; // comporte un affichage donc ne pas deplacer dans 1 boucle rapide
    endif;
  endif;
 
  if chauffage then
    LCDxy(13, 3);
    Write(LCDout, 'CH');
  else
    LCDxy(13, 3);
    Write(LCDout, '  ');
  endif;
 
  LCDxy(2, 3);
  Write(LCDout, TXTniv);
 
//------------------------------------------------------------------------------
  if rincage then
    Affiche_temps(tps_rincage_max - temps_rincage);
  endif;
//  stk1:= GetStackFree;
//fram_free:= GetFrameFree;
//  Affiche_STACK_free;
 
//------------------------------------------------------------------------------
{
  if temps_pause < 100 then inc(temps_pause);  endif; // ne reboucle pas
    if temps_pause = 2 then
      vitesse_mini;
      pause1:= false;
    endif;
  endif;
}
//------------------------------------------------------------------------------
  if defoulage then
    Affiche_temps(tps_deffou_max - temps_defoul);
  endif;
//------------------------------------------------------------------------------
  if essorage then
    Affiche_temps(tps_essr_max - temps_essorage);
  endif;
 
  if TimeOUT_sens then
    TimeOUT_sens:= false;
    change_sens;
  endif;
end;
 
 
procedure remplissage_NIV1(tps_plus : longword); // par EV2
begin
  remplissage:= true;
  LCDxy(3, 0);
  LCDclrEOL;
  Write(LCDout, 'EAU');
 
  vitesse_mini;
  stop_imps:= true;
  sens_enable:= false;
  relais1_off;
  relais2_off;
  EV2_on;
 
  repeat
    detection_bouton_ROUGE;
    detection_bouton_vert;
    detection_niveau_eau;
 
    if affi_requis then  // 1 fois par seconde
      AFFICHAGES;
    endif;
    LCDxy(2, 3);
    Write(LCDout, TXTniv);
  until NIV1 or NIV2 or vert;  // oui, NIV2 convient bien entendu.
  vert:= false;
  if octet_IR = 12 then
    STOP('3');
  endif;
  octet_IR:= 255;
  LCDxy(0, 2);
  LCDclrEOL;
  Write(LCDout, 'Remplissage +');  // un peu plus, pour eviter d'en reprendre un verre 10 fois de suite
  tempo_s(tps_plus);
 
  EV2_off;
  LCDclrLine(2);
  remplissage:= false;
end;
 
 
procedure RINCER(num_rincage : byte);
begin
 
  rincage:= true;
  LCDxy(3, 0);
  LCDclrEOL;
  Write(LCDout, 'RINCAGE' + ByteToStr(num_rincage : 1));
 
  stop_imps:= true;
  vitesse_mini;
  chauffe_enable:= false;
  Chauffage_OFF;
  periode_ch_sens:= 10;
  sens_enable:= true;
  stop_imps:= true;
  vitesse_mini;    // pour partir lentement
  tempo_s(1);
  relais2_off;
  tempo_s(1);
  relais1_ON;
  tempo_s(1);
 
  stop_imps:= false;
  temps_rincage:= 0;
 
//---------------------------- boucle rincage ----------------------------------
  LCDclrLine(2);
  LCDxy(0, 2);
  Write(LCDout, 'boucle rincage');
 
  timeOUT_rincage:= false;
  repeat
    detection_bouton_ROUGE;
    detection_bouton_vert;
    detection_niveau_eau;
    if not NIV1 then
      remplissage_NIV1(30);
    endif; // si le niveau baisse, on complete;
 
    stop_imps:= false;
    Periode:= GetTimeCounter; // 50Hz -> P=2000
    if ((Periode > P_cons_lav + 10) or (Periode = 0)) and (OCR2_real > OCRmin_LAV) then  // asservissement de la vitesse
      OCR2_real := OCR2_real - 0.01;  //accelere
    endif;
    if (Periode < P_cons_lav - 10 ) and (Periode <> 0) and (OCR2_real < OCRmax_LAV) then
      OCR2_real := OCR2_real + 0.01;  //ralentit
    endif;
    OCR2:= round(OCR2_real);
    udelay(50);
 
    if affi_requis and not vert then
      AFFICHAGES;
    endif;
 
  until timeOUT_rincage or vert;
  vert:= false;
//------------------------------------------------------------------------------
  vitesse_mini;
  stop_imps:= true;
 
  sens_enable:= false;
  stop_imps:= true;
  vitesse_mini;
  LCDxy(3, 0);
  LCDclrEOL;
  Write(LCDout, 'fin rinçage');
 
  tempo_s(1);
  relais1_off;
  relais2_off;
  tempo_s(1);
  rincage:= false;
  LCDclr;
end;
 
 
procedure PURGER;  // mini essorage à la fin de la vidange afin de purger l'eau du linge
begin
//  offset_OCR2:= 0;
  Chauffage_OFF;
  EV2_off;
  LCDxy(3, 0);
  LCDclrEOL;
  Write(LCDout, 'PURGE');
 
  stop_imps:= false;
  pompe_on;
 
  vitesse_mini;
  stop_imps:= true;
 
  tempo_s(1);
  relais2_off;
  tempo_s(1);
  relais1_ON;
  tempo_s(1);
  sens_enable:= false;
  stop_imps:= false; // demarrre le moteur
 
  temps_purge:= 0;
  purge:= true;
 
//------------------------- boucle "esso-purge" --------------------------------
  LCDclrLine(2);
  LCDxy(0, 2);
  Write(LCDout, 'boucle purge');
 
  timeOUT_purge:= false;
  repeat
    detection_bouton_ROUGE;
    detection_bouton_vert;
 
    stop_imps:= false;
 
    Periode:= GetTimeCounter; // 50Hz -> P=2000
    if ((Periode > P_cons_purge + 10) or (Periode = 0)) and (OCR2_real > OCRmin_LAV) then  // asservissement de la vitesse
      OCR2_real := OCR2_real - 0.01;  //accelere
    endif;
    if (Periode < P_cons_purge - 10 ) and (Periode <> 0) and (OCR2_real < OCRmax_LAV) then
      OCR2_real := OCR2_real + 0.01;  //ralentit
    endif;
    OCR2:= round(OCR2_real);
    udelay(50);
 
    if affi_requis then
      AFFICHAGES;
    endif;
 
  until timeOUT_purge or vert;
//------------------------------------------------------------------------------
 
  stop_imps:= true;
  vitesse_mini;
 
  tempo_s(1);
  relais1_off;
  relais2_off;
  tempo_s(1);
 
  Pompe_off;
  vidange:= false;
  defoulage:= false;
  stop_imps:= true;
  purge:= false;
  LCDclr;
end;
 
 
procedure TEST_asservissement;
var
  P_consigne     : word;
begin
  LCDclr;
  LCDxy(5, 1);
  LCDclrEOL;
  Write(LCDout, ' TEST vitesse');
  stop_imps:= true;
  tempo_s(1);
  relais2_off;
  relais1_ON;
  tempo_s(1);
 
  sens_enable:= false;
  stop_imps:= false;
  P_consigne:= 1000; // + 100 * word(pos_bt);
 
  loop
    detection_bouton_ROUGE;
{
     Periode:= GetTimeCounter; // 50Hz -> P=2000
     if ((Periode > P_consigne + 10) or (Periode = 0)) and (OCR2 > OCRmin_LAV) then  // asservissement de la vitesse
      OCR2:= OCR2 - 1;   //accelere
    endif;
    if (Periode < P_consigne - 10 ) and (Periode <> 0) and (OCR2 < OCRmax_LAV) then
      OCR2:= OCR2 + 1;   //ralentit
    endif;
    mdelay(10);
}
    Periode:= GetTimeCounter; // 50Hz -> P=2000
    if ((Periode > P_cons_lav + 10) or (Periode = 0)) and (OCR2_real > OCRmin_LAV) then  // asservissement de la vitesse
      OCR2_real := OCR2_real - 0.01;  //accelere
    endif;
    if (Periode < P_cons_lav - 10 ) and (Periode <> 0) and (OCR2_real < OCRmax_LAV) then
      OCR2_real := OCR2_real + 0.01;  //ralentit
    endif;
    OCR2:= round(OCR2_real);
    udelay(50);
 
 
  endloop;
end;
 
 
procedure ESSORER;
begin
  portA:= portA or %01000000;  // allume LED essorage
  LCDclr;
  accelere:= false;
//  offset_OCR2:= 0;
  Chauffage_OFF;
  EV2_off;
  LCDxy(3, 0);
  LCDclrEOL;
  Write(LCDout, 'ESSORAGE');
 
  stop_imps:= false;
  pompe_on;
 
  stop_imps:= true;
  tempo_s(1);
  relais2_off;
  tempo_s(1);
  relais1_ON;
  tempo_s(1);
 
  sens_enable:= false;
  stop_imps:= false;
if tps_essr_max > 5*60 then tps_essr_max:= 5*60; endif; // securite
  temps_essorage:= 0;
  essorage:= true;
 
  LCDclrLine(2);
  LCDxy(0, 2);
  Write(LCDout, 'boucle essorage');
//  mdelay(1000);
 
  timeOUT_ess:= false;
  accelere:= true; // add_F_ess sera incremente chaque seconde par RTCTickSecond
 
//  baseOCR2:= 130;
//  offset_OCR2:= 0;
 
  vitesse_mini;
  P_cons_ess:= 2000; // vitesse tres lente au depart pour defouler
 
//------------------------- boucle essorage ------------------------------------
  repeat
    detection_bouton_ROUGE;
    detection_bouton_vert;
 
    if vert then
      vert:= false;
      stop_imps:= true;
      tempo_s(12); // pour laisser le temps au tambour de ralentir
      OCR2_real:= 130;
      P_cons_ess:= 2000;
      stop_imps:= false;
    endif;
 
    stop_imps:= false;
 
 
    if temps_essorage > 10 then  // 10s de defoulage
      P_cons_ess:= 20000 div (temps_essorage);
      if  P_cons_ess < 50 then
        P_cons_ess :=50;
      endif;
// ce qui fait accelerer la vitesse linéairement / temps    (f=1/T)
    endif;
 
    Periode:= GetTimeCounter; // 50Hz -> P=2000
    if ((Periode > P_cons_ess + 10) or (Periode = 0)) and (OCR2_real > OCRmin_ESS) then  // asservissement de la vitesse
      OCR2_real := OCR2_real - 0.01;  //accelere
    endif;
    if (Periode < P_cons_ess - 10 ) and (Periode <> 0) and (OCR2_real < OCRmax_ESS) then
      OCR2_real := OCR2_real + 0.01;  //ralentit
    endif;
    OCR2:= round(OCR2_real);
    udelay(100);
 
    if affi_requis then
      AFFICHAGES;
    endif;
 
  until timeOUT_ess;
  vert:= false;
//------------------------------------------------------------------------------
  portA:= portA and %10111111;  // eteint LED essorage
  LCDclr;
  Write(LCDout, 'FIN ESSORAGE');
  LCDxy(0, 1);
  Write(LCDout, 'Lessive terminée');
 
  stop_imps:= true;
  vitesse_mini;
  Pompe_off;
  vidange:= false;
  defoulage:= false;
 
  tempo_s(1);
  relais1_off;
  relais2_off;
  tempo_s(1);
 
  essorage:= false;
  allume_LED(16);
  while true do
    detection_bouton_ROUGE;
  endwhile;
end;
 
{
procedure DEFOULER;
var
  eau_restante   : boolean;
begin
if tps_deffou_max > 5*60 then tps_deffou_max:= 5*60; endif; // securite
  chauffe_enable:= false;
  Chauffage_OFF;
  EV2_off;
  stop_imps:= true;
  LCDxy(3, 0);
  LCDclrEOL;
  Write(LCDout, 'DEFOULAGE');
 
  LCDclrLine(2);
  LCDxy(0, 2);
  Write(LCDout, 'vidange'); // par sécurité
  eau_restante:= false;     // a priori
  pompe_on;
  repeat
    detection_bouton_ROUGE;
    detection_bouton_vert;
    detection_niveau_eau;
    if NIV1 then
      eau_restante:= true;
    endif;
  until not(NIV1) or vert; // normalement on sort dès la première passe
 
  if eau_restante then
    LCDxy(0, 2);   // normalement on ne passe pas ici
    Write(LCDout, 'VIDANGE ++');
    tempo_s(30);
  endif;
 
  LCDclrLine(2);
 
  LCDxy(3, 0);
  LCDclrEOL;
  Write(LCDout, 'DEFOULAGE' );
 
  vitesse_mini;
  stop_imps:= true;
 
  tempo_s(1);
  relais2_off;
  tempo_s(1);
  relais1_ON;
  tempo_s(1);
  sens_enable:= false;
  stop_imps:= false; // demarrre le moteur
  temps_defoul:= 0;
  defoulage:= true; // le moteur tournera a la vitesse de rincage
 
//------------------------- boucle defoulage -----------------------------------
  LCDclrLine(2);
  LCDxy(0, 2);
  Write(LCDout, 'boucle defoulage');
 
  TimeOUT_defoul:= false;
  repeat
    detection_bouton_ROUGE;
    detection_bouton_vert;
// asservissement de la vitessse du moteur
 
    Periode:= GetTimeCounter; // 50Hz -> P=2000
    if ((Periode > P_cons_defoul + 10) or (Periode = 0)) and (OCR2_real > OCRmin_LAV) then  // asservissement de la vitesse
      OCR2_real := OCR2_real - 0.01;  //accelere
    endif;
    if (Periode < P_cons_defoul - 10 ) and (Periode <> 0) and (OCR2_real < OCRmax_LAV) then
      OCR2_real := OCR2_real + 0.01;  //ralentit
    endif;
    OCR2:= round(OCR2_real);
    udelay(50);
 
    if affi_requis then
      AFFICHAGES;
    endif;
  until TimeOUT_defoul or vert;
//------------------------------------------------------------------------------
  stop_imps:= true;
  vitesse_mini;
  defoulage:= false;
  LCDclr;
end;
}
 
procedure VIDANGER;
begin
  LCDxy(3, 0);
  LCDclrEOL;
  Write(LCDout, 'VIDANGE');
 
  chauffe_enable:= false;
  Chauffage_OFF;
  vidange:= true;
  LCDclrLine(2);
  LCDxy(0, 2);
  Write(LCDout, 'Pompe en marche');
 
  pompe_on;
  affi_requis:= false;
  repeat
    detection_bouton_ROUGE;
    detection_bouton_vert;
    detection_niveau_eau;
 
    if affi_requis then  // 1 fois par seconde
      affi_requis:= false;
      LCDxy(2, 3);
      Write(LCDout, TXTniv);
    endif;
 
  until not(NIV1 or NIV2) or vert;
  vert:= false;
 
  //octet_IR:= 255;
// a partir de cet instant il faut encore au minimum 16s pour vider la cuve
// On fait donc tourner la pompe 20s supplémentaires
  LCDxy(3, 0);
  LCDclrEOL;
  Write(LCDout, 'VIDANGE +');
  LCDxy(2, 3);
  Write(LCDout, TXTniv);
  tempo_s(20);
 
  Pompe_off;
  LCDclrLine(2);
  vidange:= false;
end;
 
 
 
procedure Affiche_1octet(octet_i : byte);
begin
  LCDxy(8, 3);
  Write(LCDout, 'x=' + ByteToStr(octet_i : 3 : '0') );
 
end;
 
 
procedure test_IR;
var
  chr1           : char;
begin
  LCDclr;
  LCDxy(0, 0);
  Write(LCDout, 'Lave Linge v: ' + version );
  LCDxy(0, 1);
  Write(LCDout, 'Test IR' );
  LCDxy(0, 2);
  Write(LCDout, 'ok ou 0 pour sortir' );
  repeat
    detection_bouton_ROUGE;
    interroge_IR;
    if nouveau                 = true then
      chr1:= '*';
    else chr1:= ' ';
    endif;
    if octet_IR <> 255 then
      LCDxy(0, 3);
      Write(LCDout, ByteToStr(octet_IR : 3 : ' ') + ' ' + chr1);
      mdelay(200);
    endif;
  until (octet_IR = 0) or (octet_IR = 23);
  STOP('4'); // reset
end;
 
 
procedure LAVER;
begin
  lavage:= true;
if  tps_lavage_max > 15*60 then tps_lavage_max:= 15; endif; // securite
if  Temperat_max > 80 then Temperat_max:= 80; endif;        // securite
  LCDxy(3, 0);
  LCDclrEOL;
  Write(LCDout, 'LAVAGE');
  LCDclrLine(1);
  LCDxy(7, 1);
  Write(LCDout, Nom_prg);
  sens_enable:= true;
  stop_imps:= true;
  tempo_s(1);
  relais2_off;
  tempo_s(1);
  relais1_ON;
  tempo_s(1);
  vitesse_mini;    // pour partir lentement
  temps_ch_sens:= 0;
  temps_lavage:= 0;  // de temps est incrementé par la procedure RTCtickMinute
 
//----------------------------- BOUCLE LAVAGE ----------------------------------
  LCDclrLine(2);
  LCDxy(0, 2);
  Write(LCDout, 'boucle lavage');
 
  timeOUT_lavage:= false;
  repeat
    detection_bouton_ROUGE;
    detection_bouton_vert;
    detection_niveau_eau;
 
    if not NIV1 then
      remplissage_NIV1(30);
    endif; // si le niveau baisse, on complete;
 
    if (NIV1 or NIV2) and (prog1 <> froid) and (temps_lavage > 30 ) and (temps_lavage < 5*60 ) then
//on ne chaufffe pas avant 30s (brassage pour melanger le detergent) ni après 5mn de tournage
//(la temperature descendra alors lentement... je prefère ça à rechauffer à plusieurs reprises)
      chauffe_enable:= true;
    else
      chauffe_enable:= false;
      Chauffage_OFF;
    endif;
 
    stop_imps:= chauffage; // le moteur ne peut tourner qu'en l'absence de chauffage;
    sens_enable:= not(chauffage);
 
    Periode:= GetTimeCounter; // 50Hz -> P=2000
    if ((Periode > P_cons_lav + 10) or (Periode = 0)) and (OCR2_real > OCRmin_LAV) then  // asservissement de la vitesse
      OCR2_real := OCR2_real - 0.01;  //accelere
    endif;
    if (Periode < P_cons_lav - 10 ) and (Periode <> 0) and (OCR2_real < OCRmax_LAV) then
      OCR2_real := OCR2_real + 0.01;  //ralentit
    endif;
    OCR2:= round(OCR2_real);
    udelay(50);
 
    if affi_requis then
      AFFICHAGES;
    endif;
 
  until timeOUT_lavage or vert;
  vert:= false;
//-------------------------------- FIN LAVAGE ----------------------------------
 
  Chauffage_OFF;
  chauffe_enable:= false;
 
  LCDxy(3, 0);
  LCDclrEOL;
  Write(LCDout, 'fin lavage');
 
  vitesse_mini;
  sens_enable:= false;
  stop_imps:= true;
  vitesse_mini;
  tempo_s(1);
  relais1_off;
  relais2_off;
  lavage:= false;
  LCDclr;
end;
 
 
 
 
 
procedure choix_programme;
begin
  disableInts;
  LCDclr;
  LCDxy(0, 0);  Write(LCDout, '==== PROGRAMME: ====' );
  LCDxy(0, 1);  Write(LCDout, '1:COULEUR   2:BLANC ' );
  LCDxy(0, 2);  Write(LCDout, '3:LAINE     4:FROID ' );
  LCDxy(0, 3);  Write(LCDout, '5:RAPIDE    6:TEST  ' );
  repeat
    detection_bouton_ROUGE;
    interroge_IR;
  until  octet_IR in [1..6, 12];
 
  case octet_IR of
    1  : prog1:= couleur;
         Nom_prg:= 'Couleur';
         Temperat_max:= 40;
         tps_lavage_max:= 10*60;
         tps_pause:= 1;
         nb_rincage_max:= 4;
         tps_essr_max:= 4*60;
         tps_deffou_max:= 15;
       |
    2  : prog1:= blanc;
         Nom_prg:= 'Blanc';
         Temperat_max:= 60;
         tps_lavage_max:= 15*60;
         tps_pause:= 1;
         nb_rincage_max:= 4;
         tps_essr_max:= 4*60;
         tps_deffou_max:= 15;
       |
    3  : prog1:= laine;
         Nom_prg:= 'Laine';
         Temperat_max:= 30;
         tps_lavage_max:= 10*60;
         tps_pause:= 15;
         nb_rincage_max:= 4;
         tps_essr_max:= 2*60;
         tps_deffou_max:= 20;
       |
    4  : prog1:= froid;
         Nom_prg:= 'A froid';
         Temperat_max:= 0;
         tps_lavage_max:= 10*60;
         tps_pause:= 1;
         nb_rincage_max:= 3;
         tps_essr_max:= 4*60;
         tps_deffou_max:= 15;
       |
    5  : prog1:= rapide;
         Nom_prg:= 'Rapide';
         Temperat_max:= 40;
         tps_lavage_max:= 5*60;
         tps_pause:= 1;
         nb_rincage_max:= 2;
         tps_essr_max:= 2*60;
         tps_deffou_max:= 15;
       |
    6  : prog1:= test;
         Nom_prg:= 'Test';
         Temperat_max:= 30;
         tps_lavage_max:= 1*60;
         tps_pause:= 1;
         nb_rincage_max:= 1;
         tps_essr_max:= 1*60;
         tps_deffou_max:= 10;
       |
    12 : STOP('8');
       |
  endcase;
  LCDclr;
  case prog1 of
    couleur : Write(LCDout, 'COULEUR 40 deg 10mn');
            |
    blanc   : Write(LCDout, 'BLANC 60 deg 15mn');
            |
    laine   : Write(LCDout, 'LAINE 30 deg 10mn');
            |
    froid   : Write(LCDout, 'FROID -- 15mn');
            |
    rapide  : Write(LCDout, 'RAPIDE 40 deg 5mn');
            |
    test    : Write(LCDout, 'TEST 30 deg 1mn');
            |
  endcase;
  tempo_s(3);
  LCDclr;
  temps_total:= 0;
  enableInts;
end;
 
 
procedure numero_15;                     // ESSORAGE
begin
  numero:= 15;
  allume_LED(numero);
  LCDxy(0, 0);
  Write(LCDout, ByteToStr(numero : 2));
 
  vidanger;
  purger;
  LCDclr;
  LCDxy(0, 0); Write(LCDout, '5 pour essorer' );
  LCDxy(0, 1); Write(LCDout, 'bouton rouge = stop' );
  repeat
    detection_bouton_ROUGE;
    interroge_IR;
  until (octet_IR = 5);
 
  essorer;
  LCDclr;
  STOP('5');
end;
 
 
procedure numero_14;                     // RINCAGE 4   facultatif
begin
  numero:= 14;
  allume_LED(numero);
  LCDxy(0, 0);
  Write(LCDout, ByteToStr(numero : 2));
  rincer(4);
  num_requis:= 15;
end;
 
 
procedure numero_13;
begin
  numero:= 13;
  allume_LED(numero);
  LCDxy(0, 0);
  Write(LCDout, ByteToStr(numero : 2));
  if nb_rincage_max > 3 then
    remplissage_NIV1(30);
    num_requis:= 14;
  else
    num_requis:= 15;  // vers essorage
  endif;
end;
 
 
procedure numero_12;
begin
  numero:= 12;
  allume_LED(numero);
  LCDxy(0, 0);
  Write(LCDout, ByteToStr(numero : 2));
  vidanger;
  purger;
  num_requis:= 13;
end;
 
 
procedure numero_11;                     // RINCAGE 3  facultatif
begin
  numero:= 11;
  allume_LED(numero);
  LCDxy(0, 0);
  Write(LCDout, ByteToStr(numero : 2));
  rincer(3);
  num_requis:= 12;
end;
 
 
procedure numero_10;
begin
  numero:= 10;
  allume_LED(numero);
  LCDxy(0, 0);
  Write(LCDout, ByteToStr(numero : 2));
 
  if nb_rincage_max > 2 then
    remplissage_NIV1(30);
    num_requis:= 11;
  else
    num_requis:= 15; // vers essorage
  endif;
end;
 
 
procedure numero_9;
begin
  numero:= 9;
  allume_LED(numero);
  LCDxy(0, 0);
  Write(LCDout, ByteToStr(numero : 2));
 
  vidanger;
  purger;
  num_requis:= 10;
end;
 
 
procedure numero_8;                      // RINCAGE 2
begin
  numero:= 8;
  allume_LED(numero);
  LCDxy(0, 0);
  Write(LCDout, ByteToStr(numero : 2));
 
  rincer(2);
  num_requis:= 9;
end;
 
 
procedure numero_7;
begin
  numero:= 7;
  allume_LED(numero);
  LCDxy(0, 0);
  Write(LCDout, ByteToStr(numero : 2));
 
  remplissage_NIV1(30);
  num_requis:= 8;
end;
 
 
procedure numero_6;
//bonjour chez vous
begin
  numero:= 6;
  allume_LED(numero);
  LCDxy(0, 0);
  Write(LCDout, ByteToStr(numero : 2));
 
  vidanger;
  purger;
  num_requis:= 7;
end;
 
 
procedure numero_5;                       // RINCAGE 1
begin
  numero:= 5;
  allume_LED(numero);
  LCDxy(0, 0);
  Write(LCDout, ByteToStr(numero : 2));
 
  rincer(1);
  num_requis:= 6;
end;
 
 
procedure numero_4;
begin
  numero:= 4;
  allume_LED(numero);
  LCDxy(0, 0);
  Write(LCDout, ByteToStr(numero : 2));
 
  remplissage_NIV1(30);
  num_requis:= 5;
end;
 
 
procedure numero_3;
begin
  numero:= 3;
  allume_LED(numero);
  LCDxy(0, 0);
  Write(LCDout, ByteToStr(numero : 2));
 
  vidanger;
  purger;
  num_requis:= 4;
end;
 
 
procedure numero_2;
begin
if a_choisir then choix_programme; endif;
  numero:= 2;
  allume_LED(numero);
  LCDxy(0, 0);
  Write(LCDout, ByteToStr(numero : 2));
  laver;
  num_requis:= 3;
end;
 
 
procedure numero_1;
begin
  choix_programme;
  a_choisir:= false; // pour ne pas devoir le choisir une 2eme fois au numero_2
  numero:= 1;
  allume_LED(numero);
  LCDxy(0, 0);
  Write(LCDout, ByteToStr(numero : 2));
 
  vidanger;
  remplissage_NIV1(30); // niveau haut mais inferieur à NIV2 (qui demande 3mn de remplissage!)
  num_requis:= 2;
end;
 
 
procedure numero_0;
begin
  numero:= 0;
  allume_LED(numero);
  LCDxy(0, 0);
  Write(LCDout, ByteToStr(numero : 2));
 
  STOP('6');
end;
 
 
procedure RTCtimer(chan : byte); // CallBack from RTCtimer
begin
  if chan = 1 then
    timeout1:= true;
  endif;
end;
 
{$NOSAVE}
procedure RTCtickSecond;       // CallBack from RTClock
//var
//  char1          : char;
begin
  PushAllRegs; // sauvegarde sur la pile et non dans iData en statique
 
  // portA:= portA xor %00100000;  // fait clignoter une LED pour TEST
 
  if lavage then
    if (temps_lavage  < 120*60) and not(chauffage) then  // ne boucle pas 120mn= 2h
      inc(temps_lavage);
      if temps_lavage = tps_lavage_max then
        timeOUT_lavage:= true;
      endif;
    endif;
  endif;
//------------------------------------------------------------------------------
  if rincage then
    if temps_rincage < 120*60 then
      inc(temps_rincage);   // ne boucle pas
      if temps_rincage = tps_rincage_max then
        timeOUT_rincage:= true;
      endif;
    endif;
  endif;
//------------------------------------------------------------------------------
  if essorage then
    if temps_essorage < 120*60 then
      inc(temps_essorage);
      if temps_essorage = tps_essr_max then
        timeOUT_ess:= true;
      endif;
 
    endif; // ne boucle pas
  endif;
//------------------------------------------------------------------------------
  if purge then
    if temps_purge < 120*60 then
      inc(temps_purge);
      if temps_purge = tps_purge_max then
        timeOUT_purge:= true;
      endif;
    endif; // ne boucle pas
//    inc(offset_OCR2); //pour accelerer petit à petit
  endif;
//------------------------------------------------------------------------------
 
 
  if defoulage then
    if temps_defoul < 120*60 then
      inc(temps_defoul);
      if temps_defoul = tps_deffou_max then
        TimeOUT_defoul:= true;
      endif;
    endif;
  endif;
//------------------------------------------------------------------------------
if pompe then inc(temps_pompe); endif;
  if temps_pompe > 5*60 then
    STOP('PB vidange > 5mn');
  endif;
 
if EV2 then inc(temps_EV2); endif;
  if temps_EV2 > 5*60 then
    STOP('PB niveau eau');
  endif;
 
//------------------------------------------------------------------------------
  inc(temps_ch_sens);
  if temps_ch_sens >= periode_ch_sens then
    temps_ch_sens:= 0;
    if sens_enable then
      TimeOUT_sens:= true; // changement de sens requis
    endif;
  endif;
//------------------------------------------------------------------------------
  affi_requis:= true; // pas d'affichage directement dans une interruption
  PopAllRegs;
end;
 
 
procedure RTCtickMinute;       // CallBack from RTClock
begin
 
 
if temps_total < 255 then inc(temps_total); endif;
end;
 
 
procedure RTCtickHour;         // CallBack from RTClock
begin
end;
 
 
procedure choix_numero;
var
  num            : byte;
begin
  disableInts;
  LCDclr;
  LCDxy(0, 0);  Write(LCDout, '== CHOIX NUMERO ====' );
  LCDxy(0, 1);  Write(LCDout, 'Touches < > ok' );
  num:= 1;
  allume_LED(num);
  LCDxy(0, 3);  Write(LCDout, ByteToStr(num) + ' ' + Labels_numeros[num]);
  repeat
    detection_bouton_ROUGE;
    octet_IR:= 255;
 
    repeat
      detection_bouton_ROUGE;
      interroge_IR;
    until  octet_IR in [16, 17, 23];
 
    case  octet_IR of
    16 : if num < 15 then inc(num); mdelay(20); endif;
         |
    17 : if num > 1 then dec(num); mdelay(20); endif;
         |
    endcase;
    allume_LED(num);
 
    LCDclrLine(3);
    LCDxy(0, 3);
    Write(LCDout, ByteToStr(num) + ' ' + Labels_numeros[num]);
 
  until (octet_IR = 23);
 
  LCDclr;
  enableInts;
  num_requis:= num;
if num_requis = 2 then a_choisir:= true; endif;
end;
 
 
 
//==============================================================================
{ Main Program }
{$IDATA}
 
begin
  init_variables;
  init_ports;
  allume_LED(0);
  InitINTs;
  ticked:= true;
  Chauffage_OFF;
  LCDclr;                                  { clear display }
  LCDcursor(false, false);                 { display on, cursor off & no blink }
  Write(LCDout, 'RESET');
  if (MCUCSR and %00001000) <> 0 then
    Write(LCDout, ' WT-Dog');
    while true do
      detection_bouton_ROUGE;
    endwhile;
  endif;
 
  mdelay(300);
  LCDclr;
  Write(LCDout, 'Lave Linge v: ' + version );
  tempo_s(2);
//  SetFreqCountMode(TFreqBase100kHz); // mode frequencemetre
  SetFreqCountMode(TTimeBase100ms);   // mode periodemetre
  num_requis:= 0;
 
 
  LCDclr;
  LCDxy(0, 0);  Write(LCDout, '1:LAVAGE');
  LCDxy(0, 1);  Write(LCDout, '2:Choix Numero');
  LCDxy(0, 2);  Write(LCDout, '3:vitesse');
  LCDxy(0, 3);  Write(LCDout, '9:Test IR');
  octet_IR:= 255;
  repeat
    detection_bouton_ROUGE;
    interroge_IR;
  until octet_IR in [1, 2, 3, 9, 32, 33]; // attention cette LISTE doit etre exacte
 
  case octet_IR of
    1  : numero_1;
       |
    2  : choix_numero;
       |
    3  : TEST_asservissement;
       |
    9  : Test_IR;
       |
    12 : STOP('9');
       |
    32 : inc(OCR2); //F_essorage:= F_essorage + 5;
       |
    33 : dec(OCR2); //F_essorage:= F_essorage - 5;
       |
  else
    LCDclr;
    LCDxy(0, 0);
    Write(LCDout, 'err1 - choix inconnu');
    while true do
      detection_bouton_ROUGE;
    endwhile;
  endcase;
 
  enableInts; // ne pas remonter cette ligne.
 
  loop
    case  num_requis of
      1  : numero_1;
         |
      2  : numero_2;
         |
      3  : numero_3;
         |
      4  : numero_4;
         |
      5  : numero_5;
         |
      6  : numero_6;
         |
      7  : numero_7;
         |
      8  : numero_8;
         |
      9  : numero_9;
         |
      10 : numero_10;
         |
      11 : numero_11;
         |
      12 : numero_12;
         |
      13 : numero_13;
         |
      14 : numero_14;
         |
      15 : numero_15;
         |
    else
      LCDxy(0, 0);
      Write(LCDout, 'err2 - n requis HS');
      while true do
        detection_bouton_ROUGE;
      endwhile;
    endcase;
 
  endloop;
 
 
 
//  RTCtimer_Stop(0);
end lavelinge.
 
 
 
 

Silicium628