Делаем слегка умное реле для управления нагрузкой 220 Вольт на основе ATTiny85 (ATTiny13) и симистора (BTA16) | mysku

Делаем слегка умное реле для управления нагрузкой 220 Вольт на основе ATTiny85 (ATTiny13) и симистора (BTA16)

Перейти в магазин

Последнее время активно продвигаются устройства умного дома, в том числе, и на данном ресурсе. Одной из основных проблем коммутации силовых нагрузок в таких устройствах является сохранение привычного (кнопочного или иного) управления. Устройство из обзора призвано решить данную проблему, предоставив возможность управлять как из «умной составляющей» дома, так и привычными средствами, например, выключателем. Помимо этого, на данном устройстве можно легко и безопасно реализовать управление светом несколькими выключателями. Либо сочетать разные способы управления. Поделки, пайка, код и тесты — все под катом… (осторожно: много фото и видео).

Про сам контроллер Attiny85 написано немало, в том числе и на этом ресурсе. Мой обзор про вариант использованием с подробной инструкцией по подключению контроллера можно найти здесь. Сам контроллер имеет 8 выводов: 2 из которых предназначены для подключения питания, 1 — под сигнал сброса (перезагрузки), таким образом под «полезные» задачи остается всего 5 выводов, но и этого, во многих случаях, достаточно. Контроллер:

Микроконтроллер ATtiny85 имеет следующие характеристики:
— для программного кода предусмотрено 8 КБ памяти;
— для исполняемого кода (ОЗУ, SRAM, RAM) зарезервировано 512Б;
— память данных (EEPROM) 512 Б
— наличие 6 цифровых пинов, в реальности лучше использовать 5 (оставив 1 под RESET);
— два выхода PWM и 4 АDC (разрешение 10-bit);
— частота от 1 до 20 МГц.
ATtiny85 предлагается в корпусах: MLF (WQFN) 20M1 20dip8, PDIP 8P3 8, SOIC (208mil) 8S2 8.
В зависимости от модификации источник питания может быть от 1,8 до 5,5В.

В данном обзоре речь пойдет о версии в SOIC корпусе. Посылка пришла за 3 недели (трек отслеживался).

В своих поделках я довольно часто использую реле на твердотельных элементах, в частности, при создании аппарата точечной сварки использовал именно симистор, подробнее тут. Но вот незадача, если использовать твердотельное (да и любое другое) реле в сочетании со сложной системой управления возникают следующие неприятности:
— локально таким реле можно управлять только в 2-х вариантах: либо через центральный узел, либо центральный узел будет находиться в неведении о текущем состоянии реле;
— в случае перезагрузки центрального узла — состояние реле меняется на исходное (либо нужно сохранять текущее состояние):
— но, самое главное, если вам захотелось внести изменения в центральный узел, либо его заменить (в совсем плохом случае он может просто сломаться) — то, фактически, реле становится неуправляемым, а это означает например невозможность включить свет или что-то еще.
Вторым моментом двинувшим меня на данную разработку является довольно типичная ситуация использования проходных (перекидных) выключателей. В силу дороговизны силового кабеля, а его требуется немало для организации возможностей включения/выключения света в нескольких точек, кроме того большое количество кабеля с напряжением 220 Вольт никак не способствует безопасности, мне кажется разумным коммутировать силовую нагрузку тонким проводом (например телефонным) с напряжением порядка 5 Вольт. Устройство из обзора позволит решить и эту задачу.
Если же эти две проблемы объединить, то целесообразность применения такого устройства сложно переоценить. Я отдаю себе отчет, что найдутся знатоки, которые использовав всего 15 транзисторов и несколько навесных элементов гордо заявят, что обошлись без контроллера, решив задачу (почти похожую на данную) в железе. Сразу аргументирую: цена контроллера настолько мала, что и обсуждать бессмысленно, вдобавок решение получается очень гибким, внесение изменений не требует паяльника. При этом тиньки зарекомендовали себя как очень надежные и стабильные контроллеры, кроме всего прочего, ничто не мешает иметь некий резерв таких «слегка умных» реле — чтобы совсем исключить все нештатные ситуации.

