Pisałem niedawno, że pracuję nad tanią pikawką. Vario pracuje na mikrokontrolerze Attiny85 i czujniku ciśnienia BMP180. Oprócz tych modułow do pracy wymagany jest jakiś brzęczyk (może być głośniczek ze starego telefonu. Ja zastosowałem przetwornik piezoelektryczny) i jakieś zasilanie. Attiny pracuję w zakresie 2.7-5.5V (użyłem baterii ze starej komórki). Układ w czasie pracy pobiera ok. 8mA, a w czasie wydawania dźwięku ok. 20mA. Aby uniknąć projektowania płytek, trawienia itp. użyłem gotowych modułow.
W modułach należy połączyć:
Attiny85 ----- BMP180
P0 ----------- SDA
P2 ----------- SCL
5V ----------- Vcc
GND ---------- GND
Dodatkowo (nie jest to konieczne do działania varia) zastosowałem kontrolę poziomu baterii, co wymaga użycia 2 rezystorów ( u mnie 220kom i 1 Mom, ale mogą być o innych wartościach, tylko trzeba stosownie zmodyfikować kod. Przy spadku napięcia baterii poniżej 3.2V dioda LED na płytce mikrokontrolera miga co 2 sekundy.
Podczas testów podłączałem dodatkowo wyświetlacz LCD 2x16 znaków z interfejsem I2C. Jak ktoś się chce pobawić to może sobie dokupić - obsługa wyświetlacza jest w kodzie.
Kod programu napisałem w Arduino. Moduł mikrokontrolera wyposażony jest w port USB i do programowania nic więcej nie jest wymagane. Moduł występuję w 2 wersjach portu USB - normalny wtyk, tzn USB A i mini USB A. Nie ma znaczenia który użyjesz.
Koszty:
moduł Attiny85 - 1.15$
moduł BMP180 - 1.20$
Wszystko z darmową wysyłką. Oczywiście można kupić na Allegro - będzie szybciej i kilka razy drożej.
Brzęczyka i akumulatorka nie liczę, bo takie śmieci każdy w domu ma.
Po kolei co trzeba zainstalować:
1. Ściągamy i instalujemy Arduino ( pracowałem na 1.6.5).
2. Doinstalowujemy płytę Attiny85 wg. instrukcji na stronie: http://digistump.com/wiki/digispark/tutorials/connecting
3. Doinstalowujemy biblioteki WireM i tinyBMP085.
4. Kompilujemy i wgrywamy.
Działanie:
Po włączeniu przez 5 sek nic się nie dzieje - Attiny czeka na ewentualne wgrywanie programu. Potem przy poprawnej komunikacji z BMP180 następuje 5 mrugnięć LED i vario zaczyna pracować. W razie niewykrycia czujnika ciśnienia dioda LED świeci się światłem ciągłym i vario nie pracuje.
UWAGA:
Jeśli ma być wykorzystywany monitoring baterii niezbędne jest zablokowanie funkcji RESET na pinie P5. Można tego dokonać tylko przy pomocy programatora, nie można tego cofnąć i traci się możliwość programowania przez programator. Tzn. można, ale trzeba mieć programator HV.
Tutaj próbka jak vario gra:
https://www.youtube.com/watch?v=PVbd9z2-FF8&feature=youtu.be
na wyświetlaczu ukazuje się indeks, częstotliwość dzwięku, długość trwania w ms, czas przerwy w ms. Te same dźwięki są zaprogramowane w vario. Warto "pod siebie" ustawić sobie progi załączania poszczególnych dźwięków. Jest to na tyle czytelne w kodzie, że nie powinno nikomu sprawiać problemu. Progi załączenia podane są w decymetrach. Np. kod
Kod: Zaznacz cały
if(dh > 3 ) { PLAY_CLIMB(0); return; }
Zdjęcia części i gotowego urządzenia - jak widać lutowanie nie jest moją najmocniejszą stroną
http://r0bby.cba.pl/vario.htm
I kod:
Kod: Zaznacz cały
#include <avr/pgmspace.h>
#include <avr/sleep.h>
#include <avr\power.h>
#include <util/delay.h>
#include <TinyWireM.h>
#include <tinyBMP085.h>
// //CTC, toggle OC1B, clk/16, CLK/PSC=62.5kHz, 255=122.5Hz, 31=1008Hz
#define PLAYER_START TCNT1=0; TCCR1 = _BV(CTC1) | _BV(CS12) | _BV(CS10)
#define PLAYER_STOP TCCR1=0
// #define LCD_DEBUG
#ifdef LCD_DEBUG
#include <LiquidCrystal_I2C.h>
#endif
#define BATTERY_MONITOR
#define BATTERY_LOW 56 // Uadc=U*Radc/(Radc+R), Aref=2.56V, R=1Mom, Radc=220kom, 3.2V=low => ADC=57 (real Up5=0.692V, ADC=69, Vcc=3.952V, dzielnik=0.175
#define SPEAKER 4
#define LED 1
#define PLAY_CLIMB(X) beep_pause(pgm_read_word_near(vario_sound_climb + 3*(X)), pgm_read_word_near(vario_sound_climb + 3*(X)+1), pgm_read_word_near(vario_sound_climb + 3*(X)+2))
#define PLAY_SINK(X) beep_pause(pgm_read_word_near(vario_sound_sink + 3*(X)), pgm_read_word_near(vario_sound_sink + 3*(X)+1), pgm_read_word_near(vario_sound_sink + 3*(X)+2))
const PROGMEM uint16_t vario_sound_climb[]={ //frequency (Hz), duration (ms), pause (ms),
564, 540, 320, // idx 0
701, 438, 242, // idx 1
788, 368, 189, // idx 2
846, 312, 155,
894, 259, 134,
927, 219, 115,
955, 176, 95, // ....
985, 138, 75,
1008, 110, 55,
1037, 81, 37,
1070, 60, 30,
1106, 46, 28,
1141, 36, 28 // idx 12
};
const PROGMEM uint16_t vario_sound_sink[]={
// 415, 600, 500,
344, 600, 500,
283, 600, 500,
/* 234, 600, 500,
198, 600, 500,
175, 600, 500,
159, 600, 500,
146, 600, 500,
136, 600, 500,
127, 600, 500 */
};
volatile uint16_t sound_duration, pause_duration;
volatile uint8_t sek_1;
#ifdef LCD_DEBUG
LiquidCrystal_I2C lcd(0x27, 16, 2); // adres 0x27, 2x16 znakow
#endif
tinyBMP085 bmp;
void setup()
{
pinMode(SPEAKER, OUTPUT);
pinMode(LED, OUTPUT);
#ifdef LCD_DEBUG
lcd.init();
lcd.backlight();
#endif
if (!bmp.begin(BMP085_STANDARD)){
PORTB |= _BV(PB1); // LED ON
while(1);
}
else
for(uint8_t i=0; i<10; i++)
{
PORTB ^= _BV(PB1); // LED toggle
_delay_ms(500);
}
PORTB &= ~_BV(PB1); // LED OFF
//timer 1 - PLAYER
GTCCR = _BV(COM1B0); // toggle P4
//timer 1 - koniec
//timer 0
TCCR0A = _BV(WGM01); // CTC
TCCR0B = _BV(CS00) | _BV(CS01); //prescaler 64,
OCR0A = 156; // 100Hz
TIMSK = _BV(OCIE0A);
//timer 0 - koniec
#ifdef BATTERY_MONITOR
// ADC
ADMUX = _BV(ADLAR) | _BV(REFS1)| _BV(REFS2); // vREF=2.56V, P5 analog input
ADCSRA = _BV(ADEN) | _BV(ADPS2); //ADC enable, PSC=16
// ADC - koniec
#endif
sei();
}
void loop()
{
static signed long alt=0, last_avr_alt=0, avr_alt=0;
static uint8_t loop_counter=0;
#ifdef LCD_DEBUG
static byte flipflop=0;
#endif
while(sek_1==0)
{
alt += bmp.readAltitudeSTDdm2();
loop_counter++;
}
sek_1 = 0;
last_avr_alt = avr_alt;
avr_alt = alt / loop_counter;
#ifdef BATTERY_MONITOR
ADCSRA |= _BV(ADSC); // start conversion
while (ADCSRA & _BV(ADSC) )
{}
if( ADCH < BATTERY_LOW)
PORTB ^= _BV(PB1);
else
PORTB &= ~_BV(PB1); // LED OFF
#endif
#ifdef LCD_DEBUG
lcd.clear();
lcd.setCursor(0, 0);
if(flipflop==0){
flipflop=1;
lcd.print("H=");
// lcd.print("U=");
// lcd.print(ADCH, DEC);
lcd.print(avr_alt/10);
}else{
flipflop=0;
lcd.print("P=");
lcd.print(bmp.readPressure()/100);
}
lcd.setCursor(9, 0);
lcd.print("C=");
lcd.print(loop_counter, DEC);
#endif
alt = 0;
loop_counter = 0;
H2Snd( avr_alt - last_avr_alt );
}
void H2Snd(int dh) // tabelka zamiany roznicy wysokości na index
{
#ifdef LCD_DEBUG
lcd.setCursor(0, 1);
lcd.print("DH="); //spacje zeby zmazac poprzednia wartosc
lcd.print(dh);
#endif
/* if(dh < -90 ) { PLAY_SINK(7); return; }
if(dh < -80 ) { PLAY_SINK(6); return; }
if(dh < -70 ) { PLAY_SINK(5); return; }
if(dh < -60 ) { PLAY_SINK(4); return; }
if(dh < -50 ) { PLAY_SINK(3); return; }
if(dh < -40 ) { PLAY_SINK(2); return; } */
if(dh < -35 ) { PLAY_SINK(1); return; }
if(dh < -25 ) { PLAY_SINK(0); return; }
if(dh > 120 ) { PLAY_CLIMB(12); return; }
if(dh > 100 ) { PLAY_CLIMB(11); return; }
if(dh > 88 ) { PLAY_CLIMB(10); return; }
if(dh > 75 ) { PLAY_CLIMB(9); return; }
if(dh > 63 ) { PLAY_CLIMB(8); return; }
if(dh > 52 ) { PLAY_CLIMB(7); return; }
if(dh > 42 ) { PLAY_CLIMB(6); return; }
if(dh > 33 ) { PLAY_CLIMB(5); return; }
if(dh > 25 ) { PLAY_CLIMB(4); return; }
if(dh > 18 ) { PLAY_CLIMB(3); return; }
if(dh > 12 ) { PLAY_CLIMB(2); return; }
if(dh > 7 ) { PLAY_CLIMB(1); return; }
if(dh > 3 ) { PLAY_CLIMB(0); return; }
sound_duration=0;
pause_duration=0;
#ifdef LCD_DEBUG
lcd.setCursor(9, 1);
lcd.print("f= ");
#endif
}
void beep_pause(uint16_t frequency, uint16_t duration, uint16_t pause)
{
#ifdef LCD_DEBUG
lcd.setCursor(9, 1);
lcd.print("f=");
lcd.print(frequency);
#endif
OCR1B = F_CPU/(frequency << 5); // PSC=16, F_CPU/(f*2*PRESCALER)
OCR1C = F_CPU/(frequency << 5); // PSC=16, F_CPU/(f*2*PRESCALER)
sound_duration = (duration+5)/10;//dlugość trwania w ms * f przerwania/1000 ms
pause_duration = (pause+5)/10;
}
ISR(TIMER0_COMPA_vect)
{
static uint16_t _sound_duration=0, _pause=0, counter=0;
if(_sound_duration>0)
_sound_duration--;
if(_sound_duration==0){
PLAYER_STOP;
if(_pause>0)
_pause--;
if((_pause==0) && (sound_duration>0)){
_sound_duration=sound_duration;
_pause=pause_duration;
PLAYER_START;
}
}
if(counter++ == 100){
counter=0;
sek_1=1;
}
}