Melodický zvonek

Před nedávnem se nám doma rozbil zvonek. Šmejd z NDR. Nevydržel ani třicet let. Takže jsem se najednou ocitl před dilematem: buď za 150 korun koupit zvonek nový, nebo ho zadarmo vyrobit z elektronického šrotu, který posbírám ve sklepě. Váhání netrvalo dlouho - s kupovaným zvonkem by se neužilo tolik legrace.

Melodický zvonek je nedílnou součástí výbavy domácnosti každého Kreténa. Nicméně i polokreténi i zbytek populace by se při nevybavení domácnosti melodickým zvonkem měli nad sebou vážně zamyslet.

Ten původní zvonek vydával zvuk Ding-Dong. Vydal Ding při stisknutí zvonkového tlačítka a Dong při jeho uvolnění. Pokud chtěl příchozí, aby zvonek zazvonil několikrát, musel několikrát stisknout (a uvolnit) tlačítko. To některým příchozím, pohybujícím se svojí inteligencí na spodní hranici širšího průměru, činilo určité potíže.

Tento vyrobený zvonek také vydává zvuk Ding-Dong, podobně jako ten původní z NDR. Na rozdíl od něj ovšem vydá souvisle celý zvuk Ding-Dong při stisknutí tlačítka. Pokud příchozí stále tlačítko drží, zvuk se dokola opakuje. Po několika vteřinách od posledního zazvonění zvonek podotkne: Nezvonil někdo?

Princip

Princip je jednoduchý. Z dnešního pohledu je to již klasická konstrukce, jak vystřižená z učebnice aplikované číslicové techniky vydané tak před dvaceti lety. Zvonek je řízen procesorem, který při aktivaci vstupu začne na výstupu generovat adresu a hodinové pulsy. Za procesorem je připojena EEPROM, ve které je nahraný zvuk. Výstup dat z EEPROM je zesílen budičem datové sběrnice a zaveden do odporové sítě, která tvoří DA převodník. No a za převodníkem je nízkofrekvenční zesilovač, který má na výstupu připojený reproduktor. Jak prosté. Aby zařízení dokázalo přehrát celý zvuk i při krátkém stisku zvonkového tlačítka, použil jsem fintu s diodou, kterou jsem přemostil kontakt zvonkového tlačítka. Obvod zvonku je napájený klasickým zvonkovým transformátorem. Díky tomuto řešení se tedy do zvonku stále dostává jednocestně usměrněné napětí. Při stisknutí tlačítka se na vstup zvonku dostanou obě půlvlny. Jednoduchý obvod tento stav detekuje a aktivuje vstup procesoru.

První verze

První verze je vhodná pro začínající domácnosti. Použil jsem procesor PIC18F252, za něj jsem připojil EEPROM AT29C010, za ní následoval osmibitový DA převodník a modul zesilovače s integrovaným obvodem MBA810 ze staré televize. Zařízení je umístěno ve starém nástěnném reproduktoru z drátového místního rozhlasu. Reproduktor je tříwattový, poměrně velký, takže zvuk je docela kvalitní. I když slovo docela hraje v předchozí větě hlavní roli. Nahrávka je pouze osmibitová, a to je na zvuku znát.

zvonek

Druhá verze

Druhá, zdokonalená verze je již určena do zavedených domácností. Je to zařízení ve třídě KiFi*. Záznam zvuku je šestnáctibitový a vzorkování je 44100 Hz. Je to tedy CD kvalita. Zvonek má stále pouze jeden procesor - v IT hantýrce je to tedy stále pouze jednojádro. Adresní sběrnice je z výstupu procesoru paralelně vedena na dvě EEPROMky. Na jejich výstupech jsou zřetězené DA převodníky, které dávají dohromady výsledných 16 bitů. Původní šestnáctibitovou nahrávku je třeba rozdělit na nižší a vyšší bajty, nižší bajty nahrát do nižší EEPROM a vyšší bajty do vyšší EEPROM. Doporučuji si na to vytvořit jednoduchý program, ruční třídění bajtů by bylo zdlouhavé.

zvonek

Úprava nahrávky

Nahrávku jsem upravoval v programu Audacity. Použitá EEPROM je jednomegabitová, přesněji je to 8*128 kB. Při vzorkování 44100 Hz se do ní tedy vejdou necelé tři sekundy záznamu. To na popisovanou aplikaci stačí, pokud by někdo chtěl záznam delší, může snížit vzorkovací kmitočet. Audio uložíme v holém formátu. Pro 8-bitovou verzi použijeme formát 8-bit unsigned a jsme hotovi. Výsledný soubor můžeme rovnou vypálit do EEPROM. Pro 16-bitovou verzi je zpracování trochu složitější. Audacity nenabízí možnost uložit v nesignovaném formátu. Musíme tedy použít 16-bit signed a přičtením hodnoty 0x7FFF ke každému vzorku formát nejdříve převést na neznaménkový. Potom následuje rozdělení do dvou souborů - zvlášť vyšší a zvlášť nižší bajty. Jak už jsem poznamenal, je vhodné si na to napsat nějaký program, např.:

