Created
April 8, 2024 07:36
-
-
Save dzungpv/9ba5fb971729aa1c3bfc44192ba75351 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* P1P2Serial: Library for reading/writing Daikin/Rotex P1P2 protocol | |
* | |
* Copyright (c) 2019-2022 Arnold Niessen, arnold.niessen-at-gmail-dot-com - licensed under CC BY-NC-ND 4.0 with exceptions (see LICENSE.md) | |
* | |
* Version history | |
* 20230604 v0.9.38 H-link branch merged into main branch | |
* 20230604 v0.9.37 Support for V1.2 hardware | |
* 20221028 v0.9.23 ADC code | |
* 20220918 v0.9.22 scopemode also for writes, focused on actual errors, fake error generation for test purposes, removing OLDP1P2LIB | |
* 20220830 v0.9.18 version alignment with example programs (last version supporting OLDP1P2LIB) | |
* 20220817 v0.9.17 read-back-verification bug fixes in new and old library, scopemode | |
* 20220811 v0.9.16 Added S_TIMER switch making TIMER0 use optional | |
* 20220808 v0.9.15 LEDs on P1P2-ESP-Interface all on until first byte received | |
* 20220802 v0.9.14 major rewrite of send and receive method to spread CPU load over time and allow 8MHz ATmega operation | |
* (until v0.9.18, old library version is still available as fall-back solution (#define OLDP1P2LIB below)) | |
* 20220511 v0.9.12 various minor bug fixes | |
* 20200109 v0.9.11 allow short pauses between bytes within a packet (for KLIC-DA device, to avoid detecting each byte as individual packet) | |
* 20190914 v0.9.10 upon bus collision detection, write buffer is emptied | |
* 20190914 v0.9.9 Added writeready() | |
* 20190908 v0.9.8 Removed EOP signal in errorbuf results returned by readpacket(); as of now errorbuf contains only real error flags | |
* 20190831 v0.9.7 Switch from TIMER0 to TIMER2 to avoid interference with millis() and readBytesUntil(), reduced RX_BUFFER_SIZE to 50 | |
* 20190824 v0.9.6 Added packetavailable() | |
* 20190820 v0.9.5 Changed delay behaviour, timeout added | |
* 20190817 v0.9.4 Clean up, bug fixes, improved ms counter, prescaler reset added, time measurement changed, delta/error reporting separated | |
* 20190505 v0.9.3 Changed error handling and corrected deltabuf type in readpacket | |
* 20190428 v0.9.2 Added setEcho(b), readpacket() and writepacket() | |
* 20190409 v0.9.1 Improved setDelay() | |
* 20190407 v0.9.0 Improved reading, writing, and meta-data; added support for timed writings and collision detection; added stand-alone hardware-debug mode | |
* 20190303 v0.0.1 initial release; support for raw monitoring and hex monitoring | |
* | |
* Thanks to Krakra for providing the hints and references to the HBS and MM1192 documents on | |
* https://community.openenergymonitor.org/t/hack-my-heat-pump-and-publish-data-onto-emoncms | |
* to Bart Ellast for providing explanations and sample output from his heat pump, and | |
* to Paul Stoffregen for publishing the AltSoftSerial library. | |
* | |
* The CC BY-NC-ND 4.0 licensed P1P2Serial library is based on the MIT-licensed AltSoftSerial, | |
* but please note that P1P2Serial itself is licensed under the CC BY-NC-ND 4.0. | |
* The original license for AltSoftSerial is included below. | |
* | |
** An Alternative Software Serial Library ** | |
** http://www.pjrc.com/teensy/td_libs_AltSoftSerial.html ** | |
** Copyright (c) 2014 PJRC.COM, LLC, Paul Stoffregen, [email protected] ** | |
** ** | |
** Permission is hereby granted, free of charge, to any person obtaining a copy ** | |
** of this software and associated documentation files (the "Software"), to deal ** | |
** in the Software without restriction, including without limitation the rights ** | |
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ** | |
** copies of the Software, and to permit persons to whom the Software is ** | |
** furnished to do so, subject to the following conditions: ** | |
** ** | |
** The above copyright notice and this permission notice shall be included in ** | |
** all copies or substantial portions of the Software. ** | |
** ** | |
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ** | |
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ** | |
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ** | |
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ** | |
** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ** | |
** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN ** | |
** THE SOFTWARE. ** | |
* | |
*/ | |
// TODO: fix LED code for Arduino targets | |
#include "P1P2Serial.h" | |
// New library version 0.9.14 rewritten for quick and more predictable interrupt handling. | |
// The new library runs on an 8MHz ATmega328P(B) (tested) and maybe also on an ATmega2560 (not tested) | |
// This library uses | |
// 16-bit timer1 (or timer5 on ATmega2560) (no prescaling, no reset, free running full scale) | |
// 8-bit timer2 (1kHz for ms timer, prescaled depending on F_CPU, CTC mode, and reset) | |
// 8-bit timer0 (125Hz for s timer, prescaled depending on F_CPU, CTC mode, and reset) | |
// for timing P1/P2 (semi-)bits (9600 Baud) | |
// 16MHz 8MHz | |
// CPU cycles_per_bit 1667 833 | |
// related interrupts: | |
// ICP PB0 | |
#ifdef __AVR_ATmega2560__ | |
#error ATmega2560 code has not been tested, use with caution. | |
// RW using timer5 | |
#define INPUT_CAPTURE_PIN 48 // PL1 | |
#define INPUT_CAPTURE_PIN_VALUE (PINL & 0x02) // PL1 | |
#define CONFIG_RW_TIMER() (TIMSK5 = 0, TCCR5A = 0, TCCR5B = (1 << ICNC5) | (1 << CS50)) // noise canceler, no prescaler | |
#define CONFIG_CAPTURE_FALLING_EDGE() (TCCR5B &= ~(1 << ICES5)) | |
#define CONFIG_CAPTURE_RISING_EDGE() (TCCR5B |= (1 << ICES5)) | |
#define ENABLE_INT_INPUT_CAPTURE() (TIFR5 = (1 << ICF5), TIMSK5 |= (1 << ICIE5)) | |
#define DISABLE_INT_INPUT_CAPTURE() (TIMSK5 &= ~(1 << ICIE5)) | |
#define RESET_INPUT_CAPTURE() (TIFR5 = (1 << ICF5)) | |
#define INPUT_CAPTURED() (TIFR5 & (1 << ICF5)) | |
#define GET_INPUT_CAPTURE() (ICR5) | |
#define GET_TIMER_R_COUNT() (TCNT5) | |
#define ENABLE_INT_COMPARE_R() (TIFR5 = (1 << OCF5B), TIMSK5 |= (1 << OCIE5B)) | |
#define DISABLE_INT_COMPARE_R() (TIMSK5 &= ~(1 << OCIE5B)) | |
#define CLEAR_COMPARE_R_FLAG() (TIFR5 = (1 << OCF5B)) | |
#define SET_COMPARE_R(val) (OCR5B = (val)) | |
#define CAPTURE_INTERRUPT TIMER5_CAPT_vect | |
#define COMPARE_R_INTERRUPT TIMER5_COMPB_vect | |
#define OUTPUT_COMPARE_PIN 46 // PL3 | |
#define CONFIG_MATCH_INIT() (CONFIG_MATCH_SET(), TCCR5C |= (1 << FOC5A)) | |
#define CONFIG_MATCH_NORMAL() (TCCR5A = TCCR5A & ~((1 << COM5A1) | (1 << COM5A0))) | |
#define CONFIG_MATCH_CLEAR() (TCCR5A = (TCCR5A | (1 << COM5A1)) & ~(1 << COM5A0)) | |
#define CONFIG_MATCH_SET() (TCCR5A = TCCR5A | ((1 << COM5A1) | (1 << COM5A0))) | |
#define ENABLE_INT_COMPARE_W() (TIFR5 |= (1 << OCF5A), TIMSK5 |= (1 << OCIE5A)) | |
#define ENABLE_INT_COMPARE_W() (TIFR5 |= (1 << OCF5B), TIMSK5 |= (1 << OCIE5B)) | |
#define DISABLE_INT_COMPARE_W() (TIMSK5 &= ~(1 << OCIE5A)) | |
#define GET_COMPARE_W() (OCR5A) | |
#define GET_COMPARE_R() (OCR5B) | |
#define GET_TIMER_W_COUNT() (TCNT5) | |
#define SET_COMPARE_W(val) (OCR5A = (val)) | |
#define COMPARE_W_INTERRUPT TIMER5_COMPA_vect | |
// use LED_BUILTIN on PB7 on ATmega2560 | |
#define LED_ERROR LED_BUILTIN | |
#define DIGITAL_RESET_LED_ERROR (PORTB &= 0x7F) | |
#define DIGITAL_WRITE_LED_ERROR(val) ((val) ? (PORTB |= 0x80) : (PORTB &= 0x7F)) | |
#define DIGITAL_SET_LED_ERROR (PORTB |= 0x80) | |
#elif ((defined __AVR_ATmega328P__) || (defined __AVR_ATmega328PB__)) | |
// RW using timer1 | |
#define INPUT_CAPTURE_PIN 8 // PB0 | |
#define INPUT_CAPTURE_PIN_VALUE (PINB & 0x01) // PB0 | |
#define CONFIG_RW_TIMER() (TIMSK1 = 0, TCCR1A = 0, TCCR1B = (1 << ICNC1) | (1 << CS10)) // noise canceler, no prescaler | |
#define CONFIG_CAPTURE_FALLING_EDGE() (TCCR1B &= ~(1 << ICES1)) | |
#define CONFIG_CAPTURE_RISING_EDGE() (TCCR1B |= (1 << ICES1)) | |
#define ENABLE_INT_INPUT_CAPTURE() (TIFR1 = (1 << ICF1), TIMSK1 |= (1 << ICIE1)) | |
#define DISABLE_INT_INPUT_CAPTURE() (TIMSK1 &= ~(1 << ICIE1)) | |
#define RESET_INPUT_CAPTURE() (TIFR1 = (1 << ICF1)) | |
#define INPUT_CAPTURED() (TIFR1 & (1 << ICF1)) | |
#define GET_INPUT_CAPTURE() (ICR1) | |
#define GET_TIMER_R_COUNT() (TCNT1) | |
#define ENABLE_INT_COMPARE_R() (TIFR1 = (1 << OCF1B), TIMSK1 |= (1 << OCIE1B)) | |
#define DISABLE_INT_COMPARE_R() (TIMSK1 &= ~(1 << OCIE1B)) | |
#define CLEAR_COMPARE_R_FLAG() (TIFR1 = (1 << OCF1B)) | |
#define SET_COMPARE_R(val) (OCR1B = (val)) | |
#define CAPTURE_INTERRUPT TIMER1_CAPT_vect | |
#define COMPARE_R_INTERRUPT TIMER1_COMPB_vect | |
#define OUTPUT_COMPARE_PIN 9 // PB1 | |
#define CONFIG_MATCH_INIT() (CONFIG_MATCH_SET(), TCCR1C |= (1 << FOC1A)) | |
#define CONFIG_MATCH_NORMAL() (TCCR1A = TCCR1A & ~((1 << COM1A1) | (1 << COM1A0))) | |
#define CONFIG_MATCH_CLEAR() (TCCR1A = (TCCR1A | (1 << COM1A1)) & ~(1 << COM1A0)) | |
#define CONFIG_MATCH_SET() (TCCR1A = TCCR1A | ((1 << COM1A1) | (1 << COM1A0))) | |
#define ENABLE_INT_COMPARE_W() (TIFR1 |= (1 << OCF1A), TIMSK1 |= (1 << OCIE1A)) | |
#define DISABLE_INT_COMPARE_W() (TIMSK1 &= ~(1 << OCIE1A)) | |
#define GET_COMPARE_W() (OCR1A) | |
#define GET_COMPARE_R() (OCR1B) | |
#define GET_TIMER_W_COUNT() (TCNT1) | |
#define SET_COMPARE_W(val) (OCR1A = (val)) | |
#define COMPARE_W_INTERRUPT TIMER1_COMPA_vect | |
// use LED_BUILTIN (on PB5) on Arduino Uno | |
#define LED_ERROR LED_BUILTIN | |
#define DIGITAL_WRITE_LED_ERROR(val) ((val) ? (PORTB |= 0x20) : (PORTB &= 0xDF)) // assume PB5 on Arduino Uno | |
#define DIGITAL_SET_LED_ERROR (PORTB |= 0x20) | |
#define DIGITAL_RESET_LED_ERROR (PORTB &= 0xDF) | |
#else /* __AVR_ATmega2560__ */ | |
#error Only ATmega328P or ATmega2560 supported | |
#endif /* __AVR_ATmega2560__ */ | |
#if F_CPU <= 8000000L | |
// Assume we are on P1P2-ESP-interface with LED_ERROR on PD3, overrule earlier defines | |
#define LED_ERROR PD3 | |
#define DIGITAL_SET_LED_ERROR (PORTD |= 0x08) | |
#define DIGITAL_RESET_LED_ERROR (PORTD &= 0xF7) | |
#define DIGITAL_WRITE_LED_ERROR(val) ((val) ? (PORTD |= 0x08) : (PORTD &= 0xF7)) | |
#endif /* F_CPU */ | |
// 4 leds: on P1P2-ESP-interface / Arduino ATmega250 / Arduino Uno | |
// LED_POWER (white) on PC2 / pin 35 / pin A2 | |
// LED_READ (green) on PD6 / n/a / pin 6 | |
// LED_WRITE (blue) on PD5 (*) / n/a / pin 5 | |
// LED_ERROR (red) on PD3 (*) / LED_BUILTIN / LED_BUILTIN | |
// (*) in v1.2 LEDs inverted logic: to 3V3 instead of to GND | |
#define LED_POWER PC2 | |
#define LED_READ PD6 | |
#define LED_WRITE PD5 | |
#define DIGITAL_SET_LED_POWER (PORTC |= 0x04) | |
#define DIGITAL_RESET_LED_POWER (PORTC &= 0xFB) | |
#define DIGITAL_WRITE_LED_POWER(val) ((val) ? (DIGITAL_SET_LED_POWER) : (DIGITAL_RESET_LED_POWER)) | |
#define DIGITAL_WRITE_LED_ERROR(val) ((val) ? (DIGITAL_SET_LED_ERROR) : (DIGITAL_RESET_LED_ERROR)) | |
// v1.2 uses LEDs to 3V3 instead of to GND for R,W | |
#define DIGITAL_SET_LED_READ { if (_ledRW_reverse) { PORTD &= 0xBF;} else { PORTD |= 0x40;}} | |
#define DIGITAL_RESET_LED_READ { if (_ledRW_reverse) { PORTD |= 0x40;} else { PORTD &= 0xBF;}} | |
#define DIGITAL_SET_LED_WRITE { if (_ledRW_reverse) { PORTD &= 0xDF;} else { PORTD |= 0x20;}} | |
#define DIGITAL_RESET_LED_WRITE { if (_ledRW_reverse) { PORTD |= 0x20;} else { PORTD &= 0xDF;}} | |
// timer2 for milliseconds, timer0 for seconds | |
#if F_CPU == 16000000L | |
#define CONFIG_MS_TIMER() (TCCR2A = 2, TCCR2B = 4, OCR2A = 249) // CTC mode, wraps 16MHz/(64*250)=1kHz | |
#define CONFIG_S_TIMER() (TCCR0A = 2, TCCR0B = 5, OCR0A = 124) // CTC mode, wraps 16MHz/(1024*125)=125Hz | |
#elif F_CPU == 8000000L | |
#define CONFIG_MS_TIMER() (TCCR2A = 2, TCCR2B = 3, OCR2A = 249) // CTC mode, wraps 8MHz/(32*250)=1kHz | |
#define CONFIG_S_TIMER() (TCCR0A = 2, TCCR0B = 4, OCR0A = 249) // CTC mode, wraps 8MHz/(256*250)=125Hz | |
#else /* F_CPU */ | |
#error F_CPU not supported | |
#endif /* F_CPU */ | |
#define RESET_MS_TIMER() (GTCCR = 2, SET_MS_TIMER(0), time_msec = 0) | |
#define PRESET_MS_TIMER() (GTCCR = 2, SET_MS_TIMER(0), time_msec = 1) | |
#define RESET_ENABLE_MS_TIMER() (RESET_MS_TIMER(), TIFR2 = (1 << OCF2A), TIMSK2 = (1 << OCIE2A)) | |
#define PRESET_ENABLE_MS_TIMER() (PRESET_MS_TIMER(), TIFR2 = (1 << OCF2A), TIMSK2 = (1 << OCIE2A)) | |
#define DISABLE_MS_TIMER() (TIMSK2 = 0) | |
#define SET_MS_TIMER(val) (TCNT2 = (val)) | |
#define RESET_ENABLE_S_TIMER() (time_sec = 0, time_millisec = 0, TCNT0 = 0, GTCCR = 1, TIFR0 = (1 << OCF0A), TIMSK0 = (1 << OCIE0A)) | |
#define DISABLE_S_TIMER() (TIMSK0 = 0) | |
#define MS_TIMER_COMP_vect TIMER2_COMPA_vect | |
#define S_TIMER_COMP_vect TIMER0_COMPA_vect | |
#ifdef GENERATE_FAKE_ERRORS | |
#define FAKE_ERROR_PE 0 // parity error | |
#define FAKE_ERROR_SB 1 // start bit error during write | |
#define FAKE_ERROR_BE 2 // data read-back error, likely bus collission, in direct write verification mode | |
#define FAKE_ERROR_BC 3 // high bit half read-back error, likely bus collission | |
bool errorGen[4] = { true, true, true, true }; | |
uint16_t errorGenCnt[4] = { 0, 100, 200, 300 }; | |
bool fakeError(byte x) | |
{ | |
if (++errorGenCnt[x] == SWS_FAKE_ERR_CNT) { | |
errorGenCnt[x] = 0; | |
return errorGen[x]; | |
} | |
return 0; | |
} | |
#endif /* GENERATE_FAKE_ERRORS */ | |
/****************************************/ | |
/** CPU load measurement **/ | |
/****************************************/ | |
#ifdef MEASURE_LOAD | |
// CLOCK msg 32 bytes ca 33ms 33000 us so irq_time is 16-bit counter 1us resolution, irq_lapsed is also 1us resolution | |
// TIMER1 runs at 8 MHZ / at 64 kB overflows every 8ms. | |
// if ISR does not take longer than 64k/8 = 8us, | |
volatile uint16_t irq_start_time = 0, irq_start = 0, irq_w = 0, irq_r = 0, irq_time = 0, irq_lapsed_w = 0, irq_lapsed_r = 0; | |
volatile uint8_t irq_ovf = 0, irq_busy = 0; | |
// assume 8MHz: TODO for 16MHz, use >> 4 | |
// | |
#define IRQ_START { irq_start_time = GET_TIMER_W_COUNT(); } | |
#define IRQ_STOP { irq_time += (GET_TIMER_W_COUNT() - irq_start_time) >> 3; } | |
#define IRQ_BEGIN { irq_time = 0; irq_start = GET_TIMER_W_COUNT(); irq_ovf = 0; TIFR1 = (1 << TOV1); irq_busy = 1; }; | |
#define IRQ_END_R { irq_r = irq_time; irq_w = 0; irq_lapsed_r = ((GET_TIMER_W_COUNT() - irq_start) >> 3) + ((irq_ovf + ((TIFR1 & (1 << TOV1)) ? 1 : 0)) << 13); irq_busy = 0; }; | |
#define IRQ_END_W { irq_w = irq_time; irq_r = 0; irq_lapsed_w = ((GET_TIMER_W_COUNT() - irq_start) >> 3) + ((irq_ovf + ((TIFR1 & (1 << TOV1)) ? 1 : 0)) << 13); irq_busy = 0; }; | |
// (share range range = 0 .. 255, perhaps even 256?) | |
#define OVF_vect TIMER1_OVF_vect | |
#define ENABLE_OVF (TIFR1 = (1 << TOV1), TIMSK1 |= (1 << TOIE1)) | |
#else /* MEASURE_LOAD */ | |
#define OVF_vect TIMER1_OVF_vect | |
#define ENABLE_OVF ; | |
#define IRQ_START ; | |
#define IRQ_STOP ; | |
#define IRQ_BEGIN ; | |
#define IRQ_END_R ; | |
#define IRQ_END_W ; | |
#endif /* MEASURE_LOAD */ | |
/************************/ | |
/** ADC for voltages **/ | |
/************************/ | |
static bool _use_ADC = false; | |
static bool _ledRW_reverse = false; | |
static byte ADMUX0 = 0; | |
static byte ADMUX1 = 0; | |
static uint16_t V0min = 0x3FF << ADC_AVG_SHIFT; | |
static uint16_t V0max = 0x0000; | |
static uint32_t V0avg = 0x00; | |
static uint16_t V1min = 0x3FF << ADC_AVG_SHIFT; | |
static uint16_t V1max = 0x0000; | |
static uint32_t V1avg = 0x00; | |
static uint16_t V0cnt = 0; | |
static uint16_t V1cnt = 0; | |
static uint32_t V0sum0 = 0x00; | |
static uint32_t V0sum = 0x00; | |
static uint32_t V1sum0 = 0x00; | |
static uint32_t V1sum = 0x00; | |
// hard-coded for 8 MHz ATmega328P | |
#define ADC_TRIGGER (ADCSRA = 0xDE) // set ADSC to 1 | |
#define ADC_INTERRUPT ADC_vect | |
#define ADC_VALUE (ADCL + (ADCH << 8)) | |
#define ADC_ADC0 (ADMUX = ADMUX0) | |
#define ADC_ADC1 (ADMUX = ADMUX1) | |
#define ADC_INT_DISABLE (ADCSRA = 0x86) | |
#define ADC_INT_ENABLE (ADCSRA = 0x8E) | |
ISR(ADC_vect) { | |
static bool ADC0used = true; | |
uint16_t V = ADC_VALUE; | |
if (ADC0used) { | |
ADC0used = false; | |
ADC_ADC1; | |
V0cnt ++; | |
V0sum0 += V; | |
if (!(V0cnt << (16 - ADC_AVG_SHIFT))) { // sum (avg) a few samples before min/max check | |
V0sum += V0sum0; | |
if (V0sum0 < V0min) V0min = V0sum0; | |
if (V0sum0 > V0max) V0max = V0sum0; | |
V0sum0 = 0; | |
if (!(V0cnt << ADC_CNT_SHIFT)) { | |
// sum 4k samples for average calculation approximately every second | |
V0avg = V0sum; | |
V0sum = 0; | |
} | |
} | |
} else { | |
ADC0used = true; | |
ADC_ADC0; | |
V1cnt ++; | |
V1sum0 += V; | |
if (!(V1cnt << (16 - ADC_AVG_SHIFT))) { // sum samples (16 samples if ADC_AVG_SHIFT1 == 4) | |
V1sum += V1sum0; | |
if (V1sum0 < V1min) V1min = V1sum0; | |
if (V1sum0 > V1max) V1max = V1sum0; | |
V1sum0 = 0; | |
if (!(V1cnt << ADC_CNT_SHIFT)) { | |
V1avg = V1sum; | |
V1sum = 0; | |
} | |
} | |
} | |
ADC_TRIGGER; | |
} | |
void P1P2Serial::ADC_results(uint16_t &V0_min, uint16_t &V0_max, uint32_t &V0_avg, | |
uint16_t &V1_min, uint16_t &V1_max, uint32_t &V1_avg) { | |
// if use_ADC is true in P1P2Serial::begin, | |
// ADC measurements are done alternating on pins ADC_pin0 and ADC_pin1 | |
// at 8MHz the average measured voltage is updated approximately every second | |
// V0_avg and V1_avg are summations of 2^(16 - ADC_CNT_SHIFT)) samples (default: 4k) | |
// V0_min/max and V1_min/max are sample summations over 2^ADC_AVG_SHIFT samples (default: 16) | |
// the mimimum and maximum values are reset upon each call of ADC_results | |
if (_use_ADC) { | |
ADC_INT_DISABLE; | |
V0_min = V0min; | |
V0_max = V0max; | |
V0_avg = V0avg; | |
V1_min = V1min; | |
V1_max = V1max; | |
V1_avg = V1avg; | |
V0min = 0x3FF << ADC_AVG_SHIFT; | |
V0max = 0x000; | |
V1min = 0x3FF << ADC_AVG_SHIFT; | |
V1max = 0x000; | |
ADC_INT_ENABLE; | |
} | |
} | |
/****************************************/ | |
/** LED routines (v1.2) **/ | |
/****************************************/ | |
void P1P2Serial::ledPower(bool ledOn) { | |
DIGITAL_WRITE_LED_POWER(ledOn); | |
} | |
void P1P2Serial::ledError(bool ledOn) { | |
DIGITAL_WRITE_LED_ERROR(ledOn); | |
} | |
/****************************************/ | |
/** Initialization **/ | |
/****************************************/ | |
static uint16_t Rticks_per_bit = 0; | |
static uint16_t Rticks_per_semibit = 0; | |
static uint16_t Rticks_per_bit_and_semibit = 0; | |
static uint16_t Rticks_suppression = 0; | |
static uint16_t Wticks_per_semibit = 0; | |
static uint16_t Wticks_per_bit_and_semibit = 0; | |
static uint8_t rx_state; | |
static uint8_t rx_byte; | |
static uint8_t rx_paritycheck; | |
static uint16_t rx_target; | |
static volatile uint8_t rx_buffer_head; | |
static volatile uint8_t rx_buffer_head2; | |
static volatile uint8_t rx_buffer_tail; | |
static volatile uint8_t rx_buffer[RX_BUFFER_SIZE]; | |
static volatile errorbuf_t error_buffer[RX_BUFFER_SIZE]; // records error status | |
static volatile uint16_t delta_buffer[RX_BUFFER_SIZE]; // records timing info in ms | |
static volatile uint8_t tx_state; | |
static volatile uint8_t tx_rx_state; | |
static uint8_t tx_byte; | |
static uint8_t tx_bit; | |
static uint8_t tx_byte_verify; | |
static uint8_t tx_paritybit; | |
static volatile uint8_t tx_buffer_head; | |
static volatile uint8_t tx_buffer_tail; | |
static volatile uint8_t tx_buffer[TX_BUFFER_SIZE]; | |
static volatile uint16_t tx_buffer_delay[TX_BUFFER_SIZE]; // records timing info in ms (16 bits) | |
static volatile uint16_t time_msec = 0; | |
static volatile uint16_t tx_wait = 0; | |
static volatile uint8_t time_sec_cnt = 0; | |
static volatile int32_t time_sec = 0; | |
static volatile int32_t time_millisec = 0; | |
#define scheduledelay Wticks_per_bit_and_semibit // should be more than 1.5 bits for writing | |
#ifndef INPUT_PULLUP | |
#define INPUT_PULLUP INPUT | |
#endif /* INPUT_PULLUP */ | |
void P1P2Serial::begin(uint32_t baud, bool use_ADC /* = false */, uint8_t ADC_pin0 /* = 0 */, uint8_t ADC_pin1 /* = 1 */, bool ledRW_reverse /* = false */) | |
{ | |
uint32_t cycles_per_bit = ((ALTSS_BASE_FREQ + baud / 2) / baud); // 833 cycles at 8MHz | |
Rticks_per_bit = cycles_per_bit; | |
Rticks_per_semibit = Rticks_per_bit / 2; | |
Rticks_per_bit_and_semibit = Rticks_per_bit + Rticks_per_semibit; | |
Rticks_suppression = Rticks_per_semibit + Rticks_per_semibit / 4; // to avoid early ISR capture (spike, bouncing rising edge) may lead to very long while loop behaviour and loss of sync. | |
Wticks_per_semibit = cycles_per_bit / 2; | |
Wticks_per_bit_and_semibit = 3 * Wticks_per_semibit; | |
pinMode(LED_POWER, OUTPUT); | |
pinMode(LED_READ, OUTPUT); | |
pinMode(LED_WRITE, OUTPUT); | |
pinMode(LED_ERROR, OUTPUT); | |
_ledRW_reverse = ledRW_reverse; | |
DIGITAL_SET_LED_POWER; | |
DIGITAL_SET_LED_READ; | |
DIGITAL_SET_LED_WRITE; | |
DIGITAL_SET_LED_ERROR; | |
pinMode(INPUT_CAPTURE_PIN, INPUT_PULLUP); | |
digitalWrite(OUTPUT_COMPARE_PIN, HIGH); | |
pinMode(OUTPUT_COMPARE_PIN, OUTPUT); | |
time_msec = 0; | |
rx_state = 0; | |
rx_buffer_head = 0; | |
rx_buffer_head2 = NO_HEAD2; | |
rx_buffer_tail = 0; | |
tx_state = 0; | |
tx_rx_state = 0; | |
tx_buffer_head = 0; | |
tx_buffer_tail = 0; | |
tx_wait = 0; | |
#ifdef S_TIMER | |
CONFIG_S_TIMER(); | |
RESET_ENABLE_S_TIMER(); | |
#endif /* S_TIMER */ | |
CONFIG_MS_TIMER(); | |
RESET_ENABLE_MS_TIMER(); | |
CONFIG_RW_TIMER(); | |
CONFIG_CAPTURE_FALLING_EDGE(); | |
CONFIG_MATCH_INIT(); | |
// start reading mode | |
ENABLE_INT_INPUT_CAPTURE(); | |
ENABLE_OVF; | |
// start ADC measurements on pins ADC_pin0 and ADC_pin1 if use_ADC is true | |
_use_ADC = use_ADC; | |
if (_use_ADC) { | |
ADMUX0 = 0xC0 | ADC_pin0; // 1.1V reference | |
ADMUX1 = 0xC0 | ADC_pin1; // 1.1V reference | |
DIDR0 = ((1 << ADC_pin0) | (1 << ADC_pin1)) & 0x3F; // disable digital input on analog pins | |
ADC_ADC0; // start with ADC_pin0 | |
ADCSRB = 0x00; // ACME disabled, free running mode, conversions will be triggered by ADSC | |
ADCSRA = 0xDE; // enable ADC, 8MHz/64=125kHz, clear ADC interrupt flag and trigger single ADC conversion | |
} | |
} | |
void P1P2Serial::end(void) | |
{ | |
DISABLE_INT_COMPARE_R(); | |
DISABLE_INT_INPUT_CAPTURE(); | |
flushInput(); | |
flushOutput(); | |
DISABLE_INT_COMPARE_W(); | |
DISABLE_MS_TIMER(); | |
#ifdef S_TIMER | |
DISABLE_S_TIMER(); | |
#endif /* S_TIMER */ | |
} | |
/****************************************/ | |
/** Uptime counter **/ | |
/****************************************/ | |
// As the ms counter (timer2) is frequently reset, it cannot be used for longer time measurements | |
// Use timer0 for (second-based) uptime | |
#ifdef S_TIMER | |
ISR(S_TIMER_COMP_vect) | |
{ | |
IRQ_START; | |
// called at 125Hz | |
time_millisec += 8; | |
if (++time_sec_cnt > 124) { | |
time_sec_cnt = 0; | |
time_sec++; | |
} | |
IRQ_STOP; | |
} | |
#endif /* S_TIMER */ | |
/****************************************/ | |
/** Millisecond counter **/ | |
/****************************************/ | |
static uint16_t tx_setdelaytimeout = 2500; | |
#ifdef SW_SCOPE | |
volatile byte sw_scope = 0; | |
volatile byte sw_scope_next = 0; | |
volatile byte sws_block = 0; | |
volatile byte sws_error = 0; | |
volatile byte sws_errorcount = 0; | |
volatile uint8_t sws_event[SWS_MAX]; | |
volatile uint16_t sws_capture[SWS_MAX]; | |
volatile uint16_t sws_count[SWS_MAX]; | |
volatile uint8_t sws_cnt = 0; | |
#define SW_SCOPE_LOG_EVENT(capture, event) \ | |
if (sw_scope && (sws_errorcount || !sws_error)) { \ | |
sws_capture[sws_cnt] = capture; \ | |
sws_event[sws_cnt] = event; \ | |
if (++sws_cnt == SWS_MAX) sws_cnt = 0; \ | |
if (sws_error) sws_errorcount--; \ | |
} | |
#define SW_SCOPE_LOG_ERROR(capture, event) \ | |
if (sw_scope && (sws_errorcount || !sws_error)) { \ | |
if (!sws_error) { \ | |
sws_error = 1; \ | |
sws_errorcount = SWS_MAX >> 1; \ | |
} \ | |
sws_capture[sws_cnt] = capture; \ | |
sws_event[sws_cnt] = event; \ | |
if (++sws_cnt == SWS_MAX) sws_cnt = 0; \ | |
if (sws_error) sws_errorcount--; \ | |
} | |
#define SW_SCOPE_START_LOG { \ | |
sws_block = 1; \ | |
sws_cnt = 0; \ | |
sws_event[SWS_MAX - 1] = SWS_EVENT_LOOP; \ | |
sws_error = 0; }; | |
#else /* SW_SCOPE */ | |
#define SW_SCOPE_LOG_EVENT(capture, event) {}; | |
#define SW_SCOPE_LOG_ERROR(capture, event) {}; | |
#define SW_SCOPE_START_LOG {}; | |
#endif /* SW_SCOPE */ | |
#ifdef MEASURE_LOAD | |
ISR(OVF_vect) | |
{ | |
irq_ovf++; | |
} | |
#endif | |
ISR(MS_TIMER_COMP_vect) | |
{ | |
IRQ_START; | |
// time_msec counts time in ms from the last start pulse (counting from the leading falling edge of the start pulse) | |
// max count is 65535 ms (uint16_t) | |
if (time_msec < 0xFFFF) time_msec++; | |
// if tx_state =99, a write is scheduled, so check if pause is long enough to start writing | |
if (tx_state == 99) { | |
if ((time_msec == tx_wait) || ((time_msec >= tx_wait) && (time_msec >= tx_setdelaytimeout))) { | |
// start writing: | |
// in scheduledelay ticks of timer1, falling edge of start bit is scheduled | |
// This will trigger an interrupt for next action to be set up. | |
// tx_state=1 indicates that (upon start interrupt routine) the start bit has just begun. | |
tx_state = 1; | |
uint16_t t_delta = scheduledelay; | |
// switch from reading to writing | |
// disable reading when writing | |
// in scopemode, capture (both) edges | |
// if P1P2Monitor is still processing sws_event data (sws_block), do not start writing new events | |
#ifdef SW_SCOPE | |
// if P1P2Monitor is ready reading data (sws_block = 0), start new log operation in write mode (if sw_scope_next) | |
sw_scope = sw_scope_next && !sws_block; | |
if (sw_scope) { | |
SW_SCOPE_START_LOG; | |
// keep INT_INPUT_CAPTURE enabled | |
DISABLE_INT_INPUT_CAPTURE(); | |
} else { | |
DISABLE_INT_INPUT_CAPTURE(); | |
} | |
#else /* SW_SCOPE */ | |
DISABLE_INT_INPUT_CAPTURE(); | |
#endif /* SW_SCOPE */ | |
DISABLE_INT_COMPARE_R(); | |
DISABLE_MS_TIMER(); | |
// start writing | |
IRQ_STOP; | |
IRQ_BEGIN; | |
IRQ_START; | |
SET_COMPARE_W(GET_TIMER_W_COUNT() + t_delta); | |
CONFIG_MATCH_CLEAR(); | |
CONFIG_CAPTURE_FALLING_EDGE(); | |
ENABLE_INT_COMPARE_W(); | |
DIGITAL_SET_LED_WRITE; | |
} | |
} | |
IRQ_STOP; | |
} | |
/****************************************/ | |
/** Transmission **/ | |
/****************************************/ | |
static uint16_t tx_setdelay = 0; | |
void P1P2Serial::setDelay(uint16_t t) | |
// Input parameter: 0 <= t <= 65535 | |
// This sets delay for next byte (and next byte only) when it is added to the transmission buffer. | |
// The writing of the next byte to be added to the queue will be delayed until (<= v0.9.4: at least; >= v0.9.5: exactly) t milliseconds silence | |
// since last falling edge of start bit has been detected. (v0.9.5:) In addition, writing will follow in case of a timeout situation. | |
// This means that a delay is introduced since the byte last read, or, | |
// as we also read back sent data (also for the purpose of verification), since the byte last written. | |
// If a byte is added to the queue during a silence, the past silence will also be taken into account. | |
// (<=v0.9.4:) If a byte is added with a setDelay which is shorter than the past silence, transmission will start immediately. | |
// (>=v0.9.5:) If the past silence is more that t ms, but less than the timeout value, writing will be delayed until either one of the following 2 conditions is met: | |
// 1) a new byte is received, after which a new pause will be clocked, or | |
// 2) no new byte is received, but the total pause is or becomes longer than the value set with setDelayTimeout(t) | |
// The reason for this change in behaviour is to prevent bus collissions when writing a packet near the end of a pause. | |
// If the value used as delay timeout is equal to or lower than the delay value, the writing behaviour of >=v0.9.5 equals the behaviour of library version <=v0.9.4 | |
// It is advised to call setDelaytimeout(t) with a value that is larger than the total cycle time of the P1P2 bus behaviour. | |
// For many products the cycle repeats every 0.8..2s, so a setDelaytimeout(2500) would be appropriate. This is also the default value. | |
// For some products the cycle time can be larger (15s or so), and a setDelaytimeout(20000) might be appropriate. | |
// To simplify code, setDelay(0) and setDelay(1) are no longer supported; useless for P1/P2. | |
// If setDelay (t) is called with 2 <= t <= 65535, a delay of t ms is set. | |
// If setDelay (t) is called with t < 2, a delay of 2 ms is set. | |
{ | |
if (t < 2) t = 2; | |
tx_setdelay = t; | |
} | |
void P1P2Serial::setDelayTimeout(uint16_t t) | |
// Input parameter: 0 <= t <= 65535 | |
// This sets delay timeout as described above | |
{ | |
tx_setdelaytimeout = t; | |
} | |
bool P1P2Serial::writeready(void) | |
{ | |
return (tx_buffer_tail == tx_buffer_head); | |
} | |
uint8_t tx_rx_paritycheck; | |
uint8_t tx_rx_readbackerror; | |
#ifdef GENERATE_FAKE_ERRORS | |
uint8_t tx_rx_readbackerror_fake; | |
#endif /* GENERATE_FAKE_ERRORS */ | |
void P1P2Serial::write(uint8_t b) | |
{ | |
uint8_t intr_state, head; | |
head = tx_buffer_head + 1; | |
if (head >= TX_BUFFER_SIZE) head = 0; | |
while (tx_buffer_tail == head) ; // wait until space in write buffer | |
intr_state = SREG; | |
cli(); | |
// cli() is needed here to avoid a race condition w.r.t. tx_state, which can change in ISR() | |
if (tx_state) { | |
// if already writing, add byte to write buffer | |
tx_buffer[head] = b; | |
tx_buffer_delay[head] = tx_setdelay; tx_setdelay = 0; | |
tx_buffer_head = head; | |
} else { | |
// if not already writing, (previously: start or) schedule writing | |
tx_byte = b; | |
tx_byte_verify = b; // for read-back verification | |
tx_paritybit = 0; | |
tx_rx_paritycheck = 0; | |
tx_rx_readbackerror = 0; | |
#ifdef GENERATE_FAKE_ERRORS | |
tx_rx_readbackerror_fake = 0; | |
#endif /* GENERATE_FAKE_ERRORS */ | |
// we could initiate start writing here, as we did <=v0.9.4, but we can better leave it to the ISR, which is simpler | |
// it adds some delay (max 1 ms), but it makes operation and timing slightly more predictable | |
// set tx_state 99 to start transmission in msec ISR when time_msec becomes == tx_setdelay or >= tx_setdelaytimeout | |
tx_state = 99; | |
tx_wait = tx_setdelay; | |
// next byte after the scheduled one will be written without delay | |
tx_setdelay = 0; | |
} | |
SREG = intr_state; | |
} | |
static volatile uint16_t startbit_delta; | |
static volatile uint8_t Echo = 1; | |
static volatile uint8_t Allow = ALLOW_PAUSE_BETWEEN_BYTES; | |
ISR(COMPARE_W_INTERRUPT) | |
{ | |
IRQ_START; | |
uint8_t state, bit, bit_input, errorhead, head, tail; | |
uint16_t delay; | |
state = tx_state; | |
// state indicates in which part of data pattern we are when entering this ISR | |
// 1,2 startbit | |
// 3,4 .. 17,18 data bits | |
// 19,20 parity bit | |
// 21,22 stopbit (21,22 skipped) | |
// (removed) 23 after stopbit | |
// (can't happen here) 99 pausing, pausing until we can schedule next start bit falling edge | |
// (can't happen here) 0, currently not writing | |
// SW_SCOPE logs states, error/event, and time of captured edge or counter value | |
uint16_t sws_count_temp = GET_TIMER_R_COUNT(); | |
uint16_t get_compare_w = GET_COMPARE_W(); | |
uint16_t set_compare_w; | |
DIGITAL_RESET_LED_ERROR; | |
if (state < 20) { | |
set_compare_w = get_compare_w + Wticks_per_semibit; | |
SET_COMPARE_W(set_compare_w); | |
bit_input = INPUT_CAPTURE_PIN_VALUE; | |
if (state & 1) { | |
// state is odd, 1-19, next semibit will be part 2 of databit (high) | |
CONFIG_MATCH_SET(); | |
CONFIG_CAPTURE_RISING_EDGE(); | |
if (state == 1) { | |
// state=1, start bit | |
startbit_delta = time_msec; | |
DISABLE_MS_TIMER(); | |
tx_rx_readbackerror = 0; | |
#ifdef GENERATE_FAKE_ERRORS | |
tx_rx_readbackerror_fake = 0; | |
#endif /* GENERATE_FAKE_ERRORS */ | |
} else if (state == 19) { | |
// state=19, paritybit | |
PRESET_ENABLE_MS_TIMER(); // start new milli-second timer measurement here, setting timer on 1msec given we are at paritybit. | |
} | |
// check bit input signal | |
if (state == 1) { | |
// state is 1, start bit part 1, should be 0 | |
if (bit_input) { | |
tx_rx_readbackerror = ERROR_SB; | |
SW_SCOPE_LOG_ERROR(sws_count_temp, SWS_EVENT_ERR_SB); | |
} | |
#ifdef GENERATE_FAKE_ERRORS | |
if (fakeError(FAKE_ERROR_SB)) { | |
tx_rx_readbackerror_fake |= ERROR_SB; | |
SW_SCOPE_LOG_ERROR(sws_count_temp, SWS_EVENT_ERR_SB_FAKE); | |
} | |
#endif /* GENERATE_FAKE_ERRORS */ | |
} else if (state & 1) { | |
// state is odd and >1, can be bit or parity bit | |
// faster to check bit value directly here than to reconstruct tx_rx_byte | |
if (bit_input != tx_bit) { | |
tx_rx_readbackerror |= ERROR_BE; // bit differs | |
SW_SCOPE_LOG_ERROR(sws_count_temp, SWS_EVENT_ERR_BE); | |
} | |
#ifdef GENERATE_FAKE_ERRORS | |
if (fakeError(FAKE_ERROR_BE)) { | |
tx_rx_readbackerror_fake |= ERROR_BE; | |
SW_SCOPE_LOG_ERROR(sws_count_temp, SWS_EVENT_ERR_BE_FAKE); | |
} | |
#endif /* GENERATE_FAKE_ERRORS */ | |
} | |
} else { | |
// state is even, 2..18 | |
if (state < 18) { | |
// state is even, <18, next semibit will be data bit part 1, LSB first | |
bit = (tx_byte & 1); | |
if (!bit) { | |
CONFIG_MATCH_CLEAR(); | |
CONFIG_CAPTURE_FALLING_EDGE(); | |
} | |
tx_paritybit ^= bit; | |
tx_byte >>= 1; | |
} else { | |
// state=18, next semibit will be parity bit part 1 | |
bit = tx_paritybit; | |
if (!bit) { | |
CONFIG_MATCH_CLEAR(); | |
CONFIG_CAPTURE_FALLING_EDGE(); | |
} | |
} | |
// verify | |
// state is even, check bit data (part 2), should be 1, otherwise suspect bus collission | |
#ifndef H_SERIES | |
// for H-link, this results in bus collision errors being detected, as second bit data is not consistently 1, so omit this check on H_SERIES | |
if (!bit_input) { | |
tx_rx_readbackerror |= ERROR_BC; | |
SW_SCOPE_LOG_ERROR(sws_count_temp, SWS_EVENT_ERR_BC); | |
} | |
#ifdef GENERATE_FAKE_ERRORS | |
if (fakeError(FAKE_ERROR_BC)) { | |
tx_rx_readbackerror_fake |= ERROR_BC; | |
SW_SCOPE_LOG_ERROR(sws_count_temp, SWS_EVENT_ERR_BC_FAKE); | |
} | |
#endif /* GENERATE_FAKE_ERRORS */ | |
#endif /* H_SERIES */ | |
tx_bit = bit; | |
} | |
bit_input = INPUT_CAPTURE_PIN_VALUE; // sample again in view of turn-around delay | |
// check if we have captured a rising edge? | |
if (INPUT_CAPTURED()) { | |
// edge captured | |
uint16_t capture = GET_INPUT_CAPTURE(); | |
RESET_INPUT_CAPTURE(); | |
if (bit_input) { | |
SW_SCOPE_LOG_EVENT(capture, SWS_EVENT_EDGE_RISING | state); | |
} else { | |
SW_SCOPE_LOG_EVENT(capture, SWS_EVENT_EDGE_FALLING_W | state); | |
} | |
} else { | |
if (!bit_input) { | |
SW_SCOPE_LOG_ERROR(sws_count_temp, SWS_EVENT_ERR_LOW); | |
} | |
} | |
tx_rx_state = state; | |
state++; | |
tx_state = state; | |
IRQ_STOP; | |
return; | |
} | |
// state = 20 | |
// 20: next semibit will be stop bit part 1, schedule start bit part 1 if tx_buffer is not empty | |
// no further read-back-verify for stop bit | |
bit_input = INPUT_CAPTURE_PIN_VALUE; // sample again in view of turn-around delay | |
// check if we have captured a rising edge? | |
if (INPUT_CAPTURED()) { | |
// edge captured | |
uint16_t capture = GET_INPUT_CAPTURE(); | |
RESET_INPUT_CAPTURE(); | |
if (bit_input) { | |
SW_SCOPE_LOG_EVENT(capture, SWS_EVENT_EDGE_RISING | state); | |
} else { | |
SW_SCOPE_LOG_EVENT(capture, SWS_EVENT_EDGE_FALLING_W | state); | |
} | |
} else { | |
if (!bit_input) { | |
SW_SCOPE_LOG_ERROR(sws_count_temp, SWS_EVENT_ERR_LOW); | |
} | |
} | |
if (tx_rx_readbackerror) { | |
DIGITAL_SET_LED_ERROR; | |
// As of version 0.9.22: if a bus collision is suspected (=if a read errors occurs during a write), reduce risk on further collissions by emptying write buffer | |
tx_buffer_tail = tx_buffer_head; | |
} | |
// store transmitted byte as it it were received (if buffer space available, and if Echo), and check/store errors | |
if (Echo) { | |
head = rx_buffer_head + 1; | |
if (head >= RX_BUFFER_SIZE) head = 0; | |
if (head != rx_buffer_tail) { | |
rx_buffer[head] = tx_byte_verify; // cheat, transmitted byte | |
delta_buffer[head] = startbit_delta; | |
#ifdef GENERATE_FAKE_ERRORS | |
error_buffer[head] = tx_rx_readbackerror | (tx_rx_readbackerror_fake << 8); | |
#else /* GENERATE_FAKE_ERRORS */ | |
error_buffer[head] = tx_rx_readbackerror; | |
#endif /* GENERATE_FAKE_ERRORS */ | |
rx_buffer_head = head; | |
} else { | |
// signal buffer overrun for *previous* byte | |
head = rx_buffer_head; | |
error_buffer[head] |= ERROR_OR; | |
DIGITAL_SET_LED_ERROR; | |
} | |
} | |
// more data to write? | |
errorhead = head; | |
head = tx_buffer_head; | |
tail = tx_buffer_tail; | |
if (head != tail) { | |
// there are more bytes to send; | |
if (++tail >= TX_BUFFER_SIZE) tail = 0; | |
tx_buffer_tail = tail; | |
tx_byte = tx_buffer[tail]; | |
tx_byte_verify = tx_byte; | |
delay = tx_buffer_delay[tail]; | |
tx_rx_paritycheck = 0; | |
tx_paritybit = 0; | |
if (delay < 2) { | |
// if delay=0 or 1, we effectively don't wait and we continue writing! | |
// as we are in (silent, high) stop bit time, we set target time at end of stop bit (= next start bit) | |
SET_COMPARE_W(GET_COMPARE_W() + Wticks_per_bit_and_semibit); | |
CONFIG_MATCH_CLEAR(); | |
CONFIG_CAPTURE_FALLING_EDGE(); | |
tx_state = 1; | |
IRQ_STOP; | |
return; | |
} else { | |
// it does not matter whether we are still in stop bit or beyond, we have to wait longer due to the delay setting | |
tx_state = 99; | |
tx_wait = delay; | |
} | |
} else { | |
// tx buffer empty, there are no more bytes to send..., | |
// we don't need to block transmission here until start bit part 1 | |
// because schedule_delay >= Wticks_per_bit_and_semibit ensures this too | |
// switch from writing to reading | |
tx_state = 0; | |
} | |
DISABLE_INT_COMPARE_W(); | |
CONFIG_CAPTURE_FALLING_EDGE(); // should not be needed, just in case | |
ENABLE_INT_INPUT_CAPTURE(); | |
error_buffer[errorhead] |= SIGNAL_EOP; | |
DIGITAL_RESET_LED_WRITE; | |
IRQ_STOP; | |
IRQ_END_W; | |
} | |
void P1P2Serial::flushOutput(void) | |
{ | |
while (tx_state) /* wait */ ; | |
} | |
/****************************************/ | |
/** Reception **/ | |
/****************************************/ | |
#define SUPPRESS_OSCILLATION | |
// Use this to ignore edges that are very near previous edges to avoid detection of oscillating edges | |
// Does not seem necessary for Daikin, but seems necessary for strange (double-freq) start pulse by some H-link2 devices | |
#ifdef SW_SCOPE | |
void P1P2Serial::setScope(byte b) | |
// Set scope on or off (default off) | |
// calling setScope has almost-immediate effect, starting from the next packet | |
{ | |
sw_scope_next = b; | |
} | |
#endif /* SW_SCOPE */ | |
void P1P2Serial::setAllow(uint8_t b) | |
// Set max extra time between bytes | |
// default: ALLOW_PAUSE_BETWEEN_BYTES | |
// max 150 | |
// min 0 | |
// unit: bit times | |
{ | |
Allow = b; | |
} | |
void P1P2Serial::setEcho(uint8_t b) | |
// Set echo mode (verify and read back written data) on or off | |
// off: no read-back, no verification or bus collission detection | |
// on (default): each written byte will be read back and received, collission will be detected | |
// calling Echo has immediate effect | |
{ | |
Echo = b; | |
} | |
static uint16_t prev_edge_capture; // previous capture of edge | |
#ifdef H_SERIES | |
static byte firstbyteUncertainty = 0; // 1 signals 11/12 bit uncertainty | |
#endif /* H_SERIES */ | |
ISR(CAPTURE_INTERRUPT) | |
{ | |
// called upon each edge during writes if in scopemode | |
// and | |
// called upon each falling edge detected during reads | |
// Daikin/other except H-link2: | |
// state = 0: at falling edge of start pulse, after pause | |
// state = 1: at falling edge of start pulse shortly after previous byte (so: store previously received byte) | |
// state = 2..10: nr of data/parity bit of this falling edge | |
// | |
// H-link2: | |
// state = 2: data bit OR second start bit | |
// state = 3..9: data bits | |
// state = 10: data bit OR parity bit | |
// state = 11: parity bit OR stop bit | |
// state = 12: should not happen (flag with UC|PE) | |
uint8_t state, head; | |
uint16_t capture; | |
uint16_t offset_overflow; | |
IRQ_START; | |
capture = GET_INPUT_CAPTURE(); | |
uint16_t sws_count_temp = GET_TIMER_R_COUNT(); | |
state = rx_state; | |
if (!state) { | |
// start reading, time reading: | |
IRQ_STOP; | |
IRQ_BEGIN; | |
IRQ_START; | |
DIGITAL_SET_LED_READ; | |
DIGITAL_RESET_LED_WRITE; | |
DIGITAL_RESET_LED_ERROR; | |
} | |
if (state) { | |
// detect/suppress oscillations or spurious spikes (except when expecting new start pulse, where comparison may fail due to 16-bit limitation) | |
if (capture - prev_edge_capture < Rticks_suppression) { | |
// log spike | |
SW_SCOPE_LOG_EVENT(capture, SWS_EVENT_EDGE_SPIKE | state); | |
#ifdef SUPPRESS_OSCILLATION | |
IRQ_STOP; | |
return; | |
#endif /* SUPPRESS_OSCILLATION */ | |
} | |
} | |
switch (state) { | |
case 0 : | |
case 1 : | |
#ifdef H_SERIES | |
firstbyteUncertainty = SIGNAL_UC; | |
#endif /* H_SERIES */ | |
// this is first falling edge, it must be start pulse. First confirm received byte, if any (!NO_HEAD2), without SIGNAL_EOP | |
if (rx_buffer_head2 != NO_HEAD2) { | |
rx_buffer_head = rx_buffer_head2; | |
rx_buffer_head2 = NO_HEAD2; | |
} | |
startbit_delta = time_msec; | |
// time_msec = 0; // to prevent a write start to reduce bus collision risk, not needed as MS_TIMER is disabled anyway | |
DISABLE_MS_TIMER(); | |
// rx_target set to middle of first data bit | |
rx_target = capture + Rticks_per_bit_and_semibit; | |
rx_state = 2; | |
rx_paritycheck = 0; | |
SET_COMPARE_R(rx_target); | |
ENABLE_INT_COMPARE_R(); // only needed in state = 0 but doesn't hurt to do it in state = 1 | |
#ifdef SW_SCOPE | |
// if P1P2Monitor is ready reading data (sws_block = 0), start new log operation | |
if ((state == 0) && !sws_block) { | |
sw_scope = sw_scope_next; | |
if (sw_scope) SW_SCOPE_START_LOG; | |
} | |
#endif /* SW_SCOPE */ | |
break; | |
case 2 ... 9: // data bits (except for H-link2 and state=2, in which case this is a 0 data bit OR a 2nd 0 start bit, | |
// for now assume it is data bit and correct later if assumption is wrong) | |
rx_byte >>= 1; | |
rx_target += Rticks_per_bit; // set target time to (one semibit after) next possible falling edge | |
SET_COMPARE_R(rx_target); | |
CLEAR_COMPARE_R_FLAG(); | |
rx_state = state + 1; | |
break; | |
case 10 : // state=10: parity bit (in case of H-link2: parity bit OR data bit) | |
#ifdef H_SERIES | |
if (firstbyteUncertainty) { | |
// not sure yet regular 11-bit or double-start-bit pattern; we can shift one more time, shifting 0 out of rx_byte | |
rx_byte >>= 1; | |
} | |
#endif | |
// as (data or parity) bit is 0, no need to modify rx_paritycheck | |
rx_target += Rticks_per_bit; // set target time to (one semibit after) next possible falling edge = in stopbit | |
SET_COMPARE_R(rx_target); // for non-HLink-2, this COMPARE_R_INTERRUPT is never supposed to be cancelled | |
CLEAR_COMPARE_R_FLAG(); | |
rx_state = 11; | |
break; | |
#ifdef H_SERIES | |
case 11 : // only here for 0 parity bit of double-start-bit pattern | |
firstbyteUncertainty = 0; // double-start-bit-pattern, OK, no need to shift back | |
rx_target += Rticks_per_bit + Rticks_per_bit; // set target time to (one semibit after) next possible falling edge (= stop bit, so no edge) | |
SET_COMPARE_R(rx_target); // this COMPARE_R_INTERRUPT never supposed to be cancelled | |
CLEAR_COMPARE_R_FLAG(); | |
rx_state = 12; | |
break; | |
case 12 : // should not happen, but record byte nevertheless... unless only here for 0 parity bit of double-start-bit pattern or in bounce case | |
SET_COMPARE_R(rx_target + Rticks_per_bit * (1 + Allow)); | |
CLEAR_COMPARE_R_FLAG(); | |
rx_state = 1; | |
head = rx_buffer_head + 1; | |
if (head >= RX_BUFFER_SIZE) head = 0; | |
if (head != rx_buffer_tail) { | |
rx_buffer[head] = 0xFF; | |
delta_buffer[head] = startbit_delta; // time from previous byte | |
error_buffer[head] = firstbyteUncertainty; | |
if (firstbyteUncertainty) error_buffer[head] |= ERROR_PE; // signal "should not happen" | |
// DIGITAL_WRITE_LED_ERROR(rx_paritycheck); // Suppress PE error detection to set LED_ERROR as some PE errors are to be expected | |
rx_buffer_head2 = head; | |
} else { | |
// signal buffer overrun for *previous* byte | |
error_buffer[rx_buffer_head] |= ERROR_OR; | |
DIGITAL_SET_LED_ERROR; | |
rx_buffer_head2 = rx_buffer_head; // so SIGNAL_EOP can be added | |
} | |
firstbyteUncertainty = 0; | |
PRESET_ENABLE_MS_TIMER(); | |
break; | |
#else /* H_SERIES */ | |
case 11 : // state = 11: we received falling edge during stop bit before COMPARE_R_INTERRUPT ran. Should not happen. | |
break; | |
#endif /* H_SERIES */ | |
default : // Should not happen. | |
break; | |
} | |
// log falling edge during reading | |
SW_SCOPE_LOG_EVENT(capture, SWS_EVENT_EDGE_FALLING_R | state); | |
prev_edge_capture = capture; | |
IRQ_STOP; | |
} | |
ISR(COMPARE_R_INTERRUPT) | |
// The COMPARE_R_INTERRUPT routine is called during every data bit (state=2..9) or parity bit (state=10), unless there is a falling edge for that bit, | |
// and also during stop bit (state=11) | |
// It is also called during the latest potential location of the next start bit (with state==1, taking Allow into account), | |
// but only if no start bit has been detected, otherwise this interrupt will be cancelled. | |
// this allows detection of an end of communication block. | |
// | |
// H-link2: | |
// state = 2: data bit OR second start bit | |
// state = 3..9: data bits | |
// state = 10: data bit OR parity bit | |
// state = 11: parity bit OR stop bit | |
// state = 12: stop bit | |
{ | |
IRQ_START; | |
uint16_t capture = GET_COMPARE_R(); | |
uint16_t sws_count_temp = GET_TIMER_R_COUNT(); | |
// COMPARE_R_INTERRUPT | |
uint8_t head; | |
uint8_t state; | |
state = rx_state; | |
#ifdef H_SERIES | |
uint8_t stopBit = 1; | |
#endif /* H_SERIES */ | |
switch (state) { | |
case 1 : // no new start bit detected within expected time frame; thus pause in received data detected; register SIGNAL_EOP and quit rx mode | |
DISABLE_INT_COMPARE_R(); | |
rx_state = 0; | |
if (rx_buffer_head2 != NO_HEAD2) { | |
rx_buffer_head = rx_buffer_head2; | |
error_buffer[rx_buffer_head] |= SIGNAL_EOP; | |
rx_buffer_head2 = NO_HEAD2; | |
} | |
DIGITAL_RESET_LED_READ; | |
IRQ_STOP; | |
IRQ_END_R; | |
return; | |
case 2 : // state = 2: we received a 1 data bit; for H-link2, signal that this cannot be 2nd startbit | |
#ifdef H_SERIES | |
firstbyteUncertainty = 0; | |
#endif /* H_SERIES */ | |
// fallthrough | |
case 3 ... 9: // state = 2 or 3..9: we received a 1 data bit. | |
rx_byte = (rx_byte >> 1) | 0x80; | |
rx_paritycheck ^= 0x80; | |
rx_state = state + 1; | |
rx_target += Rticks_per_bit; | |
SET_COMPARE_R(rx_target); | |
break; | |
case 10 : // state = 10: we received a 1 parity bit (or, in case of H-link2, a 1 parity bit or a 1 data bit) | |
#ifdef H_SERIES | |
if (firstbyteUncertainty) { | |
// not sure yet regular 11-bit or double-start-bit pattern; we can shift one more time, shifting 0 out of rx_byte | |
rx_byte = (rx_byte >> 1) | 0x80; // if 11-bit pattern, this may push 0 out and 1-paritybit in | |
} | |
#endif /* H_SERIES */ | |
rx_paritycheck ^= 0x80; | |
rx_state = 11; | |
rx_target += Rticks_per_bit; // this could be reduced to +semibit if needed timingwise | |
SET_COMPARE_R(rx_target); | |
#ifdef H_SERIES | |
DIGITAL_WRITE_LED_ERROR(rx_paritycheck); | |
#else /* H_SERIES */ | |
// DIGITAL_WRITE_LED_ERROR(rx_paritycheck); // Suppress PE error detection to set LED_ERROR as some PE errors are to be expected | |
#endif /* H_SERIES */ | |
break; | |
case 11 : // state = 11: we are in stop bit; where W should not be hampered by our lengthy activity of finishing up | |
// For H-link2, this can still be 1 parity bit for 12-bit pattern | |
#ifdef H_SERIES | |
if (firstbyteUncertainty) { | |
if (rx_paritycheck) { | |
// OK, this is assumed to be 12-bit pattern parity bit, not stop bit | |
rx_paritycheck ^= 0x80; | |
stopBit = 0; | |
} else { | |
// OK, this is assumed to be stop bit already, shift back, set stop bit | |
rx_byte <<= 1; // should shift back for 11-bit pattern to avoid parity error | |
// already stopBit = 1; | |
} | |
} | |
// fall-through | |
case 12 : // stop bit | |
#endif /* H_SERIES */ | |
// for non-Hlink-2, this is still "case 11" ! | |
// we do most of the work here but have to leave some for the next start pulse routine (via rx_buffer_head2) | |
#ifndef H_SERIES | |
SET_COMPARE_R(rx_target + Rticks_per_bit * (1 + Allow)); | |
#else /* H_SERIES */ | |
SET_COMPARE_R(rx_target + Rticks_per_bit * (1 + (stopBit ? 0 : 1) + Allow)); | |
#endif /* H_SERIES */ | |
rx_state = 1; | |
head = rx_buffer_head + 1; | |
if (head >= RX_BUFFER_SIZE) head = 0; | |
if (head != rx_buffer_tail) { | |
rx_buffer[head] = rx_byte; | |
delta_buffer[head] = startbit_delta; // time from previous byte | |
#ifndef H_SERIES | |
error_buffer[head] = 0; | |
#else /* H_SERIES */ | |
error_buffer[head] = firstbyteUncertainty; | |
#endif /* H_SERIES */ | |
#ifdef GENERATE_FAKE_ERRORS | |
if (fakeError(FAKE_ERROR_PE)) { | |
error_buffer[head] |= (ERROR_PE << 8); | |
SW_SCOPE_LOG_ERROR(capture, SWS_EVENT_ERR_PE_FAKE); | |
} | |
#endif /* GENERATE_FAKE_ERRORS */ | |
if (rx_paritycheck) { | |
error_buffer[head] |= ERROR_PE; | |
SW_SCOPE_LOG_ERROR(capture, SWS_EVENT_ERR_PE); | |
} | |
#ifdef H_SERIES | |
DIGITAL_WRITE_LED_ERROR(rx_paritycheck); | |
#else /* H_SERIES */ | |
// DIGITAL_WRITE_LED_ERROR(rx_paritycheck); // Suppress PE error detection to set LED_ERROR as some PE errors are to be expected | |
#endif /* H_SERIES */ | |
rx_buffer_head2 = head; | |
} else { | |
// signal buffer overrun for *previous* byte | |
error_buffer[rx_buffer_head] |= ERROR_OR; | |
DIGITAL_SET_LED_ERROR; | |
rx_buffer_head2 = rx_buffer_head; // so SIGNAL_EOP can be added | |
} | |
#ifdef H_SERIES | |
firstbyteUncertainty = 0; | |
#endif /* H_SERIES */ | |
PRESET_ENABLE_MS_TIMER(); | |
break; | |
case 0 : | |
default : // Should not happen; | |
break; | |
} | |
SW_SCOPE_LOG_EVENT(sws_count_temp, SWS_EVENT_SIGNAL_HIGH_R | state); | |
IRQ_STOP; | |
} | |
uint16_t P1P2Serial::read_delta(void) | |
// should only be called if available()==1; otherwise, returns 0 | |
{ | |
uint8_t head, tail; | |
uint16_t out; | |
head = rx_buffer_head; | |
tail = rx_buffer_tail; | |
if (head == tail) return 0; | |
if (++tail >= RX_BUFFER_SIZE) tail = 0; | |
out = delta_buffer[tail]; | |
return out; | |
} | |
errorbuf_t P1P2Serial::read_error(void) | |
// should only be called if available()==1; otherwise, returns 0 | |
{ | |
uint8_t head, tail; | |
errorbuf_t out; | |
head = rx_buffer_head; | |
tail = rx_buffer_tail; | |
if (head == tail) return 0; | |
if (++tail >= RX_BUFFER_SIZE) tail = 0; | |
return error_buffer[tail]; | |
} | |
uint8_t P1P2Serial::read(void) | |
// should only be called if available()==1; otherwise, returns 0 | |
{ | |
uint8_t head, tail, out; | |
head = rx_buffer_head; | |
tail = rx_buffer_tail; | |
if (head == tail) return 0; | |
if (++tail >= RX_BUFFER_SIZE) tail = 0; | |
out = rx_buffer[tail]; | |
rx_buffer_tail = tail; | |
return out; | |
} | |
bool P1P2Serial::available(void) | |
{ | |
uint8_t head, tail; | |
head = rx_buffer_head; | |
tail = rx_buffer_tail; | |
if (head >= tail) return head - tail; | |
return RX_BUFFER_SIZE + head - tail; | |
} | |
bool P1P2Serial::packetavailable(void) | |
{ | |
uint8_t head, tail; | |
head = rx_buffer_head; | |
tail = rx_buffer_tail; | |
while (1) { | |
if (head == tail) return 0; | |
if (++tail >= RX_BUFFER_SIZE) tail = 0; | |
if (error_buffer[tail] & SIGNAL_EOP) return 1; | |
} | |
} | |
void P1P2Serial::flushInput(void) | |
{ | |
rx_buffer_head = rx_buffer_tail; | |
} | |
uint16_t P1P2Serial::readpacket(uint8_t* readbuf, uint16_t &delta, errorbuf_t* errorbuf, uint8_t maxlen, uint8_t crc_gen, uint8_t crc_feed) | |
{ | |
// Reads one packet (in blocking mode) | |
// To avoid blocking, only call this function if packetavailable() | |
// stores maximum of maxlen bytes of read data into readbuf | |
// returns total #bytes received (until v0.9.3: #bytes stored, as of v0.9.4: #bytes received, even if not stored), | |
// if (packet size/return value > maxlen) some received bytes cannot be stored, and error codes are condensed into errorbuf[maxlen - 1] | |
// reading continues in case of error | |
// stores maximum of maxlen bytes of error codes into errorbuf (unless errorbuf = NULL), | |
// returns timing information (pause on bus before this package) in parameter delta | |
// If crc_gen is not zero, verifies last byte as CRC byte; CRC byte is also stored and is counted in return value if space is available | |
uint8_t EOP = 0; | |
uint8_t bytecnt = 0; | |
uint8_t crc = crc_feed; | |
#ifdef H_SERIES | |
uint8_t expectedLength = 0xFF; // split H-link2 packets based on 3rd byte | |
#endif /* H_SERIES */ | |
#ifndef H_SERIES | |
while (!EOP) { | |
#else /* H_SERIES */ | |
while (!EOP && (expectedLength > bytecnt)) { | |
#endif /* H_SERIES */ | |
if (available()) { | |
errorbuf_t error = read_error(); | |
EOP = (error & SIGNAL_EOP); | |
if (errorbuf) { | |
if (bytecnt < maxlen) { | |
errorbuf[bytecnt] = (error & ERROR_FLAGS); | |
} else { | |
errorbuf[maxlen - 1] |= (error & ERROR_FLAGS); | |
} | |
} | |
if (!bytecnt) delta = read_delta(); | |
uint8_t c = read(); | |
if ((EOP == 0) || (crc_gen == 0)) { | |
if (bytecnt < maxlen) { | |
readbuf[bytecnt] = c; | |
} | |
if (crc_gen != 0) for (uint8_t i = 0; i < 8; i++) { | |
crc = (((crc ^ c) & 0x01) ? ((crc >> 1) ^ crc_gen) : (crc >> 1)); | |
c >>= 1; | |
} | |
} else { | |
// EOP, crc in use, check crc | |
if (bytecnt < maxlen) { | |
readbuf[bytecnt] = c; | |
if (c != crc) { | |
if (bytecnt < maxlen) { | |
errorbuf[bytecnt] |= ERROR_CRC; | |
} else { | |
errorbuf[maxlen - 1] |= ERROR_CRC; | |
} | |
DIGITAL_SET_LED_ERROR; | |
} | |
} | |
} | |
bytecnt++; | |
#ifdef H_SERIES | |
if (bytecnt == 3) expectedLength = readbuf[2]; | |
#endif /* H_SERIES */ | |
} | |
} | |
return bytecnt; | |
} | |
void P1P2Serial::writepacket(uint8_t* writebuf, uint8_t l, uint16_t t, uint8_t crc_gen, uint8_t crc_feed) | |
{ | |
// Writes one packet of l bytes, t ms after last bus action; | |
// If crc_gen is not zero, adds CRC byte to packet | |
// Note that t=0 or t=1 increases risk of bus collisions, don't use it if not needed (t<2 will be changed to t=2 in new library). | |
setDelay(t); | |
uint8_t crc = crc_feed; | |
for (uint8_t i = 0; i < l; i++) { | |
uint8_t c = writebuf[i]; | |
write(c); | |
if (crc_gen != 0) for (uint8_t i = 0; i < 8; i++) { | |
crc = ((crc ^ c) & 0x01 ? ((crc >> 1) ^ crc_gen) : (crc >> 1)); | |
c >>= 1; | |
} | |
} | |
if (crc_gen) write(crc); | |
} | |
int32_t P1P2Serial::uptime_sec(void) | |
{ | |
// returns uptime in seconds if S_TIMER is defined, otherwise returns -1; wraps in 65.8 years | |
#ifdef S_TIMER | |
return time_sec; | |
#else | |
return -1; | |
#endif | |
} | |
int32_t P1P2Serial::uptime_millisec(void) | |
{ // returns uptime in ms, wraps in 24.8 days | |
#ifdef S_TIMER | |
// returns uptime in milliseconds in 8ms resolution, wraps in 24.8 days | |
return (time_millisec & 0x7FFFFFFF); | |
#else | |
return -1; | |
#endif | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment