C'est nocif pour la lumière, ou comment garder la charge d'une batterie de voiture

Je poursuis la série d'articles sur la construction de vélos dans le domaine de la gestion des circuits électriques basse tension. Cette fois, je vais parler d'un appareil qui empêche la décharge profonde d'une batterie de voiture par divers consommateurs secondaires.


L'une des conséquences possibles d'une décharge incontrôlée.

L'achat de la première voiture ou moto est une étape importante dans la vie de chaque personne, et en particulier d'un ingénieur. Après tout, qui d'autre que les avantages évidents de son nouveau cheval de fer prête immédiatement attention à ses inconvénients non évidents? Qui commence immédiatement à penser à des améliorations et des ajouts à la norme? Bien sûr, s'il s'agit d'une voiture du segment supérieur, et même d'une marque "à la mode", il peut sembler au départ qu'elle a absolument tout. Mais, comme le montre la pratique, dans ce cas, le temps réfute les premières impressions. Si vous achetez une voiture de classe économique, vos mains commencent à vous démanger littéralement dès le premier jour!

Le désir de "bourrer" votre voiture de divers appareils électroniques auxiliaires est tout à fait naturel. Cependant, peu de temps après la mise en œuvre de tous ces plans, la vie confronte le propriétaire de la voiture à une dure réalité. Il s'avère que même les appareils les plus modernes construits sur la dernière base élémentaire sont encore assez avides d'électricité. Et une batterie de voiture qui semble si énorme n'est pas du tout un réacteur nucléaire et peut facilement «s'asseoir» sous le poids de tous ces consommateurs apparemment inoffensifs en quelques jours.

Afin de ne pas entrer plus loin dans des situations abstraites et hypothétiques, je vais aller directement à mon histoire. Après avoir acheté une voiture, le premier était le désir d'y mettre un registraire. Cela a été fait dans les plus brefs délais, presque complètement dicté par la rapidité de livraison du colis d'AliExpress. Il est clair que l'alimentation régulière de l'allume-cigare était extrêmement gênante, et l'enregistreur a rapidement obtenu une connexion fixe avec la ligne la plus proche du réseau de bord via un convertisseur d'impulsions 12 / 5v. Et depuis c'était, pour le moins, pas hier, ce convertisseur n'était même pas moderne, pour ses propres besoins, comme il s'est avéré plus tard, il consommait jusqu'à 21 mA de courant. Estimons maintenant combien ce convertisseur pourrait alimenter uniquement une batterie neuve et entièrement chargée d'une capacité de 60 Ah. L'arithmétique est extrêmement simple et décevante.


Ainsi, en moins de quatre mois, un convertisseur qui n'est chargé de rien fera atterrir la batterie littéralement «à zéro». Si nous prenons en compte qu'une batterie qui n'est pas entièrement fraîche peut facilement devenir moins veuve et que la charge après la ville de pokatushki est loin de 100%, un jour de pluie commence facilement dans un mois avec un crochet.

Et c'est tout, je le répète, seulement un convertisseur de tension. Oui, aujourd'hui, vous pouvez acheter un convertisseur qui ne prend que la moitié d'un milliampère pour ses propres besoins, mais j'ai donné cet exemple juste pour montrer à quel point l' eau affûtait une pierre lentement, en toute confiance , même insignifiante, mais agissant constamment, le consommateur tire de l'énergie de ce qui semble être si énorme batterie.

Nous allons plus loin, l'enregistreur en mode d'enregistrement FHD @ 30fps consomme près de 300 mA de la source + 5v, qui après conversion en tenant compte de l'efficacité fournit environ 150 mA de courant du réseau embarqué. Supposons que le convertisseur soit remplacé par un convertisseur moderne, et nous calculons le temps de décharge uniquement avec ce courant.


Un peu plus de deux semaines, mais en pratique - dix jours. Maintenant, la perspective d'un éclairage (et éventuellement d'un changement de batterie) se profile après les prochaines vacances ou voyages d'affaires.

