2 июня 2018 г.

Метеостанция на arduino #10. Новый аккумулятор на 3400 мАч

Итак, на аккумуляторе неизвестной емкости (и скорее всего очень древнем, "родом" из ноутбука) метеостанция отработала примерно 1,5 месяца (плохо стала реагировать на нажатие кнопки, т.е. прерывание срабатывало не с первого раза, как выяснилось позже, проблема не в разрядившемся аккумуляторе, см. ниже)  с интенсивностью работы 5-10 просмотров/замеров в сутки. Мне кажется неплохой результат, учитывая "noname" аккумулятора.
Купил на aliexpress вот такой набор (аккумулятор 3.7 В 3400 мАч + зарядное устройство): aliexpress.com

После установки аккумулятора в метеостанцию выяснились интересные подробности: метеостанция продолжила плохо реагировать на нажатие кнопки, что, как я думал, был связано с разрядившимся аккумулятором. Я конечно же измерял напряжение на аккумуляторе, точных значений уже не помню, но было явно что-то выше 4 В, что несколько смутило меня, но на тот момент я не стал разбираться и решил, что нужен просто новый аккумулятор. 
Проблема оказалась в плохом контакте пина D2 ардуины, после починки которого все опять нормально заработало. Так что теперь буду тестировать длительность работы метеостанции на одном заряде нового аккумулятора на 3400 мАч.
Кстати, о зарядке с аккумулятором: понравились, зарядное устройство выглядит довольно качественным, зарядил несколько аккумуляторов, все норм. Аккумулятор неплохо было бы протестировать на емкость, но выглядит вроде как новый:

На зарядном устройстве есть четыре индикатора: при установке аккумулятора сначала загорается индикатор, соответствующий типу аккумулятора (зарядное само определяет тип), затем индикаторы отражают процесс и степень зарядки (25%, 50%, 75% и 100% соответственно). Есть кнопка выбора зарядного тока (0,5 A и 1 A). 

Также зарядное устройство может работать в режиме powerbank (есть USB выход, работает, но не тестировал). 

Вообщем, рекомендую, немного жалею, что взял на один аккумулятор, надо было брать на два (есть еще на четыре, но думаю на два - самое то :))  

   

8 января 2018 г.

Метеостанция на arduino #9. Сборка

Добрался я таки до метеостанции. В крышке корпуса сделал отверстие для дисплея: сначала "выплавил" прибором для выжигания по дереву прямоугольник меньших, чем дисплей, размеров, затем подогнал напильником. 
Получилось так:

Спаял схему:

Поместил все в корпус, пока крепить ничем не стал.
Результат:



Посмотрю, как будет разряжать аккумулятор. Сейчас на нем 4.19 В. Если будет хорошо держаться, то планирую "прикрутить" часы и заменю аккумуляторный бокс на односекционный (а то места маловато :)).

14 октября 2017 г.

Метеостанция на arduino #8. Датчик атмосферного давления BMP180

Доехал наконец-то до меня датчик атмосферного давления BMP180. Напомню, интерфейс у датчик - I2C, на плате встроенный стабилизатор, так что можно питать от 5 В. Питание повесил на D8 ардуины, SDA/SCL на A4/A5. Датчик делит шину I2C с дисплеем. Есть интересный момент, оба устройства на шине должны быть активны (то есть подано питание), иначе ни с одним из них не получится связаться (по крайней мере у меня не получилось). Поэтому дисплей пришлось включать еще до чтения данных с датчика атмосферного давления. "Расковырял" библиотеку BMP180_Breakout_Arduino_Library, вытащил и переработал нужный мне код. Навел красоту в коде, кому интересно, может посмотреть здесь.
Cхема:


Как это выглядит (UNO для питания, ну и прошивки):

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

24 сентября 2017 г.

Метеостанция на arduino #7. Датчик температуры и влажности DHT22

