User Tools

Site Tools


project:brmhive:sketch
/*
 * brmhive -- low-power sensor data logger
 *
 * You can force wake and trigger resonr re-read using the Alert button.
 *
 * The info led will blink happily when doing all the work and stay dark
 * while asleep. Short blinks (up to a second) are o.k. Long blinks (more
 * than 1.5 seconds) indicate error.
 *
 * In case of trouble, try pressing Reset.
 */

/* PCF8583 uses pasky's fork at github that adds extra alarm capabilities. */
/* Required modification of cores/arduino/HardwareSerial.c:
 * Change RX_BUFFER_SIZE to 2, head and tail to uint8_t. */

/* TODO: Test w/o RX_BUFFER_SIZE hack. */
/* TODO: Move taddrs[] to PROGMEM. */

#include <avr/sleep.h>
#include <avr/pgmspace.h>
#include <OneWire.h>
#include <Wire.h>
#include <PCF8583.h>
#include <SdFat.h>
#include <SdFatUtil.h>

// #define DEBUG // Temperature bus scan.

#define NUM_FSR 4 // number of weight sensors
#define NUM_TEMP 55 // number of temperature sensors

/* Pin definitions. */
#define auxPowerPin 6 /* Peripherial power transistor base. */
#define alertPin 2 /* Alert button. */
#define RTCPin 3 /* RTC alarm. */
#define infoPin 7 /* Info LED. */

#define oneWirePin 6 /* One-wire temperature sensors. */

/* SD Card is occupying fixed pins - SPI. */
/* RTC clock is occupying fixed pins - I2C. */

void powerdown_pins(void)
{
        /* Before going to sleep, bring pins to the most low-power state. */
        for (int i = 0; i <= 13; i++) {
		if (i == alertPin || i == RTCPin || i == infoPin
		    || i == oneWirePin || i == auxPowerPin)
			continue;
		pinMode(i, OUTPUT); digitalWrite(i, HIGH);
        }
        /* TODO: Stop reading analog pins? */
}


void blink(int count, int length)
{
	for (int i = 0; i < count; i++) {
		digitalWrite(infoPin, LOW);
		delay(length);
		digitalWrite(infoPin, HIGH);
		delay(length);
	}
	digitalWrite(infoPin, LOW);
}


OneWire oneWire(oneWirePin);
PCF8583 RTC(0xA0);


Sd2Card card;
SdVolume volume;
SdFile root;
SdFile file;

void SD_init(void)
{
	PRR &= ~(1<<PRSPI); // Turn on SPI circuit.
	if (!card.init(4/*SPI_QUARTER_SPEED*/)) {
		Serial.println("card init fail");
		Serial.println(card.errorCode(), DEC);
		Serial.println(card.type(), DEC);
		blink(2, 2000);
		return;
	}
	if (!volume.init(&card)) {
		Serial.println("volume init fail");
		blink(3, 2000);
		return;
	}
	if (!root.openRoot(&volume)) {
		Serial.println("root init fail");
		blink(4, 2000);
		return;
	}
	if (!file.open(&root, "templog.txt", O_CREAT | O_APPEND | O_WRITE)) {
		Serial.println("file init fail");
		blink(5, 2000);
		return;
	}
}

void SD_done(void)
{
	/* We must proceed in case of errors - open will fail if we did not
	 * close the file. */
	if (file.writeError) {
		Serial.println("file write fail");
		blink(2, 2000);
	}
	if (!file.close()) {
		Serial.println("file close fail");
		blink(3, 2000);
	}
	if (!root.close()) {
		Serial.println("root close fail");
		blink(4, 2000);
	}
	/* Turn off SPI. */
	SPCR &= ~(1 << SPE);
	PRR |= (1<<PRSPI);
}


// 0: asleep; 1: reset/bootup; 2: alert; 3: RTC
volatile char wake_cause = 1;

void alert_int(void)
{
	wake_cause = 2;
}

void rtc_int(void)
{
	wake_cause = 3;
	/* Avoid infinite low; and we cannot ack_timer() from within. */
	detachInterrupt(RTCPin - 2);
}


void init_device(void)
{
	pinMode(infoPin, OUTPUT);
	digitalWrite(infoPin, HIGH); // say we're alive
	delay(100); // make sure we actually see the LED

	Serial.begin(9600);

	pinMode(auxPowerPin, OUTPUT);

	pinMode(alertPin, INPUT);
	pinMode(RTCPin, INPUT);

	digitalWrite(infoPin, LOW); // setup complete!
	delay(200);

	Serial.println("booted up");

	blink(5, 100);
	delay(1000);
}

void sleep(void)
{
	powerdown_pins();

	wake_cause = 0;
	set_sleep_mode(SLEEP_MODE_PWR_DOWN);
	sleep_enable();

	RTC.set_timer(RTC_TIMER_HOURS, 1);
	attachInterrupt(RTCPin - 2, rtc_int, LOW);
	attachInterrupt(alertPin - 2, alert_int, LOW);

	sleep_mode(); // We zzzzzz here!

	detachInterrupt(alertPin - 2);
	detachInterrupt(RTCPin - 2);
	RTC.ack_timer();
	sleep_disable();
}