Et c'est ce qui m'est arrivé: quand je suis parti pour de courtes vacances forcées, je ne pensais pas que dans une semaine environ, même une serrure centrale ne pourrait pas ouvrir la porte pour moi.

Beaucoup diront que c'est de leur faute, que tout doit être mis hors tension, ou du moins arrêter l'enregistrement, et ils auront raison. Mais la vie, c'est la vie, et la mémoire n'est pas la même, et combien de temps dure le petit congé de maladie n'est pas toujours possible de savoir à l'avance. Par conséquent, l'idée d'un disjoncteur est immédiatement apparue.

Il y a, bien sûr, une option pour alimenter l'enregistreur à partir du contacteur d'allumage afin qu'il ne fonctionne que sur la route, mais cette option n'est pas très importante non plus, car si la voiture heurte le parking, j'aimerais avoir une chance de voir le coupable. De plus, peu de temps après l'installation de l'enregistreur, la voiture était en sous-effectif avec plusieurs autres appareils, y compris un tracker GPS caché, qui devrait fonctionner, sinon jusqu'à la fin, du moins jusqu'à ce que «presque tout» soit déjà là.

En général, pendant plusieurs semaines de réflexion passive, l'idée d'un appareil qui devrait contrôler la tension du réseau de bord et basé sur ces données pour contrôler l'alimentation de deux groupes de consommateurs: secondaire (enregistreur, prise USB) et basique (GPS-tracker et quelques-uns quoi).

Comment cela pourrait-il être fait


Les premiers prototypes virtuels de l'appareil ont été «construits» sur la base des comparateurs analogiques LM393N et ont pu tout ce qui était initialement prévu pour être reçu de l'appareil. Le schéma abstrait était quelque chose comme ça.


Ici, deux comparateurs sont utilisés pour commuter les charges. Un générateur de tension de référence commun, deux diviseurs qui déterminent les seuils de fonctionnement, des comparateurs de cerclage, deux interrupteurs de puissance. La liaison externe de l'appareil fini est prévue comme suit.


La clé primaire reste allumée plus longtemps que la clé secondaire, donc le convertisseur abaisseur lui-même est alimenté à travers elle. Les charges primaires sont connectées directement au convertisseur. Le commutateur secondaire commute les charges secondaires déjà dans le circuit + 5v à la sortie du variateur.

Ce qui est finalement sorti


Cela semble être tout ce qui est nécessaire, mais, comme cela arrive souvent, avec la pensée des détails, des idées d'implémentations alternatives sont apparues. Premièrement, le circuit analogique contenait une montagne décente d'éléments discrets qui fournissent des modes de fonctionnement du comparateur, et deuxièmement, les seuils de déclenchement sont censés être définis à l'aide de résistances de trim, ce qui complique la configuration et crée la probabilité de «s'éloigner» des secousses et du temps. Par conséquent, il a finalement été décidé de s'attarder sur une implémentation numérique, qui s'est avérée être beaucoup plus simple à la fois schématiquement et dans la configuration, tout en ouvrant d'énormes opportunités pour améliorer l'algorithme de contrôle, et, plus important encore, dans ce contexte, il s'est avéré être un ordre de grandeur plus économique en termes de consommation actuelle.

Le contrôleur ATtiny13A a simplement demandé le cœur de l'appareil, qui, en plus de sa facilité d'utilisation et de son prix abordable, est toujours disponible dans un étui DIP à tube chaud et chaud pour oldfag. Au départ, les capacités d'un contrôleur aussi petit semblaient redondantes sur tous les fronts, du nombre d'entrées / sorties à la quantité de programme et de RAM, cependant, comme vous le savez, l'appétit vient avec un repas. En conséquence, pour l'avenir, je dirai que la version finale du boîtier s'est avérée être toutes les conclusions du microcircuit, et qu'il n'y avait pas plus de deux douzaines d'octets de mémoire de programme libre.

