Потребовалось восстановить работоспособность детской игрушки. Функционал очень простой — по нажатию кнопки начинают играть светодиоды. Проходит несколько секунд и игрушка выключается. И так до следующего нажатия на кнопку. Выключателя нет — часовые батарейки-«таблетки» в количестве трех штук питают устройство непрерывно, а родной неизвестный китайский контроллер, залитый каплей компаунда, больше не работает.
Как видите, очень просто реализовать функционал с помощью Attiny13 с возможностями PicoPower. Стал копать интернет, и нашел статью на сайте radiotech.kz, используя её, как референс, сделал своё решение.
#define F_CPU 1000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <avr/sleep.h>
const int shrt=75;
const int flash=40;
void onebyone(void)
{
for (int times=0;times<=20; times++) // running one by one
{
PORTB|= (1 << PB0);
_delay_ms(shrt);
PORTB&= ~(1 << PB0);
_delay_ms(flash);
PORTB|= (1 << PB1);
_delay_ms(shrt);
PORTB&= ~(1 << PB1);
_delay_ms(flash);
PORTB|= (1 << PB2);
_delay_ms(shrt);
PORTB&= ~(1 << PB2);
_delay_ms(flash);
PORTB|= (1 << PB3);
_delay_ms(shrt);
PORTB&= ~(1 << PB3);
_delay_ms(flash);
}
}
int main(void)
{
DDRB=0b00001111;
PORTB=0b00010000;
MCUCR=0x30; // Sleep enable, mode: power-down
GIMSK=0x20; // Pin Change Interrupt Enable
PCMSK=0b00010000; // PCINT4 & PCINT3 set
asm("sei");
while(1)
{
asm ("sleep");
}
}
ISR(PCINT0_vect)
{
if ((PINB & 0x08)==0x00)
{
_delay_ms (50);
if ((PINB & 0x08)==0x00)
{
onebyone();
}
}
}
Чтобы активировать режим Power-down достаточно биты SM[1:0] регистра MCUCR установить в 10 и разрешить спящий режим МК (бит SE регистра MCUCR в 1). Усыпляется МК командой asm («sleep»);
Остается только настроить прерывание по нажатию кнопок и написать его обработчик.
Лично мне было не удобно, что режимы работы портов указаны в оригинальной статье, как HEX-значения. Честно, я долго сидел и втыкал, что значит «DDRB=0x27; PORTB=0x18;», пока не догадался, что надо значения перевести в двоичный код, и получить очень наглядное значение для каждого бита, где 1 — включен, 0 — выключен. Например, 0x27=0b100111, а 0x18=0b11000.
У меня в проекте микроконтроллер после определения нажатой кнопки переходит к исполнению процедуры «onebyone» — светодиоды бегут друг за другом. Вместо этого можно сделать выполнять любую другую задачу.
Итак, закрепим полученные знания:
- строка define F_CPU 1000000UL указывает частоту внутреннего таймера микроконтроллера.
- Двойной if в обработчике прерывания отвечает за простой программный антидребезг контактов. Может, он и примитивный, но неверных срабатываний я пока не заметил. А то уже собирался ставить триггер Шмитта.
- В основном цикле программы контроллер только и делает, что спит — «asm («sleep»);» — Не совсем наглядно, когда в коде на Си возникают вставки на ассемблере, но так, наверное, можно сэкономить много ценных байтов программной памяти.
Возможно, не совсем правильно, что обработчик прерываний используется с паузами, но так как микроконтроллер в данном проекте больше ничем не занят, то почему бы и нет. Единственное, что не устраивает — нет проверки и защиты на случай постоянного нажатия на кнопку. Хотелось бы, что в случае случайного или специального долговременного зажатия кнопки, контроллер уходил в спячку и не сажал батарейку.
Разве, после входа в обработчик прерывания ISR(PCINT0_vect) не следует его отрубать? Оно настроено на изменение состояния: отпустите кнопку и опять вывалитесь в него…
Возможно, вы правы, я не пробовал долго держать кнопку нажатой, а потом смотреть, что будет, когда отожму. В данный момент работает так: нажал кнопку — программа запустилась, отработала и контроллер уснул до следующего нажатия.
Отпуская, Вы скорее всего инициируете установку флага по PCINT0 и при выходе из обработчика, вновь влезаете в него, но т.к. кнопка отжата, то никаких действий в теле обработчика не выполняется и только теперь, выйдя вторично из него, вы попадаете в asm («sleep»);