/* * MiLightRadioPL1167_LT89000.cpp * * Created on: 31 March 2017 * Author: WoodsterDK * * Very inspired by: * https://github.com/pmoscetta/authometion-milight/tree/master/Authometion-MiLight * https://bitbucket.org/robvanderveer/lt8900lib */ #include "LT8900MiLightRadio.h" #include /**************************************************************************/ // Constructor /**************************************************************************/ LT8900MiLightRadio::LT8900MiLightRadio(byte byCSPin, byte byResetPin, byte byPktFlag, const MiLightRadioConfig& config) : _config(config), _channel(0) { _csPin = byCSPin; _pin_pktflag = byPktFlag; pinMode(_pin_pktflag, INPUT); if (byResetPin > 0) // If zero then bypass hardware reset { pinMode(byResetPin, OUTPUT); digitalWrite(byResetPin, LOW); delay(200); digitalWrite(byResetPin, HIGH); delay(200); } pinMode(_csPin, OUTPUT); digitalWrite(_csPin, HIGH); SPI.begin(); SPI.setDataMode(SPI_MODE1); // The following speed settings depends upon the wiring and PCB //SPI.setFrequency(8000000); SPI.setFrequency(4000000); SPI.setBitOrder(MSBFIRST); //Initialize transceiver with correct settings vInitRadioModule(config.type); delay(50); // Check if HW is connected _bConnected = bCheckRadioConnection(); //Reset SPI MODE to default SPI.setDataMode(SPI_MODE0); _waiting = false; } /**************************************************************************/ // Checks the connection to the radio module by verifying a register setting /**************************************************************************/ bool LT8900MiLightRadio::bCheckRadioConnection(void) { bool bRetValue = false; uint16_t value_0 = uiReadRegister(0); uint16_t value_1 = uiReadRegister(1); if ((value_0 == 0x6fe0) && (value_1 == 0x5681)) { #ifdef DEBUG_PRINTF Serial.println(F("Radio module running correctly...")); #endif bRetValue = true; } else { #ifdef DEBUG_PRINTF Serial.println(F("Failed initializing the radio module...")); #endif } return bRetValue; } /**************************************************************************/ // Initialize radio module /**************************************************************************/ void LT8900MiLightRadio::vInitRadioModule(MiLightRadioType type) { if (type == RGB_CCT) { bool bWriteDefaultDefault = true; // Is it okay to use the default power up values, without setting them regWrite16(0x00, 0x6F, 0xE0, 7); // Recommended value by PMmicro regWrite16(0x02, 0x66, 0x17, 7); // Recommended value by PMmicro regWrite16(0x04, 0x9C, 0xC9, 7); // Recommended value by PMmicro regWrite16(0x05, 0x66, 0x37, 7); // Recommended value by PMmicro regWrite16(0x07, 0x00, 0x4C, 7); // PL1167's TX/RX Enable and Channel Register, Default channel 76 regWrite16(0x08, 0x6C, 0x90, 7); // Recommended value by PMmicro regWrite16(0x09, 0x48, 0x00, 7); // PA Control register regWrite16(0x0B, 0x00, 0x08, 7); // Recommended value by PMmicro regWrite16(0x0D, 0x48, 0xBD, 7); // Recommended value by PMmicro regWrite16(0x16, 0x00, 0xFF, 7); // Recommended value by PMmicro regWrite16(0x18, 0x00, 0x67, 7); // Recommended value by PMmicro regWrite16(0x1A, 0x19, 0xE0, 7); // Recommended value by PMmicro regWrite16(0x1B, 0x13, 0x00, 7); // Recommended value by PMmicro regWrite16(0x20, 0x48, 0x00, 7); // Recommended value by PMmicro regWrite16(0x21, 0x3F, 0xC7, 7); // Recommended value by PMmicro regWrite16(0x22, 0x20, 0x00, 7); // Recommended value by PMmicro regWrite16(0x23, 0x03, 0x00, 7); // Recommended value by PMmicro regWrite16(0x24, 0x72, 0x36, 7); // Sync R0 regWrite16(0x27, 0x18, 0x09, 7); // Sync R3 regWrite16(0x28, 0x44, 0x02, 7); // Recommended value by PMmicro regWrite16(0x29, 0xB0, 0x00, 7); // Recommended value by PMmicro regWrite16(0x2A, 0xFD, 0xB0, 7); // Recommended value by PMmicro if (bWriteDefaultDefault == true) { regWrite16(0x01, 0x56, 0x81, 7); // Recommended value by PMmicro regWrite16(0x0A, 0x7F, 0xFD, 7); // Recommended value by PMmicro regWrite16(0x0C, 0x00, 0x00, 7); // Recommended value by PMmicro regWrite16(0x17, 0x80, 0x05, 7); // Recommended value by PMmicro regWrite16(0x19, 0x16, 0x59, 7); // Recommended value by PMmicro regWrite16(0x1C, 0x18, 0x00, 7); // Recommended value by PMmicro regWrite16(0x25, 0x00, 0x00, 7); // Recommended value by PMmicro regWrite16(0x26, 0x00, 0x00, 7); // Recommended value by PMmicro regWrite16(0x2B, 0x00, 0x0F, 7); // Recommended value by PMmicro } } else if((type == RGBW) || (type == CCT) || (type == RGB) ) { regWrite16(0, 111, 224, 7); // Recommended value by PMmicro regWrite16(1, 86, 129, 7); // Recommended value by PMmicro regWrite16(2, 102, 23, 7); // Recommended value by PMmicro regWrite16(4, 156, 201, 7); // Recommended value by PMmicro regWrite16(5, 102, 55, 7); // Recommended value by PMmicro regWrite16(7, 0, 76, 7); // PL1167's TX/RX Enable and Channel Register regWrite16(8, 108, 144, 7); // Recommended value by PMmicro regWrite16(9, 72, 0, 7); // PL1167's PA Control Register regWrite16(10, 127, 253, 7); // Recommended value by PMmicro regWrite16(11, 0, 8, 7); // PL1167's RSSI OFF Control Register -- ??? regWrite16(12, 0, 0, 7); // Recommended value by PMmicro regWrite16(13, 72, 189, 7); // Recommended value by PMmicro regWrite16(22, 0, 255, 7); // Recommended value by PMmicro regWrite16(23, 128, 5, 7); // PL1167's VCO Calibration Enable Register regWrite16(24, 0, 103, 7); // Recommended value by PMmicro regWrite16(25, 22, 89, 7); // Recommended value by PMmicro regWrite16(26, 25, 224, 7); // Recommended value by PMmicro regWrite16(27, 19, 0, 7); // Recommended value by PMmicro regWrite16(28, 24, 0, 7); // Recommended value by PMmicro regWrite16(32, 72, 0, 7); // PL1167's Data Configure Register: LEN_PREAMBLE = 010 -> (0xAAAAAA) 3 bytes, LEN_SYNCWORD = 01 -> 32 bits, LEN_TRAILER = 000 -> (0x05) 4 bits, TYPE_PKT_DAT = 00 -> NRZ law data, TYPE_FEC = 00 -> No FEC regWrite16(33, 63, 199, 7); // PL1167's Delay Time Control Register 0 regWrite16(34, 32, 0, 7); // PL1167's Delay Time Control Register 1 regWrite16(35, 3, 0, 7); // PL1167's Power Management and Miscellaneous Register regWrite16(40, 68, 2, 7); // PL1167's FIFO and SYNCWORD Threshold Register regWrite16(41, 176, 0, 7); // PL1167's Miscellaneous Register: CRC_ON = 1 -> ON, SCR_ON = 0 -> OFF, EN_PACK_LEN = 1 -> ON, FW_TERM_TX = 1 -> ON, AUTO_ACK = 0 -> OFF, PKT_LEVEL = 0 -> PKT active high, CRC_INIT_DAT = 0 regWrite16(42, 253, 176, 7); // PL1167's SCAN RSSI Register 0 regWrite16(43, 0, 15, 7); // PL1167's SCAN RSSI Register 1 delay(200); regWrite16(128, 0, 0, 7); regWrite16(129, 255, 255, 7); regWrite16(130, 0, 0, 7); regWrite16(132, 0, 0, 7); regWrite16(133, 255, 255, 7); regWrite16(135, 255, 255, 7); regWrite16(136, 0, 0, 7); regWrite16(137, 255, 255, 7); regWrite16(138, 0, 0, 7); regWrite16(139, 255, 255, 7); regWrite16(140, 0, 0, 7); regWrite16(141, 255, 255, 7); regWrite16(150, 0, 0, 7); regWrite16(151, 255, 255, 7); regWrite16(152, 0, 0, 7); regWrite16(153, 255, 255, 7); regWrite16(154, 0, 0, 7); regWrite16(155, 255, 255, 7); regWrite16(156, 0, 0, 7); regWrite16(160, 0, 0, 7); regWrite16(161, 255, 255, 7); regWrite16(162, 0, 0, 7); regWrite16(163, 255, 255, 7); regWrite16(168, 0, 0, 7); regWrite16(169, 255, 255, 7); regWrite16(170, 0, 0, 7); regWrite16(171, 255, 255, 7); regWrite16(7, 0, 0, 7); // Disable TX/RX and set radio channel to 0 } } /**************************************************************************/ // Set sync word /**************************************************************************/ void LT8900MiLightRadio::vSetSyncWord(uint16_t syncWord3, uint16_t syncWord2, uint16_t syncWord1, uint16_t syncWord0) { uiWriteRegister(R_SYNCWORD1, syncWord0); uiWriteRegister(R_SYNCWORD2, syncWord1); uiWriteRegister(R_SYNCWORD3, syncWord1); uiWriteRegister(R_SYNCWORD4, syncWord3); } /**************************************************************************/ // Low level register write with delay /**************************************************************************/ void LT8900MiLightRadio::regWrite16(byte ADDR, byte V1, byte V2, byte WAIT) { digitalWrite(_csPin, LOW); SPI.transfer(ADDR); SPI.transfer(V1); SPI.transfer(V2); digitalWrite(_csPin, HIGH); delayMicroseconds(WAIT); } /**************************************************************************/ // Low level register read /**************************************************************************/ uint16_t LT8900MiLightRadio::uiReadRegister(uint8_t reg) { SPI.setDataMode(SPI_MODE1); digitalWrite(_csPin, LOW); SPI.transfer(REGISTER_READ | (REGISTER_MASK & reg)); uint8_t high = SPI.transfer(0x00); uint8_t low = SPI.transfer(0x00); digitalWrite(_csPin, HIGH); SPI.setDataMode(SPI_MODE0); return (high << 8 | low); } /**************************************************************************/ // Low level 16bit register write /**************************************************************************/ uint8_t LT8900MiLightRadio::uiWriteRegister(uint8_t reg, uint16_t data) { uint8_t high = data >> 8; uint8_t low = data & 0xFF; digitalWrite(_csPin, LOW); uint8_t result = SPI.transfer(REGISTER_WRITE | (REGISTER_MASK & reg)); SPI.transfer(high); SPI.transfer(low); digitalWrite(_csPin, HIGH); return result; } /**************************************************************************/ // Start listening on specified channel and syncword /**************************************************************************/ void LT8900MiLightRadio::vStartListening(uint uiChannelToListenTo) { _dupes_received = 0; vSetSyncWord(_config.syncword3, 0,0,_config.syncword0); //vSetChannel(uiChannelToListenTo); _channel = uiChannelToListenTo; uiWriteRegister(R_CHANNEL, _channel & CHANNEL_MASK); //turn off rx/tx delay(3); uiWriteRegister(R_FIFO_CONTROL, 0x0080); //flush rx uiWriteRegister(R_CHANNEL, (_channel & CHANNEL_MASK) | _BV(CHANNEL_RX_BIT)); //enable RX delay(5); } /**************************************************************************/ // Resume listening - without changing the channel and syncword /**************************************************************************/ void LT8900MiLightRadio::vResumeRX(void) { _dupes_received = 0; uiWriteRegister(R_CHANNEL, _channel & CHANNEL_MASK); //turn off rx/tx delay(3); uiWriteRegister(R_FIFO_CONTROL, 0x0080); //flush rx uiWriteRegister(R_CHANNEL, (_channel & CHANNEL_MASK) | _BV(CHANNEL_RX_BIT)); //enable RX } /**************************************************************************/ // Check if data is available using the hardware pin PKT_FLAG /**************************************************************************/ bool LT8900MiLightRadio::bAvailablePin() { //read the PKT_FLAG pin. if (digitalRead(_pin_pktflag) != 0) { return true; } return false; } /**************************************************************************/ // Check if data is available using the PKT_FLAG state in the status register /**************************************************************************/ bool LT8900MiLightRadio::bAvailableRegister() { //read the PKT_FLAG state; this can also be done with a hard wire. uint16_t value = uiReadRegister(R_STATUS); if (value & STATUS_PKT_BIT_MASK != 0) { return true; } return false; } /**************************************************************************/ // Read the RX buffer /**************************************************************************/ int LT8900MiLightRadio::iReadRXBuffer(uint8_t *buffer, size_t maxBuffer) { uint16_t value = uiReadRegister(R_STATUS); if (bitRead(value, STATUS_CRC_BIT) == 0) { uint16_t data = uiReadRegister(R_FIFO); uint8_t packetSize = data >> 8; if (maxBuffer < packetSize + 1) { //BUFFER TOO SMALL return -2; } uint8_t pos = 0;; buffer[pos++] = (data & 0xFF); while (pos < packetSize) { data = uiReadRegister(R_FIFO); buffer[pos++] = data >> 8; buffer[pos++] = data & 0xFF; } return packetSize; } else { //CRC error return -1; } } /**************************************************************************/ // Set the active channel for the radio module /**************************************************************************/ void LT8900MiLightRadio::vSetChannel(uint8_t channel) { _channel = channel; uiWriteRegister(R_CHANNEL, (_channel & CHANNEL_MASK)); } /**************************************************************************/ // Startup /**************************************************************************/ int LT8900MiLightRadio::begin() { vSetChannel(_config.channels[0]); configure(); available(); return 0; } /**************************************************************************/ // Configure the module according to type, and start listening /**************************************************************************/ int LT8900MiLightRadio::configure() { vInitRadioModule(_config.type); vSetSyncWord(_config.syncword3, 0,0,_config.syncword0); vStartListening(_config.channels[0]); return 0; } /**************************************************************************/ // Check if data is available /**************************************************************************/ bool LT8900MiLightRadio::available() { _waiting = false; if(bAvailablePin()) { int iStartTime = millis(); int iUpdateStamp = 0; static byte byLastReceived[32]; byte byaFramesSizes[20]; byte byaFramesReceivedCount[20]; byte byaFramesReceived[20][32]; byte byFrameCounter = 0; bool bDifference = false; bool bRetVal = false; if (bAvailablePin()) { unsigned long ulTimeStamp = millis(); unsigned long ulElapsedTime = 0; do { if (bAvailablePin()) { iUpdateStamp = millis(); uint8_t buf[32]; int packetSize = iReadRXBuffer(buf, 32); if (packetSize > 0) { bRetVal = true; bDifference = false; for (int iCounter = 0; iCounter < packetSize && bDifference == false; iCounter++) { if (buf[iCounter] != byLastReceived[iCounter]) { bDifference = true; } } if (bDifference == true) { for (int i = 0; i < packetSize; i++) { byaFramesReceived[byFrameCounter][i] = buf[i]; byLastReceived[i] = buf[i]; byaFramesSizes[i] = packetSize; } byaFramesReceivedCount[byFrameCounter] = 1; byFrameCounter++; } else { byaFramesReceivedCount[byFrameCounter-1]++; } } else { if (packetSize < 0) { Serial.println(F("LT8900: Packet less than zero, buffer to small")); } else { Serial.println(F("LT8900: Packet read fail")); } } vResumeRX(); } ulElapsedTime = millis(); yield(); ulElapsedTime = ulElapsedTime - ulTimeStamp; }while (ulElapsedTime < 1000 ); #ifdef DEBUG_PRINTF printf_P(PSTR("ElapsedRX: %d\n"), iUpdateStamp - iStartTime); #endif if (byFrameCounter != 0) { #ifdef DEBUG_PRINTF printf_P(PSTR("Packets received: %d\n"), byFrameCounter); #endif for (byte byCounterFrame = 0; byCounterFrame < byFrameCounter; byCounterFrame++) { _dupes_received = byaFramesReceivedCount[byCounterFrame]; #ifdef DEBUG_PRINTF printf_P(PSTR("Packet read OK, rec: %d Frame:\n"), byaFramesReceivedCount[byCounterFrame]); #endif //dump the packet. _packet[0]=byaFramesSizes[byCounterFrame]; for (int iByteCounter = 0; iByteCounter < byaFramesSizes[byCounterFrame]; iByteCounter++) { byte byReceivedvalue = byaFramesReceived[byCounterFrame][iByteCounter]; _packet[iByteCounter+1] = byReceivedvalue; #ifdef DEBUG_PRINTF printf_P(PSTR("%02X "), byReceivedvalue); #endif } _waiting = true; } #ifdef DEBUG_PRINTF Serial.println(); #endif } } yield(); } return _waiting; } int LT8900MiLightRadio::dupesReceived() { return _dupes_received; } /**************************************************************************/ // Read received data from buffer to upper layer /**************************************************************************/ int LT8900MiLightRadio::read(uint8_t frame[], size_t &frame_length) { if (!_waiting) { frame_length = 0; return -1; } if (frame_length > sizeof(_packet) - 1) { frame_length = sizeof(_packet) - 1; } if (frame_length > _packet[0]) { frame_length = _packet[0]; } memcpy(frame, _packet +1 , frame_length); _waiting = false; // Start RX mode again vResumeRX(); return _packet[0]; } /**************************************************************************/ // Write data /**************************************************************************/ int LT8900MiLightRadio::write(uint8_t frame[], size_t frame_length) { if (frame_length > sizeof(_out_packet) - 1) { return -1; } memcpy(_out_packet + 1, frame, frame_length); _out_packet[0] = frame_length; SPI.setDataMode(SPI_MODE1); int retval = resend(); yield(); SPI.setDataMode(SPI_MODE0); if (retval < 0) { return retval; } return frame_length; } /**************************************************************************/ // Handle the transmission to regarding to freq diversity and repeats /**************************************************************************/ int LT8900MiLightRadio::resend() { byte Length = _out_packet[0]; for (size_t i = 0; i < MiLightRadioConfig::NUM_CHANNELS; i++) { sendPacket(_out_packet, Length, _config.channels[i]); delayMicroseconds(DEFAULT_TIME_BETWEEN_RETRANSMISSIONS_uS); } return 0; } /**************************************************************************/ // The actual transmit happens here /**************************************************************************/ bool LT8900MiLightRadio::sendPacket(uint8_t *data, size_t packetSize, byte byChannel) { if(_bConnected) // Must be connected to module otherwise it might lookup waiting for _pin_pktflag { if (packetSize < 1 || packetSize > 255) { return false; } uiWriteRegister(R_CHANNEL, 0x0000); uiWriteRegister(R_FIFO_CONTROL, 0x8080); //flush tx and RX digitalWrite(_csPin, LOW); // Enable PL1167 SPI transmission SPI.transfer(R_FIFO); // Start writing PL1167's FIFO Data register SPI.transfer(packetSize); // Length of data buffer: x bytes for (byte iCounter = 0; iCounter < packetSize; iCounter++) { SPI.transfer((data[1+iCounter])); } digitalWrite(_csPin, HIGH); // Disable PL1167 SPI transmission delayMicroseconds(10); uiWriteRegister(R_CHANNEL, (byChannel & CHANNEL_MASK) | _BV(CHANNEL_TX_BIT)); //enable RX //Wait until the packet is sent. while (digitalRead(_pin_pktflag) == 0) { //do nothing. } return true; } } const MiLightRadioConfig& LT8900MiLightRadio::config() { return _config; }