Нехитрая схема устройства:

R1, R2, R3 — устанавливаются опционально, подтягивая соответствующие выводы контроллера к питанию. R4 подтягивает вывод RESET к питанию, препятствуя самопроизвольной перезагрузке контроллера. Нагрузка подключается к выводу PB3 контроллера, данный вывод подтянут к земле резистором R6 во избежание нештатных ситуаций по включению симистора. R5 ограничивает ток оптосимистора MOC3063. MOC3063, R7(0.5 Вт), R8(0.5 Вт) и Т1 — образуют типовую схему включения симистора. Снабберная (демпфирующая) RC-цепь состоит из C1 и R9 (1Вт), её наличие особенно критично при индуктивной нагрузке. В качестве симистора я использовал BTA16, можно поставить как менее так и более мощный (в корпусе ТО220), в зависимости от коммутируемой нагрузки. Также, по необходимости, можно установить радиатор на симистор, я специально расположил его с краю.

Печатная плата получилась такая:

Плата изготовлена в Китае, размеры компактные (вполне войдет в подрозетник или распаячную коробку), конкретно эту плату изготавливал в dirtypcbs.com, воспользовавшись панелизацией (в ближайшее время ожидаю платы другого производителя за 2$ — по акции :) ):


После сборки устройство выглядит так:

Это экспериментальный образец, поэтому немного пострадал, не обращайте внимания на внешний вид. :) Обратная сторона:

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


void setup() {
pinMode(3, OUTPUT);
}

void loop() {
digitalWrite(3, HIGH);
delay(1000);
digitalWrite(3, LOW);
delay(1000);
}

В качестве программатора использовал arduino nano:

После подключил питание к макетной плате и подал на новое устройство:

Видео работы (использовал, доработанный по пожеланиям, стенд из этого обзора):

Простейшие функции проверены, впрочем, если вам нужна мигалка на 220 Вольт, то можно использовать устройство в таком виде :).

Далее, сымитируем ситуацию, когда нам требуется много (ну до 4-х) выключателей для одной нагрузки, и мы не хотим тянуть толстые провода с 220 Вольт. Для этого, на все свободные входы нашего устройства нужно подключить выключатели с поведением аналогичным типовым и написать соответствующую программу:


// выход на нагрузку
const uint8_t load_pin = 3;
// состояние нагрузки
bool load_on = false;

// структура, описывающая отдельный выключатель
typedef struct {
uint8_t pin; // пин
bool state; // состояние
unsigned long ButtonTimerDebounce; // время начало переключения (для защиты от дребезга)
} btn_t;

// массив выключателей
btn_t btn[] = {
{1, 0, 0},
{0, 0, 0},
{2, 0, 0},
{4, 0, 0},
};
// количество элементов в массиве - посчитаем позже
uint8_t num_btn = 0;
// количество миллисекунд со старта контроллера (для защиты от дребезга)
unsigned long CurrentTime = 0;
// защитный временной интервал в мс
const unsigned int DebounceTime = 100;

// функция определения количества элементов массива с элементами произвольного типа
template<typename T, size_t n> inline size_t arraySize(const T (&arr)[n]) {
return n;
}

void setup() {
// настраиваем пин нагрузки
pinMode(load_pin, OUTPUT);
digitalWrite(load_pin, LOW);
// считаем количество выключателей
num_btn = arraySize(btn);
// настраиваем все выключатели
for (uint8_t i = 0; i < num_btn; i++) {
pinMode(btn[i].pin, INPUT);
pinMode(btn[i].pin, INPUT_PULLUP);
btn[i].state = digitalRead(btn[i].pin); // текущее состояние выключателя считаем исходным
}
}

