Merge pull request 'dev' (#1) from dev into main
Reviewed-on: https://git.dwestgate.us/david/cat-tracker/pulls/1
This commit is contained in:
commit
f89da50a5d
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
saved_packets/*
|
||||||
|
src/__pycache__
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"python.analysis.stubPath": "./typings"
|
||||||
|
}
|
106
README.md
106
README.md
@ -1,3 +1,109 @@
|
|||||||
# cat-tracker
|
# 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.
|
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
|
||||||
|

|
||||||
|
|
||||||
|
### 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 0
|
||||||
|
2.431258000 GHz | 2431.258000 MHz| 2431258.000 kHz
|
||||||
|

|
||||||
|
#### Signal 1
|
||||||
|
2.447257000 GHz | 2447.257000 MHz | 2447257.000 kHz
|
||||||
|

|
||||||
|
#### Signal 2
|
||||||
|
2.450225000 GHz | 2450.225000 MHz | 2450225.000 kHz
|
||||||
|

|
||||||
|
#### Signal 3
|
||||||
|
2.471275000 GHz | 2471.275000 MHz | 2471275.000 kHz
|
||||||
|

|
||||||
|
|
||||||
|
#### 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`
|
BIN
cc2500.pdf
Normal file
BIN
cc2500.pdf
Normal file
Binary file not shown.
BIN
images/signals/signal0.png
Normal file
BIN
images/signals/signal0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 401 KiB |
BIN
images/signals/signal00.png
Normal file
BIN
images/signals/signal00.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 410 KiB |
BIN
images/signals/signal1.png
Normal file
BIN
images/signals/signal1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 930 KiB |
BIN
images/signals/signal2.png
Normal file
BIN
images/signals/signal2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 560 KiB |
BIN
images/signals/signal3.png
Normal file
BIN
images/signals/signal3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 385 KiB |
BIN
images/smart_rf.png
Normal file
BIN
images/smart_rf.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 96 KiB |
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
110
src/common.py
Normal file
110
src/common.py
Normal file
@ -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
|
43
src/init_regs_value.py
Normal file
43
src/init_regs_value.py
Normal file
@ -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
|
||||||
|
}
|
87
src/main.py
Normal file
87
src/main.py
Normal file
@ -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)
|
93
src/receive.py
Normal file
93
src/receive.py
Normal file
@ -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)
|
197
src/regs_addr.py
Normal file
197
src/regs_addr.py
Normal file
@ -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},
|
||||||
|
}
|
21
src/transmit.py
Normal file
21
src/transmit.py
Normal file
@ -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)
|
72
src/util.py
Normal file
72
src/util.py
Normal file
@ -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)
|
14
srfexp_json.xml
Normal file
14
srfexp_json.xml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<registerexporttemplate>
|
||||||
|
<name><![CDATA[JSON]]></name>
|
||||||
|
<commentStart><![CDATA[//]]></commentStart>
|
||||||
|
<commentEnd><![CDATA[]]></commentEnd>
|
||||||
|
<paramSummary><![CDATA[0]]></paramSummary>
|
||||||
|
<paTable><![CDATA[0]]></paTable>
|
||||||
|
<header><![CDATA[{
|
||||||
|
]]></header>
|
||||||
|
<registers><![CDATA[ "@RN@"@<<@: @<<@{ "hex":"0x@VH@", "dec":@VD@},]]></registers>
|
||||||
|
<footer><![CDATA[}]]></footer>
|
||||||
|
<filename><![CDATA[@CHIPID@_simple_link_reg_config.json]]></filename>
|
||||||
|
<devices><![CDATA[]]></devices>
|
||||||
|
</registerexporttemplate>
|
0
tx_packets/activate.bin
Normal file
0
tx_packets/activate.bin
Normal file
6
typings/spidev/__init__.pyi
Normal file
6
typings/spidev/__init__.pyi
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user