Compare commits

..

No commits in common. "5649eb59b869fc7fd041fc446405f6a44b90080e" and "a20dddb9d0d95a8b1257fd914a22392d7b81b7e6" have entirely different histories.

15 changed files with 1 additions and 499 deletions

108
README.md
View File

@ -1,109 +1,3 @@
# 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.
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 |
### Optional Pins
| CC2500 Pin | Function | Raspberry Pi GPIO Pin | GPIO Number |
|------------|-------------------------------------------|-------------------------------|-------------|
| GDO0 | Interrupt/Data Ready | Pin 12 | GPIO 18 |
| GDO2 | Additional Interrupt/Data Line | Pin 22 | GPIO 25 |
### 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_2](./images/signals/signal2.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 read.py`
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.

Binary file not shown.

View File

@ -1,21 +0,0 @@
{
"IOCFG0" : { "hex":"0x06", "dec":6},
"PKTCTRL0": { "hex":"0x05", "dec":5},
"FSCTRL1" : { "hex":"0x0A", "dec":10},
"FREQ1" : { "hex":"0x20", "dec":32},
"FREQ0" : { "hex":"0x11", "dec":17},
"MDMCFG4" : { "hex":"0x2D", "dec":45},
"MDMCFG3" : { "hex":"0x3B", "dec":59},
"MDMCFG2" : { "hex":"0x73", "dec":115},
"DEVIATN" : { "hex":"0x00", "dec":0},
"MCSM0" : { "hex":"0x18", "dec":24},
"FOCCFG" : { "hex":"0x1D", "dec":29},
"BSCFG" : { "hex":"0x1C", "dec":28},
"AGCCTRL2": { "hex":"0xC7", "dec":199},
"AGCCTRL1": { "hex":"0x00", "dec":0},
"AGCCTRL0": { "hex":"0xB0", "dec":176},
"FREND1" : { "hex":"0xB6", "dec":182},
"FSCAL3" : { "hex":"0xEA", "dec":234},
"FSCAL1" : { "hex":"0x00", "dec":0},
"FSCAL0" : { "hex":"0x11", "dec":17}
}

View File

