På ämnet teknologiska lifehacks kan jag redogöra för hur jag lade ner orimligt mycket tid och energi på att lösa världens löjligaste I-landsproblem
Min tvättmaskin har en fantastiskt irriterande designmiss. Den spelar en trudelutt för att tala om att den är klar, men det dröjer ett par minuter till innan man kan öppna luckan och komma åt tvätten. Resultatet är förstås att man väntar med att gå och öppna när man hör trudelutten, och så blir tvättmaskinen bortglömd.
Skruvade upp tvättmaskinen för att kolla vilka möjligheter som fanns. Det smidigaste hade förstås varit att gå på summerns anslutningar, och detektera spänning på dem. Färdig-trudelutten är det klart längsta ljudet som spelas upp, så hade varit enkelt att filtrera bort de kortare blippen när man manövrerar vred/knappar.
Det visade sig dock att elektroniken var ingjuten i epoxy, och att det därmed var omöjligt att ansluta sig elektriskt utan att ta bort epoxyn, och därmed löpa stor risk att kvadda elektroniken.
Spånade på en lösning ett tag, och det landade till sist i att försöka analysera ljud. Hittade efter många om och men kod för homodyn filtrering (läs: väldigt smalbandigt filter) som en Arduino orkar dra, och knåpade ihop ett program kring det.
Spelade in och kollade vilka toner/frekvenser färdigtrudelutten innehöll. Tunade in filtret på en frekvens som förekommer under ganska stor del av denna. Detekteras frekvensen i minst 140 samples på mindre än 12 sekunder, spelas en signal upp efter tre minuters fördröjning.
Hittills har det funkat 100%. Varken "falsklarm" eller missade färdigtrudelutter
Tyvärr gör sig inte projektet särskilt bra på bild. Det är inte så mycket att se, bara en liten svart 3D-printad låda med lite elektronik i:
https://i.imgur.com/1EeGgCn.jpg
Debugging
https://i.imgur.com/BSQp4J6.jpg
Innanmäte
https://i.imgur.com/jGg9oXU.jpg
På plats
Hårdvaran består av en Arduino Pro Mini, micmodul, summer, samt nätdel. I och med det ingjutna kretskortet fanns ju ingenstans att hämta 5VDC heller.
Ja, och om ni lovar att inte mobba mig för min noobkod...
/*
* homodyne.ino: Homodyne detection of a 1 kHz signal.
*
* This program continuously samples analog input 0 and uses an homodyne
* detection scheme to identify a signal at 1 kHz (+/- 24 Hz @ -3dB).
*
* The analog-to-digital converter is set to "free running mode" and
* takes one sample every 104 us. The samples are multiplied by two
* generated signals at 1 kHz (the "local oscillator"), in quadrature to
* one another. The products are then low-pass filtered with a time
* constant of 64 sample periods (6.656 ms), which gives the (I, Q)
* signals with a 24 Hz bandwidth. Finally, the signal power is computed
* as I^2 + Q^2.
*
* The program is intended for an Arduino Uno, and is likely to work on
* any AVR-based Arduino having an ADC and clocked at 16 MHz.
*
* For a detailed explanation, see
* http://arduino.stackexchange.com/a/21175
*
* Copyright (c) 2016 Edgar Bonet Orozco.
* Released under the terms of the MIT license:
* https://opensource.org/licenses/MIT
*/
#include <util/atomic.h>
// Analog input to use, should be between 0 and 5.
const uint8_t analog_in = 0;
// The frequency we want to detect, in Hz.
const float SIGNAL_FREQ = 2365.0;
// Timing bits.
const float SAMPLING_FREQ = F_CPU / (128 * 13.0); // 9.615 kHz
const long PHASE_INC = round(SIGNAL_FREQ / SAMPLING_FREQ * (1L << 16));
const int LOG_TAU = 6; // tau = 64 / SAMPLING_FREQ = 6.656 ms
// Variabel för avläsning av micingången
unsigned int POWER_READING = 0;
//Sampleräkning
unsigned int SAMPLE_COUNTER = 0; //Räknar antalet "godkända" samples över power 50 i en puls.
const int SAMPLE_POWER_LIMIT = 50; //Gränsvärde för hur stark en sample måste vara för att godkännas
const int SAMPLE_NUM = 140; //Antal godkända samples för att det ska pipas i summern
bool TIMER_RUNNING = 0; //Flagga resettimer av/på
static unsigned long START_MILLIS; //starttid för resettimer
static unsigned long CURR_MILLIS; //nuvarande tid för resettimer
//Definiera buzzerpin
const int buzzerpin = 3;
// Set the ADC to free running mode.
static void configure_adc()
{
ADMUX = _BV(REFS0) // ref = AVCC
| _BV(ADLAR) // left adjust result
| analog_in; // input channel
ADCSRB = 0; // free running mode
ADCSRA = _BV(ADEN) // enable
| _BV(ADSC) // start conversion
| _BV(ADATE) // auto trigger enable
| _BV(ADIF) // clear interrupt flag
| _BV(ADIE) // interrupt enable
| 7; // prescaler = 128
}
// Demodulated (I, Q) amplitudes.
volatile int16_t signal_I, signal_Q;
// Interrupt handler called each time an ADC reading is ready.
ISR(ADC_vect)
{
// Read the ADC and convert to signed number.
int8_t sample = ADCH - 128;
// Update the phase of the local oscillator.
static uint16_t phase;
phase += PHASE_INC;
// Multiply the sample by square waves in quadrature.
int8_t x = sample;
if (((phase>>8) + 0x00) & 0x80) x = -1 - x;
int8_t y = sample;
if (((phase>>8) + 0x40) & 0x80) y = -1 - y;
// First order low-pass filter.
signal_I += x - (signal_I >> LOG_TAU);
signal_Q += y - (signal_Q >> LOG_TAU);
}
/* Return a power reading. */
static uint16_t get_power_reading()
{
int16_t I, Q;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
I = signal_I;
Q = signal_Q;
}
return sq((int8_t)(I >> LOG_TAU)) + sq((int8_t)(Q >> LOG_TAU));
}
void setup()
{
configure_adc();
Serial.begin(9600);
pinMode (buzzerpin, OUTPUT);
}
void loop()
{
//Kör sample > 50 power-räknarfunktionen
samplecounter();
//Kör samplecounterreset() om SAMPLE_COUNTER > 0
if (SAMPLE_COUNTER > 0) {
samplecounterreset();
}
//Pip i summern efter 3 min (3*60*1000 = 180000 ms) om värdet SAMPLE_COUNTER är större än SAMPLE_NUM
if (SAMPLE_COUNTER > SAMPLE_NUM) {
SAMPLE_COUNTER = 0;
TIMER_RUNNING = 0;
delay(180000);
buzzersignal();
buzzersignal();
buzzersignal();
delay(15000);
buzzersignal();
buzzersignal();
buzzersignal();
delay(15000);
buzzersignal();
buzzersignal();
buzzersignal();
delay(15000);
buzzersignal();
buzzersignal();
buzzersignal();
}
/*
// Printa debugdata
static const uint16_t print_period = 50;
static uint16_t last_print;
uint16_t now = millis();
if (now - last_print >= print_period) {
Serial.print(POWER_READING);
Serial.print(" - ");
Serial.print(millis());
Serial.print(" - ");
Serial.print(SAMPLE_COUNTER);
Serial.print(" - ");
Serial.print(TIMER_RUNNING);
Serial.print(" - ");
Serial.print(START_MILLIS);
Serial.print("\n");
last_print += print_period;
}
*/
}
void samplecounter()
{
static const uint16_t READ_PERIOD = 10;
static uint16_t LAST_READ;
uint16_t NOW = millis();
if (NOW - LAST_READ >= READ_PERIOD) {
POWER_READING = get_power_reading(); //Gör power reading...
if (POWER_READING > SAMPLE_POWER_LIMIT) { //...om den är över SAMPLE_POWER_LIMIT...
SAMPLE_COUNTER = SAMPLE_COUNTER + 1; //..knocka upp sampleräknaren med 1.
}
LAST_READ = LAST_READ + READ_PERIOD;
}
}
void samplecounterreset()
{
if (TIMER_RUNNING == 0) {
TIMER_RUNNING = 1;
START_MILLIS = millis();
}
CURR_MILLIS = millis();
if (CURR_MILLIS - START_MILLIS >= 12000 && TIMER_RUNNING == 1) {
SAMPLE_COUNTER = 0;
TIMER_RUNNING = 0;
}
}
void buzzersignal() {
int count = 2000;
while(count > 0) {
buzzerfrekvens();
count = count -1;
}
delay(1000);
}
void buzzerfrekvens()
{
digitalWrite(buzzerpin, HIGH);
delayMicroseconds(200);
digitalWrite(buzzerpin, LOW);
delayMicroseconds(200);
}
Dold text