Доставили мне датчик DHT22 (за 13! дней с момента заказа, ехал из Латвии). Измерил потребление: 3 мкА в неактивном режиме и импульсно до 1,4 мА во время опроса. Для коммутации повесил питание датчика на цифровой пин D6. Информационный вывод подключил к D7. "Поковырял" библиотеку DHT-sensor-library, вытянул только то, что мне нужно, немного переписав. На 1 МГц пока не получилось опросить датчик (надо прошаривать код библиотеки - функции dht22_read и dht22_expectPulse см. ниже, а также даташит к DHT22), поэтому пришлось вернуться к 16 МГц (что привело к увеличению энергопотребления, но только в активном режиме). 
Схема:
Код:
//код++
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include <util/delay.h>

// для ЖК дисплея 1602 с I2C
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>

// кнопка
#define BUTTON_PIN        2

// подключение ЖК дисплея 1602 I2C
// LCD 1602 I2C <---> Arduino
// GND <------------> GND
// VCC <------------> D4
// LCD SDA <--------> A4
// LCD SCL <--------> A5
// A <--------------> D5
#define LCD_POWER_PIN       4
#define LCD_BACKLIGHT_PIN   5
#define LCD_BACKLIGHT_VALUE 50

// подключение датчика температуры/влажности DHT22
// DHT22 <---> Arduino
// VCC <------------> D6
// DATA <-----------> D7
#define DHT22_POWER_PIN     6
#define DHT22_DATA_PIN      7

// устанавливаем адрес дисплея
LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

// переменные для храниения "адреса" датчика DHT22 (порт/пин)
uint8_t dht22_port;
uint8_t dht22_bit;

uint32_t dht22_maxcycles;  

// в этот массив будем принимать данные от датчика DHT22
uint8_t data[5];

// переменные для хранения текущих и предыдущих значений температуры и влажности
float currentTemperature = 0;
int8_t currentHumidity = 0;
float previousTemperature = 0;
int8_t previousHumidity = 0;

// символ градуса
uint8_t symbolDegree[8] = {
  B11100,
  B10100,
  B11100,
  B00000,
  B00111,
  B01000,
  B01000,
  B00111
};

// символ процента
uint8_t symbolPercent[8] = {
  B11000,
  B11001,
  B00010,
  B00100,
  B00100,
  B01000,
  B10011,
  B00011
};

// да, да, получилось только через переменные вывести символы :)
int symbolDegreeNumber = 0;
int symbolPercentNumber = 1;

void setup() {
  // все пины на выход и в низкий уровень
  for (byte i = 0; i <= A3; i++) {
    pinMode(i, OUTPUT);    
    digitalWrite(i, LOW);
  }

  // установливаем на пине с кнопкой подтяжку к VCC
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  digitalWrite(BUTTON_PIN, HIGH);
  
  // устанавливаем обработчик прерывания INT0
  attachInterrupt(0, m_wake_up, FALLING);

  // инициализируем датчик DHT22
  dht22_init(DHT22_DATA_PIN);
}

void loop() {
  // засыпаем
  m_sleep();

  if(dht22_read()) {
          
    currentTemperature = dht22_getTemperature();
    currentHumidity = round(dht22_getHumidity());

    dht22_off();
    
    lcd_show(false);
    
    previousTemperature = currentTemperature;
    previousHumidity = currentHumidity;
  }
  else {
    lcd_show(true);
  } 
}

// функция для погружения МК в сон
void m_sleep() {
  // все пины на выход и в низкий уровень
  for (byte i = 0; i <= A7; i++) {
    // на второй ноге висит прерывание, поэтому ее не трогаем
    if (i == 2) continue;
    pinMode(i, OUTPUT);    
    digitalWrite(i, LOW);
  }
  
  // отключаем АЦП
  ADCSRA = 0;  

  // отключаем всю периферию
  power_all_disable ();

  // устанавливаем режим сна - самый глубокий, здоровый сон :)
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);  

  // разрешаем спящий режим
  sleep_enable();

  // разрешаем прерывания
  sei();
  
  // собственно засыпаем
  sleep_cpu();              
}

