atmega88 timer2 32.768KHz и проблема с последовательным интерфейсом

Я пытаюсь использовать atmega88 timer2 с кристаллом 32,768 кГц. Я использую слегка измененную версию кода Ника Гэммона. Я использую Minicore. Загрузчика нет, тактовая частота 1 МГц от внутреннего генератора, BOD отключен. Сон прерывается каждые 8 секунд. Я использую счетчик для подсчета 12 минут. Поскольку он проскакивает примерно на 8 секунд каждые 6 часов, я вычитаю 8 секунд каждые 6 часов. Я надеюсь в конечном итоге сделать с этим регистратор погоды.

Код в основном работает в своей урезанной версии, после того как я добавил delay(100) после digitalWrite. Однако мне также нужен Serial. Когда я включаю Serial, он больше не засыпает. Кроме того, последовательный вывод имеет много лишних странных символов. Это происходит на всех скоростях передачи данных, даже 1200 (монитор последовательного порта установлен на ту же скорость передачи данных, что и в программе). Я думаю, что некоторые прерывания вызываются во время digitalWrite и при включении usart, и это мешает сну. Я новичок в программировании uC и действительно борюсь с техническим описанием и кодом. Любая помощь будет оценена по достоинству.

Вот тестовый код:

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

const byte tickPin = 4;// (do not use 3 and 11)

//byte intCounter = 0;
//byte leapCounter = 0;

int intCounter = 0;
int leapCounter = 0;

unsigned long prevTime = 0;
unsigned long currTime = 0;

// interrupt on Timer 2 compare "A" completion - does nothing
EMPTY_INTERRUPT (TIMER2_COMPA_vect);

void setup(){
  pinMode (tickPin, OUTPUT);


  Serial.begin(9600);
  prevTime = millis();
  Serial.print(" P: ");
  Serial.println(prevTime);


  // clock input to timer 2 from XTAL1/XTAL2
  ASSR = bit (AS2);

  // set up timer 2 to count up to 256 * 1024  (8 seconds)
  TCCR2A = bit (WGM21);                             // CTC
  TCCR2B = bit (CS20) | bit (CS21) | bit (CS22);    // Prescaler of 1024
  OCR2A =  255;                                     // (zero-relative)

  // enable timer interrupts
  TIMSK2 |= bit (OCIE2A);

  // disable ADC
  ADCSRA = 0;

  // turn off everything we can
  power_twi_disable();
//  power_timer0_disable();
//  power_timer1_disable();
  power_spi_disable();
  power_usart0_disable();
  power_adc_disable ();

  // full power-down doesn't respond to Timer 2
  set_sleep_mode (SLEEP_MODE_PWR_SAVE);

  // get ready ...
  sleep_enable();

}  // end of setup

void loop(){
  sleep_cpu ();
  ++intCounter;

  power_usart0_enable();
  Serial.print("I: ");
  Serial.print(intCounter);
  Serial.print(" L: ");
  Serial.print(leapCounter);
  currTime = millis();
  Serial.print(" C: ");
  Serial.print(currTime);
  Serial.print(" E: ");
  Serial.print(currTime - prevTime);
  Serial.println();
  prevTime = currTime;
  power_usart0_disable();
  delay(1000);

  if ((leapCounter > 30) && (intCounter == 1)){
    leapCounter = 0;
    intCounter = 0;
  }
  if (intCounter > 89){
    ++leapCounter;
    intCounter = 0;
    digitalWrite (tickPin, ! digitalRead (tickPin));
    delay(100);
  }
//  digitalWrite (tickPin, ! digitalRead (tickPin));
//  delay(100);
}  // end of loop

Микросхема — Atmega88 (не P). Она потребляет те же 6,5 мкА во время сна, с таймером 0 и таймером 1 или без них. Так что мне, вероятно, все равно, оставлю ли я их включенными.

Я планирую использовать DHT11 и ds18B20 и записывать данные на флэш-память w25Q64 каждые 12 минут. Я бы предпочел считывать все это через последовательный порт каждые несколько недель, не останавливая uC.

обновление

Вызов ISR timer2 вместо обработки пробуждения напрямую в основном цикле. Возможно, более стабильно (не зависит от Serial и digitalWrite)... но я не уверен.

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

//const byte tickPin = 4;// (do not use 3 and 11)

volatile bool t2flag = LOW;
volatile byte t2counter = 0;

byte leapCounter = 0;

//preventing digitalWrite from causing interrupt (did not help much)
EMPTY_INTERRUPT(PCINT2_vect);

void setup(){
  pinMode (4, OUTPUT);
  pinMode (1, INPUT_PULLUP); //set TX pin default HIGH

  // clock input to timer 2 from XTAL1/XTAL2
  ASSR = bit (AS2);
  // set up timer 2 to count up to 256 * 1024  (8 seconds)
  TCCR2A = bit (WGM21);                             // CTC
  TCCR2B = bit (CS20) | bit (CS21) | bit (CS22);    // Prescaler of 1024
  OCR2A =  255;                                     // (zero-relative)
  // enable timer interrupts
  TIMSK2 |= bit (OCIE2A);
  // disable ADC
  ADCSRA = 0;

  // turn off everything we can
  power_twi_disable();
//  power_timer0_disable();
//  power_timer1_disable();
  power_spi_disable();
  power_usart0_disable();
  power_adc_disable();
  wdt_disable();// reduces 6.5uA to 2.2uA @ 3.3V

  // full power-down doesn't respond to Timer 2
  set_sleep_mode (SLEEP_MODE_PWR_SAVE);
  sleep_enable();
}

