Внешний сторожевой таймер это костыль для плохих разработчиков, которые не могут разработать нормально работающую программу для микроконтроллеров или стабильно работающую схему.
Тем более встроенный WDT имеется у большинства современных микроконтроллеров.
Но бывают случаи, когда приходится иметь дело с готовой платой или модулем с определенными проблемами. Свой первый WDT я сделал для борьбы с редкими, но все же иногда происходящими зависаниями ESP8266. Причем софтовый ресет тогда не спасал и ESP-шка не хотела переподключаться к WiFi. Передергивание питания внешним WDT решило проблему.
Вторая проблема возникла с GSM контроллером Elecrow ATMEGA 32u4 A9G. Здесь имели место быть очень редко случающиеся зависание SIM-карты. (Кстати эту же проблема бывает и с USB-модемами 3G и 4G). Для борьбы с таким зависанием нужно передернуть питание на SIM-ке. И вроде даже вывод у GSM модема для этого есть, но в схемотехнику устройства данная возможность не заложена. И для достижения максимальной надежность пришлось снова обращаться к внешней сторожевой собаке.
Схему на таймере 555 я не стал повторять. Слишком много недостатков у нее выявилось:
- Большие габариты и довольно много обвязки
- Неудобная установка времени срабатывания подстроечным резистором
- Довольно длительное время сброса (необходима разрядка конденсатора)
- Ну и потенциальное зависание МК с низким уровнем на выходе таймера, когда таймер просто перестает срабатывать.
- А проектов OpenSource в интернете, полностью соответствующих моим требованиям, я не нашел.
Требования к новому WDT
- Низкая цена устройства, простота изготовления и малые габариты
- Управление периодической сменой логического уровня 0/1 на входе
- Простая настройка времени срабатывания (как вариант выбор из предустановленных интервалов)
Разработка железа
В качестве основной микросхемы выбрал микроконтроллер ATtiny13. Его возможностей оказалось более чем достаточно для моей задачи. А цена, с учетом уменьшения элементов обвязки — практически такая же как у 555 микросхемы.
Пять выводов МК (RESET решил не трогать) распределились следующим образом:
- Выход таймера
- Вход для сброса
- Три оставшихся вывода — задания времени срабатывания
Для коммутации питания используется P-канальный MOSFET. Подойдет любой совместимый по корпусу, но желательно брать с так называемым «логическим уровнем управления» — то есть полностью открывающийся от низкого напряжения 3-5В: IRLML2502, AO3415 и т.п. Несмотря на малые размеры, данный транзистор способен управлять нагрузкой в 4А. Если нужно коммутировать что-то другое, к этому выходу можно напрямую подключить реле на 5В.
Светодиод загорается в момент срабатывания таймера и отключения основного устройства.
Основной разъем для подключения к плате микроконтроллера имеет четыре вывода
- Общая шина
- Вход — сброс таймера
- Выход +5В (управляется таймером)
- Вход +5В
Два разъема — ICSP программатор и джамперы питания можно не устанавливать на плате. Микроконтроллер прошить в программаторе заранее, а время срабатывания задать постоянной перемычкой.
Список комплектующих
- МК Attiny13-SSU ~ $0.3 (при покупке 10 шт)
- MOSFET P-канал IRLML2502 — $0.03 (заказ 50шт) или MOSFET AO3415 — $0.05
- Резистор 1К SMD1206
- Резистор 470 SMD1206
- Светодиод 1206 любого цвета
- Разъемы PLS-6, PLS-3 и PLS-4R (PLD-3 и PLS-4R) — нормально отрезаются от длинных гребенок
Изготовление
Платы получились маленькие — 18×22 мм. Я развел два варианта:
Для одностороннего изготовления ЛУТом
И для заказа на заводе с улучшенным дизайном и переходами меж сторонами. (Закажу у китайцев, при случае)
Домашние технологии дают примерно такой прототип.
Прошивка
Для прошивки использовал самодельный программатор на баз Arduino Nano
Программировал я в среде Arduino IDE с установленной поддержкой Attiny13 — MicroCore. В последней версии IDE были проблемы программатора ArduinoISP, но нормально заработало в версии Arduino IDE 1.6.13. Разбираться, что там накосячила наизменяла дружная команда arduino.cc желания не возникло )))
Тиньку настроил на работу от внутреннего резонатора с частотой 1.2МГц. Программа простая — настраиваем входы/выходы, считываем PB2 -PB4 и определяем время срабатывания, настраиваем таймер и переходим в режим IDLE. По прерыванию по таймеру определяем состояние контрольного входа. Если состояние изменилось на противоположное, сбрасываем счетчик. Если показания счетчика превысило установленное время срабатывания — передергиваем питание на выходе.
#define F_CPU 1200000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
boolean pb1_state;
volatile uint16_t pb1_count;
// Оброботчик прерывания по таймеру TIMER0
ISR(TIM0_OVF_vect){
pb1_count++;
}
int main(){
// Устанавливаем выход PB0
DDRB |= (1 << PB0); // pinMode(PB0, OUTPUT);
PORTB &= ~(1 << PB0); // digitalWrite(PB0, LOW);}
// Устанавливаем вход PB1 с подтягиванием
DDRB &= ~(1 << PB1); // pinMode(PB1, INPUT_PULLUP);
PORTB |= (1 << PB1);
// Устанавливаем вход PB2 с подтягиванием
DDRB &= ~(1 << PB2); // pinMode(PB2, INPUT_PULLUP);
PORTB |= (1 << PB2);
// Устанавливаем входы PB3 с подтягиванием
DDRB &= ~(1 << PB3); // pinMode(PB3, INPUT_PULLUP);
PORTB |= (1 << PB3);
// Устанавливаем входы PB4 с подтягиванием
DDRB &= ~(1 << PB4); // pinMode(PB4, INPUT_PULLUP);
PORTB |= (1 << PB4);
// Определяам время срабатывание таймера по входам PB2,PB3,PB4 (перемычки подтягивают к земле) (период, сек = TM/4 )
uint16_t TM = 0;
bool pb2 = false;
bool pb3 = false;
bool pb4 = false;
if( PINB & (1 << PINB2) )pb2 = true;
if( PINB & (1 << PINB3) )pb3 = true;
if( PINB & (1 << PINB4) )pb4 = true;
if( pb2 == true && pb3 == true && pb4 == true )TM = 4; //Таймаут 1 сек
else if( pb2 == false && pb3 == true && pb4 == true )TM = 8; //Таймаут 2 сек
else if( pb2 == true && pb3 == false && pb4 == true )TM = 20; //Таймаут 5 сек
else if( pb2 == false && pb3 == false && pb4 == true )TM = 40; //Таймаут 10 сек
else if( pb2 == true && pb3 == true && pb4 == false )TM = 80; //Таймаут 20 сек
else if( pb2 == false && pb3 == true && pb4 == false )TM = 120; //Таймаут 30 сек
else if( pb2 == true && pb3 == false && pb4 == false )TM = 240; //Таймаут 60 сек
else if( pb2 == false && pb3 == false && pb4 == false )TM = 480; //Таймаут 120 сек
pb1_count = 0;
pb1_state = false;
// Отключаем ADC
PRR = (1<<PRADC); // shut down ADC
// Настраиваем таймер
TIMSK0 = (1<<TOIE0); // Включаем таймер TIMER0
TCCR0B = (1<<CS02) | (1<<CS00); // Пределитель таймера на 1/1024
// Задаем режим сна
MCUCR &= ~(1<<SM1); // idle mode
MCUCR &= ~(1<<SM0); // idle mode
MCUCR |= (1<<SE);
sei();
while(1) {
// Зпсываем до прерывания по таймеру
asm("sleep");
// Таймер сработал
TIMSK0 &= ~ (1<<TOIE0); // Останавливаем TIMER0
// Считываем состояние PB1
bool pb1 = false;
if( PINB & (1 << PINB1) )pb1 = true;
// Если состояние входа инвертировалось, сбрасываем время
if( pb1 != pb1_state )pb1_count = 0;
pb1_state = pb1;
// Если превышено время установки таймера
if( pb1_count >= TM ){
PORTB |= (1 << PB0); // digitalWrite(PB0, HIGH);}
_delay_ms(1000); // Ждем секунду
PORTB &= ~(1 << PB0); // digitalWrite(PB0, LOW);}
pb1_count = 0; // Сбрасываем счетчик
}
TIMSK0 = (1<<TOIE0); // Включаем таймер TIMER0
sei();
}
return 0;
}
Весь код уместился в 340 байт — ровно треть от килобайта памяти тиньки. Работа таймера проверяется просто — в зависимости от времени установки — периодически загорается светодиод на 1 сек. В это время на выходе Vвых напряжение 5В пропадает. Если контакт «вход» с периодичностью 1 сек замыкать на землю — сброс не производится и светодиод не загорается.
Управление WDT в основной программе следующее
#define PIN_WDT 5 //GPIO контроллера, куда подключен WDT
bool WDT_flag = false;
// Инициализация порта таймера
void WDT_begin(){
pinMode(PIN_WDT,OUTPUT);
digitalWrite(PIN_WDT,WDT_FLAG);
}
// Сброс таймера (не реже чем 1 на время срабатывания WDT, установленное перемычкой)
void WDT_reset(){
if( WDT_flag)WDT_flag = false;
else WDT_flag = true;
digitalWrite(PIN_WDT,WDT_FLAG);
}
Вот собственно а все. Все исходные файлы, схемы и печатные платы можно скачать с GITHUBа
https://github.com/samopal-pro/wdt-tiny13
Автор: sav13