// обработчик прерывания по нажатию кнопки
void m_wake_up () {
  // запрещаем прерывания
  cli();
  
  // запрещаем режим сна
  sleep_disable();

  // включаем периферию
  power_all_enable();
}

///////////////////////////////////////////////////////////////////////////////
// LCD 1602 I2C

void lcd_on() {
  digitalWrite(LCD_POWER_PIN, HIGH);
  analogWrite(LCD_BACKLIGHT_PIN, LCD_BACKLIGHT_VALUE);
}

void lcd_off() {
  digitalWrite(LCD_POWER_PIN, LOW);
  digitalWrite(LCD_BACKLIGHT_PIN, LOW);
}

void lcd_show(bool error) {
  lcd_on();
  
  // инициализируем дисплей
  lcd.begin(16,2);

  // создаем символы градуса и процента
  lcd.createChar(symbolDegreeNumber, symbolDegree);
  lcd.createChar(symbolPercentNumber, symbolPercent);
    
  if (error) {
    lcd.setCursor(0,0);
    lcd.print("Error DHT");  
  }
  else {
    lcd.setCursor(0,0);
    lcd.print(currentTemperature, 1);
    lcd.setCursor(4,0);
    lcd.write(symbolDegreeNumber);
  
    lcd.setCursor(7,0);
    lcd.print(currentHumidity, 1);
    lcd.setCursor(9,0);
    lcd.write(symbolPercentNumber);
  
    if (previousTemperature != 0) {   
      float deltaT = currentTemperature - previousTemperature;
      lcd.setCursor(0,1);
      lcd.print(deltaT, 1);
  
      int8_t deltaH = currentHumidity - previousHumidity;
      lcd.setCursor(7,1);
      lcd.print(deltaH);
    }
  }

  _delay_ms(2000);
  lcd_off();
}

///////////////////////////////////////////////////////////////////////////////
// DHT22

void dht22_init(uint8_t pin) {
  #ifdef __AVR
    dht22_bit = digitalPinToBitMask(pin);
    dht22_port = digitalPinToPort(pin);
  #endif
  dht22_maxcycles = microsecondsToClockCycles(1000);  // 1 millisecond timeout for
                                                 // reading pulses from DHT sensor.
  // Note that count is now ignored as the DHT reading algorithm adjusts itself
  // basd on the speed of the processor.

  pinMode(pin, INPUT_PULLUP);  
}

void dht22_on() {
  digitalWrite(DHT22_POWER_PIN, HIGH);
}

void dht22_off() {
  digitalWrite(DHT22_POWER_PIN, LOW);
}