Pour mesurer la tension du réseau de bord, le microcontrôleur ne nécessitait qu'une seule entrée, qui est reliée à l'ADC. Deux autres sorties logiques consistaient à gérer les consommateurs. Tout d'abord, après la transition mentale finale vers le «numérique», il y avait une volonté d'adapter deux GPIO gratuits à l'entreprise, et la décision n'a pas tardé à venir. Lorsque, une fois de plus, dans le froid, le démarreur a fait tourner le moteur avec une déchirure mal dissimulée, la présence d'un capteur de température dans le circuit et l'algorithme a semblé très utile. En conséquence, le deuxième ADC a été utilisé pour mesurer la température. Et pour que la thermistance ne consomme du courant que lorsque cela est nécessaire, il a été décidé de l'alimenter à partir de la dernière sortie logique restante.

En conséquence, le schéma de l'appareil a acquis une telle forme finale.


Ici, nous voyons le minimum de détails, et parmi eux, rien n'est sujet à aucune sorte de «torsion». Passons brièvement en revue les points principaux.

Pour l'alimentation, le contrôleur a besoin d'une tension stable de 1,8 à 5,5 V, ce qui signifie qu'il doit y avoir un stabilisateur dans le circuit qui abaissera la tension du réseau de bord au niveau requis. Du point de vue des économies d'énergie, il peut sembler qu'il y a une place pour un convertisseur abaisseur exclusivement pulsé, mais ce n'est qu'à première vue. Le fait est que ATtiny13A, même dans le mode de fonctionnement le plus énergivore (fréquence 8 MHz, exécution de code active), ne consomme pas plus de 6 mA. Dans ce schéma, le contrôleur 99% du temps est en mode de sommeil profond et fonctionne également à une fréquence de 1,2 MHz, ce qui entraîne une consommation moyenne d'environ moins de 15 µA. De plus, environ 80 µA aux courants de base des transistors de commande (si les deux charges sont activées). Eh bien, pendant une petite fraction de seconde, la puissance de la thermistance est activée, ce qui ajoute environ 25 microampères au courant moyen. Et voici la réponse à la question «cela vaut-il la peine de charger un convertisseur d'impulsions pour le bien d'une charge d'une consommation ne dépassant pas 120 µA?» Cela ne semble pas si simple. Et si nous considérons qu'il s'agit de mesures analogiques, cela n'en vaut certainement pas la peine. Par conséquent, le stabilisateur linéaire LP2950 a été utilisé, un analogue fonctionnel du populaire 78L05, mais beaucoup plus économique. Ce convertisseur peut fournir jusqu'à 100 mA de courant à la sortie, tout en ne consommant pas plus de 75 µA pour l'être aimé.

Le diviseur de tension du réseau de bord, protégé par une diode zener et un condensateur, vous permet de mesurer des tensions jusqu'à 15 V.

Je sais que maintenant une vague de critiques va me frapper pour une telle décision, mais nous serons objectifs. Premièrement, je ne développe pas de satellite, et deuxièmement, il n’existe pas un seul facteur de ce genre qui conduirait à une catastrophe. La résistance de l'épaule est élevée, la diode zener est capable de détourner beaucoup plus de courant que celui qui peut traverser le diviseur, même dans le scénario le plus pessimiste. À partir des impulsions à haute fréquence, lorsque la diode Zener n'a pas assez de vitesse, le condensateur C2 protège (avec une résistance R7, il crée un filtre passe-bas avec une fréquence de coupure de seulement 7 Hz). D1 et R6 assurent dans une certaine mesure que le système ne se tombera pas. Et il ne faut pas oublier la linéarité, toute méthode d'isolement galvanique dans un tel endroit rendra le calcul théorique des quantités complètement irréaliste, nous devrons calibrer au moins le prototype, mais nous n'en avons pas besoin.

La résistance de sortie du diviseur est dix fois supérieure aux 10 kOhm recommandés pour la source de signal ADC, mais grâce au condensateur C2, il n'y a pas de problèmes de mesure.

