445 lines
14 KiB
C
445 lines
14 KiB
C
/* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
/* this firmware is for a cat repeller
|
|
* it uses:
|
|
* - andruino nano a micro-controller board
|
|
* - HC-SR501 PIR motion detector
|
|
* - E18-D80NK-N adjustable infrared sensor switch
|
|
* - piezo buzzer
|
|
*/
|
|
#include <stdint.h> // Standard Integer Types
|
|
#include <stdio.h> // Standard IO facilities
|
|
#include <stdlib.h> // General utilities
|
|
#include <stdbool.h> // Boolean
|
|
#include <string.h> // Strings
|
|
|
|
#include <avr/io.h> // AVR device-specific IO definitions
|
|
#include <util/delay.h> // Convenience functions for busy-wait delay loops
|
|
#include <avr/interrupt.h> // Interrupts
|
|
#include <avr/wdt.h> // Watchdog timer handling
|
|
#include <avr/pgmspace.h> // Program Space Utilities
|
|
#include <avr/sleep.h> // Power Management and Sleep Modes
|
|
|
|
#include "uart.h" // basic UART functions
|
|
#include "main.h" // main definitions
|
|
|
|
/* help strings */
|
|
const char help_00[] PROGMEM = "commands:\n";
|
|
const char help_01[] PROGMEM = "\thelp display this help\n";
|
|
const char help_02[] PROGMEM = "\tbeep T F beep for T ms at F Hz\n";
|
|
PGM_P const help_table[] PROGMEM = {
|
|
help_00,
|
|
help_01,
|
|
help_02
|
|
};
|
|
|
|
/* global variables */
|
|
char uart_in[25]; // user input from USART
|
|
volatile uint16_t ms = 0; // to count down the ms
|
|
|
|
/* flags, set in the interrupts and handled in the main program */
|
|
volatile bool uart_flag = false; // UART input data is available
|
|
volatile bool command_flag = false; // a command has been input
|
|
volatile bool countdown_flag = false; // set when the ms countdown reached 0
|
|
volatile bool fridge_flag = false; // when the fridge switch changed
|
|
volatile bool motion_flag = false; // when the PIR detected motion
|
|
volatile bool barrier_flag = false; // when the light barrier got cut
|
|
|
|
/* different melodies to play
|
|
* pair of time in ms and tone in Hz
|
|
* 0 ms to end
|
|
* 0xffff ms to restart/loop
|
|
*/
|
|
#ifdef DEBUG
|
|
const uint16_t MELODY_REPEL[] PROGMEM = {100,4000,100,0,100,3000,100,0,100,3000,100,0,100,3000,100,0,100,3000,100,0,500,5000,500,3000,500,5000,500,3000,500,5000,500,3000,0xffff}; // try to scare the cat
|
|
const uint16_t MELODY_WARN[] PROGMEM = {100,4000,3000,0,500,4000,500,5000,500,4000,500,5000,500,4000,500,5000,0xffff}; // warn human opened fridge (anyone if left open)
|
|
#else
|
|
const uint16_t MELODY_REPEL[] PROGMEM = {100,4000,100,0,100,23000,100,0,100,23000,100,0,100,23000,100,0,100,23000,100,0,500,25000,500,23000,500,25000,500,23000,500,25000,500,23000,0xffff}; // try to scare the cat
|
|
const uint16_t MELODY_WARN[] PROGMEM = {100,4000,30000,0,500,4000,500,5000,500,4000,500,5000,500,4000,500,5000,0xffff}; // warn human opened fridge (anyone if left open)
|
|
#endif
|
|
const uint16_t MELODY_ALARM[] PROGMEM = {500,5000,500,4000,0xffff,0xffff}; // sound alarm because cat opened the frigde
|
|
const uint16_t* melody = NULL; // the current melody to play
|
|
uint8_t melody_i = 0; // the current position in melody
|
|
|
|
/* switch off LED */
|
|
void led_off(void)
|
|
{
|
|
LED_PORT &= ~(1<<LED_IO); // remove power to LED
|
|
}
|
|
|
|
/* switch on LED */
|
|
void led_on(void)
|
|
{
|
|
LED_PORT |= (1<<LED_IO); // provide power to LED
|
|
}
|
|
|
|
/* toggle LED */
|
|
void led_toggle(void)
|
|
{
|
|
LED_PIN |= (1<<LED_IO);
|
|
}
|
|
|
|
/* disable watchdog when booting */
|
|
void wdt_init(void) __attribute__((naked)) __attribute__((section(".init3")));
|
|
void wdt_init(void)
|
|
{
|
|
MCUSR = 0;
|
|
wdt_disable();
|
|
|
|
return;
|
|
}
|
|
|
|
void help(void)
|
|
{
|
|
char* str;
|
|
for (uint8_t i=0; i<sizeof(help_table)/sizeof(PGM_P); i++) { /* display all help lines */
|
|
str = malloc(strlen_PF((uint_farptr_t)pgm_read_word(&(help_table[i]))));
|
|
strcpy_PF(str, (uint_farptr_t)pgm_read_word(&(help_table[i])));
|
|
printf(str);
|
|
free(str);
|
|
}
|
|
}
|
|
|
|
/* save the UART input into a string */
|
|
ISR(USART_RX_vect) { /* UART receive interrupt */
|
|
static uint8_t uart_in_i = 0; // UART input index
|
|
char c = getchar(); // current character
|
|
if ('\n'==c || '\r'==c) {
|
|
if (uart_in_i>0) { // end of line (ignore empty lines)
|
|
command_flag = true; // notify a command is ready
|
|
uart_in_i = 0; // reset input
|
|
}
|
|
} else if (uart_in_i<sizeof(uart_in)/sizeof(*uart_in)-1) { // save input only if there is space left
|
|
uart_in[uart_in_i] = c; // save input
|
|
uart_in_i++; // prepare for next character
|
|
uart_in[uart_in_i] = 0; // always end the string
|
|
uart_flag = true; // notify new character is there
|
|
}
|
|
}
|
|
|
|
/* timer 0 OCR0A match interrupt
|
|
* 1 ms has passed
|
|
*/
|
|
ISR(TIMER0_COMPA_vect)
|
|
{
|
|
if (ms>0) {
|
|
ms--; // count down
|
|
}
|
|
if (0==ms) { // always verify, also when not counting down
|
|
countdown_flag = true; // warn count down has finished
|
|
}
|
|
}
|
|
|
|
/* PCI2 Interrupt Vector for PCINT[23:16]/PORTD */
|
|
ISR(PCINT2_vect)
|
|
{
|
|
static uint8_t fridge_state = 0;
|
|
static uint8_t motion_state = 0;
|
|
static uint8_t barrier_state = 0;
|
|
|
|
if ((fridge_state&(1<<FRIDGE_IO))!=(FRIDGE_PIN&(1<<FRIDGE_IO))) { // fridge activity
|
|
fridge_flag = true;
|
|
fridge_state = FRIDGE_PIN;
|
|
}
|
|
if ((motion_state&(1<<MOTION_IO))!=(MOTION_PIN&(1<<MOTION_IO))) { // motion activity
|
|
motion_flag = true;
|
|
motion_state = MOTION_PIN;
|
|
}
|
|
if ((barrier_state&(1<<BARRIER_IO))!=(BARRIER_PIN&(1<<BARRIER_IO))) { // light barrier activity
|
|
barrier_flag = true;
|
|
barrier_state = BARRIER_PIN;
|
|
}
|
|
}
|
|
|
|
/* initialize GPIO */
|
|
void io_init(void)
|
|
{
|
|
/* use UART as terminal */
|
|
uart_init();
|
|
stdout = &uart_output;
|
|
stdin = &uart_input;
|
|
|
|
/* gpio */
|
|
/* LED */
|
|
LED_DDR |= (1<<LED_IO); // LED is driven by pin (set as output)
|
|
led_off();
|
|
/* piezo, connected on OC1A and OC1B */
|
|
DDRB |= ((1<<PB1)|(1<<PB2)); // set as output
|
|
PORTB &= ~((1<<PB1)|(1<<PB2)); // set to low
|
|
/* fridge switch */
|
|
FRIDGE_DDR &= ~(1<<FRIDGE_IO); // set as input
|
|
FRIDGE_PORT |= (1<<FRIDGE_IO); // enable pull-up
|
|
PCICR |= (1<<PCIE2); // enable interrupt on pin change for PORTD/PCINT[23:16]
|
|
PCMSK2 |= (1<<FRIDGE_PCINT); // enable interrupt on pin change for fridge switch
|
|
/* PIR motion detector */
|
|
MOTION_DDR &= ~(1<<MOTION_IO); // set as input
|
|
MOTION_PORT &= ~(1<<MOTION_IO); // disable pull-up
|
|
PCICR |= (1<<PCIE2); // enable interrupt on pin change for PORTD/PCINT[23:16]
|
|
PCMSK2 |= (1<<MOTION_PCINT); // enable interrupt on pin change for motion detector
|
|
/* light barrier */
|
|
LIGHT_DDR |= (1<<LIGHT_IO); // set as output
|
|
LIGHT_PORT |= (1<<LIGHT_IO); // switch barrier off
|
|
BARRIER_DDR &= ~(1<<BARRIER_IO); // set as input
|
|
BARRIER_PORT |= (1<<BARRIER_IO); // enable pull-up
|
|
PCICR |= (1<<PCIE2); // enable interrupt on pin change for PORTD/PCINT[23:16]
|
|
PCMSK2 |= (1<<BARRIER_PCINT); // enable interrupt on pin change for light barrier
|
|
|
|
/* use PWM to driver piezo-element
|
|
* use timer 1. with 16 bits it is the most precise one
|
|
* fast PWM fits for high frequency PWM
|
|
* use non-inverting and inverting outputs to drive the piezo element at 2x5V (the edge is the important part)
|
|
*/
|
|
TCCR1B |= (1<<WGM13)|(1<<WGM12); // set mode 14, fast PWM, TOP=ICR1
|
|
TCCR1A |= (1<<WGM11)|(0<<WGM10); // set mode 14, fast PWM, TOP=ICR1
|
|
TCCR1A |= (1<<COM1A1)|(0<<COM1A0); // set OC1A as non-inverting mode
|
|
TCCR1A |= (1<<COM1B1)|(1<<COM1B0); // set OC1B as inverting mode
|
|
|
|
/* user timer 0 to count milliseconds, for non-blocking waiting */
|
|
TCCR0B |= (0<<WGM02); // set mode 2, Clear Timer on Compare Match
|
|
TCCR0A |= (1<<WGM01)|(0<<WGM00); // set mode 2, Clear Timer on Compare Match
|
|
OCR0A = 62-1; // corresponds to 0.992 ms when using prescale 256
|
|
TIMSK0 |= (1<<OCIE0A); // enable interrupt when OCRA0A is matched
|
|
|
|
sei(); // enable interrupts
|
|
}
|
|
|
|
/* set tone of piezo at <freq> Hz */
|
|
void set_tone (uint32_t freq) {
|
|
// use prescale 1 to allow high frequencies and precision
|
|
const uint16_t PRESCALE = 1;
|
|
const uint8_t PRESCALE_CS = 1;
|
|
// minimum frequency (formula 16.9.4 in ATmega328P datasheet)
|
|
const uint32_t FREQ_MIN = (F_CPU/(PRESCALE*(1+(1UL<<16))));
|
|
// maximum frequency (min OCR1A is 0x0003)
|
|
const uint32_t FREQ_MAX = (F_CPU/(PRESCALE*(1+3)));
|
|
TCCR1B &= ~((1<<CS12)|(1<<CS11)|(1<<CS10)); // stop clock
|
|
TCNT1 = 0; // reset timer
|
|
if (0 == freq) { // switch of PWM
|
|
// clock already stopped
|
|
// PB1 and PB2 can not bet set low since OC1A and OC1B are used
|
|
} else if (freq < FREQ_MIN) {
|
|
printf("frequency to low. minimum: %lu Hz\n",FREQ_MIN);
|
|
} else if (freq > FREQ_MAX) {
|
|
printf("frequency to high. maximum: %lu Hz\n",FREQ_MAX);
|
|
} else {
|
|
ICR1 = (F_CPU/(PRESCALE*freq))-1; // set frequency
|
|
OCR1A = ICR1/2; // set 50% duty cycle
|
|
OCR1B = ICR1/2; // set 50% duty cycle
|
|
TCCR1B |= (PRESCALE_CS<<CS10); // set clock source
|
|
#ifdef DEBUG
|
|
uint32_t freq_real = F_CPU/(PRESCALE*(1+ICR1));
|
|
if (freq_real!=freq) {
|
|
printf("set tone at %lu Hz instead of %lu Hz\n",freq_real,freq);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* use pizeo to beep at <freq> Hz for <time> ms */
|
|
void beep(uint16_t time, uint32_t freq)
|
|
{
|
|
set_tone(freq); // set frequency of tone
|
|
if (0==time) {
|
|
TCCR0B &= ~((1<<CS02)|(1<<CS01)|(1<<CS00)); // stop timer 0
|
|
} else {
|
|
TCCR0B |= (1<<CS02)|(0<<CS01)|(0<<CS00); // ensure timer 0 is on, set to prescale 256
|
|
}
|
|
TCNT0 = 0; // reset timer 0
|
|
ms = time; // start countdown
|
|
}
|
|
|
|
/* load a melody
|
|
* set to NULL to stop playing
|
|
* return if it succeeded loading
|
|
*/
|
|
bool load_melody(const uint16_t* melody_ptr)
|
|
{
|
|
if (NULL!=melody_ptr) { // verify if melody is malformed before loading it
|
|
if (0xffff==pgm_read_word(&(melody_ptr[0]))) { // the first time shouldn't be a loop
|
|
return false;
|
|
}
|
|
}
|
|
melody = melody_ptr; // set melody
|
|
melody_i = 0; // play melody from start
|
|
if (NULL==melody) {
|
|
beep(0,0); // stop beeping
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* play or continue the melody
|
|
* return true if tone is played
|
|
* return false if no melody is loaded of end of melody
|
|
*/
|
|
bool play_melody()
|
|
{
|
|
if (NULL==melody) { // verify a melody is loaded
|
|
return false;
|
|
}
|
|
uint16_t time = pgm_read_word(&(melody[melody_i])); // get time
|
|
if (0xffff==time) { // special time value to loop
|
|
melody_i = 0; // play melody from start
|
|
time = pgm_read_word(&(melody[melody_i])); // reread first time
|
|
} else if (0==time) { // special time value to end
|
|
return false;
|
|
}
|
|
melody_i++; // go to tone
|
|
uint16_t tone = pgm_read_word(&(melody[melody_i])); // read tone
|
|
melody_i++; // prepare to read next time value
|
|
beep(time,tone); // play tone
|
|
|
|
return true;
|
|
}
|
|
|
|
/* process command */
|
|
void command_action(char* command)
|
|
{
|
|
/* split command */
|
|
const char* delimiter = " ";
|
|
char* word = strtok(command,delimiter);
|
|
if (!word) {
|
|
goto error;
|
|
}
|
|
/* parse command */
|
|
if (0==strcmp(word,"help")) {
|
|
help();
|
|
} else if (0==strcmp(word,"beep")) {
|
|
/* expecting time */
|
|
word = strtok(NULL,delimiter);
|
|
if (!word) {
|
|
goto error;
|
|
}
|
|
uint16_t time = atol(word);
|
|
/* expecting frequency */
|
|
word = strtok(NULL,delimiter);
|
|
if (!word) {
|
|
goto error;
|
|
}
|
|
uint32_t freq = atol(word);
|
|
beep(time,freq);
|
|
} else {
|
|
goto error;
|
|
}
|
|
|
|
return;
|
|
error:
|
|
printf(PSTR("command not recognized\n"));
|
|
return;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
uint8_t uart_out_i = 0; // how many UART input characters have been processed
|
|
|
|
io_init(); // initialize IOs
|
|
|
|
printf(PSTR("welcome on the cat repeller\n"));
|
|
fridge_flag = true; // verify fridge swtich pin state when booting
|
|
motion_flag = true; // verify motion pin state when booting
|
|
while (true) { // endless loop for micro-controller
|
|
/* process flags */
|
|
while (fridge_flag) { // fridge switch activity
|
|
if (FRIDGE_PIN&(1<<FRIDGE_IO)) { // fridge open
|
|
#ifdef DEBUG
|
|
printf(PSTR("fridge opened\n"));
|
|
#endif
|
|
if (!(LIGHT_PIN&(1<<LIGHT_IO)) && !(BARRIER_PIN&(1<<BARRIER_IO))) { // cat in front of barrier
|
|
load_melody(MELODY_ALARM); // load sound
|
|
} else {
|
|
load_melody(MELODY_WARN); // load sound
|
|
}
|
|
play_melody(); // start playing
|
|
LIGHT_PORT |= (1<<LIGHT_IO); // switch off light barrier
|
|
} else { // fridge close
|
|
#ifdef DEBUG
|
|
printf(PSTR("fridge closed\n"));
|
|
#endif
|
|
load_melody(NULL); // stop sound
|
|
}
|
|
fridge_flag = false;
|
|
}
|
|
while (motion_flag) { // PIR motion detector activity
|
|
if (!(FRIDGE_PIN&(1<<FRIDGE_IO))) { // only react if fridge closed
|
|
if (MOTION_PIN&(1<<MOTION_IO)) { // motion detected
|
|
led_on();
|
|
#ifdef DEBUG
|
|
printf(PSTR("motion detected\n"));
|
|
#endif
|
|
LIGHT_PORT &= ~(1<<LIGHT_IO); // switch on light barrier
|
|
} else {
|
|
led_off();
|
|
#ifdef DEBUG
|
|
printf(PSTR("motion stopped\n"));
|
|
#endif
|
|
LIGHT_PORT |= (1<<LIGHT_IO); // switch off light barrier
|
|
load_melody(NULL);
|
|
}
|
|
}
|
|
motion_flag = false;
|
|
}
|
|
while (barrier_flag) {
|
|
if (!(FRIDGE_PIN&(1<<FRIDGE_IO)) && !(LIGHT_PIN&(1<<LIGHT_IO))) { // only react if fridge closed and light activated
|
|
|
|
if (BARRIER_PIN&(1<<BARRIER_IO)) {
|
|
#ifdef DEBUG
|
|
printf(PSTR("nothing in front of barrier\n"));
|
|
#endif
|
|
load_melody(NULL);
|
|
} else {
|
|
#ifdef DEBUG
|
|
printf(PSTR("something in front of barrier\n"));
|
|
#endif
|
|
load_melody(MELODY_REPEL);
|
|
play_melody();
|
|
}
|
|
}
|
|
barrier_flag = false;
|
|
}
|
|
while (uart_flag) { // new UART input
|
|
while (0!=uart_in[uart_out_i]) { // go until end of string
|
|
putchar(uart_in[uart_out_i]); // print current character
|
|
uart_out_i++; // go to next character
|
|
}
|
|
uart_flag = false;
|
|
}
|
|
while (command_flag) { // UART input command is ready
|
|
putchar('\n'); // print new line
|
|
uart_out_i = 0; // reset string counter
|
|
command_action(uart_in); // process command
|
|
command_flag = false; // clear flag
|
|
}
|
|
while (countdown_flag) { // play melody
|
|
if (!play_melody()) { // play next tone
|
|
load_melody(NULL); // stop playing on last tone
|
|
}
|
|
countdown_flag = false;
|
|
}
|
|
/* go to sleep and wait for next interrupt */
|
|
if (NULL==melody) {
|
|
#ifdef DEBUG
|
|
set_sleep_mode(SLEEP_MODE_IDLE); // in idle UART input is possible
|
|
#else
|
|
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
|
#endif
|
|
} else {
|
|
set_sleep_mode(SLEEP_MODE_IDLE);
|
|
}
|
|
sleep_mode();
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|