boolean dht22_read() {
  dht22_on();

  // немного подождем, чтобы датчик "пришел в себя" :)
  _delay_ms(500);
  
  // Reset 40 bits of received data to zero.
  data[0] = data[1] = data[2] = data[3] = data[4] = 0;

  // Send start signal.  See DHT datasheet for full signal diagram:
  //   http://www.adafruit.com/datasheets/Digital%20humidity%20and%20temperature%20sensor%20AM2302.pdf

  // Go into high impedence state to let pull-up raise data line level and
  // start the reading process.
  digitalWrite(DHT22_DATA_PIN, HIGH);
  _delay_ms(250);

  // First set data line low for 20 milliseconds.
  pinMode(DHT22_DATA_PIN, OUTPUT);
  digitalWrite(DHT22_DATA_PIN, LOW);
  _delay_ms(20);

  uint32_t cycles[80];
  {
    cli();

    // End the start signal by setting data line high for 40 microseconds.
    digitalWrite(DHT22_DATA_PIN, HIGH);
    _delay_us(40);

    // Now start reading the data line to get the value from the DHT sensor.
    pinMode(DHT22_DATA_PIN, INPUT_PULLUP);
    _delay_us(10);  // Delay a bit to let sensor pull data line low.

    // First expect a low signal for ~80 microseconds followed by a high signal
    // for ~80 microseconds again.
    if (dht22_expectPulse(LOW) == 0) {
      sei();
      dht22_off();
      return false;
    }
    if (dht22_expectPulse(HIGH) == 0) {
      sei();
      dht22_off();
      return false;
    }

    // Now read the 40 bits sent by the sensor.  Each bit is sent as a 50
    // microsecond low pulse followed by a variable length high pulse.  If the
    // high pulse is ~28 microseconds then it's a 0 and if it's ~70 microseconds
    // then it's a 1.  We measure the cycle count of the initial 50us low pulse
    // and use that to compare to the cycle count of the high pulse to determine
    // if the bit is a 0 (high state cycle count < low state cycle count), or a
    // 1 (high state cycle count > low state cycle count). Note that for speed all
    // the pulses are read into a array and then examined in a later step.
    for (int i=0; i<80; i+=2) {
      cycles[i]   = dht22_expectPulse(LOW);
      cycles[i+1] = dht22_expectPulse(HIGH);
    }
  } // Timing critical code is now complete.

  // Inspect pulses and determine which ones are 0 (high state cycle count < low
  // state cycle count), or 1 (high state cycle count > low state cycle count).
  for (int i=0; i<40; ++i) {
    uint32_t lowCycles  = cycles[2*i];
    uint32_t highCycles = cycles[2*i+1];
    if ((lowCycles == 0) || (highCycles == 0)) {
      sei();
      dht22_off();
      return false;
    }
    data[i/8] <<= 1;
    // Now compare the low and high cycle times to see if the bit is a 0 or 1.
    if (highCycles > lowCycles) {
      // High cycles are greater than 50us low cycle count, must be a 1.
      data[i/8] |= 1;
    }
    // Else high cycles are less than (or equal to, a weird case) the 50us low
    // cycle count so this must be a zero.  Nothing needs to be changed in the
    // stored data.
  }

  // Check we read 40 bits and that the checksum matches.
  if (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF)) {
    sei();
    dht22_off();
    return true;
  }
  else {
    sei();
    dht22_off();
    return false;
  }
}

float dht22_getTemperature() {
  float t;

  t = data[2] & 0x7F;
  t *= 256;
  t += data[3];
  t *= 0.1;
  if (data[2] & 0x80) {
    t *= -1;
  }
  
  return t;
}

float dht22_getHumidity() {
  float h;
  
  h = data[0];
  h *= 256;
  h += data[1];
  h *= 0.1;

  return h;
}

uint32_t dht22_expectPulse(bool level) {
  uint32_t count = 0;
  
  // On AVR platforms use direct GPIO port access as it's much faster and better
  // for catching pulses that are 10's of microseconds in length:
  #ifdef __AVR
    uint8_t portState = level ? dht22_bit : 0;
    while ((*portInputRegister(dht22_port) & dht22_bit) == portState) {
      if (count++ >= dht22_maxcycles) {
        return 0; // Exceeded timeout, fail.
      }
    }
  // Otherwise fall back to using digitalRead (this seems to be necessary on ESP8266
  // right now, perhaps bugs in direct port access functions?).
  #else
    while (digitalRead(DHT22_DATA_PIN) == level) {
      if (count++ >= dht22_maxcycles) {
        return 0; // Exceeded timeout, fail.
      }
    }
  #endif

  return count;
}
//код--
Или здесь.

Подключил все это дело к одному аккуму 18650 (аккум одолжил). Измерил энергопотребление девайса: 7 мкА - режим сна, 12,6 мА - отображение показаний датчика при 4,2 В напряжения питания.
Фото:

В нижней строке отображается разница в показаниях между текущим и предыдущим замерами. Пока как-то так. Буду ждать датчик давления.

17 сентября 2017 г.

Метеостанция на arduino #6. LCD дисплей 1602

Нашел у себя ЖК дисплей 1602 с переходником на интерфейс I2C (что за зверь такой - см. здесь).