En général, l'impédance d'entrée des circuits ADC des contrôleurs AVR selon la fiche technique est déclarée d'au moins 100 mégohms. Cependant, la même fiche technique recommande néanmoins d'utiliser des sources avec une résistance interne jusqu'à 10 kOhm. Pourquoi Le point est le principe de fonctionnement de cet ADC lui-même. Le convertisseur fonctionne sur le principe de l'approximation séquentielle et son circuit d'entrée est un filtre passe-bas d'une résistance et d'un condensateur. L'obtention d'un échantillon de 10 bits est itérative, et il est nécessaire que le condensateur soit chargé à la pleine tension mesurée pendant tout le temps de mesure. Si l'impédance de sortie de la source est trop grande, le condensateur continuera à être chargé directement pendant le processus de conversion et le résultat sera inexact. Dans notre cas, la capacité C2 est plus de sept mille fois la capacité du filtre ADC, ce qui signifie que lorsque la charge est redistribuée entre ces condensateurs lorsqu'ils sont allumés au moment de la mesure, la tension d'entrée ne diminuera pas de plus de 1/7000, soit sept fois inférieure à la précision ultime d'un ADC 10 bits. Certes, vous devez garder à l'esprit que cette astuce ne fonctionne que pour des mesures uniques avec des pauses importantes entre elles, vous ne devez donc pas "améliorer" le programme de contrôle en y ajoutant une boucle pour plusieurs mesures consécutives avec la moyenne du résultat.

Le diviseur avec une thermistance en raison de la présence d'une source d'alimentation contrôlée est construit en utilisant les valeurs recommandées. Le NTCLE100E3 est utilisé comme capteur, mais il n'y a aucune restriction, vous pouvez utiliser n'importe quelle thermistance d'environ la même valeur, l'essentiel est de faire des corrections correspondant à sa caractéristique dans les constantes du code source afin que la tension du diviseur soit convertie à la valeur de température correcte.

En tant que touches de commande, des MOSFET de canal P de puissance de tout type sont utilisés avec une résistance de canal ouvert acceptable et une tension drain-source maximale d'au moins 30 volts. Le circuit ci-dessus utilise différents transistors. Cela est dû au fait qu'ils doivent commuter des tensions différentes et le type de chacun d'eux a été sélectionné pour des conditions de travail spécifiques. Le transistor supérieur doit être plus haute tension, et le inférieur doit, si possible, avoir une résistance de canal ouvert minimale. Mais, je le répète, cette décision est dictée par le circuit de commutation de l'appareil (voir ci-dessus), avec une autre inclusion, les exigences pour le transistor inférieur peuvent être différentes.

Pour contrôler les interrupteurs de puissance, une paire de transistors bipolaires identiques est utilisée. Au début, il peut sembler que ces transistors sont superflus, mais ici ce n'est pas si simple. Les transistors à effet de champ avec une grille isolée commencent à s'ouvrir non pas à partir d'une tension de la polarité requise sur la grille, mais seulement après avoir atteint un certain niveau de seuil, qui apparaît dans les fiches techniques sous le nom de «tension de seuil grille-source» et est généralement égal à 2..4 V. comptez. Le circuit de sortie du contrôleur peut former deux niveaux logiques: "0" logique avec une tension tendant vers zéro; et «1» logique avec une tension tendant à fournir. Lorsqu'elles sont alimentées par 5 volts, ce seront des tensions d'environ 0 et 5 V, respectivement. Par conséquent, lors de la commutation d'une source de 12 volts, un «0» logique sur la grille créera une différence de tension entre la source et la grille 12 - 0 = 12 volts, le transistor de puissance est ouvert. Tout semble normal, mais le «1» logique avec sa tension de 5 V créera une tension entre 12 - 5 = 7 volts entre la source et la grille, et le transistor de puissance restera toujours ouvert. Ainsi, le signal de commande de cinq volts ne peut pas contrôler la clé, qui commute la tension au-dessus de 7..9 volts. Par conséquent, les transistors bipolaires de commande ne fonctionnent pas vraiment avec des touches de signal comme des amplificateurs qui élèvent la tension de commande de 5 volts à la tension du réseau de bord.

