mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-04-07 12:58:13 +00:00
98_Arducounter.pm: bug fixes and firmware update
git-svn-id: https://svn.fhem.de/fhem/trunk@19808 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
1eb5a3500f
commit
afd9e4a3f7
@ -80,6 +80,7 @@
|
|||||||
# 2019-02-23 added maxHist attribute
|
# 2019-02-23 added maxHist attribute
|
||||||
# 2019-02-24 added documentation and better return value when get history has no data, option to pass a pinName to get history
|
# 2019-02-24 added documentation and better return value when get history has no data, option to pass a pinName to get history
|
||||||
# query new running config after configuring device
|
# query new running config after configuring device
|
||||||
|
# 2019-06-17 fix log messages and expose logRetries attribute
|
||||||
#
|
#
|
||||||
# ideas / todo:
|
# ideas / todo:
|
||||||
#
|
#
|
||||||
@ -103,7 +104,7 @@ use strict;
|
|||||||
use warnings;
|
use warnings;
|
||||||
use Time::HiRes qw(gettimeofday);
|
use Time::HiRes qw(gettimeofday);
|
||||||
|
|
||||||
my $ArduCounter_Version = '6.14 - 24.2.2019';
|
my $ArduCounter_Version = '6.15 - 17.6.2019';
|
||||||
|
|
||||||
|
|
||||||
my %ArduCounter_sets = (
|
my %ArduCounter_sets = (
|
||||||
@ -192,6 +193,7 @@ sub ArduCounter_Initialize($)
|
|||||||
'configDelay ' . # how many seconds to wait before sending config after reboot of board
|
'configDelay ' . # how many seconds to wait before sending config after reboot of board
|
||||||
'keepAliveDelay ' .
|
'keepAliveDelay ' .
|
||||||
'keepAliveTimeout ' .
|
'keepAliveTimeout ' .
|
||||||
|
'keepAliveRetries ' .
|
||||||
'nextOpenDelay ' .
|
'nextOpenDelay ' .
|
||||||
'silentReconnect:0,1 ' .
|
'silentReconnect:0,1 ' .
|
||||||
'openTimeout ' .
|
'openTimeout ' .
|
||||||
@ -538,14 +540,14 @@ sub ArduCounter_AliveTimeout($)
|
|||||||
my $param = shift;
|
my $param = shift;
|
||||||
my (undef,$name) = split(/:/,$param);
|
my (undef,$name) = split(/:/,$param);
|
||||||
my $hash = $defs{$name};
|
my $hash = $defs{$name};
|
||||||
Log3 $name, 3, "$name: device didn't reply to k(eeepAlive), setting to disconnected and try to reopen";
|
|
||||||
delete $hash->{WaitForAlive};
|
delete $hash->{WaitForAlive};
|
||||||
|
|
||||||
$hash->{KeepAliveRetries} = 0 if (!$hash->{KeepAliveRetries});
|
$hash->{KeepAliveRetries} = 0 if (!$hash->{KeepAliveRetries});
|
||||||
|
|
||||||
if (++$hash->{KeepAliveRetries} > AttrVal($name, "keepAliveRetries", 1)) {
|
if (++$hash->{KeepAliveRetries} > AttrVal($name, "keepAliveRetries", 1)) {
|
||||||
Log3 $name, 3, "$name: no retries left, setting device to disconnected";
|
Log3 $name, 3, "$name: device didn't reply to k(eeepAlive), no retries left, setting device to disconnected";
|
||||||
ArduCounter_Disconnected($hash); # set to Disconnected but let _Ready try to Reopen
|
ArduCounter_Disconnected($hash); # set to Disconnected but let _Ready try to Reopen
|
||||||
|
} else {
|
||||||
|
Log3 $name, 3, "$name: device didn't reply to k(eeepAlive), count=$hash->{KeepAliveRetries}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2070,7 +2072,13 @@ sub ArduCounter_ReadAnswer($$)
|
|||||||
<code>
|
<code>
|
||||||
attr myCounter keepAliveTimeout 3
|
attr myCounter keepAliveTimeout 3
|
||||||
</code>
|
</code>
|
||||||
|
<li><b>keepAliveRetries</b></li>
|
||||||
|
defines how often sending a keepalive is retried before the connection is closed and reopened.<br>
|
||||||
|
It defaults to 2.<br>
|
||||||
|
Example:
|
||||||
|
<code>
|
||||||
|
attr myCounter keepAliveRetries 3
|
||||||
|
</code>
|
||||||
<li><b>nextOpenDelay</b></li>
|
<li><b>nextOpenDelay</b></li>
|
||||||
defines the time that the module waits before retrying to open a disconnected tcp connection. <br>
|
defines the time that the module waits before retrying to open a disconnected tcp connection. <br>
|
||||||
This defaults to 60 seconds.<br>
|
This defaults to 60 seconds.<br>
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,804 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Changes:
|
|
||||||
V1.2
|
|
||||||
27.10.16 - use noInterrupts in report()
|
|
||||||
- avoid reporting very short timeDiff in case of very slow impulses after a report
|
|
||||||
- now reporting is delayed if impulses happened only within in intervalSml
|
|
||||||
- reporting is also delayed if less than countMin pulses counted
|
|
||||||
- extend command "int" for optional intervalSml and countMin
|
|
||||||
29.10.16 - allow interval Min >= Max or Sml > Min
|
|
||||||
which changes behavior to take fixed calculation interval instead of timeDiff between pulses
|
|
||||||
-> if intervalMin = intervalMax, counting will allways follow the reporting interval
|
|
||||||
3.11.16 - more noInterrupt blocks when accessing the non byte volatiles in report
|
|
||||||
V1.3
|
|
||||||
4.11.16 - check min pulse width and add more output,
|
|
||||||
- prefix show output with M
|
|
||||||
V1.4
|
|
||||||
10.11.16 - restructure add Cmd
|
|
||||||
- change syntax for specifying minPulseLengh
|
|
||||||
- res (reset) command
|
|
||||||
V1.6
|
|
||||||
13.12.16 - new startup message logic?, newline before first communication?
|
|
||||||
18.12.16 - replace all code containing Strings, new communication syntax and parsing from Jeelink code
|
|
||||||
V1.7
|
|
||||||
2.1.17 - change message syntax again, report time as well, first and last impulse are reported relative to start of intervall
|
|
||||||
not start of reporting intervall
|
|
||||||
V1.8
|
|
||||||
4.1.17 - fixed a missing break in the case statement for pin definition
|
|
||||||
5.1.17 - cleanup debug logging
|
|
||||||
|
|
||||||
ToDo / Ideas:
|
|
||||||
|
|
||||||
new index scheme to save memory:
|
|
||||||
array to map from pcintPin to new index, limit allowed pins.
|
|
||||||
unused pcintpins point to -1 and vomment states arduino pin number
|
|
||||||
insread of allowedPins array use new function from aPin to pcintPin
|
|
||||||
and then look up in new array for index or -1
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "pins_arduino.h"
|
|
||||||
|
|
||||||
const char versionStr[] PROGMEM = "ArduCounter V1.8";
|
|
||||||
const char errorStr[] PROGMEM = "Error: ";
|
|
||||||
|
|
||||||
#define enablePulseLenChecking 1
|
|
||||||
|
|
||||||
#define SERIAL_SPEED 38400
|
|
||||||
#define MAX_ARDUINO_PIN 24
|
|
||||||
#define MAX_PCINT_PIN 24
|
|
||||||
#define MAX_INPUT_NUM 8
|
|
||||||
|
|
||||||
/* 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[MAX_ARDUINO_PIN] =
|
|
||||||
{ 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 = 30000; // default 30 sec - report after this time if nothing else delays it
|
|
||||||
unsigned long intervalMax = 60000; // default 60 sec - report after this time if it didin't happen before
|
|
||||||
unsigned long intervalSml = 2000; // default 2 secs - continue count if timeDiff is less and intervalMax not over
|
|
||||||
unsigned int countMin = 1; // continue counting if count is less than this and intervalMax not over
|
|
||||||
|
|
||||||
unsigned long timeNextReport;
|
|
||||||
|
|
||||||
/* 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[MAX_PCINT_PIN];
|
|
||||||
/* mode for timing pulse length - derived from PCintMode (RISING etc. */
|
|
||||||
byte PulseMode[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* pin number for PCINT number if active - otherwise -1 */
|
|
||||||
char PCintActivePin[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* did we get first interrupt yet? */
|
|
||||||
volatile boolean initialized[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* individual counter for each real pin */
|
|
||||||
volatile unsigned long counter[MAX_PCINT_PIN];
|
|
||||||
/* count at last report to get difference */
|
|
||||||
unsigned long lastCount[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
#ifdef enablePulseLenChecking
|
|
||||||
/* individual reject counter for each real pin */
|
|
||||||
volatile unsigned int rejectCounter[MAX_PCINT_PIN];
|
|
||||||
unsigned int lastRejCount[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* millis at last interrupt when signal was rising (for filtering with min pulse length) */
|
|
||||||
volatile unsigned long lastPulseStart[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* millis at last interrupt when signal was falling (for filtering with min pulse length) */
|
|
||||||
volatile unsigned long lastPulseEnd[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* minimal pulse length in millis */
|
|
||||||
/* specified instead of rising or falling. isr needs to check change anyway */
|
|
||||||
unsigned int pulseWidthMin[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* sum of pulse lengths for average output */
|
|
||||||
volatile unsigned long pulseWidthSum[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* start of pulse for measuring length */
|
|
||||||
byte pulseWidthStart[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/* millis at first interrupt for current calculation
|
|
||||||
* (is also last interrupt of old interval) */
|
|
||||||
volatile unsigned long startTime[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* millis at last interrupt */
|
|
||||||
volatile unsigned long lastTime[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* millis at first interrupt in a reporting cycle */
|
|
||||||
volatile unsigned long startTimeRepInt[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
|
|
||||||
/* 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[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
unsigned int commandData[MAX_INPUT_NUM];
|
|
||||||
byte commandDataPointer = 0;
|
|
||||||
|
|
||||||
|
|
||||||
int digitalPinToPcIntPin(uint8_t aPin) {
|
|
||||||
uint8_t pcintPin; // PCINT pin number for the pin to be added (index for most arrays)
|
|
||||||
uint8_t port = digitalPinToPort(aPin) - 2; // port that this arduno pin belongs to for enabling interrupts
|
|
||||||
|
|
||||||
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
|
|
||||||
return pcintPin;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Add a pin to be handled */
|
|
||||||
byte AddPinChangeInterrupt(uint8_t aPin) {
|
|
||||||
uint8_t pcintPin; // PCINT pin number for the pin to be added (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
|
|
||||||
|
|
||||||
uint8_t bit = digitalPinToBitMask(aPin); // bit in PCMSK to enable pin change interrupt for this arduino pin
|
|
||||||
uint8_t port = digitalPinToPort(aPin); // port that this arduno pin belongs to for enabling interrupts
|
|
||||||
|
|
||||||
if (port == NOT_A_PORT)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
port -= 2;
|
|
||||||
pcmask = port_to_pcmask[port]; // point to PCMSK0 or 1 or 2 depending on the port corresponding to the pin
|
|
||||||
*pcmask |= bit; // set the pin change interrupt mask through a pointer to PCMSK0 or 1 or 2
|
|
||||||
PCICR |= 0x01 << port; // enable the interrupt
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Remove a pin to be handled */
|
|
||||||
byte RemovePinChangeInterrupt(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 0;
|
|
||||||
|
|
||||||
port -= 2;
|
|
||||||
pcmask = port_to_pcmask[port];
|
|
||||||
*pcmask &= ~bit; // disable the mask.
|
|
||||||
if (*pcmask == 0) { // if that's the last one, disable the interrupt.
|
|
||||||
PCICR &= ~(0x01 << port);
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void PrintErrorMsg() {
|
|
||||||
int len = strlen_P(errorStr);
|
|
||||||
char myChar;
|
|
||||||
for (unsigned char k = 0; k < len; k++) {
|
|
||||||
myChar = pgm_read_byte_near(errorStr + k);
|
|
||||||
Serial.print(myChar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void printVersion() {
|
|
||||||
int len = strlen_P(versionStr);
|
|
||||||
char myChar;
|
|
||||||
for (unsigned char k = 0; k < len; k++) {
|
|
||||||
myChar = pgm_read_byte_near(versionStr + k);
|
|
||||||
Serial.print(myChar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
common interrupt handler. "port" is the PCINT port number (0-2)
|
|
||||||
|
|
||||||
do counting and set start / end time of interval.
|
|
||||||
reporting is not triggered from here.
|
|
||||||
|
|
||||||
only here counter[] is modified
|
|
||||||
lastTime[] is set here and in report
|
|
||||||
startTime[] is set in case a pin was not initialized yet and in report
|
|
||||||
*/
|
|
||||||
static void PCint(uint8_t port) {
|
|
||||||
uint8_t bit;
|
|
||||||
uint8_t curr;
|
|
||||||
uint8_t mask;
|
|
||||||
uint8_t pcintPin;
|
|
||||||
unsigned long now = millis();
|
|
||||||
#ifdef enablePulseLenChecking
|
|
||||||
unsigned long len, gap;
|
|
||||||
#endif
|
|
||||||
// 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) { // did this pin change?
|
|
||||||
pcintPin = port * 8 + i; // pcint pin numbers follow the bits, only arduino pin nums are special
|
|
||||||
|
|
||||||
// count if mode is CHANGE, or if RISING and bit is high, or if mode is FALLING and bit is low.
|
|
||||||
if ((PCintMode[pcintPin] == CHANGE
|
|
||||||
|| ((PCintMode[pcintPin] == RISING) && (curr & bit))
|
|
||||||
|| ((PCintMode[pcintPin] == FALLING) && !(curr & bit)))) {
|
|
||||||
#ifdef enablePulseLenChecking
|
|
||||||
if (pulseWidthMin[pcintPin]) { // check minimal pulse length and gap
|
|
||||||
if ( ( (curr & bit) && pulseWidthStart[pcintPin] == RISING)
|
|
||||||
|| (!(curr & bit) && pulseWidthStart[pcintPin] == FALLING)) { // edge does fit defined start
|
|
||||||
lastPulseStart[pcintPin] = now;
|
|
||||||
continue;
|
|
||||||
} else { // End of defined pulse
|
|
||||||
gap = lastPulseStart[pcintPin] - lastPulseEnd[pcintPin];
|
|
||||||
len = now - lastPulseStart[pcintPin];
|
|
||||||
lastPulseEnd[pcintPin] = now;
|
|
||||||
if (len < pulseWidthMin[pcintPin] || gap < pulseWidthMin[pcintPin]) {
|
|
||||||
rejectCounter[pcintPin]++; // pulse too short
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
pulseWidthSum[pcintPin] += len; // for average calculation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
lastTime[pcintPin] = now; // remember time of in case pulse will be the last in the interval
|
|
||||||
if (!startTimeRepInt[pcintPin]) startTimeRepInt[pcintPin] = now; // time of first impulse in this reporting interval
|
|
||||||
if (initialized[pcintPin]) {
|
|
||||||
counter[pcintPin]++; // count
|
|
||||||
} else {
|
|
||||||
startTime[pcintPin] = lastTime[pcintPin]; // if this is the very first impulse on this pin -> start interval now
|
|
||||||
initialized[pcintPin] = true; // and start counting the next impulse (so far counter is 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
report count and time for pins that are between min and max interval
|
|
||||||
|
|
||||||
lastCount[] is only modified here (count at time of last reporting)
|
|
||||||
lastTime[] is modified here and in ISR - disable interrupts in critcal moments to avoid garbage in var
|
|
||||||
startTime[] is modified only here (or for very first Interrupt in ISR) -> no problem.
|
|
||||||
*/
|
|
||||||
void report() {
|
|
||||||
int aPin;
|
|
||||||
unsigned long count, countDiff;
|
|
||||||
unsigned long timeDiff, now;
|
|
||||||
unsigned long startT, endT;
|
|
||||||
unsigned long avgLen;
|
|
||||||
now = millis();
|
|
||||||
for (int pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++) { // go through all observed pins as PCINT pin number
|
|
||||||
aPin = PCintActivePin[pcintPin]; // take saved arduino pin number
|
|
||||||
if (aPin < 0) continue; // -1 means pin is not active for reporting
|
|
||||||
noInterrupts();
|
|
||||||
startT = startTime[pcintPin];
|
|
||||||
endT = lastTime[pcintPin];
|
|
||||||
count = counter[pcintPin]; // get current counter
|
|
||||||
interrupts();
|
|
||||||
|
|
||||||
timeDiff = endT - startT; // time between first and last impulse during interval
|
|
||||||
countDiff = count - lastCount[pcintPin]; // how many impulses since last report? (works with wrapping)
|
|
||||||
|
|
||||||
if((long)(now - (lastReport[pcintPin] + intervalMax)) >= 0) { // intervalMax is over
|
|
||||||
if ((countDiff >= countMin) && (timeDiff > intervalSml) && (intervalMin != intervalMax)) {
|
|
||||||
// normal procedure
|
|
||||||
lastCount[pcintPin] = count; // remember current count for next interval
|
|
||||||
noInterrupts();
|
|
||||||
startTime[pcintPin] = endT; // time of last impulse in this interval becomes also time of first impulse in next
|
|
||||||
interrupts();
|
|
||||||
} else {
|
|
||||||
// nothing counted or counts happened during a fraction of intervalMin only
|
|
||||||
noInterrupts();
|
|
||||||
lastTime[pcintPin] = now; // don't calculate with last impulse, use now instead
|
|
||||||
startTime[pcintPin] = now; // start a new interval for next report now
|
|
||||||
interrupts();
|
|
||||||
lastCount[pcintPin] = count; // remember current count for next interval
|
|
||||||
timeDiff = now - startT; // special handling - calculation ends now instead of last impulse
|
|
||||||
}
|
|
||||||
} else if((long)(now - (lastReport[pcintPin] + intervalMin)) >= 0) { // minInterval has elapsed
|
|
||||||
if ((countDiff >= countMin) && (timeDiff > intervalSml)) {
|
|
||||||
// normal procedure
|
|
||||||
lastCount[pcintPin] = count; // remember current count for next interval
|
|
||||||
noInterrupts();
|
|
||||||
startTime[pcintPin] = endT; // time of last impulse in this interval becomes also time of first impulse in next
|
|
||||||
interrupts();
|
|
||||||
} else continue; // not enough counted - wait
|
|
||||||
} else continue; // intervalMin not over - wait
|
|
||||||
|
|
||||||
Serial.print(F("R")); // R Report
|
|
||||||
Serial.print(aPin);
|
|
||||||
Serial.print(F(" C")); // C - Count
|
|
||||||
Serial.print(count);
|
|
||||||
Serial.print(F(" D")); // D - Count Diff
|
|
||||||
Serial.print(countDiff);
|
|
||||||
Serial.print(F(" T")); // T - Time
|
|
||||||
Serial.print(timeDiff);
|
|
||||||
Serial.print(F(" N")); // N - now
|
|
||||||
Serial.print((long)now);
|
|
||||||
|
|
||||||
#ifdef enablePulseLenChecking
|
|
||||||
// rejected count ausgeben
|
|
||||||
// evt auch noch average pulse len und gap len
|
|
||||||
if (pulseWidthMin[pcintPin]) { // check minimal pulse length and gap
|
|
||||||
Serial.print(F(" X")); // X Reject
|
|
||||||
Serial.print(rejectCounter[pcintPin] - lastRejCount[pcintPin]);
|
|
||||||
noInterrupts();
|
|
||||||
lastRejCount[pcintPin] = rejectCounter[pcintPin];
|
|
||||||
interrupts();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (countDiff) {
|
|
||||||
Serial.print(F(" F")); // F - first impulse after the one that started the interval
|
|
||||||
Serial.print((long)startTimeRepInt[pcintPin] - startT);
|
|
||||||
Serial.print(F(" L")); // L - last impulse - marking the end of this interval
|
|
||||||
Serial.print((long)endT - startT);
|
|
||||||
startTimeRepInt[pcintPin] = 0;
|
|
||||||
|
|
||||||
#ifdef enablePulseLenChecking
|
|
||||||
if (pulseWidthMin[pcintPin]) {// check minimal pulse length and gap
|
|
||||||
noInterrupts();
|
|
||||||
avgLen = pulseWidthSum[pcintPin] / countDiff;
|
|
||||||
pulseWidthSum[pcintPin] = 0;
|
|
||||||
interrupts();
|
|
||||||
Serial.print(F(" A"));
|
|
||||||
Serial.print(avgLen);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
Serial.println();
|
|
||||||
lastReport[pcintPin] = now; // remember when we reported
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* print status for one pin */
|
|
||||||
void showPin(byte pcintPin) {
|
|
||||||
unsigned long newCount;
|
|
||||||
unsigned long countDiff;
|
|
||||||
unsigned long timeDiff;
|
|
||||||
unsigned long avgLen;
|
|
||||||
|
|
||||||
timeDiff = lastTime[pcintPin] - startTime[pcintPin];
|
|
||||||
newCount = counter[pcintPin];
|
|
||||||
countDiff = newCount - lastCount[pcintPin];
|
|
||||||
if (!timeDiff)
|
|
||||||
timeDiff = millis() - startTime[pcintPin];
|
|
||||||
|
|
||||||
Serial.print(F("PCInt pin "));
|
|
||||||
Serial.print(pcintPin);
|
|
||||||
|
|
||||||
Serial.print(F(", iMode "));
|
|
||||||
switch (PCintMode[pcintPin]) {
|
|
||||||
case RISING: Serial.print(F("rising")); break;
|
|
||||||
case FALLING: Serial.print(F("falling")); break;
|
|
||||||
case CHANGE: Serial.print(F("change")); break;
|
|
||||||
}
|
|
||||||
#ifdef enablePulseLenChecking
|
|
||||||
if (pulseWidthMin[pcintPin] > 0) {
|
|
||||||
Serial.print(F(", min len "));
|
|
||||||
Serial.print(pulseWidthMin[pcintPin]);
|
|
||||||
Serial.print(F(" ms"));
|
|
||||||
switch (pulseWidthStart[pcintPin]) {
|
|
||||||
case RISING: Serial.print(F(" rising")); break;
|
|
||||||
case FALLING: Serial.print(F(" falling")); break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Serial.print(F(", no min len"));
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
Serial.print(F(", count "));
|
|
||||||
Serial.print(newCount);
|
|
||||||
Serial.print(F(" (+"));
|
|
||||||
Serial.print(countDiff);
|
|
||||||
Serial.print(F(") in "));
|
|
||||||
Serial.print(timeDiff);
|
|
||||||
Serial.print(F(" ms"));
|
|
||||||
#ifdef enablePulseLenChecking
|
|
||||||
// rejected count ausgeben
|
|
||||||
// evt auch noch average pulse len und gap len
|
|
||||||
if (pulseWidthMin[pcintPin]) { // check minimal pulse length and gap
|
|
||||||
Serial.print(F(" Rej "));
|
|
||||||
Serial.print(rejectCounter[pcintPin] - lastRejCount[pcintPin]);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
if (countDiff) {
|
|
||||||
Serial.println();
|
|
||||||
Serial.print(F("M first at "));
|
|
||||||
Serial.print((long)startTimeRepInt[pcintPin] - lastReport[pcintPin]);
|
|
||||||
Serial.print(F(", last at "));
|
|
||||||
Serial.print((long)lastTime[pcintPin] - lastReport[pcintPin]);
|
|
||||||
#ifdef enablePulseLenChecking
|
|
||||||
noInterrupts();
|
|
||||||
avgLen = pulseWidthSum[pcintPin] / countDiff;
|
|
||||||
interrupts();
|
|
||||||
Serial.print(F(", avg len "));
|
|
||||||
Serial.print(avgLen);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* give status report in between if requested over serial input */
|
|
||||||
void showCmd() {
|
|
||||||
unsigned long newCount;
|
|
||||||
unsigned long countDiff;
|
|
||||||
unsigned long timeDiff;
|
|
||||||
unsigned long avgLen;
|
|
||||||
char myChar;
|
|
||||||
|
|
||||||
Serial.print(F("M Status: "));
|
|
||||||
printVersion();
|
|
||||||
Serial.println();
|
|
||||||
Serial.print(F("M normal interval "));
|
|
||||||
Serial.println(intervalMin);
|
|
||||||
Serial.print(F("M max interval "));
|
|
||||||
Serial.println(intervalMax);
|
|
||||||
Serial.print(F("M min interval "));
|
|
||||||
Serial.println(intervalSml);
|
|
||||||
Serial.print(F("M min count "));
|
|
||||||
Serial.println(countMin);
|
|
||||||
|
|
||||||
for (byte pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++) {
|
|
||||||
int aPin = PCintActivePin[pcintPin];
|
|
||||||
if (aPin != -1) {
|
|
||||||
timeDiff = lastTime[pcintPin] - startTime[pcintPin];
|
|
||||||
newCount = counter[pcintPin];
|
|
||||||
countDiff = newCount - lastCount[pcintPin];
|
|
||||||
if (!timeDiff)
|
|
||||||
timeDiff = millis() - startTime[pcintPin];
|
|
||||||
Serial.print(F("M pin "));
|
|
||||||
Serial.print(aPin);
|
|
||||||
Serial.print(F(" "));
|
|
||||||
showPin(pcintPin);
|
|
||||||
Serial.println();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Serial.print(F("M Next report in "));
|
|
||||||
Serial.print(timeNextReport - millis());
|
|
||||||
Serial.print(F(" Milliseconds"));
|
|
||||||
Serial.println();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
handle add command.
|
|
||||||
*/
|
|
||||||
void addCmd(unsigned int *values, byte size) {
|
|
||||||
uint8_t pcintPin; // PCINT pin number for the pin to be added (used as index for most arrays)
|
|
||||||
byte mode;
|
|
||||||
unsigned int pw;
|
|
||||||
unsigned long now = millis();
|
|
||||||
|
|
||||||
//Serial.println(F("M Add called"));
|
|
||||||
int aPin = values[0];
|
|
||||||
pcintPin = digitalPinToPcIntPin(aPin);
|
|
||||||
if (aPin >= MAX_ARDUINO_PIN || aPin < 1
|
|
||||||
|| allowedPins[aPin] == 0 || pcintPin > MAX_PCINT_PIN) {
|
|
||||||
PrintErrorMsg();
|
|
||||||
Serial.print(F("Illegal pin specification "));
|
|
||||||
Serial.println(aPin);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (values[1]) {
|
|
||||||
case 2:
|
|
||||||
mode = FALLING;
|
|
||||||
pulseWidthStart[pcintPin] = FALLING;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
mode = RISING;
|
|
||||||
pulseWidthStart[pcintPin] = RISING;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
mode = CHANGE;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
PrintErrorMsg();
|
|
||||||
Serial.print(F("Illegal pin specification "));
|
|
||||||
Serial.println(aPin);
|
|
||||||
}
|
|
||||||
|
|
||||||
pinMode (aPin, INPUT);
|
|
||||||
if (values[2]) {
|
|
||||||
digitalWrite (aPin, HIGH); // enable pullup resistor
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef enablePulseLenChecking
|
|
||||||
PulseMode[pcintPin] = mode; // specified mode also defines pulse level in this case
|
|
||||||
if (values[3] > 0) {
|
|
||||||
pw = values[3];
|
|
||||||
mode = CHANGE;
|
|
||||||
} else {
|
|
||||||
pw = 0;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (!AddPinChangeInterrupt(aPin)) { // add Pin Change Interrupt
|
|
||||||
PrintErrorMsg(); Serial.println(F("AddInt"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
PCintMode[pcintPin] = mode; // save mode for ISR which uses the pcintPin as index
|
|
||||||
|
|
||||||
#ifdef enablePulseLenChecking
|
|
||||||
pulseWidthMin[pcintPin] = pw; // minimal pulse width in millis, 3 if not specified n add cmd
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (PCintActivePin[pcintPin] != aPin) { // in case this pin is already active counting
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
Serial.print(F("M defined pin "));
|
|
||||||
Serial.print(aPin);
|
|
||||||
Serial.print(F(" "));
|
|
||||||
showPin(pcintPin);
|
|
||||||
Serial.println();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
handle rem command.
|
|
||||||
*/
|
|
||||||
void removeCmd(unsigned int *values, byte size) {
|
|
||||||
uint8_t pcintPin; // PCINT pin number for the pin to be added (used as index for most arrays)
|
|
||||||
int aPin = values[0];
|
|
||||||
pcintPin = digitalPinToPcIntPin(aPin);
|
|
||||||
if (aPin >= MAX_ARDUINO_PIN || aPin < 1
|
|
||||||
|| allowedPins[aPin] == 0 || pcintPin > MAX_PCINT_PIN) {
|
|
||||||
PrintErrorMsg();
|
|
||||||
Serial.print(F("Illegal pin specification "));
|
|
||||||
Serial.println(aPin);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!RemovePinChangeInterrupt(aPin)) {
|
|
||||||
PrintErrorMsg(); Serial.println(F("RemInt"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PCintActivePin[pcintPin] = -1;
|
|
||||||
initialized[pcintPin] = false; // reset for next add
|
|
||||||
counter[pcintPin] = 0;
|
|
||||||
lastCount[pcintPin] = 0;
|
|
||||||
#ifdef enablePulseLenChecking
|
|
||||||
pulseWidthMin[pcintPin] = 0;
|
|
||||||
lastRejCount[pcintPin] = 0;
|
|
||||||
rejectCounter[pcintPin] = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
Serial.print(F("M removed "));
|
|
||||||
Serial.println(aPin);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void intervalCmd(unsigned int *values, byte size) {
|
|
||||||
if (size < 4) {
|
|
||||||
PrintErrorMsg();
|
|
||||||
Serial.print(F("size"));
|
|
||||||
Serial.println();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (values[0] < 1 || values[0] > 3600) {
|
|
||||||
PrintErrorMsg(); Serial.println(values[0]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
intervalMin = (long)values[0] * 1000;
|
|
||||||
if (millis() + intervalMin < timeNextReport)
|
|
||||||
timeNextReport = millis() + intervalMin;
|
|
||||||
|
|
||||||
if (values[1] < 1 || values[1] > 3600) {
|
|
||||||
PrintErrorMsg(); Serial.println(values[1]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
intervalMax = (long)values[1]* 1000;
|
|
||||||
|
|
||||||
if (values[2] > 3600) {
|
|
||||||
PrintErrorMsg(); Serial.println(values[2]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (values[2] > 0) {
|
|
||||||
intervalSml = (long)values[2] * 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (values[3]> 0) {
|
|
||||||
countMin = values[3];
|
|
||||||
}
|
|
||||||
Serial.print(F("M intervals set to "));
|
|
||||||
Serial.print(values[0]);
|
|
||||||
Serial.print(F(" "));
|
|
||||||
Serial.print(values[1]);
|
|
||||||
Serial.print(F(" "));
|
|
||||||
Serial.print(values[2]);
|
|
||||||
Serial.print(F(" "));
|
|
||||||
Serial.print(values[3]);
|
|
||||||
Serial.println();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void helloCmd() {
|
|
||||||
Serial.println();
|
|
||||||
printVersion();
|
|
||||||
Serial.println(F("Hello"));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void HandleSerialPort(char c) {
|
|
||||||
static unsigned int value;
|
|
||||||
|
|
||||||
if (c == ',') {
|
|
||||||
if (commandDataPointer + 1 < MAX_INPUT_NUM) {
|
|
||||||
commandData[commandDataPointer++] = value;
|
|
||||||
value = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ('0' <= c && c <= '9') {
|
|
||||||
value = 10 * value + c - '0';
|
|
||||||
}
|
|
||||||
else if ('a' <= c && c <= 'z') {
|
|
||||||
switch (c) {
|
|
||||||
case 'a':
|
|
||||||
commandData[commandDataPointer] = value;
|
|
||||||
addCmd(commandData, ++commandDataPointer);
|
|
||||||
commandDataPointer = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'd':
|
|
||||||
commandData[commandDataPointer] = value;
|
|
||||||
removeCmd(commandData, ++commandDataPointer);
|
|
||||||
commandDataPointer = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'i':
|
|
||||||
commandData[commandDataPointer] = value;
|
|
||||||
intervalCmd(commandData, ++commandDataPointer);
|
|
||||||
commandDataPointer = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'r':
|
|
||||||
setup();
|
|
||||||
commandDataPointer = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 's':
|
|
||||||
showCmd();
|
|
||||||
commandDataPointer = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'h':
|
|
||||||
helloCmd();
|
|
||||||
commandDataPointer = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
commandDataPointer = 0;
|
|
||||||
//PrintErrorMsg(); Serial.println();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
value = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
SIGNAL(PCINT0_vect) {
|
|
||||||
PCint(0);
|
|
||||||
}
|
|
||||||
SIGNAL(PCINT1_vect) {
|
|
||||||
PCint(1);
|
|
||||||
}
|
|
||||||
SIGNAL(PCINT2_vect) {
|
|
||||||
PCint(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
unsigned long now = millis();
|
|
||||||
|
|
||||||
for (int pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++) {
|
|
||||||
PCintActivePin[pcintPin] = -1; // set all pins to inactive (-1)
|
|
||||||
initialized[pcintPin] = false; // initialize arrays for this pin
|
|
||||||
counter[pcintPin] = 0;
|
|
||||||
lastCount[pcintPin] = 0;
|
|
||||||
startTime[pcintPin] = now;
|
|
||||||
lastTime[pcintPin] = now;
|
|
||||||
#ifdef enablePulseLenChecking
|
|
||||||
lastPulseStart[pcintPin] = now;
|
|
||||||
lastPulseEnd[pcintPin] = now;
|
|
||||||
pulseWidthMin[pcintPin] = 0;
|
|
||||||
rejectCounter[pcintPin] = 0;
|
|
||||||
lastRejCount[pcintPin] = 0;
|
|
||||||
#endif
|
|
||||||
lastReport[pcintPin] = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
timeNextReport = millis() + intervalMin; // time for first output
|
|
||||||
Serial.begin(SERIAL_SPEED); // initialize serial
|
|
||||||
delay (500);
|
|
||||||
interrupts();
|
|
||||||
Serial.println();
|
|
||||||
printVersion();
|
|
||||||
Serial.println(F("Started"));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
Main Loop
|
|
||||||
checks if report should be called because timeNextReport is reached
|
|
||||||
or lastReport for one pin is older than intervalMax
|
|
||||||
timeNextReport is only set here (and when interval is changed / at setup)
|
|
||||||
*/
|
|
||||||
void loop() {
|
|
||||||
unsigned long now = millis();
|
|
||||||
|
|
||||||
if (Serial.available()) {
|
|
||||||
HandleSerialPort(Serial.read());
|
|
||||||
}
|
|
||||||
boolean doReport = false; // check if report nedds to be called
|
|
||||||
if((long)(now - timeNextReport) >= 0) // works fine when millis wraps.
|
|
||||||
doReport = true; // intervalMin is over
|
|
||||||
else
|
|
||||||
for (byte pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++)
|
|
||||||
if (PCintActivePin[pcintPin] >= 0)
|
|
||||||
if((long)(now - (lastReport[pcintPin] + intervalMax)) >= 0)
|
|
||||||
doReport = true; // active pin has not been reported for langer than intervalMax
|
|
||||||
if (doReport) {
|
|
||||||
report();
|
|
||||||
timeNextReport = now + intervalMin; // do it again after intervalMin millis
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,899 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* to test pin 4 with interval 10-20 sec do
|
|
||||||
* 4,2,1,30a
|
|
||||||
* 10,20,2,0i
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
Changes:
|
|
||||||
V1.2
|
|
||||||
27.10.16 - use noInterrupts in report()
|
|
||||||
- avoid reporting very short timeDiff in case of very slow impulses after a report
|
|
||||||
- now reporting is delayed if impulses happened only within in intervalSml
|
|
||||||
- reporting is also delayed if less than countMin pulses counted
|
|
||||||
- extend command "int" for optional intervalSml and countMin
|
|
||||||
29.10.16 - allow interval Min >= Max or Sml > Min
|
|
||||||
which changes behavior to take fixed calculation interval instead of timeDiff between pulses
|
|
||||||
-> if intervalMin = intervalMax, counting will allways follow the reporting interval
|
|
||||||
3.11.16 - more noInterrupt blocks when accessing the non byte volatiles in report
|
|
||||||
V1.3
|
|
||||||
4.11.16 - check min pulse width and add more output,
|
|
||||||
- prefix show output with M
|
|
||||||
V1.4
|
|
||||||
10.11.16 - restructure add Cmd
|
|
||||||
- change syntax for specifying minPulseLengh
|
|
||||||
- res (reset) command
|
|
||||||
V1.6
|
|
||||||
13.12.16 - new startup message logic?, newline before first communication?
|
|
||||||
18.12.16 - replace all code containing Strings, new communication syntax and parsing from Jeelink code
|
|
||||||
V1.7
|
|
||||||
2.1.17 - change message syntax again, report time as well, first and last impulse are reported relative to start of intervall
|
|
||||||
not start of reporting intervall
|
|
||||||
V1.8
|
|
||||||
4.1.17 - fixed a missing break in the case statement for pin definition
|
|
||||||
5.1.17 - cleanup debug logging
|
|
||||||
14.10.17 - fix a bug where last port state was not initialized after interrupt attached but this is necessary there
|
|
||||||
23.11.17 - beautify code, add comments, more debugging for users with problematic pulse creation devices
|
|
||||||
28.12.17 - better reportung of first pulse (even if only one pulse and countdiff is 0 but realdiff is 1)
|
|
||||||
30.12.17 - rewrite PCInt, new handling of min pulse length, pulse history ring
|
|
||||||
1.1.18 - check len in add command, allow pin 8 and 13
|
|
||||||
2.1.18 - add history per pin to report line, show negative starting times in show history
|
|
||||||
3.1.18 - little reporting fix (start pos of history report)
|
|
||||||
|
|
||||||
ToDo / Ideas:
|
|
||||||
|
|
||||||
new index scheme to save memory:
|
|
||||||
define new array to map from pcintPin to new index, limit allowed pins.
|
|
||||||
unused pcintpins point to -1 (or some other unused number < 0) and comment states arduino pin number
|
|
||||||
instead of allowedPins array use new function from aPin to pcintPin
|
|
||||||
and then look up in new array for index or -1
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "pins_arduino.h"
|
|
||||||
|
|
||||||
const char versionStr[] PROGMEM = "ArduCounter V2.05";
|
|
||||||
const char errorStr[] PROGMEM = "Error: ";
|
|
||||||
|
|
||||||
#define SERIAL_SPEED 38400
|
|
||||||
#define MAX_ARDUINO_PIN 24
|
|
||||||
#define MAX_PCINT_PIN 24
|
|
||||||
#define MAX_INPUT_NUM 8
|
|
||||||
#define MAX_HIST 20
|
|
||||||
|
|
||||||
/* 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[MAX_ARDUINO_PIN] =
|
|
||||||
{ 0, 0, 0, 3, 4, 5, 6, 7,
|
|
||||||
8, 9, 10, 11, 12, 13,
|
|
||||||
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 = 30000; // default 30 sec - report after this time if nothing else delays it
|
|
||||||
unsigned long intervalMax = 60000; // default 60 sec - report after this time if it didin't happen before
|
|
||||||
unsigned long intervalSml = 2000; // default 2 secs - continue count if timeDiff is less and intervalMax not over
|
|
||||||
unsigned int countMin = 1; // continue counting if count is less than this and intervalMax not over
|
|
||||||
|
|
||||||
unsigned long timeNextReport;
|
|
||||||
|
|
||||||
/* 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
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* did we get first interrupt yet? */
|
|
||||||
volatile boolean initialized[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* individual counters for each real pin */
|
|
||||||
volatile unsigned long counter[MAX_PCINT_PIN];
|
|
||||||
volatile uint8_t counterIgn[MAX_PCINT_PIN]; // ignored first pulse after init
|
|
||||||
volatile unsigned int rejectCounter[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* millis at last level change (for measuring pulse length) */
|
|
||||||
volatile unsigned long lastChange[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* last valid level */
|
|
||||||
volatile uint8_t lastLevel[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* sum of pulse lengths for average output */
|
|
||||||
volatile unsigned long pulseWidthSum[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
|
|
||||||
/* count at last report to get difference */
|
|
||||||
unsigned long lastCount[MAX_PCINT_PIN];
|
|
||||||
unsigned int lastRejCount[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* history ring */
|
|
||||||
volatile uint8_t histIndex;
|
|
||||||
volatile uint8_t histPin[MAX_HIST];
|
|
||||||
volatile uint8_t histLevel[MAX_HIST];
|
|
||||||
volatile unsigned long histTime[MAX_HIST];
|
|
||||||
volatile unsigned long histLen[MAX_HIST];
|
|
||||||
volatile char histAct[MAX_HIST];
|
|
||||||
//volatile uint8_t histI1[MAX_HIST];
|
|
||||||
|
|
||||||
|
|
||||||
/* real arduino pin number for PCINT number if active - otherwise 0 */
|
|
||||||
uint8_t PCintActivePin[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* pin change mode (RISING etc.) as parameter for ISR */
|
|
||||||
uint8_t PCintMode[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* minimal pulse length in millis for filtering */
|
|
||||||
unsigned int pulseWidthMin[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* start of pulse for measuring length */
|
|
||||||
uint8_t pulseWidthStart[MAX_PCINT_PIN]; // FALLING or RISING as defined for each pin
|
|
||||||
|
|
||||||
/* start and end of an interval - typically set by first / last pulse */
|
|
||||||
volatile unsigned long intervalStart[MAX_PCINT_PIN];
|
|
||||||
volatile unsigned long intervalEnd[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* millis at first interrupt in a reporting cycle */
|
|
||||||
volatile unsigned long firstPulse[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* 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[MAX_PCINT_PIN];
|
|
||||||
|
|
||||||
/* input data over serial port */
|
|
||||||
unsigned int commandData[MAX_INPUT_NUM];
|
|
||||||
uint8_t commandDataPointer = 0;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
int digitalPinToPcIntPin(uint8_t aPin) {
|
|
||||||
uint8_t pcintPin; // PCINT pin number for the pin to be added (index for most arrays)
|
|
||||||
uint8_t portIdx = digitalPinToPort(aPin)-2; // index of port that this arduno pin belongs to for enabling interrupts
|
|
||||||
// since the macro maps to defines PB(=2), PC(=3) and PD(=4), we subtract 2
|
|
||||||
// to use the result as array index in this sketch
|
|
||||||
|
|
||||||
if (portIdx == 1) { // now calculate the PCINT pin number that corresponds to the arduino pin number
|
|
||||||
pcintPin = aPin - 6; // portIdx 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 = portIdx*8 + (aPin % 8); // portIdx 0: PB0-PB5 (D8-D13) is PCINT 0-5 (PB6/PB7 is crystal)
|
|
||||||
} // portIdx 2: PD0-PD7 (D0-D7) is PCINT 16-23
|
|
||||||
return pcintPin;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Add a pin to be handled */
|
|
||||||
byte AddPinChangeInterrupt(uint8_t aPin) {
|
|
||||||
volatile uint8_t *pcmask; // pointer to PCMSK0 or 1 or 2 depending on the port corresponding to the pin
|
|
||||||
|
|
||||||
uint8_t bitM = digitalPinToBitMask(aPin); // mask to bit in PCMSK to enable pin change interrupt for this arduino pin
|
|
||||||
uint8_t port = digitalPinToPort(aPin); // port that this arduno pin belongs to for enabling interrupts
|
|
||||||
|
|
||||||
if (port == NOT_A_PORT)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
port -= 2; // from port (PB, PC, PD) to index in our array
|
|
||||||
pcmask = port_to_pcmask[port]; // point to PCMSK0 or 1 or 2 depending on the port corresponding to the pin
|
|
||||||
*pcmask |= bitM; // set the pin change interrupt mask through a pointer to PCMSK0 or 1 or 2
|
|
||||||
PCICR |= 0x01 << port; // enable the interrupt
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Remove a pin to be handled */
|
|
||||||
byte RemovePinChangeInterrupt(uint8_t aPin) {
|
|
||||||
volatile uint8_t *pcmask;
|
|
||||||
|
|
||||||
uint8_t bitM = digitalPinToBitMask(aPin);
|
|
||||||
uint8_t port = digitalPinToPort(aPin);
|
|
||||||
|
|
||||||
if (port == NOT_A_PORT)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
port -= 2; // from port (PB, PC, PD) to index in our array
|
|
||||||
pcmask = port_to_pcmask[port];
|
|
||||||
*pcmask &= ~bitM; // clear the bit in the mask.
|
|
||||||
if (*pcmask == 0) { // if that's the last one, disable the interrupt.
|
|
||||||
PCICR &= ~(0x01 << port);
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void PrintErrorMsg() {
|
|
||||||
int len = strlen_P(errorStr);
|
|
||||||
char myChar;
|
|
||||||
for (unsigned char k = 0; k < len; k++) {
|
|
||||||
myChar = pgm_read_byte_near(errorStr + k);
|
|
||||||
Serial.print(myChar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void printVersion() {
|
|
||||||
int len = strlen_P(versionStr);
|
|
||||||
char myChar;
|
|
||||||
for (unsigned char k = 0; k < len; k++) {
|
|
||||||
myChar = pgm_read_byte_near(versionStr + k);
|
|
||||||
Serial.print(myChar);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
common interrupt handler. "port" is the PCINT port index (0-2), not PB, PC or PD which are mapped to 2-4
|
|
||||||
|
|
||||||
do counting and set start / end time of interval.
|
|
||||||
reporting is not triggered from here.
|
|
||||||
|
|
||||||
only here counter[] is modified
|
|
||||||
intervalEnd[] is set here and in report
|
|
||||||
intervalStart[] is set in case a pin was not initialized yet and in report
|
|
||||||
*/
|
|
||||||
static void PCint(uint8_t port) {
|
|
||||||
uint8_t bit;
|
|
||||||
uint8_t curr;
|
|
||||||
uint8_t delta;
|
|
||||||
uint8_t level;
|
|
||||||
uint8_t pulseLevel;
|
|
||||||
uint8_t pcintPin;
|
|
||||||
unsigned long len;
|
|
||||||
unsigned long now = millis();
|
|
||||||
|
|
||||||
// get the pin states for the indicated port.
|
|
||||||
curr = *portInputRegister(port+2); // current pin states at port (add 2 to get from index to PB, PC or PD)
|
|
||||||
delta = curr ^ PCintLast[port]; // xor gets bits that are different
|
|
||||||
PCintLast[port] = curr; // store new pin state for next interrupt
|
|
||||||
|
|
||||||
if ((delta &= *port_to_pcmask[port]) == 0) // delta is pins that have changed. screen out non pcint pins.
|
|
||||||
return; /* no handled pin changed */
|
|
||||||
|
|
||||||
for (uint8_t i=0; i < 8; i++) { // loop over each pin on the given port that changed
|
|
||||||
bit = 0x01 << i;
|
|
||||||
if (delta & bit) { // did this pin change?
|
|
||||||
pcintPin = port * 8 + i; // pcint pin numbers follow the bits, only arduino pin nums are special
|
|
||||||
level = ((curr & bit) > 0);
|
|
||||||
pulseLevel = (pulseWidthStart[pcintPin] == RISING); // RISING means that pulse is at high level
|
|
||||||
|
|
||||||
len = now - lastChange[pcintPin];
|
|
||||||
histIndex++;
|
|
||||||
if (histIndex >= MAX_HIST) histIndex = 0;
|
|
||||||
histPin[histIndex] = pcintPin;
|
|
||||||
histTime[histIndex] = lastChange[pcintPin];
|
|
||||||
histLen[histIndex] = len;
|
|
||||||
histLevel[histIndex] = !level; // before it changed
|
|
||||||
histAct[histIndex] = ' ';
|
|
||||||
//histI1[histIndex] = lastLevel[pcintPin];
|
|
||||||
|
|
||||||
// go on if mode is CHANGE, or if RISING and bit is high, or if mode is FALLING and bit is low.
|
|
||||||
if (PCintMode[pcintPin] == CHANGE || level == pulseLevel) {
|
|
||||||
|
|
||||||
if (pulseWidthMin[pcintPin]) { // if minimal pulse length defined then check minimal pulse length and gap
|
|
||||||
|
|
||||||
if (len < pulseWidthMin[pcintPin]) {
|
|
||||||
lastChange[pcintPin] = now;
|
|
||||||
if (level != pulseLevel) { // if change to gap level
|
|
||||||
rejectCounter[pcintPin]++; // pulse too short
|
|
||||||
histAct[histIndex] = 'R';
|
|
||||||
} else {
|
|
||||||
histAct[histIndex] = 'X';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
|
|
||||||
if (level == pulseLevel) { // edge does fit defined start, level is now pulse
|
|
||||||
// potential end of a valid gap, now we are at pulse level
|
|
||||||
if (lastLevel[pcintPin] == pulseLevel) { // last remembered valid level was also pulse
|
|
||||||
// last remembered valid level was pulse, now the gap was confirmed.
|
|
||||||
histAct[histIndex] = 'G';
|
|
||||||
} else {
|
|
||||||
// last remembered valid level was a gap -> now we had another valid gap -> inbetween was only a spike -> ignore
|
|
||||||
histAct[histIndex] = 'G';
|
|
||||||
}
|
|
||||||
|
|
||||||
} else { // edge is a change to gap, level is now gap
|
|
||||||
// potential end of a valid pulse, now we are at gap level
|
|
||||||
if (lastLevel[pcintPin] != pulseLevel) { // last remembered valid level was also gap
|
|
||||||
// last remembered valid level was a gap -> now we had valid new pulse -> count
|
|
||||||
|
|
||||||
intervalEnd[pcintPin] = now; // remember time of in case pulse will be the last in the interval
|
|
||||||
if (!firstPulse[pcintPin]) firstPulse[pcintPin] = now; // time of first impulse in this reporting interval
|
|
||||||
if (initialized[pcintPin]) {
|
|
||||||
counter[pcintPin]++; // count
|
|
||||||
} else {
|
|
||||||
counter[pcintPin]++; // count
|
|
||||||
counterIgn[pcintPin]++; // count as to be ignored for diff because it defines the start of the interval
|
|
||||||
intervalStart[pcintPin] = now; // if this is the very first impulse on this pin -> start interval now
|
|
||||||
initialized[pcintPin] = true; // and start counting the next impulse (so far counter is 0)
|
|
||||||
}
|
|
||||||
pulseWidthSum[pcintPin] += len; // for average calculation
|
|
||||||
histAct[histIndex] = 'C';
|
|
||||||
} else {
|
|
||||||
// last remembered valid level was a pulse -> now we had another valid pulse
|
|
||||||
// inbetween was an invalid drop so pulse is already counted.
|
|
||||||
pulseWidthSum[pcintPin] += len; // for average calculation
|
|
||||||
histAct[histIndex] = 'P';
|
|
||||||
}
|
|
||||||
} // change to gap level
|
|
||||||
|
|
||||||
// remember this valid level as lastLevel
|
|
||||||
lastLevel[pcintPin] = !level; // before it changed
|
|
||||||
|
|
||||||
} // if pulse is not too short
|
|
||||||
|
|
||||||
} // if pulseWidth checking
|
|
||||||
}
|
|
||||||
lastChange[pcintPin] = now;
|
|
||||||
} // if bit changed
|
|
||||||
} // for
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* show pulse history ring */
|
|
||||||
void showHistory() {
|
|
||||||
uint8_t hi;
|
|
||||||
Serial.println (F("D pulse history: "));
|
|
||||||
unsigned long now = millis();
|
|
||||||
unsigned long last;
|
|
||||||
uint8_t start = (histIndex + 2) % MAX_HIST;
|
|
||||||
for (uint8_t i = 0; i < MAX_HIST; i++) {
|
|
||||||
hi = (start + i) % MAX_HIST;
|
|
||||||
if (i == 0 || (last <= histTime[hi]+histLen[hi])) {
|
|
||||||
Serial.print (F("D pin "));
|
|
||||||
Serial.print (PCintActivePin[histPin[hi]]);
|
|
||||||
Serial.print (F(" start "));
|
|
||||||
Serial.print ((long) (histTime[hi] - now));
|
|
||||||
Serial.print (F(" len "));
|
|
||||||
Serial.print (histLen[hi]);
|
|
||||||
Serial.print (F(" at "));
|
|
||||||
Serial.print (histLevel[hi]);
|
|
||||||
Serial.print (F(" "));
|
|
||||||
Serial.print (histAct[hi]);
|
|
||||||
Serial.println();
|
|
||||||
}
|
|
||||||
last = histTime[hi];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
report count and time for pins that are between min and max interval
|
|
||||||
|
|
||||||
lastCount[] is only modified here (count at time of last reporting)
|
|
||||||
intervalEnd[] is modified here and in ISR - disable interrupts in critcal moments to avoid garbage in var
|
|
||||||
intervalStart[] is modified only here (or for very first Interrupt in ISR) -> no problem.
|
|
||||||
*/
|
|
||||||
void report() {
|
|
||||||
int aPin;
|
|
||||||
unsigned long count, countIgn, countDiff, realDiff;
|
|
||||||
unsigned long timeDiff, now;
|
|
||||||
unsigned long startT, endT;
|
|
||||||
unsigned long avgLen;
|
|
||||||
now = millis();
|
|
||||||
|
|
||||||
for (int pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++) { // go through all observed pins as PCINT pin number
|
|
||||||
aPin = PCintActivePin[pcintPin]; // take saved arduino pin number
|
|
||||||
if (aPin < 1) continue; // 0 means pin is not active for reporting
|
|
||||||
noInterrupts();
|
|
||||||
startT = intervalStart[pcintPin];
|
|
||||||
endT = intervalEnd[pcintPin];
|
|
||||||
count = counter[pcintPin]; // get current counter (counts all pulses
|
|
||||||
countIgn = counterIgn[pcintPin]; // pulses that mark the beginning of an interval and should not be taken into calculation (happens after restart)
|
|
||||||
interrupts();
|
|
||||||
|
|
||||||
timeDiff = endT - startT; // time between first and last impulse during interval
|
|
||||||
countDiff = count - countIgn - lastCount[pcintPin]; // how many pulses during intervall since last report? (ignore forst pulse after device restart)
|
|
||||||
realDiff = count - lastCount[pcintPin]; // (works with wrapping)
|
|
||||||
if((long)(now - (lastReport[pcintPin] + intervalMax)) >= 0) { // intervalMax is over
|
|
||||||
if ((countDiff >= countMin) && (timeDiff > intervalSml) && (intervalMin != intervalMax)) {
|
|
||||||
// normal procedure
|
|
||||||
lastCount[pcintPin] = count; // remember current count for next interval
|
|
||||||
noInterrupts();
|
|
||||||
intervalStart[pcintPin] = endT; // time of last impulse in this interval becomes also time of first impulse in next
|
|
||||||
counterIgn[pcintPin] = 0;
|
|
||||||
interrupts();
|
|
||||||
} else {
|
|
||||||
// nothing counted or counts happened during a fraction of intervalMin only
|
|
||||||
noInterrupts();
|
|
||||||
intervalEnd[pcintPin] = now; // don't calculate with last impulse, use now instead
|
|
||||||
intervalStart[pcintPin] = now; // start a new interval for next report now
|
|
||||||
counterIgn[pcintPin] = 0;
|
|
||||||
interrupts();
|
|
||||||
lastCount[pcintPin] = count; // remember current count for next interval
|
|
||||||
timeDiff = now - startT; // special handling - calculation ends now instead of last impulse
|
|
||||||
}
|
|
||||||
} else if((long)(now - (lastReport[pcintPin] + intervalMin)) >= 0) { // minInterval has elapsed
|
|
||||||
if ((countDiff >= countMin) && (timeDiff > intervalSml)) {
|
|
||||||
// normal procedure
|
|
||||||
lastCount[pcintPin] = count; // remember current count for next interval
|
|
||||||
noInterrupts();
|
|
||||||
intervalStart[pcintPin] = endT; // time of last impulse in this interval becomes also time of first impulse in next
|
|
||||||
counterIgn[pcintPin] = 0;
|
|
||||||
interrupts();
|
|
||||||
} else continue; // not enough counted - wait
|
|
||||||
} else continue; // intervalMin not over - wait
|
|
||||||
|
|
||||||
Serial.print(F("R")); // R Report
|
|
||||||
Serial.print(aPin);
|
|
||||||
Serial.print(F(" C")); // C - Count
|
|
||||||
Serial.print(count);
|
|
||||||
Serial.print(F(" D")); // D - Count Diff (without pulse that marks the begin of an interval)
|
|
||||||
Serial.print(countDiff);
|
|
||||||
Serial.print(F(" R")); // R - real Diff for incrementing long counter in Fhem - includes even the first pulse after restart
|
|
||||||
Serial.print(realDiff);
|
|
||||||
Serial.print(F(" T")); // T - Time
|
|
||||||
Serial.print(timeDiff);
|
|
||||||
Serial.print(F(" N")); // N - now
|
|
||||||
Serial.print((long)now);
|
|
||||||
|
|
||||||
// rejected count ausgeben
|
|
||||||
// evt auch noch average pulse len und gap len
|
|
||||||
if (pulseWidthMin[pcintPin]) { // check minimal pulse length and gap
|
|
||||||
Serial.print(F(" X")); // X Reject
|
|
||||||
Serial.print(rejectCounter[pcintPin] - lastRejCount[pcintPin]);
|
|
||||||
noInterrupts();
|
|
||||||
lastRejCount[pcintPin] = rejectCounter[pcintPin];
|
|
||||||
interrupts();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (realDiff) {
|
|
||||||
Serial.print(F(" F")); // F - first impulse after the one that started the interval
|
|
||||||
Serial.print((long)firstPulse[pcintPin] - startT);
|
|
||||||
Serial.print(F(" L")); // L - last impulse - marking the end of this interval
|
|
||||||
Serial.print((long)endT - startT);
|
|
||||||
firstPulse[pcintPin] = 0;
|
|
||||||
|
|
||||||
if (pulseWidthMin[pcintPin]) {// check minimal pulse length and gap
|
|
||||||
noInterrupts();
|
|
||||||
avgLen = pulseWidthSum[pcintPin] / countDiff;
|
|
||||||
pulseWidthSum[pcintPin] = 0;
|
|
||||||
interrupts();
|
|
||||||
Serial.print(F(" A"));
|
|
||||||
Serial.print(avgLen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t hi;
|
|
||||||
boolean first = true;
|
|
||||||
uint8_t start = (histIndex + 2) % MAX_HIST;
|
|
||||||
unsigned long last;
|
|
||||||
Serial.print (F(" H"));
|
|
||||||
for (uint8_t i = 0; i < MAX_HIST; i++) {
|
|
||||||
hi = (start + i) % MAX_HIST;
|
|
||||||
if (histPin[hi] == pcintPin) {
|
|
||||||
if (first || (last <= histTime[hi]+histLen[hi])) {
|
|
||||||
if (!first)
|
|
||||||
Serial.print (F(", "));
|
|
||||||
//Serial.print (F(""));
|
|
||||||
Serial.print ((long) (histTime[hi] - now));
|
|
||||||
Serial.print (F("/"));
|
|
||||||
Serial.print (histLen[hi]);
|
|
||||||
Serial.print (F(":"));
|
|
||||||
Serial.print (histLevel[hi]);
|
|
||||||
//Serial.print (F(" "));
|
|
||||||
Serial.print (histAct[hi]);
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
last = histTime[hi];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println();
|
|
||||||
lastReport[pcintPin] = now; // remember when we reported
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* print status for one pin */
|
|
||||||
void showPin(byte pcintPin) {
|
|
||||||
unsigned long newCount, countIgn, countDiff;
|
|
||||||
unsigned long timeDiff;
|
|
||||||
unsigned long avgLen;
|
|
||||||
|
|
||||||
timeDiff = intervalEnd[pcintPin] - intervalStart[pcintPin];
|
|
||||||
newCount = counter[pcintPin];
|
|
||||||
countIgn = counterIgn[pcintPin];
|
|
||||||
countDiff = newCount - countIgn - lastCount[pcintPin];
|
|
||||||
if (!timeDiff)
|
|
||||||
timeDiff = millis() - intervalStart[pcintPin];
|
|
||||||
|
|
||||||
Serial.print(F("PCInt pin "));
|
|
||||||
Serial.print(pcintPin);
|
|
||||||
|
|
||||||
Serial.print(F(", iMode "));
|
|
||||||
switch (PCintMode[pcintPin]) {
|
|
||||||
case RISING: Serial.print(F("rising")); break;
|
|
||||||
case FALLING: Serial.print(F("falling")); break;
|
|
||||||
case CHANGE: Serial.print(F("change")); break;
|
|
||||||
}
|
|
||||||
if (pulseWidthMin[pcintPin] > 0) {
|
|
||||||
Serial.print(F(", min len "));
|
|
||||||
Serial.print(pulseWidthMin[pcintPin]);
|
|
||||||
Serial.print(F(" ms"));
|
|
||||||
switch (pulseWidthStart[pcintPin]) {
|
|
||||||
case RISING: Serial.print(F(" rising")); break;
|
|
||||||
case FALLING: Serial.print(F(" falling")); break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Serial.print(F(", no min len"));
|
|
||||||
}
|
|
||||||
Serial.print(F(", count "));
|
|
||||||
Serial.print(newCount);
|
|
||||||
Serial.print(F(" (+"));
|
|
||||||
Serial.print(countDiff);
|
|
||||||
Serial.print(F(") in "));
|
|
||||||
Serial.print(timeDiff);
|
|
||||||
Serial.print(F(" ms"));
|
|
||||||
|
|
||||||
// rejected count ausgeben
|
|
||||||
// evt auch noch average pulse len und gap len
|
|
||||||
if (pulseWidthMin[pcintPin]) { // check minimal pulse length and gap
|
|
||||||
Serial.print(F(" Rej "));
|
|
||||||
Serial.print(rejectCounter[pcintPin] - lastRejCount[pcintPin]);
|
|
||||||
}
|
|
||||||
if (countDiff) {
|
|
||||||
Serial.println();
|
|
||||||
Serial.print(F("M first at "));
|
|
||||||
Serial.print((long)firstPulse[pcintPin] - lastReport[pcintPin]);
|
|
||||||
Serial.print(F(", last at "));
|
|
||||||
Serial.print((long)intervalEnd[pcintPin] - lastReport[pcintPin]);
|
|
||||||
noInterrupts();
|
|
||||||
avgLen = pulseWidthSum[pcintPin] / countDiff;
|
|
||||||
interrupts();
|
|
||||||
Serial.print(F(", avg len "));
|
|
||||||
Serial.print(avgLen);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* give status report in between if requested over serial input */
|
|
||||||
void showCmd() {
|
|
||||||
Serial.print(F("M Status: "));
|
|
||||||
printVersion();
|
|
||||||
Serial.println();
|
|
||||||
Serial.print(F("M normal interval "));
|
|
||||||
Serial.println(intervalMin);
|
|
||||||
Serial.print(F("M max interval "));
|
|
||||||
Serial.println(intervalMax);
|
|
||||||
Serial.print(F("M min interval "));
|
|
||||||
Serial.println(intervalSml);
|
|
||||||
Serial.print(F("M min count "));
|
|
||||||
Serial.println(countMin);
|
|
||||||
|
|
||||||
for (byte pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++) {
|
|
||||||
int aPin = PCintActivePin[pcintPin];
|
|
||||||
if (aPin > 0) {
|
|
||||||
Serial.print(F("M pin "));
|
|
||||||
Serial.print(aPin);
|
|
||||||
Serial.print(F(" "));
|
|
||||||
showPin(pcintPin);
|
|
||||||
Serial.println();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Serial.print(F("M Next report in "));
|
|
||||||
Serial.print(timeNextReport - millis());
|
|
||||||
Serial.print(F(" Milliseconds"));
|
|
||||||
Serial.println();
|
|
||||||
showHistory();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
handle add command.
|
|
||||||
todo: check size and clear options not passed
|
|
||||||
*/
|
|
||||||
void addCmd(unsigned int *values, byte size) {
|
|
||||||
uint8_t pcintPin; // PCINT pin number for the pin to be added (used as index for most arrays)
|
|
||||||
byte mode = 2;
|
|
||||||
uint8_t port;
|
|
||||||
unsigned int pw;
|
|
||||||
unsigned long now = millis();
|
|
||||||
|
|
||||||
|
|
||||||
//Serial.println(F("M Add called"));
|
|
||||||
int aPin = values[0]; // value 0 is pin number
|
|
||||||
pcintPin = digitalPinToPcIntPin(aPin);
|
|
||||||
if (aPin >= MAX_ARDUINO_PIN || aPin < 1
|
|
||||||
|| allowedPins[aPin] == 0 || pcintPin > MAX_PCINT_PIN) {
|
|
||||||
PrintErrorMsg();
|
|
||||||
Serial.print(F("Illegal pin specification "));
|
|
||||||
Serial.println(aPin);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
port = digitalPinToPort(aPin) - 2;
|
|
||||||
|
|
||||||
switch (values[1]) { // value 1 is rising / falling etc.
|
|
||||||
case 2:
|
|
||||||
mode = FALLING;
|
|
||||||
pulseWidthStart[pcintPin] = FALLING;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
mode = RISING;
|
|
||||||
pulseWidthStart[pcintPin] = RISING;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
mode = CHANGE;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
PrintErrorMsg();
|
|
||||||
Serial.print(F("Illegal mode for pin specification "));
|
|
||||||
Serial.println(aPin);
|
|
||||||
}
|
|
||||||
|
|
||||||
pinMode (aPin, INPUT);
|
|
||||||
if (size > 2 && values[2]) { // value 2 is pullup
|
|
||||||
digitalWrite (aPin, HIGH); // enable pullup resistor
|
|
||||||
}
|
|
||||||
|
|
||||||
if (size > 3 && values[3] > 0) { // value 3 is min length (if given)
|
|
||||||
pw = values[3];
|
|
||||||
mode = CHANGE;
|
|
||||||
} else {
|
|
||||||
pw = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!AddPinChangeInterrupt(aPin)) { // add Pin Change Interrupt
|
|
||||||
PrintErrorMsg(); Serial.println(F("AddInt"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
PCintMode[pcintPin] = mode; // save mode for ISR which uses the pcintPin as index
|
|
||||||
|
|
||||||
pulseWidthMin[pcintPin] = pw; // minimal pulse width in millis, 0 if not specified in add cmd todo: needs fixing! values[3] might contain data from last command
|
|
||||||
|
|
||||||
if (PCintActivePin[pcintPin] != aPin) { // in case this pin is not already active counting
|
|
||||||
PCintLast[port] = *portInputRegister(port+2); // current pin states at port
|
|
||||||
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;
|
|
||||||
counterIgn[pcintPin] = 0;
|
|
||||||
lastCount[pcintPin] = 0;
|
|
||||||
intervalStart[pcintPin] = now;
|
|
||||||
intervalEnd[pcintPin] = now;
|
|
||||||
lastReport[pcintPin] = now; // next reporting cycle is probably earlier than now+intervalMin (already started) so report will be later than next interval
|
|
||||||
lastChange[pcintPin] = now;
|
|
||||||
rejectCounter[pcintPin] = 0;
|
|
||||||
lastRejCount[pcintPin] = 0;
|
|
||||||
}
|
|
||||||
Serial.print(F("M defined pin "));
|
|
||||||
Serial.print(aPin);
|
|
||||||
Serial.print(F(" "));
|
|
||||||
showPin(pcintPin);
|
|
||||||
Serial.println();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
handle rem command.
|
|
||||||
*/
|
|
||||||
void removeCmd(unsigned int *values, byte size) {
|
|
||||||
uint8_t pcintPin; // PCINT pin number for the pin to be added (used as index for most arrays)
|
|
||||||
int aPin = values[0];
|
|
||||||
pcintPin = digitalPinToPcIntPin(aPin);
|
|
||||||
if (size < 1 || aPin >= MAX_ARDUINO_PIN || aPin < 1
|
|
||||||
|| allowedPins[aPin] == 0 || pcintPin > MAX_PCINT_PIN) {
|
|
||||||
PrintErrorMsg();
|
|
||||||
Serial.print(F("Illegal pin specification "));
|
|
||||||
Serial.println(aPin);
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!RemovePinChangeInterrupt(aPin)) {
|
|
||||||
PrintErrorMsg(); Serial.println(F("RemInt"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
PCintActivePin[pcintPin] = 0;
|
|
||||||
initialized[pcintPin] = false; // reset for next add
|
|
||||||
counter[pcintPin] = 0;
|
|
||||||
counterIgn[pcintPin] = 0;
|
|
||||||
lastCount[pcintPin] = 0;
|
|
||||||
pulseWidthMin[pcintPin] = 0;
|
|
||||||
lastRejCount[pcintPin] = 0;
|
|
||||||
rejectCounter[pcintPin] = 0;
|
|
||||||
|
|
||||||
Serial.print(F("M removed "));
|
|
||||||
Serial.println(aPin);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void intervalCmd(unsigned int *values, byte size) {
|
|
||||||
if (size < 4) { // i command always gets 4 values: min, max, sml, cntMin
|
|
||||||
PrintErrorMsg();
|
|
||||||
Serial.print(F("size"));
|
|
||||||
Serial.println();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (values[0] < 1 || values[0] > 3600) {
|
|
||||||
PrintErrorMsg(); Serial.println(values[0]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
intervalMin = (long)values[0] * 1000;
|
|
||||||
if (millis() + intervalMin < timeNextReport)
|
|
||||||
timeNextReport = millis() + intervalMin;
|
|
||||||
|
|
||||||
if (values[1] < 1 || values[1] > 3600) {
|
|
||||||
PrintErrorMsg(); Serial.println(values[1]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
intervalMax = (long)values[1]* 1000;
|
|
||||||
|
|
||||||
if (values[2] > 3600) {
|
|
||||||
PrintErrorMsg(); Serial.println(values[2]);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
intervalSml = (long)values[2] * 1000;
|
|
||||||
countMin = values[3];
|
|
||||||
|
|
||||||
Serial.print(F("M intervals set to "));
|
|
||||||
Serial.print(values[0]);
|
|
||||||
Serial.print(F(" "));
|
|
||||||
Serial.print(values[1]);
|
|
||||||
Serial.print(F(" "));
|
|
||||||
Serial.print(values[2]);
|
|
||||||
Serial.print(F(" "));
|
|
||||||
Serial.print(values[3]);
|
|
||||||
Serial.println();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void helloCmd() {
|
|
||||||
Serial.println();
|
|
||||||
printVersion();
|
|
||||||
Serial.println(F("Hello"));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static void HandleSerialPort(char c) {
|
|
||||||
static unsigned int value;
|
|
||||||
|
|
||||||
if (c == ',') {
|
|
||||||
if (commandDataPointer < (MAX_INPUT_NUM - 1)) {
|
|
||||||
commandData[commandDataPointer] = value;
|
|
||||||
commandDataPointer++;
|
|
||||||
value = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ('0' <= c && c <= '9') {
|
|
||||||
value = 10 * value + c - '0';
|
|
||||||
}
|
|
||||||
else if ('a' <= c && c <= 'z') {
|
|
||||||
switch (c) {
|
|
||||||
case 'a':
|
|
||||||
commandData[commandDataPointer] = value;
|
|
||||||
addCmd(commandData, ++commandDataPointer);
|
|
||||||
break;
|
|
||||||
case 'd':
|
|
||||||
commandData[commandDataPointer] = value;
|
|
||||||
removeCmd(commandData, ++commandDataPointer);
|
|
||||||
break;
|
|
||||||
case 'i':
|
|
||||||
commandData[commandDataPointer] = value;
|
|
||||||
intervalCmd(commandData, ++commandDataPointer);
|
|
||||||
break;
|
|
||||||
case 'r':
|
|
||||||
initialize();
|
|
||||||
break;
|
|
||||||
case 's':
|
|
||||||
showCmd();
|
|
||||||
break;
|
|
||||||
case 'h':
|
|
||||||
helloCmd();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
//PrintErrorMsg(); Serial.println();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
commandDataPointer = 0;
|
|
||||||
value = 0;
|
|
||||||
for (byte i=0; i < MAX_INPUT_NUM; i++)
|
|
||||||
commandData[i] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
SIGNAL(PCINT0_vect) {
|
|
||||||
PCint(0);
|
|
||||||
}
|
|
||||||
SIGNAL(PCINT1_vect) {
|
|
||||||
PCint(1);
|
|
||||||
}
|
|
||||||
SIGNAL(PCINT2_vect) {
|
|
||||||
PCint(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void initialize() {
|
|
||||||
unsigned long now = millis();
|
|
||||||
|
|
||||||
Serial.println();
|
|
||||||
printVersion();
|
|
||||||
Serial.println(F(" Started"));
|
|
||||||
|
|
||||||
for (int pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++) {
|
|
||||||
PCintActivePin[pcintPin] = 0; // set all pins to inactive (0)
|
|
||||||
initialized[pcintPin] = false; // initialize arrays for this pin
|
|
||||||
counter[pcintPin] = 0;
|
|
||||||
counterIgn[pcintPin] = 0;
|
|
||||||
lastCount[pcintPin] = 0;
|
|
||||||
intervalStart[pcintPin] = now;
|
|
||||||
intervalEnd[pcintPin] = now;
|
|
||||||
lastChange[pcintPin] = now;
|
|
||||||
pulseWidthMin[pcintPin] = 0;
|
|
||||||
rejectCounter[pcintPin] = 0;
|
|
||||||
lastRejCount[pcintPin] = 0;
|
|
||||||
lastReport[pcintPin] = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (unsigned int port=0; port <= 2; port++) {
|
|
||||||
PCintLast[port] = *portInputRegister(port+2); // current pin states at port
|
|
||||||
}
|
|
||||||
|
|
||||||
timeNextReport = millis() + intervalMin; // time for first output
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(SERIAL_SPEED); // initialize serial
|
|
||||||
delay (500);
|
|
||||||
interrupts();
|
|
||||||
initialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
Main Loop
|
|
||||||
checks if report should be called because timeNextReport is reached
|
|
||||||
or lastReport for one pin is older than intervalMax
|
|
||||||
timeNextReport is only set here (and when interval is changed / at setup)
|
|
||||||
*/
|
|
||||||
void loop() {
|
|
||||||
unsigned long now = millis();
|
|
||||||
|
|
||||||
if (Serial.available()) {
|
|
||||||
HandleSerialPort(Serial.read());
|
|
||||||
}
|
|
||||||
boolean doReport = false; // check if report nedds to be called
|
|
||||||
if((long)(now - timeNextReport) >= 0) // works fine when millis wraps.
|
|
||||||
doReport = true; // intervalMin is over
|
|
||||||
else
|
|
||||||
for (byte pcintPin=0; pcintPin < MAX_PCINT_PIN; pcintPin++)
|
|
||||||
if (PCintActivePin[pcintPin] > 0)
|
|
||||||
if((long)(now - (lastReport[pcintPin] + intervalMax)) >= 0)
|
|
||||||
doReport = true; // active pin has not been reported for langer than intervalMax
|
|
||||||
|
|
||||||
if (doReport) {
|
|
||||||
report();
|
|
||||||
timeNextReport = now + intervalMin; // do it again after intervalMin millis
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user