Измерил энергопотребление: 5,4 мА (кратковременно до 6,5 мА) потребляет сам дисплей с переходником и 19 мА - подсветка при напряжении питания 5 В. Выпаял светодиод питания на I2C переходнике - ток снизился до 2,3 мА (3,4 в пике). Подсветку повесил на PWM-пин (D5), благодаря чему ее ток можно значительно снизить, при этом информация на дисплее останется вполне читабельной. Питание самого дисплея с переходником коммутируется пином D4.
Обнаружилась одна неприятная особенность дисплея: при уменьшении напряжения питания "плывет" контрастность, приходится подкручивать подстроечный резистор на I2C переходнике. Пока придумал только такой вариант - внешний подстроечный резистор для управления контрастностью дисплея. Пожалуй оставлю этот дисплей в проекте.
Текущая схема:

Для дисплея использовал библиотеку отсюда. У моей версии I2С переходника адрес 0x3F.
Вернулся к setup() и loop(), так как не удалось "завести" дисплей без них.
Собственно код (просыпаемся по нажатию кнопки, показываем в течение 5 секунд приветствие на дисплее и засыпаем):
//код++
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include <util/delay.h>

// для ЖК дисплея 1602 с I2C

#include <Wire.h> 
#include <LiquidCrystal_I2C.h>

// кнопка

#define BUTTON_PIN        2

// Подключение дисплея

// LCD 1602 I2C <---> Arduino
// GND <------------> GND
// VCC <------------> D4
// SDA <--------> A4
// SCL <--------> A5
// A <--------------> D5
#define LCD_POWER_PIN       4
#define LCD_BACKLIGHT_PIN   5
#define LCD_BACKLIGHT_VALUE 50

// устанавливаем адрес дисплея

LiquidCrystal_I2C lcd(0x3F, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

void m_sleep();

void m_wake_up();
void lcd_on();
void lcd_off();
void lcd_show();

void setup() {

  // все пины на выход и в низкий уровень
  for (byte i = 0; i <= A3; i++) {
    pinMode(i, OUTPUT);    
    digitalWrite(i, LOW);
  }

  // установливаем на пине с кнопкой подтяжку к VCC

  pinMode(BUTTON_PIN, INPUT);
  digitalWrite(BUTTON_PIN, HIGH);
  
  // устанавливаем обработчик прерывания INT0
  attachInterrupt(0, m_wake_up, FALLING);
}

void loop() {

  // засыпаем
  m_sleep();
      
  lcd_on();
  lcd_show();
  _delay_ms(5000);
  lcd_off();  
}

void m_sleep() {
  // все пины на выход и в низкий уровень
  for (byte i = 0; i <= A7; i++) {
    // на второй ноге висит прерывание, поэтому ее не трогаем
    if (i == 2) continue;
    pinMode(i, OUTPUT);    
    digitalWrite(i, LOW);
  }
  
  // отключаем АЦП
  ADCSRA = 0;  

  // отключаем всю периферию

  power_all_disable ();

  // устанавливаем режим сна - самый глубокий, здоровый сон :)

  set_sleep_mode (SLEEP_MODE_PWR_DOWN);  

  // разрешаем спящий режим

  sleep_enable();

  // разрешаем прерывания

  sei();
  
  // собственно засыпаем
  sleep_cpu();              

}


void m_wake_up () {


  // запрещаем прерывания

  cli();
  
  // запрещаем режим сна
  sleep_disable();

  // включаем периферию

  power_all_enable();
}

///////////////////////////////////////////////////////////////////////////////

// LCD 1602 I2C

void lcd_on() {
 
  digitalWrite(LCD_POWER_PIN, HIGH);
  analogWrite(LCD_BACKLIGHT_PIN, LCD_BACKLIGHT_VALUE); 
}

void lcd_off() {

  digitalWrite(LCD_POWER_PIN, LOW);
  digitalWrite(LCD_BACKLIGHT_PIN, LOW);
}

void lcd_show() {
 
  // инициализируем дисплей
  lcd.begin(16,2);  
   
  lcd.setCursor(0,0);
  lcd.print("Hello, world!"); 
}
//код--

Или здесь.


11 сентября 2017 г.

Метеостанция на arduino #5. Выход из режима сна по кнопке

Как я уже упоминал ранее, планируется, что основным режимом работы микроконтроллера в метеостанции будет режим сна. Выход из него - по нажатию кнопки (в дальнейшем - еще и по сигналу будильника для записи показаний датчиков на карту памяти). Для этого я задействовал внешнее прерывание (хорошо про прерывания здесь). Для кнопки использовал прерывание INT0 на ноге D2 платы arduino в режиме FALLING (подробности здесь). Кнопка обычная, замыкается на землю. Пин D2, к которому подключена кнопка, подтянут к VCC (использован внутренний подтягивающий резистор). 
Схема подключения кнопки:

Модифицированный код из предыдущего поста (добавлено пробуждение по нажатию кнопки):
//код++
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#define BUTTON_PIN 2
#define LED_PIN       13

void m_sleep();
void m_wake_up();

int main() 
{
  // все пины на выход и в низкий уровень
  for (byte i = 0; i <= A7; i++) {
    pinMode(i, OUTPUT);    
    digitalWrite(i, LOW);
  }

  // установливаем на пине с кнопкой подтяжку к VCC
  pinMode(BUTTON_PIN, INPUT);
  digitalWrite(BUTTON_PIN, HIGH);
  
  // устанавливаем обработчик прерывания INT0
  attachInterrupt(0, m_wake_up, FALLING);

  while(true) {
    // светим светодиодом 10 секунд
    pinMode(LED_PIN, OUTPUT);
    digitalWrite(13, HIGH);
    
    // Ждем 10 секунд
    _delay_ms(10000);
    digitalWrite(LED_PIN, LOW);  

    // и засыпаем
    m_sleep();
    
    // отсюда выполнения программы продолжится при пробуждении МК
    // то есть снова светим 10 секунд и засыпаем
  }
}

void m_sleep() {

  // отключаем АЦП
  ADCSRA = 0;  

  // отключаем всю периферию
  power_all_disable ();

  // устанавливаем режим сна - самый глубокий, здоровый сон :)
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);  

  // разрешаем спящий режим
  sleep_enable();

  // разрешаем прерывания
  sei();
  
  // собственно засыпаем
  sleep_cpu();              
}

