Coloque o ATmega328 em um sono muito profundo e ouça a série?

13

Eu investiguei as opções de sono do ATmega328 e li alguns artigos sobre ele, e gostaria de entender se há mais opções.

Então, eu gostaria de obter a menor corrente possível, para que nada menos que 100uA fosse bom - desde que eu possa ouvir uart e interromper para acordar.

Estou usando um PCB personalizado (não o UNO), com ATmega328p.

Configurando o chip para dormir profundamente:

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

não iria acordar com comunicação serial, de acordo com isso .

Você precisará colocá-lo no IDLEmodo, para ouvir serial, mas isso consumiria alguns mA -bad.

Eu encontrei este link onde você pode conectar no hardware o serial à interrupção - o que é perigoso para que você possa perder dados e, além disso, preciso desses 2 pinos de interrupção.

Também li este artigo da Gammon , onde é possível desativar algumas coisas, para que você possa dormir inativo com muito menos energia - mas ele não mencionou exatamente como você obtém isso:

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

Então, basicamente, existe alguma opção lá fora, para obter menos de 0,25mA, pelo menos, e também ouvir porta serial, sem qualquer manipulação de hardware? Por exemplo, acordando com entrada longa de dados seriais ?

Curnelious
fonte
1
@NickAlexeev, esta é uma pergunta do ATmega328 e não do Arduino , pois lida diretamente com o chip muito abaixo do nível do Arduino. Pare com as migrações impróprias já!
Chris Stratton
1
Dificilmente. Desejar acordar um Arduino do sono não pode ser descartado, porque ele possui um chip ATmega328. Nesse ritmo, você poderá devolver toda a pergunta sobre o Arduinos ao site de EE.
Nick Gammon

Respostas:

11

Um quadro que fazemos faz isso.

  • O pino RX está conectado ao INT0
  • Pino INT0 configurado para entrada ou pullup de entrada, dependendo de como a linha RX é acionada
  • Em suspensão, a interrupção de baixo nível INT0 está ativada

    //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);
    
  • A rotina de serviço de interrupção INT0 define um sinalizador e desativa a interrupção

    void rx_interrupt()
    {
        detachInterrupt(INT_RX);
        rx_interrupt_flag = 1;
    }
    
  • Ao acordar, verificamos a bandeira (existem outras fontes de interrupção)

No lado das comunicações, usamos um protocolo de mensagens que possui um caractere inicial >e um final \r. por exemplo >setrtc,2015,07,05,20,58,09\r. Isso fornece alguma proteção básica contra a perda de mensagens, pois os caracteres recebidos não são processados ​​até que um >seja recebido. Para ativar o dispositivo, enviamos uma mensagem fictícia antes da transmissão. Um único personagem faria isso, mas enviamos >wakeup\rhehe.

O dispositivo permanece acordado por 30 segundos após o recebimento da última mensagem no caso de novas mensagens. Se uma nova mensagem for recebida, o cronômetro de 30 segundos será redefinido. O software da interface do PC envia uma mensagem fictícia a cada segundo para manter o dispositivo acordado enquanto o usuário o conecta para configuração, etc.

Este método não oferece absolutamente nenhum problema. A placa com alguns periféricos usa cerca de 40uA ao dormir. A corrente real consumida pelo ATMega328P provavelmente está em torno de 4uA.

Atualizar

Na olhada na folha de dados, mostra que o pino RX também é o pino 16 de interrupção de troca de pinos (PCINT16)

Assim, outro método sem fios pode ser

  • Antes de dormir: Defina o bit da máscara de interrupção de mudança de porta no PCMSK2 para PCINT16, limpe o sinalizador da porta de troca de pinos 2 no PCIFR, ative a interrupção da porta de troca de pinos 2 (PCINT16-PCINT23) configurando PCIE2 em PCICR.

  • Configure um ISR para a interrupção da porta 2 de troca de pinos e continue como antes.

A única ressalva com a interrupção de mudança de porta é que a interrupção é compartilhada entre todos os 8 pinos que estão habilitados para essa porta. Portanto, se você tiver mais de uma alteração de pinos ativada para a porta, precisará determinar qual acionou a interrupção no ISR. Isso não é um problema se você não estiver usando nenhuma outra interrupção de troca de pinos nessa porta (neste caso, PCINT16-PCINT23)

Idealmente, é assim que eu teria projetado nossa prancha, mas o que temos funciona.