@ -1,97 +0,0 @@
{
"CONFIG_REGS": {
"IOCFG2": {"hex": "0x00", "dec": 0},
"IOCFG1": {"hex": "0x01", "dec": 1},
"IOCFG0": {"hex": "0x02", "dec": 2},
"FIFOTHR": {"hex": "0x03", "dec": 3},
"SYNC1": {"hex": "0x04", "dec": 4},
"SYNC0": {"hex": "0x05", "dec": 5},
"PKTLEN": {"hex": "0x06", "dec": 6},
"PKTCTRL1": {"hex": "0x07", "dec": 7},
"PKTCTRL0": {"hex": "0x08", "dec": 8},
"ADDR": {"hex": "0x09", "dec": 9},
"CHANNR": {"hex": "0x0A", "dec": 10},
"FSCTRL1": {"hex": "0x0B", "dec": 11},
"FSCTRL0": {"hex": "0x0C", "dec": 12},
"FREQ2": {"hex": "0x0D", "dec": 13},
"FREQ1": {"hex": "0x0E", "dec": 14},
"FREQ0": {"hex": "0x0F", "dec": 15},
"MDMCFG4": {"hex": "0x10", "dec": 16},
"MDMCFG3": {"hex": "0x11", "dec": 17},
"MDMCFG2": {"hex": "0x12", "dec": 18},
"MDMCFG1": {"hex": "0x13", "dec": 19},
"MDMCFG0": {"hex": "0x14", "dec": 20},
"DEVIATN": {"hex": "0x15", "dec": 21},
"MCSM2": {"hex": "0x16", "dec": 22},
"MCSM1": {"hex": "0x17", "dec": 23},
"MCSM0": {"hex": "0x18", "dec": 24},
"FOCCFG": {"hex": "0x19", "dec": 25},
"BSCFG": {"hex": "0x1A", "dec": 26},
"AGCCTRL2": {"hex": "0x1B", "dec": 27},
"AGCCTRL1": {"hex": "0x1C", "dec": 28},
"AGCCTRL0": {"hex": "0x1D", "dec": 29},
"WOREVT1": {"hex": "0x1E", "dec": 30},
"WOREVT0": {"hex": "0x1F", "dec": 31},
"WORCTRL": {"hex": "0x20", "dec": 32},
"FREND1": {"hex": "0x21", "dec": 33},
"FREND0": {"hex": "0x22", "dec": 34},
"FSCAL3": {"hex": "0x23", "dec": 35},
"FSCAL2": {"hex": "0x24", "dec": 36},
"FSCAL1": {"hex": "0x25", "dec": 37},
"FSCAL0": {"hex": "0x26", "dec": 38},
"RCCTRL1": {"hex": "0x27", "dec": 39},
"RCCTRL0": {"hex": "0x28", "dec": 40},
"FSTEST": {"hex": "0x29", "dec": 41},
"PTEST": {"hex": "0x2A", "dec": 42},
"AGCTEST": {"hex": "0x2B", "dec": 43},
"TEST2": {"hex": "0x2C", "dec": 44},
"TEST1": {"hex": "0x2D", "dec": 45},
"TEST0": {"hex": "0x2E", "dec": 46}
},
"STROBES": {
"SRES": {"hex": "0x30", "dec": 48},
"SFSTXON": {"hex": "0x31", "dec": 49},
"SXOFF": {"hex": "0x32", "dec": 50},
"SCAL": {"hex": "0x33", "dec": 51},
"SRX": {"hex": "0x34", "dec": 52},
"STX": {"hex": "0x35", "dec": 53},
"SIDLE": {"hex": "0x36", "dec": 54},
"SAFC": {"hex": "0x37", "dec": 55},
"SWOR": {"hex": "0x38", "dec": 56},
"SPWD": {"hex": "0x39", "dec": 57},
"SFRX": {"hex": "0x3A", "dec": 58},
"SFTX": {"hex": "0x3B", "dec": 59},
"SWORRST": {"hex": "0x3C", "dec": 60},
"SNOP": {"hex": "0x3D", "dec": 61}
},
"STATUS": {
"PARTNUM": {"hex": "0x30", "dec": 48},
"VERSION": {"hex": "0x31", "dec": 49},
"FREQEST": {"hex": "0x32", "dec": 50},
"LQI": {"hex": "0x33", "dec": 51},
"RSSI": {"hex": "0x34", "dec": 52},
"MARCSTATE": {"hex": "0x35", "dec": 53},
"WORTIME1": {"hex": "0x36", "dec": 54},
"WORTIME0": {"hex": "0x37", "dec": 55},
"PKTSTATUS": {"hex": "0x38", "dec": 56},
"VCO_VC_DAC": {"hex": "0x39", "dec": 57},
"TXBYTES": {"hex": "0x3A", "dec": 58},
"RXBYTES": {"hex": "0x3B", "dec": 59},
"NUM_RXBYTES": {"hex": "0x7F", "dec": 127}
},
"MEMORY": {
"PATABLE": {"hex": "0x3E", "dec": 62},
"TXFIFO": {"hex": "0x3F", "dec": 63},
"RXFIFO": {"hex": "0x3F", "dec": 63}
},
"MASKS": {
"LQI_RX": {"hex": "0x01", "dec": 1},
"CRC_OK": {"hex": "0x80", "dec": 128}
},
"ACCESS": {
"WRITE_BURST": {"hex": "0x40", "dec": 64},
"READ_SINGLE": {"hex": "0x80", "dec": 128},
"READ_BURST": {"hex": "0xC0", "dec": 192}
}
}

View File

@ -1,12 +0,0 @@
Handheld Init
2.4312 GHZ (mid)
2.439256 (strong)
2.447 GHZ (strong)
2.4632 GHZ (strong)
2.471 GHZ (weak)
Test Pts
2410000.000
2430000.000
2450000.000
2470000.000

Binary file not shown.

Before

Width:  |  Height:  |  Size: 401 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 930 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 560 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 385 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

167
read.py
View File

