Mettre ATmega328 dans un sommeil très profond et écouter la série?

13

J'ai étudié les options de sommeil de l'ATmega328 et lu quelques articles à ce sujet, et j'aimerais comprendre s'il y a plus d'options.

Je voudrais donc obtenir un courant aussi faible que possible, afin que tout ce qui est inférieur à 100uA soit bon - tant que je peux écouter uart et les interruptions pour le réveil.

J'utilise un PCB personnalisé (pas l'UNO), avec ATmega328p.

Mettre la puce en veille profonde:

 set_sleep_mode (SLEEP_MODE_PWR_DOWN);  
 sleep_enable();
 sleep_cpu ();

ne le réveillerait pas avec une communication série, selon cela .

Vous aurez besoin de le mettre en IDLEmode, pour écouter en série, mais cela consommerait quelques mA -bad.

J'ai trouvé ce lien où vous pouvez connecter dans le matériel la série à l'interruption - ce qui est dangereux pour que vous puissiez perdre des données, et en plus, j'ai besoin de ces 2 broches d'interruption.

J'ai également lu cet article de Gammon , où vous pouvez désactiver certaines choses, afin que vous puissiez dormir en veille avec une puissance beaucoup plus faible - mais il n'a pas mentionné comment vous obtenez exactement cela:

 power_adc_disable();
      power_spi_disable();
      power_timer0_disable();
      power_timer1_disable();
      power_timer2_disable();
      power_twi_disable();

Donc, en fin de compte, existe-t-il une option pour obtenir moins de 0,25 mA au moins et écouter également le port série, sans aucune manipulation matérielle? Par exemple, vous vous réveillez avec une longue entrée de données série ?

Curnelious
la source
1
@NickAlexeev c'est une question ATmega328 pas une Arduino car elle traite directement avec la puce bien en dessous du niveau d'Arduino. Arrêtez déjà les migrations incorrectes!
Chris Stratton
1
À peine. Vouloir réveiller un Arduino du sommeil ne peut pas vraiment être rejeté car il contient une puce ATmega328. À ce rythme, vous pourrez renvoyer toute la question sur Arduinos sur le site EE.
Nick Gammon

Réponses:

11

Un conseil que nous fabriquons fait cela.

  • La broche RX est câblée à INT0
  • Broche INT0 réglée sur entrée ou entrée pullup selon la façon dont la ligne RX est entraînée
  • En veille, l'interruption de bas niveau INT0 est activée

    //Clear software flag for rx interrupt
    rx_interrupt_flag = 0;
    //Clear hardware flag for rx interrupt
    EIFR = _BV(INTF0);
    //Re-attach interrupt 0
    attachInterrupt(INT_RX, rx_interrupt, HIGH);
    
  • La routine de service d'interruption INT0 définit un indicateur et désactive l'interruption

    void rx_interrupt()
    {
        detachInterrupt(INT_RX);
        rx_interrupt_flag = 1;
    }
    
  • Au réveil, nous vérifions le drapeau (il existe d'autres sources d'interruption)

Du côté des communications, nous utilisons un protocole de message qui a un caractère de début >et un caractère de fin \r. par exemple >setrtc,2015,07,05,20,58,09\r. Cela donne une protection de base contre la perte de messages, car les caractères entrants ne sont pas traités jusqu'à ce que a >soit reçu. Pour réveiller l'appareil, nous envoyons un message factice avant la transmission. Un seul personnage le ferait, mais nous envoyons >wakeup\rhehe.

L'appareil reste éveillé pendant 30 secondes après la réception du dernier message en cas de nouveaux messages. Si un nouveau message est reçu, la temporisation de 30 secondes est réinitialisée. Le logiciel d'interface PC envoie un message factice toutes les secondes pour garder l'appareil éveillé pendant que l'utilisateur le connecte pour la configuration, etc.

Cette méthode ne pose absolument aucun problème. La carte avec quelques périphériques utilise environ 40 µA pendant le sommeil. Le courant réel consommé par l'ATMega328P est probablement autour de 4uA.

Mise à jour

À regarder la fiche technique montre que la broche RX est également la broche d'interruption de changement de broche 16 (PCINT16)

Ainsi, une autre méthode sans fils peut être

  • Avant de dormir: définissez le bit de masque d'interruption de changement de port dans PCMSK2 pour PCINT16, désactivez l'indicateur de port de changement de broche 2 dans PCIFR, activez l'interruption du port de changement de broche 2 (PCINT16-PCINT23) en définissant PCIE2 dans PCICR.

  • Configurez un ISR pour l'interruption du port de changement de broche 2 et continuez comme auparavant.

La seule mise en garde avec l'interruption de changement de port est que l'interruption est partagée entre toutes les 8 broches activées pour ce port. Donc, si vous avez activé plus d'un changement de broche pour le port, vous devez déterminer qui a déclenché l'interruption dans l'ISR. Ce n'est pas un problème si vous n'utilisez aucune autre interruption de changement de broche sur ce port (PCINT16-PCINT23 dans ce cas)

