Предлагаю свой вариант простенького бытового термостата на 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 минуты, реле нагрузки (обогревателя) отключается принудительно, чтобы избежать замерзания обогреваемого объекта.
Программа рассчитана на подключение обогревателя к нормально ЗАМКНУТЫМ контактам реле. Таким образом, если отключить питание термостата, он будет работать в режиме байпаса, то есть обогреватель будет включен непрерывно.