La résistance dans le circuit de base de chacun des transistors de commande limite simplement le courant des sorties du contrôleur à un niveau suffisant pour le contrôler. Leurs valeurs nominales peuvent être réduites de deux à trois fois sans conséquences pour le fonctionnement du circuit.

Il est facile de voir que les transistors de commande n'étaient pas dans le circuit analogique basé sur le LM393N. Le fait est que l'étage de sortie du comparateur sélectionné est construit selon le circuit à collecteur ouvert, c'est-à-dire que sa sortie est simplement la sortie du collecteur à transistor terminal. Ce principe de construction nécessite que des pièces supplémentaires soient accrochées à la puce pour créer la charge de l'étage de sortie, mais, d'autre part, rend la puce très flexible. Un collecteur ouvert permet au comparateur de contrôler toute source de courant acceptable, et pas seulement compatible avec celle qui alimente le comparateur lui-même.

Je dois dire que limiter la tension de seuil d'un MOSFET de puissance fonctionne non seulement vers les hautes tensions, comme mentionné ci-dessus, mais aussi vers les basses. Après tout, si la tension d'ouverture minimale du transistor est, disons, de 4 volts, alors lors de la commutation de la source 3,3 V, même la connexion de la grille à la terre ne créera pas la différence de tension souhaitée entre la source et la grille et le transistor restera fermé. Donc, 5 volts est, peut-être, la tension minimale qui peut être commutée de manière fiable par les transistors sélectionnés.

Personnalisation


La configuration d'un appareil est une conversation distincte. D'une part, il n'y a pas un seul élément d'accord dans le circuit, mais d'autre part, il s'agit de mesurer des tensions avec une précision d'au moins 0,1 V. Comment lier tout cela? Il y a deux façons. La première consiste à utiliser des résistances R6, R7 et R8 avec une tolérance d'au moins 1% (ou mieux 0,1%). La seconde implique l'utilisation de résistances conventionnelles avec mesure de leurs résistances réelles et correction des coefficients dans le code source du programme.

La première méthode est bonne pour la production de masse, mais il est beaucoup plus intéressant pour nous de ne pas nous soucier de la recherche des valeurs de haute précision nécessaires, alors allons-y dans la deuxième voie. La résistance peut être mesurée avec un multimètre ordinaire, sa précision ici est tout à fait suffisante. Un autre objet de mesure sera la tension du stabilisateur alimentant le circuit. L'ADC du contrôleur peut fonctionner dans différents modes, mais pour plusieurs raisons, il est plus pratique pour nous d'en utiliser un dans lequel le résultat de la conversion numérique est compté par rapport à la tension d'alimentation. C'est pourquoi il est important de le connaître le plus précisément possible.

Le calcul est extrêmement simple et consiste à calculer le coefficient de division du diviseur résistif et la proportion de la traduction du résultat en LSB lors de la conversion analogique-numérique.


Ux est la tension d'entrée du diviseur;
Ru est la résistance du bras supérieur du diviseur (auquel Ux est fourni);
Rd est la résistance du bras inférieur du diviseur (qui est relié à la terre);
Uref - tension de référence de l'ADC (c'est-à-dire la tension d'alimentation du contrôleur);
1024 - le nombre de valeurs discrètes à la sortie d'un ADC 10 bits;
LSB est la valeur numérique obtenue par le programme de l'ADC.

Commençons par le diviseur de tension R6-R7. . 5.0 . 13.5 :


, , , .

, , , Ru, Ux Uref. :


R8 , R9 NTCLE100E3 0⁰C:


, R8 R9 , , , . . , R9 , 0.5 m, . , , 0.01 .

