Sunday, December 28, 2014

Central ventilation augmentation

My apartment building has a central ventilation system built-in. While it does a pretty good job at getting the kitchen smells out it also makes a lot of noise and wastes heat when closed.

Out of the many possible solutions I decided on building a cover that goes on top and is activated by the red LED that lights up when the original ventilation system is turned on. This means no messing around with mains - galvanic isolation.

After the break, the story of going from this:


to this:

Along the way there will be some interesting tidbits - original design, capacitive light sensing, low-power techniques and code.


Original unit

Some pictures of the original system before cleaning:
Cover removed

Thermo-controlled blades removed

Close-up of thermal actuator

The white rectangle in the picture above contains a bimetal strip. When power (220V) is applied it heats up and the metal strip on the left raises slowly. In the process it also pushes on the plastic strips (see picture with cover off) which pivot in an 'open' position.
When power is turned off, the metal strip goes down and the plastic strips rotate to a 'closed' position.

There is no fan/ventilator unit, air is sucked by a bigger fan which probably sits on top of the (tall) apartment building. The suction power is pretty significant, a cardboard is able to hold almost more than 500g when pulled by the air.

Power is applied to the system when a light switch is closed.

Idea


Since I did not want to mess with the 220V that the original system runs on I opted for a battery powered system. The obvious choice for me was an MSP430 - specifically MSP430G2221

I cut a CD-case to the size where it would cover the unit completely, tested the fit a bit and then went on to test the mechanical properties. I wanted a servo that rotates the CD cover to 45 degrees when open and to vertical when closed.


Above you can see the CD case affixed to the servo with some painter's tape.
After testing it with 2.5-3V it was proved that the servo can work with those voltages and has barely enough force to rotate the cover.

I measured the current drawn while the servo moves - 60-200mA - and worked out a battery budget from that.

Electrical design and code

I used a less-known property of the LEDs - they can detect light without any further amplification. Instead, the intrinsic capacitance of the LED changes (decreases) when light is shone through.
To measure the light intensity, the LED is 'charged' by reverse-biasing it through the uC leads. Immediately after that one of the pins is switched to an input and a timer is started. The timer counts how long it takes for the LED to discharge.
This works because the input resistance of the uC is not infinite, but usually around 5-20 kOhms.

Although it sounds strange and unreliable I've used this kind of property a lot of times before - the most notable case being some solar trackers that are still in use.

Here's some basic code for a PIC16F688 microcontroller that does the above at 8 Mhz:

#define get_ldc_delay(trisAnode, trisCathode, anode_pin, cathode_pin){\
temp_result = 0;\
trisCathode = pOUT;\
trisAnode = pOUT;\
\
anode_pin = 0;\
cathode_pin = 1;\
delay_us(100);\
trisCathode = pIN;\
while (cathode_pin&&(temp_result!=DARK_THRESH)){\
temp_result++;\
}\
}

For the code above, DARK_THRESH is set to 0x1FF. If that threshold is reached it's not worth it to wait any longer since it's unlikely the value is meaningful. It also means that it's completely dark.


My design runs off two AA batteries, there's no schematic for it since it's a really basic circuit: microcontroller, small cap between uC VCC and GND, reset line tied to VCC through a random resistor.
There's also a big cap (470uF) to keep the voltage stable when the servo is activated.
I should have added a diode between the positive lead and the VCC of the uC, I'll explain later why.


The yellow leads going to the LED add significant capacitance and pick up noise easily. This translates to random behavior, so I removed the wires and soldered the LED directly on the perfboard.

There's a small indicator led on the right side. This was added for debugging, since testing with the servo connected draws a lot of power.

Battery budget

I misplaced the calculations somewhere, I'll add them later.
The conclusion was that with a set of fresh batteries and around 4 actuations per day (2x closed-open-closed transitions) it should last around one year.
I also worked out that I cannot power the system with a big capacitor, no matter how big.
The microcontroller reads the light in a few milliseconds, orders the servo to move to the same position (100 times, to allow the servo to settle) and goes back to sleep for 4 seconds. Obviously, if the servo is already in the desired position it will not be ordered to move.
While sleeping (those 4 seconds) the system draws a few microamps which is below the self-discharge rate of the batteries.

Electrical design issues

When the servo stalls or the battery are nearing depletion the system acts strangely. This is why I should've added a Schottky diode between the positive supply wire and the VCC of the microcontroller.
Upon stalling, the servo draws a lot of current which is more than what the internal resistance of the batteries can handle, causing a dip in the the voltage. This browns out the microcontroller which continues to actuate the servo. This loop condition eventually drains the batteries pretty quickly (minutes to hours).

Capacitive light sensing, while nice, does have it's drawbacks. The circuit is able to pick up human presence, from a proximity of about 20 centimeters. This is a cool side-effect that I might use later in other contraptions, but right now it's pretty useless and caused me to spent a lot of time in debugging.

The sensing LED has to be properly insulated from stray light otherwise it picks up ambient light as well instead of the red LED it has to follow.

The red LED is pulsating at 50 Hz since it's connected directly to mains, through a resistor. This means that the code has to take into account that it might randomly read an OFF or dimmed value when the light is on.
The way I handled this was by taking 3 readings and only acting if the all values matched - this is still not the right approach. I should instead take multiple readings and see if at least in one of them light is detected.