geometrikal
fonte
Muito obrigado. Não há outra maneira senão truques de hardware ??? Então você só conecta rx a int0 / int1 com 1 linha ??
Curnelious
1
Na verdade, acabei de dar uma olhada na folha de dados e você poderá usar uma interrupção de troca de pinos
geometrikal
Obrigado, qual seria o diferente? Enfim, eu teria que acordar com rx no int1?
Curnelious
Você só precisa de 1 pino de interrupção. Postei mais algumas acima - você pode usar o pino RX como uma interrupção na mudança do pino. Ainda não fiz isso, portanto, pode haver algumas capturas, como talvez você precise desativar o RX / ativar a alteração de pinos antes de dormir e desativar a alteração de pinos / ativar o RX após a ativação
geometrikal
obrigado, não sei por que deveria haver um problema ao conectar apenas rx ao INT1, defina a interrupção em alta, desabilite as interrupções quando int1 acontecer e habilite-as novamente quando for dormir?
Curnelious
8

O código abaixo alcança o que você está perguntando:

#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

Usei uma interrupção de troca de pinos no pino Rx para perceber quando os dados seriais chegam. Neste teste, a placa entra em suspensão se não houver atividade após 5 segundos (o LED "acordado" se apaga). Os dados seriais recebidos fazem com que a interrupção da troca de pinos ative a placa. Ele procura um número e pisca o LED "verde" esse número de vezes.

Corrente medida

Correndo a 5 V, medi cerca de 120 nA de corrente quando estava dormindo (0,120 µA).

Mensagem de despertar

Um problema, no entanto, é que o primeiro byte que chega é perdido devido ao fato de o hardware serial esperar um nível decrescente no Rx (o bit de inicialização) que já chegou no momento em que está totalmente acordado.

Sugiro (como na resposta da geometrikal) que você primeiro envie uma mensagem "acordada" e faça uma pausa por um curto período de tempo. A pausa é garantir que o hardware não interprete o próximo byte como parte da mensagem de ativação. Depois disso, deve funcionar bem.


Como isso usa uma interrupção de troca de pinos, nenhum outro hardware é necessário.


Versão alterada usando SoftwareSerial

A versão abaixo processa com êxito o primeiro byte recebido em série. Faz isso da seguinte maneira:

  • Usando SoftwareSerial, que usa interrupções de alteração de pinos. A interrupção causada pelo bit inicial do primeiro byte serial também ativa o processador.

  • Configurando os fusíveis para que possamos usar:

    • Oscilador RC interno
    • BOD desativado
    • Os fusíveis eram: Baixo: 0xD2, Alto: 0xDF, Estendido: 0xFF

Inspirado pelo FarO em um comentário, isso permite que o processador seja ativado em 6 ciclos de clock (750 ns). A 9600 bauds, cada vez que o bit é 1/9600 (104,2 µs), portanto o atraso extra é insignificante.

#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

O consumo de energia durante o sono foi medido em 260 nA (0,260 µA), de modo que o consumo é muito baixo quando não é necessário.

Observe que, com os fusíveis configurados assim, o processador funciona a 8 MHz. Portanto, você precisa informar o IDE sobre isso (por exemplo, selecione "Lilypad" como o tipo de placa). Dessa forma, os atrasos e o SoftwareSerial funcionarão na velocidade correta.

Nick Gammon
fonte
@NickGammon muito obrigado! eu já fiz e funcionou. Essa maneira é comum em outros produtos que usamos todos os dias, ou eles têm outras maneiras de ouvir a comunicação e dormir? (chanfrada o de MCU ouvir UART em sono profundo?)
Curnelious
Eu estava lendo a folha de dados e afirma que, ao usar o oscilador interno, são necessários apenas 14 ciclos de clock para iniciar o chip, desde que o BOD seja usado. Se a fonte de energia estiver sempre alta (baterias), isso também poderá ser usado sem DBO? violando as especificações, é claro. Isso traria o chip logo após a entrada do UART, mas ainda não tenho certeza de que seria o suficiente para capturar o primeiro byte.
FarO 21/11
Sim, 14 ciclos de relógio não são longos, no entanto, possivelmente, o UART ainda perderia a vantagem (afinal, a vantagem é quando o processador percebe a alteração). Portanto, mesmo que seja iniciado logo após a borda, ele ainda pode estar errado.
Nick Gammon
Um pouco de teste indica que (mesmo com o BOD ativado) ele não funciona. O processador precisa estar acordado para observar a borda inicial (bit de início) e, assim, ligá-lo após recebê-lo (mesmo que logo depois) não funcione.
Nick Gammon
Os 14 ciclos do relógio são após o reset. Você só precisa de 6 ciclos após desligar, se usar o oscilador RC interno. Veja código de exemplo adicional.
Nick Gammon