Added oregon v2.1 support to ArduinoTellstick along with some code cleanup

Former-commit-id: d05ee71fa4f2f91afd6452e894385bbcd7020917
This commit is contained in:
Daniel Collin 2016-01-29 10:52:10 +01:00
parent c2d03347ca
commit 1c23a7c360
11 changed files with 391 additions and 116 deletions

View file

@ -3,27 +3,30 @@
#include "buffer.h"
#include "config.h"
/*
* Timer2 interrupt in 16kHz. Samples the RF Rx data pin
*/
ISR(TIMER2_COMPA_vect) {
ISR(TIMER2_COMPA_vect) { //timer2 interrupt 16kHz. Samples the RF Rx data pin
if (!RFRX) { //if no Radio Rx should be performed
if ( !IS_RADIO_RECIEVER_ON() ) { //if no Radio Rx should be performed
return;
}
uint8_t bit = digitalRead(RX_PIN); // optimized "digital_read(7)" = "PIND & 0x1"
uint8_t bit = RX_PIN_READ();
//will store "lows" on even buffer addresses and "highs" on odd buffer addresses
if ( ( ((int)bufferWriteP) & 0x1 ) != bit ) { //compare the bit and the buffer pointer address. true if one is odd and one is even.
if ( ( ((uintptr_t)bufferWriteP) & 0x1 ) != bit ) { //compare the bit and the buffer pointer address. true if one is odd and one is even.
//step the buffer pointer
if ( bufferWriteP + 1 > RF_rxBufferEndP) {
if ( bufferWriteP+1 > RF_rxBufferEndP ) {
*RF_rxBufferStartP = 1; //reset the next data point before going there
bufferWriteP = RF_rxBufferStartP;
} else {
*(bufferWriteP+1) = 1; //reset the next data point before going there
++bufferWriteP;
}
*bufferWriteP = 1; //reset and step once on this addess
} else {
if (*bufferWriteP < 255) { //only values up to 255
++(*bufferWriteP); //step the buffer
if ( *bufferWriteP < 255 ) { //Do not step the value if it already is 255 (max value)
++(*bufferWriteP); //step the buffer value
}
}
@ -34,26 +37,27 @@ void setup() {
Serial.begin(9600);
pinMode(RX_PIN, INPUT); //set RX pin to input
pinMode(TX_PIN, OUTPUT); //ser TX pin as output
setupPins();
//setup timer2 interrupt at 16kHz for RF sampling
cli();//stop interrupts
TCCR2A = 0;// set entire TCCR2A register to 0
TCCR2B = 0;// same for TCCR2B
TCNT2 = 0;//initialize counter value to 0
OCR2A = 124;// = ( (16*10^6) / (16000Hz*8pc) ) - 1 (must be <256)
cli(); //stop interrupts
TCCR2A = 0; // set entire TCCR2A register to 0
TCCR2B = 0; // same for TCCR2B
TCNT2 = 0; //initialize counter value to 0
OCR2A = 124; // = ( (16000000Hz) / (16000Hz*8prescaler) ) - 1 (must be <256)
TCCR2A |= (1 << WGM21); // turn on CTC mode
TCCR2B |= (1 << CS21); // Set CS21 bit for 8 prescaler
TIMSK2 |= (1 << OCIE2A); // enable timer compare interrupt
sei();//allow interrupts
sei(); //allow interrupts
//reset buffer just to be sure
for (uint8_t* p = RF_rxBufferStartP; p <= RF_rxBufferEndP; ++p) {
*p = 0;
}
Serial.println(F("+V2"));
RFRX = true; //start receiving radio data
ACTIVATE_RADIO_RECEIVER();
};//end setup

View file

@ -1,15 +1,70 @@
#include "archtech.h"
#include "buffer.h"
bool parseArctechSelfLearning(uint8_t* bufStartP, uint8_t* bufEndP) { //start points to a "one" buffer byte, end points to a "zero" buffer byte
/*******************************************************************************
--ARCHTECH PROTOCOL SPECIFICATION--
----SIGNAL----
A signal consists of a PREAMBLE, DATA and an POSTAMBLE.
Each signal is sent 4 times in a row to increase the delivery success rate.
----PREAMBLE----
send high for 250 microseconds
send low for 2,500 microseconds
----DATA----
26 bits of transmitter id
1 bit indicating if the on/off bit is targeting a group
1 bit indicating "on" or "off"
4 bits indicating the targeted unit/channel/device
4 bits indicating a absolute dim level (optional)
Total: 32 or 36 bits depending if absolute dimming is used
Each real bit in the data field is sent over air as a pair of two inverted bits.
real bit bits over air
1 = "01"
0 = "10"
Over air a "1"(one) is sent as:
send high for 250 microseconds
send low for 1,250 microseconds
Over air a "0"(zero) is sent as:
send high for 250 microseconds
send low for 250 microseconds
----POSTAMBLE----
send high for 250 microseconds
send low for 10,000 microseconds
*******************************************************************************/
#define ARCHTECH_SHORT_MIN 2
#define ARCHTECH_SHORT_MAX 7
#define ARCHTECH_LONG_MIN 17
#define ARCHTECH_LONG_MAX 23
#define IS_SHORT(b) ( ARCHTECH_SHORT_MIN <= b && b <= ARCHTECH_SHORT_MAX )
#define IS_LONG(b) ( ARCHTECH_LONG_MIN <= b && b <= ARCHTECH_LONG_MAX )
#define IS_ZERO(b1,b2,b3,b4) ( IS_SHORT(b1) && IS_LONG(b2) && IS_SHORT(b3) && IS_SHORT(b4) )
#define IS_ONE(b1,b2,b3,b4) ( IS_SHORT(b1) && IS_SHORT(b2) && IS_SHORT(b3) && IS_LONG(b4) )
bool parseArctechSelfLearning(uint8_t* bufStartP, uint8_t* bufEndP) { //start points to a "high" buffer byte, end points to a "low" buffer byte
uint64_t data = 0;
bool dimValuePresent = false;
bool dimValuePresent;
for (uint8_t i = 0; i < 31; ++i) {
if (calculateBufferPointerDistance(bufStartP, bufEndP) < 4) { //less than 4 more high/low rto read in buffer
return false;
}
uint16_t bitsInBuffer = calculateBufferPointerDistance(bufStartP, bufEndP) / 4; //each bit is representd by 4 high/low
if (bitsInBuffer == 32) {
dimValuePresent = false;
}else if(bitsInBuffer == 36){
dimValuePresent = true;
} else {
return false;
}
for (uint8_t i = 0; i < bitsInBuffer-1; ++i) {
uint8_t b1 = *bufStartP; //no of high
stepBufferPointer(&bufStartP);
@ -20,18 +75,10 @@ bool parseArctechSelfLearning(uint8_t* bufStartP, uint8_t* bufEndP) { //start
uint8_t b4 = *bufStartP; //no of low
stepBufferPointer(&bufStartP);
//TODO: add support for absolute dim values
if (ARCHTECH_LOW_LOW <= b1 && b1 <= ARCHTECH_LOW_HIGH &&
ARCHTECH_LOW_LOW <= b2 && b2 <= ARCHTECH_LOW_HIGH &&
ARCHTECH_LOW_LOW <= b3 && b3 <= ARCHTECH_LOW_HIGH &&
ARCHTECH_HIGH_LOW <= b4 && b4 <= ARCHTECH_HIGH_HIGH) { //"one" is sent over air
if (IS_ONE(b1,b2,b3,b4)) { //"one" is sent over air
data <<= 1; //shift in a zero
data |= 0x1; //add one
} else if (ARCHTECH_LOW_LOW <= b1 && b1 <= ARCHTECH_LOW_HIGH &&
ARCHTECH_HIGH_LOW <= b2 && b2 <= ARCHTECH_HIGH_HIGH &&
ARCHTECH_LOW_LOW <= b3 && b3 <= ARCHTECH_LOW_HIGH &&
ARCHTECH_LOW_LOW <= b4 && b4 <= ARCHTECH_LOW_HIGH) { //"zero" is sent over air
} else if (IS_ZERO(b1,b2,b3,b4)) { //"zero" is sent over air
data <<= 1; //shift in a zero
} else {
return false;

View file

@ -3,11 +3,6 @@
#include "Arduino.h"
#define ARCHTECH_LOW_LOW 2 //a "low" is defined as at least this many "low" samples in a row
#define ARCHTECH_LOW_HIGH 7 //a "low" is defined as at most this many "low" samples in a row
#define ARCHTECH_HIGH_LOW 17 //a "high" is defined as at least this many "high" samples in a row
#define ARCHTECH_HIGH_HIGH 23 //a "high" is defined as at most this many "high" samples in a row
bool parseArctechSelfLearning(uint8_t* bufStartP, uint8_t* bufEndP);
#endif //ARCHTECH_H

View file

@ -4,23 +4,3 @@ uint8_t RF_rxBuffer[512]; //must have and even number of elements
uint8_t* RF_rxBufferStartP = &RF_rxBuffer[0];
uint8_t* RF_rxBufferEndP = &RF_rxBuffer[511];
volatile uint8_t* bufferWriteP = RF_rxBufferStartP;
uint16_t calculateBufferPointerDistance(uint8_t* bufStartP, uint8_t* bufEndP) {
if (bufStartP <= bufEndP) {
return bufEndP - bufStartP + 1;
} else {
return (RF_rxBufferEndP - bufStartP) + (bufEndP - RF_rxBufferStartP) + 2;
}
}; //end calculateBufferPointerDistance
uint8_t* getNextBufferPointer(uint8_t* p) {
if ( p + 1 > RF_rxBufferEndP) {
return RF_rxBufferStartP;
} else {
return p + 1;
}
}; //end getNextBufferPointer
void stepBufferPointer(uint8_t** p) {
*p = getNextBufferPointer(*p);
}; //end stepBufferPointer

View file

@ -3,13 +3,29 @@
#include "Arduino.h"
uint16_t calculateBufferPointerDistance(uint8_t* bufStartP, uint8_t* bufEndP);
uint8_t* getNextBufferPointer(uint8_t* p);
void stepBufferPointer(uint8_t** p);
extern uint8_t RF_rxBuffer[512]; //must have and even number of elements
extern uint8_t* RF_rxBufferStartP;
extern uint8_t* RF_rxBufferEndP;
extern volatile uint8_t* bufferWriteP;
inline uint16_t calculateBufferPointerDistance(uint8_t* bufStartP, uint8_t* bufEndP) {
if (bufStartP <= bufEndP) {
return bufEndP - bufStartP + 1;
} else {
return (RF_rxBufferEndP - bufStartP) + (bufEndP - RF_rxBufferStartP) + 2;
}
}; //end calculateBufferPointerDistance
inline uint8_t* getNextBufferPointer(uint8_t* p) {
if ( p + 1 > RF_rxBufferEndP) {
return RF_rxBufferStartP;
} else {
return p + 1;
}
}; //end getNextBufferPointer
inline void stepBufferPointer(uint8_t** p) {
*p = getNextBufferPointer(*p);
}; //end stepBufferPointer
#endif //BUFFER_H

View file

@ -1,7 +1,25 @@
#ifndef CONFIG_H
#define CONFIG_H
#define RX_PIN 7
#define TX_PIN 8
#include "Arduino.h"
/*
* RX PIN = 7
* TX PIN = 8
*/
inline void setupPins(){
pinMode(7, INPUT);
pinMode(8, OUTPUT);
};
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega168P__) || defined(__AVR_ATmega328P__)
//pin7 = PD7 = port D, bit 8
#define RX_PIN_READ() ( PIND & 0b00000001 ) // optimized "digitalRead(7)"
//pin8 = PB0 = port B, bit 1
#define TX_PIN_LOW() ( PORTB &= 0b01111111 ) // optimized "digitalWrite(8, LOW)"
#define TX_PIN_HIGH() ( PORTB |= 0b10000000 ) // optimized "digitalWrite(8, HIGH)"
#else
#unsupported architecture
#endif
#endif //CONFIG_H

View file

@ -1,29 +0,0 @@
SIGNAL
signal i s two times in a row with a "low" pause of 8192us between them
The message buffer is 9 bytes long
Each signal starts with a preampble, the data and a postamble
PREAMBLE
consists of two "one"-bytes
DATA
9 bytes
POSTAMBLE
consists of one "zero"-byte
A "one" bit is sent over the air as:
high for 512us
low for 1024us
high for 512us
A "zero" bit is sent over the air as:
low for 512us
high for 1024us
low for 512us
1 byte = 8*(512+512+1024)us = 16384us
one signal = 2+9+1 bytes = 12 bytes = 196608us
signal + 8192 + signal = 401408us = 401.408ms

View file

@ -0,0 +1,210 @@
#include "oregonV2.1.h"
#include "buffer.h"
/*******************************************************************************
--OREGON v2.1 PROTOCOL SPECIFICATION--
----SIGNAL----
A signal consists of a PREAMBLE, DATA and an POSTAMBLE.
Each signal is sent 2 times in a row to increase the delivery success rate.
Between the two times there is a "low" pause of 8192us.
----PREAMBLE----
send 16 "1"(ones) over the air
----DATA----
16 bits of sensor type
XX bits of data (where XX <= 64)
The length XX depends on the sensor type. I.e. 0x1A2D => XX=56
Over air a "1"(one) is sent as:
send high for 512 microseconds
send low for 1024 microseconds
send high for 512 microseconds
Over air a "0"(zero) is sent as:
send low for 512 microseconds
send high for 1024 microseconds
send low for 512 microseconds
----POSTAMBLE----
send 8 "0"(zeros) over the air
*******************************************************************************/
#define SMALL_PULSE(x) ( 4<=x && x<=13 )
#define BIG_PULSE(x) ( 12<=x && x<=22 )
#define MORE_DATA_NEEDED -1
#define INVALID_DATA -2
enum {
PARSE_PREAMP = 0,
PARSE_ID,
PARSE_DATA
} static state = PARSE_PREAMP;
static uint8_t byteCnt = 0;
static uint8_t bitCnt = 0;
static uint8_t totByteCnt = 0;
int8_t byteLength = -1;
void reset() {
byteCnt = 0;
bitCnt = 0;
totByteCnt = 0;
state = PARSE_PREAMP;
byteLength = -1;
}; //end reset
void parseOregonStream(bool level, uint8_t count) {
static uint8_t cnt = 0; //used for counting stuff independent in every state
static uint16_t sensorType = 0;
static int8_t byte;
static uint8_t bytesToParse = 0; //the number of bytes left in the data part to parse
static uint8_t buffer[8];
if (level) {
count+=3;
} else {
count-=3;
}
switch(state) {
case PARSE_PREAMP: //look for 25 big pulses followed by one short in a row
if (BIG_PULSE(count)) {
++cnt;
break;
}
if (SMALL_PULSE(count)) {
if (cnt > 25) {
state=PARSE_ID;
sensorType = 0;
}
cnt = 0;
}
break;
case PARSE_ID: //get the two first Bytes
byte = getByte(level, count);
if (byte == INVALID_DATA) {
reset();
cnt = 0;
break;
} else if (byte == MORE_DATA_NEEDED) {
break;
} else {
if (sensorType == 0) {
sensorType = byte << 8;
} else {
sensorType |= byte;
switch (sensorType) {
case 0xEA4C:
bytesToParse = 5;
byteLength = 63;
break;
case 0x0A4D:
case 0x1A2D: //sensor THGR2228N (channel + sensor_id + battery_level + temp + humidity + checksum)
bytesToParse = 7;
byteLength = 79;
break;
default:
reset();
cnt = 0;
return;
}
state = PARSE_DATA;
cnt = 0;
}
}
break;
case PARSE_DATA: //get the remaining data
byte = getByte(level, count);
if (byte == INVALID_DATA) {
reset();
cnt = 0;
break;
} else if (byte == MORE_DATA_NEEDED) {
break;
}
buffer[cnt] = byte;
++cnt;
if (bytesToParse == 0) {
Serial.print(F("+Wclass:sensor;protocol:oregon;model:0x"));
Serial.print(sensorType, HEX);
Serial.print(F(";data:0x"));
for (int8_t i = 0; i < cnt; ++i) {
Serial.print(buffer[i], HEX);
}
Serial.println(F(";"));
reset();
cnt = 0;
}
--bytesToParse;
break;
}
}; //end parseOregonStream
int8_t getByte(bool level, uint8_t count) {
int8_t bit = getBit(level, count);
static uint8_t byte = 0;
if (bit == INVALID_DATA) {
return INVALID_DATA;
} else if (bit == MORE_DATA_NEEDED) {
return MORE_DATA_NEEDED;
}
byte >>= 1;
if (bit) {
byte |= (1<<7);
}
++totByteCnt;
++byteCnt;
if (byteCnt < 8) {
return MORE_DATA_NEEDED;
}
byteCnt=0;
return byte;
}; //end getByte
int8_t getBit(bool level, uint8_t count) {
static bool bit = 0;
if (bitCnt == 0) {
//First pulse must be small
if (!SMALL_PULSE(count)) {
return INVALID_DATA;
}
bitCnt = 1;
} else if (bitCnt == 1) {
//Second pulse must be long
if (!BIG_PULSE(count) && totByteCnt!=byteLength){ //special check - last byte might have strange values
bitCnt = 0;
return INVALID_DATA;
}
bit = level;
bitCnt = 2;
return bit;
} else if (bitCnt == 2) {
//Prepare for next bit
if (level && SMALL_PULSE(count)) {
//Clean start
bitCnt = 0;
} else if (BIG_PULSE(count)) {
//Combined bit
bitCnt = 1;
} else if (SMALL_PULSE(count)) {
//Clean start
bitCnt = 0;
}
return MORE_DATA_NEEDED;
}
return MORE_DATA_NEEDED;
}; //end getBit

View file

@ -0,0 +1,11 @@
#ifndef OREGONV21_H
#define OREGONV21_H
#include "Arduino.h"
void reset();
void parseOregonStream(bool level, uint8_t sampleCount);
int8_t getByte(bool level, uint8_t count);
int8_t getBit(bool level, uint8_t count);
#endif //OREGONV21_H

View file

@ -2,6 +2,9 @@
#include "buffer.h"
#include "archtech.h"
#include "config.h"
#include "oregonV2.1.h"
#define SILENCE_LENGTH 100 //the number of samples with "low" that represents a silent period between two signals
volatile bool RFRX = false;
@ -13,19 +16,29 @@ void parseRadioRXBuffer() {
bool parse = false;
while (bufferReadP != bufferWriteP) { //stop if the read pointer is pointing to where the writing is currently performed
if ( (((int)bufferReadP) & 0x1) == 1 ) { //buffer pointer is odd (stores highs)
uint8_t sampleCount = *bufferReadP;
if ( (((uintptr_t)bufferReadP) & 0x1) == 1 ) { //buffer pointer is odd (stores highs)
if (prevValue >= SILENCE_LENGTH) {
startDataP = bufferReadP; //some new data must starrt here since this is the first "high" after a silent period
startDataP = bufferReadP; //some new data must start here since this is the first "high" after a silent period
}
//stream data to stream parsers
parseOregonStream(HIGH, sampleCount);
} else { //buffer pointer is even (stores lows)
if (*bufferReadP >= SILENCE_LENGTH) { //evaluate if it is time to parse the curernt data
if (sampleCount >= SILENCE_LENGTH) { //evaluate if it is time to parse the curernt data
endDataP = bufferReadP; //this is a silient period and must be the end of a data
parse = true;
break;
}
//stream data to stream parsers
parseOregonStream(LOW, sampleCount);
}
//step the read pointer one step
uint8_t* nextBufferReadP = getNextBufferPointer(bufferReadP);
if (nextBufferReadP == startDataP) { //next pointer will point to startDataP. Data will overflow. Reset the data pointers.
startDataP = 0;
@ -35,7 +48,7 @@ void parseRadioRXBuffer() {
//advance buffer pointer one step
bufferReadP = nextBufferReadP;
prevValue = *bufferReadP; //update previous value
prevValue = sampleCount; //update previous value
}
if (!parse) {
@ -51,50 +64,57 @@ void parseRadioRXBuffer() {
* and the endDataP will point at the first (low) silent period after the data data start.
*/
//make sure that the data set size is big enought to parse.
uint16_t dataSetSize = calculateBufferPointerDistance(startDataP, endDataP);
if (dataSetSize < 32) { //at least 32 low/high
return;
}
//Let all available parsers parse the data set now.
parseArctechSelfLearning(startDataP, endDataP);
//TODO: add more parsers here
//reset the data pointers since the data have been parsed at this point
startDataP = 0;
endDataP = 0;
}; //end radioTask
void sendTCodedData(uint8_t* data, uint8_t T_long, uint8_t* timings, uint8_t repeat, uint8_t pause) {
RFRX = false; //turn off the RF reciever
ACTIVATE_RADIO_TRANSMITTER();
for (uint8_t rep = 0; rep < repeat; ++rep) {
bool nextPinState = HIGH;
for (int i = 0; i < T_long; ++i) {
uint8_t timeIndex = (data[i / 4] >> (6 - (2 * (i % 4)))) & 0x03;
if (timings[timeIndex] > 0 || i == T_long - 1) {
digitalWrite(TX_PIN, nextPinState);
if(nextPinState){
TX_PIN_HIGH();
}else{
TX_PIN_LOW();
}
delayMicroseconds(10 * timings[timeIndex]);
}
nextPinState = !nextPinState;
}
digitalWrite(TX_PIN, LOW);
TX_PIN_LOW();
if (rep < repeat - 1) {
delay(pause);
}
}
RFRX = true; //turn on the RF reciever
ACTIVATE_RADIO_RECEIVER();
};
void sendSCodedData(uint8_t* data, uint8_t pulseCount, uint8_t repeat, uint8_t pause) {
RFRX = false; //turn off the RF reciever
ACTIVATE_RADIO_TRANSMITTER();
for (uint8_t rep = 0; rep < repeat; ++rep) {
bool nextPinState = HIGH;
for (int i = 0; i < pulseCount; ++i) {
if (data[i] > 0 || i == pulseCount - 1) {
digitalWrite(TX_PIN, nextPinState);
if(nextPinState){
TX_PIN_HIGH();
}else{
TX_PIN_LOW();
}
delayMicroseconds(data[i] * 10);
}
nextPinState = !nextPinState;
}
delay(pause);
}
RFRX = false; //turn on the RF reciever
TX_PIN_LOW();
ACTIVATE_RADIO_RECEIVER();
};

View file

@ -3,12 +3,15 @@
#include "Arduino.h"
#define SILENCE_LENGTH 100 //the number of samples with "low" that represents a silent period between two signals
#define ACTIVATE_RADIO_RECEIVER() (RFRX = true)
#define ACTIVATE_RADIO_TRANSMITTER() (RFRX = false)
#define IS_RADIO_RECIEVER_ON() (RFRX)
#define IS_RADIO_TRANSMITTER_ON() (!RFRX)
extern volatile bool RFRX;
void parseRadioRXBuffer();
void sendTCodedData(uint8_t* data, uint8_t T_long, uint8_t* timings, uint8_t repeat, uint8_t pause);
void sendSCodedData(uint8_t* data, uint8_t pulseCount, uint8_t repeat, uint8_t pause);
extern volatile bool RFRX;
#endif //RF_H