@ -1,167 +0,0 @@
import spidev
import time
import json
# Initialize SPI
global spi
global init_data
# global regs_data
global CONFIG_REGS
global STROBES
global STATUS
global MEMORY
global MASKS
global ACCESS
def asb (a):
return int(a,16)
def strobe(command):
"""Send a command strobe to CC2500"""
spi.xfer2([command])
def write_reg(addr, value):
"""Write single byte to a register"""
spi.xfer2([addr, value])
def burst_write(addr, data):
"""Write multiple bytes to FIFO or registers"""
BURST = 0x40
spi.xfer2([addr | BURST] + data)
def burst_read(addr, length):
"""Read multiple bytes"""
READ_SINGLE = ACCESS["READ_SINGLE"]["dec"]
READ_BURST = ACCESS["READ_BURST"]["dec"]
return spi.xfer2([addr | READ_SINGLE | READ_BURST] + [0x00] * length)
def read_fifo():
# Burst read RX FIFO
READ_BURST = ACCESS["READ_BURST"]["dec"]
RXFIFO = MEMORY["RXFIFI"]["dec"]
fifo = spi.xfer2([RXFIFO | READ_BURST] + [0x00]*64) # Max 64 bytes
return fifo[1:]
def read_register(addr):
READ_SINGLE = ACCESS["READ_SINGLE"]["dec"]
# Send address | 0x80 (read), then 0x00 dummy to clock in response
response = spi.xfer2([READ_SINGLE | addr, 0x00])
return response[1]
def send_packet(data):
# Flush TX FIFO
strobe(0x3B) # SFTX
# Load data to TX FIFO (fixed length)
burst_write(0x3F, data) # 0x3F = TX FIFO
# Strobe STX to transmit
strobe(0x35) # STX
print(f"Sent: {data}")
def receive_packet():
# Flush RX FIFO
SFRX = STROBES["SFRX"]["dec"]
strobe(SFRX) # SFRX
SRX = STROBES["SRX"]["dec"]
# Go into RX mode
strobe(SRX) # SRX
# Wait for data (use GDO0 in real app)
time.sleep(0.5)
# Check RXBYTES
RXBYTES = STATUS["RXBYTES"]["dec"]
rx_bytes = read_register(RXBYTES) & 0x7F
if rx_bytes == 0:
print("No data received.")
return None
# Read data
RXFIFO = MEMORY["RXFIFO"]["dec"]
data = read_fifo() # 0x3F = RX FIFO
print(f"Received: {data}")
return data
def test_read_write_reg():
FIFOTHR = CONFIG_REGS["FIFOTHR"]["dec"]
print("reg ", hex(FIFOTHR))
initial_val = read_register(FIFOTHR)
test_value = initial_val + 1
write_reg(FIFOTHR, test_value)
check = read_register(FIFOTHR)
write_reg(FIFOTHR, initial_val)
print("initial value ", initial_val)
print("test value ", test_value)
print("check ",check)
return check == test_value
def disable_crc():
spi.xfer2([CONFIG_REGS["PKTCTRL0"]["dec"], 0x01])
def dump_reg():
for reg_name_group, regs in regs_data.items():
for reg_name, reg_loc in regs.items():
addr = reg_loc['dec']
reg_value = read_register(addr)
print(reg_name + ":"+str(reg_value))
if __name__ == "__main__":
spi = spidev.SpiDev()
spi.open(0, 0) # Bus 0, CE0 (Pin 24)
spi.max_speed_hz = 2000000 # Safe start speed
spi.mode = 0b00 # SPI mode 0
with open("cc2500_init.json", "r") as f:
init_data = json.load(f)
with open("cc2500_regs.json", "r") as f:
regs_data = json.load(f)
CONFIG_REGS = regs_data["CONFIG_REGS"]
STROBES = regs_data["STROBES"]
STATUS = regs_data["STATUS"]
MEMORY = regs_data["MEMORY"]
MASKS = regs_data["MASKS"]
ACCESS = regs_data["ACCESS"]
SRES = STROBES["SRES"]["dec"]
SNOP = STROBES["SNOP"]["dec"]
VERSION = STATUS["VERSION"]["dec"]
print("Sending SRES (reset)...")
spi.xfer2([SRES])
time.sleep(0.1)
print("Sending SNOP (no-op)...")
status = spi.xfer2([SNOP])[0]
print(f"Status byte: 0x{status:02X}")
print("Reading VERSION register...")
version = read_register(VERSION)
print(f"CC2500 VERSION register: 0x{version:02X}")
# dump_reg()
for reg_name, values in init_data.items():
addr_dec = regs_data["CONFIG_REGS"][reg_name]["dec"]
addr_hex = regs_data["CONFIG_REGS"][reg_name]["hex"]
value_hex = values["hex"]
value_dec = values["dec"]
# print(addr)
# print(values["hex"])
# print("writing "+str(addr_hex) + " to "+str(addr_dec))
write_reg(addr_dec, value_dec)
disable_crc()
time.sleep(2)
# dump_reg()
print("Read + Write test ", test_read_write_reg())
try:
while True:
receive_packet()
time.sleep(1)
finally:
spi.close()