Mechanical issues

The CD case did not provide enough air sealage and was hard to actuate as the swept area was too big -> required too much torque.
I scrapped that idea and instead constructed a cardboard box that wraps around the original cover. The servo actuates a small cardboard flap that closes an opening. This is a more reliable and mechanically sound design. The suction provided by the original system makes the air seal pretty good with lower air leakage.

A piece of 'piano' wire connects the cardboard flap to the servo. You can also see the red tape that was added afterwards to fix the ambient light leakage issues.


The system could have been put inside the box but it makes for an interesting conversation piece so I decided to leave it outside. It also makes changes easier.

A picture with the flap open:

Code

The code was developed using Code Composer Studio and the Energia framework. The light detection bit was rewritten to use the native GPIO functions as the Energia/Arduino framework was too slow for this.


#include "Energia.h"

void setup();
void loop();

const uint8_t CTL_PIN = P1_0;

const int MAX_PULSE = 1250; //us //2000-2500 is the max range
const int MIN_PULSE = 550; //us // 500-550 is the min range
const int PERIOD_DELAY = 20; //ms
const uint8_t PUSH1 = P1_7;

const uint8_t LED_A = P1_4;
const uint8_t LED_K = P1_5;
//const uint8_t PUSH2 = P1_3;

#define DARK_THRESH 0xFFF0
#define DETECTION_THRESH 0x1450
#define STATE_OPEN 1
#define STATE_CLOSED 0
// how many sequential samples need to be identical
#define REPEAT_COUNT 3
// how many times to send the position update to the servo
#define REPEAT_SERVO 100

int lastState = STATE_CLOSED;

const uint16_t port_to_dir[] = {
NOT_A_PORT, (uint16_t) &P1DIR, (uint16_t) &P2DIR,
#ifdef __MSP430_HAS_PORT3_R__
(uint16_t) &P3DIR,
#endif
};

void setup() {
pinMode(CTL_PIN, OUTPUT);
pinMode(PUSH1, INPUT_PULLUP);
pinMode(PUSH2, INPUT_PULLUP);
pinMode(LED_A, INPUT);
pinMode(LED_K, INPUT);
pinMode(GREEN_LED, OUTPUT);
}

int microSeconds = 1500;

int readLight() {
unsigned int result = 0;

uint8_t bitA = digitalPinToBitMask(LED_A);
uint8_t portA = digitalPinToPort(LED_A);
uint8_t bitK = digitalPinToBitMask(LED_K);
uint8_t portK = digitalPinToPort(LED_K);

//pinMode(LED_A, OUTPUT);
*(portDirRegister(portA)) |= bitA;
//pinMode(LED_K, OUTPUT);
*(portDirRegister(portK)) |= bitK;

//digitalWrite(LED_A, 0);
*(portOutputRegister(portA)) &= ~bitA;
//digitalWrite(LED_K, 1);
*(portOutputRegister(portK)) |= bitK;

delayMicroseconds(100);

//pinMode(LED_K, INPUT);
*(portDirRegister(portK)) &= ~bitK;

//while (digitalRead(LED_K)&&(result!=DARK_THRESH)){
while ((*(portInputRegister(portK)) & bitK) && (result != DARK_THRESH)) {
result++;
}
return result;
}

void loop() {

volatile int currentState = 0;
digitalWrite(GREEN_LED, 1);

// we count multiple times to check if the light is flickering
for (int countTimes = 0; countTimes < REPEAT_COUNT; countTimes++) {
if (readLight() < DETECTION_THRESH) {
// light was detected
currentState += STATE_OPEN;
} else {
currentState += STATE_CLOSED;
}
}

if (currentState == STATE_OPEN * REPEAT_COUNT
|| currentState == STATE_CLOSED * REPEAT_COUNT) {
// if we've got a strong match
currentState = currentState / REPEAT_COUNT;

if (currentState != lastState) {
lastState = currentState;

for (int i = 0; i < REPEAT_SERVO; i++) {
delay(PERIOD_DELAY);
digitalWrite(CTL_PIN, 1);
// TODO: should be reversed normally, something is wrong
delayMicroseconds(currentState ? MIN_PULSE : MAX_PULSE);
digitalWrite(CTL_PIN, 0);

// allow the capacitor to recover
sleep(5);
}

}

digitalWrite(GREEN_LED, 0);
// sleep x+250ms with LPM3 (as of energia-0101E0013)
sleepSeconds(4);
}else{
// wait a bit and re-read the sample again, we've failed
//delay(10);
//sleep(50);
}

Conclusion

The initial code and working concept were developed in ~30 minutes. Debugging and tweaking took another several hours.
There are some things that still need to be changed:
- better fix for ambient light leakage
- nicer-looking housing
- under-voltage (brownout) protection
- rechargeable system (using a small windmill or solar panel)
- improved power savings
- better servo control, especially stall and over-current detection
However, it does the job so far, as intended. Otherwise I would have to climb up 2.5m to cover the vent hole manually, as I did before for a couple of years.

Video during the development stage:

https://www.youtube.com/watch?v=s7Bb_Up5tzo

No comments:

Post a Comment