Музыкальный дверной звонок в стиле Star Wars на Arduino

в 17:58, , рубрики: arduino, star wars, Программинг микроконтроллеров

Всем привет, не так давно я рассказал, да и показал на видео, как можно прошить Attiny13 при помощи Arduino, а теперь покажу практическое применение этому.

Скажу сразу, фьюзы, прошивку в виде hex-файла, код на Си, скетч для Arduino IDE, файлы для Proteus 7 можно скачать в конце статьи.

Ну, что уж тянуть, покажу как работает:

Давно хотел себе такой музыкальный дверной звонок, чтобы при нажатии на кнопку играл марш империи(Imperial March or Darth Vader's Theme) из «Звёздных войн», очень нравится эта мелодия.

Как видно из названия видео выше, главный компонент устройства — микроконтроллер Attiny13, его применение сделало возможным сделать размеры платы в несколько раз меньше по размерах чем спичечной коробок:

Размеры платы

image

А вот уже всё подключённое:

image

Диапазон питания примерно от 2 В до 6 В, то есть, от двух до четырех батареек формата AA, хотя в идеале бы обеспечить напряжение 3-4 В, то есть для этих целей идеально подходит литий ионный аккумулятор от мобильника или же аккумулятор формата 18650, правда желательно чтобы он был с защитой, так как я пока не реализовал никакой защиты от глубокого разряда.

Ну что же, скажу пару слов по схемотехнике:

Схема выглядит вот так(файлы Proteus прилагаются в конце статьи, можно даже ничего не собирать на макетке):

Нарисована в Proteus'е

image

Так как микроконтроллер не может самостоятельно вытянуть достаточную громкость, для того чтобы было слышно звонок по всему доме, я добавил NPN транзистор 2N3904, довольно таки распространенный транзистор, в принципе может подойти любой транзистор который потянет динамик, в моём случае транзистор рассчитан на 100 мА.
При использовании 3-х батареек формата AA транзистор немножко грелся, при использовании двух — громкость осталась на приличном уровне но уже транзистор был слегка тёплым.
Резистор R2 — стандартная обвязка для микроконтроллера, служит защитой от случайных перезагрузок микроконтроллера, в принципе работать должно и без него, резистор R1 служит для ограничения тока на базе транзистора.
На фото видно ещё защитный диод, защищает он от невнимательности, а именно от переполюсовки, в моём случае, переполюсовка очень быстро выведет микроконтроллер из строя, причём вполне возможны как пиротехнические так и звуковые эффекты. Кстати, на схеме ниже я забыл указать его, подойдёт любой диод который рассчитан на напряжение от 10 В и ток от 200 мА, ставиться последовательно входу платы, или по минусу или по плюсу, у меня по минусу.
Динамик от старого Dial up модема Zyxel, кстати, про Dial up модемы этой фирмы есть один известный анекдот:

Анекдот

Сидят два хакера, и в комнату заходит кот. Один хакер спрашивает:
— Твой кот?
— Да, мой. Зухель зовут!
— Почему Зухель?
— Вот смотри. Берет веник, тычет им в кота и говорит: «Зухель, коннект!!!» Кот:
— Пшшшшшшшшшшшшшш!

Код, скажу сразу, нагуглил, точней нашёл на просторах ютуба, вот собственно само видео:

Под видео есть ссылка на код, вот он:

Код из описания к видео

#include <avr/pgmspace.h>


// by @tartakynov
// programmed for ATtiny13 in Arduino IDE using core13 http://sourceforge.net/projects/ard-core13/

#define PIN_LED 1
#define PIN_BUZZER 0
#define COUNT_NOTES 39

word frequences[COUNT_NOTES] PROGMEM = { 
                                392, 392, 392, 311, 466, 392, 311, 466, 392, 
                                587, 587, 587, 622, 466, 369, 311, 466, 392, 
                                784, 392, 392, 784, 739, 698, 659, 622, 659, 
                                415, 554, 523, 493, 466, 440, 466, 
                                311, 369, 311, 466, 392 };
word durations[COUNT_NOTES] PROGMEM = {  
                                350, 350, 350, 250, 100, 350, 250, 100, 700, 
                                350, 350, 350, 250, 100, 350, 250, 100, 700, 
                                350, 250, 100, 350, 250, 100, 100, 100, 200, 
                                100, 350, 250, 100, 100, 100, 200, 
                                100, 350, 250, 100, 750 };

void setup()
{
    pinMode(PIN_LED, OUTPUT);
    pinMode(PIN_BUZZER, OUTPUT);
}

void loop()
{
    for (byte i = 0; i < COUNT_NOTES; i++)
    {
        buzz(PIN_BUZZER, pgm_read_word(&(frequences[i])), 2 * pgm_read_word(&(durations[i])));
        delay(100);
    }
    delay(3000);
}

void buzz(unsigned char pin, word frequencyInHertz, word timeInMilliseconds)
{ 
    long delayAmount = (long)(long(1000000) / (long)frequencyInHertz);
    long loopTime = (long)(((long)timeInMilliseconds * 500) / delayAmount);
    for (long i = 0; i < loopTime; i++)  
    {  
        digitalWrite(pin, HIGH);
        delayMicroseconds(delayAmount);
        digitalWrite(pin, LOW);
        delayMicroseconds(delayAmount);
    }
}

Размер скетча в двоичном коде: 946 байт (из 1 024 байт максимум)

Как Вы можете видеть, есть два массива frequences — в переводе из английского частота и durations — длительность, всё данные типа word, PROGMEM — данные хранятся во флеш-памяти микроконтроллера(без использования библиотеки pgmspace.h работать не будет) ну и есть генератор частоты buzz() который принимает три параметра — пин, на котором будет генерироваться частота, второй — частота в герцах, третий — длительность в миллисекундах.
Данный код должен быть Arduino совместимым и работать даже на Arduino Uno, Arduino Nano или же Arduino Pro Mini ну и и других дуинах.

Как Вы могли слышать на видео, звучание моего видео немного отличается от второго видео, дело в том, что я немного изменил длительность нот и в три раза популярным методом «научного тыка», поднял частоту, так как такой маленький динамик как у меня плохо воспроизводит те частоты что были изначально, и добавил кнопку, какой же дверной звонок без кнопки?

Немного подправил под себя и закомментировал


// by @tartakynov:
// http://youtu.be/5R7NeQkVS_8
// and me - vk.com/razniepodelki

#define F_CPU 1200000L  // Частота МК в герцах
#include <avr/pgmspace.h> // нужно для PROGMEM

#define PIN_BUZZER 2 // PB2 Динамик
#define BUTTON 4 // PB4 Кнопка

#define COUNT_NOTES 39  // количество нот

word frequences[COUNT_NOTES] PROGMEM = { // тут лежат частоты
  392, 392, 392, 311, 466, 392, 311, 466, 392,
  587, 587, 587, 622, 466, 369, 311, 466, 392,
  784, 392, 392, 784, 739, 698, 659, 622, 659,
  415, 554, 523, 493, 466, 440, 466,
  311, 369, 311, 466, 392 };

word durations[COUNT_NOTES] PROGMEM = { // тут их длительность 
  350, 350, 350, 250, 100, 350, 250, 100, 700,
  350, 350, 350, 250, 100, 350, 250, 100, 700,
  350, 250, 100, 350, 250, 100, 100, 100, 450,
  150, 350, 250, 100, 100, 100, 450,
  150, 350, 250, 100, 750 };

//void setup()
//{
  
int main( void ) // это аналог void setup(), для экономии места
{
  pinMode(PIN_BUZZER, OUTPUT); // инициализация пинов
  pinMode(BUTTON, INPUT); // подключаем подтягивающий резистор
  digitalWrite(BUTTON, HIGH); // чтобы кнопка возвращала 
  //LOW при нажатии

  //}
  //void loop()
  //{
    
  while(1){ // аналог void loop()(вечный цикл)
    if (digitalRead(BUTTON) == LOW) {   // когда кнопка нажата
      for (byte i = 0; i < COUNT_NOTES; i++) // собственно проигрываем мелодию
      {
        buzz(PIN_BUZZER, pgm_read_word(&(frequences[i])) * 3, 2 * pgm_read_word(&(durations[i])));
        // изначально было:
        // buzz(PIN_BUZZER, pgm_read_word(&(frequences[i])), 2 * pgm_read_word(&(durations[i])));
        // но я умножил частоту на 3
        //(pgm_read_word(&(frequences[i])) * 3) 
        //чтобы было громче слышно на небольшом динамике
        //delay(100); // этого не нужно
      }
    }
    //delay(3000); // и этого
    
  }         // эти строчки нужны 
  return 0; // int main( void ) 
}           // и while(1)

void buzz(unsigned char pin, word frequencyInHertz, word timeInMilliseconds) // по сути это 
{                                                                            // генератор частоты            
  long delayAmount = (long)(long(1000000) / (long)frequencyInHertz);         // имеет 3 параметра
  long loopTime = (long)(((long)timeInMilliseconds * 500) / delayAmount);    // 1 - пин
  for (long i = 0; i < loopTime; i++)                                      // 2 - частота
  {                                                                          // 3 - длительность
    digitalWrite(pin, HIGH);        // генерируем импульсы нужной частоты  
    delayMicroseconds(delayAmount);
    digitalWrite(pin, LOW);
    delayMicroseconds(delayAmount);
  }
}

Размер скетча в двоичном коде: 976 байт (из 1 024 байт максимум)

Как видите, добавления срабатывания при нажатии на кнопку превысило 1024 байта и пришлось вставлять кусочки кода Си чтобы уместиться в attiny13.
Частоту поднял строках buzz(PIN_BUZZER, pgm_read_word(&(frequences[i])) * 3, 2 * pgm_read_word(&(durations[i]))); тем самим умножив генерируемые частоты, как Вы поняли на три, это чисто мой каприз, можете ничего не умножать вообще.

Если Вы опытный ардуинщик, то думаю заметите в коде что я сделал программную подтяжку PULLUP резистора к порту PB4.
Для тех кто не знает делается это так:
Выставляем порт на вход и подаём на него логическую единицу. Теперь на порту будет напряжение, примерно равно напряжению питания, если этот порт закоротить на землю то он изменит своё состояние с логической единицы на логический ноль, причём микроконтроллер при этом не будет страдать, так как используется внутренний резистор, номинал которого 10-100 кОм.
Сделал я это чисто для экономии размеров платы, можно было просто подпаять резистор на 10 кОм к плюсу питанию и нужному порту, при нажатии на кнопку мы «притягиваем порт к земле» и на нём будет логической ноль.

Добавляю код на чистом AVR-С для тру AVR'щиков:

Код на AVR-С


// by @tartakynov:
// http://youtu.be/5R7NeQkVS_8
// and me - vk.com/razniepodelki


#define F_CPU 1200000UL  // Частота МК в герцах
#include <avr/io.h>  // библиотека ввода вывода
#include <util/delay.h> // библиотека для работы с задержками
#include <avr/pgmspace.h> // нужно для PROGMEM

#define PIN_BUZZER 2 // PB2 Динамик
#define BUTTON 4 // PB4 Кнопка

#define COUNT_NOTES 39 // количество нот

word frequences[COUNT_NOTES] PROGMEM = { // тут лежат частоты
  392, 392, 392, 311, 466, 392, 311, 466, 392,
  587, 587, 587, 622, 466, 369, 311, 466, 392,
  784, 392, 392, 784, 739, 698, 659, 622, 659,
  415, 554, 523, 493, 466, 440, 466,
  311, 369, 311, 466, 392 };

word durations[COUNT_NOTES] PROGMEM = { // тут их длительность 
  350, 350, 350, 250, 100, 350, 250, 100, 700,
  350, 350, 350, 250, 100, 350, 250, 100, 700,
  350, 250, 100, 350, 250, 100, 100, 100, 450,
  150, 350, 250, 100, 100, 100, 450,
  150, 350, 250, 100, 750 };

//void setup()
//{

int main( void ) // это аналог void setup(), для экономии места
{
  DDRB |= (1<<PIN_BUZZER); // иницыализация портов
  DDRB &= ~(1<<BUTTON); // подключаем подтягивающий резистор
  PORTB |= (1<<BUTTON);  // чтобы кнопка возвращала 
  //0 при нажатии(замыкинии)

  //}
  //void loop()
  //{

  while(1){ // аналог void loop()(вечный цикл)
    if (!(PINB & (1<<BUTTON))) {   // когда кнопка нажата
      for (byte i = 0; i < COUNT_NOTES; i++) // собственно проигрываем мелодию
      {
        buzz(pgm_read_word(&(frequences[i])) * 3, 2 * pgm_read_word(&(durations[i])));
        // изначально было:
        // buzz(PIN_BUZZER, pgm_read_word(&(frequences[i])), 2 * pgm_read_word(&(durations[i])));
        // но я умножил частоту на 3
        //(pgm_read_word(&(frequences[i])) * 3) 
        //чтобы было громче слышно на небольшом динамике
      }
      //_delay_ms(100); // для отладки
    }

    //_delay_ms(3000); // для отладки

  }         // эти строчки нужны 
  return 0; // int main( void ) 
}           // и while(1)

//void buzz(unsigned char pin, word frequencyInHertz, word timeInMilliseconds) 
void buzz(word frequencyInHertz, word timeInMilliseconds)  // по сути это 
{                                                                            // генератор частоты            
  long delayAmount = (long)(long(1000000) / (long)frequencyInHertz);         // имеет 3 параметра
  long loopTime = (long)(((long)timeInMilliseconds * 500) / delayAmount);    // 1 - пин
  for (long i = 0; i < loopTime; i++)                                      // 2 - частота
  {                                                                          // 3 - длительность
    //digitalWrite(pin, HIGH);        // генерируем импульсы нужной частоты  
    PORTB |= (1<<PIN_BUZZER);  // для экономии вставлю строчки на Си
    //delayMicroseconds(delayAmount); // мкс задержка на ардуино
    _delay_us(delayAmount); // на Си

    //digitalWrite(pin, LOW);
    PORTB &= ~(1<<PIN_BUZZER);
    //delayMicroseconds(delayAmount); // мкс задержка на ардуино
    _delay_us(delayAmount); // на Си
  }
}

Вроде как должен компилироваться без проблем, а вот при использовании этого кода в Arduino IDE почему-то размер hex файла как минимум удваивается «Размер скетча в двоичном коде: 1 986 байт (из 1 024 байт максимум)».
Как мне удалось выяснить всё это из за использования _delay_us(*); хотя ардуиновская функция delayMicroseconds(*); по сути делает то же самое, думаю это недочёт файлов ядра для тини13, вот ветка на буржуйском форуме ардуино откуда я и взял файлы ядра, правда самую малость доработал под себя, ссылка на доработанные мною файлы ядра есть в статье про прошивку Attiny13 при помощи Arduino.
Если кто-то поможет разобраться с этим багом буду только рад, ведь чисто теоретически Arduino IDE должна без проблем проглатывать С-шний код.

Ну и пару слов по прошивке — на первом видео запускал тиньку на частоте 1.2 мГц, как по мне, так 1.2 миллиона операций вполне достаточно чтобы играть любимую мелодию и при этом кушать совсем не много, кстати hex-файл скомпилирован именно под эту частоту.
Вот фьюзы из калькулятора фьюзов:

Скрытый текст

image

Теперь, каждый кто имеет AVR программатор сможет повторить это устройство.

Как было обещано в начале статьи вот все нужные файлы.

И напоследок — «Да прибудет с Вами сила».

Автор: HWman

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js