Samstag, 27. Dezember 2014

CatDetect - a touchless and remote door bell

The Story behind this project

My parents have three cats and they are allowed to roam outside in the backyard and the gardens in the neighbourhood during the day. But in the evening when it dawns we like to have them inside where it's warm and safe. Especially in winter it's important that they're in before it gets too cold. However, one cannot watch the door the whole day and look whether they want in or not; and a cat flap would allow them to bring all kinds of mice and other pests inside. That's the story how it came to this little project.

Different concepts

How could you detect a cat sitting infront of the door (reliably!)?
  • RFID
    (+) distinct identification of our cats, hardly any false alarms
    (-) range too short, the readout of the cats' RFID chip only works when really close to the detector (< 5 cm) or they needed another RFID chip at a collar which they would lose (we tried that before); cost intensive
  • Pressure plate
    (+) probably invisible under the doormat
    (-) needs to be very sensitive because the cats aren't that heavy -> more likely to have false alarms
  • Light barrier
    (+) relatively small and cheap
    (-) heavily dependent on lighting conditions, large effort needed to "clean" the signal from disturbance; emitter and receiver needed
  • Reflective light barrier
    (+) probably the smallest solution
    (-) heavily dependent on lighting conditions; cats' fur isn't very reflective
  • Reflective sonar barrier
    (+) small, cheap (an ultrasonic rangefinder costs about $2) and very reliable (lighting conditions don't matter)
    (-)  temperature dependent
I finally went with the ultrasonic method, and I even had some sensors lying around, as well as a simple wireless doorbell. The bell button and sender unit ran off a 12 V battery and was testet to run even at 8 volts with only slightly less range. A pair transistors is used to short the push button electrically by the ATtiny84. I added a few LEDs to simplify the positioning and troubleshooting. Every 1.5 seconds the sensor is activated and read out once. The green LED indicates a correct measurement, after 4 good readouts the bell is rung (red LED). If motion is detected but out of range, the yellow LED blinks briefly. Everything was put on a little breadboard.
The whole device is powered by a Nokia charger which outputs 8 Volts - quite strange, since it is specified to 5 volts. Probably the voltage drops to 5 when enough current is drawn during the charching. That is actually quite handy for my application because it can still power the sending unit as well as it can be regulated down easily with a 78L05 to the right voltage for the microcontroller and the ultrasonic sensor.
However the testing went not that well. It sometimes worked, but far away from reliable.

A fail?

To this point, yes. There where a lot of false alarms and undetected cats. For example, after a cold night the whole device stopped working at all. Unplugging it briefly, and it worked again. I couldn't imagine what the problem was and eventually I stopped working on it. That was a year ago.

Why breadboarding is not a great idea

This winter I gave it another try. The circuit and the software hasn't been changed, but I soldered everything on perfboard and put it in a nice 12x6x4 cm ABS box to keep it safe from spray water. Of course it isn't waterproof, but under the balcony there shouldn't be any downfall. I cut out holes for the LEDs, the sensor (those 16 mm holes were a real pain to drill) and the power jack (the charching socket salvaged from the broken cell phone the charger is from).





The finished product

I also threw together a simple artwork to make the box look pretty and give the user some information what the LEDs mean.


It is now exactly two months in use, and everything is working out really good. The electronics don't seem to fail, even at temperatures less than -10 °C. There hasn't been a single false alarm or a cat sitting infront of that hasn't been detected correctly. Awesome!

Here a little video where I catched one of the cats "using" it ;)



In case you want to take a look at it, here is the code (neither pretty nor efficient, I threw it together in just an hour or so)


const byte gongpin = 0;
const byte trig    = 1;
const byte echo    = 2;
//debug leds:
const byte red     = 6;
const byte yellow  = 7;
const byte green   = 8;

const long waittime = 1500;   //cycle
const int triggercm = 70;
const int minimalcm = 5;
const int numberofreads = 4;
const int repeattime = 20000;  //milliseconds
const int hightime = 500;      //milliseconds

void setup() { 
  pinMode(gongpin, OUTPUT);    
  pinMode(trig, OUTPUT);  
  pinMode(echo, INPUT);   
  digitalWrite(trig,LOW);
  digitalWrite(gongpin,LOW);

  pinMode(red,OUTPUT);
  pinMode(yellow,OUTPUT);
  pinMode(green,OUTPUT);
}

bool gonged = false;
bool gong = false;
int cm = 0;
byte count = 0;

void loop() {
  initialRead();
  handleGong();
}

