Предлагаю свой вариант простенького бытового термостата на ATtiny13. Данный проект я пока не собирал в виде законченного устройства, а сделал только прототип на своей отладочной плате. Задумка была — сделать максимально простой термостат, который поддерживал бы положительную температуру, включая при необходимости обогреватель. За основу взят код, найденный где-то на просторах интернета, обеспечивающий взаимодействие МК с термодатчиками Dallas DS18B20 по протоколу 1Wire. Программа рассчитана на работу только с одним датчиком. Идентификатор конкретного датчика знать необязательно, т.к. программа определяет присутствие датчика автоматически и обращается к нему, минуя процесс идентификации.
#define F_CPU 1200000UL // указываем рабочую частоту контроллера для работы задержек
#include <avr/io.h>
#include <util/delay.h>
#define SENSOR_PIN PB3 // вывод датчика
#define RELAY_PIN PB0 // вывод реле
#define LED1_PIN PB1 // вывод светодиода индикации режима реле
#define LED2_PIN PB2 // вывод светодиода индикации температуры
#define SENSOR_PORTReg PORTB
#define SENSOR_DDRReg DDRB
#define SENSOR_PINReg PINB
#define RELAY_PORTReg PORTB
#define RELAY_DDRReg DDRB
#define RELAY_PINReg PINB
#define LED1_PORTReg PORTB
#define LED1_DDRReg DDRB
#define LED1_PINReg PINB
#define LED2_PORTReg PORTB
#define LED2_DDRReg DDRB
#define LED2_PINReg PINB
int8_t actualTemp; // текущая температура
int8_t setTemp = 0; // заданная температура
uint8_t hist = 1; // гистерезис
uint8_t errorsCounter = 0; // счётчик ошибок
// Сброс линии 1Wire
uint8_t oneWire_reset(void)
{
  uint8_t sensor_OK;
  uint8_t retries = 125;
  // Ждем 250мкс установки высокого уровня на линии.
  do
  {
    if (--retries == 0) return 0;
    _delay_us(2);
  }
  while ( !(SENSOR_PINReg & (1 << SENSOR_PIN)));
  
  SENSOR_PORTReg &= ~(1 << SENSOR_PIN); // устанавливаем на линии LOW
  SENSOR_DDRReg |= (1 << SENSOR_PIN); // устанавливаем линию как выход
  _delay_us(490);
  SENSOR_DDRReg &= ~(1 << SENSOR_PIN); // устанавливаем линию как вход
  _delay_us(70);
  sensor_OK = !(SENSOR_PINReg & (1 << SENSOR_PIN)); // проверяем присутствие датчика
  _delay_us(410);
  return sensor_OK; // если получили ответный импульс от датчика, возвращаем 1, если нет - возвращаем 0
}
// Чтение байта из датчика
uint8_t oneWire_readByte(void)
{
  uint8_t i;
  uint8_t data = 0;
  for(i=0; i<8; i++)
  {
    SENSOR_PORTReg &= ~(1 << SENSOR_PIN); // устанавливаем на линии LOW
    SENSOR_DDRReg |= (1 << SENSOR_PIN); // устанавливаем линию как выход
    _delay_us(3);
    SENSOR_DDRReg &= ~(1 << SENSOR_PIN); // устанавливаем линию как вход
    _delay_us(10);
    data >>= 1; // сдвигаем данные на один разряд вправо
    if(SENSOR_PINReg & (1 << SENSOR_PIN)) data |= 0x80; // считываем бит данных
    _delay_us(53);
  }
  return data;
}
// Запись байта в датчик
void oneWire_writeByte(uint8_t data)
{
  uint8_t i;
  for( i=0; i<8; i++)
  {
    SENSOR_PORTReg &= ~(1 << SENSOR_PIN); // устанавливаем на линии LOW
    SENSOR_DDRReg |= (1 << SENSOR_PIN); // устанавливаем линию как выход
    if (data & 0x01)
    {
      // Запись 1
      _delay_us(10);
      SENSOR_DDRReg &= ~(1 << SENSOR_PIN); // устанавливаем линию как вход
      _delay_us(55);
    }
    else
    {
      // Запись 0
      _delay_us(65);
      SENSOR_DDRReg &= ~(1 << SENSOR_PIN); // устанавливаем линию как вход
      _delay_us(5);
    }
    data >>= 1; // сдвигаем данные на один разряд вправо
  }
}
// Обновляет значение контольной суммы crc применением всех бит байта b.
// Возвращает обновлённое значение контрольной суммы
uint8_t oneWire_crc_update(uint8_t crc, uint8_t b) {
  for (uint8_t p = 8; p; p--) {
    crc = ((crc ^ b) & 1) ? (crc >> 1) ^ 0b10001100 : (crc >> 1);
    b >>= 1;
  }
  return crc;
}
// Включение реле
void relayOn(void)
{
  RELAY_PORTReg |= (1 << RELAY_PIN);
  LED1_PORTReg &= ~(1 << LED1_PIN);
}
// Отключение реле
void relayOff(void)
{
  RELAY_PORTReg &= ~(1 << RELAY_PIN);
  LED1_PORTReg |= (1 << LED1_PIN);
}
// Включение аварийного режима:
// Реле принудительно отключается в случае,
// если не удаётся считать температуру с датчика в течение примерно 1 минуты
void emergencyModeOn(void)
{
  LED2_PORTReg |= (1 << LED2_PIN); // включаем контрольный светодиод
  if (errorsCounter < 60) errorsCounter++;
  if (errorsCounter >= 60) relayOff(); // примерно через 1 минуту принудительно отключаем реле
}
// Отключение аварийного режима
void emergencyModeOff(void)
{
  errorsCounter = 0; // обнуляем счётчик ошибок
  LED2_PORTReg &= ~(1 << LED2_PIN); // гасим контрольный светодиод
}
// Вспышка светодиода
void flash(void)
{
   LED2_PORTReg |= (1 << LED2_PIN);
  _delay_ms(50);
  LED2_PORTReg &= ~(1 << LED2_PIN);
  _delay_ms(100);           
}
int main(void)            
{
  // Инициализация портов
  RELAY_DDRReg |= (1 << RELAY_PIN); // выход реле
  LED1_DDRReg |= (1 << LED1_PIN); // выход светодиода индикации режима реле
  LED2_DDRReg |= (1 << LED2_PIN); // выход светодиода индикации температуры
  emergencyModeOff(); // нормальный режим работы
  relayOff(); // реле по умолчанию отключено
  
  // Основной цикл
  while(1)
  {
    uint8_t data[8]; // буфер для считываемых из датчика данных
    uint8_t crc = 0; // контрольная сумма CRC полученных данных
    oneWire_reset(); // сброс линии 1wire
    oneWire_writeByte(0xCC); // пропуск ROM
    oneWire_writeByte(0x44); // запуск температурного преобразования
    _delay_ms(1000);
    
    if (!oneWire_reset()) // сброс шины
    {
      emergencyModeOn(); // если не получен сигнал присутствия, запускаем аварийный режим
      continue; // перезапускаем цикл
    }
    oneWire_writeByte(0xCC); // пропуск ROM
    oneWire_writeByte(0xBE); // запрос содержимого ОЗУ
    // Последовательно считываем 8 байт данных из датчика и обновляем CRC
    for (uint8_t i = 0; i < 8; i++)
    {
      uint8_t b = oneWire_readByte();
      data[i] = b;
      crc = oneWire_crc_update(crc, b);
    }
    // Проверяем контрольную сумму
    if (oneWire_readByte() != crc) // если контрольная сумма не совпала
    {
      emergencyModeOn(); // запускаем аварийный режим
      continue; // перезапускаем цикл
    }
    else emergencyModeOff(); // если контрольная сумма в порядке, отключаем аварийный режим
    actualTemp = ((data[1] << 8) | data[0]) / 16; // преобразуем значение температуры в градусы Цельсия
    // Переключение реле
    if (actualTemp >= (setTemp + hist)) relayOn();
    else if (actualTemp < setTemp) relayOff();
    
    // Индикация текущей температуры вспышками светодиода
    LED2_PORTReg &= ~(1 << LED2_PIN); // выключаем светодиод
    int8_t f = actualTemp; // счётчик вспышек светодиода
    if (actualTemp > 0) // Если температура положительная, светодиод моргает одиночными вспышками
    {
      do
      {
        _delay_ms(300);
        flash();
        f--;
      }
      while (f > 0);
    }
    else if (actualTemp < 0)
    {
      // Если температура отрицательная, светодиод моргает двойными вспышками
      do
      {
        _delay_ms(300);
        for (uint8_t n=0; n<2; n++) flash();
        f++;
      }
      while (f < 0);
    }
    else if (actualTemp == 0)
    {
      // Если температура равна 0, светодиод загорается на 1 сек. и гаснет
      LED2_PORTReg |= (1 << LED2_PIN);
      _delay_ms(1000);
      LED2_PORTReg &= ~(1 << LED2_PIN);
    }
    _delay_ms(2000);
  }
}
Функции устройства
Изначально предполагалось сделать максимально простую прошивку, обеспечивающую только измерение температуры, сравнение её с заданным значением и переключение, при необходимости, реле нагрузки. Но по ходу дела я решил, что всё-таки будет целесообразно добавить пару фишек, тем более память МК это позволяет.
Индикация температуры и состояния реле
Для индикации на устройстве предусмотрены два светодиода: один — сигнализирует о текущем состоянии реле (вкл./выкл.), второй — отображает текущую температуру. Изначально, отображение температуры не предусматривалось, но в процессе написания, я где-то в коде допустил ошибку и никак не мог понять, почему неправильно срабатывает реле. Поэтому было решено сделать индикацию температуры для удобства отладки кода. Так как свободных ног у ATtiny13 мало, тут возникают некоторые затруднения. Кто-то тут использует для индикации сдвиговые регистры, кто-то — трёхцветный светодиод… Я же решил ещё больше изголиться, и обойтись обычным светодиодом! Впрочем, подобное решение мне позже попадалось на Youtube, так что тут я, видимо, тоже не придумал ничего принципиально нового.
Температура отображается вспышками светодиода: положительная — короткими одинарными, отрицательная — короткими двойными, при нуле градусов — светодиод загорается и гаснет примерно на одну секунду, если данные с термодатчика получить не удаётся — светодиод светится постоянно.
Хоть индикация получилась и не самая наглядная, но для отладки такой способ вполне пойдёт, да и вообще, индикация лишней не будет. Надо заметить, что пока идёт отсчёт времени вспышек светодиода, МК простаивает, не выполняя никакой полезной работы (хорошим тоном тут было бы задействовать вместо пауз (delay) — счётчики/таймеры и прерывания, но в данном случае это не особо важно, т.к. в фоне нам не требуется выполнения каких-либо операций, а замеры температуры достаточно делать раз в несколько секунд.
Защита от замерзания (аварийный режим)
Для макетирования этот момент может и не очень важен, но при сборке реального устройства требуется предусмотреть защиту на случай, если термодатчик выйдет из строя, либо оборвётся связь с ним. В данном случае предусмотрено, что если текущая температура не будет получена от термодатчика в течение 1 минуты, реле нагрузки (обогревателя) отключается принудительно, чтобы избежать замерзания обогреваемого объекта.
Программа рассчитана на подключение обогревателя к нормально ЗАМКНУТЫМ контактам реле. Таким образом, если отключить питание термостата, он будет работать в режиме байпаса, то есть обогреватель будет включен непрерывно.