char scratch[50];
void append_scratch(void)
{
	Serial.print(scratch);
	file.print(scratch);
}

void get_timestamp(void)
{
	RTC.get_time();

	snprintf(scratch, sizeof(scratch),
		 "%d\t%02d/%02d/%02d %02d:%02d:%02d\t",
		 (int)wake_cause, RTC.year, RTC.month, RTC.day,
		 RTC.hour, RTC.minute, RTC.second);
	append_scratch();

	blink(2, 500);
}

void collect_weights(void)
{
	for (int i = 0; i < NUM_FSR; i++) {
		digitalWrite(infoPin, HIGH);
		uint16_t weight = analogRead(i);
		delay(100);
		digitalWrite(infoPin, LOW);
		snprintf(scratch, sizeof(scratch), "%d=%d%c",
			 i, weight, i == NUM_FSR - 1 ? '\t' : ' ');
		append_scratch();
		delay(50);
	}
}

static const uint16_t PROGMEM taddrs[NUM_TEMP] = {
#if 0
	0x65c1, // 2
	0x1b7f, // 3
	0x29a2, // 4
#else
0xD269, 0x9BC6, 0x65C1, 0x1B7F, 0x29A2, 0xABB8, 0x39A8, 0x3697, 0x3A8D, 0xB77B, 0xA8B2, 0xF4D0, 0xAEC9, 0x4CDB, 0x4BC8, 0x03A3, 0xECAB, 0xCA84, 0x9DC5, 0xBB6E, 0x5989, 0x61D7, 0xBD72, 0x3E86, 0x1FCA, 0x4F68, 0xBFC8, 0x3978, 0xBEA8, 0x9EDB, 0x2468, 0x9FCA, 0x467B, 0x0B7C, 0xA0D1, 0x568D, 0x5FCC, 0x0BDF, 0x76BB, 0x3FC7, 0x63D0, 0x9ABF, 0x91C4, 0x01B3, 0x5ABA, 0x9FAF, 0x9190, 0xCEB6, 0x506A, 0x98BE, 0x808A, 0x1F9E, 0x3A68, 0xBCCF, 0x0AAE
#endif
};

void collect_temps(void)
{
	byte addr[8];

#ifdef DEBUG
	while (oneWire.search(addr)) {
		Serial.print("R=");
		for (int i = 0; i < 8; i++) {
			Serial.print(addr[i], HEX);
			Serial.print(" ");
		}
		Serial.print("\n");
	}
	oneWire.reset_search();
#endif

	for (int i = 0; i < NUM_TEMP; i++) {
		addr[0] = 0x28;
		addr[1] = taddrs[i] >> 8;
		addr[2] = taddrs[i] & 0xff;
		addr[3] = 0x7d;
		addr[4] = 2;
		addr[5] = 0;
		addr[6] = 0;
		addr[7] = OneWire::crc8(addr, 7);

		digitalWrite(infoPin, HIGH);
		oneWire.reset();
		oneWire.select(addr);
		oneWire.write(0x44);
		delay(800);

		int present = oneWire.reset();
		if (present) {
			oneWire.select(addr);
			oneWire.write(0xbe);
			byte data[12];
			for (int j = 0; j < 9; j++)
				data[j] = oneWire.read();
			digitalWrite(infoPin, LOW);

			int16_t temp = (data[1] << 8) | data[0];
			/* data is in 0.0625 increments, fixed-point *100. */
			temp = temp * 6 + temp / 4;
			snprintf(scratch, sizeof(scratch), "%d=%d%c",
				 i, temp, i == NUM_TEMP - 1 ? '\t' : ' ');
			append_scratch();
		} else {
			blink(3, 1500);
			delay(1500);
			snprintf(scratch, sizeof(scratch), "%d=!%c",
				 i, i == NUM_TEMP - 1 ? '\t' : ' ');
			append_scratch();
		}
		delay(100);
	}
}

void collect_data(void)
{
	digitalWrite(auxPowerPin, HIGH);
	SD_init();

	get_timestamp();

	collect_weights();

	blink(2, 500);
	delay(500);

	collect_temps();

	strcpy(scratch, "\r\n");
	append_scratch();

	SD_done();
	digitalWrite(auxPowerPin, LOW);
	blink(3, 100);

	Serial.println("done");
}


/* This is the one-time routine called when we are turned on. It should
 * set up the whole device and go through the drill. */
void setup(void)
{
	init_device();
	collect_data();
	sleep();
}

/* This is the repeat routine called all the time after setup().
 * We sleep() between each two calls to it. */
void loop(void)
{
	if (wake_cause == 1 || wake_cause == 2 || wake_cause == 3) {
		blink(2, 200);
		delay(1000);
		collect_data();
		sleep();
	} else {
		/* Something wrong happenned. Blink around a bit and try to go
		 * to sleep. */
		snprintf(scratch, sizeof(scratch), "unknown wake cause %d\r\n",
			 wake_cause);
		append_scratch();
		blink(3, 3000);
		sleep();
	}
}
project/brmhive/sketch.txt · Last modified: 2011/01/07 18:40 by pasky