diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fed3f81 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +saved_packets/* +src/__pycache__ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..8ed7c59 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.analysis.stubPath": "./typings" +} \ No newline at end of file diff --git a/README.md b/README.md index 3c4c1c2..c1dbca9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,109 @@ # cat-tracker -A multi-function application designed to run on a pi node. When used in a mesh network, this app will help track your cat with a CC2500 based radio collar, using complementary CC2500 based radio modules interfaced with your Pi. \ No newline at end of file +A multi-function application designed to run on a pi node. When used in a mesh network, this app will help track your cat with a CC2500 based radio collar, using complementary CC2500 based radio modules interfaced with your Pi. + +The implementation here uses CC2500 register values which intend to mimic my [Girafus M07A](https://www.girafus.com/en-en/collections/peilsender/products/ersatzteil-basismodul-fur-girafus%C2%AE-pro-track-tor-katzen-hunde-haustier-kleintier-sucher) unit. This unit is paired with my associated [tracker](https://www.girafus.com/en-en/collections/peilsender/products/extra-sender-tag-fur-den-girafus%C2%AE-pro-track-tor-haustier-hund-katze-kleintier-finder-sucher-ortung) + +## Links +* [Arduion Forum Port](https://forum.arduino.cc/t/replicating-a-signal-from-a-locating-device-2-4ghz-to-locate-tag/321993/2) +* [cc2500 data sheet](./cc2500.pdf) +* [C Code transciever example](https://github.com/alexbirkett/cc2500-raspberry-pi) + +## CC2500 to Raspberry Pi GPIO Pin Mapping + +| CC2500 Pin | Function Description | Raspberry Pi GPIO Pin | GPIO Number | +|------------|------------------------------------------------------|-------------------------------|-------------| +| GND | Ground | Pin 6 | - | +| VCC | 3.3V Power (⚠️ **Do not use 5V**) | Pin 1 or Pin 17 | - | + +### SPI Connections + +| CC2500 Pin | Function | Raspberry Pi GPIO Pin | GPIO Number | +|------------|----------|-------------------------------|-------------| +| SI | MOSI | Pin 19 | GPIO 10 | +| SCK | SCLK | Pin 23 | GPIO 11 | +| CSN | CE0 (Chip Select) | Pin 24 | GPIO 8 | +| SO | MISO | Pin 21 | GPIO 9 | + +### Interrupt Pins + +| CC2500 Pin | Function | Raspberry Pi GPIO Pin | GPIO Number | +|------------|-------------------------------------------|-------------------------------|-------------| +| GDO0 | Interrupt/Data Ready | Pin 11 | GPIO 17 | +| GDO2 | Additional Interrupt/Data Line | Pin 23 | GPIO 27 | + +### Power Amplifier Pins (If Applicable) + +| CC2500 Pin | Function | Raspberry Pi GPIO Pin | Notes | +|------------|-------------------------------------------|-------------------------------|----------------------------------------| +| PA_EN | Control Power Amplifier (Optional) | Any GPIO Pin | Connect if amplifier control is needed | +| PA_EX | Reserved | Not Connected | Only connect if required | + +## Reverse Engineering the Registers + +### SmartRF Studio + +#### Install +[SmartRF Studio](https://www.ti.com/tool/SMARTRFTM-STUDIO#downloads) will automatically generate a configuration for the CC2500 Radio based on some RF parameters +* A free registration and login is required to download this app from Texas Instruments +* The app seems to run well via `wine` on linux. I chose to install it on an actual Windows OS, then copy the binaries. + +#### Copy Register JSON template +When this is installed, copy the code export json file to help export the initial register configuration in a JSON format + +```bash +cp srfexp_json.xml "~/Documents/Texas Instruments/SmartRF Studio v7/codeexport/." +# Example copy command for linux+wine +``` + +#### Understanding necessary RF Parameters +In SmartRF Studio, open the CC2500 Device Controller under the 2.4 GHZ list. + +At the time of writing, it is believed this device uses 250 kBaud MSK encoding. We will choose this option. + +Next we need to figure out the Base Frequency, Channel numbers, and Channel Spacing +![smart_rf](./images/smart_rf.png) + +### Singal Capture +I have captured the initial signal emitted by the handheld unit when it is first powered on. To do this, I have used a HackRF SDR + gqrx application on linux. + +The known frequency range is 2400 – 2483.5 MHz, and my SDR has a 20 MHZ bandwidth. I choose sweep for signals by tuning the following frequencies +| Sweep # | Center Frequency | Center Frequency (kHz) | Approx. Coverage (MHz) | Approx. Coverage (kHz) | +|---------|------------------|-------------------------|-------------------------|-----------------------------| +| 1 | 2410 MHz | 2,410,000 kHz | 2400 – 2420 MHz | 2,400,000 – 2,420,000 kHz | +| 2 | 2430 MHz | 2,430,000 kHz | 2420 – 2440 MHz | 2,420,000 – 2,440,000 kHz | +| 3 | 2450 MHz | 2,450,000 kHz | 2440 – 2460 MHz | 2,440,000 – 2,460,000 kHz | +| 4 | 2470 MHz | 2,470,000 kHz | 2460 – 2480 MHz | 2,460,000 – 2,480,000 kHz | +| 5 | 2483.5 MHz | 2,483,500 kHz | 2473.5 – 2483.5 MHz | 2,473,500 – 2,483,500 kHz | + +#### Signal 00 +2.423257000 GHz | 2423.257000 MHz | 2423257.000 kHz | +![signal_00](./images/signals/signal00.png) +#### Signal 0 +2.431258000 GHz | 2431.258000 MHz| 2431258.000 kHz +![signal_0](./images/signals/signal0.png) +#### Signal 1 +2.447257000 GHz | 2447.257000 MHz | 2447257.000 kHz +![signal_1](./images/signals/signal1.png) +#### Signal 2 +2.450225000 GHz | 2450.225000 MHz | 2450225.000 kHz +![signal_2](./images/signals/signal2.png) +#### Signal 3 +2.471275000 GHz | 2471.275000 MHz | 2471275.000 kHz +![signal_3](./images/signals/signal3.png) + +#### Signal Summary + +* Base frequency: ? MHz +* Date Rate ? Baud +* Channel spacing: ? kHz +* Signal channels: +* Signal 00 → Channel ? +* Signal 0 → Channel ? +* Signal 1 → Channel ? +* Signal 2 → Channel ? +* Signal 3 → Channel ? + + +## Run +`python3 -m src.main` \ No newline at end of file diff --git a/cc2500.pdf b/cc2500.pdf new file mode 100644 index 0000000..ae83236 Binary files /dev/null and b/cc2500.pdf differ diff --git a/images/signals/signal0.png b/images/signals/signal0.png new file mode 100644 index 0000000..3a0ece2 Binary files /dev/null and b/images/signals/signal0.png differ diff --git a/images/signals/signal00.png b/images/signals/signal00.png new file mode 100644 index 0000000..26df8be Binary files /dev/null and b/images/signals/signal00.png differ diff --git a/images/signals/signal1.png b/images/signals/signal1.png new file mode 100644 index 0000000..65f7494 Binary files /dev/null and b/images/signals/signal1.png differ diff --git a/images/signals/signal2.png b/images/signals/signal2.png new file mode 100644 index 0000000..d1213d7 Binary files /dev/null and b/images/signals/signal2.png differ diff --git a/images/signals/signal3.png b/images/signals/signal3.png new file mode 100644 index 0000000..ea414dd Binary files /dev/null and b/images/signals/signal3.png differ diff --git a/images/smart_rf.png b/images/smart_rf.png new file mode 100644 index 0000000..dfe8a88 Binary files /dev/null and b/images/smart_rf.png differ diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/common.py b/src/common.py new file mode 100644 index 0000000..12b9c19 --- /dev/null +++ b/src/common.py @@ -0,0 +1,110 @@ +from spidev import SpiDev +import RPi.GPIO as GPIO +from .util import sleep, get_addr +from .init_regs_value import init_regs_value +from .regs_addr import regs_addr + +CONFIG_REGS = regs_addr["CONFIG_REGS"] +STROBES = regs_addr["STROBES"] +STATUS = regs_addr["STATUS"] +MEMORY = regs_addr["MEMORY"] +MASKS = regs_addr["MASKS"] +ACCESS = regs_addr["ACCESS"] +SRES = STROBES["SRES"] +SNOP = STROBES["SNOP"] +VERSION = STATUS["VERSION"] +MARCSTATE = STATUS["MARCSTATE"] + + +def write_reg(spi: SpiDev, addr: int, value: int): + """Write single byte to a register""" + spi.xfer2([addr, value]) + sleep(0.1) + + +def read_register(spi: SpiDev, addr: int): + READ_SINGLE = get_addr("READ_SINGLE") + # Send address | 0x80 (read), then 0x00 dummy to clock in response + response = spi.xfer2([READ_SINGLE | addr, 0x00]) + # sleep(0.1) + return response[1] + + +def write_burst(spi: SpiDev, addr: int, data): + """Write multiple bytes using burst write""" + WRITE_BURST = get_addr("WRITE_BURST") + tbuf = [addr | WRITE_BURST] + data + spi.xfer2(tbuf) + sleep(0.1) + + +def read_burst(spi: SpiDev, addr: int, length): + """Read multiple bytes using burst read""" + READ_BURST = get_addr("READ_BURST") + rbuf = [addr | READ_BURST] + [0x00] * length + response = spi.xfer2(rbuf) + sleep(0.1) + return response[1:] # Skip status byte + + +def strobe(spi: SpiDev, command: int): + """Send a command strobe to CC2500""" + spi.xfer2([command]) + sleep(0.1) + + +def init_cc_2500(spi: SpiDev): + for reg_name, value in init_regs_value.items(): + addr = get_addr(reg_name) + write_reg(spi, addr, value) + + +def setup_spi(): + spi = SpiDev() + spi.open(0, 0) # Bus 0, CE0 (Pin 24) + spi.max_speed_hz = 100_000 # Safe start speed + spi.mode = 0b00 # SPI mode 0 + return spi + + +def setup_gpio(GDO0_PIN=17, GDO2_PIN=27): + GPIO.setmode(GPIO.BCM) + GPIO.setup(GDO0_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) + GPIO.setup(GDO2_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) + sleep(0.1) + + +def reset(spi: SpiDev): + # print("Sending SRES (reset)...") + spi.xfer2([SRES]) + sleep(0.5) + + # print("Sending SNOP (no-op)...") + status = spi.xfer2([SNOP])[0] + print(f"Status byte: 0x{status:02X}") + + # print("Reading MARCSTATE...") + marc = spi.xfer2([MARCSTATE | 0x80, 0x00])[0] + print(f"Marcstate byte: 0x{marc:02X}") + + print("Reading VERSION register...") + version = read_register(spi, VERSION) + print(f"CC2500 VERSION register: 0x{version:02X}") + # if version != 0x2F: + # raise Exception("Expected Version was 0x2F!! Quitting") + + +def test_read_write_reg(spi: SpiDev, dbg=False): + FIFOTHR = get_addr("FIFOTHR") + initial_val = read_register(spi, FIFOTHR) + test_value = initial_val + 1 + write_reg(spi, FIFOTHR, test_value) + check = read_register(spi, FIFOTHR) + write_reg(spi, FIFOTHR, initial_val) + if (check != test_value) or dbg: + print("initial value ", initial_val) + print("test value ", test_value) + print("check ", check) + if not dbg: + raise Exception("Test Read+Write failed") + return check == test_value diff --git a/src/init_regs_value.py b/src/init_regs_value.py new file mode 100644 index 0000000..a6b0336 --- /dev/null +++ b/src/init_regs_value.py @@ -0,0 +1,43 @@ +# Address Config = No address check +# Base Frequency = 2447.256836 +# CRC Autoflush = false +# CRC Enable = true +# Carrier Frequency = 2447.256836 +# Channel Number = 0 +# Channel Spacing = 199.951172 +# Data Format = Normal mode +# Data Rate = 249.939 +# Device Address = 0 +# Manchester Enable = false +# Modulated = true +# Modulation Format = MSK +# Packet Length = 255 +# Packet Length Mode = Variable packet length mode. Packet length configured by the first byte after sync word +# Phase Transition Time = 0 +# Preamble Count = 4 +# RX Filter BW = 541.666667 +# Sync Word Qualifier Mode = 30/32 sync word bits detected +# TX Power = 0 +# Whitening = false +init_regs_value = { + "IOCFG0" : 0x06, # 6 - GDO0OUTPUT PIN CONFIGURATION + "PKTCTRL0": 0b00000001,# 5 - PACKET AUTOMATION CONTROL + "PKTCTRL1": 0b00000100,# 4 - PACKET AUTOMATION CONTROL + "FSCTRL1" : 0x12, # 18 - FREQUENCY SYNTHESIZER CONTROL + "FREQ1" : 0x20, # 32 - FREQUENCY CONTROL WORD, MIDDLE BYTE + "FREQ0" : 0x11, # 17 - FREQUENCY CONTROL WORD, LOW BYTE + "MDMCFG4" : 0x2D, # 45 - MODEM CONFIGURATION + "MDMCFG3" : 0x3B, # 59 - MODEM CONFIGURATION + "MDMCFG2" : 0xF3, # 243 - MODEM CONFIGURATION + "DEVIATN" : 0x00, # 0 - MODEM DEVIATION SETTING + "MCSM0" : 0x18, # 24 - MAIN RADIO CONTROL STATE MACHINE CONFIGURATION + "FOCCFG" : 0x1D, # 29 - FREQUENCY OFFSET COMPENSATION CONFIGURATION + "BSCFG" : 0x1C, # 28 - BIT SYNCHRONIZATION CONFIGURATION + "AGCCTRL2": 0xC7, # 199 - AGC CONTROL + "AGCCTRL1": 0x00, # 0 - AGC CONTROL + "AGCCTRL0": 0xB0, # 176 - AGC CONTROL + "FREND1" : 0xB6, # 182 - FRONT END RX CONFIGURATION + "FSCAL3" : 0xEA, # 234 - FREQUENCY SYNTHESIZER CALIBRATION + "FSCAL1" : 0x00, # 0 - FREQUENCY SYNTHESIZER CALIBRATION + "FSCAL0" : 0x11, # 17 - FREQUENCY SYNTHESIZER CALIBRATION +} \ No newline at end of file diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..47983ca --- /dev/null +++ b/src/main.py @@ -0,0 +1,87 @@ +import spidev +from .util import print_gdo_state, sleep, get_addr, dump_regs, debug, GDO0_PIN, GDO2_PIN +from .receive import rx_data_rf +from .transmit import transmit_packet +from .common import ( + reset, + setup_spi, + setup_gpio, + read_register, + test_read_write_reg, + init_cc_2500, + write_reg, + SRES, + SNOP, + MARCSTATE, + VERSION, +) + + +def menu(): + print("\nMenu") + print("1: Read Reg by name") + print("2: Write reg hex value by name") + print("3: Dump registers") + print("4: rx_data_rf") + print("5: transmit_packet") + print("6: Run Read+Write test") + print("7: Print GDO state") + print("0: Quit") + + +if __name__ == "__main__": + setup_gpio(GDO0_PIN, GDO2_PIN) + print_gdo_state(GDO0_PIN, GDO2_PIN) + spi = setup_spi() + reg_name = "" + reg_hex_val = "" + try: + + reset(spi) + test_read_write_reg(spi) + init_cc_2500(spi) + stop = False + while not stop: + menu() + cmd = int(input("$: ")) + if cmd == 0: + stop = True + elif cmd == 1: + reg_name = input("Register name: ") + addr = get_addr(reg_name) + value = read_register(spi, addr) + print( + "Address: " + + hex(addr) + + ", Value: " + + hex(value) + + " == " + + str(value) + ) + elif cmd == 2: + reg_name = input("Register name: ") + addr = get_addr(reg_name) + value = int(input("New Register value (hex): "), 16) + write_reg(spi, addr, value) + sleep(0.1) + value_check = read_register(spi, addr) + print("Updated Value: " + hex(value) + " == " + str(value)) + elif cmd == 3: + dump_regs(spi, True) + elif cmd == 4: + while True: + rx_data_rf(spi) + elif cmd == 5: + transmit_packet(spi) + elif cmd == 6: + res = test_read_write_reg(spi, True) + print("Test result : " + str(res)) + elif cmd == 7: + print_gdo_state(GDO0_PIN, GDO2_PIN) + else: + print("Invalid command") + finally: + print("Closing SPI...") + sleep(0.1) + spi.close() + sleep(0.1) diff --git a/src/receive.py b/src/receive.py new file mode 100644 index 0000000..56300e5 --- /dev/null +++ b/src/receive.py @@ -0,0 +1,93 @@ +from datetime import datetime +from pathlib import Path + +from spidev import SpiDev +from .util import sleep, get_addr, digital_read, GDO0_PIN, GDO2_PIN, debug, delay +from .common import strobe, read_register + +SIDLE = get_addr("SIDLE") +SFRX = get_addr("SFRX") +SRX = get_addr("SRX") +RXFIFO = get_addr("RXFIFO") +RSSI = get_addr("RSSI") + + +def print_packet(spi: SpiDev, packet_length): + for i in range(packet_length): + print(f", byte: {0}: 0x{1}", i, read_register(spi, RXFIFO)) + + +# Save packet to a timestamped binary file +def save_packet(packet: list[int]): + # Create directory to store packets if it doesn't exist + packet_dir = Path("saved_packets") + packet_dir.mkdir(parents=True, exist_ok=True) + + # Create timestamped filename + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + filename = packet_dir / f"packet_{timestamp}.bin" + + # Save packet as binary data + with open(filename, "wb") as f: + f.write(bytearray(packet)) + + print(f"Packet saved to {filename}") + + +def get_rssi_from_pkt(packet: list[int]): + if len(packet) < 2: + raise ValueError("Packet too short to contain RSSI and status bytes.") + rssi_raw = packet[-2] # Second-to-last byte is RSSI + return rssi_raw + + +def get_rssi_from_reg(spi: SpiDev): + return read_register(spi, RSSI) + + +def get_signal_strength_rssi_raw(rssi_raw: int) -> float: + if rssi_raw >= 128: + rssi_dec = rssi_raw - 256 + else: + rssi_dec = rssi_raw + return rssi_dec / 2.0 - 74 # According to CC2500 datasheet + + +def flush_rx(spi: SpiDev): + # Make sure that the radio is in IDLE state before flushing the FIFO + # (Unless RXOFF_MODE has been changed, the radio should be in IDLE state at this point) + delay(10) + strobe(spi, SIDLE) + delay(10) + # Flush RX FIFO + strobe(spi, SFRX) + delay(10) + + +def rx_data_rf(spi: SpiDev): + strobe(spi, SRX) + gdo2_state = False + count = 0 + strength = 0 + while gdo2_state == False: + gdo2_state = digital_read(GDO2_PIN) + delay(1) + count = count + 1 + if count > 1000: + flush_rx(spi) + # print("ERR NO DATA") + return + while gdo2_state == True: + gdo2_state = digital_read(GDO2_PIN) + delay(100) + packet_length: int = read_register(spi, RXFIFO) + packet: list[int] = [read_register(spi, RXFIFO) for _ in range(packet_length)] + rssi_raw = get_rssi_from_pkt(packet) + strength = get_signal_strength_rssi_raw(rssi_raw) + print("Length: {0} bytes\t Signal: {1} dBm".format(packet_length, strength)) + save_packet(packet) + # Make sure that the radio is in IDLE state before flushing the FIFO + # (Unless RXOFF_MODE has been changed, the radio should be in IDLE state at this point) + strobe(spi, SIDLE) + # Flush RX FIFO + strobe(spi, SFRX) diff --git a/src/regs_addr.py b/src/regs_addr.py new file mode 100644 index 0000000..64f371e --- /dev/null +++ b/src/regs_addr.py @@ -0,0 +1,197 @@ +from typing import TypedDict + + +class ConfigRegs(TypedDict): + IOCFG2: int + IOCFG1: int + IOCFG0: int + FIFOTHR: int + SYNC1: int + SYNC0: int + PKTLEN: int + PKTCTRL1: int + PKTCTRL0: int + ADDR: int + CHANNR: int + FSCTRL1: int + FSCTRL0: int + FREQ2: int + FREQ1: int + FREQ0: int + MDMCFG4: int + MDMCFG3: int + MDMCFG2: int + MDMCFG1: int + MDMCFG0: int + DEVIATN: int + MCSM2: int + MCSM1: int + MCSM0: int + FOCCFG: int + BSCFG: int + AGCCTRL2: int + AGCCTRL1: int + AGCCTRL0: int + WOREVT1: int + WOREVT0: int + WORCTRL: int + FREND1: int + FREND0: int + FSCAL3: int + FSCAL2: int + FSCAL1: int + FSCAL0: int + RCCTRL1: int + RCCTRL0: int + FSTEST: int + PTEST: int + AGCTEST: int + TEST2: int + TEST1: int + TEST0: int + + +class Strobes(TypedDict): + SRES: int + SFSTXON: int + SXOFF: int + SCAL: int + SRX: int + STX: int + SIDLE: int + SAFC: int + SWOR: int + SPWD: int + SFRX: int + SFTX: int + SWORRST: int + SNOP: int + + +class Status(TypedDict): + PARTNUM: int + VERSION: int + FREQEST: int + LQI: int + RSSI: int + MARCSTATE: int + WORTIME1: int + WORTIME0: int + PKTSTATUS: int + VCO_VC_DAC: int + TXBYTES: int + RXBYTES: int + NUM_RXBYTES: int + + +class Memory(TypedDict): + PATABLE: int + TXFIFO: int + RXFIFO: int + + +class Masks(TypedDict): + LQI_RX: int + CRC_OK: int + + +class Access(TypedDict): + WRITE_BURST: int + READ_SINGLE: int + READ_BURST: int + + +class RegsAddr(TypedDict): + CONFIG_REGS: ConfigRegs + STROBES: Strobes + STATUS: Status + MEMORY: Memory + MASKS: Masks + ACCESS: Access + + +regs_addr: RegsAddr = { + "CONFIG_REGS": { + "IOCFG2": 0x00, + "IOCFG1": 0x01, + "IOCFG0": 0x02, + "FIFOTHR": 0x03, + "SYNC1": 0x04, + "SYNC0": 0x05, + "PKTLEN": 0x06, + "PKTCTRL1": 0x07, + "PKTCTRL0": 0x08, + "ADDR": 0x09, + "CHANNR": 0x0A, + "FSCTRL1": 0x0B, + "FSCTRL0": 0x0C, + "FREQ2": 0x0D, + "FREQ1": 0x0E, + "FREQ0": 0x0F, + "MDMCFG4": 0x10, + "MDMCFG3": 0x11, + "MDMCFG2": 0x12, + "MDMCFG1": 0x13, + "MDMCFG0": 0x14, + "DEVIATN": 0x15, + "MCSM2": 0x16, + "MCSM1": 0x17, + "MCSM0": 0x18, + "FOCCFG": 0x19, + "BSCFG": 0x1A, + "AGCCTRL2": 0x1B, + "AGCCTRL1": 0x1C, + "AGCCTRL0": 0x1D, + "WOREVT1": 0x1E, + "WOREVT0": 0x1F, + "WORCTRL": 0x20, + "FREND1": 0x21, + "FREND0": 0x22, + "FSCAL3": 0x23, + "FSCAL2": 0x24, + "FSCAL1": 0x25, + "FSCAL0": 0x26, + "RCCTRL1": 0x27, + "RCCTRL0": 0x28, + "FSTEST": 0x29, + "PTEST": 0x2A, + "AGCTEST": 0x2B, + "TEST2": 0x2C, + "TEST1": 0x2D, + "TEST0": 0x2E, + }, + "STROBES": { + "SRES": 0x30, + "SFSTXON": 0x31, + "SXOFF": 0x32, + "SCAL": 0x33, + "SRX": 0x34, + "STX": 0x35, + "SIDLE": 0x36, + "SAFC": 0x37, + "SWOR": 0x38, + "SPWD": 0x39, + "SFRX": 0x3A, + "SFTX": 0x3B, + "SWORRST": 0x3C, + "SNOP": 0x3D, + }, + "STATUS": { + "PARTNUM": 0x30, + "VERSION": 0x31, + "FREQEST": 0x32, + "LQI": 0x33, + "RSSI": 0x34, + "MARCSTATE": 0x35, + "WORTIME1": 0x36, + "WORTIME0": 0x37, + "PKTSTATUS": 0x38, + "VCO_VC_DAC": 0x39, + "TXBYTES": 0x3A, + "RXBYTES": 0x3B, + "NUM_RXBYTES": 0x7F, + }, + "MEMORY": {"PATABLE": 0x3E, "TXFIFO": 0x3F, "RXFIFO": 0x3F}, + "MASKS": {"LQI_RX": 0x01, "CRC_OK": 0x80}, + "ACCESS": {"WRITE_BURST": 0x40, "READ_SINGLE": 0x80, "READ_BURST": 0xC0}, +} diff --git a/src/transmit.py b/src/transmit.py new file mode 100644 index 0000000..8748476 --- /dev/null +++ b/src/transmit.py @@ -0,0 +1,21 @@ +from .common import strobe, write_burst +from .util import get_addr + + +SIDLE = get_addr("SIDLE") +SFTX = get_addr("SFTX") +STX = get_addr("STX") +WRITE_BURST = get_addr("WRITE_BURST") +TXFIFO_BURST = 0x7F + + +def transmit_packet(spi): + strobe(spi, SIDLE) + strobe(spi, SFTX) + data = [0] * 5 + data[0] = 5 + data[1] = 1 + data[2] = 2 + data[3] = 3 + data[4] = 4 + write_burst(spi, TXFIFO_BURST, data) diff --git a/src/util.py b/src/util.py new file mode 100644 index 0000000..cb95bc2 --- /dev/null +++ b/src/util.py @@ -0,0 +1,72 @@ +from spidev import SpiDev +from .regs_addr import regs_addr +import RPi.GPIO as GPIO +import time + +debug = True +GDO0_PIN = 17 +GDO2_PIN = 27 + + +def rr(spi: SpiDev, addr): + READ_SINGLE = get_addr("READ_SINGLE") + # Send address | 0x80 (read), then 0x00 dummy to clock in response + response = spi.xfer2([READ_SINGLE | addr, 0x00]) + sleep(0.1) + return response[1] + + +def print_gdo_state(GDO0_PIN=17, GDO2_PIN=27): + gdo0 = GPIO.input(GDO0_PIN) # Reads 1 or 0 + gdo2 = GPIO.input(GDO2_PIN) + print(f"GDO0 (GPIO{GDO0_PIN}): {gdo0}, GDO2 (GPIO{GDO2_PIN}): {gdo2}") + sleep(0.1) + + +def digital_read(GDO_PIN: int): + return GPIO.input(GDO_PIN) + + +def get_addr(name: str): + addr = 0x00 + stop = False + for reg_type, reg_data in regs_addr.items(): + for reg_name, reg_addr in reg_data.items(): + if reg_name == name: + stop = True + addr = int(reg_addr) + if stop == False: + raise Exception("Failed to find address for " + name) + return addr + + +def dump_regs(spi: SpiDev, cfgonly=False): + if cfgonly: + for reg_name, reg_addr in regs_addr["CONFIG_REGS"].items(): + name: str = reg_name + value = rr(spi, reg_addr) + print((name + ":").ljust(15) + hex(value).ljust(4) + "\t" + str(value)) + else: + for reg_type, reg_data in regs_addr.items(): + for reg_name, reg_addr in reg_data.items(): + name: str = reg_name + value = rr(spi, reg_addr) + print((name + ":").ljust(15) + hex(value).ljust(4) + "\t" + str(value)) + + +# def disable_crc(spi): +# spi.xfer2([CONFIG_REGS["PKTCTRL0"], 0x01]) + + +def sleep(t): + return time.sleep(t) + + +def delay(t): + """Delay for t milliseconds.""" + time.sleep(t / 1000.0) + + +def delayMicroseconds(t): + """Delay for t microseconds.""" + time.sleep(t / 1_000_000.0) diff --git a/srfexp_json.xml b/srfexp_json.xml new file mode 100644 index 0000000..8f83de9 --- /dev/null +++ b/srfexp_json.xml @@ -0,0 +1,14 @@ + + + + + + + +
+ + + + +
diff --git a/tx_packets/activate.bin b/tx_packets/activate.bin new file mode 100644 index 0000000..e69de29 diff --git a/typings/spidev/__init__.pyi b/typings/spidev/__init__.pyi new file mode 100644 index 0000000..fbdbaae --- /dev/null +++ b/typings/spidev/__init__.pyi @@ -0,0 +1,6 @@ +class SpiDev: + def open(self, bus: int, device: int) -> None: ... + def close(self) -> None: ... + def xfer2(self, data: list[int]) -> list[int]: ... + max_speed_hz: int + mode: int \ No newline at end of file