Skip to content
Snippets Groups Projects
Commit 3bcfb8cc authored by Bram Veenboer's avatar Bram Veenboer
Browse files

Add src

parent a5188217
Branches
Tags
No related merge requests found
#NO_CORE= 1
#MCU= m328p
#F_CPU= 16000000
#VARIANT= standard
TARGET= PowerSensor
BOARD_TAG= uno
#ARDUINO_PORT= /dev/ttyACM0
ARDUINO_PORT= /dev/ttyUSB0
include /usr/share/arduino/Arduino.mk
// Copyright (C) 2016
// ASTRON (Netherlands Institute for Radio Astronomy) / John W. Romein
// P.O. Box 2, 7990 AA Dwingeloo, the Netherlands
// This file is part of PowerSensor.
// PowerSensor 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.
// PowerSensor 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 PowerSensor. If not, see <http://www.gnu.org/licenses/>.
#include <avr/eeprom.h>
#include <Wire.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
#define MAX_SENSORS 5
struct EEPROM
{
struct Sensor {
double volt;
double type;
int16_t nullLevels;
} sensors[MAX_SENSORS];
} eeprom __attribute__((section(".eeprom")));
int64_t accumulated_values[MAX_SENSORS];
int16_t levels[MAX_SENSORS];
long previous_time;
double weights[MAX_SENSORS];
int16_t nullLevels[MAX_SENSORS];
bool inUse[MAX_SENSORS];
int64_t total_accumulated_value()
{
int64_t sum = 0;
for (uint8_t sensor = 0; sensor < MAX_SENSORS; sensor ++)
if (inUse[sensor])
sum += weights[sensor] * accumulated_values[sensor];
return sum;
}
inline uint8_t next_sensor(uint8_t current_sensor)
{
do
if (++ current_sensor == MAX_SENSORS)
current_sensor = 0;
while (!inUse[current_sensor]);
return current_sensor;
}
ISR(ADC_vect)
{
static uint8_t current_sensor = 0;
uint8_t low = ADCL, high = ADCH; // read in this order
levels[current_sensor] = (high << 8) | low;
// start next ADC ASAP.
uint8_t previous_sensor = current_sensor;
current_sensor = next_sensor(current_sensor);
ADMUX = _BV(REFS0) | (current_sensor + 1); // ADC0 reads the LCD buttons; skip it
ADCSRA |= _BV(ADSC); // start ADC conversion
// handle measured value
if (current_sensor <= previous_sensor) {
// we had one round of all sensors. now handle them in one go
long measured_time = micros();
long time_difference = measured_time - previous_time;
previous_time = measured_time;
for (uint8_t sensor = 0; sensor < MAX_SENSORS; sensor ++)
// compiler will generate 16x16 bit multiply producing 32-bit result
accumulated_values[sensor] += (long) (int) time_difference * (levels[sensor] - nullLevels[sensor]);
}
}
void readState()
{
noInterrupts();
int32_t total = total_accumulated_value() >> 16;
Serial.write((const uint8_t *) &total, sizeof total);
Serial.write((const uint8_t *) &previous_time, sizeof previous_time);
interrupts();
}
void configureFromEEPROM()
{
EEPROM copy;
eeprom_read_block(&copy, &eeprom, sizeof copy);
for (unsigned i = 0; i < MAX_SENSORS; i ++) {
inUse[i] = copy.sensors[i].volt != 0;
weights[i] = inUse[i] ? copy.sensors[i].volt * 2.5 / copy.sensors[i].type : 0;
nullLevels[i] = copy.sensors[i].nullLevels;
}
}
void readConfig()
{
EEPROM copy;
eeprom_read_block(&copy, &eeprom, sizeof copy);
Serial.write((const uint8_t *) &copy, sizeof copy);
}
void writeConfig()
{
Serial.begin(115200); // serial comm from host to Arduino seems less reliable --> reduce baud rate
EEPROM copy;
for (unsigned i = 0; i < sizeof copy; i ++) {
while (Serial.available() == 0)
;
((uint8_t *) &copy)[i] = Serial.read();
}
Serial.begin(2000000); // probably only neecssary on Leonardo, that does not automatically reset after creating a new connection
eeprom_update_block(&copy, &eeprom, sizeof copy);
configureFromEEPROM();
}
void readLevels()
{
Serial.write((const uint8_t *) levels, sizeof levels);
}
void serialEventRun()
{
switch (Serial.read()) {
case 'l': readLevels();
break;
case 'r': readConfig();
break;
case 's': readState();
break;
case 'w': writeConfig();
break;
}
}
void setup()
{
configureFromEEPROM();
lcd.begin(16, 2);
lcd.print("Sensor : W");
lcd.setCursor(0, 1);
lcd.print("Total: W");
Serial.begin(2000000);
ADMUX = _BV(REFS0) + 1;
ADCSRA |= _BV(ADIE); // enable ADC interrupts
ADCSRA |= _BV(ADSC); // start ADC conversion
}
void loop()
{
static unsigned long previous_print_time;
static int64_t previous_value_of_next_sensor, previous_value_total;
static uint8_t count, current_sensor = next_sensor(MAX_SENSORS - 1), current_line;
noInterrupts();
unsigned long current_time = micros(), time_difference = current_time - previous_print_time;
if (time_difference > 333333) {
double power;
if (current_line == 0) {
// print top line; rotate among the used sensors
int64_t total = weights[current_sensor] * accumulated_values[current_sensor];
if ((++ count & 7) == 0)
current_sensor = next_sensor(current_sensor);
power = (double) (total - previous_value_of_next_sensor) / (512 * time_difference);
previous_value_of_next_sensor = weights[current_sensor] * accumulated_values[current_sensor];
interrupts();
if ((count & 7) == 1) {
lcd.setCursor(7, 0);
lcd.print((char) ('0' + current_sensor));
}
} else { // current_line == 1
// print bottom line; the sum of all sensors
int64_t total = total_accumulated_value();
interrupts();
power = (double) (total - previous_value_total) / (512 * time_difference);
previous_value_total = total;
previous_print_time = current_time;
}
char buffer[16];
dtostrf(power, 6, 1, buffer);
lcd.setCursor(9, current_line);
lcd.print(buffer);
current_line ^= 1;
}
interrupts();
}
ARCH=$(shell arch)
CXX= g++
CXXFLAGS= -O2 -g -pthread -fopenmp
obj/$(ARCH)/%.o: %.cc
$(CXX) -c $(CXXFLAGS) $< -o $@
all:: lib/$(ARCH)/libPowerSensor.a bin/$(ARCH)/testPowerSensor arduino
lib/$(ARCH)/libPowerSensor.a: obj/$(ARCH)/PowerSensor.o lib/$(ARCH)
$(AR) cr $@ $<
bin/$(ARCH)/testPowerSensor: obj/$(ARCH)/testPowerSensor.o lib/$(ARCH)/libPowerSensor.a bin/$(ARCH)
$(CXX) $(CXXFLAGS) obj/$(ARCH)/testPowerSensor.o -Llib/$(ARCH) -lPowerSensor -o $@
obj/$(ARCH)/testPowerSensor.o: testPowerSensor.cc PowerSensor.h obj/$(ARCH)
obj/$(ARCH)/PowerSensor.o: PowerSensor.cc PowerSensor.h obj/$(ARCH)
obj/$(ARCH) lib/$(ARCH) bin/$(ARCH):
mkdir -p $@
arduino::
+make -C Arduino
upload:: all
+make -C Arduino upload
clean::
make -C Arduino clean
// Copyright (C) 2016
// ASTRON (Netherlands Institute for Radio Astronomy) / John W. Romein
// P.O. Box 2, 7990 AA Dwingeloo, the Netherlands
// This file is part of PowerSensor.
// PowerSensor 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.
// PowerSensor 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 PowerSensor. If not, see <http://www.gnu.org/licenses/>.
#include "PowerSensor.h"
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <errno.h>
#include <fcntl.h>
#include <omp.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <termios.h>
#include <unistd.h>
PowerSensor::PowerSensor(const char *device, const char *dumpFileName)
:
dumpFile(dumpFileName == 0 ? 0 : new std::ofstream(dumpFileName)),
stop(false)
{
lastState.microSeconds = 0;
if ((fd = open(device, O_RDWR)) < 0) {
perror("open device");
exit(1);
}
//Configure port for 8N1 transmission
struct termios options;
tcgetattr(fd, &options); //Gets the current options for the port
cfsetispeed(&options, B2000000); //Sets the Input Baud Rate
cfsetospeed(&options, B2000000); //Sets the Output Baud Rate
options.c_cflag = (options.c_cflag & ~CSIZE) | CS8;
options.c_iflag = IGNBRK;
options.c_lflag = 0;
options.c_oflag = 0;
options.c_cflag |= CLOCAL | CREAD;
options.c_cc[VMIN] = sizeof(State::MC_State);
options.c_cc[VTIME] = 0;
options.c_iflag &= ~(IXON | IXOFF | IXANY);
options.c_cflag &= ~(PARENB | PARODD);
/* commit the options */
tcsetattr(fd, TCSANOW, &options);
/* Wait for the Arduino to reset */
sleep(2);
/* Flush anything already in the serial buffer */
tcflush(fd, TCIFLUSH);
if ((errno = pthread_mutex_init(&mutex, 0)) != 0) {
perror("pthread_mutex_init");
exit(1);
}
doMeasurement(); // initialise
if ((errno = pthread_create(&thread, 0, &PowerSensor::IOthread, this)) != 0) {
perror("pthread_create");
exit(1);
}
}
PowerSensor::~PowerSensor()
{
stop = true;
if ((errno = pthread_join(thread, 0)) != 0)
perror("pthread_join");
if ((errno = pthread_mutex_destroy(&mutex)) != 0)
perror("pthread_mutex_destroy");
if (close(fd))
perror("close device");
if (dumpFile != 0)
delete dumpFile;
}
void PowerSensor::lock()
{
if ((errno = pthread_mutex_lock(&mutex)) != 0) {
perror("pthread_mutex_lock");
exit(1);
}
}
void PowerSensor::unlock()
{
if ((errno = pthread_mutex_unlock(&mutex)) != 0) {
perror("pthread_mutex_unlock");
exit(1);
}
}
void *PowerSensor::IOthread(void *arg)
{
return static_cast<PowerSensor *>(arg)->IOthread();
}
void *PowerSensor::IOthread()
{
while (!stop)
doMeasurement();
return 0;
}
void PowerSensor::doMeasurement()
{
State::MC_State currentState;
ssize_t retval, bytesRead = 0;
if (write(fd, "s", 1) != 1) {
perror("write device");
exit(1);
}
do {
if ((retval = ::read(fd, (char *) &currentState.consumedEnergy + bytesRead, sizeof(State::MC_State) - bytesRead)) < 0) {
perror("read device");
exit(1);
}
} while ((bytesRead += retval) < sizeof(State::MC_State));
lock();
if (lastState.microSeconds != currentState.microSeconds) {
previousState = lastState;
lastState = currentState;
if (dumpFile != 0)
*dumpFile << "S " << currentState.microSeconds / 1e6 << ' ' << (currentState.consumedEnergy - previousState.consumedEnergy) * (65536.0 / 512) / (currentState.microSeconds - previousState.microSeconds) << std::endl;
}
#if 0
static double lastTime;
static unsigned count;
++ count;
if (lastTime + 1 < omp_get_wtime()) {
#pragma omp critical (cout)
std::cout << "nr times read = " << count << std::endl;
lastTime = omp_get_wtime();
count = 0;
}
#endif
unlock();
}
void PowerSensor::mark(const State &state, const char *name, unsigned tag)
{
if (dumpFile != 0) {
lock();
*dumpFile << "M " << state.lastState.microSeconds * 1e-6 << ' ' << tag << " \"" << (name == 0 ? "" : name) << '"' << std::endl;
unlock();
}
}
void PowerSensor::mark(const State &startState, const State &stopState, const char *name, unsigned tag)
{
if (dumpFile != 0) {
lock();
*dumpFile << "M " << startState.lastState.microSeconds * 1e-6 << ' ' << stopState.lastState.microSeconds * 1e-6 << ' ' << tag << " \"" << (name == 0 ? "" : name) << '"' << std::endl;
unlock();
}
}
PowerSensor::State PowerSensor::read()
{
State state;
lock();
state.previousState = previousState;
state.lastState = lastState;
state.timeAtRead = omp_get_wtime();
unlock();
return state;
}
double PowerSensor::Joules(const State &firstState, const State &secondState)
{
return Watt(firstState, secondState) * seconds(firstState, secondState);
}
double PowerSensor::seconds(const State &firstState, const State &secondState)
{
return secondState.timeAtRead - firstState.timeAtRead;
}
double PowerSensor::Watt(const State &firstState, const State &secondState)
{
uint32_t microSeconds = secondState.lastState.microSeconds - firstState.lastState.microSeconds;
if (microSeconds != 0)
return (secondState.lastState.consumedEnergy - firstState.lastState.consumedEnergy) * (65536.0 / 512) / microSeconds;
else // very short time
return (secondState.lastState.consumedEnergy - secondState.previousState.consumedEnergy) * (65536.0 / 512) / (secondState.lastState.microSeconds - secondState.previousState.microSeconds);
}
// Copyright (C) 2016
// ASTRON (Netherlands Institute for Radio Astronomy) / John W. Romein
// P.O. Box 2, 7990 AA Dwingeloo, the Netherlands
// This file is part of PowerSensor.
// PowerSensor 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.
// PowerSensor 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 PowerSensor. If not, see <http://www.gnu.org/licenses/>.
#if !defined POWER_SENSOR_H
#define POWER_SENSOR_H
#include <inttypes.h>
#include <pthread.h>
#include <fstream>
class PowerSensor
{
public:
struct State
{
struct MC_State
{
int32_t consumedEnergy;
uint32_t microSeconds;
} previousState, lastState;
double timeAtRead;
};
PowerSensor(const char *device = "/dev/ttyACM0", const char *dumpFileName = 0);
~PowerSensor();
State read();
void mark(const State &, const char *name = 0, unsigned tag = 0);
void mark(const State &start, const State &stop, const char *name = 0, unsigned tag = 0);
static double Joules(const State &firstState, const State &secondState);
static double seconds(const State &firstState, const State &secondState);
static double Watt(const State &firstState, const State &secondState);
private:
int fd;
std::ofstream *dumpFile;
pthread_t thread;
pthread_mutex_t mutex;
volatile bool stop;
State::MC_State previousState, lastState;
void lock(), unlock();
static void *IOthread(void *);
void *IOthread();
void doMeasurement();
};
#endif
#!/usr/bin/python
# Copyright (C) 2016
# ASTRON (Netherlands Institute for Radio Astronomy) / John W. Romein
# P.O. Box 2, 7990 AA Dwingeloo, the Netherlands
# This file is part of PowerSensor.
# PowerSensor 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.
# PowerSensor 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 PowerSensor. If not, see <http://www.gnu.org/licenses/>.
import getopt, serial, sys, time, struct
maxSensors = 5
def approximates(a, b):
return a / b > .999999 and a / b < 1.000001
class Sensor:
def readEEPROM(self, ser):
self.volt, self.voltPerAmpere, self.nullLevel = struct.unpack("<ffh", ser.read(10))
def writeEEPROM(self, ser):
ser.write(struct.pack("<ffh", self.volt, self.voltPerAmpere, self.nullLevel))
def readLevel(self, ser):
self.level = struct.unpack("<h", ser.read(2))[0]
class EEPROM:
def __init__(self, device = "/dev/ttyUSB0"):
self.ser = None
try:
self.ser = serial.Serial(device, 2000000);
except serial.SerialException:
print "Could not open", device
sys.exit(1)
time.sleep(2) # wait until device reset complete
# read EEPROM sensor configuration
self.sensors = []
self.ser.write('r')
for i in range(maxSensors):
self.sensors.append(Sensor())
self.sensors[i].readEEPROM(self.ser)
# read current sensor levels
self.ser.write('l')
for sensor in self.sensors:
sensor.readLevel(self.ser)
self.currentSensor = self.sensors[0]
def __del__(self):
if self.ser != None: # may be None if constructor threw exception
self.ser.write('w')
self.ser.flush() # serial comm from host to Arduino seems less reliable --> reduce baud rate
time.sleep(.2)
self.ser.baudrate = 115200
for sensor in self.sensors:
sensor.writeEEPROM(self.ser)
self.ser.close()
def setSensor(self, arg):
sensorNumber = int(arg)
if sensorNumber < 0 or sensorNumber >= maxSensors:
print >> sys.stderr, 'illegal sensor number'
sys.exit(1)
self.currentSensor = self.sensors[sensorNumber]
def setNullLevel(self, arg):
if arg == "auto":
arg = self.currentSensor.level - 512
if int(arg) < -512 or int(arg) >= 512:
print >> sys.stderr, 'illegal nullLevel value'
sys.exit(1)
self.currentSensor.nullLevel = int(arg) + 512
def setVolt(self, arg):
self.currentSensor.volt = float(arg)
def setType(self, arg):
if arg in ("acs712-5", "ACS712-5"):
voltPerAmpere = 0.185
elif arg in ("acs712-20", "ACS712-20"):
voltPerAmpere = 0.1
elif arg in ("acs712-30", "ACS712-30"):
voltPerAmpere = 0.66
else:
voltPerAmpere = float(arg)
self.currentSensor.voltPerAmpere = voltPerAmpere
def doPrint(self):
totalWatt = 0.0
for i in range(len(self.sensors)):
sensor = self.sensors[i]
if approximates(sensor.voltPerAmpere, 0.185):
sensorType = "type = ACS712-5"
elif approximates(sensor.voltPerAmpere, 0.1):
sensorType = "type = ACS712-20"
elif approximates(sensor.voltPerAmpere, 0.66):
sensorType = "type = ACS712-30"
else:
sensorType = "voltPerAmpere = " + str(sensor.voltPerAmpere)
if sensor.volt == 0:
print "sensor %u: off" % i
else:
watt = sensor.volt * 2.5 / sensor.voltPerAmpere * (sensor.level - sensor.nullLevel) / 512
totalWatt += watt
print "sensor %u: volt = %f, %s, nullLevel = %d, level = %d, Watt = %f" % (i, sensor.volt, sensorType, sensor.nullLevel - 512, sensor.level - 512, watt)
print "total Watt = %f" % totalWatt
def printHelp():
print "Usage: [-d device] [-s sensor_nr] [-v volatage] [-t sensor_type] [-n null_level] [-s next_sensor_nr] [-o] ... [-p]"
print
print "the order in which the arguments appear is important"
print "-h (--help): print this help message"
print "-d dev (--device=dev): use this device (default: /dev/ttyUSB0)"
print "-s sensor_nr (--sensor=sensor_nr): from now on, configuration parameters apply to this sennsor number"
print "-v volt (--voltage=volt: voltage of the rail being measured"
print "-t sensor_type (--type=sensor_type): use this sensor type (ACS712-5, ACS712-20, ACS712-30, or a number indicating the volt/ampere ratio of the sensor"
print "-n null_level (--nulllevel=null_level): null level to be used for calibration. If no current is flowing, use \"-n auto\""
print "-o (--off): do not use this sensor number"
print "-p (--print): print configuration parameterss and the currently measured sensor values"
def getopts(argv):
eeprom = None
try:
opts, argv = getopt.getopt(argv, "d:hn:ops:t:v:", ["device=", "help", "nulllevel=", "off", "print", "sensor=", "type=", "voltage="])
except getopt.GetoptError:
printHelp()
sys.exit(1)
for opt, arg in opts:
if opt in ("-h", "--help"):
printHelp()
sys.exit()
if opt in ("-d", "--device"):
del eeprom
eeprom = EEPROM(arg)
elif eeprom == None:
eeprom = EEPROM()
if opt in ("-n", "--nullLevel"):
eeprom.setNullLevel(arg)
elif opt in ("-o", "--off"):
eeprom.setVolt(0.0)
elif opt in ("-p", "--print"):
eeprom.doPrint()
elif opt in ("-s", "--sensor"):
eeprom.setSensor(arg)
elif opt in ("-t", "--type"):
eeprom.setType(arg)
elif opt in ("-v", "--volt"):
eeprom.setVolt(arg)
if __name__ == "__main__":
getopts(sys.argv[1:])
// Copyright (C) 2016
// ASTRON (Netherlands Institute for Radio Astronomy) / John W. Romein
// P.O. Box 2, 7990 AA Dwingeloo, the Netherlands
// This file is part of PowerSensor.
// PowerSensor 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.
// PowerSensor 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 PowerSensor. If not, see <http://www.gnu.org/licenses/>.
#include <iostream>
#include <iomanip>
#include "PowerSensor.h"
#include <cstdlib>
#include <iostream>
#include <inttypes.h>
#include <unistd.h>
#define MAX_MICRO_SECONDS 4000000
int main(int argc, char **argv)
{
if (argc > 2) {
std::cerr << "usage: " << argv[0] << " [device]" << std::endl;
exit(1);
}
const char *device = argc == 2 ? argv[1] : "/dev/ttyUSB0";
PowerSensor powerSensor(device, "/tmp/sensor_values");
PowerSensor::State states[2];
states[0] = powerSensor.read();
for (uint32_t micros = 100, i = 1; micros <= MAX_MICRO_SECONDS; micros *= 2, i ^= 1) {
usleep(micros);
states[i] = powerSensor.read();
std::cout << "exp. time: " << micros * 1e-6 << " s, "
"measured: " << PowerSensor::seconds(states[i ^ 1], states[i]) << " s, " <<
PowerSensor::Joules(states[i ^ 1], states[i]) << " J, " <<
PowerSensor::Watt(states[i ^ 1], states[i]) << " W" <<
std::endl;
}
return 0;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment