Enums & Masks
The code was filled with “magic numbers” so it was time to get rid of them. In C# there are bit constants which I missed for my backport to .NetMF.
I sat down with the Semtech SX1276 datasheet and started typing in register names and adding constants and enums for all the bit masks, flags and defaults.
The initialisation of the RFM9X is now done in one of two constructors and an initialise method. Much like the approach used in the nRF24L01P libraries I use on Windows 10 IoT Core and .NetMF.
A few weeks ago I had a failed attempt at building a library which tried to hide as much of the hardware and wireless implementation details from the user as possible. Realistically if you’re building systems using LoRa, a basic understanding of the technology plus any regional regulatory requirements (frequency use, duty cycles etc.) is necessary.
sealed class Rfm9XDevice { // Registers from SemTech SX127X Datasheet enum Registers : byte { MinValue = RegOpMode, RegFifo = 0x0, RegOpMode = 0x01, //Reserved 0x02-0x06 RegFrMsb = 0x06, RegFrMid = 0x7, RegFrLsb = 0x08, RegPAConfig = 0x09, //RegPARamp = 0x0A, // not inlcuded as FSK/OOK functionality RegOcp = 0x0B, RegLna = 0x0C, RegFifoAddrPtr = 0x0D, //RegFifoTxBaseAddr = 0x0E RegFifoRxCurrent =0x10, RegIrqFlagsMask = 0x11, RegIrqFlags = 0x12, // RegRxNdBytes = 0x13 // RegRxHeaderCnValueMsb=0x14 // RegRxHeaderCnValueLsb=0x15 // RegRxPacketCntValueMsb=0x16 // RegRxPacketCntValueMsb=0x17 // RegModemStat=0x18 // RegPktSnrVale=0x19 // RegPktRssiValue=0x1A // RegRssiValue=0x1B // RegHopChannel=0x1C RegModemConfig1 = 0x1D, RegModemConfig2 = 0x1E, RegSymbTimeout = 0x1F, RegPreambleMsb = 0x20, RegPreambleLsb = 0x21, RegPayloadLength = 0x22, RegMaxPayloadLength = 0x23, RegHopPeriod = 0x24, // RegFifiRxByteAddr = 0x25 RegModemConfig3 = 0x26, RegPpmCorrection = 0x27, // RegFeiMsb = 0x28 // RegFeiMid = 0x29 // RegFeiLsb = 0x2A // Reserved 0x2B // RegRssiWideband = 0x2C // Reserved 0x2D-0x30 RegDetectOptimize = 0x31, // Reserved 0x32 RegInvertIQ = 0x33, // Reserved 0x34-0x36 RegDetectionThreshold = 0x37, // Reserved 0x38 RegSyncWord = 0x39, RegDioMapping1 = 0x40, RegVersion = 0x42, MaxValue = RegVersion, } // RegOpMode mode flags private const byte RegOpModeLongRangeModeLoRa = 0b10000000; private const byte RegOpModeLongRangeModeFskOok = 0b00000000; private const byte RegOpModeLongRangeModeDefault = RegOpModeLongRangeModeFskOok; private const byte RegOpModeAcessSharedRegLoRa = 0b00000000; private const byte RegOpModeAcessSharedRegFsk = 0b01000000; private const byte RegOpModeAcessSharedRegDefault = RegOpModeAcessSharedRegLoRa; private const byte RegOpModeLowFrequencyModeOnHighFrequency = 0b00000000; private const byte RegOpModeLowFrequencyModeOnLowFrequency = 0b00001000; private const byte RegOpModeLowFrequencyModeOnDefault = RegOpModeLowFrequencyModeOnLowFrequency; [Flags] public enum RegOpModeMode : byte { Sleep = 0b00000000, StandBy = 0b00000001, FrequencySynthesisTX = 0b00000010, Transmit = 0b00000011, FrequencySynthesisRX = 0b00000100, ReceiveContinuous = 0b00000101, ReceiveSingle = 0b00000110, ChannelActivityDetection = 0b00000111, }; // Frequency configuration magic numbers from Semtech SX127X specs private const double RH_RF95_FXOSC = 32000000.0; private const double RH_RF95_FSTEP = RH_RF95_FXOSC / 524288.0; // RegFrMsb, RegFrMid, RegFrLsb private const double FrequencyDefault = 434000000.0;
One constructor is for shields where the chip select pin is connected to one of the two standard lines CS0/CS1.
// Constructor for shields with chip select connected to CS0/CS1 e.g. Elecrow/Electronic tricks public Rfm9XDevice(ChipSelectPin chipSelectPin, int resetPinNumber, int interruptPinNumber) { RegisterManager = new RegisterManager(chipSelectPin); // Check that SX127X chip is present Byte regVersionValue = RegisterManager.ReadByte((byte)Registers.RegVersion); if (regVersionValue != RegVersionValueExpected) { throw new ApplicationException("Semtech SX127X not found"); } GpioController gpioController = GpioController.GetDefault();
The other is for shields with the chip select connected to another pin (the chip select has to be set to one of the default pins even though I am implementing the drive logic in code
// Constructor for shields with chip select not connected to CS0/CS1 (but needs to be configured anyway) e.g. Dragino public Rfm9XDevice(ChipSelectPin chipSelectPin, int chipSelectPinNumber, int resetPinNumber, int interruptPinNumber) { RegisterManager = new RegisterManager(chipSelectPin, chipSelectPinNumber); // Check that SX127X chip is present Byte regVersionValue = RegisterManager.ReadByte((byte)Registers.RegVersion); if (regVersionValue != RegVersionValueExpected) { throw new ApplicationException("Semtech SX127X not found"); } GpioController gpioController = GpioController.GetDefault();
The Initialise method has a large number of parameters (most of them can be ignored and defaults used). I only set registers if the configuration has been changed from the default value. This is fine for most settings, but some (like RegSymbTimeoutMsb & RegSymbTimeoutLsb span two registers and are combined with other settings.
public void Initialise(RegOpModeMode modeAfterInitialise, // RegOpMode double frequency = FrequencyDefault, // RegFrMsb, RegFrMid, RegFrLsb bool paBoost = false, byte maxPower = RegPAConfigMaxPowerDefault, byte outputPower = RegPAConfigOutputPowerDefault, // RegPaConfig bool ocpOn = true, byte ocpTrim = RegOcpOcpTrimDefault, // RegOcp RegLnaLnaGain lnaGain = LnaGainDefault, bool lnaBoostLF = false, bool lnaBoostHf = false, // RegLna RegModemConfigBandwidth bandwidth = RegModemConfigBandwidthDefault, RegModemConfigCodingRate codingRate = RegModemConfigCodingRateDefault, RegModemConfigImplicitHeaderModeOn implicitHeaderModeOn = RegModemConfigImplicitHeaderModeOnDefault, //RegModemConfig1 RegModemConfig2SpreadingFactor spreadingFactor = RegModemConfig2SpreadingFactorDefault, bool txContinuousMode = false, bool rxPayloadCrcOn = false, ushort symbolTimeout = SymbolTimeoutDefault, ushort preambleLength = PreambleLengthDefault, byte payloadLength = PayloadLengthDefault, byte payloadMaxLength = PayloadMaxLengthDefault, byte freqHoppingPeriod = FreqHoppingPeriodDefault, bool lowDataRateOptimize = false, bool agcAutoOn = false, byte ppmCorrection = ppmCorrectionDefault, RegDetectOptimizeDectionOptimize detectionOptimize=RegDetectOptimizeDectionOptimizeDefault, bool invertIQ = false, RegisterDetectionThreshold detectionThreshold = RegisterDetectionThresholdDefault, byte syncWord = RegSyncWordDefault ) { Frequency = frequency; // Store this away for RSSI adjustments RegOpModeModeCurrent = modeAfterInitialise; // Strobe Reset pin briefly to factory reset SX127X chip ResetGpioPin.Write(GpioPinValue.Low); Task.Delay(10); ResetGpioPin.Write(GpioPinValue.High); Task.Delay(10); // Put the device into sleep mode so registers can be changed SetMode(RegOpModeMode.Sleep); // Configure RF Carrier frequency if (frequency != FrequencyDefault) { byte[] bytes = BitConverter.GetBytes((long)(frequency / RH_RF95_FSTEP)); RegisterManager.WriteByte((byte)Registers.RegFrMsb, bytes[2]); RegisterManager.WriteByte((byte)Registers.RegFrMid, bytes[1]); RegisterManager.WriteByte((byte)Registers.RegFrLsb, bytes[0]); }
Next step is add event handlers for inbound and outbound messages, then the finally split the device specific code into a stand alone library.
Pingback: RFM69 hat library Part11 | devMobile's blog