void m_wake_up ()
{
  // запрещаем режим сна
  sleep_disable();
  
  // запрещаем прерывания
  cli();
}
//код--

Или здесь

8 сентября 2017 г.

Метеостанция на arduino. #4. Уменьшение энергопотребления Arduino

Так как метеостанцию планируется питать от аккумулятора немаловажен вопрос уменьшения ее энергопотребления. Об уменьшении энергопотребления метеостанции в целом - в другой раз, здесь же хочется рассказать о снижении энергопотребления именно платы arduino.
Ресурсы по данной теме, которые были найдены (наиболее интересные):
Материал указанных источников можно обобщить в виде следующих действий по уменьшению энергопотребления плат arduino:
  • убрать с платы все "лишнее": светодиоды (питания, RX, TX), отказаться от использования стабилизатора или заменить его на более эффективный, коммутировать питание контроллера USB (включать только на время прошивки), увеличить номинал подтягивающего резистора на ноге reset (совсем экзотика :); 
  • уменьшить тактовую частоту микроконтроллера;
  • уменьшить напряжение питания;
  • отключать неиспользуемую периферию микроконтроллера (АЦП, SPI, USART, таймеров, TWI/I2C, watchDog), отключить Brown-out Detector (BOD) и выключать все неиспользуемые пины;
  • на время бездействия использовать режим сна;
  • никаких setup() и loop(), только main().