void loop() {
CurrentTime = millis();
// обходим все выключатели
for (uint8_t i = 0; i < num_btn; i++) {
// Если состояние выключателя отличается от текущего
if (btn[i].state != digitalRead(btn[i].pin)) {
// Если мы в самом начале устранения дребезга, то ставим время отсчета
if (btn[i].ButtonTimerDebounce == 0) btn[i].ButtonTimerDebounce = CurrentTime;
// Если произошло переключение с учетом устранения дребезга
if ((btn[i].ButtonTimerDebounce + (unsigned long)DebounceTime) < CurrentTime) {
// Меняем состояние нагрузки
btn[i].state = !btn[i].state;
load_on = !load_on;
if (load_on) {
digitalWrite(load_pin, HIGH);
} else {
digitalWrite(load_pin, LOW);
}
// сбрасываем время отсчета
btn[i].ButtonTimerDebounce = 0;
}
} else {
// сбрасываем время отсчета при ложной тревоге - переключатель не изменил состояние за заданное время
btn[i].ButtonTimerDebounce = 0;
}
}
}

В качестве имитаторов выключателей решил использовать такие кнопки со сменой состояния:

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

С обратной стороны:

Результат:

Надеваем изолятор:

Другой конец жал штырьками для макетной платы:

Провод толстоват для таких наконечников, поэтому обжимные части и узкая и широкая обжимают сам провод, хотя широкая должна впиваться в изоляцию, но держится все очень крепко — руками у меня не вышло сорвать терминал с провода. Лишнее закрываем термоусадкой:

Если кому интересно, то клещи и процесс обжима я обозревал здесь.
Получилось вот такие проводки:

Далее защелкнул их на кнопки и сдвинул изоляцию:

Тест кнопок проводил сразу всех (3 штуки сделал) в режиме прозвонки поочередно нажимал:

Итоговый стенд:

Видео тестов:

Логика работы такая же как у проходных (перекидных) выключателей. Памяти данный код занимает мало и на месте 85-ой тиньки вполне справится ATTiny13, благо корпуса у них похожи и я предусмотрел на плате их заменяемость. Таким образом, вместо дорогих и толстых проводов можно с успехом использовать тонкий телефонный, и напряжение безопасно, более того перебив и замкнув провод можно вывести из строя только один выключатель, остальные будут продолжать работать.

Приступим к самой сложной части — взаимодействии нашего «немного умного» реле с другими, более умными, собратьями. Для тестов, да и на практике, вполне работоспособно решение с сериал портом. ATtiny85 не имеет аппаратного Serial-порта, но программный работает отлично. Пишем код:


#include "SoftwareSerial.h"
const uint8_t Rx = 0;
const uint8_t Tx = 2;
SoftwareSerial TinySerial(Rx, Tx);

const uint8_t load_pin = 3;
bool load_on = false;

typedef struct {
uint8_t pin;
bool state;
unsigned long ButtonTimerDebounce;
}
btn_t;

btn_t btn[] = {
{1, 0, 0  },
{4, 0, 0  },
};
uint8_t num_btn = 0;
unsigned long CurrentTime = 0;
const unsigned int DebounceTime = 100;

template<typename T, size_t n> inline size_t arraySize(const T (&arr)[n]){
return n;
}

void setup() {
pinMode(load_pin, OUTPUT);
digitalWrite(load_pin, LOW);

pinMode(Rx, INPUT);
pinMode(Tx, OUTPUT);
TinySerial.begin(9600);
TinySerial.write(101);


num_btn = arraySize(btn);
for (uint8_t i = 0; i < num_btn; i++) {
pinMode(btn[i].pin, INPUT);
pinMode(btn[i].pin, INPUT_PULLUP);
btn[i].state=digitalRead(btn[i].pin);
}
}

void loop() {
CurrentTime = millis();

if(TinySerial.available()){
uint8_t recived = TinySerial.read();
if(recived==152){
change_power(true);
}
else if(recived==151){
change_power(false);
}
else if(recived==153){
if(load_on){
TinySerial.write(202);
}
else {
TinySerial.write(201);
}
}
}

for (uint8_t i = 0; i < num_btn; i++) {
if(btn[i].state!=digitalRead(btn[i].pin)){
if(btn[i].ButtonTimerDebounce==0) btn[i].ButtonTimerDebounce=CurrentTime;
if((btn[i].ButtonTimerDebounce + (unsigned long)DebounceTime) < CurrentTime){
btn[i].state=!btn[i].state;
if(load_on){
change_power(false);
}else{
change_power(true);
}
btn[i].ButtonTimerDebounce=0;
}
}else{
btn[i].ButtonTimerDebounce=0;
}
}
}

