Ok, I implemented this now and it works perfect. However, I could not find the low and high FIFO threshold you were referring to, it seems the RX FIFO only has a single threshold. So I set this threshold to a low value first and then raise it when its interrupt triggers for the first time. I did have to set this to a fairly high value, below 7 it seemed to cause problems where the RF22 would remain stuck in RX mode / filling the FIFO (even after the pkvalid interrupt). I wasn't able to properly find out what was going wrong, but now the FIFO receives 10 bytes before finding out the real length, which seems to work fine (I'm not sure what happens for packets shorter than 10 packets, though).
Below is my Arduino program, which receives packets and decodes a few of them. I also have a patch against the
(version 1.25), since the "first read 10 bytes and then set the packet size"-stuff needed changes inside that library. Finally, here's a dump of all the register values I'm writing:
Code: Select all
#include <SPI.h>
#include <RF22.h>
#define lengthof(x) (sizeof(x) / sizeof(*x))
const RF22::ModemConfig config =
{
.reg_1c = 0x01,
.reg_1f = 0x03,
.reg_20 = 0x90,
.reg_21 = 0x20,
.reg_22 = 0x51,
.reg_23 = 0xea,
.reg_24 = 0x00,
.reg_25 = 0x58,
/* 2c - 2e are only for OOK */
.reg_2c = 0x00,
.reg_2d = 0x00,
.reg_2e = 0x00,
.reg_58 = 0x80, /* Copied from RF22 defaults */
.reg_69 = 0x60, /* Copied from RF22 defaults */
.reg_6e = 0x08,
.reg_6f = 0x31,
.reg_70 = 0x24,
.reg_71 = RF22_DTMOD_FIFO | RF22_MODTYP_FSK,
.reg_72 = 0x1e,
};
/* Sync words to send / check for. Don't forget to update RF22_SYNCLEN
* below if changing the length of this array. */
const uint8_t sync_words[] = {
0xc6,
0x26,
0xc6,
0x26,
};
enum modes {MODE_AUTO, MODE_MANUAL, MODE_TEMPORARY, MODE_BOOST};
const char *mode_str[] = {
[MODE_AUTO] = "auto",
[MODE_MANUAL] = "manual",
[MODE_TEMPORARY] = "temporary",
[MODE_BOOST] = "boost"
};
char *type_str(uint8_t type) {
switch(type) {
case 0x00: return "PairPing";
case 0x01: return "PairPong";
case 0x02: return "Ack";
case 0x03: return "TimeInformation";
case 0x10: return "ConfigWeekProfile";
case 0x11: return "ConfigTemperatures";
case 0x12: return "ConfigValve";
case 0x20: return "AddLinkPartner";
case 0x21: return "RemoveLinkPartner";
case 0x22: return "SetGroupId";
case 0x23: return "RemoveGroupId";
case 0x30: return "ShutterContactState";
case 0x40: return "SetTemperature";
case 0x42: return "WallThermostatState";
case 0x43: return "SetComfortTemperature";
case 0x44: return "SetEcoTemperature";
case 0x50: return "PushButtonState";
case 0x60: return "ThermostatState";
case 0x82: return "SetDisplayActualTemperature";
case 0xF1: return "WakeUp";
case 0xF0: return "Reset";
}
return "Unknown";
}
/* First 255 bytes of PN9 sequence used for data whitening by the CC1101
* chip. The RF22 chip is documented to support the same data whitening
* algorithm, but in practice seems to use a different sequence.
*
* Data was generated using the following python snippet:
*
import itertools
def pn9(state):
while True:
yield hex(state & 0xff)
# The pn9 generator is clocked 8 times while shifting in the
# next data byte
for i in range(8):
state = (state >> 1) + (((state & 1) ^ (state >> 5) & 1) << 8)
print(list(itertools.islice(pn9(0x1ff), 255)))
*/
const uint8_t pn9[] = {
0xff, 0xe1, 0x1d, 0x9a, 0xed, 0x85, 0x33, 0x24,
0xea, 0x7a, 0xd2, 0x39, 0x70, 0x97, 0x57, 0x0a,
0x54, 0x7d, 0x2d, 0xd8, 0x6d, 0x0d, 0xba, 0x8f,
0x67, 0x59, 0xc7, 0xa2, 0xbf, 0x34, 0xca, 0x18,
0x30, 0x53, 0x93, 0xdf, 0x92, 0xec, 0xa7, 0x15,
0x8a, 0xdc, 0xf4, 0x86, 0x55, 0x4e, 0x18, 0x21,
0x40, 0xc4, 0xc4, 0xd5, 0xc6, 0x91, 0x8a, 0xcd,
0xe7, 0xd1, 0x4e, 0x09, 0x32, 0x17, 0xdf, 0x83,
0xff, 0xf0, 0x0e, 0xcd, 0xf6, 0xc2, 0x19, 0x12,
0x75, 0x3d, 0xe9, 0x1c, 0xb8, 0xcb, 0x2b, 0x05,
0xaa, 0xbe, 0x16, 0xec, 0xb6, 0x06, 0xdd, 0xc7,
0xb3, 0xac, 0x63, 0xd1, 0x5f, 0x1a, 0x65, 0x0c,
0x98, 0xa9, 0xc9, 0x6f, 0x49, 0xf6, 0xd3, 0x0a,
0x45, 0x6e, 0x7a, 0xc3, 0x2a, 0x27, 0x8c, 0x10,
0x20, 0x62, 0xe2, 0x6a, 0xe3, 0x48, 0xc5, 0xe6,
0xf3, 0x68, 0xa7, 0x04, 0x99, 0x8b, 0xef, 0xc1,
0x7f, 0x78, 0x87, 0x66, 0x7b, 0xe1, 0x0c, 0x89,
0xba, 0x9e, 0x74, 0x0e, 0xdc, 0xe5, 0x95, 0x02,
0x55, 0x5f, 0x0b, 0x76, 0x5b, 0x83, 0xee, 0xe3,
0x59, 0xd6, 0xb1, 0xe8, 0x2f, 0x8d, 0x32, 0x06,
0xcc, 0xd4, 0xe4, 0xb7, 0x24, 0xfb, 0x69, 0x85,
0x22, 0x37, 0xbd, 0x61, 0x95, 0x13, 0x46, 0x08,
0x10, 0x31, 0x71, 0xb5, 0x71, 0xa4, 0x62, 0xf3,
0x79, 0xb4, 0x53, 0x82, 0xcc, 0xc5, 0xf7, 0xe0,
0x3f, 0xbc, 0x43, 0xb3, 0xbd, 0x70, 0x86, 0x44,
0x5d, 0x4f, 0x3a, 0x07, 0xee, 0xf2, 0x4a, 0x81,
0xaa, 0xaf, 0x05, 0xbb, 0xad, 0x41, 0xf7, 0xf1,
0x2c, 0xeb, 0x58, 0xf4, 0x97, 0x46, 0x19, 0x03,
0x66, 0x6a, 0xf2, 0x5b, 0x92, 0xfd, 0xb4, 0x42,
0x91, 0x9b, 0xde, 0xb0, 0xca, 0x09, 0x23, 0x04,
0x88, 0x98, 0xb8, 0xda, 0x38, 0x52, 0xb1, 0xf9,
0x3c, 0xda, 0x29, 0x41, 0xe6, 0xe2, 0x7b};
/**
* CRC code based on example from Texas Instruments DN502, matches
* CC1101 implementation
*/
#define CRC16_POLY 0x8005
uint16_t calc_crc_step(uint8_t crcData, uint16_t crcReg) {
uint8_t i;
for (i = 0; i < 8; i++) {
if (((crcReg & 0x8000) >> 8) ^ (crcData & 0x80))
crcReg = (crcReg << 1) ^ CRC16_POLY;
else
crcReg = (crcReg << 1);
crcData <<= 1;
}
return crcReg;
} // culCalcCRC
#define CRC_INIT 0xFFFF
uint16_t calc_crc(uint8_t *buf, size_t len) {
uint16_t checksum;
checksum = CRC_INIT;
// Init value for CRC calculation
for (size_t i = 0; i < len; i++)
checksum = calc_crc_step(buf[i], checksum);
return checksum;
}
void printHex(uint8_t *buf, size_t len, bool nl) {
for (size_t i = 0; i < len; ++i) {
if (buf[i] < 0x10)
Serial.print("0");
Serial.print(buf[i], HEX);
}
if (nl)
Serial.println("");
}
// Singleton instance of the radio
RF22 rf22;
void setup()
{
Serial.begin(115200); // setup serial
if (rf22.init())
Serial.println("Initialized");
else
Serial.println("RF22 init failed");
rf22.setModemRegisters(&config);
rf22.setFrequency(868.3, 0.035);
/* Disable TX packet control, since the RF22 doesn't do proper
* whitening so can't read the length header or CRC. We need RX packet
* control so the RF22 actually sends pkvalid interrupts when the
* manually set packet length is reached. */
rf22.spiWrite(RF22_REG_30_DATA_ACCESS_CONTROL, RF22_MSBFRST | RF22_ENPACRX);
/* No packet headers, 4 sync words, fixed packet length */
rf22.spiWrite(RF22_REG_32_HEADER_CONTROL1, RF22_BCEN_NONE | RF22_HDCH_NONE);
rf22.spiWrite(RF22_REG_33_HEADER_CONTROL2, RF22_HDLEN_0 | RF22_FIXPKLEN | RF22_SYNCLEN_4);
rf22.setSyncWords(sync_words, lengthof(sync_words));
/* Detect preamble after 4 nibbles */
rf22.spiWrite(RF22_REG_35_PREAMBLE_DETECTION_CONTROL1, (0x4 << 3));
/* Send 8 bytes of preamble */
rf22.setPreambleLength(8); // in nibbles
rf22.spiWrite(RF22_REG_3E_PACKET_LENGTH, 20);
}
void printUntil(uint8_t *buf) {
uint8_t year = buf[1] & 0x3f;
uint8_t month = ((buf[0] & 0xE0) >> 4) | (buf[1] >> 7);
uint8_t day = buf[0] & 0x1f;
/* In 30-minute increments */
uint8_t time = buf[2] & 0x3f;
Serial.print("Until: 20");
if (year < 10) Serial.print("0");
Serial.print(year);
Serial.print(".");
if (month < 10) Serial.print("0");
Serial.print(month);
Serial.print(".");
if (day < 10) Serial.print("0");
Serial.print(day);
Serial.print(" ");
if (time < 20) Serial.print("0");
Serial.print(time / 2);
if (time % 2)
Serial.print(":30");
else
Serial.print(":00");
Serial.println("");
}
void loop()
{
uint8_t buf[RF22_MAX_MESSAGE_LEN];
uint8_t len = sizeof(buf);
if (Serial.read() != -1)
Serial.println("OK");
if (rf22.recv(buf, &len))
{
Serial.print("Recv: ");
Serial.println(len, HEX);
if (len < 3 || len > lengthof(pn9)) {
Serial.print("Packet length too long (");
Serial.print(len);
Serial.println(")");
return;
}
/* Dewhiten data */
for (int i = 0; i < len; ++i)
buf[i] ^= pn9[i];
/* Calculate CRC (but don't include the CRC itself) */
uint16_t crc = calc_crc(buf, len - 2);
if (buf[len - 1] != (crc & 0xff) || buf[len - 2] != (crc >> 8)) {
Serial.println("CRC error");
return;
}
/* Don't use the CRC as data */
len -= 2;
uint8_t type = buf[3];
Serial.print("Packet from: ");
printHex(buf + 4, 3, true);
Serial.print("Packet to: ");
printHex(buf + 7, 3, true);
Serial.print("Packet type: ");
printHex(&type, 1, false);
Serial.print(" (");
Serial.print(type_str(type));
Serial.println(")");
Serial.print("Message count: ");
printHex(buf + 1, 1, true);
Serial.print("Flags: ");
printHex(buf + 2, 1, true);
if (type == 0x60 && len >= 13) { /* ThermostatState */
uint8_t mode = buf[11] & 0x3;
bool dst = (buf[11] >> 2) & 0x1;
bool locked = (buf[11] >> 5) & 0x1;
bool baterry_low = (buf[11] >> 7) & 0x1;
/* 0 - 64 */
uint8_t valve = buf[12];
uint8_t set_temp = buf[13];
Serial.print("Mode: ");
Serial.println(mode_str[mode]);
Serial.print("Valve pos: ");
Serial.print(100 * valve / 64, DEC);
Serial.println("%");
Serial.print("Set temp: ");
Serial.print(set_temp / 2);
Serial.println(set_temp % 2 ? ".5" : ".0");
if (len > 15 && mode != MODE_TEMPORARY) {
/* In tenths of degrees */
uint8_t actual_temp = ((buf[14] & 0x1) << 8) + buf[15];
Serial.print("Actual temp: ");
Serial.print(actual_temp / 10);
Serial.print(".");
Serial.println(actual_temp % 10);
}
if (len > 16 && mode == MODE_TEMPORARY) {
printUntil(buf + 14);
}
} else if (type == 0x40 && len >= 11) { /* SetTemperature */
uint8_t set_temp = buf[11] & 0x3f;
uint8_t mode = buf[11] >> 6;
Serial.print("Mode: ");
Serial.println(mode_str[mode]);
Serial.print("Set temp: ");
Serial.print(set_temp / 2);
Serial.println(set_temp % 2 ? ".5" : ".0");
if (len > 14) {
printUntil(buf + 12);
}
}
#if 1
// Print the data
int i, j;
for (i = 0; i < len; i += 16)
{
// Hex
for (j = 0; j < 16 && i+j < len; j++)
{
if (buf[i+j] < 16)
Serial.print("0"); // Sigh, Serial.print does not know how to pad hex
Serial.print(buf[i+j], HEX);
Serial.print(" ");
}
// Padding on last block
while (j++ < 16)
Serial.print(" ");
Serial.print(" ");
// ASCII
for (j = 0; j < 16 && i+j < len; j++)
Serial.write(isprint(buf[i+j]) ? buf[i+j] : '.');
Serial.println("");
}
Serial.println("");
#endif
}
}
/* vim: set sw=2 sts=2 expandtab filetype=cpp: */