, , , . , . - , .

, , , .

Firmware


AtmelStudio ( gcc-avr 5.4.0) , hex . , .

Code source
//#define F_CPU 1200000UL //    

#include <avr/io.h>
#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/interrupt.h> 
#include <util/delay.h>

//#define DBG

#define TEMPERATURE_OVERHEAT 753 // LSB-  +50⁰C
#define TEMPERATURE_GIST     8   //    ( LSB)     
#define VOLTAGE_GIST         3   //    ( LSB)     

#define INTERVAL             WDTO_1S //     (1 )
#ifndef DBG
#define CELL_CHANGE_TIMEOUT  90  //      (  INTERVAL,   254)
#define OVERHEAT_TIMEOUT     300 //      "" (  INTERVAL)
#else
#define CELL_CHANGE_TIMEOUT  2
#define OVERHEAT_TIMEOUT     3
#endif

typedef unsigned char bool; //    
#define true  0 == 0        //     
#define false 0 != 0        //      

typedef enum {st_none = 0b00, st_primary = 0b01, st_secondary = 0b10, st_both = 0b11} t_states; //    
                                                                                                //       ,      
typedef enum {adc_temperature, adc_voltage} t_measure;                                          //   
typedef enum {move_null, move_up, move_down} t_movement;                                        //      

//    
struct t_coordidates {
  signed char row, col;
};

//       
struct t_correction {
  t_movement voltage, temperature;
};

#define CELLS_ROWS 3 //      ( )
#define CELLS_COLS 5 //      ( )

//  
const t_states CELLS[CELLS_ROWS][CELLS_COLS] = {
  {st_both, st_both,    st_both,    st_primary, st_none},
  {st_both, st_both,    st_primary, st_none,    st_none},
  {st_both, st_primary, st_none,    st_none,    st_none}
};

// LSB- ,      
const unsigned int ROWS_EDGES[CELLS_ROWS - 1] = {
  241, // 0⁰C
  157  // -10⁰C
};

// LSB- ,      
const unsigned int COLS_EDGES[CELLS_COLS - 1] = {
  864, // 13.5V
  800, // 12.5V
  787, // 12.3V
  768  // 12.0V
};

unsigned int overheat_rest_time = 0; //       ""
unsigned char cell_change_time  = 0; //      
unsigned char no_cur_cell_time  = 0; //  ,            

#define NULL_CELL (struct t_coordidates){.col = -1, .row = -1} // ,   
#define NULL_CORRECTION (struct t_correction){.voltage = move_null, .temperature = move_null} // ,   

struct t_correction moved_from = NULL_CORRECTION; //       
struct t_coordidates cur_cell  = NULL_CELL,       //      
                     next_cell = NULL_CELL;       //  -   

//  
static void init_pins() {
  DDRB |= (1 << PB0) | (1 << PB1) | (1 << PB3);     //   2 (PB3), 5 (PB0)  6 (PB1)  
  PORTB &= ~(1 << PB0) & ~(1 << PB1) & ~(1 << PB3); //      2 (PB3), 5 (PB0)  6 (PB1)
}

// /    
static void toggle_thermal_sensor(bool state) {
  if(state) {
    PORTB |= (1 << PB1);  //  state ,      6 (PB1)

    _delay_ms(5); //    
  } else {
    PORTB &= ~(1 << PB1); //  state  ,      6 (PB1)
  }
}

//   
static unsigned int measure_adc(t_measure measure) {
  if(measure == adc_temperature) {
    toggle_thermal_sensor(true); //    ,    

    ADMUX = 0b10; //      -   3 (PB4)
  } else {
    ADMUX = 0b01; //      -   7 (PB2)
  }

  ADCSRA = (1 << ADPS2) | //       = 16 (75 )
           (1 << ADIE) |  //    
           (1 << ADEN);   //  

  set_sleep_mode(SLEEP_MODE_ADC); //   "" 
  do {
    sleep_cpu(); //      ,      ,   
  } while(ADCSRA & (1 << ADSC)); //        ,  

  ADCSRA = 0; //  

  toggle_thermal_sensor(false); //     

  return ADC; //  10-  
}