void change_power(bool val){
if(val){
digitalWrite(load_pin, HIGH);
load_on = true;
TinySerial.write(102);
}
else{
digitalWrite(load_pin, LOW);
load_on = false;
TinySerial.write(101);
}
}

Я не буду построчно его комментировать, благо они похожи с предыдущим. Поясню, я использовал несколько кодов для взаимодействия устройств. При загрузке тинька сообщает старшему собрату, что нагрузка отключена. Далее она может принять и обработать команды включения, выключения и запроса статуса нагрузки. Процедуру изменения состояния нагрузки вынес в отдельную функцию, так как кода там немного прибавилось, и количество возможных мест вызова тоже изменилось. В качестве старшего собрата выступит все та же arduino nano (хотя это может быть что угодно: компьютер (в том числе raspberry pi), другой контроллер и тп). Код ардуины попроще:


#include "SoftwareSerial.h"
const uint8_t Rx = 12;
const uint8_t Tx = 11;
SoftwareSerial TinySerial(Rx, Tx);

const uint8_t led_pin = 13;
const uint8_t bt_pin = 14;

bool load_on = false;

bool ButtonOn = false;
unsigned long CurrentTime = 0;
unsigned long ButtonTimerDebounce = 0;
const unsigned int DebounceTime = 50;

void setup() {

pinMode(led_pin, OUTPUT);
digitalWrite(led_pin, LOW);

pinMode(bt_pin, INPUT);
pinMode(bt_pin, INPUT_PULLUP);

pinMode(Rx, INPUT);
pinMode(Tx, OUTPUT);
TinySerial.begin(9600);
TinySerial.write(153);

Serial.begin(9600);

delay(15);
if(TinySerial.available()){
uint8_t recived = TinySerial.read();
if(recived ==202){
change_led(true);
}
else if(recived ==201){
change_led(false);
}
}
}

void loop() {

CurrentTime = millis();

if(TinySerial.available()){
uint8_t recived = TinySerial.read();
if(recived==102){
change_led(true);
} else if(recived==101){
change_led(false);
}
}

if(Serial.available()){
uint8_t recived = Serial.read();
TinySerial.write(recived);
}

if(digitalRead(bt_pin)==LOW && !ButtonOn){
ButtonOn = true;
if(ButtonTimerDebounce==0) ButtonTimerDebounce=CurrentTime;
if(ButtonTimerDebounce + (unsigned long)DebounceTime < CurrentTime){
if(load_on){
TinySerial.write(151);
}else{
TinySerial.write(152);
}
ButtonTimerDebounce=0;
}
}else if(ButtonOn && digitalRead(bt_pin)==HIGH){
ButtonOn = false;
}
}

void change_led(bool val){
if(val){
digitalWrite(led_pin, HIGH);
load_on = true;
Serial.println("load on");
} else {
digitalWrite(led_pin, LOW);
load_on = false;
Serial.println("load off");
}
}

После загрузки считывается состояние нагрузки и отображается светодиодом. Также, принимается информация об изменении состояния нагрузки самой тинькой (локальными выключателями). К ардуино подключил кнопку, обработчик которой посылает команду на изменение состояния нагрузки тинькой. индикация меняется только при подтверждении смены состояния. Итоговый стенд:

Видео иллюстрирующее работы такого варианта:

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

Таким образом, устройство показало свою работоспособность и, я думаю, что очень пригодится мне в решении дачных и не только задач. Если требуется больше выводов, то это также возможно:
1 — использовать другой, более ногастый, контроллер (например ATTiny 2313);
2 — применить расширитель портов по шине i2c (например: PCF8574 — для 8 портов или MCP23017 — для 16 портов), которая в данном контроллере присутствует. Сохранение состояния нагрузки при отключении питания нетрудно реализовать, но я предпочту выключенный свет при восстановлении электричества по целому ряду причин.

Если будет интересно, расскажу ещё о своих новых поделках.

Спасибо тем кто дочитал до конца! Надеюсь, что данный огромный обзор такого простого устройства вас не утомил! Всех поздравляю с наступившим новым Годом!

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Наверх