void handleGong(){
  static long lm = millis();
  static bool activated = false;
  if (count >= numberofreads && !activated){
    count = 0;
    activated = true;
    gonged = true;
    digitalWrite(gongpin,HIGH);
    digitalWrite(red,HIGH);
    lm = millis();
  }
  if (activated && (millis()-lm >= hightime)){
    activated = false;
    digitalWrite(gongpin,LOW);
    digitalWrite(red,LOW);
  }
  if (gonged && (millis()-lm >= repeattime)){
    gonged = false;
  }
  
}

void initialRead(){
  static long lm = millis();
  if (millis()-lm > waittime){  
    lm = millis();
    cm = readCm();
    if (cm < triggercm){
      if (!gonged) count++;
      digitalWrite(green,HIGH);
      delay(10);
      digitalWrite(green,LOW);
    }
    else
    count = 0;
  }
}

long readCm(){
  digitalWrite(trig, HIGH);
  delayMicroseconds(12);
  digitalWrite(trig, LOW); 
  long microseconds = pulseIn(echo, HIGH, 100000);
  long c = microseconds / 29 / 2;
  if (c < minimalcm){
    digitalWrite(yellow,HIGH);
    delay(10);
    digitalWrite(yellow,LOW);
    c = triggercm*2;
  }
  return c;
}
- Marv

Samstag, 19. Juli 2014

A chart recorder printing text

Chart recorders were pretty neat devices to graph time curves of analog values, such as temperature changes. My dad had still a pair of MFE 4144 thermal line recorders in his cabinet of replacement parts. However, the device using those modules is discontinued for now about two decades and he asked me whether I could make use of them.
Luckily, my dad found the schematics and the pin mapping of the recorder, so attaching it to a microcontroller was pretty easy. The supply voltage is 12 V; I power it from a bench power supply. It takes 5 parallel TTL signals as a forward speed selection (a microcontroller drives a stepper motor), and a +/-2 V analog signal for the 'pen' position. The 'pen' is actually a needle that is being heated by a resistor. The thermopaper stains in a nice blue color when having contact to that hot metal.


To control the line recorder, I used the PWM signal of an ATmega32u4 on a Pro Micro Arduino-like board, which can be programmed just like the Arduino Leonardo. There are two pins for the needle postion, both independent of the supply voltage. The positive input is hooked up to a PWM output of the microcontroller, the negative input is wired to the wiper of a voltage divider (in this case a potentiometer) between GND and +5V to avoid having to deal with negative voltages. However, the 1 kHz PWM frequency is so slow that it creates a non-linearity between the value inputted to the device and the actual position of the needle: In the middle the resolution is way higher than near the maximum displacement. Only an extremely large RC low-pass filter (R = 10k, C = 10µF) gets rid of that problem but also makes the needle movement incredibly slow. Another way of solving the problem is raising the PWM frequence by setting the timer prescalers of the AVR differently - and that's what I eventually did. Running the PWM at 65 kHz lets you even remove the low-pass filter completely - the result is therefore an extremely simple circuit only consisting of the power supply, the microcontroller, a potentiometer and the line writer itself.
But what can the line recorder do? You guessed right, drawing a line on a piece of paper. For example these beautiful sine waves:

Quite boring, right? Well, to draw more complicated things you could buy a real thermal printer. But as I love the challenge....
You would either need to be able to move the paper in both directions (the stepper only pulls it off the reel) or the possibility to discontinue the line. The first is unfeasible without enormous effort in modifying the mechanical part of the machine. The latter can be done by either moving the pen really fast that it doesn't leave a trace on the paper (if the needle arrangement wasn't that inert), turning the heater off and back on (which takes about half a minute - definitely too long) or by lifting the needle off the paper (what I did).

You could use a servo to do that job, but since it is only needed to lift the needle less than a millimeter to discontinue the line, a solenoid is probably the faster solution. But where can you get small solenoids which are easy to use? Right, from relays. Those where lying around in the basement:


So I cut them open and soldered a little wire frame to the pallet of the relays which goes just right below the needle and lifts it up when 12 V are applied. The BD649 NPN darlington transistor is way overpowered to operate the relays, but the TO-220 package makes a nice convinient way of mounting the perf board to the recorder. Another transistor in that kind of package is placed on the other side for secure fixation. In the front, spacing bolts and 3 mm screws make it easy to adjust the height of the wire frame later for perfect performance. The other components are a 1 k resistor at the base of the transistor and a flyback diode for the solenoids.

Now just upload bit of code to the microcontroller and your line recorder can print text :D


Currently I only have it print text in a 8x6 font, but it should be pretty easy to adjust it for larger fonts and even bitmap images.

