From d44c802b7cd5a89fee9cdf294acb999d30dd2507 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C5=82awek=20Piotrowski?= Date: Thu, 4 Sep 2014 00:32:14 +0200 Subject: [PATCH] RFM12 decoder --- decoders/rfm12/__init__.py | 26 ++ decoders/rfm12/pd.py | 498 +++++++++++++++++++++++++++++++++++++ 2 files changed, 524 insertions(+) create mode 100644 decoders/rfm12/__init__.py create mode 100644 decoders/rfm12/pd.py diff --git a/decoders/rfm12/__init__.py b/decoders/rfm12/__init__.py new file mode 100644 index 0000000..a4748b6 --- /dev/null +++ b/decoders/rfm12/__init__.py @@ -0,0 +1,26 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Sławek Piotrowski +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +''' +This decoder stacks on top of the 'spi' PD and decodes the RFM12 +control protocol. +''' + +from .pd import * diff --git a/decoders/rfm12/pd.py b/decoders/rfm12/pd.py new file mode 100644 index 0000000..c4b5475 --- /dev/null +++ b/decoders/rfm12/pd.py @@ -0,0 +1,498 @@ +## +## This file is part of the libsigrokdecode project. +## +## Copyright (C) 2014 Sławek Piotrowski +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with this program; if not, write to the Free Software +## Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +## + +import traceback +import sigrokdecode as srd + +class Decoder(srd.Decoder): + api_version = 2 + id = 'rfm12' + name = 'RFM12' + longname = 'RFM12 control protocol' + desc = 'Protocol for controlling RFM12 wireless transceivers from HoperRF' + license = 'gplv2+' + inputs = ['spi'] + outputs = ['rfm12'] + annotations = ( + ('cmd', 'Command'), + ('params', 'Command parameters'), + ('disabled', 'Disabled bits'), + ('return', 'Returned values'), + ('disabled_return', 'Disabled returned values'), + ('interpretation', 'Interpretation'), + ) + annotation_rows = ( + ('commands', 'Commands', (0, 1, 2)), + ('return', 'Return', (3, 4)), + ('interpretation', 'Interpretation', (5,)), + ) + + def __init__(self, **kwargs): + self.mosi_bytes, self.miso_bytes = [], [] + self.mosi_bits, self.miso_bits = [], [] + self.row_pos = [0, 0, 0] + + self.ann_to_row = [0, 0, 0, 1, 1, 2] + + # Initialize with Power-On-Reset values. + self.last_status = [0x00, 0x00] + self.last_config = 0x08 + self.last_power = 0x08 + self.last_freq = 0x680 + self.last_data_rate = 0x23 + self.last_fifo_and_reset = 0x80 + self.last_afc = 0xF7 + self.last_transceiver = 0x00 + self.last_pll = 0x77 + + def advance_ann(self, ann, length): + row = self.ann_to_row[ann] + self.row_pos[row] += length + + def putx(self, ann, length, description): + if not isinstance(description, list): + description = [description] + row = self.ann_to_row[ann] + bit = self.row_pos[row] + self.put(self.mosi_bits[bit][1], self.mosi_bits[bit + length - 1][2], + self.out_ann, [ann, description]) + bit += length + self.row_pos[row] = bit + + def describe_bits(self, data, names): + i = 0x01 << len(names) - 1 + bit = 0 + while i != 0: + if names[bit] != '': + self.putx(1 if (data & i) else 2, 1, names[bit]) + i >>= 1 + bit += 1 + + def describe_return_bits(self, data, names): + i = 0x01 << len(names) - 1 + bit = 0 + while i != 0: + if names[bit] != '': + self.putx(3 if (data & i) else 4, 1, names[bit]) + else: + self.advance_ann(3, 1) + i >>= 1 + bit += 1 + + def describe_changed_bits(self, data, old_data, names): + changes = data ^ old_data + i = 0x01 << (len(names) - 1) + bit = 0 + while i != 0: + if names[bit] != '' and changes & i: + s = ['+', 'Turning on'] if (data & i) else ['-', 'Turning off'] + self.putx(5, 1, s) + else: + self.advance_ann(5, 1) + i >>= 1 + bit += 1 + + def start(self): + self.out_ann = self.register(srd.OUTPUT_ANN) + + def handle_configuration_cmd(self, cmd, ret): + self.putx(0, 8, ['Configuration command', 'Configuration']) + NAMES = [['Internal data register', 'el'], ['FIFO mode', 'ef']] + + bits = (cmd[1] & 0xC0) >> 6 + old_bits = (self.last_config & 0xC0) >> 6 + self.describe_bits(bits, NAMES) + self.describe_changed_bits(bits, old_bits, NAMES) + + FREQUENCIES = ['Reserved', '433', '868', '912'] + f = FREQUENCIES[(cmd[1] & 0x30) >> 4] + 'MHz' + self.putx(1, 2, ['Frequency: ' + f, f]) + if cmd[1] & 0x30 != self.last_config & 0x30: + self.putx(5, 2, ['Changed', '~']) + + c = '%.1fpF' % (8.5 + (cmd[1] & 0xF) * 0.5) + self.putx(1, 4, ['Capacitance: ' + c, c]) + if cmd[1] & 0xF != self.last_config & 0xF: + self.putx(5, 4, ['Changed', '~']) + + self.last_config = cmd[1] + + def handle_power_management_cmd(self, cmd, ret): + self.putx(0, 8, ['Power management', 'Power']) + NAMES = [['Receiver chain', 'er'], ['Baseband circuit', 'ebb'], + ['Transmission', 'et'], ['Synthesizer', 'es'], + ['Crystal oscillator', 'ex'], ['Low battery detector', 'eb'], + ['Wake-up timer', 'ew'], ['Clock output off switch', 'dc']] + + self.describe_bits(cmd[1], NAMES) + + power = cmd[1] + + # Some bits imply other, even if they are set to 0. + if power & 0x80: + power |= 0x58 + if power & 0x20: + power |= 0x18 + self.describe_changed_bits(power, self.last_power, NAMES) + + self.last_power = power + + def handle_frequency_setting_cmd(self, cmd, ret): + self.putx(0, 4, ['Frequency setting', 'Frequency']) + f = ((cmd[1] & 0xF) << 8) + cmd[2] + self.putx(0, 12, ['F = %3.4f' % f]) + self.row_pos[2] -= 4 + if self.last_freq != f: + self.putx(5, 12, ['Changing', '~']) + self.last_freq = f + + def handle_data_rate_cmd(self, cmd, ret): + self.putx(0, 8, ['Data rate command', 'Data rate']) + r = cmd[1] & 0x7F + cs = (cmd[1] & 0x80) >> 7 + rate = 10000 / 29.0 / (r + 1) / (1 + 7 * cs) + self.putx(0, 8, ['%3.1fkbps' % rate]) + if self.last_data_rate != cmd[1]: + self.putx(5, 8, ['Changing', '~']) + self.last_data_rate = cmd[1] + + def handle_receiver_control_cmd(self, cmd, ret): + self.putx(0, 5, ['Receiver control command']) + s = 'interrupt input' if (cmd[0] & 0x04) else 'VDI output' + self.putx(0, 1, ['pin16 = ' + s]) + VDI_NAMES = ['Fast', 'Medium', 'Slow', 'Always on'] + vdi_speed = VDI_NAMES[cmd[0] & 0x3] + self.putx(0, 2, ['VDI: %s' % vdi_speed]) + BANDWIDTH_NAMES = ['Reserved', '400kHz', '340kHz', '270kHz', '200kHz', + '134kHz', '67kHz', 'Reserved'] + bandwidth = BANDWIDTH_NAMES[(cmd[1] & 0xE0) >> 5] + self.putx(0, 3, ['Bandwidth: %s' % bandwidth]) + LNA_GAIN_NAMES = [0, -6, -14, -20] + lna_gain = LNA_GAIN_NAMES[(cmd[1] & 0x18) >> 3] + self.putx(0, 2, ['LNA gain: %ddB' % lna_gain]) + RSSI_THRESHOLD_NAMES = ['-103', '-97', '-91', '-85', '-79', '-73', + 'Reserved', 'Reserved'] + rssi_threshold = RSSI_THRESHOLD_NAMES[cmd[1] & 0x7] + self.putx(0, 3, ['RSSI threshold: %s' % rssi_threshold]) + + def handle_data_filter_cmd(self, cmd, ret): + self.putx(0, 8, ['Data filter command']) + if cmd[1] & 0x80: + clock_recovery = 'auto' + elif cmd[1] & 0x40: + clock_recovery = 'fast' + else: + clock_recovery = 'slow' + self.putx(0, 2, ['Clock recovery: %s mode' % clock_recovery]) + self.advance_ann(0, 1) # Should always be 1. + s = 'analog' if (cmd[1] & 0x10) else 'digital' + self.putx(0, 1, ['Data filter: ' + s]) + self.advance_ann(0, 1) # Should always be 1. + self.putx(0, 3, ['DQD threshold: %d' % (cmd[1] & 0x7)]) + + def handle_fifo_and_reset_cmd(self, cmd, ret): + self.putx(0, 8, ['FIFO and reset command']) + fifo_level = (cmd[1] & 0xF0) >> 4 + self.putx(0, 4, ['FIFO trigger level: %d' % fifo_level]) + last_fifo_level = (self.last_fifo_and_reset & 0xF0) >> 4 + if fifo_level != last_fifo_level: + self.putx(5, 4, ['Changing', '~']) + else: + self.advance_ann(5, 4) + s = 'one byte' if (cmd[1] & 0x08) else 'two bytes' + self.putx(0, 1, ['Synchron length: ' + s]) + if (cmd[1] & 0x08) != (self.last_fifo_and_reset & 0x08): + self.putx(5, 1, ['Changing', '~']) + else: + self.advance_ann(5, 1) + + if cmd[1] & 0x04: + fifo_fill = 'Always' + elif cmd[1] & 0x02: + fifo_fill = 'After synchron pattern' + else: + fifo_fill = 'Never' + self.putx(0, 2, ['FIFO fill: %s' % fifo_fill]) + if (cmd[1] & 0x06) != (self.last_fifo_and_reset & 0x06): + self.putx(5, 2, ['Changing', '~']) + else: + self.advance_ann(5, 2) + + s = 'non-sensitive' if (cmd[1] & 0x01) else 'sensitive' + self.putx(0, 1, ['Reset mode: ' + s]) + if (cmd[1] & 0x01) != (self.last_fifo_and_reset & 0x01): + self.putx(5, 1, ['Changing', '~']) + else: + self.advance_ann(5, 1) + + self.last_fifo_and_reset = cmd[1] + + def handle_synchron_pattern_cmd(self, cmd, ret): + self.putx(0, 8, ['Synchron pattern command']) + if self.last_fifo_and_reset & 0x08: + self.putx(0, 8, ['Pattern: 0x2D%02X' % pattern]) + else: + self.putx(0, 8, ['Pattern: %02X' % pattern]) + + def handle_fifo_read_cmd(self, cmd, ret): + self.putx(0, 8, ['FIFO read command', 'FIFO read']) + self.putx(3, 8, ['Data: %02X' % ret[1]]) + + def handle_afc_cmd(self, cmd, ret): + self.putx(0, 8, ['AFC command']) + MODES = ['Off', 'Once', 'During receiving', 'Always'] + mode = (cmd[1] & 0xC0) >> 6 + self.putx(0, 2, ['Mode: %s' % MODES[mode]]) + if (cmd[1] & 0xC0) != (self.last_afc & 0xC0): + self.putx(5, 2, ['Changing', '~']) + else: + self.advance_ann(5, 2) + + range = (cmd[1] & 0x30) >> 4 + FREQ_TABLE = [0.0, 2.5, 5.0, 7.5] + freq_delta = FREQ_TABLE[(self.last_config & 0x30) >> 4] + + if range == 0: + self.putx(0, 2, ['Range: No limit']) + elif range == 1: + self.putx(0, 2, ['Range: +/-%dkHz' % (15 * freq_delta)]) + elif range == 2: + self.putx(0, 2, ['Range: +/-%dkHz' % (7 * freq_delta)]) + elif range == 3: + self.putx(0, 2, ['Range: +/-%dkHz' % (3 * freq_delta)]) + + if (cmd[1] & 0x30) != (self.last_afc & 0x30): + self.putx(5, 2, ['Changing', '~']) + else: + self.advance_ann(5, 2) + + NAMES = ['Strobe edge', 'High accuracy mode', 'Enable offset register', + 'Enable offset calculation'] + self.describe_bits(cmd[1] & 0xF, NAMES) + self.describe_changed_bits(cmd[1] & 0xF, self.last_afc & 0xF, NAMES) + + self.last_afc = cmd[1] + + def handle_transceiver_control_cmd(self, cmd, ret): + self.putx(0, 8, ['Transceiver control command']) + self.putx(0, 4, ['FSK frequency delta: %dkHz' % (15 * ((cmd[1] & 0xF0) >> 4))]) + if cmd[1] & 0xF0 != self.last_transceiver & 0xF0: + self.putx(5, 4, ['Changing', '~']) + else: + self.advance_ann(5, 4) + + POWERS = [0, -2.5, -5, -7.5, -10, -12.5, -15, -17.5] + self.advance_ann(0, 1) + self.advance_ann(5, 1) + self.putx(0,3, ['Relative power: %dB' % (cmd[1] & 0x07)]) + if (cmd[1] & 0x07) != (self.last_transceiver & 0x07): + self.putx(5, 3, ['Changing', '~']) + else: + self.advance_ann(5, 3) + self.last_transceiver = cmd[1] + + def handle_pll_setting_cmd(self, cmd, ret): + self.putx(0, 8, ['PLL setting command']) + self.advance_ann(0, 1) + self.putx(0, 2, ['Clock buffer rise and fall time']) + self.advance_ann(0, 1) + self.advance_ann(5, 4) + NAMES = [['Delay in phase detector', 'dly'], ['Disable dithering', 'ddit']] + self.describe_bits((cmd[1] & 0xC) >> 2, NAMES) + self.describe_changed_bits((cmd[1] & 0xC) >> 2, (self.last_pll & 0xC) >> 2, NAMES) + s = '256kbps, high' if (cmd[1] & 0x01) else '86.2kbps, low' + self.putx(0, 1, ['Max bit rate: %s noise' % s]) + + self.advance_ann(5, 1) + if (cmd[1] & 0x01) != (self.last_pll & 0x01): + self.putx(5, 1, ['Changing', '~']) + + self.last_pll = cmd[1] + + def handle_transmitter_register_cmd(self, cmd, ret): + self.putx(0, 8, ['Transmitter register command', 'Transmit']) + self.putx(0, 8, ['Data: %s' % cmd[1], '%s' % cmd[1]]) + + def handle_software_reset_cmd(self, cmd, ret): + self.putx(0, 16, ['Software reset command']) + + def handle_wake_up_timer_cmd(self, cmd, ret): + self.putx(0, 3, ['Wake-up timer command', 'Timer']) + r = cmd[0] & 0x1F + m = cmd[1] + time = 1.03 * m * pow(2, r) + 0.5 + self.putx(0, 13, ['Time: %7.2f' % time]) + + def handle_low_duty_cycle_cmd(self, cmd, ret): + self.putx(0, 16, ['Low duty cycle command']) + + def handle_low_battery_detector_cmd(self, cmd, ret): + self.putx(0, 8, ['Low battery detector command']) + NAMES = ['1', '1.25', '1.66', '2', '2.5', '3.33', '5', '10'] + clock = NAMES[(cmd[1] & 0xE0) >> 5] + self.putx(0, 3, ['Clock output: %sMHz' % clock, '%sMHz' % clock]) + self.advance_ann(0, 1) + v = 2.25 + (cmd[1] & 0x0F) * 0.1 + self.putx(0, 4, ['Low battery voltage: %1.2fV' % v, '%1.2fV' % v]) + + def handle_status_read_cmd(self, cmd, ret): + self.putx(0, 8, ['Status read command', 'Status']) + NAMES = ['RGIT/FFIT', 'POR', 'RGUR/FFOV', 'WKUP', 'EXT', 'LBD', + 'FFEM', 'RSSI/ATS', 'DQD', 'CRL', 'ATGL'] + status = (ret[0] << 3) + (ret[1] >> 5) + self.row_pos[1] -= 8 + self.row_pos[2] -= 8 + self.describe_return_bits(status, NAMES) + receiver_enabled = (self.last_power & 0x80) >> 7 + + if ret[0] & 0x80: + if receiver_enabled: + s = 'Received data in FIFO' + else: + s = 'Transmit register ready' + self.putx(5, 1, s) + else: + self.advance_ann(5, 1) + if ret[0] & 0x40: + self.putx(5, 1, 'Power on Reset') + else: + self.advance_ann(5, 1) + if ret[0] & 0x20: + if receiver_enabled: + s = 'RX FIFO overflow' + else: + s = 'Transmit register under run' + self.putx(5, 1, s) + else: + self.advance_ann(5, 1) + if ret[0] & 0x10: + self.putx(5, 1, 'Wake-up timer') + else: + self.advance_ann(5, 1) + if ret[0] & 0x08: + self.putx(5, 1, 'External interrupt') + else: + self.advance_ann(5, 1) + if ret[0] & 0x04: + self.putx(5, 1, 'Low battery') + else: + self.advance_ann(5, 1) + if ret[0] & 0x02: + self.putx(5, 1, 'FIFO is empty') + else: + self.advance_ann(5, 1) + if ret[0] & 0x01: + if receiver_enabled: + s = 'Incoming signal above limit' + else: + s = 'Antenna detected RF signal' + self.putx(5, 1, s) + else: + self.advance_ann(5, 1) + if ret[1] & 0x80: + self.putx(5, 1, 'Data quality detector') + else: + self.advance_ann(5, 1) + if ret[1] & 0x40: + self.putx(5, 1, 'Clock recovery locked') + else: + self.advance_ann(5, 1) + self.advance_ann(5, 1) + + self.putx(3, 5, ['AFC offset']) + if (self.last_status[1] & 0x1F) != (ret[1] & 0x1F): + self.putx(5, 5, ['Changed', '~']) + self.last_status = ret + + def handle_cmd(self, cmd, ret): + if cmd[0] == 0x80: + self.handle_configuration_cmd(cmd, ret) + elif cmd[0] == 0x82: + self.handle_power_management_cmd(cmd, ret) + elif cmd[0] & 0xF0 == 0xA0: + self.handle_frequency_setting_cmd(cmd, ret) + elif cmd[0] == 0xC6: + self.handle_data_rate_cmd(cmd, ret) + elif cmd[0] & 0xF8 == 0x90: + self.handle_receiver_control_cmd(cmd, ret) + elif cmd[0] == 0xC2: + self.handle_data_filter_cmd(cmd, ret) + elif cmd[0] == 0xCA: + self.handle_fifo_and_reset_cmd(cmd, ret) + elif cmd[0] == 0xCE: + self.handle_synchron_pattern_cmd(cmd, ret) + elif cmd[0] == 0xB0: + self.handle_fifo_read_cmd(cmd, ret) + elif cmd[0] == 0xC4: + self.handle_afc_cmd(cmd, ret) + elif cmd[0] & 0xFE == 0x98: + self.handle_transceiver_control_cmd(cmd, ret) + elif cmd[0] == 0xCC: + self.handle_pll_setting_cmd(cmd, ret) + elif cmd[0] == 0xB8: + self.handle_transmitter_register_cmd(cmd, ret) + elif cmd[0] == 0xFE: + self.handle_software_reset_cmd(cmd, ret) + elif cmd[0] & 0xE0 == 0xE0: + self.handle_wake_up_timer_cmd(cmd, ret) + elif cmd[0] == 0xC8: + self.handle_low_duty_cycle_cmd(cmd, ret) + elif cmd[0] == 0xC0: + self.handle_low_battery_detector_cmd(cmd, ret) + elif cmd[0] == 0x00: + self.handle_status_read_cmd(cmd, ret) + else: + c = '%02x %02x' % tuple(cmd) + r = '%02x %02x' % tuple(ret) + self.putx(0, 16, ['Uknown command: %s (reply: %s)!' % (c, r)]) + + def decode(self, ss, es, data): + ptype, mosi, miso = data + + # For now, only use DATA and BITS packets. + if ptype not in ('DATA', 'BITS'): + return + + # Store the individual bit values and ss/es numbers. The next packet + # is guaranteed to be a 'DATA' packet belonging to this 'BITS' one. + if ptype == 'BITS': + if mosi is not None: + self.mosi_bits.extend(reversed(mosi)) + if miso is not None: + self.miso_bits.extend(reversed(miso)) + return + + # Append new bytes. + self.mosi_bytes.append(mosi) + self.miso_bytes.append(miso) + + # All commands consist of 2 bytes. + if len(self.mosi_bytes) < 2: + return + + self.row_pos = [0, 8, 8] + + try: + self.handle_cmd(self.mosi_bytes, self.miso_bytes) + except: + traceback.print_exc() + + self.mosi_bytes, self.miso_bytes = [], [] + self.mosi_bits, self.miso_bits = [], [] -- 2.30.2