//ISR to handle timer2
ISR (TIMER2_COMPA_vect){
  ++t2counter;
  if (t2counter > 89){t2flag = HIGH;}
}

void loop(){
  if (t2flag == LOW){

    power_usart0_enable();
    Serial.begin(9600);
    Serial.print("I: ");
    Serial.print(t2counter);
    Serial.println();
    Serial.flush();
    delay(10);
    Serial.end();
    power_usart0_disable();

    //blip the tickPin every 8 sec
    PORTD |= (1<<PD4);
    delay(10);
    PORTD &= ~(1<<PD4);

    delay(100);//helps a lot
    sleep_cpu();
    return;
  }

  ++leapCounter;  
  if (leapCounter == 30){
    t2flag = LOW;
    return; //delay another 8 sec every 6 hours
  }
  if (leapCounter > 30){leapCounter = 0;}

  t2flag = LOW;
  t2counter = 0;


  power_usart0_enable();
  Serial.begin(9600);
  Serial.print("L: ");
  Serial.print(leapCounter);
  Serial.println();
  Serial.flush();
  delay(10);
  Serial.end();
  power_usart0_disable();

  //long blip every 12 minutes
  PORTD |= (1<<PD4);
  delay(100);
  PORTD &= ~(1<<PD4);
}

Проблема присутствует не всегда. Но когда она появляется, то происходит одинаково. После нажатия кнопки сброса первое мигание светодиода всегда происходит через 8 секунд. После этого, похоже, происходит одно из трех. 1. Вторая вспышка через 8 секунд, и все работает как положено. 2. Вторая вспышка через 3 секунды, и все последующие вспышки также каждые 3 секунды. 3. Вторая вспышка происходит сразу после первой вспышки (и любой задержки () в коде), и это продолжается. Во всех случаях последовательный вывод также показывает ту же картину.

Иногда кажется, что провода, подключенные к последовательному порту или miso, mosi, sck, могут быть причиной этого, но это не всегда так. Проблема может возникнуть, даже если эти провода не подключены. Кроме того, проблема может возникнуть или не возникнуть с тем же кодом (например, нажатие сброса через 30 секунд может не показать никакой проблемы, но нажатие сброса в течение 5 или 10 секунд приводит к проблемам 2 и 3 выше. Так что есть некое состояние гонки, или буфер, или шум прерывания, или что-то еще.

Мне приходится программировать с Arduino в качестве ISP, так как загрузчик по какой-то причине не может синхронизироваться с чипом ftdi при загрузке скетча. Но передача данных из скетча с помощью serial.print работает нормально. Так что вытаскивать каждый раз штырьки miso mosi sck — это хлопотно. Все на макетной плате. Я перенес всю схему на новую макетную плату (со старой отсоединенной макетной платы), но никаких изменений нет. Так что, возможно, проблема не в макетной плате.

, 👍0


1 ответ


Лучший ответ:

2

Я вижу две проблемы в этом коде.

Во-первых, вы пытаетесь отправить данные через UART, который имеет был выключен. Serial.print() не ждет, пока данные будут выведены, он просто сохраняет его в буфере памяти. Вам следует подождать, пока все вылезает перед выключением UART. Это можно сделать вызывая либо Serial.flush(), либо Serial.end(). Я бы использовал последнее, так как кажется более безопасным остановить UART перед его выключением. Но затем вам придется снова Serial.begin() при пробуждении.

Чтобы избежать искаженных символов в последовательном потоке, вы можете попробовать либо pinMode(1, INPUT_PULLUP); или digitalWrite(1, HIGH);. Таким образом вы гарантируете, что линия TX останется на ВЫСОКОМ уровне (что соответствует ее нормальному состоянию ожидания состояние), когда UART отключается.

Вторая проблема — это использование вами функций хронометража Arduino. millis() и delay(). Эти функции полагаются на прерывание, вызванное по Таймеру 0. Если вы отключите этот таймер, millis() и delay() не сработает. Если вы этого не сделаете, SLEEP_MODE_PWR_SAVE сделает это за вас, пока спящий. Тогда эти функции будут работать во время бодрствования, но millis() будет не учитывайте время, потраченное на сон.


Изменить: Есть два прерывания, связанных с UART, которые включены по Serial.begin():

  • USART_UDRE, «Data Register Empty», используется для передачи. Он срабатывает, когда аппаратный буфер передачи готов к приему новых данных. Соответствующая задача ISR — скопировать один байт из передаваемого буфер памяти (тот, в который Serial.print() записывает) в аппаратное обеспечение буфер передачи.

  • USART_RX, «Получение завершено», используется для приема. Он срабатывает, когда Байт получен. Соответствующий ISR копирует этот байт в прием буфер памяти (тот, из которого Serial.read() считывает данные).

Первый срабатывает при передаче. Как только вы дождетесь окончания передача (например, с помощью Serial.flush()), вы не должны этого видеть больше не прерывать стрельбу.

Второй срабатывает только при получении. Если вы не получаете ничего, это прерывание не должно сработать. Если вывод RX плавающий, прерывание может быть вызвано внешним шумом. Это можно предотвратить включение внутреннего подтягивающего резистора на выводе RX. В качестве альтернативы вы можете отключить приемную часть UART:

UCSR0B &= ~_BV(RXEN0);  // отключаем приемник UART

Обратите внимание, что если вы остановите UART с помощью Serial.end(), то и передатчик и приемник отключены, и вы не получаете прерываний, связанных с UART больше нет.

,