#include <QCoreApplication>
#include <QFile>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QString filename = argv[1];

    QFile input(filename);
    QFile ol(filename+"L");
    QFile oh(filename+"H");

    input.open(QIODevice::ReadOnly);
    ol.open(QIODevice::WriteOnly);
    oh.open(QIODevice::WriteOnly);

    signed short ss;
    unsigned char ch;

    while (input.read((char*)&ss, 2)) {
        ss += 32767;
        ch = ss & 0xFF;
        ol.write((char*)&ch, 1);
        ch = (ss >> 8) & 0xFF;
        oh.write((char*)&ch, 1);
    }

    input.close();
    ol.close();
    oh.close();

    return 0;
}

Aby zvonek pracoval tak, jak je popsáno v úvodu, jsou v paměti nahrány dva zvuky za sebou. Procesor má v programu uloženy konstanty, které definují adresy začátků a konců jednotlivých zvuků. Ty je tedy třeba si při vytváření nahrávky zjistit a doplnit do zdrojového kódu. A nebo je samozřejmě možné nahrávky zorganizovat v paměti úplně jinak, případně úplně změnit program, aby zvonek dělal něco úplně jiného, případně toto zařízení vůbec nevyrábět.

Program

Program je napsaný v jazyce C a je velmi primitivní. V hlavní smyčce programu se snímá stav vstupu, a pokud je třeba přehrávat zvuk, povolí se přerušení. To se stará o inkrement adresy a hodinové pulsy pro EEPROM. Program rozeznává několik fází, ve kterých se zrovna nachází, aktivace vstupu, deaktivace vstupu a dokončené přehrávání zvuku způsobují přechod z jedné fáze do další. Celý cyklus se dokola opakuje:

/* 
 * File:   main.c
 * Author: k15
 *
 * Created on 8. prosinec 2013, 15:29
 */

#include <stdio.h>
#include <stdlib.h>
#include <pic18f252.h>

// PIC18F252 Configuration Bit Settings

#include <xc.h>

// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

// CONFIG1H
#pragma config OSC = HS         // Oscillator Selection bits (HS oscillator)
#pragma config OSCS = OFF       // Oscillator System Clock Switch Enable bit (Oscillator system clock switch option is disabled (main oscillator is source))

// CONFIG2L
#pragma config PWRT = OFF       // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOR = OFF        // Brown-out Reset Enable bit (Brown-out Reset disabled)
#pragma config BORV = 20        // Brown-out Reset Voltage bits (VBOR set to 2.0V)

// CONFIG2H
#pragma config WDT = ON         // Watchdog Timer Enable bit (WDT disabled (control is placed on the SWDTEN bit))
#pragma config WDTPS = 128      // Watchdog Timer Postscale Select bits (1:128)

// CONFIG3H
#pragma config CCP2MUX = ON     // CCP2 Mux bit (CCP2 input/output is multiplexed with RC1)

// CONFIG4L
#pragma config STVR = ON        // Stack Full/Underflow Reset Enable bit (Stack Full/Underflow will cause RESET)
#pragma config LVP = OFF        // Low Voltage ICSP Enable bit (Low Voltage ICSP enabled)

// CONFIG5L
#pragma config CP0 = OFF        // Code Protection bit (Block 0 (000200-001FFFh) not code protected)
#pragma config CP1 = OFF        // Code Protection bit (Block 1 (002000-003FFFh) not code protected)
#pragma config CP2 = OFF        // Code Protection bit (Block 2 (004000-005FFFh) not code protected)
#pragma config CP3 = OFF        // Code Protection bit (Block 3 (006000-007FFFh) not code protected)

// CONFIG5H
#pragma config CPB = OFF        // Boot Block Code Protection bit (Boot Block (000000-0001FFh) not code protected)
#pragma config CPD = OFF        // Data EEPROM Code Protection bit (Data EEPROM not code protected)

// CONFIG6L
#pragma config WRT0 = OFF       // Write Protection bit (Block 0 (000200-001FFFh) not write protected)
#pragma config WRT1 = OFF       // Write Protection bit (Block 1 (002000-003FFFh) not write protected)
#pragma config WRT2 = OFF       // Write Protection bit (Block 2 (004000-005FFFh) not write protected)
#pragma config WRT3 = OFF       // Write Protection bit (Block 3 (006000-007FFFh) not write protected)

// CONFIG6H
#pragma config WRTC = OFF       // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) not write protected)
#pragma config WRTB = OFF       // Boot Block Write Protection bit (Boot Block (000000-0001FFh) not write protected)
#pragma config WRTD = OFF       // Data EEPROM Write Protection bit (Data EEPROM not write protected)