View File

@ -1,14 +0,0 @@
<?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>

50
test.py
View File

@ -1,50 +0,0 @@
import spidev
import time
cc2500_config = {
0x00: 0x0B, # IOCFG2
0x02: 0x06, # IOCFG0
0x03: 0x00, # FIFOTHR
0x07: 0x08, # FSCTRL1
0x08: 0x00, # FSCTRL0
0x0B: 0x06, # IOCFG0 (repeating correct setting)
0x0C: 0x00, # FIFOTHR
0x0D: 0x5D, # FREQ2
0x0E: 0x93, # FREQ1
0x0F: 0xB1, # FREQ0
0x10: 0x2D, # MDMCFG4
0x11: 0x3B, # MDMCFG3
0x12: 0x73, # MDMCFG2
0x15: 0x00, # DEVIATN
0x18: 0x18, # MCSM0
0x19: 0x1D, # FOCCFG
0x1A: 0x1C, # BSCFG
0x1B: 0xC7, # AGCCTRL2
0x22: 0x11, # FREND0
0x23: 0xE9, # FSCAL3
0x24: 0x2A, # FSCAL2
0x25: 0x00, # FSCAL1
0x26: 0x1F, # FSCAL0
0x2C: 0x81, # TEST2
0x2D: 0x35, # TEST1
0x2E: 0x09, # TEST0
0x06: 0x00, # ADDR (only once)
0x1E: 0x05, # PKTCTRL0
0x07: 0x00, # CHANNR
}
spi = spidev.SpiDev()
spi.open(0, 0) # Bus 0, CE0
spi.max_speed_hz = 500000 # Adjust if needed
spi.mode = 0 # CC2500 usually works in SPI mode 0
def cc2500_strobe(command):
resp = spi.xfer2([command])
return resp[0]
SRES = 0x30 # Reset strobe command
print("Sending reset to CC2500...")
status = cc2500_strobe(SRES)
print(f"Status byte: 0x{status:02X}")

View File

@ -1,31 +0,0 @@
CC2500 Pins to Raspberry Pi GPIO Pins
GND (CC2500) → Ground (Pin 6 on Raspberry Pi GPIO header)
VCC (CC2500) → 3.3V Power (Pin 1 or Pin 17 on Raspberry Pi GPIO header)
Ensure the CC2500 module operates at 3.3V. Connecting it to 5V may damage it.
SPI Pins:
SI (CC2500) → MOSI (GPIO 10) (Pin 19 on Raspberry Pi GPIO header)
SCK (CC2500) → SCLK (GPIO 11) (Pin 23 on Raspberry Pi GPIO header)
CSN (CC2500) → CE0 (GPIO 8) (Pin 24 on Raspberry Pi GPIO header)
This is the Chip Select line.
SO (CC2500) → MISO (GPIO 9) (Pin 21 on Raspberry Pi GPIO header)
Optional Pins:
GDO0 (CC2500) → Any GPIO Pin (e.g., GPIO 18) (Pin 12 on Raspberry Pi GPIO header)
Use this for interrupt handling or data-ready signals if required.
GDO2 (CC2500) → Any GPIO Pin (e.g., GPIO 25) (Pin 22 on Raspberry Pi GPIO header)
Additional interrupt or data pin.
Power Amplifier Pins (If Applicable):
PA_EN (CC2500) → Any GPIO Pin (Optional, depending on whether you control the amplifier externally or not).
PA_EX (CC2500) → Not Connected (unless needed for a specific configuration)