Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #include "cc1101.h"
- #define cc1101_Select() digitalWrite(chipSelectPin, LOW)
- #define cc1101_Deselect() digitalWrite(chipSelectPin, HIGH)
- // MISO low = chip ready (CC1101 "CHIP_RDYn" signal).
- #define wait_Miso() while (digitalRead(MISO) > 0)
- CC1101::CC1101(void) {}
- void CC1101::writeReg(byte registerAddress, byte value)
- {
- cc1101_Select();
- wait_Miso();
- SPI.transfer(registerAddress);
- SPI.transfer(value);
- cc1101_Deselect();
- }
- void CC1101::writeBurstReg(byte registerAddress, byte *buffer, byte length)
- {
- byte address = registerAddress | WRITE_BURST;
- cc1101_Select();
- wait_Miso();
- SPI.transfer(address);
- for (byte i = 0; i < length; i++)
- SPI.transfer(buffer[i]);
- cc1101_Deselect();
- }
- void CC1101::cmdStrobe(byte cmd)
- {
- cc1101_Select();
- wait_Miso();
- SPI.transfer(cmd);
- cc1101_Deselect();
- }
- byte CC1101::readReg(byte registerAddress, byte registerType)
- {
- byte address = registerAddress | registerType;
- cc1101_Select();
- wait_Miso();
- SPI.transfer(address);
- byte val = SPI.transfer(0x00);
- cc1101_Deselect();
- return val;
- }
- void CC1101::reset(void)
- {
- cc1101_Deselect();
- delayMicroseconds(5);
- cc1101_Select();
- delayMicroseconds(10);
- cc1101_Deselect();
- delayMicroseconds(41);
- cc1101_Select();
- wait_Miso();
- SPI.transfer(CC1101_SRES);
- wait_Miso();
- cc1101_Deselect();
- }
- void CC1101::setCarrierFreq()
- {
- writeReg(CC1101_FREQ2, CC1101_DEFVAL_FREQ2_868);
- writeReg(CC1101_FREQ1, CC1101_DEFVAL_FREQ1_868);
- writeReg(CC1101_FREQ0, CC1101_DEFVAL_FREQ0_868);
- }
- void CC1101::setPowerDownState()
- {
- // SPWD only takes effect from IDLE — strobe IDLE first in case we were in RX/TX.
- cmdStrobe(CC1101_SIDLE);
- cmdStrobe(CC1101_SPWD);
- }
- void CC1101::setRxState(void)
- {
- writeReg(CC1101_MDMCFG2, CC1101_DEFVAL_MDMCFG2);
- writeReg(CC1101_IOCFG0, CC1101_SERIAL_OUT_IOCFG0);
- cmdStrobe(CC1101_SRX);
- }
- void CC1101::setTxState(void)
- {
- writeReg(CC1101_IOCFG0, CC1101_DEFVAL_IOCFG0);
- writeReg(CC1101_MDMCFG2, CC1101_DEFVAL_MDMCFG2);
- cmdStrobe(CC1101_STX);
- }
- void CC1101::configRegisters(void)
- {
- reset();
- setCarrierFreq();
- writeReg(CC1101_IOCFG0, CC1101_DEFVAL_IOCFG0);
- writeReg(CC1101_IOCFG2, CC1101_DEFVAL_IOCFG2);
- writeReg(CC1101_FSCTRL1, CC1101_DEFVAL_FSCTRL1);
- writeReg(CC1101_FSCTRL0, CC1101_DEFVAL_FSCTRL0);
- writeReg(CC1101_MDMCFG4, CC1101_DEFVAL_MDMCFG4);
- writeReg(CC1101_MDMCFG3, CC1101_DEFVAL_MDMCFG3);
- writeReg(CC1101_MDMCFG2, CC1101_DEFVAL_MDMCFG2);
- writeReg(CC1101_MDMCFG1, CC1101_DEFVAL_MDMCFG1);
- writeReg(CC1101_MDMCFG0, CC1101_DEFVAL_MDMCFG0);
- writeReg(CC1101_CHANNR, CC1101_DEFVAL_CHANNR);
- writeReg(CC1101_DEVIATN, CC1101_DEFVAL_DEVIATN);
- writeReg(CC1101_FREND1, CC1101_DEFVAL_FREND1);
- writeReg(CC1101_FREND0, CC1101_DEFVAL_FREND0);
- writeReg(CC1101_MCSM0, CC1101_DEFVAL_MCSM0);
- writeReg(CC1101_FOCCFG, CC1101_DEFVAL_FOCCFG);
- writeReg(CC1101_BSCFG, CC1101_DEFVAL_BSCFG);
- writeReg(CC1101_AGCCTRL2, CC1101_DEFVAL_AGCCTRL2);
- writeReg(CC1101_AGCCTRL1, CC1101_DEFVAL_AGCCTRL1);
- writeReg(CC1101_AGCCTRL0, CC1101_DEFVAL_AGCCTRL0);
- writeReg(CC1101_WORCTRL, CC1101_DEFVAL_WORCTRL);
- writeReg(CC1101_FSCAL3, CC1101_DEFVAL_FSCAL3);
- writeReg(CC1101_FSCAL2, CC1101_DEFVAL_FSCAL2);
- writeReg(CC1101_FSCAL1, CC1101_DEFVAL_FSCAL1);
- writeReg(CC1101_FSCAL0, CC1101_DEFVAL_FSCAL0);
- writeReg(CC1101_TEST0, CC1101_DEFVAL_TEST0);
- writeReg(CC1101_PKTCTRL1, CC1101_DEFVAL_PKTCTRL1);
- writeReg(CC1101_PKTCTRL0, CC1101_DEFVAL_PKTCTRL0);
- uint8_t txBuffer[8] = { 0x6F, 0x26, 0x2E, 0x7F, 0x8A, 0x84, 0xCA, 0xC4
- };
- writeBurstReg(CC1101_UNKNOWNFIFO, txBuffer, 8);
- cmdStrobe(CC1101_SIDLE);
- cmdStrobe(CC1101_SIDLE);
- }
- void CC1101::init(void)
- {
- pinMode(chipSelectPin, OUTPUT);
- SPI.begin();
- pinMode(CC1101_GDO2, INPUT);
- reset();
- setPowerDownState();
- msgId[indoorHum].codeId = 0x12A0;
- msgId[fanSpeed].codeId = 0x22F1;
- msgId[fanInfo].codeId = 0x31DA;
- }
- void CC1101::setRxMode(void)
- {
- configRegisters();
- setRxState();
- }
- // Ramses II checksum: byte sum of all preceding bytes, two's complement.
- // The frame as a whole (data + CRC) sums to zero modulo 256.
- uint8_t CC1101::calcCrc(uint8_t dataframe[], uint8_t length)
- {
- uint8_t sum = 0;
- for (uint8_t i = 0; i < length - 1; i++)
- sum += dataframe[i]; // wraps mod 256
- return (uint8_t)(0 - sum);
- }
- // Slide a 6-byte window across Serial1 looking for Ramses II BOF: 55 ?? 00 33 55 53.
- // Byte at offset 1 is intentionally unchecked (preserved from original implementation).
- // Timeout only starts ticking once the first byte arrives — a W (22F7) makes the
- // fan mutate state before replying, so the quiet period before bytes start can
- // exceed the per-frame timeout. Hard upper bound of 7 s keeps us safely under the
- // 8 s watchdog if the line is truly silent.
- bool CC1101::waitForBof(uint16_t timeoutMs)
- {
- uint8_t rxBuffer[6] = {0};
- unsigned long startedMillis = 0;
- unsigned long entryMillis = millis();
- bool started = false;
- while (true)
- {
- if (Serial1.available() > 0)
- {
- if (!started)
- {
- started = true;
- startedMillis = millis();
- }
- for (uint8_t i = 0; i < 5; i++)
- rxBuffer[i] = rxBuffer[i + 1];
- rxBuffer[5] = Serial1.read();
- if ((rxBuffer[5] == 0x53) && (rxBuffer[4] == 0x55) && (rxBuffer[3] == 0x33) &&
- (rxBuffer[2] == 0x00) && (rxBuffer[0] == 0x55))
- return true;
- }
- if (started && millis() - startedMillis > timeoutMs)
- return false;
- if (millis() - entryMillis > 7000UL)
- return false;
- }
- }
- // Build a Ramses II frame: header byte, 6 address bytes, 2-byte opcode, length, payload, CRC.
- // `out` must be at least 11 + payloadLength bytes.
- void CC1101::buildRamsesFrame(uint8_t header, uint16_t opcode, const uint8_t *payload, uint8_t payloadLength, uint8_t *out)
- {
- out[0] = header;
- for (uint8_t i = 0; i < 6; i++)
- out[1 + i] = newFanState.address[i];
- out[7] = (uint8_t)(opcode >> 8);
- out[8] = (uint8_t)(opcode & 0xFF);
- out[9] = payloadLength;
- for (uint8_t i = 0; i < payloadLength; i++)
- out[10 + i] = payload[i];
- const uint8_t frameLength = 11 + payloadLength;
- out[frameLength - 1] = calcCrc(out, frameLength);
- }
- void CC1101::manchesterDecode(uint8_t rxBuffer[], uint8_t length, uint8_t *rxPayload)
- {
- uint8_t payloadCount = 0;
- const uint8_t manchesterTable[16] = {0xAA, 0xA9, 0xA6, 0xA5, 0x9A, 0x99, 0x96, 0x95, 0x6A, 0x69, 0x66, 0x65, 0x5A, 0x59, 0x56, 0x55};
- for (uint8_t i = 0; i < length; i += 2)
- {
- for (uint8_t x = 0; x < 16; x++)
- {
- if (rxBuffer[i + 1] == manchesterTable[x])
- rxPayload[payloadCount] = x;
- }
- for (uint8_t x = 0; x < 16; x++)
- {
- if (rxBuffer[i] == manchesterTable[x])
- rxPayload[payloadCount] |= x << 4;
- }
- payloadCount++;
- }
- }
- void CC1101::manchesterEncode(uint8_t txBuffer[], uint8_t length, uint8_t *payloadEncoded)
- {
- const uint8_t manchesterTable[16] = {0xAA, 0xA9, 0xA6, 0xA5, 0x9A, 0x99, 0x96, 0x95, 0x6A, 0x69, 0x66, 0x65, 0x5A, 0x59, 0x56, 0x55};
- uint8_t txCount = 0;
- for (int i = 0; i < length; i++)
- {
- payloadEncoded[txCount++] = manchesterTable[(txBuffer[i] & 0xF0) >> 4];
- payloadEncoded[txCount++] = manchesterTable[txBuffer[i] & 0x0F];
- }
- }
- // Decode a CRC- and address-validated Ramses II / Orcon payload into newFanState.
- // CRC passing doesn't bound the buffer length, so each opcode branch verifies that
- // the bytes it reads are actually present before touching newFanState.
- void CC1101::parseRamsesPayload(uint8_t dataframe[], uint8_t length)
- {
- // Need at least header (0..6) + opcode (7..8) + protocol-length byte (9).
- if (length < 10) return;
- uint16_t opcode = ((uint16_t)dataframe[7] << 8) | (uint16_t)dataframe[8];
- uint8_t payloadLength = dataframe[9];
- if ((opcode == 0x22F1) && (payloadLength == 0x03) && (length >= 12)) // FAN SPEED
- {
- newFanState.fanSpeed = dataframe[11];
- msgId[fanSpeed].rxFlag = true;
- }
- if ((opcode == 0x22F7) && (payloadLength == 0x03) && (length >= 12)) // BYPASS (BYPASS_OPEN / BYPASS_CLOSE / BYPASS_AUTO)
- {
- newFanState.bypassMode = dataframe[11];
- }
- if ((opcode == 0x31D9) && (length >= 13)) // FAN SPEED, protocol-length may differ between MVS and HRC so don't check it
- {
- newFanState.fanSpeed = dataframe[12];
- msgId[fanSpeed].rxFlag = true;
- }
- if ((opcode == 0x12A0) && (payloadLength == 0x02) && (length >= 12)) // Humidity
- {
- newFanState.indoorHumidity = dataframe[11];
- msgId[indoorHum].rxFlag = true;
- }
- // 2411 RP carries a single config parameter; param 0x75 is the Orcon "Comfort temperature"
- // (bypass target). Older Orcons reply with 22-byte payloads, newer with 23 — the value
- // sits at the same offset in both, so accept either length. Value is a 4-byte big-endian
- // int32 of centi-°C; max is 3000 (30.0°C), so the low 16 bits suffice and match the
- // representation used by the other temperature fields.
- if ((opcode == 0x2411) && ((payloadLength == 0x16) || (payloadLength == 0x17)) && (length >= 19))
- {
- if (dataframe[11] == 0x00 && dataframe[12] == 0x75 && dataframe[14] == 0x92)
- {
- newFanState.comfortTemperature = ((uint16_t)dataframe[17] << 8) | dataframe[18];
- }
- }
- if ((opcode == 0x31DA) && (payloadLength == 0x1E) && (length >= 39)) // EXTENDED 31DA INFO
- {
- newFanState.indoorHumidity = dataframe[15];
- newFanState.outdoorHumidity = dataframe[16];
- newFanState.exhaustTemperature = (dataframe[17] * 256) + dataframe[18];
- newFanState.supplyTemperature = (dataframe[19] * 256) + dataframe[20];
- newFanState.indoorTemperature = (dataframe[21] * 256) + dataframe[22];
- newFanState.outdoorTemperature = (dataframe[23] * 256) + dataframe[24];
- newFanState.bypassPosition = dataframe[27];
- newFanState.exhaustFanspeed = dataframe[29];
- newFanState.supplyFanspeed = dataframe[30];
- newFanState.supplyFlow = (dataframe[35] * 256) + dataframe[36];
- newFanState.exhaustFlow = (dataframe[37] * 256) + dataframe[38];
- msgId[fanInfo].rxFlag = true;
- }
- }
- bool CC1101::transmitData(uint8_t payload[], uint8_t length)
- {
- const uint8_t bufferLength = 100;
- uint8_t rxBuffer[bufferLength];
- int rxLength = 0;
- uint8_t txFrameLength = (length * 2) + 15; // 15 bytes overhead, length*2 manchester-encoded data
- uint8_t txBuffer[txFrameLength];
- uint8_t txPayloadEncoded[length * 2];
- bool fanFrameValid = false;
- manchesterEncode(payload, length, txPayloadEncoded);
- // PREAMBLE
- for (int i = 0; i < 9; i++)
- txBuffer[i] = 0x55;
- // BOF
- txBuffer[9] = 0xFF;
- txBuffer[10] = 0x00;
- txBuffer[11] = 0x33;
- txBuffer[12] = 0x55;
- txBuffer[13] = 0x53;
- // Manchester-encoded payload
- for (int i = 0; i < txFrameLength - 1; i++)
- txBuffer[i + 14] = txPayloadEncoded[i];
- // EOF
- txBuffer[txFrameLength - 1] = 0x35;
- Serial1.setTimeout(RX_TIME_OUT);
- configRegisters();
- setTxState();
- Serial1.write(txBuffer, txFrameLength);
- Serial1.flush();
- // Drain TX loopback (RX and TX pins are tied)
- while (Serial1.available()) Serial1.read();
- cmdStrobe(CC1101_SIDLE);
- setRxState();
- if (waitForBof(RX_TIME_OUT))
- {
- rxLength = Serial1.readBytesUntil(0x35, rxBuffer, sizeof(rxBuffer) - 1);
- if (rxLength > 0 && (rxLength % 2) == 0)
- {
- uint8_t dataframeDecoded[rxLength / 2];
- manchesterDecode(rxBuffer, rxLength, dataframeDecoded);
- if (calcCrc(dataframeDecoded, rxLength / 2) == dataframeDecoded[(rxLength / 2) - 1])
- {
- fanFrameValid = true;
- for (uint8_t i = 1; i < 4; i++)
- if (dataframeDecoded[i] != newFanState.address[i + 2])
- fanFrameValid = false;
- if (fanFrameValid)
- parseRamsesPayload(dataframeDecoded, rxLength / 2);
- }
- }
- }
- cmdStrobe(CC1101_SIDLE);
- writeReg(CC1101_IOCFG0, CC1101_DEFVAL_IOCFG0);
- setPowerDownState();
- return fanFrameValid;
- }
- bool CC1101::cloneMode(void)
- {
- const uint8_t bufferLength = 75;
- uint8_t rxBuffer[bufferLength];
- int rxLength = 0;
- Serial1.setTimeout(RX_TIME_OUT);
- // Drain input buffer
- while (Serial1.available()) Serial1.read();
- if (!waitForBof(PAIR_TIME_OUT))
- return false;
- rxLength = Serial1.readBytesUntil(0x35, rxBuffer, sizeof(rxBuffer) - 1);
- if (rxLength == 0 || (rxLength % 2) != 0)
- return false;
- uint8_t dataframeDecoded[rxLength / 2];
- manchesterDecode(rxBuffer, rxLength, dataframeDecoded);
- if (calcCrc(dataframeDecoded, rxLength / 2) != dataframeDecoded[(rxLength / 2) - 1])
- return false;
- for (uint8_t i = 1; i < 7; i++)
- newFanState.address[i - 1] = dataframeDecoded[i];
- return true;
- }
- // Ramses II header bytes: RQ = 0x0C, I = 0x1C, W = 0x2C, RP = 0x3C
- bool CC1101::setBypass(uint8_t bypassMode)
- {
- // 22F7 bypass payload: 00 <mode> EF
- const uint8_t data[3] = { 0x00, bypassMode, 0xEF };
- uint8_t frame[11 + sizeof data];
- buildRamsesFrame(0x2C, 0x22F7, data, sizeof data, frame);
- return transmitData(frame, sizeof frame);
- }
- // Request the Orcon "Comfort temperature" (2411 param 0x75) — the bypass target temperature.
- // RQ payload: 00 00 <param_id>. Caller diffs newFanState vs currentFanState to detect a
- // fresh value; a coincident 31DA in the RX window can flip transmitData to true without
- // our RP arriving, in which case the diff stays empty and the next poll picks it up.
- bool CC1101::requestComfortTemperature(void)
- {
- const uint8_t data[3] = { 0x00, 0x00, 0x75 };
- uint8_t frame[11 + sizeof data];
- buildRamsesFrame(0x0C, 0x2411, data, sizeof data, frame);
- return transmitData(frame, sizeof frame);
- }
- // Write the Orcon "Comfort temperature" (2411 param 0x75) — the bypass target.
- // W payload (23 bytes, matches the RF15 remote):
- // [0..1] 00 00 prefix
- // [2] 75 param id
- // [3..4] 00 92 flags (W=0x00, RP/I=0x58) + data_type (centi-°C)
- // [5..8] 00 00 hi lo value, 4-byte BE — max 3000 so top 2 bytes are always 0
- // [9..12] 00 00 00 00 min = 0
- // [13..16] 00 00 0B B8 max = 3000 (30.0°C)
- // [17..20] 00 00 00 01 precision = 1
- // [21..22] 00 01 W trailing
- // The fan replies with an I broadcast carrying the new value; the 2411 RP parser
- // also matches I-frames so newFanState updates. Caller retries until currentFanState
- // matches the requested setpoint.
- bool CC1101::setComfortTemperature(uint16_t centiCelsius)
- {
- if (centiCelsius > 3000) centiCelsius = 3000;
- uint8_t data[23] = {
- 0x00, 0x00, 0x75, 0x00, 0x92,
- 0x00, 0x00, (uint8_t)(centiCelsius >> 8), (uint8_t)(centiCelsius & 0xFF),
- 0x00, 0x00, 0x00, 0x00,
- 0x00, 0x00, 0x0B, 0xB8,
- 0x00, 0x00, 0x00, 0x01,
- 0x00, 0x01
- };
- uint8_t frame[11 + sizeof data];
- buildRamsesFrame(0x2C, 0x2411, data, sizeof data, frame);
- return transmitData(frame, sizeof frame);
- }
- bool CC1101::txFanspeed(uint8_t fanSpeed)
- {
- // 22F1 fan speed payload: 00 <speed> 04
- const uint8_t data[3] = { 0x00, fanSpeed, 0x04 };
- uint8_t frame[11 + sizeof data];
- buildRamsesFrame(0x1C, 0x22F1, data, sizeof data, frame);
- return transmitData(frame, sizeof frame);
- }
- bool CC1101::txDemand(uint8_t demandByte)
- {
- // 31E0 8-byte Orcon CO2-sensor form: 00 00 <XX> 00 01 00 <ST> 00
- // <XX> at index 2: demand 0x00..0xFF; fan maps this to its own curve (clipped).
- // <ST> at index 6: sensor-type marker (0x64 / 0xAA observed; 0x64 default).
- const uint8_t data[8] = { 0x00, 0x00, demandByte, 0x00, 0x01, 0x00, 0x64, 0x00 };
- uint8_t frame[11 + sizeof data];
- buildRamsesFrame(0x1C, 0x31E0, data, sizeof data, frame);
- return transmitData(frame, sizeof frame);
- }
- // Polls the fan for one of several status opcodes per call. Two phases:
- // Warm-up (first TX_REQUEST_COUNT calls): round-robin all NUM_CODES opcodes so
- // each opcode has a chance to be heard at least once.
- // Steady-state (after): alternate fanSpeed with whichever of fanInfo or
- // indoorHum is responding (fanInfo preferred once seen).
- // Returns true if the TX/RX exchange succeeded.
- bool CC1101::requestFanState(void)
- {
- uint8_t payload[12] = {0x0C, newFanState.address[0], newFanState.address[1], newFanState.address[2], newFanState.address[3], newFanState.address[4], newFanState.address[5], 0x00, 0x00, 0x01, 0x00, 0x00};
- const uint8_t arrSize = sizeof payload;
- static uint8_t txCount = 0;
- static uint8_t requestCount = 0;
- static uint8_t slotOrder[NUM_CODES] = {fanSpeed, indoorHum, fanInfo};
- payload[7] = (msgId[slotOrder[txCount]].codeId >> 8);
- payload[8] = (msgId[slotOrder[txCount]].codeId & 0xFF);
- payload[arrSize - 1] = calcCrc(payload, arrSize);
- if (requestCount < TX_REQUEST_COUNT)
- {
- // Warm-up: cycle through all NUM_CODES slots.
- txCount = (txCount < NUM_CODES - 1) ? (txCount + 1) : 0;
- requestCount++;
- }
- else
- {
- // Steady-state: only slots 0 and 1 are used.
- slotOrder[0] = fanSpeed;
- slotOrder[1] = msgId[fanInfo].rxFlag ? fanInfo : indoorHum;
- txCount = (txCount < 1) ? (txCount + 1) : 0;
- }
- return transmitData(payload, arrSize);
- }
Add Comment
Please, Sign In to add comment