Video of it in action:




#define FORWARD 25   //ms
#define WIDTH 4      //lines; Times 2
#define PIXELWIDTH 2 //mm
#define DOWNTIME 50  //ms spring takes to move it back down
#define UPTIME   20  //ms relays take to lift
#define DRAWTIME 4   //ms on paper  per step
#define MOVETIME 1   //ms lifted up per mm

//pin definitions
const int drive = 5;
const int pos = 3;
const int up  = 10;

const byte font[]=
{
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00,      // Code for char  
        0x00, 0x00, 0x6F, 0x00, 0x00, 0x00,      // Code for char !
        0x00, 0x07, 0x00, 0x07, 0x00, 0x00,      // Code for char "
        0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00,      // Code for char #
        0x24, 0x2A, 0x6B, 0x2A, 0x12, 0x00,      // Code for char $
        0x23, 0x13, 0x08, 0x64, 0x62, 0x00,      // Code for char %
        0x36, 0x49, 0x55, 0x22, 0x50, 0x00,      // Code for char &
        0x00, 0x00, 0x07, 0x00, 0x00, 0x00,      // Code for char '
        0x00, 0x1C, 0x22, 0x41, 0x00, 0x00,      // Code for char (
        0x00, 0x41, 0x22, 0x1C, 0x00, 0x00,      // Code for char )
        0x14, 0x08, 0x3E, 0x08, 0x14, 0x00,      // Code for char *
        0x08, 0x08, 0x3E, 0x08, 0x08, 0x00,      // Code for char +
        0x00, 0xB0, 0x70, 0x00, 0x00, 0x00,      // Code for char ,
        0x08, 0x08, 0x08, 0x08, 0x08, 0x00,      // Code for char -
        0x00, 0x60, 0x60, 0x00, 0x00, 0x00,      // Code for char .
        0x20, 0x10, 0x08, 0x04, 0x02, 0x00,      // Code for char /
        0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00,      // Code for char 0
        0x00, 0x42, 0x7F, 0x40, 0x00, 0x00,      // Code for char 1
        0x42, 0x61, 0x51, 0x49, 0x46, 0x00,      // Code for char 2
        0x22, 0x41, 0x49, 0x49, 0x36, 0x00,      // Code for char 3
        0x18, 0x14, 0x12, 0x7F, 0x10, 0x00,      // Code for char 4
        0x27, 0x45, 0x45, 0x45, 0x39, 0x00,      // Code for char 5
        0x3E, 0x49, 0x49, 0x49, 0x32, 0x00,      // Code for char 6
        0x03, 0x01, 0x71, 0x09, 0x07, 0x00,      // Code for char 7
        0x36, 0x49, 0x49, 0x49, 0x36, 0x00,      // Code for char 8
        0x26, 0x49, 0x49, 0x49, 0x3E, 0x00,      // Code for char 9
        0x00, 0x36, 0x36, 0x00, 0x00, 0x00,      // Code for char :
        0x00, 0xB6, 0x76, 0x00, 0x00, 0x00,      // Code for char ;
        0x08, 0x14, 0x14, 0x22, 0x22, 0x00,      // Code for char <
        0x14, 0x14, 0x14, 0x14, 0x14, 0x00,      // Code for char =
        0x22, 0x22, 0x14, 0x14, 0x08, 0x00,      // Code for char >
        0x02, 0x01, 0x51, 0x09, 0x06, 0x00,      // Code for char ?
        0x3E, 0x41, 0x5D, 0x51, 0x4E, 0x00,      // Code for char @
        0x7E, 0x09, 0x09, 0x09, 0x7E, 0x00,      // Code for char A
        0x7F, 0x49, 0x49, 0x49, 0x36, 0x00,      // Code for char B
        0x3E, 0x41, 0x41, 0x41, 0x22, 0x00,      // Code for char C
        0x7F, 0x41, 0x41, 0x41, 0x3E, 0x00,      // Code for char D
        0x7F, 0x49, 0x49, 0x49, 0x41, 0x00,      // Code for char E
        0x7F, 0x09, 0x09, 0x09, 0x01, 0x00,      // Code for char F
        0x3E, 0x41, 0x49, 0x49, 0x7A, 0x00,      // Code for char G
        0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00,      // Code for char H
        0x00, 0x41, 0x7F, 0x41, 0x00, 0x00,      // Code for char I
        0x31, 0x41, 0x41, 0x41, 0x3F, 0x00,      // Code for char J
        0x7F, 0x08, 0x14, 0x22, 0x41, 0x00,      // Code for char K
        0x7F, 0x40, 0x40, 0x40, 0x40, 0x00,      // Code for char L
        0x7F, 0x02, 0x0C, 0x02, 0x7F, 0x00,      // Code for char M
        0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00,      // Code for char N
        0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00,      // Code for char O
        0x7F, 0x09, 0x09, 0x09, 0x06, 0x00,      // Code for char P
        0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00,      // Code for char Q
        0x7F, 0x09, 0x19, 0x29, 0x46, 0x00,      // Code for char R
        0x26, 0x49, 0x49, 0x49, 0x32, 0x00,      // Code for char S
        0x01, 0x01, 0x7F, 0x01, 0x01, 0x00,      // Code for char T
        0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00,      // Code for char U
        0x0F, 0x30, 0x40, 0x30, 0x0F, 0x00,      // Code for char V
        0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00,      // Code for char W
        0x63, 0x14, 0x08, 0x14, 0x63, 0x00,      // Code for char X
        0x07, 0x08, 0x70, 0x08, 0x07, 0x00,      // Code for char Y
        0x61, 0x51, 0x49, 0x45, 0x43, 0x00,      // Code for char Z
        0x00, 0x00, 0x7F, 0x41, 0x00, 0x00,      // Code for char [
        0x02, 0x04, 0x08, 0x10, 0x20, 0x00,      // Code for char BackSlash
        0x00, 0x41, 0x7F, 0x00, 0x00, 0x00,      // Code for char ]
        0x04, 0x02, 0x01, 0x02, 0x04, 0x00,      // Code for char ^
        0x40, 0x40, 0x40, 0x40, 0x40, 0x00,      // Code for char _
        0x00, 0x01, 0x02, 0x04, 0x00, 0x00,      // Code for char `
        0x20, 0x54, 0x54, 0x54, 0x78, 0x00,      // Code for char a
        0x7F, 0x44, 0x44, 0x44, 0x38, 0x00,      // Code for char b
        0x38, 0x44, 0x44, 0x44, 0x28, 0x00,      // Code for char c
        0x38, 0x44, 0x44, 0x44, 0x7F, 0x00,      // Code for char d
        0x38, 0x54, 0x54, 0x54, 0x58, 0x00,      // Code for char e
        0x00, 0x08, 0xFE, 0x09, 0x00, 0x00,      // Code for char f
        0x18, 0xA4, 0xA4, 0xA4, 0x78, 0x00,      // Code for char g
        0x7F, 0x04, 0x04, 0x04, 0x78, 0x00,      // Code for char h
        0x00, 0x44, 0x7D, 0x40, 0x00, 0x00,      // Code for char i
        0x00, 0x40, 0x84, 0x7D, 0x00, 0x00,      // Code for char j
        0x7F, 0x10, 0x28, 0x44, 0x00, 0x00,      // Code for char k
        0x00, 0x41, 0x7F, 0x40, 0x00, 0x00,      // Code for char l
        0x7C, 0x04, 0x18, 0x04, 0x78, 0x00,      // Code for char m
        0x7C, 0x08, 0x04, 0x04, 0x78, 0x00,      // Code for char n
        0x38, 0x44, 0x44, 0x44, 0x38, 0x00,      // Code for char o
        0xFC, 0x24, 0x24, 0x24, 0x18, 0x00,      // Code for char p
        0x18, 0x24, 0x24, 0x24, 0xFC, 0x00,      // Code for char q
        0x7C, 0x08, 0x04, 0x04, 0x08, 0x00,      // Code for char r
        0x48, 0x54, 0x54, 0x54, 0x24, 0x00,      // Code for char s
        0x04, 0x3F, 0x44, 0x44, 0x00, 0x00,      // Code for char t
        0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00,      // Code for char u
        0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00,      // Code for char v
        0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00,      // Code for char w
        0x44, 0x28, 0x10, 0x28, 0x44, 0x00,      // Code for char x
        0x1C, 0xA0, 0xA0, 0xA0, 0x7C, 0x00,      // Code for char y
        0x44, 0x64, 0x54, 0x4C, 0x44, 0x00,      // Code for char z
        0x00, 0x08, 0x77, 0x41, 0x41, 0x00,      // Code for char {
        0x00, 0x00, 0x7F, 0x00, 0x00, 0x00,      // Code for char |
        0x41, 0x41, 0x77, 0x08, 0x00, 0x00,      // Code for char }
        0x08, 0x04, 0x08, 0x10, 0x08, 0x00,      // Code for char ~
        0x00, 0x7F, 0x41, 0x7F, 0x00, 0x00       // Code for char Block
        };
        
byte posx = 0; //remembers where the pen is

void setup(){
  pinMode(drive,OUTPUT);
  digitalWrite(drive,HIGH);  //Stop motor
  pinMode(pos,OUTPUT);
  pinMode(up,OUTPUT);
  
  TCCR0B = (TCCR0B & 0xF8) | 1;  //set pwm to 65 khz; millis run 64 times as fast
  
  penUp();
  setmm(20);
  
  Serial.begin(9600);
}


void loop(){
  if (Serial.available()>0){
    printChar(Serial.read());  //Just print the incoming ascii characters
  }
  
}

void printChar(unsigned char c){  //print one char
  uint16_t address = (c-32)*6;
  for (byte i = 0; i<6 data-blogger-escaped-0="" data-blogger-escaped-40="" data-blogger-escaped-8="" data-blogger-escaped-a="" data-blogger-escaped-address="" data-blogger-escaped-and="" data-blogger-escaped-b--="" data-blogger-escaped-b-back="" data-blogger-escaped-b="" data-blogger-escaped-back="" data-blogger-escaped-between="" data-blogger-escaped-bit="" data-blogger-escaped-bitread="" data-blogger-escaped-bits="" data-blogger-escaped-bool="" data-blogger-escaped-break="" data-blogger-escaped-byte="" data-blogger-escaped-calculate="" data-blogger-escaped-char="" data-blogger-escaped-column="" data-blogger-escaped-data="" data-blogger-escaped-delay="" data-blogger-escaped-dest="" data-blogger-escaped-destination="" data-blogger-escaped-digitalwrite="" data-blogger-escaped-direction="" data-blogger-escaped-do="" data-blogger-escaped-draw="" data-blogger-escaped-drawto="" data-blogger-escaped-drive="" data-blogger-escaped-drivep="" data-blogger-escaped-else="" data-blogger-escaped-endpos="" data-blogger-escaped-first="" data-blogger-escaped-font="" data-blogger-escaped-for="" data-blogger-escaped-frommm="" data-blogger-escaped-handle="" data-blogger-escaped-i="" data-blogger-escaped-if="" data-blogger-escaped-int="" data-blogger-escaped-is="map(frommm," data-blogger-escaped-last="" data-blogger-escaped-lastbit="false;" data-blogger-escaped-line="" data-blogger-escaped-low="" data-blogger-escaped-mm="" data-blogger-escaped-move="" data-blogger-escaped-nothing="" data-blogger-escaped-paper="" data-blogger-escaped-parse="" data-blogger-escaped-pendown="" data-blogger-escaped-penup="" data-blogger-escaped-pixel="" data-blogger-escaped-positions="" data-blogger-escaped-print="" data-blogger-escaped-printchar="" data-blogger-escaped-pwm="" data-blogger-escaped-row="" data-blogger-escaped-set="" data-blogger-escaped-setmm="" data-blogger-escaped-single="" data-blogger-escaped-start="" data-blogger-escaped-startpos="" data-blogger-escaped-string="" data-blogger-escaped-that="" data-blogger-escaped-the="" data-blogger-escaped-to="" data-blogger-escaped-tomm="" data-blogger-escaped-uint8_t="" data-blogger-escaped-up="" data-blogger-escaped-value="" data-blogger-escaped-void="" data-blogger-escaped-while="" data-blogger-escaped-width="" data-blogger-escaped-write8bit="" data-blogger-escaped-write="" data-blogger-escaped-writing="" data-blogger-escaped-xff=""> is) {
    while (is <= dest){
      analogWrite(pos,is++);
      delay(DRAWTIME*64);
    }
  }
  else  {
    while (is >= dest){
      analogWrite(pos,is--);
      delay(DRAWTIME*64);
    }
  }
  posx = tomm;   //remember position
}
void setmm(int mm){  //move pen to mm position
  byte x = map(mm, 0, 40,200,57); //calculate pwm value
  analogWrite(pos,x);
  delay(abs(mm-posx)*MOVETIME*64L);
  posx = mm;    //remember position
}
- Marv

Freitag, 18. Juli 2014

Hello World

Hi folks,

I'm Marvin - Marv for short - and I'm an 18 year old student of electrical engineering and computer science (2014). I created this blog in order to share my work and ideas with the world. I won't get to specific on what I'm going to post, it can be anything from artistic work, music and videos to programming, electronics and hardware hacking - and maybe even something completely different. I just don't know yet. ;)

Enjoy :)
- Marv