// CONFIG7L
#pragma config EBTR0 = OFF      // Table Read Protection bit (Block 0 (000200-001FFFh) not protected from Table Reads executed in other blocks)
#pragma config EBTR1 = OFF      // Table Read Protection bit (Block 1 (002000-003FFFh) not protected from Table Reads executed in other blocks)
#pragma config EBTR2 = OFF      // Table Read Protection bit (Block 2 (004000-005FFFh) not protected from Table Reads executed in other blocks)
#pragma config EBTR3 = OFF      // Table Read Protection bit (Block 3 (006000-007FFFh) not protected from Table Reads executed in other blocks)

// CONFIG7H
#pragma config EBTRB = OFF      // Boot Block Table Read Protection bit (Boot Block (000000-0001FFh) not protected from Table Reads executed in other blocks)

// timer 227 -> 22050Hz @ 20MHz crystal
// timer 113 -> 44100Hz @ 20MHz crystal
#define TMR_LOAD 180

#define AMP     LA3
#define CLK     LA1
#define ADRL    LATB
#define ADRH    LATC
#define ADR16   LA0

#define SND1BEGIN   0x0
#define SND1END     0xF910

#define SND2BEGIN   0xF910
#define SND2END     0x1F3C0

#define BUTTON     !PORTAbits.RA2

#define DELAY       1000000

typedef enum _PHASE {
    stIdle,
    stSnd1,
    stPause,
    stSnd2,
    stEnd
} PHASE;

PHASE phase;

typedef union {
    unsigned long val;

    struct {
        unsigned char b0;
        unsigned char b1;
        unsigned char b2;
        unsigned char b3;
    };
} DW;

DW pos; // current memory position
DW end; // end of sound

unsigned long wait;

int main(int argc, char** argv) {

    ADCON0 = 0b00000000; // AD converter OFF
    ADCON1 = 0b10000111; // all inputs digital

    TRISB = 0; // EEPROM address bit 0-7
    TRISC = 0; // EEPROM address bit 8-15
    TRISAbits.RA0 = 0; // EEPROM address bit 16
    TRISAbits.RA1 = 0; // EEPROM clock
    TRISAbits.RA2 = 1; // START button
    TRISAbits.RA3 = 0; // AMPLIFIER power

    T0CON = 0b11001000; // TMR0 on, 8-bit, internal clock, -, no prescaler, ---

    INTCON = 0b00100000; // enable TMR0 interrupt

    ADRL = 0;
    ADRH = 0;
    ADR16 = 0;

    AMP = 0;

    phase = stIdle;

    while (1) {

        switch (phase) {
            case stIdle:
                if (BUTTON) {
                    AMP = 1;
                    pos.val = SND1BEGIN;
                    end.val = SND1END;
                    ADRL = pos.b0;
                    ADRH = pos.b1;
                    ADR16 = pos.b2;
                    GIE = 1;
                    phase++;
                    wait = DELAY;
                }
                break;
            case stSnd1:
                // playing 1st sound
                break;
            case stPause:
                GIE = 0;
                if (BUTTON) {
                    pos.val = SND1BEGIN;
                    end.val = SND1END;
                    GIE = 1;
                    phase = stSnd1;
                    wait = DELAY;
                }
                if (!--wait) {
                    pos.val = SND2BEGIN;
                    end.val = SND2END;
                    GIE = 1;
                    phase++;
                    break;
                }
            case stSnd2:
                // playing 2nd sound
                break;
            case stEnd:
                GIE = 0;
                AMP = 0;
                ADRL = 0;
                ADRH = 0;
                ADR16 = 0;
                phase = stIdle;
                break;
        }
        CLRWDT();
    }

    return (EXIT_SUCCESS);
}

void interrupt clock() {
    TMR0 = TMR_LOAD;
    pos.val++;
    CLK = 1;
    if (pos.val < end.val) {
        ADRL = pos.b0;
        ADRH = pos.b1;
        ADR16 = pos.b2;
    } else {
        phase++;
    }
    CLK = 0;
    TMR0IF = 0;
}

Snížení spotřeby

Toto zařízení je šetrné k životnímu prostředí. Procesor jedním ze svých výstupů spíná napájecí napětí koncového zesilovače. V klidu tedy tento zesilovač není napájen, čímž je dosaženo úspory energie. Další úspory by bylo možno dosáhnout usínáním procesoru, ale na to mi nevyšly piny. A koneckonců, není třeba to hned zpočátku přehánět, to by potom už nebyl prostor pro zdokonalování.

Plošný spoj byl vytvořen ve volné verzi programu Eagle. Program byl vytvořen v prostředí MPLABX. Konvertor pro 16 bitové audio byl vytvořen v prostředí QtCreator.


Celý projekt je ke stažení zde
* Kretinos Fidelity

P15/1, K15 - prosinec 2006, naposledy aktualizováno: září 2015