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

Add src

parent a5188217
No related branches found
No related tags found
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