Batty

From RevSpace
Revision as of 10:13, 10 October 2019 by Ralph Benadski (talk | contribs) (Schema)
Jump to navigation Jump to search

"Tiny bat detector kit" voor het weekend van de wetenschap 2019

Het was weer een groot succes, vooral jong, maar ook oud kwamen solderen, 36 kits zijn er inelkaar gezet, alle gingen werkend mee naar huis! Dit jaar heeft benadski (van Wilenzo) zich weer (net op tijd) druk gemaakt en kits ontwikkeld, geprogrammeerd en geleverd. Dit keer is met behulp van de ATtiny402 microcontroller een programmeerbare vleermuisdetector gemaakt. De microcontroller is eenvoudig te voorzien van nieuwe software met behulp van een USB serial converter en een 3k weerstand. Let op: Verwijder eerst de batterij voordat er geprogrammeerd wordt! Programmeren kan met behulp van de nieuwste versie van het Python 3 programma pyupdi. (python3 pyupdi.py -d tiny402 -c /dev/ttyUSB0 -v -f batty.hex)

Let op: De IO_PROG interface heeft pin 1 rechts zitten, de pinnen tellen vlnr dus van 5 t/m 1, net als in het schema.


Schema

Het schema hieronder is erg simpel en kan vast nog verder geoptimaliseerd worden. Het analoge deel tussen microfoon en AVR versterkt vooral de hoge tonen vanaf pak 'm beet 10kHz. Doordat het signaal uit Q1 naast een directe verbinding met de AVR ook nog via een laagdoorlaatfilter naar de AVR gaat (beide signalen komen uit op de analoge comparator pins) ontstaat een differentieel signaal op de AC van de AVR.

(Je zou ook kunnen proberen het signaal met de ADC te samplen, maar dan moet je de ADC overklokken (2.5MHz?) en niet meer dan 8 bits gebruiken. Je hebt dan waarschijnlijk niet veel meer dan 3 a 4 bits (LSB) resolutie van het signaal, maar dat is beter dan 1. In free running mode kost een conversie 13 cycles, dus haal je 192ksps bij 2.5MHz, maar of de AVR het trekt weet ik niet. Je krijgt misschien ook last van de interne capaciteit van de ADC, dat filtert dan juist weer de hoge tonen eruit. Of dit significant is heb ik niet berekend of getest.)

Zie het "Discussion" tabblad, bertrik heeft een idee voor heterodyne detectie. Dat zou een mooie software upgrade zijn, het verbetert de audio kwaliteit enorm! (AC vs. ADC input en 1 bit vs. PWM output, met daarbij nog een lineaire verschuiving in het geluid in plaats van een deling maakt het bijna een professioneel apparaatje.) Het mooie is dat bij de ATtiny402 de ADC veel meer opties heeft om op verschillende sample frequenties te draaien in continuous mode dan bij oudere AVR's. Dus dit moet kunnen werken!

Schema

Source

De source kan worden gecompileerd met AVR-GCC (nieuwste versie). Onderstaande code is snel inelkaar geflanst, je kunt er vast wat beters van maken! Als dat gelukt is, graag de code delen, je kunt het "Discussion" tabblad gebruiken om de source te posten. Ik zal de zooi binnenkort ook op github plaatsen, dan kun je vorken enzo.

/*
 * t402code.c
 *
 * Created: 01/10/2019 15:22:39
 * Author : Wilenzo
 */ 

#define F_CPU 10000000UL // 20 MHz clock speed / 2 prescaler

#include <avr/io.h>
#include <util/delay_basic.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>

//Pins
#define UAMP  1
#define BUTN  2
#define AOUT  3
#define NMIC  6
#define PMIC  7

//Port
#define FALLING       0x03
#define INPUT_DISABLE 0x04
#define PULLUPEN      0x08

//Analog Comparator
#define HYS_00        0x01 
#define HYS_10        0x03
#define HYS_25        0x05
#define HYS_50        0x07
#define AC_OFF        0x00
#define NEG_EDGE      0x20

//Global variables
volatile uint8_t cntAC = 0;  //AC call counter, for frequency division
volatile uint8_t toAC  = 1;  //time-out flag for AC, silences output when active
volatile uint8_t fDiv  = 16; //Division factor for frequency of microphone 32kHz->2kHz after reset, must never be lower than 2!
volatile uint8_t Zzz   = 0;  //Sleep trigger

//Every low to high transition a counter is increased, except when a previous time-out is detected, then only the time-out is cleared.
ISR(AC0_AC_vect){
    AC0_STATUS |= 0x01;
    if (!(toAC)) ++cntAC; else toAC = 0; 
    TCB0_CNT = 0;
}

//Timeout! Signal used to disable sound generation is setting cntAC to max.
ISR(TCB0_INT_vect){
    TCB0_INTFLAGS |= 0x01;
    toAC = 1;
    cntAC = 0xFF;
}