Idéalement, c'est ainsi que j'aurais conçu notre planche, mais ce que nous avons fonctionne.

geometrikal
la source
Merci beaucoup . N'y a-t-il pas d'autre moyen que des astuces matérielles ??? Donc, vous ne connectez rx à int0 / int1 qu'avec 1 ligne ??
Curnelious
1
En fait, je viens de jeter un coup d'œil à la fiche technique et vous pourriez peut-être utiliser une interruption de changement de broche
geometrikal
Merci, quelle serait la différence? Quoi qu'il en soit, je devrais me réveiller avec rx sur int1?
Curnelious
Vous n'avez besoin que d'une broche d'interruption. J'ai posté un peu plus ci-dessus - vous pouvez utiliser la broche RX comme interruption de changement de broche. Je n'ai pas fait cela cependant, donc il pourrait y avoir quelques captures comme vous devrez peut-être désactiver RX / activer le changement de broche avant le sommeil et désactiver le changement de broche / activer RX après le réveil
geometrikal
merci, je ne sais pas pourquoi devrait être un problème avec simplement la connexion de rx à INT1, définissez l'interruption sur haut, que désactivez les interruptions lorsque int1 se produit et les réactivez lorsque vous vous mettez en veille?
Curnelious
8

Le code ci-dessous réalise ce que vous demandez:

#include <avr/sleep.h>
#include <avr/power.h>

const byte AWAKE_LED = 8;
const byte GREEN_LED = 9;
const unsigned long WAIT_TIME = 5000;

ISR (PCINT2_vect)
{
  // handle pin change interrupt for D0 to D7 here
}  // end of PCINT2_vect

void setup() 
{
  pinMode (GREEN_LED, OUTPUT);
  pinMode (AWAKE_LED, OUTPUT);
  digitalWrite (AWAKE_LED, HIGH);
  Serial.begin (9600);
} // end of setup

unsigned long lastSleep;

void loop() 
{
  if (millis () - lastSleep >= WAIT_TIME)
  {
    lastSleep = millis ();

    noInterrupts ();

    byte old_ADCSRA = ADCSRA;
    // disable ADC
    ADCSRA = 0;  
    // pin change interrupt (example for D0)
    PCMSK2 |= bit (PCINT16); // want pin 0
    PCIFR  |= bit (PCIF2);   // clear any outstanding interrupts
    PCICR  |= bit (PCIE2);   // enable pin change interrupts for D0 to D7

    set_sleep_mode (SLEEP_MODE_PWR_DOWN);  
    power_adc_disable();
    power_spi_disable();
    power_timer0_disable();
    power_timer1_disable();
    power_timer2_disable();
    power_twi_disable();

    UCSR0B &= ~bit (RXEN0);  // disable receiver
    UCSR0B &= ~bit (TXEN0);  // disable transmitter

    sleep_enable();
    digitalWrite (AWAKE_LED, LOW);
    interrupts ();
    sleep_cpu ();      
    digitalWrite (AWAKE_LED, HIGH);
    sleep_disable();
    power_all_enable();

    ADCSRA = old_ADCSRA;
    PCICR  &= ~bit (PCIE2);   // disable pin change interrupts for D0 to D7
    UCSR0B |= bit (RXEN0);  // enable receiver
    UCSR0B |= bit (TXEN0);  // enable transmitter
  }  // end of time to sleep

  if (Serial.available () > 0)
  {
    byte flashes = Serial.read () - '0';
    if (flashes > 0 && flashes < 10)
      {
      // flash LED x times 
      for (byte i = 0; i < flashes; i++)
        {
        digitalWrite (GREEN_LED, HIGH);
        delay (200);  
        digitalWrite (GREEN_LED, LOW);
        delay (200);  
        }
      }        
  }  // end of if

}  // end of loop

