2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2024-11-22 09:49:50 +00:00

98_Arducounter.pm: Arduino sketch für den ArduCounter hinzugefügt

git-svn-id: https://svn.fhem.de/fhem/trunk@7940 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
ststrobel 2015-02-11 18:15:07 +00:00
parent 31a723aa60
commit 57840f7453

View File

@ -0,0 +1,464 @@
/*
* Sketch for counting impulses in a defined interval
* e.g. for power meters with an s0 interface that can be
* connected to an input of an arduino board
*
* the sketch uses pin change interrupts which can be anabled
* for any of the inputs on e.g. an arduino uno or a jeenode
*
* the pin change Interrupt handling used here
* is based on the arduino playground example on PCINT:
* http://playground.arduino.cc/Main/PcInt
*
* Refer to avr-gcc header files, arduino source and atmega datasheet.
*/
/* Pin to interrupt map:
* D0-D7 = PCINT 16-23 = PCIR2 = PD = PCIE2 = pcmsk2
* D8-D13 = PCINT 0-5 = PCIR0 = PB = PCIE0 = pcmsk0
* A0-A5 (D14-D19) = PCINT 8-13 = PCIR1 = PC = PCIE1 = pcmsk1
*/
#include "pins_arduino.h"
char* version = "ArduCounter V1.0";
char* error = "error ";
/* arduino pins that are typically ok to use
* (some are left out because they are used
* as reset, serial, led or other things on most boards) */
byte allowedPins[20] =
{ 0, 0, 0, 3, 4, 5, 6, 7,
0, 9, 10, 11, 12, 0,
14, 15, 16, 17, 0, 0};
/* Pin change mask for each chip port */
volatile uint8_t *port_to_pcmask[] = {
&PCMSK0,
&PCMSK1,
&PCMSK2
};
/* last PIN States to detect individual pin changes in ISR */
volatile static uint8_t PCintLast[3];
unsigned long intervalMin = 10000; // default 10 sec
unsigned long intervalMax = 120000; // default 2 min
unsigned long timeNextReport;
unsigned long now;
boolean doReport = false;
/* index to the following arrays is the internal PCINT pin number, not the arduino
* pin number because the PCINT pin number corresponds to the physical ports
* and this saves time for mapping to the arduino numbers
*/
/* pin change mode (RISING etc.) as parameter for ISR */
byte PCintMode[24];
/* pin number for PCINT number if active - otherwise -1 */
char PCintActivePin[24];
/* did we get first interrupt yet? */
volatile boolean initialized[24];
/* individual counter for each real pin */
volatile unsigned long counter[24];
/* count at last report to get difference */
unsigned long lastCount[24];
/* millis at first interrupt for current interval
* (is also last interrupt of old interval) */
volatile unsigned long startTime[24];
/* millis at last interrupt */
volatile unsigned long lastTime[24];
/* millis at last report
* to find out when maxInterval is over
* and report has to be done even if
* no impulses were counted */
unsigned long lastReport[24];
/* max for SplitLine */
#define MAXLINEPARTS 5
String inputString = ""; // a string to hold incoming data
boolean newCommand = false; // whether the command string is complete
String linePart[MAXLINEPARTS];
int lineParts = 0;
/* Add a pin to be handled */
int AddPin(uint8_t aPin, int mode) {
uint8_t pcintPin; // PCINT pin number for the pin to be added (this is used as index for most arrays)
volatile uint8_t *pcmask; // pointer to PCMSK0 or 1 or 2 depending on the port corresponding to the pin
unsigned long now = millis();
uint8_t bit = digitalPinToBitMask(aPin); // bit in PCMSK to enable pin change interrupt for this pin (arduino pin number!)
uint8_t port = digitalPinToPort(aPin); // port that this pin belongs to for enabling interrupts for the whole port (arduino pin number!)
if (port == NOT_A_PORT) {
return 1;
} else { // map port to bit in PCIR register
port -= 2;
pcmask = port_to_pcmask[port]; // point to PCMSK0 or 1 or 2 depending on the port corresponding to the pin
}
if (port == 1) { // now calculate the PCINT pin number that corresponds to the arduino pin number
pcintPin = aPin - 6; // port 1: PC0-PC5 (A0-A5 or D14-D19) is PCINT 8-13 (PC6 is reset)
} else { // arduino numbering continues at D14 since PB6/PB7 are used for other things
pcintPin = port * 8 + (aPin % 8); // port 0: PB0-PB5 (D8-D13) is PCINT 0-5 (PB6/PB7 is crystal)
} // port 2: PD0-PD7 (D0-D7) is PCINT 16-23
PCintMode[pcintPin] = mode; // save mode for ISR which uses the pcintPin as index because this is easy to get in ISR
PCintActivePin[pcintPin] = aPin; // save real arduino pin number and flag this pin as active for reporting
initialized[pcintPin] = false; // initialize arrays for this pin
counter[pcintPin] = 0;
lastCount[pcintPin] = 0;
startTime[pcintPin] = now;
lastTime[pcintPin] = now;
lastReport[pcintPin] = now;
*pcmask |= bit; // set the pin change interrupt mask through a pointer to PCMSK0 or 1 or 2 depending on the port corresponding to the pin
PCICR |= 0x01 << port; // enable the interrupt
return 0;
}
/* Remove a pin to be handled */
int RemovePin(uint8_t aPin) {
uint8_t pcintPin;
volatile uint8_t *pcmask;
uint8_t bit = digitalPinToBitMask(aPin);
uint8_t port = digitalPinToPort(aPin);
if (port == NOT_A_PORT) {
return 1;
} else {
port -= 2;
pcmask = port_to_pcmask[port];
}
if (port == 1) { // see comments at AddPin above
pcintPin = port * 8 + (aPin - 14);
} else {
pcintPin = port * 8 + (aPin % 8);
}
PCintActivePin[pcintPin] = -1;
*pcmask &= ~bit; // disable the mask.
if (*pcmask == 0) { // if that's the last one, disable the interrupt.
PCICR &= ~(0x01 << port);
}
return 0;
}
// common interrupt handler. "port" is the PCINT port number (0-2)
static void PCint(uint8_t port) {
uint8_t bit;
uint8_t curr;
uint8_t mask;
uint8_t pcintPin;
// get the pin states for the indicated port.
curr = *portInputRegister(port+2); // current pin states at port
mask = curr ^ PCintLast[port]; // xor gets bits that are different
PCintLast[port] = curr; // store new pin state for next interrupt
if ((mask &= *port_to_pcmask[port]) == 0) { // mask is pins that have changed. screen out non pcint pins.
return; /* no handled pin changed */
}
for (uint8_t i=0; i < 8; i++) {
bit = 0x01 << i; // loop over each pin that changed
if (bit & mask) { // is pin change interrupt enabled for this pin?
pcintPin = port * 8 + i; // pcint pin numbers directly follow the bit numbers, only arduino pin numbers are special
// count if mode is CHANGE, or if mode is RISING and
// the bit is currently high, or if mode is FALLING and bit is low.
if ((PCintMode[pcintPin] == CHANGE
|| ((PCintMode[pcintPin] == RISING) && (curr & bit))
|| ((PCintMode[pcintPin] == FALLING) && !(curr & bit)))) {
lastTime[pcintPin] = millis(); // remember time of this impulse in case it will be the last in the interval
if (initialized[pcintPin]) {
counter[pcintPin]++; // count
} else {
startTime[pcintPin] = lastTime[pcintPin]; // if this is the first impulse on this pin, remember time as first impulse in interval
initialized[pcintPin] = true; // and start counting the next impulse
}
}
}
}
}
SIGNAL(PCINT0_vect) {
PCint(0);
}
SIGNAL(PCINT1_vect) {
PCint(1);
}
SIGNAL(PCINT2_vect) {
PCint(2);
}
/* split a line read from serial into individual words
* and store them in the array lineParts[] */
void SplitLine () {
int index = 0;
int sepPos = 0;
lineParts = 0;
while (sepPos > -1 && index < inputString.length()) { // as long as a blank was found an not at end of line ...
while (inputString.charAt(index) == ' ' // if next char is another blank
&& lineParts < MAXLINEPARTS
&& index < inputString.length()) index++; // skip more blanks
if (index < inputString.length()) {
sepPos = inputString.indexOf(' ', index+1); // find next blank after the word?
if (sepPos == -1)
linePart[lineParts] = inputString.substring(index); // no more blanks -> take rest of string
else
linePart[lineParts] = inputString.substring(index, sepPos); // more blanks -> take word nefore next blank and go on
index = sepPos + 1; // continue looking after last blank
lineParts ++;
}
}
for (int i=lineParts; i<MAXLINEPARTS; i++) // clear the rest of the array
linePart[i] = "";
}
void report() {
int aPin;
unsigned long newCount, countDiff;
unsigned long timeDiff, now;
now = millis();
for (int pcintPin=0; pcintPin<24; pcintPin++) { // go through all observed pins as PCINT pin number
aPin = PCintActivePin[pcintPin]; // take saved arduino pin number
if (aPin >= 0) { // -1 means pin is not active for reporting
newCount = counter[pcintPin]; // get current counter
countDiff = newCount - lastCount[pcintPin]; // how many impulses since last report?
if (countDiff == 0 &&
(now - lastReport[pcintPin] < intervalMax)) // if nothing to report, take next pin
continue;
if (countDiff > 0) { // if there was an impulse, report
timeDiff = lastTime[pcintPin] - startTime[pcintPin]; // time between first and last impulse during interval
lastCount[pcintPin] = newCount; // remember current count for next interval
startTime[pcintPin] = lastTime[pcintPin]; // time of last impulse in this interval becomes also time of first impulse in next interval
} else {
timeDiff = now - startTime[pcintPin]; // there was no impulse, but maxInterval is over: show from last impulse to now
startTime[pcintPin] = now; // start a new interval for next report - last one will be reported as 0 imulses ...
lastTime[pcintPin] = now; // also time of first impulse in next interval
}
Serial.println((String) "R" + aPin + // report on serial out
" C" + newCount +
" D" + countDiff +
" T" + timeDiff);
lastReport[pcintPin] = now; // remember when we reported
}
}
}
/* give status report in between if requested over serial input */
void showCmd() {
unsigned long newCount;
unsigned long countDiff;
unsigned long timeDiff;
char* pName;
Serial.println((String) version);
Serial.println((String) "Min " + intervalMin);
Serial.println((String) "Max " + intervalMax);
for (int i=0; i<24; i++) {
int aPin = PCintActivePin[i];
if (aPin != -1) {
timeDiff = lastTime[i] - startTime[i];
newCount = counter[i];
countDiff = newCount - lastCount[i];
if (!timeDiff)
timeDiff = millis() - startTime[i];
Serial.println((String) "PCInt " + i + " aPin " + aPin
+ " Cnt " + newCount + " (+" + countDiff
+ " ) in " + timeDiff + " Millis");
}
}
Serial.println((String) "Next in " + (timeNextReport - millis()));
}
void addCmd() {
String pinArg = linePart[1]; // given arduino pin number or name as string
String modeArg = linePart[2]; // mode falling, rising or change
String pullArg = linePart[3]; // optional pullup
int aPin = -1;
int mode = RISING; // default
if (pinArg.charAt(0) == 'D' || pinArg.charAt(0) == 'd')
aPin = pinArg.substring(1).toInt(); // arduino pin name starting with a "d"?
if (aPin == -1) {
aPin = pinArg.toInt(); // interpret string as pin number
if (aPin >= 20 || aPin < 1) {
Serial.print(error); Serial.println(aPin);
return;
}
}
if (allowedPins[aPin] == 0) {
Serial.print(error); Serial.println(aPin);
return;
};
if (lineParts > 2) {
if (modeArg.equalsIgnoreCase("f"))
mode = FALLING;
else if (modeArg.equalsIgnoreCase("c"))
mode = CHANGE;
else if (modeArg.equalsIgnoreCase("r"))
mode = RISING;
else {
Serial.print(error); Serial.println(modeArg);
return;
}
}
pinMode (aPin, INPUT);
if (lineParts > 3) {
if (pullArg.equalsIgnoreCase("p"))
digitalWrite (aPin, HIGH); // enable pullup resistor
else {
Serial.print(error); Serial.println(pullArg);
return;
}
}
if (AddPin(aPin, mode) == 0) { // call AddPin with arduino pin number
Serial.print("added "); Serial.println(aPin);
} else {
Serial.print(error); Serial.println(pinArg);
}
}
void removeCmd() {
String pinArg = linePart[1]; // given arduino pin number or name as string in first part
int aPin = -1;
if (pinArg.charAt(0) == 'D' || pinArg.charAt(0) == 'd')
aPin = pinArg.substring(1).toInt(); // arduino pin name starting with a "d"?
if (aPin == -1) {
aPin = pinArg.toInt(); // interpret string as pin number
if (aPin >= 20 || aPin < 1) {
Serial.print(error); Serial.println(aPin);
return;
}
}
if (allowedPins[aPin] == 0) {
Serial.print(error); Serial.println(aPin);
return;
};
if (RemovePin(aPin) == 0) { // call RemovePin with arduino pin number
Serial.println((String)"removed " + aPin);
} else {
Serial.print(error); Serial.println(pinArg);
}
}
void intervalCmd() {
String timeArgMin = linePart[1];
String timeArgMax = linePart[2];
int timeMin = timeArgMin.toInt();
if (timeMin < 1 || timeMin > 3600) {
Serial.print(error); Serial.println(timeArgMin);
return;
}
int timeMax = timeArgMax.toInt();
if (timeMax < 1 || timeMax > 3600 || timeMax < timeMin) {
Serial.print(error); Serial.println(timeArgMax);
return;
}
intervalMin = (long)timeMin * 1000;
intervalMax = (long)timeMax * 1000;
if (millis() + intervalMin < timeNextReport)
timeNextReport = millis() + intervalMin;
}
void doCommand() {
SplitLine();
if (linePart[0].equals("show")) {
showCmd();
} else if (linePart[0].equals("add")) {
addCmd();
} else if (linePart[0].equals("rem")) {
removeCmd();
} else if (linePart[0].equals("int")) {
intervalCmd();
} else {
Serial.print(error); Serial.println(linePart[0]);
}
}
void setup() {
unsigned long now = millis();
for (int pcintPin=0; pcintPin < 24; pcintPin++) {
PCintActivePin[pcintPin] = -1; // set all pins to inactive (-1)
}
timeNextReport = millis() + intervalMin; // time for first output
Serial.begin(9600); // initialize serial
inputString.reserve(200); // reserve 200 bytes for the inputString
interrupts();
Serial.println((String) version + " Setup done.");
}
void loop() {
now = millis();
doReport = false; // check if report nedds to be called
if((long)(now - timeNextReport) >= 0)
doReport = true; // intervalMin is over
else
for (byte pcintPin=0; pcintPin<24; pcintPin++)
if (PCintActivePin[pcintPin] >= 0)
if((long)(now - (lastReport[pcintPin] + intervalMax)) >= 0)
doReport = true; // active pin has not been reported for langer than maxInterval
if (doReport) {
report();
timeNextReport = now + intervalMin; // do it again after interval millis
}
if (newCommand) {
doCommand();
inputString = "";
newCommand = false;
}
}
/*
SerialEvent occurs whenever a new data comes in the
hardware serial RX. This routine is run between each
time loop() runs, so using delay inside loop can delay
response. Multiple bytes of data may be available.
*/
void serialEvent() {
while (Serial.available()) {
char inChar = (char)Serial.read();
if (inChar == '\n' or inChar == '\r') {
if (inputString.length() > 0)
newCommand = true;
} else {
inputString += inChar;
}
}
}