//Wake-up from deep sleep or button pressed. Turn on RTC if it was turned off, else change division factor of frequency
ISR(PORTA_PORT_vect){
    if (RTC_CTRLA & 0x01){
        // Already running: Change division factor, selectable are: 2, 4, 8, 16, 32, 64 and 128
        while(RTC_STATUS & 0x02);
        RTC_CNT = 0;
        fDiv <<= 1; 
        if (fDiv == 0) fDiv = 2; 
        if (fDiv > 4) TCB0_CCMP = 1000; else TCB0_CCMP = 2000; //Select 10kHz or 5kHz lower detection limit (for divisions 2 and 4).
    } else {
        // Wake from sleep
        sleep_disable();
        Zzz = 0;
        while(RTC_STATUS & 0x01);
        RTC_CTRLA |= 0x01;
    }

    //Wait a while, then wait for release, wait more: poor man's debounce function
    _delay_loop_2(0);
    _delay_loop_2(0);    
    _delay_loop_2(0);    
    while(!(PORTA_IN & 1<<BUTN));
    _delay_loop_2(0);
        
    //Turn on microphone amplifier, clear interrupt flags
    PORTA_OUTSET = (1<<UAMP);
    PORTA_INTFLAGS = 0xFF;
    RTC_INTFLAGS |= 0x02;
}

//Auto power off after 5 minutes of button inactivity
ISR(RTC_CNT_vect){
    RTC_INTFLAGS |= 0x02;
    Zzz = 1;
}

//Init I/O, timers, interrupts etc...
void init(void){
    	//Set clock to 10MHz, should work from 2.7V and up.
      _PROTECTED_WRITE(CLKCTRL_MCLKCTRLA, CLKCTRL_CLKSEL_OSC20M_gc);
      _PROTECTED_WRITE(CLKCTRL_MCLKCTRLB, CLKCTRL_PEN_bm | CLKCTRL_PDIV_2X_gc);

      //CPU: Round robin interrupt handling
      CPUINT_CTRLA = CPUINT_LVL0RR_bm;

      //Port configuration
      PORTA_DIR = (1<<AOUT)|(1<<UAMP);
      PORTA_OUT = 0;
      PORTA_PIN1CTRL = INPUT_DISABLE;
      PORTA_PIN2CTRL = PULLUPEN | FALLING;
      PORTA_PIN3CTRL = INPUT_DISABLE;
      PORTA_PIN6CTRL = INPUT_DISABLE;
      PORTA_PIN7CTRL = INPUT_DISABLE;
      
      //Analog comparator
      AC0_CTRLA    = HYS_00 | NEG_EDGE; //0mV hysteresis, negative edge interrupt, on.
      AC0_MUXCTRLA = 0;                 //Normal pos/neg IO pins are used
      AC0_INTCTRL  = 0x01;              //Negative edge interrupt on

      //Timer B (time-out check)
      TCB0_CCMP    = 1000; // 10000Hz interrupts, for ignoring lower frequencies, higher number => lower frequency
      TCB0_CTRLA   = 0x01; // Counter on, peripheral speed (=> 10MHz)
      TCB0_INTCTRL = 0x01; // Interrupt on

      //RTC, used for auto power off 
      while(RTC_STATUS & 0x08);
      RTC_CMP      = 300;  // About 5 minutes of listening time before the unit is turned off 
      while(RTC_STATUS & 0x02);
      RTC_CNT      = 0;
      RTC_INTCTRL  = 0x02; // Compare interrupt
      RTC_CLKSEL   = 0x01; // Set clock rate to 1024Hz
      while(RTC_STATUS & 0x01);
      RTC_CTRLA    = 0x51; // Turn RTC on, with 1024 prescaling factor.

      //Sleep to power down mode (deep)
      SLPCTRL_CTRLA = 0x04;

      //Enable interrupts
      sei();
}



int main(void)
{
    init();

    while (1) 
    {
        //Check sleepiness
        if (Zzz) {
            //beep
            PORTA_DIRSET = (1<<AOUT);
            for (uint16_t a=50; a<255; ++a){
              _delay_loop_2(a<<5);
              PORTA_OUTTGL = (1<<AOUT);
            }

            //Turn off all outputs, RTC too and reset RTC
            PORTA_OUT = 0;
            while(RTC_STATUS & 0x01);
            RTC_CTRLA &= 0xFE; 
            while(RTC_STATUS & 0x02);
            RTC_CNT   = 0;            
            
            //Sleep (and unsleep after wake to be sure)
            sleep_enable();
            sleep_cpu();
            sleep_disable();
        }

        //Auto turn off underflow protection
        if(RTC_CNT > 300) RTC_CNT = 300;

        //Check if output pin should be toggled or be turned off. This produces "sound".
        if (fDiv < 2) fDiv = 2;
        if (cntAC >= (fDiv>>1)){
            if (cntAC == 0xFF){
                //Off
                PORTA_DIRCLR = (1<<AOUT);
                PORTA_OUTCLR = (1<<AOUT);
            } else {
                //Toggle
                cntAC -= (fDiv>>1);
                PORTA_DIRSET = (1<<AOUT);
                PORTA_OUTTGL = (1<<AOUT);
            }
        }
    }
}