J'ai utilisé une interruption de changement de broche sur la broche Rx pour remarquer quand les données série arrivent. Dans ce test, la carte se met en veille s'il n'y a pas d'activité au bout de 5 secondes (la LED "éveillé" s'éteint). Les données série entrantes provoquent l'interruption du changement de broche pour réveiller la carte. Il recherche un nombre et clignote la LED "verte" ce nombre de fois.

Courant mesuré

Fonctionnant à 5 V, j'ai mesuré environ 120 nA de courant pendant le sommeil (0,120 µA).

Message d'éveil

Un problème est cependant que le premier octet arrivant est perdu en raison du fait que le matériel série s'attend à une baisse du niveau sur Rx (le bit de démarrage) qui est déjà arrivé au moment où il est complètement réveillé.

Je suggère (comme dans la réponse de geometrikal) que vous envoyiez d'abord un message "éveillé", puis que vous vous arrêtiez un court instant. La pause consiste à s'assurer que le matériel n'interprète pas l'octet suivant comme faisant partie du message d'éveil. Après cela, cela devrait bien fonctionner.


Comme cela utilise une interruption par changement de broches, aucun autre matériel n'est requis.


Version modifiée utilisant SoftwareSerial

La version ci-dessous traite avec succès le premier octet reçu en série. Il le fait en:

  • Utilisation de SoftwareSerial qui utilise des interruptions de changement de broche. L'interruption provoquée par le bit de démarrage du premier octet série réveille également le processeur.

  • Réglage des fusibles pour utiliser:

    • Oscillateur RC interne
    • DBO désactivé
    • Les fusibles étaient: Low: 0xD2, High: 0xDF, Extended: 0xFF

Inspiré par FarO dans un commentaire, cela permet au processeur de se réveiller en 6 cycles d'horloge (750 ns). À 9600 bauds, chaque temps de bit est de 1/9600 (104,2 µs), donc le retard supplémentaire est insignifiant.

#include <avr/sleep.h>
#include <avr/power.h>
#include <SoftwareSerial.h>

const byte AWAKE_LED = 8;
const byte GREEN_LED = 9;
const unsigned long WAIT_TIME = 5000;
const byte RX_PIN = 4;
const byte TX_PIN = 5;

SoftwareSerial mySerial(RX_PIN, TX_PIN); // RX, TX

void setup() 
{
  pinMode (GREEN_LED, OUTPUT);
  pinMode (AWAKE_LED, OUTPUT);
  digitalWrite (AWAKE_LED, HIGH);
  mySerial.begin(9600);
} // end of setup

unsigned long lastSleep;

void loop() 
{
  if (millis () - lastSleep >= WAIT_TIME)
  {
    lastSleep = millis ();

    noInterrupts ();

    byte old_ADCSRA = ADCSRA;
    // disable ADC
    ADCSRA = 0;  

    set_sleep_mode (SLEEP_MODE_PWR_DOWN);  
    power_adc_disable();
    power_spi_disable();
    power_timer0_disable();
    power_timer1_disable();
    power_timer2_disable();
    power_twi_disable();

    sleep_enable();
    digitalWrite (AWAKE_LED, LOW);
    interrupts ();
    sleep_cpu ();      
    digitalWrite (AWAKE_LED, HIGH);
    sleep_disable();
    power_all_enable();

    ADCSRA = old_ADCSRA;
  }  // end of time to sleep

  if (mySerial.available () > 0)
  {
    byte flashes = mySerial.read () - '0';
    if (flashes > 0 && flashes < 10)
      {
      // flash LED x times 
      for (byte i = 0; i < flashes; i++)
        {
        digitalWrite (GREEN_LED, HIGH);
        delay (200);  
        digitalWrite (GREEN_LED, LOW);
        delay (200);  
        }
      }        
  }  // end of if

}  // end of loop

La consommation d'énergie pendant le sommeil a été mesurée à 260 nA (0,260 µA), ce qui représente une très faible consommation lorsqu'elle n'est pas nécessaire.

Notez qu'avec les fusibles réglés comme ça, le processeur fonctionne à 8 MHz. Vous devez donc en informer l'IDE (par exemple, sélectionnez "Lilypad" comme type de carte). De cette façon, les retards et SoftwareSerial fonctionneront à la bonne vitesse.

Nick Gammon
la source
@NickGammon merci beaucoup! je l'ai déjà fait et cela a fonctionné. Est-ce que cette façon est courante dans d'autres produits que nous utilisons tous les jours, ou ils ont d'autres façons d'écouter la communication et de dormir? (tous les MCU ne
peuvent
Je lisais la fiche technique et il indique que lors de l'utilisation de l'oscillateur interne, seuls 14 cycles d'horloge sont nécessaires pour démarrer la puce, à condition que le BOD soit utilisé. Si la source d'alimentation est toujours active (piles), cela pourrait-il être utilisé également sans DBO? violant les spécifications bien sûr. Cela ferait monter la puce très peu de temps après le bord UART entrant, mais je ne suis toujours pas sûr que ce serait suffisant pour attraper le premier octet.
FarO
Oui, 14 cycles d'horloge ne sont pas longs, mais peut-être que l'UART manquerait toujours le bord (après tout, le bord est lorsque le processeur remarque le changement). Donc, même s'il démarre très peu de temps après le bord, il pourrait toujours le manquer.
Nick Gammon
Un peu de test indique que (même avec BOD activé) cela ne fonctionne pas. Le processeur doit être éveillé pour remarquer le bord d'attaque (bit de démarrage) et donc le mettre sous tension après l'avoir reçu (même si très peu de temps après) ne fonctionne pas.
Nick Gammon
Les 14 cycles d'horloge sont après réinitialisation. Vous n'avez besoin que de 6 cycles après la mise hors tension, si vous utilisez l'oscillateur RC interne. Voir un exemple de code supplémentaire.
Nick Gammon