//    watchdog
static void init_interrupts(void) {
  sleep_enable(); //   

  WDTCR = (1 << WDCE) | (1 << WDE); //  watchdog
  WDTCR = (1 << WDTIE) | INTERVAL; // watchdog      ,  1 

  sei(); //  
}

//          
static void toggle_loads(t_states states) {
  unsigned char port = PORTB & ~((1 << PB3) | (1 << PB0)),     //           ,   
                bits = (((states & st_primary) >> 0) << PB3) | //        
                       (((states & st_secondary) >> 1) << PB0);

  PORTB = port | bits; //    
}

//     t_coordidates
static bool cells_equal(struct t_coordidates cell1, struct t_coordidates cell2) {
  return cell1.row == cell2.row && cell1.col == cell2.col;
}

//          LSB- 
static signed char get_cell_row(unsigned int temperature) {
  signed char row = 0;

  while(row < CELLS_ROWS - 1) {          //          
    if(temperature >= ROWS_EDGES[row]) { //  temperature     ,    
      return row;
    } else {
      ++row;
    }
  }

  return CELLS_ROWS - 1; //  temperature         ,       
}

//          LSB- 
static signed char get_cell_col(unsigned int voltage) {
  signed char col = 0;

  while(col < CELLS_COLS - 1) {      //          
    if(voltage >= COLS_EDGES[col]) { //  voltage     ,    
      return col;
    } else {
      ++col;
    }
  }

  return CELLS_COLS - 1; //  voltage         ,       
}

//    ,       
static void get_row_edges(signed char row, unsigned int *upper, unsigned int *lower) {
  *upper = row > 0 ? ROWS_EDGES[row - 1] : 0xffff - TEMPERATURE_GIST; //       ,    
  *lower = row < CELLS_ROWS - 1 ? ROWS_EDGES[row] : TEMPERATURE_GIST; //       ,    
}

//    ,       
static void get_col_edges(signed char col, unsigned int *upper, unsigned int *lower) {
  *upper = col > 0 ? COLS_EDGES[col - 1] : 0xffff - VOLTAGE_GIST; //      (  )  ,    
  *lower = col < CELLS_COLS - 1 ? COLS_EDGES[col] : VOLTAGE_GIST; //      (  )  ,    
}

//    -              
static void gisteresis_correction(struct t_coordidates* new_cell, unsigned int temperature, unsigned int voltage) {
  unsigned int upper_edge, lower_edge;

  get_row_edges(cur_cell.row, &upper_edge, &lower_edge); //    
  if(new_cell->row > cur_cell.row && moved_from.temperature == move_up && temperature >= lower_edge - TEMPERATURE_GIST) {
    --new_cell->row; //   -   ,    ,        ,    
  }

  if(new_cell->row < cur_cell.row && moved_from.temperature == move_down && temperature <= upper_edge + TEMPERATURE_GIST) {
    ++new_cell->row; //   -   ,    ,        ,    
  }

  get_col_edges(cur_cell.col, &upper_edge, &lower_edge); //    
  if(new_cell->col > cur_cell.col && moved_from.voltage == move_up && voltage >= lower_edge - VOLTAGE_GIST) {
    --new_cell->col; //   -   ,     (  ),        ,    
  }

  if(new_cell->col < cur_cell.col && moved_from.voltage == move_down && voltage <= upper_edge + VOLTAGE_GIST) {
    ++new_cell->col; //   -   ,     (  ),        ,    
  }
}

//       stdlib::abs()
 static unsigned char absolute(signed char value) {
  return value >= 0 ? value : -value;
}

//      -
static void calc_movement(struct t_coordidates new_cell) {
  moved_from = NULL_CORRECTION;                                                   // -   
  if(!cells_equal(new_cell, NULL_CELL) && !cells_equal(cur_cell, NULL_CELL)) {    //         ,  -
    if(absolute(new_cell.row - cur_cell.row) == 1) {                              //      
      moved_from.temperature = new_cell.row < cur_cell.row ? move_up : move_down; //   
    }

    if(absolute(new_cell.col - cur_cell.col) == 1) {                              //      
      moved_from.voltage = new_cell.col < cur_cell.col ? move_up : move_down;     //   
    }
  }
}

//   -
static void set_next_cell(struct t_coordidates cell) {
  next_cell = cell;
  cell_change_time = 0; //    
}

//    
static void set_cur_cell(struct t_coordidates cell) {
  cur_cell = cell;
  no_cur_cell_time = 0; //        
  set_next_cell(NULL_CELL); //  -
}

// ,      
static void change_cell(struct t_coordidates new_cell) {
  if(cells_equal(new_cell, NULL_CELL)) { //         
    toggle_loads(st_none);
  } else {
    toggle_loads(CELLS[new_cell.row][new_cell.col]); //         
  }

  calc_movement(new_cell); //     
  set_cur_cell(new_cell);  //   
}

//  
static void main_proc(void) {
  unsigned int temperature, voltage; // 10- LSB-    
  struct t_coordidates cell;         //      -

  if(overheat_rest_time) { //      ""  ,          
    --overheat_rest_time;
  } else {
    temperature = measure_adc(adc_temperature); //  
    if(temperature >= TEMPERATURE_OVERHEAT) {   //      +50C,  :
      change_cell(NULL_CELL);                   //      (   )
      overheat_rest_time = OVERHEAT_TIMEOUT;    //        
    } else {
      voltage = measure_adc(adc_voltage);   //  

      cell.col = get_cell_col(voltage);     //    -  
      cell.row = get_cell_row(temperature); //    -  

      if(cells_equal(cur_cell, NULL_CELL)) { //        ,         
        change_cell(cell);
      } else {
        gisteresis_correction(&cell, temperature, voltage); //              

        if(cells_equal(cell, cur_cell)) { //   -   ,      
          set_next_cell(NULL_CELL);
          no_cur_cell_time = 0; //    ,  
        } else {
          if(no_cur_cell_time++ > CELL_CHANGE_TIMEOUT) { //    CELL_CHANGE_TIMEOUT+1        cur_cell,      
            change_cell(cell); //    ,     
          } else {
            if(cells_equal(next_cell, NULL_CELL) || !cells_equal(next_cell, cell)) { //  -       ,   
              set_next_cell(cell);
            } else {
              if(++cell_change_time >= CELL_CHANGE_TIMEOUT) { //   ,       , ,    
                change_cell(cell);
              }
            }
          }
        }
      }
    }
  }
}

//    watchdog
ISR(WDT_vect) {
  WDTCR |= (1 << WDTIE); //    watchdog   ""    
}

//    ,        ADSC  measure_adc()
EMPTY_INTERRUPT(ADC_vect);

//  
int main(void) {
  init_pins();       //  
  init_interrupts(); //    watchdog
	
  while(true) {                          //  ,       
    set_sleep_mode(SLEEP_MODE_PWR_DOWN); //        
    sleep_cpu();                         //        watchdog 

    main_proc();                         //          
  }
}


: L:0x6A, H:0xFF.

. , – , – . , . :


, .

, . , . , , .

, - , . , , . , - , , , , . .. - , , . . . , , .

, . , , , 12.5 , , 12.4 . . , .



, , , . , . «» 8-9 .

, . «» , . , , «» , - (, , , , , - ).

, +50⁰C , . , , , . .

«», , (watchdog). .

, – . . Watchdog , , , . , , , watchdog. , , .

. 1006 , - .

, , . , O2, , Os , 1024 . -, .

.


Eagle .

Source: https://habr.com/ru/post/fr446956/


All Articles