На своей Pro Mini я сделал следующее:
  • убрал светодиод питания, стабилизатор пока трогать не стал;
  • тактовую частоту пробовал уменьшать - здорово уменьшается энергопотребление в активном режиме, на режим сна не влияет (оно и понятно, МК же спит :)). Здесь нужен компромисс - меньше частота -> меньше производительность -> больше необходимо времени для выполнения аналогичных действий (относительно частоты по-умолчанию). Надо будет поэкспериментировать. Вроде особо ничего затратного выполнять не планируется, поэтому можно уменьшать, но надо будет посмотреть как при этом заработают интерфейсы SPI и I2C. Нашел два способа изменения частоты (см. ниже);
  • остальное см. в коде.
Собственно код (вариант 1):

//код++
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include <util/delay.h>

void m_sleep();

int main() 
{
  // инициализация (аналог setup())
  // уменьшаем частоту тактирования микроконтроллера в 16 раз
  clock_prescale_set(clock_div_16);
  
  // аналог loop()
  while(true) {
    pinMode(13, OUTPUT);
    // светим светодиодом 10 секунд
    digitalWrite(13, HIGH);

    // Функция  _delay_ms() завязана на тактовой частоте, define-ом частоту переопределить 
    // не получилось (#define F_CPU 1000000), поэтому делаем так
    // Ждем 10 секунд
    _delay_ms(10000/16);
    digitalWrite(13, LOW);  

    // и засыпаем
    m_sleep();
  }
}

void m_sleep() {

  // все пины на выход и в низкий уровень
  for (byte i = 0; i <= A7; i++) {
    pinMode(i, OUTPUT);    
    digitalWrite(i, LOW);
  }
  
  // отключаем АЦП
  ADCSRA = 0;  

  // отключаем всю периферию
  power_all_disable ();

  // устанавливаем режим сна - самый глубокий, здоровый сон :)
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);  

  // разрешаем спящий режим
  sleep_enable();

  // ничто не должно прервать отключение BOD-а, поэтому вырубаем прерывания
  cli();

  // отключаем BOD
  MCUCR = bit (BODS) | bit (BODSE);
  MCUCR = bit (BODS); 
  
  sei();            

  // собственно засыпаем
  sleep_cpu();              
}
//код--

Результат - энергопотребление на уровне 7,2 мА в активном режиме (пустой бесконечный цикл; 13,5 мА без снижения частоты) и 10 мкА в режиме сна при 5 В напряжения питания. 
Позже нашел очень интересный ресурс: MiniCore. После установки этой фичи в Arduino IDE меню "Инструменты" становится таким:
Эта надстройка позволяет простым выбором пунктов меню сконфигурировать FUSE-биты. Интересны настройка BOD-а (выключил) и установка частоты (1 МГц). Результаты в принципе аналогичны, только в спящем режиме энергопотребление 8 мкА. Плюсы: BOD постоянно выключен (хотя он и потребляет совсем немного), нет проблемы с пробуждением по watchdog-гу (подробности здесь) - думаю, в будущем понадобиться эта функция. По ссылке вроде все понятно что и как делать, но если возникнут вопросы, пишите, расскажу поподробнее.
Ну и код упростился (вариант 2):

//код++
#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include <util/delay.h>

void m_sleep();

int main() 
{
  
  // аналог loop()
  while(true) {
    // светим светодиодом 10 секунд
    pinMode(13, OUTPUT);
    digitalWrite(13, HIGH);
    
    // Теперь функция _delay_ms() работает нормально
    // Ждем 10 секунд
    _delay_ms(10000);
    digitalWrite(13, LOW);  

    // и засыпаем
    m_sleep();
  }
}

void m_sleep() {

  // все пины на выход и в низкий уровень
  for (byte i = 0; i <= A7; i++) {
    pinMode(i, OUTPUT);    
    digitalWrite(i, LOW);
  }
  
  // отключаем АЦП
  ADCSRA = 0;  

  // отключаем всю периферию
  power_all_disable ();

  // устанавливаем режим сна - самый глубокий, здоровый сон :)
  set_sleep_mode (SLEEP_MODE_PWR_DOWN);  

  // разрешаем спящий режим
  sleep_enable();

  // собственно засыпаем
  sleep_cpu();              
}
//код--

Исходники здесь. Дальше буду разбираться с выходом из режима сна.